@palantir/pack.state.core 0.0.1-beta.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 (63) hide show
  1. package/.turbo/turbo-lint.log +4 -0
  2. package/.turbo/turbo-transpileBrowser.log +5 -0
  3. package/.turbo/turbo-transpileCjs.log +5 -0
  4. package/.turbo/turbo-transpileEsm.log +5 -0
  5. package/.turbo/turbo-transpileTypes.log +5 -0
  6. package/.turbo/turbo-typecheck.log +4 -0
  7. package/LICENSE.txt +13 -0
  8. package/README.md +55 -0
  9. package/build/browser/index.js +1257 -0
  10. package/build/browser/index.js.map +1 -0
  11. package/build/cjs/index.cjs +1298 -0
  12. package/build/cjs/index.cjs.map +1 -0
  13. package/build/cjs/index.d.cts +272 -0
  14. package/build/esm/index.js +1257 -0
  15. package/build/esm/index.js.map +1 -0
  16. package/build/types/DocumentServiceModule.d.ts +6 -0
  17. package/build/types/DocumentServiceModule.d.ts.map +1 -0
  18. package/build/types/__tests__/DocumentStatusTracking.test.d.ts +1 -0
  19. package/build/types/__tests__/DocumentStatusTracking.test.d.ts.map +1 -0
  20. package/build/types/__tests__/RefStability.test.d.ts +1 -0
  21. package/build/types/__tests__/RefStability.test.d.ts.map +1 -0
  22. package/build/types/__tests__/StateModule.integration.test.d.ts +1 -0
  23. package/build/types/__tests__/StateModule.integration.test.d.ts.map +1 -0
  24. package/build/types/__tests__/testUtils.d.ts +7 -0
  25. package/build/types/__tests__/testUtils.d.ts.map +1 -0
  26. package/build/types/index.d.ts +11 -0
  27. package/build/types/index.d.ts.map +1 -0
  28. package/build/types/service/BaseYjsDocumentService.d.ts +155 -0
  29. package/build/types/service/BaseYjsDocumentService.d.ts.map +1 -0
  30. package/build/types/service/InMemoryDocumentService.d.ts +12 -0
  31. package/build/types/service/InMemoryDocumentService.d.ts.map +1 -0
  32. package/build/types/service/YjsSchemaMapper.d.ts +9 -0
  33. package/build/types/service/YjsSchemaMapper.d.ts.map +1 -0
  34. package/build/types/types/DocumentRefImpl.d.ts +5 -0
  35. package/build/types/types/DocumentRefImpl.d.ts.map +1 -0
  36. package/build/types/types/DocumentService.d.ts +62 -0
  37. package/build/types/types/DocumentService.d.ts.map +1 -0
  38. package/build/types/types/DocumentServiceConfig.d.ts +5 -0
  39. package/build/types/types/DocumentServiceConfig.d.ts.map +1 -0
  40. package/build/types/types/RecordCollectionRefImpl.d.ts +5 -0
  41. package/build/types/types/RecordCollectionRefImpl.d.ts.map +1 -0
  42. package/build/types/types/RecordRefImpl.d.ts +5 -0
  43. package/build/types/types/RecordRefImpl.d.ts.map +1 -0
  44. package/build/types/types/StateModule.d.ts +59 -0
  45. package/build/types/types/StateModule.d.ts.map +1 -0
  46. package/package.json +71 -0
  47. package/src/DocumentServiceModule.ts +53 -0
  48. package/src/__tests__/DocumentStatusTracking.test.ts +229 -0
  49. package/src/__tests__/RefStability.test.ts +441 -0
  50. package/src/__tests__/StateModule.integration.test.ts +1187 -0
  51. package/src/__tests__/testUtils.ts +106 -0
  52. package/src/index.ts +38 -0
  53. package/src/service/BaseYjsDocumentService.ts +1277 -0
  54. package/src/service/InMemoryDocumentService.ts +162 -0
  55. package/src/service/YjsSchemaMapper.ts +194 -0
  56. package/src/types/DocumentRefImpl.ts +98 -0
  57. package/src/types/DocumentService.ts +210 -0
  58. package/src/types/DocumentServiceConfig.ts +22 -0
  59. package/src/types/RecordCollectionRefImpl.ts +124 -0
  60. package/src/types/RecordRefImpl.ts +106 -0
  61. package/src/types/StateModule.ts +329 -0
  62. package/tsconfig.json +21 -0
  63. package/vitest.config.mjs +26 -0
