@prabhask5/stellar-engine 1.1.18 → 1.2.1

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 (95) hide show
  1. package/README.md +448 -321
  2. package/dist/bin/commands.d.ts +14 -0
  3. package/dist/bin/commands.d.ts.map +1 -0
  4. package/dist/bin/commands.js +68 -0
  5. package/dist/bin/commands.js.map +1 -0
  6. package/dist/bin/install-pwa.d.ts +20 -6
  7. package/dist/bin/install-pwa.d.ts.map +1 -1
  8. package/dist/bin/install-pwa.js +111 -184
  9. package/dist/bin/install-pwa.js.map +1 -1
  10. package/dist/config.d.ts +67 -11
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +265 -24
  13. package/dist/config.js.map +1 -1
  14. package/dist/crdt/awareness.d.ts +128 -0
  15. package/dist/crdt/awareness.d.ts.map +1 -0
  16. package/dist/crdt/awareness.js +284 -0
  17. package/dist/crdt/awareness.js.map +1 -0
  18. package/dist/crdt/channel.d.ts +165 -0
  19. package/dist/crdt/channel.d.ts.map +1 -0
  20. package/dist/crdt/channel.js +522 -0
  21. package/dist/crdt/channel.js.map +1 -0
  22. package/dist/crdt/config.d.ts +58 -0
  23. package/dist/crdt/config.d.ts.map +1 -0
  24. package/dist/crdt/config.js +123 -0
  25. package/dist/crdt/config.js.map +1 -0
  26. package/dist/crdt/helpers.d.ts +104 -0
  27. package/dist/crdt/helpers.d.ts.map +1 -0
  28. package/dist/crdt/helpers.js +116 -0
  29. package/dist/crdt/helpers.js.map +1 -0
  30. package/dist/crdt/offline.d.ts +58 -0
  31. package/dist/crdt/offline.d.ts.map +1 -0
  32. package/dist/crdt/offline.js +130 -0
  33. package/dist/crdt/offline.js.map +1 -0
  34. package/dist/crdt/persistence.d.ts +65 -0
  35. package/dist/crdt/persistence.d.ts.map +1 -0
  36. package/dist/crdt/persistence.js +171 -0
  37. package/dist/crdt/persistence.js.map +1 -0
  38. package/dist/crdt/provider.d.ts +109 -0
  39. package/dist/crdt/provider.d.ts.map +1 -0
  40. package/dist/crdt/provider.js +543 -0
  41. package/dist/crdt/provider.js.map +1 -0
  42. package/dist/crdt/store.d.ts +111 -0
  43. package/dist/crdt/store.d.ts.map +1 -0
  44. package/dist/crdt/store.js +158 -0
  45. package/dist/crdt/store.js.map +1 -0
  46. package/dist/crdt/types.d.ts +281 -0
  47. package/dist/crdt/types.d.ts.map +1 -0
  48. package/dist/crdt/types.js +26 -0
  49. package/dist/crdt/types.js.map +1 -0
  50. package/dist/database.d.ts +66 -1
  51. package/dist/database.d.ts.map +1 -1
  52. package/dist/database.js +133 -7
  53. package/dist/database.js.map +1 -1
  54. package/dist/diagnostics.d.ts +75 -0
  55. package/dist/diagnostics.d.ts.map +1 -1
  56. package/dist/diagnostics.js +114 -2
  57. package/dist/diagnostics.js.map +1 -1
  58. package/dist/engine.d.ts.map +1 -1
  59. package/dist/engine.js +21 -1
  60. package/dist/engine.js.map +1 -1
  61. package/dist/entries/crdt.d.ts +32 -0
  62. package/dist/entries/crdt.d.ts.map +1 -0
  63. package/dist/entries/crdt.js +52 -0
  64. package/dist/entries/crdt.js.map +1 -0
  65. package/dist/entries/types.d.ts +5 -3
  66. package/dist/entries/types.d.ts.map +1 -1
  67. package/dist/entries/utils.d.ts +1 -0
  68. package/dist/entries/utils.d.ts.map +1 -1
  69. package/dist/entries/utils.js +8 -0
  70. package/dist/entries/utils.js.map +1 -1
  71. package/dist/entries/vite.d.ts +1 -1
  72. package/dist/entries/vite.d.ts.map +1 -1
  73. package/dist/index.d.ts +9 -2
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +14 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/schema.d.ts +150 -0
  78. package/dist/schema.d.ts.map +1 -0
  79. package/dist/schema.js +891 -0
  80. package/dist/schema.js.map +1 -0
  81. package/dist/sw/build/vite-plugin.d.ts +93 -18
  82. package/dist/sw/build/vite-plugin.d.ts.map +1 -1
  83. package/dist/sw/build/vite-plugin.js +356 -22
  84. package/dist/sw/build/vite-plugin.js.map +1 -1
  85. package/dist/types.d.ts +139 -0
  86. package/dist/types.d.ts.map +1 -1
  87. package/package.json +9 -3
  88. package/dist/operations.d.ts +0 -73
  89. package/dist/operations.d.ts.map +0 -1
  90. package/dist/operations.js +0 -227
  91. package/dist/operations.js.map +0 -1
  92. package/dist/reconnectHandler.d.ts +0 -16
  93. package/dist/reconnectHandler.d.ts.map +0 -1
  94. package/dist/reconnectHandler.js +0 -21
  95. package/dist/reconnectHandler.js.map +0 -1
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @fileoverview CRDT IndexedDB Persistence Layer
3
+ *
4
+ * Provides CRUD operations for the two CRDT-specific IndexedDB tables:
5
+ * - `crdtDocuments` — Full Yjs document state snapshots
6
+ * - `crdtPendingUpdates` — Incremental Yjs update deltas for crash recovery
7
+ *
8
+ * These tables are conditionally created by {@link ../database.ts#buildDexie}
9
+ * only when `crdt` config is provided to `initEngine()`.
10
+ *
11
+ * This module also exposes offline management query functions:
12
+ * - {@link isOfflineEnabled} — check if a document is stored for offline
13
+ * - {@link getOfflineDocuments} — list all offline-enabled documents
14
+ * - {@link getOfflineDocumentCount} — count for limit enforcement
15
+ *
16
+ * All functions access Dexie via the engine-managed instance from
17
+ * {@link ../database.ts#getDb}. Binary Yjs state is stored directly as
18
+ * `Uint8Array` — Dexie/IndexedDB handles binary data natively.
19
+ *
20
+ * @see {@link ./types.ts} for record shapes (CRDTDocumentRecord, CRDTPendingUpdate)
21
+ * @see {@link ./provider.ts} for the orchestrator that calls these functions
22
+ * @see {@link ../database.ts} for conditional CRDT table creation
23
+ */
24
+ import { getDb } from '../database';
25
+ import { debugLog } from '../debug';
26
+ // =============================================================================
27
+ // Document State CRUD
28
+ // =============================================================================
29
+ /**
30
+ * Load a CRDT document record from IndexedDB.
31
+ *
32
+ * @param documentId - The unique document identifier.
33
+ * @returns The document record, or `undefined` if not found.
34
+ */
35
+ export async function loadDocumentState(documentId) {
36
+ const db = getDb();
37
+ return db.table('crdtDocuments').get(documentId);
38
+ }
39
+ /**
40
+ * Save a full CRDT document state snapshot to IndexedDB.
41
+ *
42
+ * Uses Dexie's `put()` for upsert semantics — creates a new record if the
43
+ * document doesn't exist, or overwrites the existing one.
44
+ *
45
+ * @param record - The full document record to persist.
46
+ */
47
+ export async function saveDocumentState(record) {
48
+ const db = getDb();
49
+ await db.table('crdtDocuments').put(record);
50
+ debugLog(`[CRDT] Document ${record.documentId}: local save (${record.stateSize} bytes to IndexedDB)`);
51
+ }
52
+ /**
53
+ * Delete a CRDT document record from IndexedDB.
54
+ *
55
+ * Also clears all associated pending updates for the document.
56
+ *
57
+ * @param documentId - The document to delete.
58
+ */
59
+ export async function deleteDocumentState(documentId) {
60
+ const db = getDb();
61
+ await db.table('crdtDocuments').delete(documentId);
62
+ await clearPendingUpdates(documentId);
63
+ }
64
+ /**
65
+ * Load a CRDT document record by page ID.
66
+ *
67
+ * Pages may have at most one CRDT document. Returns the first match.
68
+ *
69
+ * @param pageId - The page/entity ID to look up.
70
+ * @returns The document record, or `undefined` if not found.
71
+ */
72
+ export async function loadDocumentByPageId(pageId) {
73
+ const db = getDb();
74
+ return db.table('crdtDocuments').where('pageId').equals(pageId).first();
75
+ }
76
+ // =============================================================================
77
+ // Pending Updates (Crash Recovery)
78
+ // =============================================================================
79
+ /**
80
+ * Append an incremental Yjs update to the pending updates table.
81
+ *
82
+ * Called on every `doc.on('update')` event for crash safety. If the browser
83
+ * crashes between full-state saves (every 5s), these deltas can be replayed
84
+ * to recover the document.
85
+ *
86
+ * @param documentId - The document this update belongs to.
87
+ * @param update - The incremental Yjs update delta.
88
+ */
89
+ export async function appendPendingUpdate(documentId, update) {
90
+ const db = getDb();
91
+ const record = {
92
+ documentId,
93
+ update,
94
+ timestamp: new Date().toISOString()
95
+ };
96
+ await db.table('crdtPendingUpdates').add(record);
97
+ }
98
+ /**
99
+ * Load all pending updates for a specific document, ordered by ID (insertion order).
100
+ *
101
+ * Used during document opening to replay any updates that weren't captured
102
+ * in the last full-state save.
103
+ *
104
+ * @param documentId - The document to load updates for.
105
+ * @returns Array of pending update records, oldest first.
106
+ */
107
+ export async function loadPendingUpdates(documentId) {
108
+ const db = getDb();
109
+ return db.table('crdtPendingUpdates').where('documentId').equals(documentId).sortBy('id');
110
+ }
111
+ /**
112
+ * Clear all pending updates for a document.
113
+ *
114
+ * Called after a successful full-state save to IndexedDB or Supabase persist,
115
+ * since the updates have been captured in the full state snapshot.
116
+ *
117
+ * @param documentId - The document to clear updates for.
118
+ * @returns The number of updates cleared.
119
+ */
120
+ export async function clearPendingUpdates(documentId) {
121
+ const db = getDb();
122
+ return db.table('crdtPendingUpdates').where('documentId').equals(documentId).delete();
123
+ }
124
+ // =============================================================================
125
+ // Offline Management Queries
126
+ // =============================================================================
127
+ /**
128
+ * Check whether a specific document is stored for offline access.
129
+ *
130
+ * @param documentId - The document to check.
131
+ * @returns `true` if the document has `offlineEnabled: 1` in IndexedDB.
132
+ */
133
+ export async function isOfflineEnabled(documentId) {
134
+ const record = await loadDocumentState(documentId);
135
+ return record?.offlineEnabled === 1;
136
+ }
137
+ /**
138
+ * Get all documents that are stored for offline access.
139
+ *
140
+ * @returns Array of document records with `offlineEnabled: 1`.
141
+ */
142
+ export async function getOfflineDocuments() {
143
+ const db = getDb();
144
+ return db.table('crdtDocuments').where('offlineEnabled').equals(1).toArray();
145
+ }
146
+ /**
147
+ * Count the number of documents currently stored for offline access.
148
+ *
149
+ * Used by {@link ./offline.ts#enableOffline} to enforce the
150
+ * `maxOfflineDocuments` limit.
151
+ *
152
+ * @returns The number of offline-enabled documents.
153
+ */
154
+ export async function getOfflineDocumentCount() {
155
+ const db = getDb();
156
+ return db.table('crdtDocuments').where('offlineEnabled').equals(1).count();
157
+ }
158
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/crdt/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * @fileoverview CRDT Subsystem Type Definitions
3
+ *
4
+ * Defines all TypeScript interfaces and types used by the CRDT collaborative
5
+ * editing subsystem. This includes:
6
+ * - {@link CRDTConfig} — configuration passed via `initEngine({ crdt: ... })`
7
+ * - {@link CRDTDocumentRecord} — IndexedDB record shape for persisted CRDT state
8
+ * - {@link CRDTPendingUpdate} — crash-safe incremental update records
9
+ * - {@link UserPresenceState} — per-user cursor/presence state for awareness
10
+ * - {@link OpenDocumentOptions} — options bag for `openDocument()`
11
+ * - {@link BroadcastMessage} — union of all Broadcast channel message types
12
+ *
13
+ * Architecture note:
14
+ * The CRDT subsystem is an optional layer on top of the existing sync engine.
15
+ * It uses Yjs for conflict-free document merging, Supabase Broadcast for
16
+ * real-time update distribution, Supabase Presence for cursor/awareness,
17
+ * and IndexedDB (via Dexie) for local persistence. Consumers never import
18
+ * yjs directly — all Yjs types are re-exported from the engine.
19
+ *
20
+ * @see {@link ./config.ts} for configuration singleton management
21
+ * @see {@link ./provider.ts} for the per-document lifecycle orchestrator
22
+ * @see {@link ./channel.ts} for Broadcast message handling
23
+ * @see {@link ./awareness.ts} for Supabase Presence ↔ Yjs Awareness bridge
24
+ */
25
+ /**
26
+ * Configuration for the CRDT collaborative editing subsystem.
27
+ *
28
+ * Passed as the `crdt` field of {@link SyncEngineConfig} in `initEngine()`.
29
+ * All fields are optional with sensible defaults. When this config is provided,
30
+ * the engine creates additional IndexedDB tables for CRDT document storage and
31
+ * enables the `@prabhask5/stellar-engine/crdt` API surface.
32
+ *
33
+ * @example
34
+ * initEngine({
35
+ * prefix: 'myapp',
36
+ * tables: [...],
37
+ * database: { name: 'myapp-db', versions: [...] },
38
+ * crdt: {
39
+ * persistIntervalMs: 60000, // Persist to Supabase every 60s
40
+ * maxOfflineDocuments: 100, // Allow up to 100 docs offline
41
+ * },
42
+ * });
43
+ */
44
+ export interface CRDTConfig {
45
+ /**
46
+ * Supabase table name for CRDT document storage.
47
+ * @default 'crdt_documents'
48
+ */
49
+ supabaseTable?: string;
50
+ /**
51
+ * Columns to SELECT from Supabase (egress optimization).
52
+ * @default 'id,page_id,state,state_vector,state_size,device_id,updated_at,created_at'
53
+ */
54
+ columns?: string;
55
+ /**
56
+ * How often to persist dirty documents to Supabase (ms).
57
+ * Lower values reduce data loss risk on crash but increase Supabase writes.
58
+ * @default 30000 (30 seconds)
59
+ */
60
+ persistIntervalMs?: number;
61
+ /**
62
+ * Broadcast debounce window (ms). Updates within this window are merged
63
+ * via `Y.mergeUpdates()` into a single Broadcast payload.
64
+ * @default 100
65
+ */
66
+ broadcastDebounceMs?: number;
67
+ /**
68
+ * How long to debounce local IndexedDB full-state saves (ms).
69
+ * Writes full Yjs state to IndexedDB on this interval to ensure recovery
70
+ * from crashes without storing every single keystroke.
71
+ * @default 5000 (5 seconds)
72
+ */
73
+ localSaveDebounceMs?: number;
74
+ /**
75
+ * Cursor/presence update debounce (ms). Limits the rate at which
76
+ * cursor position changes are broadcast to other users.
77
+ * @default 50
78
+ */
79
+ cursorDebounceMs?: number;
80
+ /**
81
+ * Maximum number of documents stored locally for offline access.
82
+ * When the limit is reached, `enableOffline()` will reject new documents.
83
+ * @default 50
84
+ */
85
+ maxOfflineDocuments?: number;
86
+ /**
87
+ * Maximum Broadcast payload size in bytes before chunking.
88
+ * Supabase Broadcast has a ~1MB hard limit; we chunk well below that.
89
+ * @default 250000 (250KB)
90
+ */
91
+ maxBroadcastPayloadBytes?: number;
92
+ /**
93
+ * Timeout (ms) waiting for peer sync-step-2 responses on reconnect.
94
+ * If no peers respond within this window, falls back to Supabase fetch.
95
+ * @default 3000
96
+ */
97
+ syncPeerTimeoutMs?: number;
98
+ /**
99
+ * Maximum reconnection attempts for the Broadcast channel.
100
+ * After this many failures, the channel enters a permanent error state
101
+ * and must be manually reconnected.
102
+ * @default 5
103
+ */
104
+ maxReconnectAttempts?: number;
105
+ /**
106
+ * Base delay (ms) for exponential backoff on channel reconnect.
107
+ * Actual delay: `baseDelay * 2^(attemptNumber - 1)`.
108
+ * @default 1000
109
+ */
110
+ reconnectBaseDelayMs?: number;
111
+ }
112
+ /**
113
+ * Fully resolved CRDT configuration with all defaults applied.
114
+ *
115
+ * Created by {@link config.ts#_initCRDT} from the user-provided
116
+ * {@link CRDTConfig}. All fields are required (no `undefined` values).
117
+ *
118
+ * @internal
119
+ */
120
+ export interface ResolvedCRDTConfig {
121
+ supabaseTable: string;
122
+ columns: string;
123
+ persistIntervalMs: number;
124
+ broadcastDebounceMs: number;
125
+ localSaveDebounceMs: number;
126
+ cursorDebounceMs: number;
127
+ maxOfflineDocuments: number;
128
+ maxBroadcastPayloadBytes: number;
129
+ syncPeerTimeoutMs: number;
130
+ maxReconnectAttempts: number;
131
+ reconnectBaseDelayMs: number;
132
+ }
133
+ /**
134
+ * Shape of a record in the `crdtDocuments` IndexedDB table.
135
+ *
136
+ * Stores the full Yjs document state as a binary `Uint8Array`, along with
137
+ * metadata for sync, offline management, and diagnostics.
138
+ *
139
+ * The `offlineEnabled` field uses `0 | 1` instead of `boolean` because
140
+ * Dexie/IndexedDB cannot index boolean fields.
141
+ */
142
+ export interface CRDTDocumentRecord {
143
+ /** Unique document identifier (primary key). */
144
+ documentId: string;
145
+ /** The page/entity this document belongs to. Indexed for lookups. */
146
+ pageId: string;
147
+ /** Full Yjs document state (`Y.encodeStateAsUpdate(doc)`). */
148
+ state: Uint8Array;
149
+ /** Yjs state vector for delta sync (`Y.encodeStateVector(doc)`). */
150
+ stateVector: Uint8Array;
151
+ /**
152
+ * Whether this document is stored locally for offline access.
153
+ * Uses `0 | 1` because IndexedDB cannot index booleans.
154
+ */
155
+ offlineEnabled: 0 | 1;
156
+ /** ISO 8601 timestamp of the last local modification. */
157
+ localUpdatedAt: string;
158
+ /** ISO 8601 timestamp of the last successful Supabase write, or `null` if never persisted. */
159
+ lastPersistedAt: string | null;
160
+ /** Byte size of the `state` field, for diagnostics and compaction decisions. */
161
+ stateSize: number;
162
+ }
163
+ /**
164
+ * Shape of a record in the `crdtPendingUpdates` IndexedDB table.
165
+ *
166
+ * Stores incremental Yjs update deltas for crash safety. These are written
167
+ * on every `doc.on('update')` event so that if the browser crashes between
168
+ * full-state saves (every 5s), the pending deltas can be replayed to recover
169
+ * the document to its last known state.
170
+ *
171
+ * Cleared after a successful full-state save to IndexedDB or Supabase persist.
172
+ */
173
+ export interface CRDTPendingUpdate {
174
+ /** Auto-increment primary key (assigned by IndexedDB). */
175
+ id?: number;
176
+ /** Which document this update belongs to. Indexed for efficient batch queries. */
177
+ documentId: string;
178
+ /** Incremental Yjs update delta (`Uint8Array` from `doc.on('update')`). */
179
+ update: Uint8Array;
180
+ /** ISO 8601 timestamp of when the update was received. */
181
+ timestamp: string;
182
+ }
183
+ /**
184
+ * Per-user presence state broadcast via Supabase Presence and bridged to
185
+ * Yjs Awareness. This is what other users see when collaborating on a document.
186
+ *
187
+ * The `cursor` and `selection` fields are intentionally typed as `unknown`
188
+ * because their shape depends on the editor implementation (Tiptap, Prosemirror,
189
+ * CodeMirror, etc.). The CRDT engine transports them opaquely.
190
+ */
191
+ export interface UserPresenceState {
192
+ /** Supabase user UUID. */
193
+ userId: string;
194
+ /** Display name (e.g., first name or email). */
195
+ name: string;
196
+ /** Avatar URL, if available. */
197
+ avatarUrl?: string;
198
+ /**
199
+ * Deterministic color assigned from userId hash.
200
+ * Used for cursor color, selection highlight, avatar ring, etc.
201
+ */
202
+ color: string;
203
+ /** Editor-specific cursor position (opaque to the engine). */
204
+ cursor?: unknown;
205
+ /** Editor-specific selection range (opaque to the engine). */
206
+ selection?: unknown;
207
+ /** Device identifier for multi-tab dedup. */
208
+ deviceId: string;
209
+ /** ISO 8601 timestamp of the user's last activity in this document. */
210
+ lastActiveAt: string;
211
+ }
212
+ /**
213
+ * Options for {@link provider.ts#openDocument}.
214
+ */
215
+ export interface OpenDocumentOptions {
216
+ /**
217
+ * If `true`, the document will be persisted to IndexedDB for offline access.
218
+ * When `false` or omitted, the document exists only in memory and is lost
219
+ * when the provider is destroyed.
220
+ * @default false
221
+ */
222
+ offlineEnabled?: boolean;
223
+ /**
224
+ * Initial user presence state to announce when joining the document.
225
+ * If omitted, no presence is announced until `updateCursor()` is called.
226
+ */
227
+ initialPresence?: {
228
+ name: string;
229
+ avatarUrl?: string;
230
+ };
231
+ }
232
+ /**
233
+ * Connection state of a CRDT document's Broadcast channel.
234
+ */
235
+ export type CRDTConnectionState = 'disconnected' | 'connecting' | 'connected';
236
+ /**
237
+ * A CRDT document update distributed via Supabase Broadcast.
238
+ *
239
+ * Contains the merged Yjs update (possibly the result of debouncing multiple
240
+ * rapid edits) encoded as a base64 string for JSON transport.
241
+ */
242
+ export interface BroadcastUpdateMessage {
243
+ type: 'update';
244
+ data: string;
245
+ deviceId: string;
246
+ }
247
+ /**
248
+ * Sync step 1: sent on channel join to request missing updates from peers.
249
+ * Contains the local state vector so peers can compute the delta.
250
+ */
251
+ export interface BroadcastSyncStep1Message {
252
+ type: 'sync-step-1';
253
+ stateVector: string;
254
+ deviceId: string;
255
+ }
256
+ /**
257
+ * Sync step 2: response to a sync-step-1 request.
258
+ * Contains the computed delta update for the requester.
259
+ */
260
+ export interface BroadcastSyncStep2Message {
261
+ type: 'sync-step-2';
262
+ update: string;
263
+ deviceId: string;
264
+ }
265
+ /**
266
+ * A chunk of a large Broadcast payload that exceeds the max payload size.
267
+ * The receiver reassembles chunks by matching `chunkId` and ordering by `index`.
268
+ */
269
+ export interface BroadcastChunkMessage {
270
+ type: 'chunk';
271
+ chunkId: string;
272
+ index: number;
273
+ total: number;
274
+ data: string;
275
+ deviceId: string;
276
+ }
277
+ /**
278
+ * Union of all Broadcast message types sent over the CRDT channel.
279
+ */
280
+ export type BroadcastMessage = BroadcastUpdateMessage | BroadcastSyncStep1Message | BroadcastSyncStep2Message | BroadcastChunkMessage;
281
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/crdt/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @fileoverview CRDT Subsystem Type Definitions
3
+ *
4
+ * Defines all TypeScript interfaces and types used by the CRDT collaborative
5
+ * editing subsystem. This includes:
6
+ * - {@link CRDTConfig} — configuration passed via `initEngine({ crdt: ... })`
7
+ * - {@link CRDTDocumentRecord} — IndexedDB record shape for persisted CRDT state
8
+ * - {@link CRDTPendingUpdate} — crash-safe incremental update records
9
+ * - {@link UserPresenceState} — per-user cursor/presence state for awareness
10
+ * - {@link OpenDocumentOptions} — options bag for `openDocument()`
11
+ * - {@link BroadcastMessage} — union of all Broadcast channel message types
12
+ *
13
+ * Architecture note:
14
+ * The CRDT subsystem is an optional layer on top of the existing sync engine.
15
+ * It uses Yjs for conflict-free document merging, Supabase Broadcast for
16
+ * real-time update distribution, Supabase Presence for cursor/awareness,
17
+ * and IndexedDB (via Dexie) for local persistence. Consumers never import
18
+ * yjs directly — all Yjs types are re-exported from the engine.
19
+ *
20
+ * @see {@link ./config.ts} for configuration singleton management
21
+ * @see {@link ./provider.ts} for the per-document lifecycle orchestrator
22
+ * @see {@link ./channel.ts} for Broadcast message handling
23
+ * @see {@link ./awareness.ts} for Supabase Presence ↔ Yjs Awareness bridge
24
+ */
25
+ export {};
26
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/crdt/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
@@ -44,6 +44,21 @@ export interface DatabaseConfig {
44
44
  /** Ordered list of version declarations (each adds or modifies tables). */
45
45
  versions: DatabaseVersionConfig[];
46
46
  }
47
+ /**
48
+ * Dexie indexes automatically appended to every app table when using the
49
+ * schema-driven API (`initEngine({ schema: {...} })`).
50
+ *
51
+ * These correspond to the system columns that every synced table has:
52
+ * - `id` — UUID primary key
53
+ * - `user_id` — ownership filter for RLS
54
+ * - `created_at` — creation timestamp
55
+ * - `updated_at` — last modification timestamp (sync cursor)
56
+ * - `deleted` — soft-delete flag
57
+ * - `_version` — optimistic concurrency version counter
58
+ *
59
+ * @see {@link config.ts#generateDatabaseFromSchema} for usage
60
+ */
61
+ export declare const SYSTEM_INDEXES = "id, user_id, created_at, updated_at, deleted, _version";
47
62
  /**
48
63
  * Create a Dexie database with system tables auto-merged into every version.
49
64
  *
@@ -62,7 +77,7 @@ export interface DatabaseConfig {
62
77
  * @param config - Database name and version declarations.
63
78
  * @returns The opened Dexie instance, ready for use.
64
79
  */
65
- export declare function createDatabase(config: DatabaseConfig): Promise<Dexie>;
80
+ export declare function createDatabase(config: DatabaseConfig, crdtEnabled?: boolean): Promise<Dexie>;
66
81
  /**
67
82
  * Get the engine-managed Dexie instance.
68
83
  *
@@ -80,6 +95,56 @@ export declare function getDb(): Dexie;
80
95
  * @internal
81
96
  */
82
97
  export declare function _setManagedDb(db: Dexie): void;
98
+ /**
99
+ * Result of schema version computation.
100
+ *
101
+ * Contains the resolved version number plus the previous stores schema
102
+ * (if any) so that the caller can declare both versions and give Dexie
103
+ * a proper upgrade path.
104
+ */
105
+ export interface SchemaVersionResult {
106
+ /** The resolved Dexie version number (positive integer, starts at 1). */
107
+ version: number;
108
+ /**
109
+ * The previous version's store schema, or `null` if this is the first
110
+ * run or no change was detected. When non-null, the caller should declare
111
+ * **both** `previousStores` at `version - 1` and the current stores at
112
+ * `version` so Dexie can perform a non-destructive upgrade.
113
+ */
114
+ previousStores: Record<string, string> | null;
115
+ /** The previous version number, or `null` if no upgrade is needed. */
116
+ previousVersion: number | null;
117
+ }
118
+ /**
119
+ * Compute a stable Dexie version number from a merged store schema.
120
+ *
121
+ * Uses a localStorage-backed hash comparison to detect schema changes:
122
+ * 1. Compute a deterministic hash of the stringified stores object.
123
+ * 2. Compare to the previously stored hash in localStorage.
124
+ * 3. If changed → increment the stored version, persist both hash and
125
+ * previous stores schema, and return the upgrade info.
126
+ * 4. If unchanged → return the stored version.
127
+ * 5. If first run → return version 1.
128
+ *
129
+ * When a schema change is detected, the previous stores schema is returned
130
+ * so that the caller can declare both versions. This gives Dexie a proper
131
+ * upgrade path (version N → version N+1) instead of requiring a full
132
+ * database rebuild.
133
+ *
134
+ * @param prefix - Application prefix for namespacing localStorage keys.
135
+ * @param mergedStores - The complete Dexie store schema (app + system tables).
136
+ * @returns Version info including previous stores for upgrade path.
137
+ *
138
+ * @example
139
+ * const result = computeSchemaVersion('stellar', {
140
+ * goals: 'id, user_id, goal_list_id, order',
141
+ * });
142
+ * // First run: { version: 1, previousStores: null, previousVersion: null }
143
+ * // On change: { version: 2, previousStores: { goals: '...' }, previousVersion: 1 }
144
+ *
145
+ * @see {@link config.ts#generateDatabaseFromSchema} for the caller
146
+ */
147
+ export declare function computeSchemaVersion(prefix: string, mergedStores: Record<string, string>): SchemaVersionResult;
83
148
  /**
84
149
  * Delete the IndexedDB database entirely and clear associated state.
85
150
  *
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,sEAAsE;IACtE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAmCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,CAsD3E;AA+BD;;;;;GAKG;AACH,wBAAgB,KAAK,IAAI,KAAK,CAK7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAE7C;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyB5D"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,sEAAsE;IACtE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAMD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,2DAA2D,CAAC;AAoDvF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAsDhG;AAoCD;;;;;GAKG;AACH,wBAAgB,KAAK,IAAI,KAAK,CAK7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAE7C;AAMD;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C,sEAAsE;IACtE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,mBAAmB,CA+CrB;AAyBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyB5D"}