diff --git a/packages/automerge-repo/src/DocHandle.ts b/packages/automerge-repo/src/DocHandle.ts index 64db33b62..df502af2d 100644 --- a/packages/automerge-repo/src/DocHandle.ts +++ b/packages/automerge-repo/src/DocHandle.ts @@ -91,24 +91,16 @@ export class DocHandle extends EventEmitter> { states: { idle: { on: { - CREATE: "ready", - FIND: "loading", + BEGIN: "loading", }, }, loading: { on: { REQUEST: "requesting", DOC_READY: "ready", - AWAIT_NETWORK: "awaitingNetwork", }, after: { [delay]: "unavailable" }, }, - awaitingNetwork: { - on: { - DOC_READY: "ready", - NETWORK_READY: "requesting" - }, - }, requesting: { on: { DOC_UNAVAILABLE: "unavailable", @@ -139,7 +131,7 @@ export class DocHandle extends EventEmitter> { // Start the machine, and send a create or find event to get things going this.#machine.start() - this.#machine.send({ type: FIND }) + this.#machine.send({ type: BEGIN }) } // PRIVATE @@ -429,17 +421,6 @@ export class DocHandle extends EventEmitter> { if (this.#state === "loading") this.#machine.send({ type: REQUEST }) } - /** @hidden */ - awaitNetwork() { - if (this.#state === "loading") this.#machine.send({ type: AWAIT_NETWORK }) - } - - /** @hidden */ - networkReady() { - if (this.#state === "awaitingNetwork") - this.#machine.send({ type: NETWORK_READY }) - } - /** Called by the repo when the document is deleted. */ delete() { this.#machine.send({ type: DELETE }) @@ -554,8 +535,6 @@ export const HandleState = { IDLE: "idle", /** We are waiting for storage to finish loading */ LOADING: "loading", - /** We are waiting for the network to be come ready */ - AWAITING_NETWORK: "awaitingNetwork", /** We are waiting for someone in the network to respond to a sync request */ REQUESTING: "requesting", /** The document is available */ @@ -567,15 +546,8 @@ export const HandleState = { } as const export type HandleState = (typeof HandleState)[keyof typeof HandleState] -export const { - IDLE, - LOADING, - AWAITING_NETWORK, - REQUESTING, - READY, - DELETED, - UNAVAILABLE, -} = HandleState +export const { IDLE, LOADING, REQUESTING, READY, DELETED, UNAVAILABLE } = + HandleState // context @@ -588,7 +560,7 @@ interface DocHandleContext { /** These are the (internal) events that can be sent to the state machine */ type DocHandleEvent = - | { type: typeof FIND } + | { type: typeof BEGIN } | { type: typeof REQUEST } | { type: typeof DOC_READY } | { @@ -598,14 +570,10 @@ type DocHandleEvent = | { type: typeof TIMEOUT } | { type: typeof DELETE } | { type: typeof DOC_UNAVAILABLE } - | { type: typeof AWAIT_NETWORK } - | { type: typeof NETWORK_READY } -const FIND = "FIND" +const BEGIN = "BEGIN" const REQUEST = "REQUEST" const DOC_READY = "DOC_READY" -const AWAIT_NETWORK = "AWAIT_NETWORK" -const NETWORK_READY = "NETWORK_READY" const UPDATE = "UPDATE" const DELETE = "DELETE" const TIMEOUT = "TIMEOUT" diff --git a/packages/automerge-repo/src/Repo.ts b/packages/automerge-repo/src/Repo.ts index 8b561083c..e80d53e16 100644 --- a/packages/automerge-repo/src/Repo.ts +++ b/packages/automerge-repo/src/Repo.ts @@ -24,7 +24,7 @@ import { SyncStatePayload } from "./synchronizer/Synchronizer.js" import type { AnyDocumentId, DocumentId, PeerId } from "./types.js" function randomPeerId() { - return "peer-" + Math.random().toString(36).slice(4) as PeerId + return ("peer-" + Math.random().toString(36).slice(4)) as PeerId } /** A Repo is a collection of documents with networking, syncing, and storage capabilities. */ @@ -88,9 +88,7 @@ export class Repo extends EventEmitter { }: DocHandleEncodedChangePayload) => { void storageSubsystem.saveDoc(handle.documentId, doc) } - handle.on("heads-changed", - throttle(saveFn, this.saveDebounceRate) - ) + handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate)) } handle.on("unavailable", () => { @@ -100,18 +98,6 @@ export class Repo extends EventEmitter { }) }) - if (!this.networkSubsystem.isReady()) { - handle.awaitNetwork() - this.networkSubsystem - .whenReady() - .then(() => { - handle.networkReady() - }) - .catch(err => { - this.#log("error waiting for network", { err }) - }) - } - // Register the document with the synchronizer. This advertises our interest in the document. this.#synchronizer.addDocument(handle.documentId) }) @@ -316,7 +302,7 @@ export class Repo extends EventEmitter { /** Returns an existing handle if we have it; creates one otherwise. */ #getHandle({ - documentId + documentId, }: { /** The documentId of the handle to look up or create */ documentId: DocumentId /** If we know we're creating a new document, specify this so we can have access to it immediately */ @@ -354,7 +340,7 @@ export class Repo extends EventEmitter { // Generate a new UUID and store it in the buffer const { documentId } = parseAutomergeUrl(generateAutomergeUrl()) const handle = this.#getHandle({ - documentId + documentId, }) as DocHandle this.emit("document", { handle }) @@ -365,10 +351,10 @@ export class Repo extends EventEmitter { nextDoc = Automerge.from(initialValue) } else { nextDoc = Automerge.emptyChange(Automerge.init()) - } + } return nextDoc }) - + handle.doneLoading() return handle } @@ -438,7 +424,7 @@ export class Repo extends EventEmitter { const handle = this.#getHandle({ documentId, }) as DocHandle - + // Try to load from disk if (this.storageSubsystem) { void this.storageSubsystem.loadDoc(handle.documentId).then(loadedDoc => { @@ -447,15 +433,21 @@ export class Repo extends EventEmitter { handle.update(() => loadedDoc as Automerge.Doc) handle.doneLoading() } else { - handle.request() + this.networkSubsystem + .whenReady() + .then(() => { + handle.request() + }) + .catch(err => { + this.#log("error waiting for network", { err }) + }) + this.emit("document", { handle }) } }) - } else { handle.request() + this.emit("document", { handle }) } - - this.emit("document", { handle }) return handle } diff --git a/packages/automerge-repo/src/network/NetworkSubsystem.ts b/packages/automerge-repo/src/network/NetworkSubsystem.ts index 81f729487..b4ab1a5c7 100644 --- a/packages/automerge-repo/src/network/NetworkSubsystem.ts +++ b/packages/automerge-repo/src/network/NetworkSubsystem.ts @@ -157,7 +157,10 @@ export class NetworkSubsystem extends EventEmitter { } isReady = () => { - return this.#readyAdapterCount === this.#adapters.length + return ( + this.#adapters.length === 0 || + this.#readyAdapterCount === this.#adapters.length + ) } whenReady = async () => { diff --git a/packages/automerge-repo/test/DocHandle.test.ts b/packages/automerge-repo/test/DocHandle.test.ts index ef756b064..23791dd03 100644 --- a/packages/automerge-repo/test/DocHandle.test.ts +++ b/packages/automerge-repo/test/DocHandle.test.ts @@ -12,7 +12,7 @@ describe("DocHandle", () => { const TEST_ID = parseAutomergeUrl(generateAutomergeUrl()).documentId const setup = (options?) => { const handle = new DocHandle(TEST_ID, options) - handle.update( () => A.init() ) + handle.update(() => A.init()) handle.doneLoading() return handle } @@ -58,7 +58,7 @@ describe("DocHandle", () => { it("should return the heads when requested", async () => { const handle = setup() - handle.change( d => d.foo = "bar") + handle.change(d => (d.foo = "bar")) assert.equal(handle.isReady(), true) const heads = A.getHeads(handle.docSync()) @@ -143,7 +143,7 @@ describe("DocHandle", () => { it("should emit a change message when changes happen", async () => { const handle = setup() - + const p = new Promise>(resolve => handle.once("change", d => resolve(d)) ) @@ -174,7 +174,7 @@ describe("DocHandle", () => { it("should update the internal doc prior to emitting the change message", async () => { const handle = setup() - + const p = new Promise(resolve => handle.once("change", ({ handle, doc }) => { assert.equal(handle.docSync()?.foo, doc.foo) @@ -192,7 +192,7 @@ describe("DocHandle", () => { it("should emit distinct change messages when consecutive changes happen", async () => { const handle = setup() - + let calls = 0 const p = new Promise(resolve => handle.on("change", async ({ doc: d }) => { @@ -284,7 +284,7 @@ describe("DocHandle", () => { it("should not time out if the document is updated in time", async () => { // set docHandle time out after 5 ms - const handle = setup({timeoutDelay:1}) + const handle = setup({ timeoutDelay: 1 }) // simulate requesting from the network handle.request() diff --git a/packages/automerge-repo/test/DocSynchronizer.test.ts b/packages/automerge-repo/test/DocSynchronizer.test.ts index 8e617e5bf..81f4ae2e4 100644 --- a/packages/automerge-repo/test/DocSynchronizer.test.ts +++ b/packages/automerge-repo/test/DocSynchronizer.test.ts @@ -24,11 +24,11 @@ describe("DocSynchronizer", () => { const docId = parseAutomergeUrl(generateAutomergeUrl()).documentId handle = new DocHandle(docId) handle.doneLoading() - + docSynchronizer = new DocSynchronizer({ handle: handle as DocHandle, }) - + return { handle, docSynchronizer } } diff --git a/packages/automerge-repo/test/Repo.test.ts b/packages/automerge-repo/test/Repo.test.ts index 89386ef46..08a7fed19 100644 --- a/packages/automerge-repo/test/Repo.test.ts +++ b/packages/automerge-repo/test/Repo.test.ts @@ -198,8 +198,11 @@ describe("Repo", () => { it("doesn't find a document that doesn't exist", async () => { const { repo } = setup() const handle = repo.find(generateAutomergeUrl()) - assert.equal(handle.isReady(), false) + await handle.whenReady(["ready", "unavailable"]) + + assert.equal(handle.isReady(), false) + assert.equal(handle.state, "unavailable") const doc = await handle.doc() assert.equal(doc, undefined) }) @@ -221,6 +224,7 @@ describe("Repo", () => { handle.on("unavailable", () => { wasUnavailable = true }) + await pause(50) assert.equal(wasUnavailable, false) @@ -400,6 +404,8 @@ describe("Repo", () => { d.count = 1 }) + await repo.flush() + for (let i = 0; i < 3; i++) { const repo2 = new Repo({ storage, @@ -484,8 +490,8 @@ describe("Repo", () => { let resume = (documentIds?: DocumentId[]) => { const savesToUnblock = documentIds ? Array.from(blockedSaves).filter(({ path }) => - documentIds.some(documentId => path.includes(documentId)) - ) + documentIds.some(documentId => path.includes(documentId)) + ) : Array.from(blockedSaves) savesToUnblock.forEach(({ resolve }) => resolve()) } @@ -1023,9 +1029,9 @@ describe("Repo", () => { const doc = Math.random() < 0.5 ? // heads, create a new doc - repo.create() + repo.create() : // tails, pick a random doc - (getRandomItem(docs) as DocHandle) + (getRandomItem(docs) as DocHandle) // make sure the doc is ready if (!doc.isReady()) { @@ -1408,7 +1414,7 @@ describe("Repo", () => { }) const warn = console.warn -const NO_OP = () => { } +const NO_OP = () => {} const disableConsoleWarn = () => { console.warn = NO_OP