@dabble/patches 0.5.6 → 0.5.7
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/algorithms/shared/changeBatching.js +4 -1
- package/dist/client/PatchesDoc.js +7 -1
- package/dist/net/PatchesSync.d.ts +6 -6
- package/dist/solid/context.d.ts +67 -0
- package/dist/solid/context.js +20 -0
- package/dist/solid/doc-manager.d.ts +88 -0
- package/dist/solid/doc-manager.js +125 -0
- package/dist/solid/index.d.ts +19 -0
- package/dist/solid/index.js +17 -0
- package/dist/solid/primitives.d.ts +235 -0
- package/dist/solid/primitives.js +226 -0
- package/dist/vue/composables.d.ts +207 -0
- package/dist/vue/composables.js +216 -0
- package/dist/vue/doc-manager.d.ts +88 -0
- package/dist/vue/doc-manager.js +125 -0
- package/dist/vue/index.d.ts +19 -0
- package/dist/vue/index.js +28 -0
- package/dist/vue/provider.d.ts +89 -0
- package/dist/vue/provider.js +27 -0
- package/package.json +29 -5
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Patches } from '../client/Patches.js';
|
|
2
|
+
import { PatchesDoc } from '../client/PatchesDoc.js';
|
|
3
|
+
import '../event-signal.js';
|
|
4
|
+
import '../types.js';
|
|
5
|
+
import '../json-patch/JSONPatch.js';
|
|
6
|
+
import '@dabble/delta';
|
|
7
|
+
import '../json-patch/types.js';
|
|
8
|
+
import '../client/PatchesStore.js';
|
|
9
|
+
import '../algorithms/shared/changeBatching.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Reference counting manager for PatchesDoc instances.
|
|
13
|
+
*
|
|
14
|
+
* Tracks how many Vue components are using each document and only opens/closes
|
|
15
|
+
* documents when the reference count goes to/from zero.
|
|
16
|
+
*
|
|
17
|
+
* This prevents the footgun where multiple components open the same doc but the
|
|
18
|
+
* first one to unmount closes it for everyone else.
|
|
19
|
+
*/
|
|
20
|
+
declare class DocManager {
|
|
21
|
+
private refCounts;
|
|
22
|
+
private pendingOps;
|
|
23
|
+
/**
|
|
24
|
+
* Opens a document with reference counting.
|
|
25
|
+
*
|
|
26
|
+
* - If this is the first reference, calls patches.openDoc()
|
|
27
|
+
* - If doc is already open, returns existing instance and increments count
|
|
28
|
+
* - Handles concurrent opens to the same doc safely
|
|
29
|
+
*
|
|
30
|
+
* @param patches - Patches instance
|
|
31
|
+
* @param docId - Document ID to open
|
|
32
|
+
* @returns Promise resolving to PatchesDoc instance
|
|
33
|
+
*/
|
|
34
|
+
openDoc<T extends object>(patches: Patches, docId: string): Promise<PatchesDoc<T>>;
|
|
35
|
+
/**
|
|
36
|
+
* Closes a document with reference counting.
|
|
37
|
+
*
|
|
38
|
+
* - Decrements the reference count
|
|
39
|
+
* - Only calls patches.closeDoc() when count reaches zero
|
|
40
|
+
* - Safe to call even if doc was never opened
|
|
41
|
+
*
|
|
42
|
+
* @param patches - Patches instance
|
|
43
|
+
* @param docId - Document ID to close
|
|
44
|
+
*/
|
|
45
|
+
closeDoc(patches: Patches, docId: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Increments the reference count for a document without opening it.
|
|
48
|
+
*
|
|
49
|
+
* Used in explicit mode to track usage and prevent premature closes
|
|
50
|
+
* from autoClose mode.
|
|
51
|
+
*
|
|
52
|
+
* @param docId - Document ID
|
|
53
|
+
*/
|
|
54
|
+
incrementRefCount(docId: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Decrements the reference count for a document without closing it.
|
|
57
|
+
*
|
|
58
|
+
* Used in explicit mode to release usage tracking.
|
|
59
|
+
*
|
|
60
|
+
* @param docId - Document ID
|
|
61
|
+
*/
|
|
62
|
+
decrementRefCount(docId: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Gets the current reference count for a document.
|
|
65
|
+
*
|
|
66
|
+
* Useful for debugging or advanced use cases.
|
|
67
|
+
*
|
|
68
|
+
* @param docId - Document ID
|
|
69
|
+
* @returns Current reference count (0 if not tracked)
|
|
70
|
+
*/
|
|
71
|
+
getRefCount(docId: string): number;
|
|
72
|
+
/**
|
|
73
|
+
* Clears all reference counts without closing documents.
|
|
74
|
+
*
|
|
75
|
+
* Use with caution - this is mainly for testing or cleanup scenarios
|
|
76
|
+
* where you want to reset the manager state.
|
|
77
|
+
*/
|
|
78
|
+
reset(): void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Gets or creates a DocManager for a Patches instance.
|
|
82
|
+
*
|
|
83
|
+
* @param patches - Patches instance
|
|
84
|
+
* @returns DocManager for this Patches instance
|
|
85
|
+
*/
|
|
86
|
+
declare function getDocManager(patches: Patches): DocManager;
|
|
87
|
+
|
|
88
|
+
export { DocManager, getDocManager };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import "../chunk-IZ2YBCUP.js";
|
|
2
|
+
class DocManager {
|
|
3
|
+
refCounts = /* @__PURE__ */ new Map();
|
|
4
|
+
pendingOps = /* @__PURE__ */ new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Opens a document with reference counting.
|
|
7
|
+
*
|
|
8
|
+
* - If this is the first reference, calls patches.openDoc()
|
|
9
|
+
* - If doc is already open, returns existing instance and increments count
|
|
10
|
+
* - Handles concurrent opens to the same doc safely
|
|
11
|
+
*
|
|
12
|
+
* @param patches - Patches instance
|
|
13
|
+
* @param docId - Document ID to open
|
|
14
|
+
* @returns Promise resolving to PatchesDoc instance
|
|
15
|
+
*/
|
|
16
|
+
async openDoc(patches, docId) {
|
|
17
|
+
const currentCount = this.refCounts.get(docId) || 0;
|
|
18
|
+
if (currentCount === 0 && this.pendingOps.has(docId)) {
|
|
19
|
+
const doc = await this.pendingOps.get(docId);
|
|
20
|
+
this.refCounts.set(docId, (this.refCounts.get(docId) || 0) + 1);
|
|
21
|
+
return doc;
|
|
22
|
+
}
|
|
23
|
+
if (currentCount > 0) {
|
|
24
|
+
this.refCounts.set(docId, currentCount + 1);
|
|
25
|
+
const doc = patches.getOpenDoc(docId);
|
|
26
|
+
if (!doc) {
|
|
27
|
+
throw new Error(`Document ${docId} has ref count ${currentCount} but is not open in Patches`);
|
|
28
|
+
}
|
|
29
|
+
return doc;
|
|
30
|
+
}
|
|
31
|
+
const openPromise = patches.openDoc(docId);
|
|
32
|
+
this.pendingOps.set(docId, openPromise);
|
|
33
|
+
try {
|
|
34
|
+
const doc = await openPromise;
|
|
35
|
+
this.refCounts.set(docId, 1);
|
|
36
|
+
return doc;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
this.refCounts.delete(docId);
|
|
39
|
+
throw error;
|
|
40
|
+
} finally {
|
|
41
|
+
this.pendingOps.delete(docId);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Closes a document with reference counting.
|
|
46
|
+
*
|
|
47
|
+
* - Decrements the reference count
|
|
48
|
+
* - Only calls patches.closeDoc() when count reaches zero
|
|
49
|
+
* - Safe to call even if doc was never opened
|
|
50
|
+
*
|
|
51
|
+
* @param patches - Patches instance
|
|
52
|
+
* @param docId - Document ID to close
|
|
53
|
+
*/
|
|
54
|
+
async closeDoc(patches, docId) {
|
|
55
|
+
const currentCount = this.refCounts.get(docId) || 0;
|
|
56
|
+
if (currentCount === 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (currentCount === 1) {
|
|
60
|
+
this.refCounts.delete(docId);
|
|
61
|
+
await patches.closeDoc(docId);
|
|
62
|
+
} else {
|
|
63
|
+
this.refCounts.set(docId, currentCount - 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Increments the reference count for a document without opening it.
|
|
68
|
+
*
|
|
69
|
+
* Used in explicit mode to track usage and prevent premature closes
|
|
70
|
+
* from autoClose mode.
|
|
71
|
+
*
|
|
72
|
+
* @param docId - Document ID
|
|
73
|
+
*/
|
|
74
|
+
incrementRefCount(docId) {
|
|
75
|
+
const currentCount = this.refCounts.get(docId) || 0;
|
|
76
|
+
this.refCounts.set(docId, currentCount + 1);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Decrements the reference count for a document without closing it.
|
|
80
|
+
*
|
|
81
|
+
* Used in explicit mode to release usage tracking.
|
|
82
|
+
*
|
|
83
|
+
* @param docId - Document ID
|
|
84
|
+
*/
|
|
85
|
+
decrementRefCount(docId) {
|
|
86
|
+
const currentCount = this.refCounts.get(docId) || 0;
|
|
87
|
+
if (currentCount > 0) {
|
|
88
|
+
this.refCounts.set(docId, currentCount - 1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Gets the current reference count for a document.
|
|
93
|
+
*
|
|
94
|
+
* Useful for debugging or advanced use cases.
|
|
95
|
+
*
|
|
96
|
+
* @param docId - Document ID
|
|
97
|
+
* @returns Current reference count (0 if not tracked)
|
|
98
|
+
*/
|
|
99
|
+
getRefCount(docId) {
|
|
100
|
+
return this.refCounts.get(docId) || 0;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Clears all reference counts without closing documents.
|
|
104
|
+
*
|
|
105
|
+
* Use with caution - this is mainly for testing or cleanup scenarios
|
|
106
|
+
* where you want to reset the manager state.
|
|
107
|
+
*/
|
|
108
|
+
reset() {
|
|
109
|
+
this.refCounts.clear();
|
|
110
|
+
this.pendingOps.clear();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const managers = /* @__PURE__ */ new WeakMap();
|
|
114
|
+
function getDocManager(patches) {
|
|
115
|
+
let manager = managers.get(patches);
|
|
116
|
+
if (!manager) {
|
|
117
|
+
manager = new DocManager();
|
|
118
|
+
managers.set(patches, manager);
|
|
119
|
+
}
|
|
120
|
+
return manager;
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
DocManager,
|
|
124
|
+
getDocManager
|
|
125
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { PATCHES_KEY, PATCHES_SYNC_KEY, PatchesContext, providePatches, providePatchesContext, usePatchesContext } from './provider.js';
|
|
2
|
+
export { UsePatchesDocOptions, UsePatchesDocReturn, UsePatchesSyncReturn, providePatchesDoc, useCurrentDoc, usePatchesDoc, usePatchesSync } from './composables.js';
|
|
3
|
+
export { DocManager, getDocManager } from './doc-manager.js';
|
|
4
|
+
import 'vue';
|
|
5
|
+
import '../client/Patches.js';
|
|
6
|
+
import '../event-signal.js';
|
|
7
|
+
import '../types.js';
|
|
8
|
+
import '../json-patch/JSONPatch.js';
|
|
9
|
+
import '@dabble/delta';
|
|
10
|
+
import '../json-patch/types.js';
|
|
11
|
+
import '../client/PatchesDoc.js';
|
|
12
|
+
import '../algorithms/shared/changeBatching.js';
|
|
13
|
+
import '../client/PatchesStore.js';
|
|
14
|
+
import '../net/PatchesSync.js';
|
|
15
|
+
import '../net/protocol/JSONRPCClient.js';
|
|
16
|
+
import '../net/protocol/types.js';
|
|
17
|
+
import '../net/websocket/PatchesWebSocket.js';
|
|
18
|
+
import '../net/PatchesClient.js';
|
|
19
|
+
import '../net/websocket/WebSocketTransport.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import "../chunk-IZ2YBCUP.js";
|
|
2
|
+
import {
|
|
3
|
+
PATCHES_KEY,
|
|
4
|
+
PATCHES_SYNC_KEY,
|
|
5
|
+
providePatchesContext,
|
|
6
|
+
providePatches,
|
|
7
|
+
usePatchesContext
|
|
8
|
+
} from "./provider.js";
|
|
9
|
+
import {
|
|
10
|
+
usePatchesDoc,
|
|
11
|
+
usePatchesSync,
|
|
12
|
+
providePatchesDoc,
|
|
13
|
+
useCurrentDoc
|
|
14
|
+
} from "./composables.js";
|
|
15
|
+
import { DocManager, getDocManager } from "./doc-manager.js";
|
|
16
|
+
export {
|
|
17
|
+
DocManager,
|
|
18
|
+
PATCHES_KEY,
|
|
19
|
+
PATCHES_SYNC_KEY,
|
|
20
|
+
getDocManager,
|
|
21
|
+
providePatches,
|
|
22
|
+
providePatchesContext,
|
|
23
|
+
providePatchesDoc,
|
|
24
|
+
useCurrentDoc,
|
|
25
|
+
usePatchesContext,
|
|
26
|
+
usePatchesDoc,
|
|
27
|
+
usePatchesSync
|
|
28
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { InjectionKey, App } from 'vue';
|
|
2
|
+
import { Patches } from '../client/Patches.js';
|
|
3
|
+
import { PatchesSync } from '../net/PatchesSync.js';
|
|
4
|
+
import '../event-signal.js';
|
|
5
|
+
import '../types.js';
|
|
6
|
+
import '../json-patch/JSONPatch.js';
|
|
7
|
+
import '@dabble/delta';
|
|
8
|
+
import '../json-patch/types.js';
|
|
9
|
+
import '../client/PatchesDoc.js';
|
|
10
|
+
import '../algorithms/shared/changeBatching.js';
|
|
11
|
+
import '../client/PatchesStore.js';
|
|
12
|
+
import '../net/protocol/JSONRPCClient.js';
|
|
13
|
+
import '../net/protocol/types.js';
|
|
14
|
+
import '../net/websocket/PatchesWebSocket.js';
|
|
15
|
+
import '../net/PatchesClient.js';
|
|
16
|
+
import '../net/websocket/WebSocketTransport.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Injection key for Patches instance.
|
|
20
|
+
*/
|
|
21
|
+
declare const PATCHES_KEY: InjectionKey<Patches>;
|
|
22
|
+
/**
|
|
23
|
+
* Injection key for PatchesSync instance (optional).
|
|
24
|
+
*/
|
|
25
|
+
declare const PATCHES_SYNC_KEY: InjectionKey<PatchesSync | undefined>;
|
|
26
|
+
/**
|
|
27
|
+
* Context containing Patches and optional PatchesSync instances.
|
|
28
|
+
*/
|
|
29
|
+
interface PatchesContext {
|
|
30
|
+
patches: Patches;
|
|
31
|
+
sync?: PatchesSync;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Provides Patches context to the Vue app.
|
|
35
|
+
*
|
|
36
|
+
* Call this in your app setup to make Patches available to all components via inject.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* import { createApp } from 'vue'
|
|
41
|
+
* import { Patches, InMemoryStore } from '@dabble/patches/client'
|
|
42
|
+
* import { PatchesSync } from '@dabble/patches/net'
|
|
43
|
+
* import { providePatchesContext } from '@dabble/patches/vue'
|
|
44
|
+
*
|
|
45
|
+
* const patches = new Patches({ store: new InMemoryStore() })
|
|
46
|
+
* const sync = new PatchesSync(patches, 'wss://your-server.com')
|
|
47
|
+
*
|
|
48
|
+
* const app = createApp(App)
|
|
49
|
+
* providePatchesContext(app, patches, sync)
|
|
50
|
+
* app.mount('#app')
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @param app - Vue app instance
|
|
54
|
+
* @param patches - Patches instance
|
|
55
|
+
* @param sync - Optional PatchesSync instance for network synchronization
|
|
56
|
+
*/
|
|
57
|
+
declare function providePatchesContext(app: App, patches: Patches, sync?: PatchesSync): void;
|
|
58
|
+
/**
|
|
59
|
+
* Provides Patches context within a component setup function.
|
|
60
|
+
*
|
|
61
|
+
* Use this instead of providePatchesContext when providing from a component
|
|
62
|
+
* rather than at the app level.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* // In a component setup
|
|
67
|
+
* providePatches(patches, sync)
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function providePatches(patches: Patches, sync?: PatchesSync): void;
|
|
71
|
+
/**
|
|
72
|
+
* Gets the injected Patches context.
|
|
73
|
+
*
|
|
74
|
+
* Throws an error if Patches has not been provided.
|
|
75
|
+
*
|
|
76
|
+
* @returns The Patches context
|
|
77
|
+
* @throws Error if Patches context has not been provided
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* const { patches, sync } = usePatchesContext()
|
|
82
|
+
*
|
|
83
|
+
* // Use patches to open/close docs
|
|
84
|
+
* await patches.openDoc('doc-123')
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function usePatchesContext(): PatchesContext;
|
|
88
|
+
|
|
89
|
+
export { PATCHES_KEY, PATCHES_SYNC_KEY, type PatchesContext, providePatches, providePatchesContext, usePatchesContext };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import "../chunk-IZ2YBCUP.js";
|
|
2
|
+
import { inject, provide } from "vue";
|
|
3
|
+
const PATCHES_KEY = Symbol("patches");
|
|
4
|
+
const PATCHES_SYNC_KEY = Symbol("patches-sync");
|
|
5
|
+
function providePatchesContext(app, patches, sync) {
|
|
6
|
+
app.provide(PATCHES_KEY, patches);
|
|
7
|
+
app.provide(PATCHES_SYNC_KEY, sync);
|
|
8
|
+
}
|
|
9
|
+
function providePatches(patches, sync) {
|
|
10
|
+
provide(PATCHES_KEY, patches);
|
|
11
|
+
provide(PATCHES_SYNC_KEY, sync);
|
|
12
|
+
}
|
|
13
|
+
function usePatchesContext() {
|
|
14
|
+
const patches = inject(PATCHES_KEY);
|
|
15
|
+
const sync = inject(PATCHES_SYNC_KEY);
|
|
16
|
+
if (!patches) {
|
|
17
|
+
throw new Error("Patches context not found. Did you forget to call providePatchesContext() in your app setup?");
|
|
18
|
+
}
|
|
19
|
+
return { patches, sync };
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
PATCHES_KEY,
|
|
23
|
+
PATCHES_SYNC_KEY,
|
|
24
|
+
providePatches,
|
|
25
|
+
providePatchesContext,
|
|
26
|
+
usePatchesContext
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dabble/patches",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
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": {
|
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
"./compression": {
|
|
34
34
|
"import": "./dist/compression/index.js",
|
|
35
35
|
"types": "./dist/compression/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./vue": {
|
|
38
|
+
"import": "./dist/vue/index.js",
|
|
39
|
+
"types": "./dist/vue/index.d.ts"
|
|
40
|
+
},
|
|
41
|
+
"./solid": {
|
|
42
|
+
"import": "./dist/solid/index.js",
|
|
43
|
+
"types": "./dist/solid/index.d.ts"
|
|
36
44
|
}
|
|
37
45
|
},
|
|
38
46
|
"files": [
|
|
@@ -60,8 +68,8 @@
|
|
|
60
68
|
"prepare": "npm run build",
|
|
61
69
|
"test": "vitest run",
|
|
62
70
|
"tdd": "vitest",
|
|
63
|
-
"format": "prettier --write src/",
|
|
64
|
-
"format:check": "prettier --check src/",
|
|
71
|
+
"format": "prettier --write src/ tests/ docs/",
|
|
72
|
+
"format:check": "prettier --check src/ tests/ docs/",
|
|
65
73
|
"type:check": "tsc --noEmit",
|
|
66
74
|
"lint": "eslint src tests",
|
|
67
75
|
"lint:fix": "eslint src tests --fix"
|
|
@@ -79,9 +87,25 @@
|
|
|
79
87
|
"@typescript-eslint/parser": "^8.48.0",
|
|
80
88
|
"eslint": "^9.39.1",
|
|
81
89
|
"fake-indexeddb": "^6.2.5",
|
|
90
|
+
"happy-dom": "^20.1.0",
|
|
82
91
|
"prettier": "^3.7.3",
|
|
92
|
+
"solid-js": "^1.9.3",
|
|
83
93
|
"tsup": "^8.5.1",
|
|
84
94
|
"typescript": "^5.9.3",
|
|
85
|
-
"
|
|
95
|
+
"vite-plugin-solid": "^2.11.10",
|
|
96
|
+
"vitest": "^4.0.14",
|
|
97
|
+
"vue": "^3.5.26"
|
|
98
|
+
},
|
|
99
|
+
"peerDependencies": {
|
|
100
|
+
"solid-js": "^1.8.0",
|
|
101
|
+
"vue": "^3.3.0"
|
|
102
|
+
},
|
|
103
|
+
"peerDependenciesMeta": {
|
|
104
|
+
"vue": {
|
|
105
|
+
"optional": true
|
|
106
|
+
},
|
|
107
|
+
"solid-js": {
|
|
108
|
+
"optional": true
|
|
109
|
+
}
|
|
86
110
|
}
|
|
87
|
-
}
|
|
111
|
+
}
|