@zveltio/sdk 3.0.0-beta.5 → 3.0.0-beta.70

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 (105) hide show
  1. package/dist/client/Auth.d.ts.map +1 -1
  2. package/dist/client/Auth.js +3 -0
  3. package/dist/client/QueryBuilder.d.ts.map +1 -1
  4. package/dist/client/QueryBuilder.js +4 -0
  5. package/dist/client/RealtimeClient.d.ts +19 -1
  6. package/dist/client/RealtimeClient.d.ts.map +1 -1
  7. package/dist/client/RealtimeClient.js +65 -12
  8. package/dist/client/ZveltioClient.d.ts.map +1 -1
  9. package/dist/client/ZveltioClient.js +12 -1
  10. package/dist/client.d.ts +28 -2
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +41 -14
  13. package/dist/core.d.ts.map +1 -1
  14. package/dist/core.js +21 -3
  15. package/dist/crdt.d.ts.map +1 -1
  16. package/dist/crdt.js +11 -2
  17. package/dist/ddl/index.d.ts.map +1 -1
  18. package/dist/errors.d.ts +58 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +124 -0
  21. package/dist/extension/index.d.ts +459 -29
  22. package/dist/extension/index.d.ts.map +1 -1
  23. package/dist/extension/index.js +3 -0
  24. package/dist/extension/jsonb.d.ts +62 -0
  25. package/dist/extension/jsonb.d.ts.map +1 -0
  26. package/dist/extension/jsonb.js +64 -0
  27. package/dist/extension/multipart.d.ts +58 -0
  28. package/dist/extension/multipart.d.ts.map +1 -0
  29. package/dist/extension/multipart.js +61 -0
  30. package/dist/extension/numeric.d.ts +94 -0
  31. package/dist/extension/numeric.d.ts.map +1 -0
  32. package/dist/extension/numeric.js +162 -0
  33. package/dist/extension/permission-gate.d.ts +15 -0
  34. package/dist/extension/permission-gate.d.ts.map +1 -1
  35. package/dist/extension/permission-gate.js +33 -1
  36. package/dist/index.d.ts +3 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/local-store.d.ts +5 -0
  40. package/dist/local-store.d.ts.map +1 -1
  41. package/dist/local-store.js +62 -10
  42. package/dist/offline/index.d.ts +3 -3
  43. package/dist/offline/index.js +51 -15
  44. package/dist/publish/index.d.ts.map +1 -1
  45. package/dist/publish/index.js +6 -1
  46. package/dist/realtime.d.ts +18 -1
  47. package/dist/realtime.d.ts.map +1 -1
  48. package/dist/realtime.js +34 -3
  49. package/dist/rpc/index.d.ts.map +1 -1
  50. package/dist/rpc/index.js +2 -0
  51. package/dist/schema-codegen.d.ts +3 -1
  52. package/dist/schema-codegen.d.ts.map +1 -1
  53. package/dist/schema-codegen.js +41 -7
  54. package/dist/schema-watcher.d.ts +10 -2
  55. package/dist/schema-watcher.d.ts.map +1 -1
  56. package/dist/schema-watcher.js +30 -10
  57. package/dist/studio/index.d.ts +1 -0
  58. package/dist/studio/index.d.ts.map +1 -1
  59. package/dist/studio/index.js +3 -0
  60. package/dist/studio/slot-owner-registry.d.ts +13 -0
  61. package/dist/studio/slot-owner-registry.d.ts.map +1 -0
  62. package/dist/studio/slot-owner-registry.js +21 -0
  63. package/dist/svelte.d.ts +1 -1
  64. package/dist/svelte.d.ts.map +1 -1
  65. package/dist/svelte.js +4 -2
  66. package/dist/sync-manager.d.ts +30 -4
  67. package/dist/sync-manager.d.ts.map +1 -1
  68. package/dist/sync-manager.js +101 -15
  69. package/dist/testing/index.d.ts.map +1 -1
  70. package/dist/testing/index.js +23 -1
  71. package/dist/testing/with-test-db.d.ts.map +1 -1
  72. package/dist/testing/with-test-db.js +21 -14
  73. package/dist/tests/client.test.d.ts +2 -0
  74. package/dist/tests/client.test.d.ts.map +1 -0
  75. package/dist/tests/client.test.js +61 -0
  76. package/dist/tests/crdt-orset.test.d.ts +2 -0
  77. package/dist/tests/crdt-orset.test.d.ts.map +1 -0
  78. package/dist/tests/crdt-orset.test.js +23 -0
  79. package/dist/tests/local-store.test.js +44 -0
  80. package/dist/tests/multipart.test.d.ts +2 -0
  81. package/dist/tests/multipart.test.d.ts.map +1 -0
  82. package/dist/tests/multipart.test.js +37 -0
  83. package/dist/tests/offline-provider-crdt.test.d.ts +2 -0
  84. package/dist/tests/offline-provider-crdt.test.d.ts.map +1 -0
  85. package/dist/tests/offline-provider-crdt.test.js +119 -0
  86. package/dist/tests/realtime.test.d.ts +2 -0
  87. package/dist/tests/realtime.test.d.ts.map +1 -0
  88. package/dist/tests/realtime.test.js +284 -0
  89. package/dist/tests/sync-manager.test.d.ts +2 -0
  90. package/dist/tests/sync-manager.test.d.ts.map +1 -0
  91. package/dist/tests/sync-manager.test.js +65 -0
  92. package/dist/tests/with-test-db.test.d.ts +2 -0
  93. package/dist/tests/with-test-db.test.d.ts.map +1 -0
  94. package/dist/tests/with-test-db.test.js +57 -0
  95. package/dist/types/index.d.ts.map +1 -1
  96. package/dist/validate/index.d.ts +49 -0
  97. package/dist/validate/index.d.ts.map +1 -1
  98. package/dist/validate/index.js +503 -7
  99. package/dist/validate/shared-message-keys.d.ts +16 -0
  100. package/dist/validate/shared-message-keys.d.ts.map +1 -0
  101. package/dist/validate/shared-message-keys.js +227 -0
  102. package/package.json +7 -7
  103. package/dist/generate-types.d.ts +0 -37
  104. package/dist/generate-types.d.ts.map +0 -1
  105. package/dist/generate-types.js +0 -157
