@automerge/automerge-repo 1.0.19 → 1.1.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +12 -7
  2. package/dist/AutomergeUrl.js +2 -2
  3. package/dist/DocHandle.d.ts +6 -5
  4. package/dist/DocHandle.d.ts.map +1 -1
  5. package/dist/DocHandle.js +7 -7
  6. package/dist/RemoteHeadsSubscriptions.d.ts +42 -0
  7. package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -0
  8. package/dist/RemoteHeadsSubscriptions.js +284 -0
  9. package/dist/Repo.d.ts +29 -2
  10. package/dist/Repo.d.ts.map +1 -1
  11. package/dist/Repo.js +168 -9
  12. package/dist/helpers/debounce.js +1 -1
  13. package/dist/helpers/pause.d.ts.map +1 -1
  14. package/dist/helpers/pause.js +2 -0
  15. package/dist/helpers/throttle.js +1 -1
  16. package/dist/helpers/withTimeout.d.ts.map +1 -1
  17. package/dist/helpers/withTimeout.js +2 -0
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/network/NetworkAdapter.d.ts +15 -1
  22. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  23. package/dist/network/NetworkAdapter.js +3 -1
  24. package/dist/network/NetworkSubsystem.d.ts +4 -2
  25. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  26. package/dist/network/NetworkSubsystem.js +13 -7
  27. package/dist/network/messages.d.ts +68 -35
  28. package/dist/network/messages.d.ts.map +1 -1
  29. package/dist/network/messages.js +9 -7
  30. package/dist/storage/StorageSubsystem.d.ts +5 -3
  31. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  32. package/dist/storage/StorageSubsystem.js +23 -5
  33. package/dist/storage/keyHash.d.ts.map +1 -1
  34. package/dist/storage/types.d.ts +4 -0
  35. package/dist/storage/types.d.ts.map +1 -1
  36. package/dist/synchronizer/CollectionSynchronizer.d.ts +2 -2
  37. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  38. package/dist/synchronizer/CollectionSynchronizer.js +9 -3
  39. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  40. package/dist/synchronizer/DocSynchronizer.js +20 -17
  41. package/dist/synchronizer/Synchronizer.d.ts +12 -3
  42. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  43. package/package.json +6 -6
  44. package/src/AutomergeUrl.ts +2 -2
  45. package/src/DocHandle.ts +10 -9
  46. package/src/RemoteHeadsSubscriptions.ts +375 -0
  47. package/src/Repo.ts +241 -16
  48. package/src/helpers/debounce.ts +1 -1
  49. package/src/helpers/pause.ts +4 -0
  50. package/src/helpers/throttle.ts +1 -1
  51. package/src/helpers/withTimeout.ts +2 -0
  52. package/src/index.ts +3 -1
  53. package/src/network/NetworkAdapter.ts +19 -2
  54. package/src/network/NetworkSubsystem.ts +21 -9
  55. package/src/network/messages.ts +88 -50
  56. package/src/storage/StorageSubsystem.ts +30 -7
  57. package/src/storage/keyHash.ts +2 -0
  58. package/src/storage/types.ts +3 -0
  59. package/src/synchronizer/CollectionSynchronizer.ts +13 -5
  60. package/src/synchronizer/DocSynchronizer.ts +27 -27
  61. package/src/synchronizer/Synchronizer.ts +13 -3
  62. package/test/DocHandle.test.ts +0 -17
  63. package/test/RemoteHeadsSubscriptions.test.ts +353 -0
  64. package/test/Repo.test.ts +108 -17
  65. package/test/StorageSubsystem.test.ts +29 -7
  66. package/test/helpers/waitForMessages.ts +22 -0
  67. package/test/remoteHeads.test.ts +260 -0
  68. package/.eslintrc +0 -28
@@ -1,55 +1,64 @@
1
1
  import { SyncState } from "@automerge/automerge";
2
+ import { StorageId } from "../storage/types.js";
2
3
  import { DocumentId, PeerId, SessionId } from "../types.js";
4
+ export type Message = {
5
+ type: string;
6
+ /** The peer ID of the sender of this message */
7
+ senderId: PeerId;
8
+ /** The peer ID of the recipient of this message */
9
+ targetId: PeerId;
10
+ data?: Uint8Array;
11
+ documentId?: DocumentId;
12
+ };
3
13
  /**
4
14
  * A sync message for a particular document
5
15
  */
6
16
  export type SyncMessage = {
7
17
  type: "sync";
8
- /** The peer ID of the sender of this message */
9
18
  senderId: PeerId;
10
- /** The peer ID of the recipient of this message */
11
19
  targetId: PeerId;
12
20
  /** The automerge sync message */
13
21
  data: Uint8Array;
14
22
  /** The document ID of the document this message is for */
15
23
  documentId: DocumentId;
16
24
  };
