@contember/bindx 0.1.29 → 0.1.31

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 (49) hide show
  1. package/dist/adapter/ContemberAdapter.d.ts.map +1 -1
  2. package/dist/adapter/types.d.ts +4 -0
  3. package/dist/adapter/types.d.ts.map +1 -1
  4. package/dist/core/ActionDispatcher.d.ts.map +1 -1
  5. package/dist/core/actions.d.ts +4 -2
  6. package/dist/core/actions.d.ts.map +1 -1
  7. package/dist/handles/HasManyListHandle.d.ts.map +1 -1
  8. package/dist/handles/HasOneHandle.d.ts +10 -0
  9. package/dist/handles/HasOneHandle.d.ts.map +1 -1
  10. package/dist/handles/types.d.ts +2 -0
  11. package/dist/handles/types.d.ts.map +1 -1
  12. package/dist/persistence/BatchPersister.d.ts +30 -0
  13. package/dist/persistence/BatchPersister.d.ts.map +1 -1
  14. package/dist/persistence/MutationCollector.d.ts +28 -0
  15. package/dist/persistence/MutationCollector.d.ts.map +1 -1
  16. package/dist/persistence/types.d.ts +6 -0
  17. package/dist/persistence/types.d.ts.map +1 -1
  18. package/dist/store/EntityMetaStore.d.ts +4 -0
  19. package/dist/store/EntityMetaStore.d.ts.map +1 -1
  20. package/dist/store/EntitySnapshotStore.d.ts +4 -0
  21. package/dist/store/EntitySnapshotStore.d.ts.map +1 -1
  22. package/dist/store/ErrorStore.d.ts +6 -0
  23. package/dist/store/ErrorStore.d.ts.map +1 -1
  24. package/dist/store/RelationStore.d.ts +15 -0
  25. package/dist/store/RelationStore.d.ts.map +1 -1
  26. package/dist/store/SnapshotStore.d.ts +7 -0
  27. package/dist/store/SnapshotStore.d.ts.map +1 -1
  28. package/dist/store/SubscriptionManager.d.ts +15 -0
  29. package/dist/store/SubscriptionManager.d.ts.map +1 -1
  30. package/dist/store/TouchedStore.d.ts +4 -0
  31. package/dist/store/TouchedStore.d.ts.map +1 -1
  32. package/package.json +2 -2
  33. package/src/adapter/ContemberAdapter.ts +11 -5
  34. package/src/adapter/types.ts +4 -0
  35. package/src/core/ActionDispatcher.ts +3 -0
  36. package/src/core/actions.ts +6 -2
  37. package/src/handles/HasManyListHandle.ts +1 -6
  38. package/src/handles/HasOneHandle.ts +22 -1
  39. package/src/handles/types.ts +2 -0
  40. package/src/persistence/BatchPersister.ts +237 -0
  41. package/src/persistence/MutationCollector.ts +167 -19
  42. package/src/persistence/types.ts +6 -0
  43. package/src/store/EntityMetaStore.ts +29 -0
  44. package/src/store/EntitySnapshotStore.ts +23 -0
  45. package/src/store/ErrorStore.ts +30 -0
  46. package/src/store/RelationStore.ts +137 -0
  47. package/src/store/SnapshotStore.ts +62 -5
  48. package/src/store/SubscriptionManager.ts +82 -2
  49. package/src/store/TouchedStore.ts +16 -0