@@ -35,6 +35,7 @@ export declare function applyFormAlterHooks(hooks: FormAlterHook[], schema: Form
35
35
  export declare function sortSlotContributions<T extends {
36
36
  priority?: number;
37
37
  }>(items: T[]): T[];
38
+ export { removeOwnerFromSlots, registerOwnedOnSlot, type OwnedSlotContribution, } from './slot-owner-registry.js';
38
39
  /** Register a top-level Studio page contributed by this extension. */
39
40
  export declare function registerRoute(route: StudioRoute): void;
40
41
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/studio/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EAEV,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,UAAU,SAAU,SAAQ,YAAY;IACtC,qDAAqD;IACrD,MAAM,IAAI,UAAU,CAAC;CACtB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAoE5D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EAAE,EACtB,MAAM,EAAE,UAAU,EAClB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAChC,UAAU,CAWZ;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAGtF;AAoCD,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAEtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAE/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAE3E;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAEvE;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAID,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,GACV,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/studio/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EAEV,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,UAAU,SAAU,SAAQ,YAAY;IACtC,qDAAqD;IACrD,MAAM,IAAI,UAAU,CAAC;CACtB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAoE5D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EAAE,EACtB,MAAM,EAAE,UAAU,EAClB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAChC,UAAU,CAWZ;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAGtF;AAED,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAqClC,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAEtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAE/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAE3E;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAEvE;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAID,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,GACV,MAAM,uBAAuB,CAAC"}
@@ -109,9 +109,11 @@ export function sortSlotContributions(items) {
109
109
  // We rely on Array.prototype.sort being stable (TC39 since 2019).
110
110
  return items.slice().sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
111
111
  }
112
+ export { removeOwnerFromSlots, registerOwnedOnSlot, } from './slot-owner-registry.js';
112
113
  function getStudioGlobal() {
113
114
  if (typeof window === 'undefined')
114
115
  return null;
116
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
115
117
  const g = window.__zveltio;
116
118
  if (!g) {
117
119
  console.warn('[zveltio/sdk/studio] window.__zveltio is not installed. Is the bundle running inside Studio?');
@@ -176,5 +178,6 @@ export function registerAssetPreview(handler) {
176
178
  export function getEngineUrl() {
177
179
  if (typeof window === 'undefined')
178
180
  return '';
181
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
179
182
  return window.__ZVELTIO_ENGINE_URL__ || getStudioGlobal()?.engineUrl || '';
180
183
  }
@@ -0,0 +1,13 @@
1
+ import type { SlotContribution } from '../extension/index.js';
2
+ /** Owner tag set by compile-time `contribute.ts` modules; absent on legacy window.__zveltio calls. */
3
+ export type OwnedSlotContribution = SlotContribution & {
4
+ owner?: string;
5
+ };
6
+ /** Remove every contribution registered by `owner` across all slot names. */
7
+ export declare function removeOwnerFromSlots(slots: Record<string, OwnedSlotContribution[]>, owner: string): Record<string, OwnedSlotContribution[]>;
8
+ /**
9
+ * Register (or replace) a single owner's contribution on one slot.
10
+ * Prior entries from the same owner on that slot are dropped first.
11
+ */
12
+ export declare function registerOwnedOnSlot(slots: Record<string, OwnedSlotContribution[]>, owner: string, slotName: string, contribution: SlotContribution): Record<string, OwnedSlotContribution[]>;
13
+ //# sourceMappingURL=slot-owner-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-owner-registry.d.ts","sourceRoot":"","sources":["../../src/studio/slot-owner-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,sGAAsG;AACtG,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,EAC9C,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAOzC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,EAC9C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,gBAAgB,GAC7B,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAMzC"}
@@ -0,0 +1,21 @@
1
+ /** Remove every contribution registered by `owner` across all slot names. */
2
+ export function removeOwnerFromSlots(slots, owner) {
3
+ const out = {};
4
+ for (const [name, list] of Object.entries(slots)) {
5
+ const kept = list.filter((c) => c.owner !== owner);
6
+ if (kept.length > 0)
7
+ out[name] = kept;
8
+ }
9
+ return out;
10
+ }
11
+ /**
12
+ * Register (or replace) a single owner's contribution on one slot.
13
+ * Prior entries from the same owner on that slot are dropped first.
14
+ */
15
+ export function registerOwnedOnSlot(slots, owner, slotName, contribution) {
16
+ const out = { ...slots };
17
+ const prev = out[slotName] ?? [];
18
+ const kept = prev.filter((c) => c.owner !== owner);
19
+ out[slotName] = [...kept, { ...contribution, owner }];
20
+ return out;
21
+ }
package/dist/svelte.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * ```svelte
7
7
  * <script lang="ts">
8
8
  * import { SyncManager } from '@zveltio/sdk';
9
- * import { useSyncCollection, useSyncStatus } from '@zveltio/sdk/svelte';
9
+ * import { useSyncCollection, useSyncStatus } from '@zveltio/sdk';
10
10
  *
11
11
  * let todos = $state<any[]>([]);
12
12
  * const unsub = useSyncCollection(sync, 'todos', (records) => { todos = records; });
@@ -1 +1 @@
1
- {"version":3,"file":"svelte.d.ts","sourceRoot":"","sources":["../src/svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,IAAI,GAC/B,MAAM,IAAI,CAGZ;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,EACnF,UAAU,SAAO,GAChB,MAAM,IAAI,CAeZ"}
1
+ {"version":3,"file":"svelte.d.ts","sourceRoot":"","sources":["../src/svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAElB,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,IAAI,GAC/B,MAAM,IAAI,CAGZ;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,EACnF,UAAU,SAAO,GAChB,MAAM,IAAI,CAeZ"}
package/dist/svelte.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * ```svelte
7
7
  * <script lang="ts">
8
8
  * import { SyncManager } from '@zveltio/sdk';
9
- * import { useSyncCollection, useSyncStatus } from '@zveltio/sdk/svelte';
9
+ * import { useSyncCollection, useSyncStatus } from '@zveltio/sdk';
10
10
  *
11
11
  * let todos = $state<any[]>([]);
12
12
  * const unsub = useSyncCollection(sync, 'todos', (records) => { todos = records; });
@@ -23,7 +23,9 @@
23
23
  * Calls `setter` immediately with current state and on each update.
24
24
  * Returns the unsubscribe function.
25
25
  */
26
- export function useSyncCollection(sync, collection, setter) {
26
+ export function useSyncCollection(sync, collection,
27
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
28
+ setter) {
27
29
  const col = sync.collection(collection);
28
30
  return col.subscribe(setter);
29
31
  }
@@ -20,6 +20,10 @@ export declare class SyncManager {
20
20
  private syncTimer;
21
21
  private isOnline;
22
22
  private isSyncing;
23
+ /** Set by `stop()`; `start()` checks it after every await it makes. */
24
+ private stopped;
25
+ private readonly onOnline;
26
+ private readonly onOffline;
23
27
  private listeners;
24
28
  constructor(client: ZveltioClient, config?: SyncManagerConfig);
25
29
  start(realtimeUrl?: string): Promise<void>;
@@ -32,22 +36,22 @@ export declare class SyncManager {
32
36
  collection(name: string): {
33
37
  /** List records — reads LOCAL instantly */
34
38
  list: () => Promise<{
35
- _syncStatus: "synced" | "pending" | "conflict";
39
+ _syncStatus: "conflict" | "synced" | "pending";
36
40
  id: string;
37
41
  }[]>;
38
42
  /** Get one record — reads LOCAL instantly */
39
43
  get: (id: string) => Promise<{
40
- _syncStatus: "synced" | "pending" | "conflict";
44
+ _syncStatus: "conflict" | "synced" | "pending";
41
45
  id: string;
42
46
  } | null>;
43
47
  /** Create — writes LOCAL + queues sync */
44
48
  create: (data: Record<string, any>) => Promise<{
45
- _syncStatus: "synced" | "pending" | "conflict";
49
+ _syncStatus: "conflict" | "synced" | "pending";
46
50
  id: string;
47
51
  }>;
48
52
  /** Update — writes LOCAL + queues sync */
49
53
  update: (id: string, data: Record<string, any>) => Promise<{
50
- _syncStatus: "synced" | "pending" | "conflict";
54
+ _syncStatus: "conflict" | "synced" | "pending";
51
55
  id: string;
52
56
  }>;
53
57
  /** Delete — soft delete LOCAL + queues sync */
@@ -62,6 +66,28 @@ export declare class SyncManager {
62
66
  /** Force sync now (non-blocking) */
63
67
  syncNow(): Promise<void>;
64
68
  private notifyListeners;
69
+ /**
70
+ * Open the local store without starting background sync.
71
+ *
72
+ * `start()` opens the store, attaches online/offline listeners, connects
73
+ * realtime and installs a periodic timer. A caller driving `pull()` and
74
+ * `push()` explicitly — the `OfflineProvider` contract in `offline/` — wants
75
+ * the store and none of the rest, and a timer firing `syncNow()` underneath it
76
+ * makes "how many operations did that push send" unanswerable.
77
+ */
78
+ open(): Promise<void>;
79
+ /**
80
+ * Pull rows for the named collections from the server into the local store.
81
+ *
82
+ * There was no way to do this. Records reached the local store only through
83
+ * realtime events, one at a time, which means a client that was offline while
84
+ * a row was created never learns it exists: it missed the event and nothing
85
+ * ever asks the server what it has. `syncNow()` is push-only despite the name.
86
+ *
87
+ * Pages through `list()` rather than asking for everything at once, and stops
88
+ * on the first page that comes back empty or short.
89
+ */
90
+ pull(collections: string[], pageSize?: number): Promise<number>;
65
91
  /** Status: pending operations count, conflicts count */
66
92
  getStatus(): Promise<{
67
93
  pending: number;
@@ -1 +1 @@
1
- {"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../src/sync-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,GAAG,CAAC;IAC9C,mHAAmH;IACnH,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;CAC7C;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,SAAS,CAA+C;IAChE,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,SAAS,CAAyD;gBAE9D,MAAM,EAAE,aAAa,EAAE,MAAM,GAAE,iBAAsB;IAY3D,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BhD;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM;QAEnB,2CAA2C;;;;;QAU3C,6CAA6C;kBAC7B,MAAM;;;;QAUtB,0CAA0C;uBACrB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;;;QAYxC,0CAA0C;qBACvB,MAAM,QAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;;;QAapD,+CAA+C;qBAC5B,MAAM;QAMzB,qDAAqD;8BAC/B,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,IAAI;QA0C9C,8CAA8C;;QAK9C,kCAAkC;8BACN,MAAM,gBAAgB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;IAQzE,oCAAoC;IAC9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YA8FhB,eAAe;IAmB7B,wDAAwD;IAClD,SAAS,IAAI,OAAO,CAAC;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;IAUI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAK5B"}
1
+ {"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../src/sync-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IAEpD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,GAAG,CAAC;IAC9C,mHAAmH;IACnH,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;CAC7C;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,SAAS,CAA+C;IAChE,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,SAAS,CAAkB;IACnC,uEAAuE;IACvE,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAGvB;IACF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAExB;IAEF,OAAO,CAAC,SAAS,CAAyD;gBAE9D,MAAM,EAAE,aAAa,EAAE,MAAM,GAAE,iBAAsB;IAY3D,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BhD;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM;QAEnB,2CAA2C;;;;;QAU3C,6CAA6C;kBAC7B,MAAM;;;;QAUtB,0CAA0C;uBAErB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;;;QAYxC,0CAA0C;qBAEvB,MAAM,QAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;;;QAapD,+CAA+C;qBAC5B,MAAM;QAMzB,qDAAqD;8BAE/B,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,IAAI;QA8C9C,8CAA8C;;QAK9C,kCAAkC;8BAEN,MAAM,gBAAgB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;IAQzE,oCAAoC;IAC9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAwGhB,eAAe;IAmB7B;;;;;;;;OAQG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;;;;;;;;;OAUG;IACG,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,SAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBlE,wDAAwD;IAClD,SAAS,IAAI,OAAO,CAAC;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;IAUI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAY5B"}
@@ -9,6 +9,16 @@ export class SyncManager {
9
9
  syncTimer = null;
10
10
  isOnline = true;
11
11
  isSyncing = false;
12
+ /** Set by `stop()`; `start()` checks it after every await it makes. */
13
+ stopped = false;
14
+ onOnline = () => {
15
+ this.isOnline = true;
16
+ this.syncNow(); // Sync immediately on reconnect
17
+ };
18
+ onOffline = () => {
19
+ this.isOnline = false;
20
+ };
21
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
12
22
  listeners = new Map();
13
23
  constructor(client, config = {}) {
14
24
  this.store = new LocalStore();
@@ -22,21 +32,24 @@ export class SyncManager {
22
32
  };
23
33
  }
24
34
  async start(realtimeUrl) {
35
+ this.stopped = false;
25
36
  await this.store.open();
37
+ // A caller that unmounts before `start()` settles calls `stop()` first — a
38
+ // React effect cleanup, and StrictMode runs one on every mount in
39
+ // development. Arming the timer and socket after that leaked both forever.
40
+ if (this.stopped)
41
+ return this.store.close();
26
42
  // Online/offline detection
27
43
  if (typeof window !== 'undefined') {
28
- window.addEventListener('online', () => {
29
- this.isOnline = true;
30
- this.syncNow(); // Sync immediately on reconnect
31
- });
32
- window.addEventListener('offline', () => {
33
- this.isOnline = false;
34
- });
44
+ window.addEventListener('online', this.onOnline);
45
+ window.addEventListener('offline', this.onOffline);
35
46
  this.isOnline = navigator.onLine;
36
47
  }
37
48
  // Realtime: receives push updates from server
38
49
  if (realtimeUrl) {
39
50
  const { ZveltioRealtime } = await import('./realtime.js');
51
+ if (this.stopped)
52
+ return;
40
53
  this.realtime = new ZveltioRealtime(realtimeUrl);
41
54
  this.realtime.connect();
42
55
  // Subscriptions are per-collection via collection()
@@ -75,6 +88,7 @@ export class SyncManager {
75
88
  };
76
89
  },
77
90
  /** Create — writes LOCAL + queues sync */
91
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
78
92
  create: async (data) => {
79
93
  const id = data.id || generateUUID();
80
94
  const record = await this.store.put(name, id, data);
@@ -87,6 +101,7 @@ export class SyncManager {
87
101
  };
88
102
  },
89
103
  /** Update — writes LOCAL + queues sync */
104
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
90
105
  update: async (id, data) => {
91
106
  const existing = await this.store.get(name, id);
92
107
  const merged = { ...(existing?.data || {}), ...data };
@@ -106,6 +121,7 @@ export class SyncManager {
106
121
  this.syncNow();
107
122
  },
108
123
  /** Subscribe la changes (realtime + local writes) */
124
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
109
125
  subscribe: (callback) => {
110
126
  if (!this.listeners.has(name))
111
127
  this.listeners.set(name, new Set());
@@ -114,19 +130,24 @@ export class SyncManager {
114
130
  let unsubRealtime;
115
131
  if (this.realtime) {
116
132
  unsubRealtime = this.realtime.subscribe(name, async (event) => {
117
- // Apply update from server to local store
118
- if (event.event === 'record.created' || event.event === 'record.updated') {
133
+ // Apply update from server to local store. The engine's socket
134
+ // sends `{ type: 'event', event: 'insert'|'update'|'delete', data }`
135
+ // (routes/ws.ts `broadcastEvent`); the id lives on `data`.
136
+ const id = event.data?.id;
137
+ if (event.type !== 'event' || !id)
138
+ return;
139
+ if (event.event === 'insert' || event.event === 'update') {
119
140
  try {
120
- const serverRecord = await this.client.collection(name).get(event.record_id);
121
- await this.store.applyServerUpdate(name, event.record_id, serverRecord, Date.now());
141
+ const serverRecord = await this.client.collection(name).get(id);
142
+ await this.store.applyServerUpdate(name, id, serverRecord, Date.now());
122
143
  this.notifyListeners(name);
123
144
  }
124
145
  catch {
125
146
  /* offline or error — ignore, periodic sync will resolve */
126
147
  }
127
148
  }
128
- else if (event.event === 'record.deleted') {
129
- await this.store.delete(name, event.record_id);
149
+ else if (event.event === 'delete') {
150
+ await this.store.applyServerDelete(name, id);
130
151
  this.notifyListeners(name);
131
152
  }
132
153
  });
@@ -150,6 +171,7 @@ export class SyncManager {
150
171
  return this.store.getConflicts(name);
151
172
  },
152
173
  /** Resolve a conflict manually */
174
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
153
175
  resolveConflict: async (id, resolvedData) => {
154
176
  await this.store.resolveConflict(name, id, resolvedData);
155
177
  this.notifyListeners(name);
@@ -170,6 +192,7 @@ export class SyncManager {
170
192
  const file = new File([blobItem.blob], `offline_${blobItem.id}`, {
171
193
  type: blobItem.blob.type,
172
194
  });
195
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
173
196
  const result = (await this.client.storage.upload(file));
174
197
  const url = result?.url || result?.publicUrl || result?.path || '';
175
198
  if (!url)
@@ -214,6 +237,7 @@ export class SyncManager {
214
237
  }
215
238
  await this.store.markSynced(op.id, op.collection, op.recordId, serverVersion);
216
239
  this.notifyListeners(op.collection);
240
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
217
241
  }
218
242
  catch (err) {
219
243
  // Conflict from server (409) — apply conflict resolution
@@ -224,15 +248,23 @@ export class SyncManager {
224
248
  // CRDT LWW merge if both sides have CRDT docs and strategy is 'lww'
225
249
  if (this.config.conflictResolution === 'lww' &&
226
250
  localRecord?._crdtDoc &&
251
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
227
252
  serverRecord.__crdt) {
253
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
228
254
  const merged = mergeLWW(localRecord._crdtDoc, serverRecord.__crdt);
229
- const resolvedData = fromDocument(merged);
230
- await this.store.resolveConflict(op.collection, op.recordId, resolvedData);
255
+ await this.store.resolveConflict(op.collection, op.recordId, fromDocument(merged));
231
256
  }
232
257
  else {
233
258
  const resolved = this.config.onConflict(localRecord?.data, serverRecord);
234
259
  await this.store.resolveConflict(op.collection, op.recordId, resolved);
235
260
  }
261
+ // The operation the server just rejected stayed in the queue with
262
+ // its original payload and its attempt count untouched, so the next
263
+ // tick sent the same payload, took the same 409 and resolved the
264
+ // same conflict again — every `syncInterval`, with no end. Count
265
+ // the attempt so `maxRetries` retires it; the resolved row is
266
+ // queued by `resolveConflict` as its own operation.
267
+ await this.store.markFailed(op.id, 'rejected with 409 — resolved locally');
236
268
  }
237
269
  catch {
238
270
  /* fallback: server wins — ignore error */
@@ -268,6 +300,53 @@ export class SyncManager {
268
300
  }
269
301
  });
270
302
  }
303
+ /**
304
+ * Open the local store without starting background sync.
305
+ *
306
+ * `start()` opens the store, attaches online/offline listeners, connects
307
+ * realtime and installs a periodic timer. A caller driving `pull()` and
308
+ * `push()` explicitly — the `OfflineProvider` contract in `offline/` — wants
309
+ * the store and none of the rest, and a timer firing `syncNow()` underneath it
310
+ * makes "how many operations did that push send" unanswerable.
311
+ */
312
+ async open() {
313
+ await this.store.open();
314
+ }
315
+ /**
316
+ * Pull rows for the named collections from the server into the local store.
317
+ *
318
+ * There was no way to do this. Records reached the local store only through
319
+ * realtime events, one at a time, which means a client that was offline while
320
+ * a row was created never learns it exists: it missed the event and nothing
321
+ * ever asks the server what it has. `syncNow()` is push-only despite the name.
322
+ *
323
+ * Pages through `list()` rather than asking for everything at once, and stops
324
+ * on the first page that comes back empty or short.
325
+ */
326
+ async pull(collections, pageSize = 200) {
327
+ let applied = 0;
328
+ for (const name of collections) {
329
+ for (let page = 1;; page++) {
330
+ const result = await this.client.collection(name).list({ page, limit: pageSize });
331
+ const rows = result?.data ?? [];
332
+ for (const row of rows) {
333
+ const id = row.id;
334
+ if (!id)
335
+ continue;
336
+ // `Date.now()` as the server version: the collections API does not
337
+ // return a version column, and the local store only compares versions
338
+ // to decide whether an incoming row is newer than one it already has.
339
+ // A server row arriving now IS newer than anything already local.
340
+ await this.store.applyServerUpdate(name, id, row, Date.now());
341
+ applied++;
342
+ }
343
+ if (rows.length < pageSize)
344
+ break;
345
+ }
346
+ await this.notifyListeners(name);
347
+ }
348
+ return applied;
349
+ }
271
350
  /** Status: pending operations count, conflicts count */
272
351
  async getStatus() {
273
352
  const pending = await this.store.getPendingOps();
@@ -279,9 +358,16 @@ export class SyncManager {
279
358
  };
280
359
  }
281
360
  async stop() {
361
+ this.stopped = true;
282
362
  if (this.syncTimer)
283
363
  clearInterval(this.syncTimer);
364
+ this.syncTimer = null;
365
+ if (typeof window !== 'undefined') {
366
+ window.removeEventListener('online', this.onOnline);
367
+ window.removeEventListener('offline', this.onOffline);
368
+ }
284
369
  this.realtime?.disconnect();
370
+ this.realtime = null;
285
371
  await this.store.close();
286
372
  }
287
373
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"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"}
@@ -24,10 +24,13 @@ import { Hono } from 'hono';
24
24
  * `.executeTakeFirstOrThrow()` returns a preset if matched, otherwise
25
25
  * an empty list (for execute) or undefined.
26
26
  */
27
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
27
28
  export function mockDb(presets = {}) {
28
29
  const calls = [];
29
30
  const presetMap = new Map(Object.entries(presets));
31
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
30
32
  function buildChain(prefix) {
33
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
31
34
  const proxy = new Proxy(() => { }, {
32
35
  get(_, prop) {
33
36
  if (typeof prop === 'symbol')
@@ -78,6 +81,7 @@ export function mockDb(presets = {}) {
78
81
  });
79
82
  return proxy;
80
83
  }
84
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
81
85
  return buildChain([]);
82
86
  }
83
87
  function matchPreset(chain, presets) {
@@ -93,7 +97,9 @@ function matchPreset(chain, presets) {
93
97
  }
94
98
  export function mockEventBus() {
95
99
  const emitted = [];
100
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
96
101
  const listeners = new Map();
102
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
97
103
  const beforeHandlers = new Map();
98
104
  return {
99
105
  emit(event, payload) {
@@ -128,12 +134,14 @@ export function mockEventBus() {
128
134
  };
129
135
  },
130
136
  async runBefore(event, seed) {
137
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
131
138
  const payload = { ...seed };
132
139
  let aborted = false;
133
140
  let abortReason = '';
134
141
  payload.abort = (reason) => {
135
142
  aborted = true;
136
143
  abortReason = reason;
144
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
137
145
  const err = new Error(`Aborted: ${reason}`);
138
146
  err.name = 'AbortHookError';
139
147
  err.reason = reason;
@@ -187,8 +195,10 @@ export function mockServiceRegistry() {
187
195
  }
188
196
  // ─── Mock queryAlter / entityAccess scoped registries ──────────────────────
189
197
  function mockQueryAlterScope() {
198
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
190
199
  const entries = [];
191
200
  return {
201
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
192
202
  register(def) {
193
203
  entries.push(def);
194
204
  },
@@ -201,8 +211,10 @@ function mockQueryAlterScope() {
201
211
  };
202
212
  }
203
213
  function mockEntityAccessScope() {
214
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
204
215
  const entries = [];
205
216
  return {
217
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
206
218
  register(def) {
207
219
  entries.push(def);
208
220
  },
@@ -214,6 +226,7 @@ function mockEntityAccessScope() {
214
226
  },
215
227
  };
216
228
  }
229
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
217
230
  export function mockAuth(opts = {}) {
218
231
  const user = opts.user === undefined ? { id: 'test-user', email: 'test@example.com', roles: [] } : opts.user;
219
232
  return {
@@ -224,6 +237,7 @@ export function mockAuth(opts = {}) {
224
237
  },
225
238
  };
226
239
  }
240
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
227
241
  export function createTestContext(opts = {}) {
228
242
  const db = opts.db ?? mockDb();
229
243
  const events = opts.events ?? mockEventBus();
@@ -239,14 +253,20 @@ export function createTestContext(opts = {}) {
239
253
  deserialize: (_t, v) => v,
240
254
  serialize: (_t, v) => v,
241
255
  },
256
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
242
257
  events: events,
243
258
  checkPermission: async () => true,
244
259
  getUserRoles: async () => ['admin'],
260
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
245
261
  DDLManager: {},
262
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
246
263
  services: mockServiceRegistry(),
264
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
247
265
  queryAlter: mockQueryAlterScope(),
266
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
248
267
  entityAccess: mockEntityAccessScope(),
249
268
  registerPublicRoute: () => { },
269
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
250
270
  internals: {},
251
271
  ...(opts.extra ?? {}),
252
272
  };
@@ -259,7 +279,9 @@ export function createTestContext(opts = {}) {
259
279
  * mounts the sub-app at `/ext/<extension.name>` by default — same shape
260
280
  * the engine produces in production.
261
281
  */
262
- export async function createTestApp(extension, opts = {}) {
282
+ export async function createTestApp(
283
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
284
+ extension, opts = {}) {
263
285
  const ctx = opts.ctx ?? createTestContext();
264
286
  const strategy = extension.mountStrategy ?? 'global';
265
287
  if (strategy === 'subapp') {
@@ -1 +1 @@
1
- {"version":3,"file":"with-test-db.d.ts","sourceRoot":"","sources":["../../src/testing/with-test-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,MAAM;IACrB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAChB,+CAA+C;IAC/C,gBAAgB,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AA6BD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4E/E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,iBAAiB,EAC/D,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC,CAYZ;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhG;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzF;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBtD;AAgBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAuErD;AAGD,eAAO,MAAM,iBAAiB;;CAAsB,CAAC"}
1
+ {"version":3,"file":"with-test-db.d.ts","sourceRoot":"","sources":["../../src/testing/with-test-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGrC,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,MAAM;IACrB,8DAA8D;IAE9D,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAChB,+CAA+C;IAC/C,gBAAgB,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AA4BD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8E/E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAEhC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,iBAAiB,EAE/D,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC,CAYZ;AAED;;;;;;;;;;;GAWG;AAGH,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBhG;AAED;;;GAGG;AAGH,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzF;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAStD;AAkBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAuErD;AAGD,eAAO,MAAM,iBAAiB;;CAAsB,CAAC"}