17
- /** An ephemeral message
25
+ /**
26
+ * An ephemeral message.
18
27
  *
19
28
  * @remarks
20
- * Ephemeral messages are not persisted anywhere and have no particular
21
- * structure. `automerge-repo` will gossip them around, in order to avoid
22
- * eternal loops of ephemeral messages every message has a session ID, which
23
- * is a random number generated by the sender at startup time, and a sequence
24
- * number. The combination of these two things allows us to discard messages
25
- * we have already seen.
29
+ * Ephemeral messages are not persisted anywhere. The data property can be used by the application
30
+ * as needed. The repo gossips these around.
31
+ *
32
+ * In order to avoid infinite loops of ephemeral messages, every message has (a) a session ID, which
33
+ * is a random number generated by the sender at startup time; and (b) a sequence number. The
34
+ * combination of these two things allows us to discard messages we have already seen.
26
35
  * */
27
36
  export type EphemeralMessage = {
28
37
  type: "ephemeral";
29
- /** The peer ID of the sender of this message */
30
38
  senderId: PeerId;
31
- /** The peer ID of the recipient of this message */
32
39
  targetId: PeerId;
33
- /** A sequence number which must be incremented for each message sent by this peer */
40
+ /** A sequence number which must be incremented for each message sent by this peer. */
34
41
  count: number;
35
- /** The ID of the session this message is part of. The sequence number for a given session always increases */
42
+ /** The ID of the session this message is part of. The sequence number for a given session always increases. */
36
43
  sessionId: SessionId;
37
- /** The document ID this message pertains to */
44
+ /** The document ID this message pertains to. */
38
45
  documentId: DocumentId;
39
- /** The actual data of the message */
46
+ /** The actual data of the message. */
40
47
  data: Uint8Array;
41
48
  };
42
- /** Sent by a {@link Repo} to indicate that it does not have the document and none of it's connected peers do either */
49
+ /**
50
+ * Sent by a {@link Repo} to indicate that it does not have the document and none of its connected
51
+ * peers do either.
52
+ */
43
53
  export type DocumentUnavailableMessage = {
44
54
  type: "doc-unavailable";
45
- /** The peer ID of the sender of this message */
46
55
  senderId: PeerId;
47
- /** The peer ID of the recipient of this message */
48
56
  targetId: PeerId;
49
57
  /** The document which the peer claims it doesn't have */
50
58
  documentId: DocumentId;
51
59
  };
52
- /** Sent by a {@link Repo} to request a document from a peer
60
+ /**
61
+ * Sent by a {@link Repo} to request a document from a peer.
53
62
  *
54
63
  * @remarks
55
64
  * This is identical to a {@link SyncMessage} except that it is sent by a {@link Repo}
@@ -57,42 +66,66 @@ export type DocumentUnavailableMessage = {
57
66
  * */
58
67
  export type RequestMessage = {
59
68
  type: "request";
60
- /** The peer ID of the sender of this message */
61
69
  senderId: PeerId;
62
- /** The peer ID of the recipient of this message */
63
70
  targetId: PeerId;
64
- /** The initial automerge sync message */
71
+ /** The automerge sync message */
65
72
  data: Uint8Array;
66
- /** The document ID this message requests */
73
+ /** The document ID of the document this message is for */
67
74
  documentId: DocumentId;
68
75
  };
