@dabble/patches 0.7.8 → 0.7.10

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.
package/dist/types.d.ts CHANGED
@@ -57,13 +57,30 @@ interface PatchesSnapshot<T = any> extends PatchesState<T> {
57
57
  changes: Change[];
58
58
  }
59
59
  /**
60
- * Represents the syncing state of a document.
61
- * @property initial - The document is not syncing.
62
- * @property updating - The document is syncing.
63
- * @property null - The document is not syncing.
64
- * @property Error - The document is syncing with an error.
60
+ * Sync status for a document, used by both PatchesDoc and PatchesSync's SyncedDoc.
61
+ * - `'unsynced'` not yet synced (initial state, or disconnected)
62
+ * - `'syncing'` — actively syncing with the server
63
+ * - `'synced'` — up to date with the server
64
+ * - `'error'` — sync failed (see syncError for details)
65
65
  */
66
- type SyncingState = 'initial' | 'updating' | null | Error;
66
+ type DocSyncStatus = 'unsynced' | 'syncing' | 'synced' | 'error';
67
+ /**
68
+ * Represents the synced state of a document.
69
+ * @property committedRev - The last committed revision number from the server.
70
+ * @property hasPending - Whether there are local changes that haven't been committed yet.
71
+ * @property syncStatus - The current sync status of the document.
72
+ * @property syncError - The error from the last failed sync attempt, if any.
73
+ * @property isLoaded - Whether the document has completed its initial load. Sticky: once true, never reverts to false
74
+ * within a sync lifecycle. A document is considered loaded when it has data to display (server data, cached data,
75
+ * or local changes) or sync has resolved (successfully or with error).
76
+ */
77
+ interface SyncedDoc {
78
+ committedRev: number;
79
+ hasPending: boolean;
80
+ syncStatus: DocSyncStatus;
81
+ syncError: Error | null;
82
+ isLoaded: boolean;
83
+ }
67
84
  /** Status options for a branch */
68
85
  type BranchStatus = 'open' | 'closed' | 'merged' | 'archived' | 'abandoned';
