@prabhask5/stellar-engine 1.1.18 → 1.2.0

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 (73) hide show
  1. package/README.md +55 -1
  2. package/dist/bin/install-pwa.js +50 -0
  3. package/dist/bin/install-pwa.js.map +1 -1
  4. package/dist/config.d.ts +11 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +8 -2
  7. package/dist/config.js.map +1 -1
  8. package/dist/crdt/awareness.d.ts +128 -0
  9. package/dist/crdt/awareness.d.ts.map +1 -0
  10. package/dist/crdt/awareness.js +284 -0
  11. package/dist/crdt/awareness.js.map +1 -0
  12. package/dist/crdt/channel.d.ts +165 -0
  13. package/dist/crdt/channel.d.ts.map +1 -0
  14. package/dist/crdt/channel.js +522 -0
  15. package/dist/crdt/channel.js.map +1 -0
  16. package/dist/crdt/config.d.ts +58 -0
  17. package/dist/crdt/config.d.ts.map +1 -0
  18. package/dist/crdt/config.js +123 -0
  19. package/dist/crdt/config.js.map +1 -0
  20. package/dist/crdt/helpers.d.ts +104 -0
  21. package/dist/crdt/helpers.d.ts.map +1 -0
  22. package/dist/crdt/helpers.js +116 -0
  23. package/dist/crdt/helpers.js.map +1 -0
  24. package/dist/crdt/offline.d.ts +58 -0
  25. package/dist/crdt/offline.d.ts.map +1 -0
  26. package/dist/crdt/offline.js +130 -0
  27. package/dist/crdt/offline.js.map +1 -0
  28. package/dist/crdt/persistence.d.ts +65 -0
  29. package/dist/crdt/persistence.d.ts.map +1 -0
  30. package/dist/crdt/persistence.js +171 -0
  31. package/dist/crdt/persistence.js.map +1 -0
  32. package/dist/crdt/provider.d.ts +109 -0
  33. package/dist/crdt/provider.d.ts.map +1 -0
  34. package/dist/crdt/provider.js +543 -0
  35. package/dist/crdt/provider.js.map +1 -0
  36. package/dist/crdt/store.d.ts +111 -0
  37. package/dist/crdt/store.d.ts.map +1 -0
  38. package/dist/crdt/store.js +158 -0
  39. package/dist/crdt/store.js.map +1 -0
  40. package/dist/crdt/types.d.ts +281 -0
  41. package/dist/crdt/types.d.ts.map +1 -0
  42. package/dist/crdt/types.js +26 -0
  43. package/dist/crdt/types.js.map +1 -0
  44. package/dist/database.d.ts +1 -1
  45. package/dist/database.d.ts.map +1 -1
  46. package/dist/database.js +28 -7
  47. package/dist/database.js.map +1 -1
  48. package/dist/diagnostics.d.ts +75 -0
  49. package/dist/diagnostics.d.ts.map +1 -1
  50. package/dist/diagnostics.js +114 -2
  51. package/dist/diagnostics.js.map +1 -1
  52. package/dist/engine.d.ts.map +1 -1
  53. package/dist/engine.js +21 -1
  54. package/dist/engine.js.map +1 -1
  55. package/dist/entries/crdt.d.ts +32 -0
  56. package/dist/entries/crdt.d.ts.map +1 -0
  57. package/dist/entries/crdt.js +52 -0
  58. package/dist/entries/crdt.js.map +1 -0
  59. package/dist/entries/types.d.ts +1 -0
  60. package/dist/entries/types.d.ts.map +1 -1
  61. package/dist/index.d.ts +3 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +7 -0
  64. package/dist/index.js.map +1 -1
  65. package/package.json +7 -2
  66. package/dist/operations.d.ts +0 -73
  67. package/dist/operations.d.ts.map +0 -1
  68. package/dist/operations.js +0 -227
  69. package/dist/operations.js.map +0 -1
  70. package/dist/reconnectHandler.d.ts +0 -16
  71. package/dist/reconnectHandler.d.ts.map +0 -1
  72. package/dist/reconnectHandler.js +0 -21
  73. package/dist/reconnectHandler.js.map +0 -1
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview CRDT Configuration Singleton
3
+ *
4
+ * Manages the resolved CRDT configuration singleton. The raw {@link CRDTConfig}
5
+ * (with optional fields) is provided by the consumer via `initEngine({ crdt: ... })`.
6
+ * This module applies sensible defaults and stores the result as a
7
+ * {@link ResolvedCRDTConfig} singleton, accessible to all other CRDT modules
8
+ * via {@link getCRDTConfig}.
9
+ *
10
+ * Initialization flow:
11
+ * 1. Consumer calls `initEngine({ crdt: { ... } })`
12
+ * 2. `config.ts#initEngine` calls `_initCRDT(rawConfig, prefix)`
13
+ * 3. This module merges defaults → stores singleton
14
+ * 4. Other CRDT modules call `getCRDTConfig()` to read the resolved config
15
+ *
16
+ * If `initEngine()` is called without a `crdt` field, the singleton remains
17
+ * `null` and `getCRDTConfig()` throws with a descriptive error message.
18
+ *
19
+ * @see {@link ../config.ts} for the `initEngine()` entry point
20
+ * @see {@link ./types.ts} for the config interfaces
21
+ */
22
+ // =============================================================================
23
+ // Default Values
24
+ // =============================================================================
25
+ /**
26
+ * Default values for all optional CRDT configuration fields.
27
+ *
28
+ * These defaults are tuned for a typical collaborative document editor:
29
+ * - 30s persist interval balances durability vs. Supabase write costs
30
+ * - 100ms broadcast debounce merges rapid keystrokes without noticeable lag
31
+ * - 5s local save debounce provides crash recovery with minimal IndexedDB churn
32
+ * - 50ms cursor debounce keeps presence smooth without flooding the channel
33
+ * - 250KB chunk threshold stays well below Supabase's ~1MB Broadcast limit
34
+ */
35
+ const DEFAULTS = {
36
+ supabaseTable: 'crdt_documents',
37
+ columns: 'id,page_id,state,state_vector,state_size,device_id,updated_at,created_at',
38
+ persistIntervalMs: 30000,
39
+ broadcastDebounceMs: 100,
40
+ localSaveDebounceMs: 5000,
41
+ cursorDebounceMs: 50,
42
+ maxOfflineDocuments: 50,
43
+ maxBroadcastPayloadBytes: 250000,
44
+ syncPeerTimeoutMs: 3000,
45
+ maxReconnectAttempts: 5,
46
+ reconnectBaseDelayMs: 1000
47
+ };
48
+ // =============================================================================
49
+ // Module State
50
+ // =============================================================================
51
+ /** The resolved config singleton (set by {@link _initCRDT}). */
52
+ let resolvedConfig = null;
53
+ /** The application prefix (e.g., 'myapp'), used for channel naming. */
54
+ let crdtPrefix = '';
55
+ // =============================================================================
56
+ // Initialization
57
+ // =============================================================================
58
+ /**
59
+ * Initialize the CRDT configuration singleton.
60
+ *
61
+ * Called internally by {@link ../config.ts#initEngine} when `config.crdt` is
62
+ * provided. Merges user-provided values with defaults and stores the result.
63
+ *
64
+ * @param rawConfig - The user-provided CRDT config (with optional fields).
65
+ * @param prefix - The application prefix from `SyncEngineConfig.prefix`.
66
+ * @internal
67
+ */
68
+ export function _initCRDT(rawConfig, prefix) {
69
+ crdtPrefix = prefix;
70
+ resolvedConfig = {
71
+ supabaseTable: rawConfig.supabaseTable ?? DEFAULTS.supabaseTable,
72
+ columns: rawConfig.columns ?? DEFAULTS.columns,
73
+ persistIntervalMs: rawConfig.persistIntervalMs ?? DEFAULTS.persistIntervalMs,
74
+ broadcastDebounceMs: rawConfig.broadcastDebounceMs ?? DEFAULTS.broadcastDebounceMs,
75
+ localSaveDebounceMs: rawConfig.localSaveDebounceMs ?? DEFAULTS.localSaveDebounceMs,
76
+ cursorDebounceMs: rawConfig.cursorDebounceMs ?? DEFAULTS.cursorDebounceMs,
77
+ maxOfflineDocuments: rawConfig.maxOfflineDocuments ?? DEFAULTS.maxOfflineDocuments,
78
+ maxBroadcastPayloadBytes: rawConfig.maxBroadcastPayloadBytes ?? DEFAULTS.maxBroadcastPayloadBytes,
79
+ syncPeerTimeoutMs: rawConfig.syncPeerTimeoutMs ?? DEFAULTS.syncPeerTimeoutMs,
80
+ maxReconnectAttempts: rawConfig.maxReconnectAttempts ?? DEFAULTS.maxReconnectAttempts,
81
+ reconnectBaseDelayMs: rawConfig.reconnectBaseDelayMs ?? DEFAULTS.reconnectBaseDelayMs
82
+ };
83
+ }
84
+ // =============================================================================
85
+ // Accessors
86
+ // =============================================================================
87
+ /**
88
+ * Get the resolved CRDT configuration.
89
+ *
90
+ * @throws {Error} If CRDT was not configured in `initEngine()`.
91
+ * @returns The fully resolved {@link ResolvedCRDTConfig} with all defaults applied.
92
+ */
93
+ export function getCRDTConfig() {
94
+ if (!resolvedConfig) {
95
+ throw new Error('CRDT not configured. Add crdt to your initEngine() config.');
96
+ }
97
+ return resolvedConfig;
98
+ }
99
+ /**
100
+ * Get the application prefix for use in channel naming and storage keys.
101
+ *
102
+ * @throws {Error} If CRDT was not configured in `initEngine()`.
103
+ * @returns The prefix string (e.g., `'myapp'`).
104
+ */
105
+ export function getCRDTPrefix() {
106
+ if (!resolvedConfig) {
107
+ throw new Error('CRDT not configured. Add crdt to your initEngine() config.');
108
+ }
109
+ return crdtPrefix;
110
+ }
111
+ /**
112
+ * Check whether the CRDT subsystem has been initialized.
113
+ *
114
+ * Unlike {@link getCRDTConfig}, this does not throw — it returns a boolean.
115
+ * Used by conditional code paths that need to check CRDT availability
116
+ * without triggering an error (e.g., sign-out cleanup).
117
+ *
118
+ * @returns `true` if `_initCRDT()` has been called.
119
+ */
120
+ export function isCRDTEnabled() {
121
+ return resolvedConfig !== null;
122
+ }
123
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/crdt/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,gFAAgF;AAChF,kBAAkB;AAClB,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,QAAQ,GAAuB;IACnC,aAAa,EAAE,gBAAgB;IAC/B,OAAO,EAAE,0EAA0E;IACnF,iBAAiB,EAAE,KAAM;IACzB,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,IAAK;IAC1B,gBAAgB,EAAE,EAAE;IACpB,mBAAmB,EAAE,EAAE;IACvB,wBAAwB,EAAE,MAAO;IACjC,iBAAiB,EAAE,IAAK;IACxB,oBAAoB,EAAE,CAAC;IACvB,oBAAoB,EAAE,IAAK;CAC5B,CAAC;AAEF,gFAAgF;AAChF,gBAAgB;AAChB,gFAAgF;AAEhF,gEAAgE;AAChE,IAAI,cAAc,GAA8B,IAAI,CAAC;AAErD,uEAAuE;AACvE,IAAI,UAAU,GAAG,EAAE,CAAC;AAEpB,gFAAgF;AAChF,kBAAkB;AAClB,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,SAAqB,EAAE,MAAc;IAC7D,UAAU,GAAG,MAAM,CAAC;IACpB,cAAc,GAAG;QACf,aAAa,EAAE,SAAS,CAAC,aAAa,IAAI,QAAQ,CAAC,aAAa;QAChE,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO;QAC9C,iBAAiB,EAAE,SAAS,CAAC,iBAAiB,IAAI,QAAQ,CAAC,iBAAiB;QAC5E,mBAAmB,EAAE,SAAS,CAAC,mBAAmB,IAAI,QAAQ,CAAC,mBAAmB;QAClF,mBAAmB,EAAE,SAAS,CAAC,mBAAmB,IAAI,QAAQ,CAAC,mBAAmB;QAClF,gBAAgB,EAAE,SAAS,CAAC,gBAAgB,IAAI,QAAQ,CAAC,gBAAgB;QACzE,mBAAmB,EAAE,SAAS,CAAC,mBAAmB,IAAI,QAAQ,CAAC,mBAAmB;QAClF,wBAAwB,EACtB,SAAS,CAAC,wBAAwB,IAAI,QAAQ,CAAC,wBAAwB;QACzE,iBAAiB,EAAE,SAAS,CAAC,iBAAiB,IAAI,QAAQ,CAAC,iBAAiB;QAC5E,oBAAoB,EAAE,SAAS,CAAC,oBAAoB,IAAI,QAAQ,CAAC,oBAAoB;QACrF,oBAAoB,EAAE,SAAS,CAAC,oBAAoB,IAAI,QAAQ,CAAC,oBAAoB;KACtF,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF;;;;;GAKG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,cAAc,KAAK,IAAI,CAAC;AACjC,CAAC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @fileoverview CRDT Document Type Factories and Yjs Re-exports
3
+ *
4
+ * Provides convenience factory functions for creating shared data types within
5
+ * a Yjs document. Consumers use these instead of importing `yjs` directly,
6
+ * which keeps `yjs` as an internal dependency of the engine.
7
+ *
8
+ * Each factory function takes a `Y.Doc` and a type name, and returns the
9
+ * corresponding shared type instance. If the type already exists in the doc
10
+ * (e.g., from a previous session or a remote peer), the existing instance is
11
+ * returned — Yjs shared types are singletons keyed by name within a doc.
12
+ *
13
+ * The "block document" factory ({@link createBlockDocument}) sets up a standard
14
+ * structure for Notion-style block editors: an `XmlFragment` for the content
15
+ * tree and a `Map` for per-block metadata.
16
+ *
17
+ * @see {@link ./provider.ts} for the orchestrator that creates Y.Doc instances
18
+ * @see {@link ./types.ts} for the TypeScript types
19
+ *
20
+ * @example
21
+ * import { openDocument, createSharedText } from '@prabhask5/stellar-engine/crdt';
22
+ *
23
+ * const provider = await openDocument('doc-1', 'page-1');
24
+ * const title = createSharedText(provider.doc, 'title');
25
+ * title.insert(0, 'Hello, World!');
26
+ */
27
+ import * as Y from 'yjs';
28
+ /**
29
+ * Get or create a shared `Y.Text` within a Yjs document.
30
+ *
31
+ * `Y.Text` supports rich text with formatting attributes (bold, italic, etc.)
32
+ * and is the standard type for collaborative text editors.
33
+ *
34
+ * @param doc - The Yjs document instance.
35
+ * @param name - The shared type name (unique within the document). Default: `'text'`.
36
+ * @returns The `Y.Text` instance, either existing or newly created.
37
+ *
38
+ * @example
39
+ * const text = createSharedText(doc, 'title');
40
+ * text.insert(0, 'My Page Title');
41
+ */
42
+ export declare function createSharedText(doc: Y.Doc, name?: string): Y.Text;
43
+ /**
44
+ * Get or create a shared `Y.XmlFragment` within a Yjs document.
45
+ *
46
+ * `Y.XmlFragment` is the standard container for block-based editors
47
+ * (Prosemirror, Tiptap, BlockNote). It represents a tree of XML elements
48
+ * that maps to the editor's document model.
49
+ *
50
+ * @param doc - The Yjs document instance.
51
+ * @param name - The shared type name. Default: `'content'`.
52
+ * @returns The `Y.XmlFragment` instance.
53
+ *
54
+ * @example
55
+ * const content = createSharedXmlFragment(doc, 'content');
56
+ * // Use with Tiptap: new Editor({ extensions: [Collaboration.configure({ fragment: content })] })
57
+ */
58
+ export declare function createSharedXmlFragment(doc: Y.Doc, name?: string): Y.XmlFragment;
59
+ /**
60
+ * Get or create a shared `Y.Array` within a Yjs document.
61
+ *
62
+ * `Y.Array` is a CRDT list type suitable for ordered collections
63
+ * (e.g., a list of block IDs, kanban columns, or comment threads).
64
+ *
65
+ * @param doc - The Yjs document instance.
66
+ * @param name - The shared type name. Default: `'array'`.
67
+ * @returns The `Y.Array` instance.
68
+ */
69
+ export declare function createSharedArray<T>(doc: Y.Doc, name?: string): Y.Array<T>;
70
+ /**
71
+ * Get or create a shared `Y.Map` within a Yjs document.
72
+ *
73
+ * `Y.Map` is a CRDT key-value map suitable for document metadata,
74
+ * settings, or per-block properties.
75
+ *
76
+ * @param doc - The Yjs document instance.
77
+ * @param name - The shared type name. Default: `'map'`.
78
+ * @returns The `Y.Map` instance.
79
+ */
80
+ export declare function createSharedMap<T>(doc: Y.Doc, name?: string): Y.Map<T>;
81
+ /**
82
+ * Set up a standard "block document" structure within a Yjs document.
83
+ *
84
+ * Creates two shared types commonly used by Notion-style block editors:
85
+ * - `content` (`Y.XmlFragment`) — The block tree (paragraphs, headings, lists, etc.)
86
+ * - `meta` (`Y.Map`) — Per-document metadata (title, icon, cover, properties, etc.)
87
+ *
88
+ * This is a convenience function — consumers can also create these types
89
+ * individually using the other factory functions.
90
+ *
91
+ * @param doc - The Yjs document instance.
92
+ * @returns An object with `content` and `meta` shared types.
93
+ *
94
+ * @example
95
+ * const provider = await openDocument('doc-1', 'page-1');
96
+ * const { content, meta } = createBlockDocument(provider.doc);
97
+ * meta.set('title', 'My Page');
98
+ * // Pass `content` to your block editor's collaboration extension
99
+ */
100
+ export declare function createBlockDocument(doc: Y.Doc): {
101
+ content: Y.XmlFragment;
102
+ meta: Y.Map<unknown>;
103
+ };
104
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/crdt/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAMzB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,SAAS,GAAG,CAAC,CAAC,IAAI,CAElE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,SAAY,GAAG,CAAC,CAAC,WAAW,CAEnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,SAAU,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAE3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,SAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAErE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG;IAC/C,OAAO,EAAE,CAAC,CAAC,WAAW,CAAC;IACvB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;CACtB,CAKA"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @fileoverview CRDT Document Type Factories and Yjs Re-exports
3
+ *
4
+ * Provides convenience factory functions for creating shared data types within
5
+ * a Yjs document. Consumers use these instead of importing `yjs` directly,
6
+ * which keeps `yjs` as an internal dependency of the engine.
7
+ *
8
+ * Each factory function takes a `Y.Doc` and a type name, and returns the
9
+ * corresponding shared type instance. If the type already exists in the doc
10
+ * (e.g., from a previous session or a remote peer), the existing instance is
11
+ * returned — Yjs shared types are singletons keyed by name within a doc.
12
+ *
13
+ * The "block document" factory ({@link createBlockDocument}) sets up a standard
14
+ * structure for Notion-style block editors: an `XmlFragment` for the content
15
+ * tree and a `Map` for per-block metadata.
16
+ *
17
+ * @see {@link ./provider.ts} for the orchestrator that creates Y.Doc instances
18
+ * @see {@link ./types.ts} for the TypeScript types
19
+ *
20
+ * @example
21
+ * import { openDocument, createSharedText } from '@prabhask5/stellar-engine/crdt';
22
+ *
23
+ * const provider = await openDocument('doc-1', 'page-1');
24
+ * const title = createSharedText(provider.doc, 'title');
25
+ * title.insert(0, 'Hello, World!');
26
+ */
27
+ // =============================================================================
28
+ // Shared Type Factories
29
+ // =============================================================================
30
+ /**
31
+ * Get or create a shared `Y.Text` within a Yjs document.
32
+ *
33
+ * `Y.Text` supports rich text with formatting attributes (bold, italic, etc.)
34
+ * and is the standard type for collaborative text editors.
35
+ *
36
+ * @param doc - The Yjs document instance.
37
+ * @param name - The shared type name (unique within the document). Default: `'text'`.
38
+ * @returns The `Y.Text` instance, either existing or newly created.
39
+ *
40
+ * @example
41
+ * const text = createSharedText(doc, 'title');
42
+ * text.insert(0, 'My Page Title');
43
+ */
44
+ export function createSharedText(doc, name = 'text') {
45
+ return doc.getText(name);
46
+ }
47
+ /**
48
+ * Get or create a shared `Y.XmlFragment` within a Yjs document.
49
+ *
50
+ * `Y.XmlFragment` is the standard container for block-based editors
51
+ * (Prosemirror, Tiptap, BlockNote). It represents a tree of XML elements
52
+ * that maps to the editor's document model.
53
+ *
54
+ * @param doc - The Yjs document instance.
55
+ * @param name - The shared type name. Default: `'content'`.
56
+ * @returns The `Y.XmlFragment` instance.
57
+ *
58
+ * @example
59
+ * const content = createSharedXmlFragment(doc, 'content');
60
+ * // Use with Tiptap: new Editor({ extensions: [Collaboration.configure({ fragment: content })] })
61
+ */
62
+ export function createSharedXmlFragment(doc, name = 'content') {
63
+ return doc.getXmlFragment(name);
64
+ }
65
+ /**
66
+ * Get or create a shared `Y.Array` within a Yjs document.
67
+ *
68
+ * `Y.Array` is a CRDT list type suitable for ordered collections
69
+ * (e.g., a list of block IDs, kanban columns, or comment threads).
70
+ *
71
+ * @param doc - The Yjs document instance.
72
+ * @param name - The shared type name. Default: `'array'`.
73
+ * @returns The `Y.Array` instance.
74
+ */
75
+ export function createSharedArray(doc, name = 'array') {
76
+ return doc.getArray(name);
77
+ }
78
+ /**
79
+ * Get or create a shared `Y.Map` within a Yjs document.
80
+ *
81
+ * `Y.Map` is a CRDT key-value map suitable for document metadata,
82
+ * settings, or per-block properties.
83
+ *
84
+ * @param doc - The Yjs document instance.
85
+ * @param name - The shared type name. Default: `'map'`.
86
+ * @returns The `Y.Map` instance.
87
+ */
88
+ export function createSharedMap(doc, name = 'map') {
89
+ return doc.getMap(name);
90
+ }
91
+ /**
92
+ * Set up a standard "block document" structure within a Yjs document.
93
+ *
94
+ * Creates two shared types commonly used by Notion-style block editors:
95
+ * - `content` (`Y.XmlFragment`) — The block tree (paragraphs, headings, lists, etc.)
96
+ * - `meta` (`Y.Map`) — Per-document metadata (title, icon, cover, properties, etc.)
97
+ *
98
+ * This is a convenience function — consumers can also create these types
99
+ * individually using the other factory functions.
100
+ *
101
+ * @param doc - The Yjs document instance.
102
+ * @returns An object with `content` and `meta` shared types.
103
+ *
104
+ * @example
105
+ * const provider = await openDocument('doc-1', 'page-1');
106
+ * const { content, meta } = createBlockDocument(provider.doc);
107
+ * meta.set('title', 'My Page');
108
+ * // Pass `content` to your block editor's collaboration extension
109
+ */
110
+ export function createBlockDocument(doc) {
111
+ return {
112
+ content: doc.getXmlFragment('content'),
113
+ meta: doc.getMap('meta')
114
+ };
115
+ }
116
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/crdt/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,gFAAgF;AAChF,yBAAyB;AACzB,gFAAgF;AAEhF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAU,EAAE,IAAI,GAAG,MAAM;IACxD,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAU,EAAE,IAAI,GAAG,SAAS;IAClE,OAAO,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAI,GAAU,EAAE,IAAI,GAAG,OAAO;IAC7D,OAAO,GAAG,CAAC,QAAQ,CAAI,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAI,GAAU,EAAE,IAAI,GAAG,KAAK;IACzD,OAAO,GAAG,CAAC,MAAM,CAAI,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAU;IAI5C,OAAO;QACL,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,SAAS,CAAC;QACtC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;KACzB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @fileoverview CRDT Offline Document Management
3
+ *
4
+ * Controls which CRDT documents are available for offline access. Offline-enabled
5
+ * documents have their full Yjs state persisted to IndexedDB, allowing editing
6
+ * even without network connectivity.
7
+ *
8
+ * Key behaviors:
9
+ * - {@link enableOffline} — Mark a document for offline access (stores state locally)
10
+ * - {@link disableOffline} — Remove a document from offline storage
11
+ * - Enforces `maxOfflineDocuments` limit (default: 50)
12
+ * - If the document is currently open, saves its current state immediately
13
+ * - If not open but online, fetches from Supabase and saves locally
14
+ * - If not open and offline, returns an error (can't fetch remote state)
15
+ *
16
+ * Non-offline documents opened while offline return `null` from the provider,
17
+ * signaling to the consumer app that the document is unavailable offline.
18
+ * Non-offline documents opened while online exist only in memory (no IndexedDB).
19
+ *
20
+ * @see {@link ./store.ts} for IndexedDB CRUD and offline queries
21
+ * @see {@link ./provider.ts} for document lifecycle
22
+ * @see {@link ./config.ts} for `maxOfflineDocuments` configuration
23
+ */
24
+ /**
25
+ * Enable offline access for a CRDT document.
26
+ *
27
+ * Persists the document's current Yjs state to IndexedDB so it can be loaded
28
+ * and edited without network connectivity. If the document is currently open
29
+ * in a provider, its live state is saved. If not open but online, the state
30
+ * is fetched from Supabase.
31
+ *
32
+ * @param pageId - The page/entity this document belongs to.
33
+ * @param documentId - The unique document identifier.
34
+ *
35
+ * @throws {Error} If the offline document limit has been reached.
36
+ * @throws {Error} If the document is not open and the device is offline.
37
+ *
38
+ * @example
39
+ * await enableOffline('page-1', 'doc-1');
40
+ * // Document is now available offline
41
+ */
42
+ export declare function enableOffline(pageId: string, documentId: string): Promise<void>;
43
+ /**
44
+ * Disable offline access for a CRDT document.
45
+ *
46
+ * Removes the document and all its pending updates from IndexedDB.
47
+ * If the document is currently open in a provider, it continues to work
48
+ * in memory but will no longer persist to IndexedDB.
49
+ *
50
+ * @param pageId - The page/entity this document belongs to (unused, kept for API consistency).
51
+ * @param documentId - The document to remove from offline storage.
52
+ *
53
+ * @example
54
+ * await disableOffline('page-1', 'doc-1');
55
+ * // Document is no longer available offline
56
+ */
57
+ export declare function disableOffline(_pageId: string, documentId: string): Promise<void>;
58
+ //# sourceMappingURL=offline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline.d.ts","sourceRoot":"","sources":["../../src/crdt/offline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAmBH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ErF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvF"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @fileoverview CRDT Offline Document Management
3
+ *
4
+ * Controls which CRDT documents are available for offline access. Offline-enabled
5
+ * documents have their full Yjs state persisted to IndexedDB, allowing editing
6
+ * even without network connectivity.
7
+ *
8
+ * Key behaviors:
9
+ * - {@link enableOffline} — Mark a document for offline access (stores state locally)
10
+ * - {@link disableOffline} — Remove a document from offline storage
11
+ * - Enforces `maxOfflineDocuments` limit (default: 50)
12
+ * - If the document is currently open, saves its current state immediately
13
+ * - If not open but online, fetches from Supabase and saves locally
14
+ * - If not open and offline, returns an error (can't fetch remote state)
15
+ *
16
+ * Non-offline documents opened while offline return `null` from the provider,
17
+ * signaling to the consumer app that the document is unavailable offline.
18
+ * Non-offline documents opened while online exist only in memory (no IndexedDB).
19
+ *
20
+ * @see {@link ./store.ts} for IndexedDB CRUD and offline queries
21
+ * @see {@link ./provider.ts} for document lifecycle
22
+ * @see {@link ./config.ts} for `maxOfflineDocuments` configuration
23
+ */
24
+ import * as Y from 'yjs';
25
+ import { debugLog, debugWarn } from '../debug';
26
+ import { getCRDTConfig } from './config';
27
+ import { getActiveProvider } from './provider';
28
+ import { fetchRemoteState } from './persistence';
29
+ import { saveDocumentState, deleteDocumentState, getOfflineDocumentCount, loadDocumentState } from './store';
30
+ // =============================================================================
31
+ // Public API
32
+ // =============================================================================
33
+ /**
34
+ * Enable offline access for a CRDT document.
35
+ *
36
+ * Persists the document's current Yjs state to IndexedDB so it can be loaded
37
+ * and edited without network connectivity. If the document is currently open
38
+ * in a provider, its live state is saved. If not open but online, the state
39
+ * is fetched from Supabase.
40
+ *
41
+ * @param pageId - The page/entity this document belongs to.
42
+ * @param documentId - The unique document identifier.
43
+ *
44
+ * @throws {Error} If the offline document limit has been reached.
45
+ * @throws {Error} If the document is not open and the device is offline.
46
+ *
47
+ * @example
48
+ * await enableOffline('page-1', 'doc-1');
49
+ * // Document is now available offline
50
+ */
51
+ export async function enableOffline(pageId, documentId) {
52
+ const config = getCRDTConfig();
53
+ /* Check if already enabled. */
54
+ const existing = await loadDocumentState(documentId);
55
+ if (existing?.offlineEnabled === 1) {
56
+ debugLog(`[CRDT] Document ${documentId} is already offline-enabled`);
57
+ return;
58
+ }
59
+ /* Enforce max offline documents limit. */
60
+ const currentCount = await getOfflineDocumentCount();
61
+ if (currentCount >= config.maxOfflineDocuments) {
62
+ const msg = `Offline limit reached (${config.maxOfflineDocuments}), cannot enable for ${documentId}`;
63
+ debugWarn(`[CRDT] ${msg}`);
64
+ throw new Error(msg);
65
+ }
66
+ /* If provider is active, save its current state. */
67
+ const provider = getActiveProvider(documentId);
68
+ if (provider) {
69
+ const state = Y.encodeStateAsUpdate(provider.doc);
70
+ const stateVector = Y.encodeStateVector(provider.doc);
71
+ const record = {
72
+ documentId,
73
+ pageId,
74
+ state,
75
+ stateVector,
76
+ offlineEnabled: 1,
77
+ localUpdatedAt: new Date().toISOString(),
78
+ lastPersistedAt: existing?.lastPersistedAt ?? null,
79
+ stateSize: state.byteLength
80
+ };
81
+ await saveDocumentState(record);
82
+ debugLog(`[CRDT] Offline enabled for document ${documentId} (${currentCount + 1}/${config.maxOfflineDocuments} offline docs)`);
83
+ return;
84
+ }
85
+ /* Not open — try to fetch from Supabase if online. */
86
+ if (typeof navigator !== 'undefined' && navigator.onLine) {
87
+ const remoteState = await fetchRemoteState(pageId);
88
+ if (remoteState) {
89
+ /* Create a temporary doc to extract the state vector. */
90
+ const tempDoc = new Y.Doc();
91
+ Y.applyUpdate(tempDoc, remoteState);
92
+ const stateVector = Y.encodeStateVector(tempDoc);
93
+ tempDoc.destroy();
94
+ const record = {
95
+ documentId,
96
+ pageId,
97
+ state: remoteState,
98
+ stateVector,
99
+ offlineEnabled: 1,
100
+ localUpdatedAt: new Date().toISOString(),
101
+ lastPersistedAt: new Date().toISOString(),
102
+ stateSize: remoteState.byteLength
103
+ };
104
+ await saveDocumentState(record);
105
+ debugLog(`[CRDT] Offline enabled for document ${documentId} (${currentCount + 1}/${config.maxOfflineDocuments} offline docs)`);
106
+ return;
107
+ }
108
+ }
109
+ /* Offline and not open — can't enable. */
110
+ throw new Error(`Cannot enable offline for document ${documentId}: not currently open and device is offline.`);
111
+ }
112
+ /**
113
+ * Disable offline access for a CRDT document.
114
+ *
115
+ * Removes the document and all its pending updates from IndexedDB.
116
+ * If the document is currently open in a provider, it continues to work
117
+ * in memory but will no longer persist to IndexedDB.
118
+ *
119
+ * @param pageId - The page/entity this document belongs to (unused, kept for API consistency).
120
+ * @param documentId - The document to remove from offline storage.
121
+ *
122
+ * @example
123
+ * await disableOffline('page-1', 'doc-1');
124
+ * // Document is no longer available offline
125
+ */
126
+ export async function disableOffline(_pageId, documentId) {
127
+ await deleteDocumentState(documentId);
128
+ debugLog(`[CRDT] Offline disabled for document ${documentId}`);
129
+ }
130
+ //# sourceMappingURL=offline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline.js","sourceRoot":"","sources":["../../src/crdt/offline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @fileoverview CRDT Supabase Persistence
3
+ *
4
+ * Handles durable persistence of Yjs document state to the Supabase
5
+ * `crdt_documents` table. This is the long-term storage layer — the
6
+ * "source of truth" that survives device loss, browser data clearing,
7
+ * and cross-device sync.
8
+ *
9
+ * Key behaviors:
10
+ * - {@link persistDocument} — Upserts full Yjs state to Supabase
11
+ * - {@link persistAllDirty} — Persists all active dirty documents
12
+ * - {@link fetchRemoteState} — Fetches latest state for initial load / sync
13
+ * - Binary state is base64-encoded for Supabase REST transport
14
+ * - Skips unchanged documents (compares state vectors)
15
+ * - Updates `lastPersistedAt` in IndexedDB on success
16
+ * - Clears pending updates after successful persist
17
+ *
18
+ * Timing:
19
+ * - Periodic persist: every `persistIntervalMs` (default 30s)
20
+ * - On document close (if dirty and online)
21
+ * - On offline → online reconnection (immediate)
22
+ *
23
+ * @see {@link ./provider.ts} for the timer that triggers periodic persists
24
+ * @see {@link ./store.ts} for IndexedDB operations
25
+ * @see {@link ./config.ts} for timing configuration
26
+ */
27
+ import * as Y from 'yjs';
28
+ /**
29
+ * Persist a Yjs document's full state to Supabase.
30
+ *
31
+ * Performs an upsert: if a row for this `page_id` already exists, it is
32
+ * updated; otherwise a new row is created. The upsert key is `page_id`
33
+ * (unique per user via RLS).
34
+ *
35
+ * On success:
36
+ * - Clears `crdtPendingUpdates` for this document in IndexedDB
37
+ * - Updates `lastPersistedAt` in the local `crdtDocuments` record
38
+ *
39
+ * @param documentId - The document identifier (for logging and IndexedDB updates).
40
+ * @param doc - The Yjs document to persist.
41
+ *
42
+ * @throws {Error} If the Supabase upsert fails.
43
+ */
44
+ export declare function persistDocument(documentId: string, doc: Y.Doc): Promise<void>;
45
+ /**
46
+ * Persist all active dirty documents to Supabase.
47
+ *
48
+ * Iterates all active providers, checks if they are dirty, and persists
49
+ * each one. Errors are caught per-document to avoid one failure blocking others.
50
+ *
51
+ * Useful as a manual "save all" action or for pre-close cleanup.
52
+ */
53
+ export declare function persistAllDirty(): Promise<void>;
54
+ /**
55
+ * Fetch the latest CRDT document state from Supabase by page ID.
56
+ *
57
+ * Used during `openDocument` when no local state exists and the device is
58
+ * online. Returns the raw Yjs binary state ready to be applied via
59
+ * `Y.applyUpdate(doc, state)`.
60
+ *
61
+ * @param pageId - The page/entity ID to fetch the document for.
62
+ * @returns The Yjs state as a `Uint8Array`, or `null` if no document exists.
63
+ */
64
+ export declare function fetchRemoteState(pageId: string): Promise<Uint8Array | null>;
65
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/crdt/persistence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAuCzB;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CnF;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBrD;AAMD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAqBjF"}