69
- /** (anticipating work in progress) */
70
- export type AuthMessage<TPayload = any> = {
71
- type: "auth";
72
- /** The peer ID of the sender of this message */
76
+ /**
77
+ * Sent by a {@link Repo} to add or remove storage IDs from a remote peer's subscription.
78
+ */
79
+ export type RemoteSubscriptionControlMessage = {
80
+ type: "remote-subscription-change";
81
+ senderId: PeerId;
82
+ targetId: PeerId;
83
+ /** The storage IDs to add to the subscription */
84
+ add?: StorageId[];
85
+ /** The storage IDs to remove from the subscription */
86
+ remove?: StorageId[];
87
+ };
88
+ /**
89
+ * Sent by a {@link Repo} to indicate that the heads of a document have changed on a remote peer.
90
+ */
91
+ export type RemoteHeadsChanged = {
92
+ type: "remote-heads-changed";
73
93
  senderId: PeerId;
74
- /** The peer ID of the recipient of this message */
75
94
  targetId: PeerId;
76
- /** The payload of the auth message (up to the specific auth provider) */
77
- payload: TPayload;
95
+ /** The document ID of the document that has changed */
96
+ documentId: DocumentId;
97
+ /** The document's new heads */
98
+ newHeads: {
99
+ [key: StorageId]: {
100
+ heads: string[];
101
+ timestamp: number;
102
+ };
103
+ };
78
104
  };
79
105
  /** These are message types that a {@link NetworkAdapter} surfaces to a {@link Repo}. */
80
- export type RepoMessage = SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
81
- /** These are all the message types that a {@link NetworkAdapter} might see. */
82
- export type Message = RepoMessage | AuthMessage;
106
+ export type RepoMessage = SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage | RemoteSubscriptionControlMessage | RemoteHeadsChanged;
107
+ /** These are message types that are handled by the {@link CollectionSynchronizer}.*/
108
+ export type DocMessage = SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
83
109
  /**
84
110
  * The contents of a message, without the sender ID or other properties added by the {@link NetworkSubsystem})
85
111
  */
86
- export type MessageContents<T extends Message = Message> = T extends EphemeralMessage ? Omit<T, "senderId" | "count" | "sessionId"> : Omit<T, "senderId">;
112
+ export type MessageContents<T extends Message = RepoMessage> = T extends EphemeralMessage ? Omit<T, "senderId" | "count" | "sessionId"> : Omit<T, "senderId">;
87
113
  /** Notify the repo that the sync state has changed */
88
114
  export interface SyncStateMessage {
89
115
  peerId: PeerId;
90
116
  documentId: DocumentId;
91
117
  syncState: SyncState;
92
118
  }
93
- export declare const isValidRepoMessage: (message: Message) => message is RepoMessage;
119
+ /** Notify the repo that a peer started syncing with a doc */
120
+ export interface OpenDocMessage {
121
+ peerId: PeerId;
122
+ documentId: DocumentId;
123
+ }
124
+ export declare const isRepoMessage: (message: Message) => message is RepoMessage;
94
125
  export declare const isDocumentUnavailableMessage: (msg: Message) => msg is DocumentUnavailableMessage;
95
126
  export declare const isRequestMessage: (msg: Message) => msg is RequestMessage;
96
127
  export declare const isSyncMessage: (msg: Message) => msg is SyncMessage;
97
128
  export declare const isEphemeralMessage: (msg: Message) => msg is EphemeralMessage;
129
+ export declare const isRemoteSubscriptionControlMessage: (msg: Message) => msg is RemoteSubscriptionControlMessage;
130
+ export declare const isRemoteHeadsChanged: (msg: Message) => msg is RemoteHeadsChanged;
98
131
  //# sourceMappingURL=messages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE3D;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IAEZ,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,iCAAiC;IACjC,IAAI,EAAE,UAAU,CAAA;IAEhB,0DAA0D;IAC1D,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED;;;;;;;;;KASK;AACL,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,WAAW,CAAA;IAEjB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IAEb,8GAA8G;IAC9G,SAAS,EAAE,SAAS,CAAA;IAEpB,+CAA+C;IAC/C,UAAU,EAAE,UAAU,CAAA;IAEtB,qCAAqC;IACrC,IAAI,EAAE,UAAU,CAAA;CACjB,CAAA;AAED,uHAAuH;AACvH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,iBAAiB,CAAA;IAEvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,yDAAyD;IACzD,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED;;;;;KAKK;AACL,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAA;IAEf,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,yCAAyC;IACzC,IAAI,EAAE,UAAU,CAAA;IAEhB,4CAA4C;IAC5C,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED,sCAAsC;AACtC,MAAM,MAAM,WAAW,CAAC,QAAQ,GAAG,GAAG,IAAI;IACxC,IAAI,EAAE,MAAM,CAAA;IAEZ,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,yEAAyE;IACzE,OAAO,EAAE,QAAQ,CAAA;CAClB,CAAA;AAED,wFAAwF;AACxF,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,gBAAgB,GAChB,cAAc,GACd,0BAA0B,CAAA;AAE9B,+EAA+E;AAC/E,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,WAAW,CAAA;AAE/C;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IACrD,CAAC,SAAS,gBAAgB,GACtB,IAAI,CAAC,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,WAAW,CAAC,GAC3C,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;AAEzB,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;CACrB;AAID,eAAO,MAAM,kBAAkB,YAAa,OAAO,2BAOT,CAAA;AAG1C,eAAO,MAAM,4BAA4B,QAAS,OAAO,sCACzB,CAAA;AAEhC,eAAO,MAAM,gBAAgB,QAAS,OAAO,0BACrB,CAAA;AAExB,eAAO,MAAM,aAAa,QAAS,OAAO,uBACrB,CAAA;AAErB,eAAO,MAAM,kBAAkB,QAAS,OAAO,4BACrB,CAAA"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,15 @@
1
1
  // TYPE GUARDS
2
- export const isValidRepoMessage = (message) => typeof message === "object" &&
3
- typeof message.type === "string" &&
4
- typeof message.senderId === "string" &&
5
- (isSyncMessage(message) ||
6
- isEphemeralMessage(message) ||
7
- isRequestMessage(message) ||
8
- isDocumentUnavailableMessage(message));
2
+ export const isRepoMessage = (message) => isSyncMessage(message) ||
3
+ isEphemeralMessage(message) ||
4
+ isRequestMessage(message) ||
5
+ isDocumentUnavailableMessage(message) ||
6
+ isRemoteSubscriptionControlMessage(message) ||
7
+ isRemoteHeadsChanged(message);
9
8
  // prettier-ignore
10
9
  export const isDocumentUnavailableMessage = (msg) => msg.type === "doc-unavailable";
11
10
  export const isRequestMessage = (msg) => msg.type === "request";
12
11
  export const isSyncMessage = (msg) => msg.type === "sync";
13
12
  export const isEphemeralMessage = (msg) => msg.type === "ephemeral";
13
+ // prettier-ignore
14
+ export const isRemoteSubscriptionControlMessage = (msg) => msg.type === "remote-subscription-change";
15
+ export const isRemoteHeadsChanged = (msg) => msg.type === "remote-heads-changed";
@@ -1,6 +1,7 @@
1
1
  import * as A from "@automerge/automerge/next";
2
- import { PeerId, type DocumentId } from "../types.js";
2
+ import { type DocumentId } from "../types.js";
3
3
  import { StorageAdapter } from "./StorageAdapter.js";
4
+ import { StorageId } from "./types.js";
4
5
  /**
5
6
  * The storage subsystem is responsible for saving and loading Automerge documents to and from
6
7
  * storage adapter. It also provides a generic key/value storage interface for other uses.
@@ -8,6 +9,7 @@ import { StorageAdapter } from "./StorageAdapter.js";
8
9
  export declare class StorageSubsystem {
9
10
  #private;
10
11
  constructor(storageAdapter: StorageAdapter);
12
+ id(): Promise<StorageId>;
11
13
  /** Loads a value from storage. */
12
14
  load(
13
15
  /** Namespace to prevent collisions with other users of the storage subsystem. */
@@ -44,7 +46,7 @@ export declare class StorageSubsystem {
44
46
  * Removes the Automerge document with the given ID from storage
45
47
  */
46
48
  removeDoc(documentId: DocumentId): Promise<void>;
47
- loadSyncState(documentId: DocumentId, peerId: PeerId): Promise<A.SyncState | undefined>;
48
- saveSyncState(documentId: DocumentId, peerId: PeerId, syncState: A.SyncState): Promise<void>;
49
+ loadSyncState(documentId: DocumentId, storageId: StorageId): Promise<A.SyncState | undefined>;
50
+ saveSyncState(documentId: DocumentId, storageId: StorageId, syncState: A.SyncState): Promise<void>;
49
51
  }
50
52
  //# sourceMappingURL=StorageSubsystem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAI9C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAKpD;;;GAGG;AACH,qBAAa,gBAAgB;;gBAef,cAAc,EAAE,cAAc;IAc1C,kCAAkC;IAC5B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKlC,gCAAgC;IAC1B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM;IAEX,sCAAsC;IACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC;IAKhB,oCAAoC;IAC9B,MAAM;IACV,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAOhB;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAmClE;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAazE;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,UAAU;IAkEhC,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC;IAM7B,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,CAAC,CAAC,SAAS,GACrB,OAAO,CAAC,IAAI,CAAC;CAyCjB"}
1
+ {"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAI9C,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;AAK7D;;;GAGG;AACH,qBAAa,gBAAgB;;gBAef,cAAc,EAAE,cAAc;IAIpC,EAAE,IAAI,OAAO,CAAC,SAAS,CAAC;IA2B9B,kCAAkC;IAC5B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKlC,gCAAgC;IAC1B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM;IAEX,sCAAsC;IACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC;IAKhB,oCAAoC;IAC9B,MAAM;IACV,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAOhB;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAmClE;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAazE;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,UAAU;IAkEhC,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC;IAM7B,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,CAAC,CAAC,SAAS,GACrB,OAAO,CAAC,IAAI,CAAC;CA8CjB"}
@@ -4,6 +4,7 @@ import { headsAreSame } from "../helpers/headsAreSame.js";
4
4
  import { mergeArrays } from "../helpers/mergeArrays.js";
5
5
  import { keyHash, headsHash } from "./keyHash.js";
6
6
  import { chunkTypeFromKey } from "./chunkTypeFromKey.js";
7
+ import * as Uuid from "uuid";
7
8
  /**
8
9
  * The storage subsystem is responsible for saving and loading Automerge documents to and from
9
10
  * storage adapter. It also provides a generic key/value storage interface for other uses.
@@ -21,6 +22,18 @@ export class StorageSubsystem {
21
22
  constructor(storageAdapter) {
22
23
  this.#storageAdapter = storageAdapter;
23
24
  }
25
+ async id() {
26
+ const storedId = await this.#storageAdapter.load(["storage-adapter-id"]);
27
+ let id;
28
+ if (storedId) {
29
+ id = new TextDecoder().decode(storedId);
30
+ }
31
+ else {
32
+ id = Uuid.v4();
33
+ await this.#storageAdapter.save(["storage-adapter-id"], new TextEncoder().encode(id));
34
+ }
35
+ return id;
36
+ }
24
37
  // ARBITRARY KEY/VALUE STORAGE
25
38
  // The `load`, `save`, and `remove` methods are for generic key/value storage, as opposed to
26
39
  // Automerge documents. For example, they're used by the LocalFirstAuthProvider to persist the
@@ -163,13 +176,13 @@ export class StorageSubsystem {
163
176
  this.#chunkInfos.set(documentId, newChunkInfos);
164
177
  this.#compacting = false;
165
178
  }
166
- async loadSyncState(documentId, peerId) {
167
- const key = [documentId, "sync-state", peerId];
179
+ async loadSyncState(documentId, storageId) {
180
+ const key = [documentId, "sync-state", storageId];
168
181
  const loaded = await this.#storageAdapter.load(key);
169
182
  return loaded ? A.decodeSyncState(loaded) : undefined;
170
183
  }
171
- async saveSyncState(documentId, peerId, syncState) {
172
- const key = [documentId, "sync-state", peerId];
184
+ async saveSyncState(documentId, storageId, syncState) {
185
+ const key = [documentId, "sync-state", storageId];
173
186
  await this.#storageAdapter.save(key, A.encodeSyncState(syncState));
174
187
  }
175
188
  /**
@@ -204,6 +217,11 @@ export class StorageSubsystem {
204
217
  incrementalSize += chunk.size;
205
218
  }
206
219
  }
207
- return incrementalSize >= snapshotSize;
220
+ // if the file is currently small, don't worry, just compact
221
+ // this might seem a bit arbitrary (1k is arbitrary) but is designed to ensure compaction
222
+ // for documents with only a single large change on top of an empty (or nearly empty) document
223
+ // for example: imported NPM modules, images, etc.
224
+ // if we have even more incrementals (so far) than the snapshot, compact
225
+ return snapshotSize < 1024 || incrementalSize >= snapshotSize;
208
226
  }
209
227
  }
@@ -1 +1 @@
1
- {"version":3,"file":"keyHash.d.ts","sourceRoot":"","sources":["../../src/storage/keyHash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAI9C,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,UAIzC;AACD,wBAAgB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,GAAG,MAAM,CAIhD"}
1
+ {"version":3,"file":"keyHash.d.ts","sourceRoot":"","sources":["../../src/storage/keyHash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAI9C,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,UAIzC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,GAAG,MAAM,CAIhD"}
@@ -34,4 +34,8 @@ export type ChunkType = "snapshot" | "incremental";
34
34
  * should not assume any particular structure.
35
35
  **/
36
36
  export type StorageKey = string[];
37
+ /** A branded type for storage IDs */
38
+ export type StorageId = string & {
39
+ __storageId: true;
40
+ };
37
41
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,GAAG,EAAE,UAAU,CAAA;IACf,IAAI,EAAE,UAAU,GAAG,SAAS,CAAA;CAC7B,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,UAAU,CAAA;IACf,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;AAElD;;;;;;;;;;;;;;;;;IAiBI;AACJ,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,GAAG,EAAE,UAAU,CAAA;IACf,IAAI,EAAE,UAAU,GAAG,SAAS,CAAA;CAC7B,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,UAAU,CAAA;IACf,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;AAElD;;;;;;;;;;;;;;;;;IAiBI;AACJ,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,CAAA;AAEjC,qCAAqC;AACrC,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,WAAW,EAAE,IAAI,CAAA;CAAE,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { Repo } from "../Repo.js";
2
- import { RepoMessage } from "../network/messages.js";
2
+ import { DocMessage } from "../network/messages.js";
3
3
  import { DocumentId, PeerId } from "../types.js";
4
4
  import { Synchronizer } from "./Synchronizer.js";
5
5
  /** A CollectionSynchronizer is responsible for synchronizing a DocCollection with peers. */
@@ -11,7 +11,7 @@ export declare class CollectionSynchronizer extends Synchronizer {
11
11
  * When we receive a sync message for a document we haven't got in memory, we
12
12
  * register it with the repo and start synchronizing
13
13
  */
14
- receiveMessage(message: RepoMessage): Promise<void>;
14
+ receiveMessage(message: DocMessage): Promise<void>;
15
15
  /**
16
16
  * Starts synchronizing the given document with all peers that we share it generously with.
17
17
  */
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IA8C9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,WAAW;IAyBzC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAYlC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IASzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;CACF"}
1
+ {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAqD9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;IAyBxC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAalC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IASzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;CACF"}
@@ -28,14 +28,19 @@ export class CollectionSynchronizer extends Synchronizer {
28
28
  #initDocSynchronizer(handle) {
29
29
  const docSynchronizer = new DocSynchronizer({
30
30
  handle,
31
- onLoadSyncState: peerId => {
31
+ onLoadSyncState: async (peerId) => {
32
32
  if (!this.repo.storageSubsystem) {
33
- return Promise.resolve(undefined);
33
+ return;
34
34
  }
35
- return this.repo.storageSubsystem.loadSyncState(handle.documentId, peerId);
35
+ const { storageId, isEphemeral } = this.repo.peerMetadataByPeerId[peerId] || {};
36
+ if (!storageId || isEphemeral) {
37
+ return;
38
+ }
39
+ return this.repo.storageSubsystem.loadSyncState(handle.documentId, storageId);
36
40
  },
37
41
  });
38
42
  docSynchronizer.on("message", event => this.emit("message", event));
43
+ docSynchronizer.on("open-doc", event => this.emit("open-doc", event));
39
44
  docSynchronizer.on("sync-state", event => this.emit("sync-state", event));
40
45
  return docSynchronizer;
41
46
  }
@@ -82,6 +87,7 @@ export class CollectionSynchronizer extends Synchronizer {
82
87
  });
83
88
  }
84
89
  // TODO: implement this
90
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
85
91
  removeDocument(documentId) {
86
92
  throw new Error("not implemented");
87
93
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAG9C,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,GAAG,SAAS,CAAA;CAC9D;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAsBV,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyB9D,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IAqID,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IA+C3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,WAAW;IAkBnC,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CA8EzD"}
1
+ {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAG9C,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,GAAG,SAAS,CAAA;CAC9D;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAsBV,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyB9D,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IAkID,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IAmD3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,WAAW;IAkBnC,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CA8EzD"}
@@ -5,7 +5,6 @@ import { READY, REQUESTING, UNAVAILABLE, } from "../DocHandle.js";
5
5
  import { isRequestMessage, } from "../network/messages.js";
6
6
  import { Synchronizer } from "./Synchronizer.js";
7
7
  import { throttle } from "../helpers/throttle.js";
8
- import { headsAreSame } from "../helpers/headsAreSame.js";
9
8
  /**
10
9
  * DocSynchronizer takes a handle to an Automerge document, and receives & dispatches sync messages
11
10
  * to bring it inline with all other peers' versions.
@@ -67,9 +66,7 @@ export class DocSynchronizer extends Synchronizer {
67
66
  this.emit("message", message);
68
67
  }
69
68
  #withSyncState(peerId, callback) {
70
- if (!this.#peers.includes(peerId)) {
71
- this.#peers.push(peerId);
72
- }
69
+ this.#addPeer(peerId);
73
70
  if (!(peerId in this.#peerDocumentStatuses)) {
74
71
  this.#peerDocumentStatuses[peerId] = "unknown";
75
72
  }
@@ -80,13 +77,23 @@ export class DocSynchronizer extends Synchronizer {
80
77
  }
81
78
  let pendingCallbacks = this.#pendingSyncStateCallbacks[peerId];
82
79
  if (!pendingCallbacks) {
83
- this.#onLoadSyncState(peerId).then(syncState => {
80
+ this.#onLoadSyncState(peerId)
81
+ .then(syncState => {
84
82
  this.#initSyncState(peerId, syncState ?? A.initSyncState());
83
+ })
84
+ .catch(err => {
85
+ this.#log(`Error loading sync state for ${peerId}: ${err}`);
85
86
  });
86
87
  pendingCallbacks = this.#pendingSyncStateCallbacks[peerId] = [];
87
88
  }
88
89
  pendingCallbacks.push(callback);
89
90
  }
91
+ #addPeer(peerId) {
92
+ if (!this.#peers.includes(peerId)) {
93
+ this.#peers.push(peerId);
94
+ this.emit("open-doc", { documentId: this.documentId, peerId });
95
+ }
96
+ }
90
97
  #initSyncState(peerId, syncState) {
91
98
  const pendingCallbacks = this.#pendingSyncStateCallbacks[peerId];
92
99
  if (pendingCallbacks) {
@@ -98,15 +105,7 @@ export class DocSynchronizer extends Synchronizer {
98
105
  this.#syncStates[peerId] = syncState;
99
106
  }
100
107
  #setSyncState(peerId, syncState) {
101
- const previousSyncState = this.#syncStates[peerId];
102
108
  this.#syncStates[peerId] = syncState;
103
- const haveTheirSyncedHeadsChanged = syncState.theirHeads &&
104
- (!previousSyncState ||
105
- !previousSyncState.theirHeads ||
106
- !headsAreSame(previousSyncState.theirHeads, syncState.theirHeads));
107
- if (haveTheirSyncedHeadsChanged) {
108
- this.#handle.setRemoteHeads(peerId, syncState.theirHeads);
109
- }
110
109
  this.emit("sync-state", {
111
110
  peerId,
112
111
  syncState,
@@ -179,10 +178,14 @@ export class DocSynchronizer extends Synchronizer {
179
178
  // TODO: cover that case with a test and remove this hack
180
179
  const reparsedSyncState = A.decodeSyncState(A.encodeSyncState(syncState));
181
180
  this.#setSyncState(peerId, reparsedSyncState);
182
- docPromise.then(doc => {
181
+ docPromise
182
+ .then(doc => {
183
183
  if (doc) {
184
184
  this.#sendSyncMessage(peerId, doc);
185
185
  }
186
+ })
187
+ .catch(err => {
188
+ this.#log(`Error loading doc for ${peerId}: ${err}`);
186
189
  });
187
190
  });
188
191
  });
@@ -236,9 +239,9 @@ export class DocSynchronizer extends Synchronizer {
236
239
  return;
237
240
  }
238
241
  this.#processAllPendingSyncMessages();
239
- this.#processSyncMessage(message, new Date());
242
+ this.#processSyncMessage(message);
240
243
  }
241
- #processSyncMessage(message, received) {
244
+ #processSyncMessage(message) {
242
245
  if (isRequestMessage(message)) {
243
246
  this.#peerDocumentStatuses[message.senderId] = "wants";
244
247
  }
@@ -279,7 +282,7 @@ export class DocSynchronizer extends Synchronizer {
279
282
  }
280
283
  #processAllPendingSyncMessages() {
281
284
  for (const message of this.#pendingSyncMessages) {
282
- this.#processSyncMessage(message.message, message.received);
285
+ this.#processSyncMessage(message.message);
283
286
  }
284
287
  this.#pendingSyncMessages = [];
285
288
  }
@@ -1,10 +1,19 @@
1
1
  import { EventEmitter } from "eventemitter3";
2
- import { MessageContents, RepoMessage, SyncStateMessage } from "../network/messages.js";
2
+ import { MessageContents, OpenDocMessage, RepoMessage } from "../network/messages.js";
3
+ import { SyncState } from "@automerge/automerge";
4
+ import { PeerId, DocumentId } from "../types.js";
3
5
  export declare abstract class Synchronizer extends EventEmitter<SynchronizerEvents> {
4
6
  abstract receiveMessage(message: RepoMessage): void;
5
7
  }
6
8
  export interface SynchronizerEvents {
7
- message: (arg: MessageContents) => void;
8
- "sync-state": (arg: SyncStateMessage) => void;
9
+ message: (payload: MessageContents) => void;
10
+ "sync-state": (payload: SyncStatePayload) => void;
11
+ "open-doc": (arg: OpenDocMessage) => void;
12
+ }
13
+ /** Notify the repo that the sync state has changed */
14
+ export interface SyncStatePayload {
15
+ peerId: PeerId;
16
+ documentId: DocumentId;
17
+ syncState: SyncState;
9
18
  }
10
19
  //# sourceMappingURL=Synchronizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EACjB,MAAM,wBAAwB,CAAA;AAE/B,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;IACvC,YAAY,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAC9C"}
1
+ {"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,cAAc,EACd,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhD,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;IACjD,UAAU,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CAC1C;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;CACrB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo",
3
- "version": "1.0.19",
3
+ "version": "1.1.0-alpha.13",
4
4
  "description": "A repository object to manage a collection of automerge documents",
5
5
  "repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo",
6
6
  "author": "Peter van Hardenberg <pvh@pvh.ca>",
@@ -10,20 +10,20 @@
10
10
  "scripts": {
11
11
  "build": "tsc",
12
12
  "watch": "npm-watch build",
13
- "test:coverage": "c8 --reporter=lcov --reporter=html --reporter=text yarn test",
13
+ "test:coverage": "c8 --reporter=lcov --reporter=html --reporter=text pnpm test",
14
14
  "test": "vitest",
15
15
  "test:watch": "npm-watch test",
16
- "test:log": "cross-env DEBUG='automerge-repo:*' yarn test",
17
16
  "fuzz": "ts-node --esm --experimentalSpecifierResolution=node fuzz/fuzz.ts"
18
17
  },
19
18
  "browser": {
20
19
  "crypto": false
21
20
  },
22
21
  "devDependencies": {
23
- "http-server": "^14.1.0"
22
+ "http-server": "^14.1.0",
23
+ "vite": "^5.0.8"
24
24
  },
25
25
  "dependencies": {
26
- "@automerge/automerge": "^2.1.7",
26
+ "@automerge/automerge": "^2.1.9",
27
27
  "bs58check": "^3.0.1",
28
28
  "cbor-x": "^1.3.0",
29
29
  "debug": "^4.3.4",
@@ -55,5 +55,5 @@
55
55
  "publishConfig": {
56
56
  "access": "public"
57
57
  },
58
- "gitHead": "7d28ca50dfa437ac6f7b1722b89b3f6844b90de7"
58
+ "gitHead": "f4ce1376d900ad98f00a638626be9611077460b5"
59
59
  }
@@ -13,7 +13,7 @@ export const urlPrefix = "automerge:"
13
13
  /** Given an Automerge URL, returns the DocumentId in both base58check-encoded form and binary form */
14
14
  export const parseAutomergeUrl = (url: AutomergeUrl) => {
15
15
  const regex = new RegExp(`^${urlPrefix}(\\w+)$`)
16
- const [_, docMatch] = url.match(regex) || []
16
+ const [, docMatch] = url.match(regex) || []
17
17
  const documentId = docMatch as DocumentId
18
18
  const binaryDocumentId = documentIdToBinary(documentId)
19
19
 
@@ -33,7 +33,7 @@ export const parseAutomergeUrl = (url: AutomergeUrl) => {
33
33
  export const stringifyAutomergeUrl = (
34
34
  arg: UrlOptions | DocumentId | BinaryDocumentId
35
35
  ) => {
36
- let documentId =
36
+ const documentId =
37
37
  arg instanceof Uint8Array || typeof arg === "string"
38
38
  ? arg
39
39
  : "documentId" in arg
package/src/DocHandle.ts CHANGED
@@ -19,6 +19,7 @@ import { encode } from "./helpers/cbor.js"
19
19
  import { headsAreSame } from "./helpers/headsAreSame.js"
20
20
  import { withTimeout } from "./helpers/withTimeout.js"
21
21
  import type { AutomergeUrl, DocumentId, PeerId } from "./types.js"
22
+ import { StorageId } from "./storage/types.js"
22
23
 
23
24
  /** DocHandle is a wrapper around a single Automerge document that lets us
24
25
  * listen for changes and notify the network and storage of new changes.
@@ -39,7 +40,7 @@ export class DocHandle<T> //
39
40
 
40
41
  #machine: DocHandleXstateMachine<T>
41
42
  #timeoutDelay: number
42
- #remoteHeads: Record<PeerId, A.Heads> = {}
43
+ #remoteHeads: Record<StorageId, A.Heads> = {}
43
44
 
44
45
  /** The URL of this document
45
46
  *
@@ -325,17 +326,17 @@ export class DocHandle<T> //
325
326
  })
326
327
  }
327
328
 
328
- /** `setRemoteHeads` is called by the doc synchronizer
329
+ /** `setRemoteHeads` is called by the repo either when a doc handle changes or we receive new remote heads
329
330
  * @hidden
330
331
  */
331
- setRemoteHeads(peerId: PeerId, heads: A.Heads) {
332
- this.#remoteHeads[peerId] = heads
333
- this.emit("remote-heads", { peerId, heads })
332
+ setRemoteHeads(storageId: StorageId, heads: A.Heads) {
333
+ this.#remoteHeads[storageId] = heads
334
+ this.emit("remote-heads", { storageId, heads })
334
335
  }
335
336
 
336
- /** Returns the heads of the peer */
337
- getRemoteHeads(peerId: PeerId): A.Heads | undefined {
338
- return this.#remoteHeads[peerId]
337
+ /** Returns the heads of the storageId */
338
+ getRemoteHeads(storageId: StorageId): A.Heads | undefined {
339
+ return this.#remoteHeads[storageId]
339
340
  }
340
341
 
341
342
  /** `change` is called by the repo when the document is changed locally */
@@ -494,7 +495,7 @@ export interface DocHandleOutboundEphemeralMessagePayload<T> {
494
495
  }
495
496
 
496
497
  export interface DocHandleRemoteHeadsPayload {
497
- peerId: PeerId
498
+ storageId: StorageId
498
499
  heads: A.Heads
499
500
  }
500
501