69
86
  interface Branch {
@@ -220,4 +237,4 @@ type PathProxy<T = any> = IsAny<T> extends true ? DeepPathProxy : {
220
237
  */
221
238
  type ChangeMutator<T> = (patch: JSONPatch, root: PathProxy<T>) => void;
222
239
 
223
- export type { Branch, BranchStatus, Change, ChangeInput, ChangeMutator, CommitChangesOptions, DeleteDocOptions, DocumentTombstone, EditableBranchMetadata, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions, PatchesSnapshot, PatchesState, PathProxy, SyncingState, VersionMetadata };
240
+ export type { Branch, BranchStatus, Change, ChangeInput, ChangeMutator, CommitChangesOptions, DeleteDocOptions, DocSyncStatus, DocumentTombstone, EditableBranchMetadata, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions, PatchesSnapshot, PatchesState, PathProxy, SyncedDoc, VersionMetadata };
@@ -1,6 +1,6 @@
1
1
  import { ShallowRef, Ref, MaybeRef } from 'vue';
2
2
  import { OpenDocOptions } from '../client/Patches.js';
3
- import { a as PatchesDoc } from '../BaseDoc-_Rsau70J.js';
3
+ import { a as PatchesDoc } from '../BaseDoc-BfVJNeCi.js';
4
4
  import { JSONPatch } from '../json-patch/JSONPatch.js';
5
5
  import { ChangeMutator } from '../types.js';
6
6
  import '../event-signal.js';
@@ -12,7 +12,7 @@ import { JSONPatch } from "../json-patch/JSONPatch.js";
12
12
  import { usePatchesContext } from "./provider.js";
13
13
  import { getDocManager } from "./doc-manager.js";
14
14
  function createDocReactiveState(options) {
15
- const { initialLoading = true, transformState, changeBehavior } = options;
15
+ const { initialLoading = true, hasSyncContext = false, transformState, changeBehavior } = options;
16
16
  const doc = ref(void 0);
17
17
  const data = shallowRef(void 0);
18
18
  const loading = ref(initialLoading);
@@ -21,6 +21,19 @@ function createDocReactiveState(options) {
21
21
  const hasPending = ref(false);
22
22
  function setupDoc(patchesDoc) {
23
23
  doc.value = patchesDoc;
24
+ let loaded = false;
25
+ function updateLoading() {
26
+ if (loaded) return;
27
+ if (patchesDoc.isLoaded) {
28
+ loaded = true;
29
+ loading.value = false;
30
+ } else if (patchesDoc.syncStatus === "syncing") {
31
+ loading.value = true;
32
+ } else if (!hasSyncContext) {
33
+ loaded = true;
34
+ loading.value = false;
35
+ }
36
+ }
24
37
  const unsubState = patchesDoc.subscribe((state) => {
25
38
  if (transformState && state) {
26
39
  state = transformState(state, patchesDoc);
@@ -28,12 +41,12 @@ function createDocReactiveState(options) {
28
41
  data.value = state;
29
42
  rev.value = patchesDoc.committedRev;
30
43
  hasPending.value = patchesDoc.hasPending;
44
+ updateLoading();
31
45
  });
32
- const unsubSync = patchesDoc.onSyncing((syncState) => {
33
- loading.value = syncState === "initial" || syncState === "updating";
34
- error.value = syncState instanceof Error ? syncState : null;
46
+ const unsubSync = patchesDoc.onSyncStatus((status) => {
47
+ updateLoading();
48
+ error.value = status === "error" ? patchesDoc.syncError : null;
35
49
  });
36
- loading.value = patchesDoc.syncing !== null;
37
50
  return () => {
38
51
  unsubState();
39
52
  unsubSync();
@@ -75,12 +88,16 @@ function usePatchesDoc(docIdOrOptions, options) {
75
88
  return _usePatchesDocLazy(docIdOrOptions ?? {});
76
89
  }
77
90
  function _usePatchesDocEager(docId, options) {
78
- const { patches } = usePatchesContext();
91
+ const { patches, sync } = usePatchesContext();
79
92
  const { autoClose = false, algorithm, metadata } = options;
80
93
  const shouldUntrack = autoClose === "untrack";
81
94
  const openDocOpts = { algorithm, metadata };
82
95
  const manager = getDocManager(patches);
83
- const { setupDoc, baseReturn } = createDocReactiveState({ changeBehavior: "throw" });
96
+ const { setupDoc, baseReturn } = createDocReactiveState({
97
+ initialLoading: !!autoClose,
98
+ hasSyncContext: !!sync,
99
+ changeBehavior: "throw"
100
+ });
84
101
  let unsubscribe = null;
85
102
  if (autoClose) {
86
103
  manager.openDoc(patches, docId, openDocOpts).then((patchesDoc) => {
@@ -110,10 +127,11 @@ function _usePatchesDocEager(docId, options) {
110
127
  return baseReturn;
111
128
  }
112
129
  function _usePatchesDocLazy(options) {
113
- const { patches } = usePatchesContext();
130
+ const { patches, sync } = usePatchesContext();
114
131
  const { idProp } = options;
115
- const { setupDoc, resetRefs, baseReturn } = createDocReactiveState({
132
+ const { setupDoc, resetRefs, loading, baseReturn } = createDocReactiveState({
116
133
  initialLoading: false,
134
+ hasSyncContext: !!sync,
117
135
  changeBehavior: "noop",
118
136
  transformState: idProp ? (state, patchesDoc) => ({ ...state, [idProp]: patchesDoc.id }) : void 0
119
137
  });
@@ -131,12 +149,13 @@ function _usePatchesDocLazy(options) {
131
149
  await patches.closeDoc(prevPath);
132
150
  }
133
151
  path.value = docPath;
152
+ loading.value = true;
134
153
  try {
135
154
  const patchesDoc = await patches.openDoc(docPath, options2);
136
155
  unsubscribe = setupDoc(patchesDoc);
137
156
  } catch (err) {
138
157
  baseReturn.error.value = err;
139
- baseReturn.loading.value = false;
158
+ loading.value = false;
140
159
  }
141
160
  }
142
161
  async function close() {
@@ -168,11 +187,11 @@ function usePatchesSync() {
168
187
  throw new Error("PatchesSync not found in context. Did you forget to pass sync to providePatchesContext()?");
169
188
  }
170
189
  const connected = ref(sync.state.connected);
171
- const syncing = ref(sync.state.syncing === "updating");
190
+ const syncing = ref(sync.state.syncStatus === "syncing");
172
191
  const online = ref(sync.state.online);
173
192
  const unsubscribe = sync.onStateChange((state) => {
174
193
  connected.value = state.connected;
175
- syncing.value = state.syncing === "updating";
194
+ syncing.value = state.syncStatus === "syncing";
176
195
  online.value = state.online;
177
196
  });
178
197
  onBeforeUnmount(() => {
@@ -188,12 +207,16 @@ function createDocInjectionKey(name) {
188
207
  return Symbol(`patches-doc-${name}`);
189
208
  }
190
209
  function providePatchesDoc(name, docId, options = {}) {
191
- const { patches } = usePatchesContext();
210
+ const { patches, sync } = usePatchesContext();
192
211
  const { autoClose = false, algorithm, metadata } = options;
193
212
  const shouldUntrack = autoClose === "untrack";
194
213
  const openDocOpts = { algorithm, metadata };
195
214
  const manager = getDocManager(patches);
196
- const { setupDoc, baseReturn } = createDocReactiveState({ changeBehavior: "throw" });
215
+ const { setupDoc, baseReturn } = createDocReactiveState({
216
+ initialLoading: !!autoClose,
217
+ hasSyncContext: !!sync,
218
+ changeBehavior: "throw"
219
+ });
197
220
  const currentDocId = ref(unref(docId));
198
221
  let unsubscribe = null;
199
222
  async function initDoc(id) {
@@ -6,5 +6,5 @@ import '../types.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../client/ClientAlgorithm.js';
9
- import '../BaseDoc-_Rsau70J.js';
9
+ import '../BaseDoc-BfVJNeCi.js';
10
10
  import '../client/PatchesStore.js';
@@ -11,7 +11,7 @@ import '../types.js';
11
11
  import '../json-patch/JSONPatch.js';
12
12
  import '@dabble/delta';
13
13
  import '../client/ClientAlgorithm.js';
14
- import '../BaseDoc-_Rsau70J.js';
14
+ import '../BaseDoc-BfVJNeCi.js';
15
15
  import '../client/PatchesStore.js';
16
16
  import '../net/PatchesSync.js';
17
17
  import '../net/protocol/types.js';
@@ -6,7 +6,7 @@ import '../types.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../client/ClientAlgorithm.js';
9
- import '../BaseDoc-_Rsau70J.js';
9
+ import '../BaseDoc-BfVJNeCi.js';
10
10
  import '../client/PatchesStore.js';
11
11
 
12
12
  /**
@@ -7,7 +7,7 @@ import '../types.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
9
9
  import '../client/ClientAlgorithm.js';
10
- import '../BaseDoc-_Rsau70J.js';
10
+ import '../BaseDoc-BfVJNeCi.js';
11
11
  import '../client/PatchesStore.js';
12
12
  import '../net/protocol/types.js';
13
13
  import '../net/protocol/JSONRPCClient.js';
@@ -16,6 +16,7 @@ import '../net/PatchesClient.js';
16
16
  import '../net/websocket/WebSocketTransport.js';
17
17
  import '../utils/deferred.js';
18
18
  import '../algorithms/ot/shared/changeBatching.js';
19
+ import '../shared/utils.js';
19
20
 
20
21
  /**
21
22
  * Injection key for Patches instance.
@@ -1 +1,5 @@
1
1
  export { areSetsEqual, fillPath } from '../shared/utils.js';
2
+ import '../types.js';
3
+ import '../json-patch/JSONPatch.js';
4
+ import '@dabble/delta';
5
+ import '../json-patch/types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "description": "Immutable JSON Patch implementation based on RFC 6902 supporting operational transformation and last-writer-wins",
5
5
  "author": "Jacob Wright <jacwright@gmail.com>",
6
6
  "bugs": {