@dabble/patches 0.7.1 → 0.7.3
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/client/IndexedDBStore.js +1 -1
- package/dist/client/LWWAlgorithm.js +3 -2
- package/dist/client/LWWBatcher.d.ts +84 -0
- package/dist/client/LWWBatcher.js +85 -0
- package/dist/client/LWWDoc.d.ts +3 -1
- package/dist/client/LWWDoc.js +4 -2
- package/dist/client/Patches.d.ts +5 -0
- package/dist/client/Patches.js +16 -2
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/compression/index.js +1 -6
- package/dist/index.d.ts +1 -0
- package/dist/net/PatchesClient.d.ts +2 -2
- package/dist/net/PatchesClient.js +2 -2
- package/dist/net/index.d.ts +0 -1
- package/dist/net/protocol/JSONRPCServer.js +5 -3
- package/dist/net/protocol/types.d.ts +1 -0
- package/dist/net/webrtc/WebRTCAwareness.js +2 -3
- package/dist/net/websocket/WebSocketTransport.js +5 -2
- package/dist/server/LWWServer.d.ts +4 -4
- package/dist/server/LWWServer.js +3 -3
- package/dist/server/OTServer.d.ts +2 -2
- package/dist/server/OTServer.js +2 -3
- package/dist/shared/doc-manager.d.ts +89 -0
- package/dist/shared/doc-manager.js +126 -0
- package/dist/shared/utils.d.ts +22 -0
- package/dist/shared/utils.js +22 -0
- package/dist/solid/doc-manager.d.ts +3 -81
- package/dist/solid/doc-manager.js +1 -120
- package/dist/solid/index.d.ts +4 -2
- package/dist/solid/index.js +4 -0
- package/dist/solid/managed-docs.d.ts +63 -0
- package/dist/solid/managed-docs.js +105 -0
- package/dist/solid/primitives.d.ts +73 -68
- package/dist/solid/primitives.js +111 -4
- package/dist/solid/utils.d.ts +1 -0
- package/dist/solid/utils.js +6 -0
- package/dist/vue/composables.d.ts +67 -35
- package/dist/vue/composables.js +111 -4
- package/dist/vue/doc-manager.d.ts +3 -81
- package/dist/vue/doc-manager.js +1 -120
- package/dist/vue/index.d.ts +4 -2
- package/dist/vue/index.js +4 -0
- package/dist/vue/managed-docs.d.ts +66 -0
- package/dist/vue/managed-docs.js +101 -0
- package/dist/vue/utils.d.ts +1 -0
- package/dist/vue/utils.js +6 -0
- package/package.json +1 -1
- package/dist/net/types.d.ts +0 -8
- package/dist/net/types.js +0 -0
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
import { Accessor } from 'solid-js';
|
|
2
2
|
import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
|
|
3
|
+
import { JSONPatch } from '../json-patch/JSONPatch.js';
|
|
3
4
|
import { ChangeMutator } from '../types.js';
|
|
4
5
|
import '../event-signal.js';
|
|
5
6
|
import '../json-patch/types.js';
|
|
6
|
-
import '../json-patch/JSONPatch.js';
|
|
7
7
|
import '@dabble/delta';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Options for usePatchesDoc primitive.
|
|
10
|
+
* Options for usePatchesDoc primitive (eager mode with docId).
|
|
11
11
|
*/
|
|
12
12
|
interface UsePatchesDocOptions {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Controls document lifecycle management on cleanup.
|
|
15
15
|
*
|
|
16
|
-
* - `false` (default): Assumes doc is already open. Throws if not.
|
|
17
|
-
* - `true`: Opens doc on mount with ref counting, closes on cleanup.
|
|
16
|
+
* - `false` (default): Explicit mode. Assumes doc is already open. Throws if not.
|
|
17
|
+
* - `true`: Opens doc on mount with ref counting, closes on cleanup (doc stays tracked).
|
|
18
|
+
* - `'untrack'`: Opens doc on mount, closes AND untracks on cleanup (removes from sync).
|
|
18
19
|
*
|
|
19
20
|
* @default false
|
|
20
21
|
*/
|
|
21
|
-
autoClose?: boolean;
|
|
22
|
+
autoClose?: boolean | 'untrack';
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
+
* Options for usePatchesDoc primitive (lazy mode without docId).
|
|
26
|
+
*/
|
|
27
|
+
interface UsePatchesDocLazyOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Inject doc.id into state under this key on every state update.
|
|
30
|
+
* Useful when the document ID is derived from the path but needed in the data.
|
|
31
|
+
*/
|
|
32
|
+
idProp?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Return type for usePatchesDoc primitive (eager mode).
|
|
25
36
|
*/
|
|
26
37
|
interface UsePatchesDocReturn<T extends object> {
|
|
27
38
|
/**
|
|
@@ -65,57 +76,69 @@ interface UsePatchesDocReturn<T extends object> {
|
|
|
65
76
|
*/
|
|
66
77
|
doc: Accessor<PatchesDoc<T> | undefined>;
|
|
67
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Return type for usePatchesDoc primitive (lazy mode).
|
|
81
|
+
* Extends the eager return type with lifecycle management methods.
|
|
82
|
+
*/
|
|
83
|
+
interface UsePatchesDocLazyReturn<T extends object> extends UsePatchesDocReturn<T> {
|
|
84
|
+
/**
|
|
85
|
+
* Current document path. `null` when no document is loaded.
|
|
86
|
+
*/
|
|
87
|
+
path: Accessor<string | null>;
|
|
88
|
+
/**
|
|
89
|
+
* Open a document by path. Closes any previously loaded document first.
|
|
90
|
+
*
|
|
91
|
+
* @param docPath - The document path to open
|
|
92
|
+
*/
|
|
93
|
+
load: (docPath: string) => Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Close the current document, unsubscribe, and reset all state.
|
|
96
|
+
* Calls `patches.closeDoc()` but does not untrack — tracking is managed separately.
|
|
97
|
+
*/
|
|
98
|
+
close: () => Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a new document: open it, set initial state, then close it.
|
|
101
|
+
* A one-shot operation that doesn't bind the document to this handle.
|
|
102
|
+
*
|
|
103
|
+
* @param docPath - The document path to create
|
|
104
|
+
* @param initialState - Initial state object or JSONPatch to apply
|
|
105
|
+
*/
|
|
106
|
+
create: (docPath: string, initialState: T | JSONPatch) => Promise<void>;
|
|
107
|
+
}
|
|
68
108
|
/**
|
|
69
109
|
* Solid primitive for reactive Patches document state.
|
|
70
110
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* ## Explicit Lifecycle (default)
|
|
111
|
+
* ## Eager Mode (with docId)
|
|
74
112
|
*
|
|
75
|
-
*
|
|
76
|
-
* You control when documents are opened and closed.
|
|
113
|
+
* Provides reactive access to an already-open Patches document.
|
|
77
114
|
*
|
|
78
115
|
* @example
|
|
79
116
|
* ```tsx
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* function MyComponent(props) {
|
|
84
|
-
* const { patches } = usePatchesContext();
|
|
85
|
-
*
|
|
86
|
-
* // You control lifecycle
|
|
87
|
-
* onMount(() => patches.openDoc(props.docId));
|
|
88
|
-
* onCleanup(() => patches.closeDoc(props.docId));
|
|
89
|
-
*
|
|
90
|
-
* // Just adds reactivity
|
|
91
|
-
* // For static: usePatchesDoc('doc-123')
|
|
92
|
-
* // For reactive: usePatchesDoc(() => props.docId)
|
|
93
|
-
* const { data, loading, change } = usePatchesDoc(() => props.docId);
|
|
117
|
+
* // Explicit lifecycle — you control open/close
|
|
118
|
+
* const { data, loading, change } = usePatchesDoc(() => props.docId)
|
|
94
119
|
*
|
|
95
|
-
*
|
|
96
|
-
* }
|
|
120
|
+
* // Auto lifecycle — opens on mount, closes on cleanup
|
|
121
|
+
* const { data, loading, change } = usePatchesDoc(() => props.docId, { autoClose: true })
|
|
97
122
|
* ```
|
|
98
123
|
*
|
|
99
|
-
* ##
|
|
124
|
+
* ## Lazy Mode (without docId)
|
|
100
125
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
126
|
+
* Returns a deferred handle with `load()`, `close()`, and `create()` methods.
|
|
127
|
+
* Does NOT register `onCleanup` — caller manages lifecycle.
|
|
103
128
|
*
|
|
104
129
|
* @example
|
|
105
130
|
* ```tsx
|
|
106
|
-
*
|
|
107
|
-
* const { data, loading, change } = usePatchesDoc('doc-123', {
|
|
108
|
-
* autoClose: true
|
|
109
|
-
* });
|
|
110
|
-
* ```
|
|
131
|
+
* const { data, load, close, change, create } = usePatchesDoc<Project>()
|
|
111
132
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
133
|
+
* // Later, when the user navigates:
|
|
134
|
+
* await load('projects/abc/content')
|
|
135
|
+
*
|
|
136
|
+
* // When leaving:
|
|
137
|
+
* await close()
|
|
138
|
+
* ```
|
|
117
139
|
*/
|
|
118
140
|
declare function usePatchesDoc<T extends object>(docId: MaybeAccessor<string>, options?: UsePatchesDocOptions): UsePatchesDocReturn<T>;
|
|
141
|
+
declare function usePatchesDoc<T extends object>(options?: UsePatchesDocLazyOptions): UsePatchesDocLazyReturn<T>;
|
|
119
142
|
/**
|
|
120
143
|
* Return type for usePatchesSync primitive.
|
|
121
144
|
*/
|
|
@@ -159,6 +182,14 @@ declare function usePatchesSync(): UsePatchesSyncReturn;
|
|
|
159
182
|
* Type for document ID - can be static string or accessor function.
|
|
160
183
|
*/
|
|
161
184
|
type MaybeAccessor<T> = T | Accessor<T>;
|
|
185
|
+
/**
|
|
186
|
+
* Props for the Provider component returned by createPatchesDoc.
|
|
187
|
+
*/
|
|
188
|
+
interface PatchesDocProviderProps {
|
|
189
|
+
docId: MaybeAccessor<string>;
|
|
190
|
+
autoClose?: boolean | 'untrack';
|
|
191
|
+
children: any;
|
|
192
|
+
}
|
|
162
193
|
/**
|
|
163
194
|
* Creates a named document context that can be provided to child components.
|
|
164
195
|
*
|
|
@@ -199,36 +230,10 @@ type MaybeAccessor<T> = T | Accessor<T>;
|
|
|
199
230
|
*
|
|
200
231
|
* @param name - Unique identifier for this document context (e.g., 'whiteboard', 'user')
|
|
201
232
|
* @returns Object with Provider component and useDoc hook
|
|
202
|
-
*
|
|
203
|
-
* @example
|
|
204
|
-
* ```tsx
|
|
205
|
-
* // Create the context
|
|
206
|
-
* const { Provider: WhiteboardProvider, useDoc: useWhiteboard } =
|
|
207
|
-
* createPatchesDoc<WhiteboardDoc>('whiteboard');
|
|
208
|
-
*
|
|
209
|
-
* // Use in parent
|
|
210
|
-
* <WhiteboardProvider docId="whiteboard-123">
|
|
211
|
-
* <Canvas />
|
|
212
|
-
* </WhiteboardProvider>
|
|
213
|
-
*
|
|
214
|
-
* // Use in child
|
|
215
|
-
* function Canvas() {
|
|
216
|
-
* const { data, change } = useWhiteboard();
|
|
217
|
-
* return <div>{data()?.title}</div>;
|
|
218
|
-
* }
|
|
219
|
-
* ```
|
|
220
233
|
*/
|
|
221
|
-
/**
|
|
222
|
-
* Props for the Provider component returned by createPatchesDoc.
|
|
223
|
-
*/
|
|
224
|
-
interface PatchesDocProviderProps {
|
|
225
|
-
docId: MaybeAccessor<string>;
|
|
226
|
-
autoClose?: boolean;
|
|
227
|
-
children: any;
|
|
228
|
-
}
|
|
229
234
|
declare function createPatchesDoc<T extends object>(name: string): {
|
|
230
235
|
Provider: (props: PatchesDocProviderProps) => any;
|
|
231
236
|
useDoc: () => UsePatchesDocReturn<T>;
|
|
232
237
|
};
|
|
233
238
|
|
|
234
|
-
export { type MaybeAccessor, type PatchesDocProviderProps, type UsePatchesDocOptions, type UsePatchesDocReturn, type UsePatchesSyncReturn, createPatchesDoc, usePatchesDoc, usePatchesSync };
|
|
239
|
+
export { type MaybeAccessor, type PatchesDocProviderProps, type UsePatchesDocLazyOptions, type UsePatchesDocLazyReturn, type UsePatchesDocOptions, type UsePatchesDocReturn, type UsePatchesSyncReturn, createPatchesDoc, usePatchesDoc, usePatchesSync };
|
package/dist/solid/primitives.js
CHANGED
|
@@ -7,11 +7,19 @@ import {
|
|
|
7
7
|
createEffect,
|
|
8
8
|
onCleanup
|
|
9
9
|
} from "solid-js";
|
|
10
|
+
import { JSONPatch } from "../json-patch/JSONPatch.js";
|
|
10
11
|
import { usePatchesContext } from "./context.js";
|
|
11
12
|
import { getDocManager } from "./doc-manager.js";
|
|
12
|
-
function usePatchesDoc(
|
|
13
|
+
function usePatchesDoc(docIdOrOptions, options) {
|
|
14
|
+
if (typeof docIdOrOptions === "string" || typeof docIdOrOptions === "function") {
|
|
15
|
+
return _usePatchesDocEager(docIdOrOptions, options ?? {});
|
|
16
|
+
}
|
|
17
|
+
return _usePatchesDocLazy(docIdOrOptions ?? {});
|
|
18
|
+
}
|
|
19
|
+
function _usePatchesDocEager(docId, options) {
|
|
13
20
|
const { patches } = usePatchesContext();
|
|
14
21
|
const autoClose = options.autoClose ?? false;
|
|
22
|
+
const shouldUntrack = autoClose === "untrack";
|
|
15
23
|
const [doc, setDoc] = createSignal(void 0);
|
|
16
24
|
const [data, setData] = createSignal(void 0);
|
|
17
25
|
const [loading, setLoading] = createSignal(true);
|
|
@@ -52,7 +60,7 @@ function usePatchesDoc(docId, options = {}) {
|
|
|
52
60
|
});
|
|
53
61
|
onCleanup(() => {
|
|
54
62
|
const id = docIdAccessor();
|
|
55
|
-
manager.closeDoc(patches, id);
|
|
63
|
+
manager.closeDoc(patches, id, shouldUntrack);
|
|
56
64
|
});
|
|
57
65
|
} else {
|
|
58
66
|
createEffect(() => {
|
|
@@ -87,6 +95,104 @@ function usePatchesDoc(docId, options = {}) {
|
|
|
87
95
|
doc
|
|
88
96
|
};
|
|
89
97
|
}
|
|
98
|
+
function _usePatchesDocLazy(options) {
|
|
99
|
+
const { patches } = usePatchesContext();
|
|
100
|
+
const { idProp } = options;
|
|
101
|
+
let currentDoc = null;
|
|
102
|
+
let unsubscribe = null;
|
|
103
|
+
const [path, setPath] = createSignal(null);
|
|
104
|
+
const [doc, setDoc] = createSignal(void 0);
|
|
105
|
+
const [data, setData] = createSignal(void 0);
|
|
106
|
+
const [loading, setLoading] = createSignal(false);
|
|
107
|
+
const [error, setError] = createSignal(null);
|
|
108
|
+
const [rev, setRev] = createSignal(0);
|
|
109
|
+
const [hasPending, setHasPending] = createSignal(false);
|
|
110
|
+
function setupDoc(patchesDoc) {
|
|
111
|
+
currentDoc = patchesDoc;
|
|
112
|
+
setDoc(patchesDoc);
|
|
113
|
+
unsubscribe = patchesDoc.subscribe((state) => {
|
|
114
|
+
if (state && idProp && currentDoc) {
|
|
115
|
+
state = { ...state, [idProp]: currentDoc.id };
|
|
116
|
+
}
|
|
117
|
+
setData(() => state);
|
|
118
|
+
setRev(patchesDoc.committedRev);
|
|
119
|
+
setHasPending(patchesDoc.hasPending);
|
|
120
|
+
});
|
|
121
|
+
const unsubSync = patchesDoc.onSyncing((syncState) => {
|
|
122
|
+
setLoading(syncState === "initial" || syncState === "updating");
|
|
123
|
+
setError(syncState instanceof Error ? syncState : null);
|
|
124
|
+
});
|
|
125
|
+
const origUnsub = unsubscribe;
|
|
126
|
+
unsubscribe = () => {
|
|
127
|
+
origUnsub();
|
|
128
|
+
unsubSync();
|
|
129
|
+
};
|
|
130
|
+
setLoading(patchesDoc.syncing !== null);
|
|
131
|
+
}
|
|
132
|
+
function teardown() {
|
|
133
|
+
unsubscribe?.();
|
|
134
|
+
unsubscribe = null;
|
|
135
|
+
currentDoc = null;
|
|
136
|
+
setDoc(void 0);
|
|
137
|
+
setData(void 0);
|
|
138
|
+
setLoading(false);
|
|
139
|
+
setError(null);
|
|
140
|
+
setRev(0);
|
|
141
|
+
setHasPending(false);
|
|
142
|
+
}
|
|
143
|
+
async function load(docPath) {
|
|
144
|
+
if (path()) {
|
|
145
|
+
const prevPath = path();
|
|
146
|
+
teardown();
|
|
147
|
+
await patches.closeDoc(prevPath);
|
|
148
|
+
}
|
|
149
|
+
setPath(docPath);
|
|
150
|
+
try {
|
|
151
|
+
const patchesDoc = await patches.openDoc(docPath);
|
|
152
|
+
setupDoc(patchesDoc);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
setError(err);
|
|
155
|
+
setLoading(false);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function close() {
|
|
159
|
+
if (path()) {
|
|
160
|
+
const prevPath = path();
|
|
161
|
+
teardown();
|
|
162
|
+
setPath(null);
|
|
163
|
+
await patches.closeDoc(prevPath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function create(docPath, initialState) {
|
|
167
|
+
const newDoc = await patches.openDoc(docPath);
|
|
168
|
+
newDoc.change((patch, root) => {
|
|
169
|
+
if (initialState instanceof JSONPatch) {
|
|
170
|
+
patch.ops = initialState.ops;
|
|
171
|
+
} else {
|
|
172
|
+
const state = { ...initialState };
|
|
173
|
+
if (idProp) delete state[idProp];
|
|
174
|
+
patch.replace(root, state);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
await patches.closeDoc(docPath);
|
|
178
|
+
}
|
|
179
|
+
function change(mutator) {
|
|
180
|
+
currentDoc?.change(mutator);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
data,
|
|
184
|
+
loading,
|
|
185
|
+
error,
|
|
186
|
+
rev,
|
|
187
|
+
hasPending,
|
|
188
|
+
change,
|
|
189
|
+
doc,
|
|
190
|
+
path,
|
|
191
|
+
load,
|
|
192
|
+
close,
|
|
193
|
+
create
|
|
194
|
+
};
|
|
195
|
+
}
|
|
90
196
|
function usePatchesSync() {
|
|
91
197
|
const { sync } = usePatchesContext();
|
|
92
198
|
if (!sync) {
|
|
@@ -118,6 +224,7 @@ function createPatchesDoc(name) {
|
|
|
118
224
|
const { patches } = usePatchesContext();
|
|
119
225
|
const manager = getDocManager(patches);
|
|
120
226
|
const autoClose = props.autoClose ?? false;
|
|
227
|
+
const shouldUntrack = autoClose === "untrack";
|
|
121
228
|
const [doc, setDoc] = createSignal(void 0);
|
|
122
229
|
const [data, setData] = createSignal(void 0);
|
|
123
230
|
const [loading, setLoading] = createSignal(true);
|
|
@@ -158,13 +265,13 @@ function createPatchesDoc(name) {
|
|
|
158
265
|
createEffect((prevId) => {
|
|
159
266
|
const currentId = docIdAccessor();
|
|
160
267
|
if (prevId && prevId !== currentId) {
|
|
161
|
-
manager.closeDoc(patches, prevId);
|
|
268
|
+
manager.closeDoc(patches, prevId, shouldUntrack);
|
|
162
269
|
}
|
|
163
270
|
return currentId;
|
|
164
271
|
});
|
|
165
272
|
onCleanup(() => {
|
|
166
273
|
const id = docIdAccessor();
|
|
167
|
-
manager.closeDoc(patches, id);
|
|
274
|
+
manager.closeDoc(patches, id, shouldUntrack);
|
|
168
275
|
});
|
|
169
276
|
} else {
|
|
170
277
|
createEffect((prevId) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { areSetsEqual, fillPath } from '../shared/utils.js';
|
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
import { ShallowRef, Ref, MaybeRef } from 'vue';
|
|
2
2
|
import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
|
|
3
|
+
import { JSONPatch } from '../json-patch/JSONPatch.js';
|
|
3
4
|
import { ChangeMutator } from '../types.js';
|
|
4
5
|
import '../event-signal.js';
|
|
5
6
|
import '../json-patch/types.js';
|
|
6
|
-
import '../json-patch/JSONPatch.js';
|
|
7
7
|
import '@dabble/delta';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Options for usePatchesDoc composable.
|
|
10
|
+
* Options for usePatchesDoc composable (eager mode with docId).
|
|
11
11
|
*/
|
|
12
12
|
interface UsePatchesDocOptions {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Controls document lifecycle management on component unmount.
|
|
15
15
|
*
|
|
16
|
-
* - `false` (default): Assumes doc is already open. Throws if not.
|
|
17
|
-
* - `true`: Opens doc on mount with ref counting, closes on unmount.
|
|
16
|
+
* - `false` (default): Explicit mode. Assumes doc is already open. Throws if not.
|
|
17
|
+
* - `true`: Opens doc on mount with ref counting, closes on unmount (doc stays tracked).
|
|
18
|
+
* - `'untrack'`: Opens doc on mount, closes AND untracks on unmount (removes from sync).
|
|
18
19
|
*
|
|
19
20
|
* @default false
|
|
20
21
|
*/
|
|
21
|
-
autoClose?: boolean;
|
|
22
|
+
autoClose?: boolean | 'untrack';
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
+
* Options for usePatchesDoc composable (lazy mode without docId).
|
|
26
|
+
*/
|
|
27
|
+
interface UsePatchesDocLazyOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Inject doc.id into state under this key on every state update.
|
|
30
|
+
* Useful when the document ID is derived from the path but needed in the data.
|
|
31
|
+
*/
|
|
32
|
+
idProp?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Return type for usePatchesDoc composable (eager mode).
|
|
25
36
|
*/
|
|
26
37
|
interface UsePatchesDocReturn<T extends object> {
|
|
27
38
|
/**
|
|
@@ -65,50 +76,71 @@ interface UsePatchesDocReturn<T extends object> {
|
|
|
65
76
|
*/
|
|
66
77
|
doc: Ref<PatchesDoc<T> | undefined>;
|
|
67
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Return type for usePatchesDoc composable (lazy mode).
|
|
81
|
+
* Extends the eager return type with lifecycle management methods.
|
|
82
|
+
*/
|
|
83
|
+
interface UsePatchesDocLazyReturn<T extends object> extends UsePatchesDocReturn<T> {
|
|
84
|
+
/**
|
|
85
|
+
* Current document path. `null` when no document is loaded.
|
|
86
|
+
*/
|
|
87
|
+
path: Ref<string | null>;
|
|
88
|
+
/**
|
|
89
|
+
* Open a document by path. Closes any previously loaded document first.
|
|
90
|
+
*
|
|
91
|
+
* @param docPath - The document path to open
|
|
92
|
+
*/
|
|
93
|
+
load: (docPath: string) => Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Close the current document, unsubscribe, and reset all state.
|
|
96
|
+
* Calls `patches.closeDoc()` but does not untrack — tracking is managed separately.
|
|
97
|
+
*/
|
|
98
|
+
close: () => Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a new document: open it, set initial state, then close it.
|
|
101
|
+
* A one-shot operation that doesn't bind the document to this handle.
|
|
102
|
+
*
|
|
103
|
+
* @param docPath - The document path to create
|
|
104
|
+
* @param initialState - Initial state object or JSONPatch to apply
|
|
105
|
+
*/
|
|
106
|
+
create: (docPath: string, initialState: T | JSONPatch) => Promise<void>;
|
|
107
|
+
}
|
|
68
108
|
/**
|
|
69
109
|
* Vue composable for reactive Patches document state.
|
|
70
110
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* ## Explicit Lifecycle (default)
|
|
111
|
+
* ## Eager Mode (with docId)
|
|
74
112
|
*
|
|
75
|
-
*
|
|
76
|
-
* You control when documents are opened and closed.
|
|
113
|
+
* Provides reactive access to an already-open Patches document.
|
|
77
114
|
*
|
|
78
115
|
* @example
|
|
79
116
|
* ```typescript
|
|
80
|
-
* //
|
|
81
|
-
* const
|
|
82
|
-
* const { patches } = usePatchesContext()
|
|
83
|
-
*
|
|
84
|
-
* // You control lifecycle
|
|
85
|
-
* onMounted(() => patches.openDoc(props.docId))
|
|
86
|
-
* onBeforeUnmount(() => patches.closeDoc(props.docId))
|
|
117
|
+
* // Explicit lifecycle — you control open/close
|
|
118
|
+
* const { data, loading, change } = usePatchesDoc('doc-123')
|
|
87
119
|
*
|
|
88
|
-
* //
|
|
89
|
-
* const { data, loading, change } = usePatchesDoc(
|
|
120
|
+
* // Auto lifecycle — opens on mount, closes on unmount
|
|
121
|
+
* const { data, loading, change } = usePatchesDoc('doc-123', { autoClose: true })
|
|
90
122
|
* ```
|
|
91
123
|
*
|
|
92
|
-
* ##
|
|
124
|
+
* ## Lazy Mode (without docId)
|
|
93
125
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
126
|
+
* Returns a deferred handle with `load()`, `close()`, and `create()` methods.
|
|
127
|
+
* Ideal for Pinia stores where the document path isn't known at creation time.
|
|
128
|
+
* Does NOT use `onBeforeUnmount` — caller manages lifecycle.
|
|
96
129
|
*
|
|
97
130
|
* @example
|
|
98
131
|
* ```typescript
|
|
99
|
-
* //
|
|
100
|
-
* const { data,
|
|
101
|
-
* autoClose: true
|
|
102
|
-
* })
|
|
103
|
-
* ```
|
|
132
|
+
* // In a Pinia store
|
|
133
|
+
* const { data, load, close, change, create } = usePatchesDoc<Project>()
|
|
104
134
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
135
|
+
* // Later, when the user navigates:
|
|
136
|
+
* await load('projects/abc/content')
|
|
137
|
+
*
|
|
138
|
+
* // When leaving:
|
|
139
|
+
* await close()
|
|
140
|
+
* ```
|
|
110
141
|
*/
|
|
111
142
|
declare function usePatchesDoc<T extends object>(docId: string, options?: UsePatchesDocOptions): UsePatchesDocReturn<T>;
|
|
143
|
+
declare function usePatchesDoc<T extends object>(options?: UsePatchesDocLazyOptions): UsePatchesDocLazyReturn<T>;
|
|
112
144
|
/**
|
|
113
145
|
* Return type for usePatchesSync composable.
|
|
114
146
|
*/
|
|
@@ -203,4 +235,4 @@ declare function providePatchesDoc<T extends object>(name: string, docId: MaybeR
|
|
|
203
235
|
*/
|
|
204
236
|
declare function useCurrentDoc<T extends object>(name: string): UsePatchesDocReturn<T>;
|
|
205
237
|
|
|
206
|
-
export { type UsePatchesDocOptions, type UsePatchesDocReturn, type UsePatchesSyncReturn, providePatchesDoc, useCurrentDoc, usePatchesDoc, usePatchesSync };
|
|
238
|
+
export { type UsePatchesDocLazyOptions, type UsePatchesDocLazyReturn, type UsePatchesDocOptions, type UsePatchesDocReturn, type UsePatchesSyncReturn, providePatchesDoc, useCurrentDoc, usePatchesDoc, usePatchesSync };
|
package/dist/vue/composables.js
CHANGED
|
@@ -8,11 +8,19 @@ import {
|
|
|
8
8
|
provide,
|
|
9
9
|
inject
|
|
10
10
|
} from "vue";
|
|
11
|
+
import { JSONPatch } from "../json-patch/JSONPatch.js";
|
|
11
12
|
import { usePatchesContext } from "./provider.js";
|
|
12
13
|
import { getDocManager } from "./doc-manager.js";
|
|
13
|
-
function usePatchesDoc(
|
|
14
|
+
function usePatchesDoc(docIdOrOptions, options) {
|
|
15
|
+
if (typeof docIdOrOptions === "string") {
|
|
16
|
+
return _usePatchesDocEager(docIdOrOptions, options ?? {});
|
|
17
|
+
}
|
|
18
|
+
return _usePatchesDocLazy(docIdOrOptions ?? {});
|
|
19
|
+
}
|
|
20
|
+
function _usePatchesDocEager(docId, options) {
|
|
14
21
|
const { patches } = usePatchesContext();
|
|
15
22
|
const { autoClose = false } = options;
|
|
23
|
+
const shouldUntrack = autoClose === "untrack";
|
|
16
24
|
const doc = ref(void 0);
|
|
17
25
|
const data = shallowRef(void 0);
|
|
18
26
|
const loading = ref(true);
|
|
@@ -45,7 +53,7 @@ function usePatchesDoc(docId, options = {}) {
|
|
|
45
53
|
});
|
|
46
54
|
onBeforeUnmount(() => {
|
|
47
55
|
unsubscribers.forEach((unsub) => unsub());
|
|
48
|
-
manager.closeDoc(patches, docId);
|
|
56
|
+
manager.closeDoc(patches, docId, shouldUntrack);
|
|
49
57
|
});
|
|
50
58
|
} else {
|
|
51
59
|
const patchesDoc = patches.getOpenDoc(docId);
|
|
@@ -77,6 +85,104 @@ function usePatchesDoc(docId, options = {}) {
|
|
|
77
85
|
doc
|
|
78
86
|
};
|
|
79
87
|
}
|
|
88
|
+
function _usePatchesDocLazy(options) {
|
|
89
|
+
const { patches } = usePatchesContext();
|
|
90
|
+
const { idProp } = options;
|
|
91
|
+
let currentDoc = null;
|
|
92
|
+
let unsubscribe = null;
|
|
93
|
+
const path = ref(null);
|
|
94
|
+
const doc = ref(void 0);
|
|
95
|
+
const data = shallowRef(void 0);
|
|
96
|
+
const loading = ref(false);
|
|
97
|
+
const error = ref(null);
|
|
98
|
+
const rev = ref(0);
|
|
99
|
+
const hasPending = ref(false);
|
|
100
|
+
function setupDoc(patchesDoc) {
|
|
101
|
+
currentDoc = patchesDoc;
|
|
102
|
+
doc.value = patchesDoc;
|
|
103
|
+
unsubscribe = patchesDoc.subscribe((state) => {
|
|
104
|
+
if (state && idProp && currentDoc) {
|
|
105
|
+
state = { ...state, [idProp]: currentDoc.id };
|
|
106
|
+
}
|
|
107
|
+
data.value = state;
|
|
108
|
+
rev.value = patchesDoc.committedRev;
|
|
109
|
+
hasPending.value = patchesDoc.hasPending;
|
|
110
|
+
});
|
|
111
|
+
const unsubSync = patchesDoc.onSyncing((syncState) => {
|
|
112
|
+
loading.value = syncState === "initial" || syncState === "updating";
|
|
113
|
+
error.value = syncState instanceof Error ? syncState : null;
|
|
114
|
+
});
|
|
115
|
+
const origUnsub = unsubscribe;
|
|
116
|
+
unsubscribe = () => {
|
|
117
|
+
origUnsub();
|
|
118
|
+
unsubSync();
|
|
119
|
+
};
|
|
120
|
+
loading.value = patchesDoc.syncing !== null;
|
|
121
|
+
}
|
|
122
|
+
function teardown() {
|
|
123
|
+
unsubscribe?.();
|
|
124
|
+
unsubscribe = null;
|
|
125
|
+
currentDoc = null;
|
|
126
|
+
doc.value = void 0;
|
|
127
|
+
data.value = void 0;
|
|
128
|
+
loading.value = false;
|
|
129
|
+
error.value = null;
|
|
130
|
+
rev.value = 0;
|
|
131
|
+
hasPending.value = false;
|
|
132
|
+
}
|
|
133
|
+
async function load(docPath) {
|
|
134
|
+
if (path.value) {
|
|
135
|
+
const prevPath = path.value;
|
|
136
|
+
teardown();
|
|
137
|
+
await patches.closeDoc(prevPath);
|
|
138
|
+
}
|
|
139
|
+
path.value = docPath;
|
|
140
|
+
try {
|
|
141
|
+
const patchesDoc = await patches.openDoc(docPath);
|
|
142
|
+
setupDoc(patchesDoc);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
error.value = err;
|
|
145
|
+
loading.value = false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function close() {
|
|
149
|
+
if (path.value) {
|
|
150
|
+
const prevPath = path.value;
|
|
151
|
+
teardown();
|
|
152
|
+
path.value = null;
|
|
153
|
+
await patches.closeDoc(prevPath);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function create(docPath, initialState) {
|
|
157
|
+
const newDoc = await patches.openDoc(docPath);
|
|
158
|
+
newDoc.change((patch, root) => {
|
|
159
|
+
if (initialState instanceof JSONPatch) {
|
|
160
|
+
patch.ops = initialState.ops;
|
|
161
|
+
} else {
|
|
162
|
+
const state = { ...initialState };
|
|
163
|
+
if (idProp) delete state[idProp];
|
|
164
|
+
patch.replace(root, state);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
await patches.closeDoc(docPath);
|
|
168
|
+
}
|
|
169
|
+
function change(mutator) {
|
|
170
|
+
currentDoc?.change(mutator);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
data,
|
|
174
|
+
loading,
|
|
175
|
+
error,
|
|
176
|
+
rev,
|
|
177
|
+
hasPending,
|
|
178
|
+
change,
|
|
179
|
+
doc,
|
|
180
|
+
path,
|
|
181
|
+
load,
|
|
182
|
+
close,
|
|
183
|
+
create
|
|
184
|
+
};
|
|
185
|
+
}
|
|
80
186
|
function usePatchesSync() {
|
|
81
187
|
const { sync } = usePatchesContext();
|
|
82
188
|
if (!sync) {
|
|
@@ -105,6 +211,7 @@ function createDocInjectionKey(name) {
|
|
|
105
211
|
function providePatchesDoc(name, docId, options = {}) {
|
|
106
212
|
const { patches } = usePatchesContext();
|
|
107
213
|
const { autoClose = false } = options;
|
|
214
|
+
const shouldUntrack = autoClose === "untrack";
|
|
108
215
|
const manager = getDocManager(patches);
|
|
109
216
|
const doc = ref(void 0);
|
|
110
217
|
const data = shallowRef(void 0);
|
|
@@ -181,7 +288,7 @@ function providePatchesDoc(name, docId, options = {}) {
|
|
|
181
288
|
unsubscribers.forEach((unsub) => unsub());
|
|
182
289
|
unsubscribers.length = 0;
|
|
183
290
|
if (autoClose) {
|
|
184
|
-
await manager.closeDoc(patches, oldDocId);
|
|
291
|
+
await manager.closeDoc(patches, oldDocId, shouldUntrack);
|
|
185
292
|
} else {
|
|
186
293
|
manager.decrementRefCount(oldDocId);
|
|
187
294
|
}
|
|
@@ -191,7 +298,7 @@ function providePatchesDoc(name, docId, options = {}) {
|
|
|
191
298
|
onBeforeUnmount(async () => {
|
|
192
299
|
unsubscribers.forEach((unsub) => unsub());
|
|
193
300
|
if (autoClose) {
|
|
194
|
-
await manager.closeDoc(patches, currentDocId.value);
|
|
301
|
+
await manager.closeDoc(patches, currentDocId.value, shouldUntrack);
|
|
195
302
|
} else {
|
|
196
303
|
manager.decrementRefCount(currentDocId.value);
|
|
197
304
|
}
|