@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.
- package/dist/client/Auth.d.ts.map +1 -1
- package/dist/client/Auth.js +3 -0
- package/dist/client/QueryBuilder.d.ts.map +1 -1
- package/dist/client/QueryBuilder.js +4 -0
- package/dist/client/RealtimeClient.d.ts +19 -1
- package/dist/client/RealtimeClient.d.ts.map +1 -1
- package/dist/client/RealtimeClient.js +65 -12
- package/dist/client/ZveltioClient.d.ts.map +1 -1
- package/dist/client/ZveltioClient.js +12 -1
- package/dist/client.d.ts +28 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +41 -14
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +21 -3
- package/dist/crdt.d.ts.map +1 -1
- package/dist/crdt.js +11 -2
- package/dist/ddl/index.d.ts.map +1 -1
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +124 -0
- package/dist/extension/index.d.ts +459 -29
- package/dist/extension/index.d.ts.map +1 -1
- package/dist/extension/index.js +3 -0
- package/dist/extension/jsonb.d.ts +62 -0
- package/dist/extension/jsonb.d.ts.map +1 -0
- package/dist/extension/jsonb.js +64 -0
- package/dist/extension/multipart.d.ts +58 -0
- package/dist/extension/multipart.d.ts.map +1 -0
- package/dist/extension/multipart.js +61 -0
- package/dist/extension/numeric.d.ts +94 -0
- package/dist/extension/numeric.d.ts.map +1 -0
- package/dist/extension/numeric.js +162 -0
- package/dist/extension/permission-gate.d.ts +15 -0
- package/dist/extension/permission-gate.d.ts.map +1 -1
- package/dist/extension/permission-gate.js +33 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/local-store.d.ts +5 -0
- package/dist/local-store.d.ts.map +1 -1
- package/dist/local-store.js +62 -10
- package/dist/offline/index.d.ts +3 -3
- package/dist/offline/index.js +51 -15
- package/dist/publish/index.d.ts.map +1 -1
- package/dist/publish/index.js +6 -1
- package/dist/realtime.d.ts +18 -1
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +34 -3
- package/dist/rpc/index.d.ts.map +1 -1
- package/dist/rpc/index.js +2 -0
- package/dist/schema-codegen.d.ts +3 -1
- package/dist/schema-codegen.d.ts.map +1 -1
- package/dist/schema-codegen.js +41 -7
- package/dist/schema-watcher.d.ts +10 -2
- package/dist/schema-watcher.d.ts.map +1 -1
- package/dist/schema-watcher.js +30 -10
- package/dist/studio/index.d.ts +1 -0
- package/dist/studio/index.d.ts.map +1 -1
- package/dist/studio/index.js +3 -0
- package/dist/studio/slot-owner-registry.d.ts +13 -0
- package/dist/studio/slot-owner-registry.d.ts.map +1 -0
- package/dist/studio/slot-owner-registry.js +21 -0
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.d.ts.map +1 -1
- package/dist/svelte.js +4 -2
- package/dist/sync-manager.d.ts +30 -4
- package/dist/sync-manager.d.ts.map +1 -1
- package/dist/sync-manager.js +101 -15
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +23 -1
- package/dist/testing/with-test-db.d.ts.map +1 -1
- package/dist/testing/with-test-db.js +21 -14
- package/dist/tests/client.test.d.ts +2 -0
- package/dist/tests/client.test.d.ts.map +1 -0
- package/dist/tests/client.test.js +61 -0
- package/dist/tests/crdt-orset.test.d.ts +2 -0
- package/dist/tests/crdt-orset.test.d.ts.map +1 -0
- package/dist/tests/crdt-orset.test.js +23 -0
- package/dist/tests/local-store.test.js +44 -0
- package/dist/tests/multipart.test.d.ts +2 -0
- package/dist/tests/multipart.test.d.ts.map +1 -0
- package/dist/tests/multipart.test.js +37 -0
- package/dist/tests/offline-provider-crdt.test.d.ts +2 -0
- package/dist/tests/offline-provider-crdt.test.d.ts.map +1 -0
- package/dist/tests/offline-provider-crdt.test.js +119 -0
- package/dist/tests/realtime.test.d.ts +2 -0
- package/dist/tests/realtime.test.d.ts.map +1 -0
- package/dist/tests/realtime.test.js +284 -0
- package/dist/tests/sync-manager.test.d.ts +2 -0
- package/dist/tests/sync-manager.test.d.ts.map +1 -0
- package/dist/tests/sync-manager.test.js +65 -0
- package/dist/tests/with-test-db.test.d.ts +2 -0
- package/dist/tests/with-test-db.test.d.ts.map +1 -0
- package/dist/tests/with-test-db.test.js +57 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/validate/index.d.ts +49 -0
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js +503 -7
- package/dist/validate/shared-message-keys.d.ts +16 -0
- package/dist/validate/shared-message-keys.d.ts.map +1 -0
- package/dist/validate/shared-message-keys.js +227 -0
- package/package.json +7 -7
- package/dist/generate-types.d.ts +0 -37
- package/dist/generate-types.d.ts.map +0 -1
- package/dist/generate-types.js +0 -157
package/dist/studio/index.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/studio/index.js
CHANGED
|
@@ -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
|
|
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; });
|
package/dist/svelte.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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,
|
|
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
|
}
|
package/dist/sync-manager.d.ts
CHANGED
|
@@ -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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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;
|
|
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"}
|
package/dist/sync-manager.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
121
|
-
await this.store.applyServerUpdate(name,
|
|
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 === '
|
|
129
|
-
await this.store.
|
|
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
|
-
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIhF,MAAM,WAAW,QAAQ;IACvB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;OAUG;IACH,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAC3B,yCAAyC;IACzC,KAAK,IAAI,IAAI,CAAC;IACd,mDAAmD;IACnD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7C;AAED;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/index.js
CHANGED
|
@@ -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(
|
|
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;
|
|
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"}
|