@@ -0,0 +1,1257 @@
1
+ import { getMetadata, DocumentRefBrand, RecordCollectionRefBrand, RecordRefBrand } from '@palantir/pack.document-schema.model-types';
2
+ import { isDeepEqual } from 'remeda';
3
+ import invariant from 'tiny-invariant';
4
+ import * as Y from 'yjs';
5
+ import { assertIsAppInternal, generateId } from '@palantir/pack.core';
6
+
7
+ // src/DocumentServiceModule.ts
8
+ var DOCUMENT_SERVICE_MODULE_KEY = {
9
+ key: Symbol("DocumentService"),
10
+ initModule: initDocumentService
11
+ };
12
+ function getDocumentService(app) {
13
+ return app.getModule(DOCUMENT_SERVICE_MODULE_KEY);
14
+ }
15
+ function createDocumentServiceConfig(init, config) {
16
+ return [DOCUMENT_SERVICE_MODULE_KEY, {
17
+ ...config,
18
+ init
19
+ }];
20
+ }
21
+ function initDocumentService(app, config) {
22
+ if (config == null) {
23
+ throw new Error("DocumentServiceConfig is required to initialize DocumentService");
24
+ }
25
+ return config.init(app, config);
26
+ }
27
+ var STATE_MODULE_ACCESSOR = "state";
28
+ var STATE_MODULE_KEY = {
29
+ appMemberName: STATE_MODULE_ACCESSOR,
30
+ key: Symbol.for("pack.state"),
31
+ initModule: (app) => {
32
+ const documentService = app.getModule(DOCUMENT_SERVICE_MODULE_KEY);
33
+ return new StateModuleImpl(documentService);
34
+ }
35
+ };
36
+ var StateModuleImpl = class {
37
+ constructor(documentService) {
38
+ this.documentService = documentService;
39
+ }
40
+ createDocRef(id, schema) {
41
+ return this.documentService.createDocRef(id, schema);
42
+ }
43
+ createRecordRef(docRef, id, model) {
44
+ return this.documentService.getCreateRecordRef(docRef, id, model);
45
+ }
46
+ async createDocument(metadata, schema) {
47
+ return this.documentService.createDocument(metadata, schema);
48
+ }
49
+ async getDocumentSnapshot(docRef) {
50
+ return this.documentService.getDocumentSnapshot(docRef);
51
+ }
52
+ onMetadataChange(docRef, cb) {
53
+ return this.documentService.onMetadataChange(docRef, cb);
54
+ }
55
+ onStateChange(docRef, cb) {
56
+ return this.documentService.onStateChange(docRef, cb);
57
+ }
58
+ async getRecordSnapshot(recordRef) {
59
+ return this.documentService.getRecordSnapshot(recordRef);
60
+ }
61
+ async setRecord(recordRef, state) {
62
+ return this.documentService.setRecord(recordRef, state);
63
+ }
64
+ async updateRecord(recordRef, partialState) {
65
+ return this.documentService.updateRecord(recordRef, partialState);
66
+ }
67
+ // Collection methods
68
+ getCreateRecordCollectionRef(docRef, model) {
69
+ return this.documentService.getCreateRecordCollectionRef(docRef, model);
70
+ }
71
+ // FIXME: confusing vs createRecordRef
72
+ getRecord(collection, id) {
73
+ return this.documentService.getRecord(collection, id);
74
+ }
75
+ hasRecord(collection, id) {
76
+ return this.documentService.hasRecord(collection, id);
77
+ }
78
+ async setCollectionRecord(collection, id, state) {
79
+ return this.documentService.setCollectionRecord(collection, id, state);
80
+ }
81
+ getCollectionSize(collection) {
82
+ return this.documentService.getCollectionSize(collection);
83
+ }
84
+ getCollectionRecords(collection) {
85
+ return this.documentService.getCollectionRecords(collection);
86
+ }
87
+ onRecordChanged(record, callback) {
88
+ return this.documentService.onRecordChanged(record, callback);
89
+ }
90
+ onRecordDeleted(record, callback) {
91
+ return this.documentService.onRecordDeleted(record, callback);
92
+ }
93
+ onCollectionItemsAdded(collection, callback) {
94
+ return this.documentService.onCollectionItemsAdded(collection, callback);
95
+ }
96
+ onCollectionItemsChanged(collection, callback) {
97
+ return this.documentService.onCollectionItemsChanged(collection, callback);
98
+ }
99
+ onCollectionItemsDeleted(collection, callback) {
100
+ return this.documentService.onCollectionItemsDeleted(collection, callback);
101
+ }
102
+ // Status methods implementation
103
+ getDocumentStatus(docRef) {
104
+ return this.documentService.getDocumentStatus(docRef);
105
+ }
106
+ onStatusChange(docRef, callback) {
107
+ return this.documentService.onStatusChange(docRef, callback);
108
+ }
109
+ async waitForMetadataLoad(docRef) {
110
+ return this.documentService.waitForMetadataLoad(docRef);
111
+ }
112
+ async waitForDataLoad(docRef) {
113
+ return this.documentService.waitForDataLoad(docRef);
114
+ }
115
+ async deleteRecord(record) {
116
+ return this.documentService.deleteRecord(record);
117
+ }
118
+ };
119
+ function getStateModule(app) {
120
+ assertIsAppInternal(app);
121
+ return app.getModule(STATE_MODULE_KEY);
122
+ }
123
+
124
+ // src/types/DocumentRefImpl.ts
125
+ var INVALID_DOC_REF_ID = "INVALID_DOC_REF";
126
+ var INVALID_DOC_REF = Object.freeze({
127
+ id: INVALID_DOC_REF_ID,
128
+ schema: {},
129
+ [DocumentRefBrand]: DocumentRefBrand,
130
+ getDocSnapshot: () => Promise.reject(new Error("Invalid document reference")),
131
+ getRecords: () => {
132
+ throw new Error("Invalid document reference");
133
+ },
134
+ onMetadataChange: () => () => {
135
+ },
136
+ onStateChange: () => () => {
137
+ }
138
+ });
139
+ var createDocRef = (app, id, schema) => {
140
+ return new DocumentRefImpl(app, id, schema);
141
+ };
142
+ function invalidDocRef() {
143
+ return INVALID_DOC_REF;
144
+ }
145
+ function isValidDocRef(docRef) {
146
+ return docRef.id !== INVALID_DOC_REF_ID && docRef.id !== "";
147
+ }
148
+ var DocumentRefImpl = class {
149
+ id;
150
+ schema;
151
+ #stateModule;
152
+ constructor(app, id, schema) {
153
+ this.#stateModule = getStateModule(app);
154
+ this.id = id;
155
+ this.schema = schema;
156
+ }
157
+ async getDocSnapshot() {
158
+ return this.#stateModule.getDocumentSnapshot(this);
159
+ }
160
+ getRecords(model) {
161
+ return this.#stateModule.getCreateRecordCollectionRef(this, model);
162
+ }
163
+ onMetadataChange(cb) {
164
+ return this.#stateModule.onMetadataChange(this, cb);
165
+ }
166
+ onStateChange(callback) {
167
+ return this.#stateModule.onStateChange(this, callback);
168
+ }
169
+ };
170
+
171
+ // src/types/DocumentService.ts
172
+ var DocumentLoadStatus = {
173
+ UNLOADED: "unloaded",
174
+ // Not yet loaded
175
+ LOADING: "loading",
176
+ // Initial load in progress
177
+ LOADED: "loaded",
178
+ // Successfully loaded
179
+ ERROR: "error"
180
+ // Load failed
181
+ };
182
+ var DocumentLiveStatus = {
183
+ DISCONNECTED: "disconnected",
184
+ // Not syncing
185
+ CONNECTING: "connecting",
186
+ // Establishing connection
187
+ CONNECTED: "connected",
188
+ // Live syncing active
189
+ ERROR: "error"
190
+ // Connection error
191
+ };
192
+ var INVALID_RECORD_COLLECTION_REF = Object.freeze({
193
+ docRef: invalidDocRef(),
194
+ model: {},
195
+ [RecordCollectionRefBrand]: RecordCollectionRefBrand,
196
+ get: () => void 0,
197
+ has: () => false,
198
+ set: () => Promise.reject(new Error("Invalid record collection reference")),
199
+ delete: () => Promise.reject(new Error("Invalid record collection reference")),
200
+ size: 0,
201
+ [Symbol.iterator]: () => ({
202
+ next: () => ({
203
+ done: true,
204
+ value: void 0
205
+ })
206
+ }),
207
+ onItemsAdded: () => () => {
208
+ },
209
+ onItemsChanged: () => () => {
210
+ },
211
+ onItemsDeleted: () => () => {
212
+ }
213
+ });
214
+ var createRecordCollectionRef = (documentService, docRef, model) => {
215
+ return new RecordCollectionRefImpl(documentService, docRef, model);
216
+ };
217
+ function invalidRecordCollectionRef() {
218
+ return INVALID_RECORD_COLLECTION_REF;
219
+ }
220
+ function isValidRecordCollectionRef(collectionRef) {
221
+ return collectionRef !== INVALID_RECORD_COLLECTION_REF;
222
+ }
223
+ var RecordCollectionRefImpl = class {
224
+ docRef;
225
+ model;
226
+ #documentService;
227
+ constructor(documentService, docRef, model) {
228
+ this.docRef = docRef;
229
+ this.model = model;
230
+ this.#documentService = documentService;
231
+ }
232
+ get(id) {
233
+ return this.#documentService.getRecord(this, id);
234
+ }
235
+ has(id) {
236
+ return this.#documentService.hasRecord(this, id);
237
+ }
238
+ async set(id, state) {
239
+ return this.#documentService.setCollectionRecord(this, id, state);
240
+ }
241
+ delete(id) {
242
+ const recordRefInstance = this.#documentService.getRecord(this, id);
243
+ if (recordRefInstance) {
244
+ return this.#documentService.deleteRecord(recordRefInstance);
245
+ }
246
+ return Promise.reject(new Error(`Unknown record`));
247
+ }
248
+ get size() {
249
+ return this.#documentService.getCollectionSize(this);
250
+ }
251
+ [Symbol.iterator]() {
252
+ return this.#documentService.getCollectionRecords(this)[Symbol.iterator]();
253
+ }
254
+ onItemsAdded = (callback) => {
255
+ return this.#documentService.onCollectionItemsAdded(this, callback);
256
+ };
257
+ onItemsChanged = (callback) => {
258
+ return this.#documentService.onCollectionItemsChanged(this, callback);
259
+ };
260
+ onItemsDeleted = (callback) => {
261
+ return this.#documentService.onCollectionItemsDeleted(this, callback);
262
+ };
263
+ };
264
+ var INVALID_RECORD_ID = "INVALID_RECORD_REF";
265
+ var INVALID_RECORD_REF = Object.freeze({
266
+ docRef: invalidDocRef(),
267
+ id: INVALID_RECORD_ID,
268
+ model: {},
269
+ [RecordRefBrand]: RecordRefBrand,
270
+ getSnapshot: () => Promise.reject(new Error("Invalid record reference")),
271
+ set: () => Promise.reject(new Error("Invalid record reference")),
272
+ onChange: () => () => {
273
+ },
274
+ onDeleted: () => () => {
275
+ }
276
+ });
277
+ var createRecordRef = (documentService, docRef, id, model) => {
278
+ return new RecordRefImpl(documentService, docRef, id, model);
279
+ };
280
+ function invalidRecordRef() {
281
+ return INVALID_RECORD_REF;
282
+ }
283
+ function isValidRecordRef(recordRef) {
284
+ return recordRef.id !== INVALID_RECORD_ID;
285
+ }
286
+ var RecordRefImpl = class {
287
+ docRef;
288
+ id;
289
+ model;
290
+ #documentService;
291
+ constructor(documentService, docRef, id, model) {
292
+ this.#documentService = documentService;
293
+ this.docRef = docRef;
294
+ this.id = id;
295
+ this.model = model;
296
+ }
297
+ async getSnapshot() {
298
+ return this.#documentService.getRecordSnapshot(this);
299
+ }
300
+ onChange(callback) {
301
+ return this.#documentService.onRecordChanged(this, callback);
302
+ }
303
+ onDeleted(callback) {
304
+ return this.#documentService.onRecordDeleted(this, callback);
305
+ }
306
+ delete() {
307
+ return this.#documentService.deleteRecord(this);
308
+ }
309
+ set(record) {
310
+ return this.#documentService.setRecord(this, record);
311
+ }
312
+ update(partialRecord) {
313
+ return this.#documentService.updateRecord(this, partialRecord);
314
+ }
315
+ };
316
+ function initializeDocumentStructure(yDoc, schema) {
317
+ Object.values(schema).forEach((modelEntry) => {
318
+ yDoc.getMap(getMetadata(modelEntry).name);
319
+ });
320
+ }
321
+ function getRecordsMap(yDoc, storageName) {
322
+ return yDoc.getMap(storageName);
323
+ }
324
+ function getRecordData(yDoc, storageName, recordId) {
325
+ const recordsCollection = getRecordsMap(yDoc, storageName);
326
+ return recordsCollection.get(recordId);
327
+ }
328
+ function setRecord(yDoc, storageName, recordId, state) {
329
+ const recordsCollection = getRecordsMap(yDoc, storageName);
330
+ const currentRecord = recordsCollection.get(recordId);
331
+ const wasExisting = currentRecord != null;
332
+ yDoc.transact(() => {
333
+ if (currentRecord != null) {
334
+ currentRecord.clear();
335
+ populateYMapFromState(currentRecord, state);
336
+ } else {
337
+ const newRecord = new Y.Map();
338
+ populateYMapFromState(newRecord, state);
339
+ recordsCollection.set(recordId, newRecord);
340
+ }
341
+ });
342
+ return wasExisting;
343
+ }
344
+ function getRecordSnapshot(yDoc, storageName, recordId) {
345
+ const data = getRecordData(yDoc, storageName, recordId);
346
+ if (!data) {
347
+ return void 0;
348
+ }
349
+ return yMapToState(data);
350
+ }
351
+ function updateRecord(yDoc, storageName, recordId, partialState) {
352
+ const recordsCollection = getRecordsMap(yDoc, storageName);
353
+ const currentRecord = recordsCollection.get(recordId);
354
+ if (currentRecord == null) {
355
+ return false;
356
+ }
357
+ yDoc.transact(() => {
358
+ updateYMapFromPartialState(currentRecord, partialState);
359
+ });
360
+ return true;
361
+ }
362
+ function getAllRecordIds(yDoc, storageName) {
363
+ const recordsCollection = getRecordsMap(yDoc, storageName);
364
+ return Array.from(recordsCollection.keys()).map((key) => key);
365
+ }
366
+ function populateYMapFromState(yMap, state) {
367
+ if (state != null && typeof state === "object") {
368
+ Object.entries(state).forEach(([key, value]) => {
369
+ if (value === void 0) return;
370
+ if (Array.isArray(value)) {
371
+ const yArray = new Y.Array();
372
+ value.forEach((item) => {
373
+ yArray.push([item]);
374
+ });
375
+ yMap.set(key, yArray);
376
+ } else if (typeof value === "object" && value != null) {
377
+ const nestedMap = new Y.Map();
378
+ populateYMapFromState(nestedMap, value);
379
+ yMap.set(key, nestedMap);
380
+ } else {
381
+ yMap.set(key, value);
382
+ }
383
+ });
384
+ }
385
+ }
386
+ function updateYMapFromPartialState(yMap, partialState) {
387
+ if (typeof partialState === "object") {
388
+ Object.entries(partialState).forEach(([key, value]) => {
389
+ if (value === void 0) {
390
+ yMap.delete(key);
391
+ return;
392
+ }
393
+ if (Array.isArray(value)) {
394
+ const yArray = new Y.Array();
395
+ value.forEach((item) => {
396
+ yArray.push([item]);
397
+ });
398
+ yMap.set(key, yArray);
399
+ } else if (typeof value === "object" && value != null) {
400
+ const existingValue = yMap.get(key);
401
+ if (existingValue instanceof Y.Map) {
402
+ updateYMapFromPartialState(existingValue, value);
403
+ } else {
404
+ const nestedMap = new Y.Map();
405
+ populateYMapFromState(nestedMap, value);
406
+ yMap.set(key, nestedMap);
407
+ }
408
+ } else {
409
+ yMap.set(key, value);
410
+ }
411
+ });
412
+ }
413
+ }
414
+ function yMapToState(yMap) {
415
+ const state = {};
416
+ yMap.forEach((value, key) => {
417
+ if (value instanceof Y.Array) {
418
+ state[key] = value.toArray();
419
+ } else if (value instanceof Y.Map) {
420
+ state[key] = yMapToState(value);
421
+ } else {
422
+ state[key] = value;
423
+ }
424
+ });
425
+ return state;
426
+ }
427
+
428
+ // src/service/BaseYjsDocumentService.ts
429
+ var BaseYjsDocumentService = class {
430
+ documents = /* @__PURE__ */ new Map();
431
+ constructor(app, logger) {
432
+ this.app = app;
433
+ this.logger = logger;
434
+ }
435
+ createDocRef = (id, schema) => {
436
+ const temporaryRef = createDocRef(this.app, id, schema);
437
+ const {
438
+ internalDocRef
439
+ } = this.getCreateInternalDoc(temporaryRef);
440
+ return internalDocRef;
441
+ };
442
+ getCreateRecordCollectionRef = (docRef, model) => {
443
+ const {
444
+ internalDoc
445
+ } = this.getCreateInternalDoc(docRef);
446
+ const modelName = getMetadata(model).name;
447
+ const existingRef = internalDoc.collectionRefs.get(modelName)?.deref();
448
+ if (existingRef != null) {
449
+ return existingRef;
450
+ }
451
+ const newRef = createRecordCollectionRef(this, docRef, model);
452
+ internalDoc.collectionRefs.set(modelName, new WeakRef(newRef));
453
+ return newRef;
454
+ };
455
+ getCreateRecordRef = (docRef, id, model) => {
456
+ const {
457
+ internalDoc
458
+ } = this.getCreateInternalDoc(docRef);
459
+ const modelName = getMetadata(model).name;
460
+ let modelMap = internalDoc.recordRefs.get(modelName);
461
+ if (!modelMap) {
462
+ modelMap = /* @__PURE__ */ new Map();
463
+ internalDoc.recordRefs.set(modelName, modelMap);
464
+ }
465
+ const existingRef = modelMap.get(id)?.deref();
466
+ if (existingRef != null) {
467
+ return existingRef;
468
+ }
469
+ const newRef = createRecordRef(this, docRef, id, model);
470
+ modelMap.set(id, new WeakRef(newRef));
471
+ return newRef;
472
+ };
473
+ /**
474
+ * Called when the first metadata subscription is opened for a document.
475
+ * Implementation must:
476
+ * - Set status to LOADING immediately
477
+ * - Load/validate metadata asynchronously
478
+ * - Set status to LOADED or ERROR when complete
479
+ * - Handle all errors internally (never throw/reject)
480
+ */
481
+ /**
482
+ * Called when the first data subscription is opened for a document.
483
+ * Implementation must:
484
+ * - Set status to LOADING immediately
485
+ * - Set up data synchronization asynchronously
486
+ * - Set status to LOADED or ERROR when ready
487
+ * - Handle all errors internally (never throw/reject)
488
+ */
489
+ /**
490
+ * Called when the last metadata subscription is closed for a document.
491
+ * Implementation should clean up any resources related to metadata loading.
492
+ */
493
+ /**
494
+ * Called when the last data subscription is closed for a document.
495
+ * Implementation should clean up any resources related to data synchronization.
496
+ */
497
+ createBaseInternalDoc = (ref, metadata, yDoc) => {
498
+ const schema = ref.schema;
499
+ return {
500
+ ref: new WeakRef(ref),
501
+ metadata,
502
+ schema,
503
+ metadataStatus: {
504
+ load: metadata ? DocumentLoadStatus.LOADED : DocumentLoadStatus.UNLOADED,
505
+ live: DocumentLiveStatus.DISCONNECTED
506
+ },
507
+ dataStatus: {
508
+ load: DocumentLoadStatus.UNLOADED,
509
+ live: DocumentLiveStatus.DISCONNECTED
510
+ },
511
+ metadataError: void 0,
512
+ dataError: void 0,
513
+ statusSubscribers: /* @__PURE__ */ new Set(),
514
+ hasMetadataSubscriptions: false,
515
+ hasDataSubscriptions: false,
516
+ collectionRefs: /* @__PURE__ */ new Map(),
517
+ recordRefs: /* @__PURE__ */ new Map(),
518
+ collectionSubscriptions: /* @__PURE__ */ new Map(),
519
+ docStateSubscribers: /* @__PURE__ */ new Set(),
520
+ metadataSubscribers: /* @__PURE__ */ new Set(),
521
+ recordSubscriptions: /* @__PURE__ */ new Map(),
522
+ yDoc: yDoc || this.initializeYDoc(schema),
523
+ yDocUpdateHandler: void 0,
524
+ yjsCollectionHandlers: /* @__PURE__ */ new Map()
525
+ };
526
+ };
527
+ hasSubscriptions(internalDoc) {
528
+ if (internalDoc.metadataSubscribers.size > 0 || internalDoc.docStateSubscribers.size > 0) {
529
+ return true;
530
+ }
531
+ for (const subs of internalDoc.recordSubscriptions.values()) {
532
+ if (!subs.changed?.size || !subs.deleted?.size) {
533
+ return true;
534
+ }
535
+ }
536
+ return false;
537
+ }
538
+ // Status helper methods
539
+ notifyStatusSubscribers(internalDoc, docRef) {
540
+ const status = {
541
+ metadata: internalDoc.metadataStatus,
542
+ data: internalDoc.dataStatus,
543
+ metadataError: internalDoc.metadataError,
544
+ dataError: internalDoc.dataError
545
+ };
546
+ for (const callback of internalDoc.statusSubscribers) {
547
+ callback(docRef, status);
548
+ }
549
+ }
550
+ updateMetadataStatus(internalDoc, docRef, update) {
551
+ if (update.load != null || update.live != null) {
552
+ internalDoc.metadataStatus = {
553
+ load: update.load ?? internalDoc.metadataStatus.load,
554
+ live: update.live ?? internalDoc.metadataStatus.live
555
+ };
556
+ }
557
+ if (update.error != null) {
558
+ internalDoc.metadataError = update.error;
559
+ } else if (update.load === DocumentLoadStatus.LOADED) {
560
+ internalDoc.metadataError = void 0;
561
+ }
562
+ this.notifyStatusSubscribers(internalDoc, docRef);
563
+ }
564
+ updateDataStatus(internalDoc, docRef, update) {
565
+ if (update.load != null || update.live != null) {
566
+ internalDoc.dataStatus = {
567
+ load: update.load ?? internalDoc.dataStatus.load,
568
+ live: update.live ?? internalDoc.dataStatus.live
569
+ };
570
+ }
571
+ if (update.error != null) {
572
+ internalDoc.dataError = update.error;
573
+ } else if (update.load === DocumentLoadStatus.LOADED) {
574
+ internalDoc.dataError = void 0;
575
+ }
576
+ this.notifyStatusSubscribers(internalDoc, docRef);
577
+ }
578
+ /**
579
+ * Hook method called after a record is set. Subclasses can override to handle
580
+ * backend synchronization, logging, or other side effects.
581
+ */
582
+ /**
583
+ * Initialize a Y.Doc with the given schema
584
+ */
585
+ initializeYDoc(schema) {
586
+ const yDoc = new Y.Doc();
587
+ initializeDocumentStructure(yDoc, schema);
588
+ return yDoc;
589
+ }
590
+ /**
591
+ * Get existing internal doc or create one with placeholder metadata for lazy initialization
592
+ */
593
+ getCreateInternalDoc(ref, metadata, initialYDoc) {
594
+ const {
595
+ id,
596
+ schema
597
+ } = ref;
598
+ const existingDoc = this.documents.get(id);
599
+ if (existingDoc != null) {
600
+ !(existingDoc.schema === schema || isDeepEqual(existingDoc.schema, schema)) ? process.env.NODE_ENV !== "production" ? invariant(false, "Schema mismatch for existing document") : invariant(false) : void 0;
601
+ const existingRef = existingDoc.ref.deref();
602
+ if (existingRef == null) {
603
+ existingDoc.ref = new WeakRef(ref);
604
+ }
605
+ return {
606
+ internalDocRef: existingRef ?? ref,
607
+ internalDoc: existingDoc,
608
+ wasExisting: true
609
+ };
610
+ }
611
+ const internalDoc = this.createInternalDoc(ref, metadata, initialYDoc);
612
+ this.documents.set(id, internalDoc);
613
+ return {
614
+ internalDocRef: ref,
615
+ internalDoc,
616
+ wasExisting: false
617
+ };
618
+ }
619
+ getDocumentSnapshot = (docRef) => {
620
+ const {
621
+ internalDoc
622
+ } = this.getCreateInternalDoc(docRef);
623
+ return Promise.resolve(internalDoc.yDoc);
624
+ };
625
+ getRecordSnapshot = (recordRef) => {
626
+ const {
627
+ internalDoc
628
+ } = this.getCreateInternalDoc(recordRef.docRef);
629
+ const snapshot = this.getRecordSnapshotInternal(internalDoc, recordRef);
630
+ if (snapshot == null) {
631
+ return Promise.reject(new Error(`Record not found: ${recordRef.id}`));
632
+ }
633
+ return Promise.resolve(snapshot);
634
+ };
635
+ getRecordSnapshotInternal(internalDoc, recordRef) {
636
+ return getRecordSnapshot(internalDoc.yDoc, getMetadata(recordRef.model).name, recordRef.id);
637
+ }
638
+ setRecord = (recordRef, state) => {
639
+ const internalDoc = this.documents.get(recordRef.docRef.id);
640
+ !(internalDoc != null) ? process.env.NODE_ENV !== "production" ? invariant(false, `Cannot set record as document not found: ${recordRef.docRef.id}`) : invariant(false) : void 0;
641
+ setRecord(internalDoc.yDoc, getMetadata(recordRef.model).name, recordRef.id, state);
642
+ this.onRecordSet?.(recordRef, state);
643
+ return Promise.resolve();
644
+ };
645
+ updateRecord = (recordRef, partialState) => {
646
+ const internalDoc = this.documents.get(recordRef.docRef.id);
647
+ !(internalDoc != null) ? process.env.NODE_ENV !== "production" ? invariant(false, `Cannot update record as document not found: ${recordRef.docRef.id}`) : invariant(false) : void 0;
648
+ const wasUpdated = updateRecord(internalDoc.yDoc, getMetadata(recordRef.model).name, recordRef.id, partialState);
649
+ if (!wasUpdated) {
650
+ return Promise.reject(new Error(`Record not found for update: ${recordRef.id}`));
651
+ }
652
+ this.onRecordSet?.(recordRef, partialState);
653
+ return Promise.resolve();
654
+ };
655
+ onMetadataChange(docRef, callback) {
656
+ const {
657
+ internalDoc,
658
+ internalDocRef
659
+ } = this.getCreateInternalDoc(docRef);
660
+ const isFirstSubscription = !internalDoc.hasMetadataSubscriptions;
661
+ internalDoc.metadataSubscribers.add(callback);
662
+ internalDoc.hasMetadataSubscriptions = true;
663
+ if (isFirstSubscription && internalDoc.metadataStatus.load === DocumentLoadStatus.UNLOADED) {
664
+ this.onMetadataSubscriptionOpened(internalDoc, internalDocRef);
665
+ }
666
+ if (internalDoc.metadata != null) {
667
+ callback(docRef, internalDoc.metadata);
668
+ }
669
+ return () => {
670
+ const currentDoc = this.documents.get(docRef.id);
671
+ if (currentDoc) {
672
+ currentDoc.metadataSubscribers.delete(callback);
673
+ if (currentDoc.metadataSubscribers.size === 0) {
674
+ currentDoc.hasMetadataSubscriptions = false;
675
+ this.onMetadataSubscriptionClosed(currentDoc, internalDocRef);
676
+ }
677
+ }
678
+ };
679
+ }
680
+ onStateChange = (docRef, callback) => {
681
+ const {
682
+ internalDoc,
683
+ internalDocRef
684
+ } = this.getCreateInternalDoc(docRef);
685
+ const isFirstDataSubscription = !internalDoc.hasDataSubscriptions;
686
+ const isFirstStateSubscription = internalDoc.docStateSubscribers.size === 0;
687
+ internalDoc.docStateSubscribers.add(callback);
688
+ internalDoc.hasDataSubscriptions = true;
689
+ if (isFirstDataSubscription && internalDoc.dataStatus.load === DocumentLoadStatus.UNLOADED) {
690
+ this.onDataSubscriptionOpened(internalDoc, internalDocRef);
691
+ }
692
+ if (isFirstStateSubscription && !internalDoc.yDocUpdateHandler) {
693
+ const updateHandler = () => {
694
+ this.notifyStateSubscribers(internalDoc, docRef);
695
+ };
696
+ internalDoc.yDoc.on("update", updateHandler);
697
+ internalDoc.yDocUpdateHandler = updateHandler;
698
+ }
699
+ callback(internalDocRef);
700
+ return () => {
701
+ const currentDoc = this.documents.get(docRef.id);
702
+ if (!currentDoc) return;
703
+ currentDoc.docStateSubscribers.delete(callback);
704
+ if (currentDoc.docStateSubscribers.size === 0 && currentDoc.yDocUpdateHandler) {
705
+ currentDoc.yDoc.off("update", currentDoc.yDocUpdateHandler);
706
+ currentDoc.yDocUpdateHandler = void 0;
707
+ }
708
+ const hasDataSubs = currentDoc.docStateSubscribers.size > 0 || currentDoc.recordSubscriptions.size > 0 || Array.from(currentDoc.collectionSubscriptions.values()).some((subs) => subs.added?.size || subs.changed?.size || subs.deleted?.size);
709
+ if (!hasDataSubs) {
710
+ currentDoc.hasDataSubscriptions = false;
711
+ this.onDataSubscriptionClosed(currentDoc, internalDocRef);
712
+ }
713
+ };
714
+ };
715
+ getDocumentRef(docId) {
716
+ const internalDoc = this.documents.get(docId);
717
+ if (!internalDoc) return null;
718
+ return createDocRef(this.app, docId, internalDoc.schema);
719
+ }
720
+ notifyMetadataSubscribers(internalDoc, docRef, metadata) {
721
+ for (const callback of internalDoc.metadataSubscribers) {
722
+ callback(docRef, metadata);
723
+ }
724
+ }
725
+ notifyStateSubscribers(internalDoc, docRef) {
726
+ for (const callback of internalDoc.docStateSubscribers) {
727
+ callback(docRef);
728
+ }
729
+ }
730
+ updateMetadata(docId, metadata) {
731
+ const internalDoc = this.documents.get(docId);
732
+ if (internalDoc) {
733
+ internalDoc.metadata = metadata;
734
+ const docRef = this.getDocumentRef(docId);
735
+ if (docRef) {
736
+ this.notifyMetadataSubscribers(internalDoc, docRef, metadata);
737
+ }
738
+ }
739
+ }
740
+ notifyCollectionSubscribers(internalDoc, collection, recordId, changeType) {
741
+ const storageName = getMetadata(collection.model).name;
742
+ const subs = internalDoc.collectionSubscriptions.get(storageName);
743
+ if (!subs) {
744
+ return;
745
+ }
746
+ const subscribers = subs[changeType];
747
+ if (subscribers == null || subscribers.size === 0) {
748
+ return;
749
+ }
750
+ const recordRefInstance = this.getCreateRecordRef(collection.docRef, recordId, collection.model);
751
+ const records = [recordRefInstance];
752
+ for (const callback of subscribers) {
753
+ callback(records);
754
+ }
755
+ }
756
+ notifyRecordSubscribers(recordRef, changeType) {
757
+ const internalDoc = this.documents.get(recordRef.docRef.id);
758
+ !(internalDoc != null) ? process.env.NODE_ENV !== "production" ? invariant(false, "Document not found for record notifications") : invariant(false) : void 0;
759
+ const recordSubs = internalDoc.recordSubscriptions.get(recordRef.id);
760
+ if (recordSubs == null) {
761
+ return;
762
+ }
763
+ !(getMetadata(recordSubs.ref.model).name === getMetadata(recordRef.model).name) ? process.env.NODE_ENV !== "production" ? invariant(false, `Model mismatch when notifying record subscribers for ${recordRef.id}: expected ${getMetadata(recordSubs.ref.model).name}, got ${getMetadata(recordRef.model).name}`) : invariant(false) : void 0;
764
+ switch (changeType) {
765
+ case "changed": {
766
+ const snapshot = this.getRecordSnapshotInternal(internalDoc, recordRef);
767
+ for (const callback of recordSubs.changed ?? []) {
768
+ try {
769
+ callback(snapshot, recordRef);
770
+ } catch (e) {
771
+ console.error("Record onChanged callback threw unhandled error", e, {
772
+ model: getMetadata(recordRef.model).name,
773
+ id: recordRef.id
774
+ });
775
+ }
776
+ }
777
+ break;
778
+ }
779
+ case "deleted": {
780
+ for (const callback of recordSubs.deleted ?? []) {
781
+ try {
782
+ callback(recordRef);
783
+ } catch (e) {
784
+ console.error("Record onDeleted callback threw unhandled error", e, {
785
+ model: getMetadata(recordRef.model).name,
786
+ id: recordRef.id
787
+ });
788
+ }
789
+ }
790
+ break;
791
+ }
792
+ }
793
+ }
794
+ // Collection methods implementation
795
+ getRecord = (collection, id) => {
796
+ const internalDoc = this.documents.get(collection.docRef.id);
797
+ if (!internalDoc) return void 0;
798
+ const storageName = getMetadata(collection.model).name;
799
+ const recordExists = getRecordData(internalDoc.yDoc, storageName, id);
800
+ return recordExists ? this.getCreateRecordRef(collection.docRef, id, collection.model) : void 0;
801
+ };
802
+ hasRecord = (collection, id) => {
803
+ const internalDoc = this.documents.get(collection.docRef.id);
804
+ if (!internalDoc) return false;
805
+ const storageName = getMetadata(collection.model).name;
806
+ return getRecordData(internalDoc.yDoc, storageName, id) != null;
807
+ };
808
+ setCollectionRecord = (collection, id, state) => {
809
+ const recordRefInstance = this.getCreateRecordRef(collection.docRef, id, collection.model);
810
+ return this.setRecord(recordRefInstance, state);
811
+ };
812
+ deleteRecord = (record) => {
813
+ const internalDoc = this.documents.get(record.docRef.id);
814
+ if (!internalDoc) {
815
+ return Promise.resolve();
816
+ }
817
+ const storageName = getMetadata(record.model).name;
818
+ const recordsCollection = getRecordsMap(internalDoc.yDoc, storageName);
819
+ const existed = recordsCollection.has(record.id);
820
+ if (existed) {
821
+ recordsCollection.delete(record.id);
822
+ }
823
+ return Promise.resolve();
824
+ };
825
+ getCollectionSize = (collection) => {
826
+ const internalDoc = this.documents.get(collection.docRef.id);
827
+ if (!internalDoc) return 0;
828
+ const storageName = getMetadata(collection.model).name;
829
+ return getAllRecordIds(internalDoc.yDoc, storageName).length;
830
+ };
831
+ getCollectionRecords = (collection) => {
832
+ const internalDoc = this.documents.get(collection.docRef.id);
833
+ if (!internalDoc) return [];
834
+ const storageName = getMetadata(collection.model).name;
835
+ const recordIds = getAllRecordIds(internalDoc.yDoc, storageName);
836
+ return recordIds.map((id) => this.getCreateRecordRef(collection.docRef, id, collection.model));
837
+ };
838
+ onCollectionItemsAdded = (collection, callback) => {
839
+ const {
840
+ internalDoc,
841
+ internalDocRef
842
+ } = this.getCreateInternalDoc(collection.docRef);
843
+ return this.subscribeToCollectionChanges(internalDoc, internalDocRef, collection, "added", callback);
844
+ };
845
+ onCollectionItemsChanged = (collection, callback) => {
846
+ const {
847
+ internalDoc,
848
+ internalDocRef
849
+ } = this.getCreateInternalDoc(collection.docRef);
850
+ return this.subscribeToCollectionChanges(internalDoc, internalDocRef, collection, "changed", callback);
851
+ };
852
+ onCollectionItemsDeleted = (collection, callback) => {
853
+ const {
854
+ internalDoc,
855
+ internalDocRef
856
+ } = this.getCreateInternalDoc(collection.docRef);
857
+ return this.subscribeToCollectionChanges(internalDoc, internalDocRef, collection, "deleted", callback);
858
+ };
859
+ // TODO: clearer naming of subscription vs handlers etc.
860
+ onRecordChanged = (record, callback) => {
861
+ const {
862
+ internalDoc,
863
+ internalDocRef
864
+ } = this.getCreateInternalDoc(record.docRef);
865
+ const isFirstDataSubscription = !internalDoc.hasDataSubscriptions;
866
+ const storageName = getMetadata(record.model).name;
867
+ const collectionRef = this.getCreateRecordCollectionRef(record.docRef, record.model);
868
+ const needsCollectionListener = !internalDoc.yjsCollectionHandlers.has(storageName);
869
+ const recordSubs = this.getCreateRecordSubscriptions(internalDoc, record);
870
+ (recordSubs.changed ??= /* @__PURE__ */ new Set()).add(callback);
871
+ internalDoc.hasDataSubscriptions = true;
872
+ if (isFirstDataSubscription && internalDoc.dataStatus.load === DocumentLoadStatus.UNLOADED) {
873
+ this.onDataSubscriptionOpened(internalDoc, internalDocRef);
874
+ }
875
+ if (needsCollectionListener) {
876
+ this.getCreateCollectionSubscriptions(internalDoc, collectionRef);
877
+ this.setupCollectionListener(internalDoc, collectionRef);
878
+ }
879
+ const snapshot = this.getRecordSnapshotInternal(internalDoc, record);
880
+ if (snapshot != null) {
881
+ callback(snapshot, record);
882
+ }
883
+ return () => {
884
+ recordSubs.changed?.delete(callback);
885
+ const currentDoc = this.documents.get(record.docRef.id);
886
+ if (!currentDoc) return;
887
+ if (isRecordSubscriptionsEmpty(recordSubs)) {
888
+ currentDoc.recordSubscriptions.delete(record.id);
889
+ }
890
+ this.cleanupCollectionListenerIfUnused(currentDoc, record.docRef.id, storageName);
891
+ const hasDataSubs = currentDoc.docStateSubscribers.size > 0 || currentDoc.recordSubscriptions.size > 0 || Array.from(currentDoc.collectionSubscriptions.values()).some((subs) => subs.added?.size || subs.changed?.size || subs.deleted?.size);
892
+ if (!hasDataSubs) {
893
+ currentDoc.hasDataSubscriptions = false;
894
+ this.onDataSubscriptionClosed(currentDoc, internalDocRef);
895
+ }
896
+ };
897
+ };
898
+ onRecordDeleted = (record, callback) => {
899
+ const {
900
+ internalDoc,
901
+ internalDocRef
902
+ } = this.getCreateInternalDoc(record.docRef);
903
+ const isFirstDataSubscription = !internalDoc.hasDataSubscriptions;
904
+ const storageName = getMetadata(record.model).name;
905
+ const collectionRef = this.getCreateRecordCollectionRef(record.docRef, record.model);
906
+ const needsCollectionListener = !internalDoc.yjsCollectionHandlers.has(storageName);
907
+ const recordSubs = this.getCreateRecordSubscriptions(internalDoc, record);
908
+ (recordSubs.deleted ??= /* @__PURE__ */ new Set()).add(callback);
909
+ internalDoc.hasDataSubscriptions = true;
910
+ if (isFirstDataSubscription && internalDoc.dataStatus.load === DocumentLoadStatus.UNLOADED) {
911
+ this.onDataSubscriptionOpened(internalDoc, internalDocRef);
912
+ }
913
+ if (needsCollectionListener) {
914
+ this.getCreateCollectionSubscriptions(internalDoc, collectionRef);
915
+ this.setupCollectionListener(internalDoc, collectionRef);
916
+ }
917
+ return () => {
918
+ recordSubs.deleted?.delete(callback);
919
+ const currentDoc = this.documents.get(record.docRef.id);
920
+ if (!currentDoc) return;
921
+ if (isRecordSubscriptionsEmpty(recordSubs)) {
922
+ currentDoc.recordSubscriptions.delete(record.id);
923
+ }
924
+ this.cleanupCollectionListenerIfUnused(currentDoc, record.docRef.id, storageName);
925
+ const hasDataSubs = currentDoc.docStateSubscribers.size > 0 || currentDoc.recordSubscriptions.size > 0 || Array.from(currentDoc.collectionSubscriptions.values()).some((subs) => subs.added?.size || subs.changed?.size || subs.deleted?.size);
926
+ if (!hasDataSubs) {
927
+ currentDoc.hasDataSubscriptions = false;
928
+ this.onDataSubscriptionClosed(currentDoc, internalDocRef);
929
+ }
930
+ };
931
+ };
932
+ getCreateCollectionSubscriptions(internalDoc, collection) {
933
+ const storageName = getMetadata(collection.model).name;
934
+ const docCollectionSubs = internalDoc.collectionSubscriptions.get(storageName) ?? internalDoc.collectionSubscriptions.set(storageName, {}).get(storageName);
935
+ return docCollectionSubs;
936
+ }
937
+ getCreateRecordSubscriptions(internalDoc, record) {
938
+ let recordSubs = internalDoc.recordSubscriptions.get(record.id);
939
+ if (!recordSubs) {
940
+ recordSubs = {
941
+ ref: record
942
+ };
943
+ internalDoc.recordSubscriptions.set(record.id, recordSubs);
944
+ } else {
945
+ if (getMetadata(recordSubs.ref.model).name !== getMetadata(record.model).name) {
946
+ throw new Error(`Model mismatch for record ${record.id}: expected ${getMetadata(recordSubs.ref.model).name}, got ${getMetadata(record.model).name}`);
947
+ }
948
+ }
949
+ return recordSubs;
950
+ }
951
+ subscribeToCollectionChanges(internalDoc, internalDocRef, collection, changeType, callback) {
952
+ const isFirstDataSubscription = !internalDoc.hasDataSubscriptions;
953
+ const modelSubscriptions = this.getCreateCollectionSubscriptions(internalDoc, collection);
954
+ const wasEmpty = isCollectionSubscriptionsEmpty(modelSubscriptions);
955
+ (modelSubscriptions[changeType] ??= /* @__PURE__ */ new Set()).add(callback);
956
+ internalDoc.hasDataSubscriptions = true;
957
+ if (isFirstDataSubscription && internalDoc.dataStatus.load === DocumentLoadStatus.UNLOADED) {
958
+ this.onDataSubscriptionOpened(internalDoc, internalDocRef);
959
+ }
960
+ if (wasEmpty) {
961
+ this.setupCollectionListener(internalDoc, collection);
962
+ }
963
+ return () => {
964
+ modelSubscriptions[changeType]?.delete(callback);
965
+ const currentDoc = this.documents.get(collection.docRef.id);
966
+ if (!currentDoc) return;
967
+ const storageName = getMetadata(collection.model).name;
968
+ this.cleanupCollectionListenerIfUnused(currentDoc, collection.docRef.id, storageName);
969
+ const hasDataSubs = currentDoc.docStateSubscribers.size > 0 || currentDoc.recordSubscriptions.size > 0 || Array.from(currentDoc.collectionSubscriptions.values()).some((subs) => subs.added?.size || subs.changed?.size || subs.deleted?.size);
970
+ if (!hasDataSubs) {
971
+ currentDoc.hasDataSubscriptions = false;
972
+ this.onDataSubscriptionClosed(currentDoc, internalDocRef);
973
+ }
974
+ };
975
+ }
976
+ setupCollectionListener(internalDoc, collection) {
977
+ const docId = collection.docRef.id;
978
+ const storageName = getMetadata(collection.model).name;
979
+ const yCollection = internalDoc.yDoc.getMap(storageName);
980
+ this.logger.debug("Setting up collection listener", {
981
+ docId,
982
+ storageName,
983
+ existingKeys: Array.from(yCollection.keys()),
984
+ allDocMaps: Array.from(internalDoc.yDoc.share.keys())
985
+ });
986
+ const eventHandler = (events) => {
987
+ this.logger.debug("Y.Map observeDeep fired", {
988
+ docId,
989
+ storageName,
990
+ eventCount: events.length
991
+ });
992
+ const currentDoc = this.documents.get(docId);
993
+ if (!currentDoc) return;
994
+ const subs = currentDoc.collectionSubscriptions.get(storageName);
995
+ if (!subs) return;
996
+ const addedKeys = /* @__PURE__ */ new Set();
997
+ const changedKeys = /* @__PURE__ */ new Set();
998
+ const deletedKeys = /* @__PURE__ */ new Set();
999
+ for (const event of events) {
1000
+ if (event.target === yCollection) {
1001
+ for (const [key, change] of event.changes.keys) {
1002
+ switch (change.action) {
1003
+ case "add":
1004
+ addedKeys.add(key);
1005
+ break;
1006
+ case "update":
1007
+ changedKeys.add(key);
1008
+ break;
1009
+ case "delete":
1010
+ deletedKeys.add(key);
1011
+ break;
1012
+ }
1013
+ }
1014
+ } else {
1015
+ const recordId = event.path[0];
1016
+ if (recordId != null) {
1017
+ changedKeys.add(recordId);
1018
+ }
1019
+ }
1020
+ }
1021
+ if (addedKeys.size > 0) {
1022
+ const addedRecords = Array.from(addedKeys).map((id) => this.getCreateRecordRef(collection.docRef, id, collection.model));
1023
+ if (subs.added != null) {
1024
+ for (const callback of subs.added) {
1025
+ callback(addedRecords);
1026
+ }
1027
+ }
1028
+ for (const record of addedRecords) {
1029
+ this.notifyRecordSubscribers(record, "changed");
1030
+ }
1031
+ }
1032
+ if (changedKeys.size > 0) {
1033
+ const changedRecords = Array.from(changedKeys).map((id) => this.getCreateRecordRef(collection.docRef, id, collection.model));
1034
+ if (subs.changed != null) {
1035
+ for (const callback of subs.changed) {
1036
+ callback(changedRecords);
1037
+ }
1038
+ }
1039
+ for (const record of changedRecords) {
1040
+ this.notifyRecordSubscribers(record, "changed");
1041
+ }
1042
+ }
1043
+ if (deletedKeys.size > 0) {
1044
+ const deletedRecords = Array.from(deletedKeys).map((id) => this.getCreateRecordRef(collection.docRef, id, collection.model));
1045
+ if (subs.deleted?.size) {
1046
+ for (const callback of subs.deleted) {
1047
+ callback(deletedRecords);
1048
+ }
1049
+ }
1050
+ for (const record of deletedRecords) {
1051
+ this.notifyRecordSubscribers(record, "deleted");
1052
+ }
1053
+ }
1054
+ };
1055
+ yCollection.observeDeep(eventHandler);
1056
+ internalDoc.yjsCollectionHandlers.set(storageName, () => {
1057
+ yCollection.unobserveDeep(eventHandler);
1058
+ });
1059
+ }
1060
+ cleanupCollectionListener(docId, storageName) {
1061
+ const internalDoc = this.documents.get(docId);
1062
+ if (internalDoc == null) {
1063
+ return;
1064
+ }
1065
+ const cleanup = internalDoc.yjsCollectionHandlers.get(storageName);
1066
+ if (cleanup != null) {
1067
+ cleanup();
1068
+ internalDoc.yjsCollectionHandlers.delete(storageName);
1069
+ }
1070
+ }
1071
+ cleanupCollectionListenerIfUnused(internalDoc, docId, storageName) {
1072
+ const collectionSubs = internalDoc.collectionSubscriptions.get(storageName);
1073
+ const hasCollectionSubs = collectionSubs != null && (collectionSubs.added?.size || collectionSubs.changed?.size || collectionSubs.deleted?.size);
1074
+ const hasRecordSubs = Array.from(internalDoc.recordSubscriptions.values()).some((recordSubs) => getMetadata(recordSubs.ref.model).name === storageName && !isRecordSubscriptionsEmpty(recordSubs));
1075
+ if (!hasCollectionSubs && !hasRecordSubs) {
1076
+ this.cleanupCollectionListener(docId, storageName);
1077
+ if (collectionSubs != null) {
1078
+ internalDoc.collectionSubscriptions.delete(storageName);
1079
+ }
1080
+ }
1081
+ }
1082
+ // DocumentService status methods implementation
1083
+ getDocumentStatus = (docRef) => {
1084
+ const {
1085
+ internalDoc
1086
+ } = this.getCreateInternalDoc(docRef);
1087
+ return {
1088
+ metadata: internalDoc.metadataStatus,
1089
+ data: internalDoc.dataStatus,
1090
+ metadataError: internalDoc.metadataError,
1091
+ dataError: internalDoc.dataError
1092
+ };
1093
+ };
1094
+ onStatusChange = (docRef, callback) => {
1095
+ const {
1096
+ internalDoc
1097
+ } = this.getCreateInternalDoc(docRef);
1098
+ internalDoc.statusSubscribers.add(callback);
1099
+ const status = {
1100
+ metadata: internalDoc.metadataStatus,
1101
+ data: internalDoc.dataStatus,
1102
+ metadataError: internalDoc.metadataError,
1103
+ dataError: internalDoc.dataError
1104
+ };
1105
+ callback(docRef, status);
1106
+ return () => {
1107
+ const currentDoc = this.documents.get(docRef.id);
1108
+ if (currentDoc) {
1109
+ currentDoc.statusSubscribers.delete(callback);
1110
+ }
1111
+ };
1112
+ };
1113
+ waitForMetadataLoad = async (docRef) => {
1114
+ const {
1115
+ internalDoc
1116
+ } = this.getCreateInternalDoc(docRef);
1117
+ if (internalDoc.metadataStatus.load === DocumentLoadStatus.LOADED) {
1118
+ return Promise.resolve();
1119
+ }
1120
+ if (internalDoc.metadataStatus.load === DocumentLoadStatus.ERROR) {
1121
+ return Promise.reject(new Error("Metadata load error", {
1122
+ cause: internalDoc.metadataError
1123
+ }));
1124
+ }
1125
+ return new Promise((resolve, reject) => {
1126
+ const unsubscribe = this.onStatusChange(docRef, (_, status) => {
1127
+ if (status.metadata.load === DocumentLoadStatus.LOADED) {
1128
+ unsubscribe();
1129
+ resolve();
1130
+ } else if (status.metadata.load === DocumentLoadStatus.ERROR) {
1131
+ unsubscribe();
1132
+ reject(new Error("Metadata load error", {
1133
+ cause: status.metadataError
1134
+ }));
1135
+ }
1136
+ });
1137
+ });
1138
+ };
1139
+ waitForDataLoad = async (docRef) => {
1140
+ const {
1141
+ internalDoc
1142
+ } = this.getCreateInternalDoc(docRef);
1143
+ if (internalDoc.dataStatus.load === DocumentLoadStatus.LOADED) {
1144
+ return Promise.resolve();
1145
+ }
1146
+ if (internalDoc.dataStatus.load === DocumentLoadStatus.ERROR) {
1147
+ return Promise.reject(new Error("Data load error", {
1148
+ cause: internalDoc.dataError
1149
+ }));
1150
+ }
1151
+ return new Promise((resolve, reject) => {
1152
+ const unsubscribe = this.onStatusChange(docRef, (_, status) => {
1153
+ if (status.data.load === DocumentLoadStatus.LOADED) {
1154
+ unsubscribe();
1155
+ resolve();
1156
+ } else if (status.data.load === DocumentLoadStatus.ERROR) {
1157
+ unsubscribe();
1158
+ reject(new Error("Data load error", {
1159
+ cause: status.dataError
1160
+ }));
1161
+ }
1162
+ });
1163
+ });
1164
+ };
1165
+ // FIXME: don't expose in production builds
1166
+ /**
1167
+ * @internal
1168
+ */
1169
+ getYDocForTesting(docId) {
1170
+ const internalDoc = this.documents.get(docId);
1171
+ return internalDoc ? internalDoc.yDoc : null;
1172
+ }
1173
+ };
1174
+ function isCollectionSubscriptionsEmpty(subs) {
1175
+ return !subs.added?.size && !subs.changed?.size && !subs.deleted?.size;
1176
+ }
1177
+ function isRecordSubscriptionsEmpty(subs) {
1178
+ return !subs.changed?.size && !subs.deleted?.size;
1179
+ }
1180
+ function createInMemoryDocumentServiceConfig({
1181
+ autoCreateDocuments = true
1182
+ } = {}) {
1183
+ const config = {
1184
+ autoCreateDocuments
1185
+ };
1186
+ return createDocumentServiceConfig(internalCreateInMemoryDocumentService, config);
1187
+ }
1188
+ function internalCreateInMemoryDocumentService(app, options) {
1189
+ return new InMemoryDocumentService(app, options);
1190
+ }
1191
+ var InMemoryDocumentService = class extends BaseYjsDocumentService {
1192
+ constructor(app, config) {
1193
+ super(app, app.config.logger.child({}, {
1194
+ level: "debug",
1195
+ msgPrefix: "InMemoryDocumentService"
1196
+ }));
1197
+ this.config = config;
1198
+ }
1199
+ createInternalDoc(ref, metadata, yDoc) {
1200
+ return this.createBaseInternalDoc(ref, metadata, yDoc);
1201
+ }
1202
+ get hasMetadataSubscriptions() {
1203
+ return Array.from(this.documents.values()).some((doc) => this.hasSubscriptions(doc) && doc.metadataSubscribers.size > 0);
1204
+ }
1205
+ get hasStateSubscriptions() {
1206
+ return Array.from(this.documents.values()).some((doc) => this.hasSubscriptions(doc) && doc.docStateSubscribers.size > 0);
1207
+ }
1208
+ createDocument = (metadata, schema) => {
1209
+ const id = generateDocumentId();
1210
+ const docRef = createDocRef(this.app, id, schema);
1211
+ const yDoc = this.initializeYDoc(schema);
1212
+ this.getCreateInternalDoc(docRef, metadata, yDoc);
1213
+ return Promise.resolve(docRef);
1214
+ };
1215
+ // Lifecycle method implementations
1216
+ onMetadataSubscriptionOpened(internalDoc, docRef) {
1217
+ this.updateMetadataStatus(internalDoc, docRef, {
1218
+ load: DocumentLoadStatus.LOADING
1219
+ });
1220
+ if (this.config.autoCreateDocuments === false && internalDoc.metadata == null) {
1221
+ this.updateMetadataStatus(internalDoc, docRef, {
1222
+ error: new Error("Document not found and autoCreateDocuments is disabled"),
1223
+ load: DocumentLoadStatus.ERROR
1224
+ });
1225
+ return;
1226
+ }
1227
+ this.updateMetadataStatus(internalDoc, docRef, {
1228
+ load: DocumentLoadStatus.LOADED
1229
+ });
1230
+ }
1231
+ onDataSubscriptionOpened(internalDoc, docRef) {
1232
+ this.updateDataStatus(internalDoc, docRef, {
1233
+ load: DocumentLoadStatus.LOADING
1234
+ });
1235
+ if (this.config.autoCreateDocuments === false && internalDoc.metadata == null) {
1236
+ this.updateDataStatus(internalDoc, docRef, {
1237
+ error: new Error("Document not found and autoCreateDocuments is disabled"),
1238
+ load: DocumentLoadStatus.ERROR
1239
+ });
1240
+ return;
1241
+ }
1242
+ this.updateDataStatus(internalDoc, docRef, {
1243
+ load: DocumentLoadStatus.LOADED
1244
+ });
1245
+ }
1246
+ onMetadataSubscriptionClosed(_internalDoc, _docRef) {
1247
+ }
1248
+ onDataSubscriptionClosed(_internalDoc, _docRef) {
1249
+ }
1250
+ };
1251
+ function generateDocumentId() {
1252
+ return generateId();
1253
+ }
1254
+
1255
+ export { BaseYjsDocumentService, DocumentLiveStatus, DocumentLoadStatus, STATE_MODULE_ACCESSOR, createDocRef, createDocumentServiceConfig, createInMemoryDocumentServiceConfig, createRecordCollectionRef, createRecordRef, getDocumentService, getStateModule, invalidDocRef, invalidRecordCollectionRef, invalidRecordRef, isValidDocRef, isValidRecordCollectionRef, isValidRecordRef };
1256
+ //# sourceMappingURL=index.js.map
1257
+ //# sourceMappingURL=index.js.map