@@ -31,9 +31,28 @@ export class SubscriptionManager {
31
31
  /** Parent-child relationships: childKey -> Set of parentKeys */
32
32
  private readonly childToParents = new Map<string, Set<string>>()
33
33
 
34
+ /** Maps old keys → new keys after rekey, so unsubscribe closures can find migrated callbacks */
35
+ private readonly rekeyedKeys = new Map<string, string>()
36
+
34
37
  /** Global version number for change detection */
35
38
  private globalVersion = 0
36
39
 
40
+ /**
41
+ * Resolves a key through the rekey redirect chain.
42
+ * The rekey() method collapses chains (A→C instead of A→B→C), so this should
43
+ * always resolve in one hop. The loop with depth limit is a safety net in case
44
+ * chain collapsing has a bug — prevents infinite loops instead of silently hanging.
45
+ */
46
+ private resolveKey(key: string): string {
47
+ let resolved = key
48
+ for (let depth = 0; depth < 10; depth++) {
49
+ const next = this.rekeyedKeys.get(resolved)
50
+ if (!next || next === resolved) return resolved
51
+ resolved = next
52
+ }
53
+ return resolved
54
+ }
55
+
37
56
  /**
38
57
  * Subscribe to changes on a specific entity.
39
58
  * Returns unsubscribe function.
@@ -46,7 +65,8 @@ export class SubscriptionManager {
46
65
  this.entitySubscribers.get(key)!.add(callback)
47
66
 
48
67
  return () => {
49
- this.entitySubscribers.get(key)?.delete(callback)
68
+ const resolvedKey = this.resolveKey(key)
69
+ this.entitySubscribers.get(resolvedKey)?.delete(callback)
50
70
  }
51
71
  }
52
72
 
@@ -62,7 +82,8 @@ export class SubscriptionManager {
62
82
  this.relationSubscribers.get(key)!.add(callback)
63
83
 
64
84
  return () => {
65
- this.relationSubscribers.get(key)?.delete(callback)
85
+ const resolvedKey = this.resolveKey(key)
86
+ this.relationSubscribers.get(resolvedKey)?.delete(callback)
66
87
  }
67
88
  }
68
89
 
@@ -238,4 +259,63 @@ export class SubscriptionManager {
238
259
  sub()
239
260
  }
240
261
  }
262
+
263
+ /**
264
+ * Moves subscriptions and parent-child relationships from oldKey to newKey.
265
+ * Also rekeys relation subscribers under oldKeyPrefix to newKeyPrefix.
266
+ * Registers redirects so unsubscribe closures can find migrated callbacks.
267
+ */
268
+ rekey(oldKey: string, newKey: string, oldKeyPrefix: string, newKeyPrefix: string): void {
269
+ // Register redirect for entity key (update existing chains first)
270
+ for (const [fromKey, toKey] of this.rekeyedKeys) {
271
+ if (toKey === oldKey) {
272
+ this.rekeyedKeys.set(fromKey, newKey)
273
+ }
274
+ }
275
+ this.rekeyedKeys.set(oldKey, newKey)
276
+
277
+ // Move entity subscribers
278
+ const entitySubs = this.entitySubscribers.get(oldKey)
279
+ if (entitySubs) {
280
+ this.entitySubscribers.delete(oldKey)
281
+ this.entitySubscribers.set(newKey, entitySubs)
282
+ }
283
+
284
+ // Move relation subscribers by prefix (e.g. "Entity:tempId:" → "Entity:persistedId:")
285
+ const toMoveRelations: [string, Set<Subscriber>][] = []
286
+ for (const [key, subs] of this.relationSubscribers) {
287
+ if (key.startsWith(oldKeyPrefix)) {
288
+ toMoveRelations.push([key, subs])
289
+ }
290
+ }
291
+ for (const [oldRelKey, subs] of toMoveRelations) {
292
+ const newRelKey = newKeyPrefix + oldRelKey.slice(oldKeyPrefix.length)
293
+
294
+ // Register redirect for relation key (update existing chains first)
295
+ for (const [fromKey, toKey] of this.rekeyedKeys) {
296
+ if (toKey === oldRelKey) {
297
+ this.rekeyedKeys.set(fromKey, newRelKey)
298
+ }
299
+ }
300
+ this.rekeyedKeys.set(oldRelKey, newRelKey)
301
+
302
+ this.relationSubscribers.delete(oldRelKey)
303
+ this.relationSubscribers.set(newRelKey, subs)
304
+ }
305
+
306
+ // Move parent-child: update child→parent mappings
307
+ const parents = this.childToParents.get(oldKey)
308
+ if (parents) {
309
+ this.childToParents.delete(oldKey)
310
+ this.childToParents.set(newKey, parents)
311
+ }
312
+
313
+ // Update parent-child: replace oldKey in any parent sets that reference it
314
+ for (const parentSet of this.childToParents.values()) {
315
+ if (parentSet.has(oldKey)) {
316
+ parentSet.delete(oldKey)
317
+ parentSet.add(newKey)
318
+ }
319
+ }
320
+ }
241
321
  }
@@ -36,6 +36,22 @@ export class TouchedStore {
36
36
  }
37
37
  }
38
38
 
39
+ /**
40
+ * Rekeys all touched fields from oldKeyPrefix to newKeyPrefix.
41
+ */
42
+ rekey(oldKeyPrefix: string, newKeyPrefix: string): void {
43
+ const toMove: [string, boolean][] = []
44
+ for (const [key, value] of this.touchedFields) {
45
+ if (key.startsWith(oldKeyPrefix)) {
46
+ toMove.push([key, value])
47
+ }
48
+ }
49
+ for (const [oldKey, value] of toMove) {
50
+ this.touchedFields.delete(oldKey)
51
+ this.touchedFields.set(newKeyPrefix + oldKey.slice(oldKeyPrefix.length), value)
52
+ }
53
+ }
54
+
39
55
  clear(): void {
40
56
  this.touchedFields.clear()
41
57
  }