@carlonicora/nextjs-jsonapi 6.12.2 → 6.13.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.
- package/dist/{ApiData-DPKNfY-9.d.mts → ApiData-Be8yxCOX.d.mts} +2 -0
- package/dist/{ApiData-DPKNfY-9.d.ts → ApiData-Be8yxCOX.d.ts} +2 -0
- package/dist/{BlockNoteEditor-7M5QW2YQ.js → BlockNoteEditor-UBVEUFNR.js} +34 -22
- package/dist/BlockNoteEditor-UBVEUFNR.js.map +1 -0
- package/dist/{BlockNoteEditor-75ED2CN2.mjs → BlockNoteEditor-VXMDSQAA.mjs} +20 -8
- package/dist/BlockNoteEditor-VXMDSQAA.mjs.map +1 -0
- package/dist/{BlockNoteViewer-WGCDB7IJ.js → BlockNoteViewer-2XYASA5Y.js} +8 -5
- package/dist/BlockNoteViewer-2XYASA5Y.js.map +1 -0
- package/dist/{BlockNoteViewer-MTGS6YJV.mjs → BlockNoteViewer-NBW2H6LC.mjs} +7 -4
- package/dist/{BlockNoteViewer-MTGS6YJV.mjs.map → BlockNoteViewer-NBW2H6LC.mjs.map} +1 -1
- package/dist/{JsonApiRequest-A66XK5WT.mjs → JsonApiRequest-F6TMJ42G.mjs} +2 -2
- package/dist/JsonApiRequest-WISJZSDE.js +26 -0
- package/dist/{JsonApiRequest-FZGPLNUF.js.map → JsonApiRequest-WISJZSDE.js.map} +1 -1
- package/dist/billing/index.js +499 -494
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +13 -8
- package/dist/billing/index.mjs.map +1 -1
- package/dist/chunk-27LQDGNP.mjs +317 -0
- package/dist/chunk-27LQDGNP.mjs.map +1 -0
- package/dist/{chunk-KSYQEKJX.js → chunk-2SV4SS6N.js} +14 -14
- package/dist/{chunk-KSYQEKJX.js.map → chunk-2SV4SS6N.js.map} +1 -1
- package/dist/{chunk-4FNGWALA.mjs → chunk-47BBY5KA.mjs} +6 -4
- package/dist/{chunk-4FNGWALA.mjs.map → chunk-47BBY5KA.mjs.map} +1 -1
- package/dist/chunk-6JUCIWDC.mjs +570 -0
- package/dist/chunk-6JUCIWDC.mjs.map +1 -0
- package/dist/chunk-7DBJTNSU.mjs +1049 -0
- package/dist/chunk-7DBJTNSU.mjs.map +1 -0
- package/dist/chunk-7JZHPMGW.mjs +704 -0
- package/dist/chunk-7JZHPMGW.mjs.map +1 -0
- package/dist/{chunk-L6PYFQTO.js → chunk-7ZHTPFFT.js} +16 -14
- package/dist/chunk-7ZHTPFFT.js.map +1 -0
- package/dist/chunk-B6JGKDP2.js +214 -0
- package/dist/chunk-B6JGKDP2.js.map +1 -0
- package/dist/{chunk-3YW35YHQ.mjs → chunk-BH6JWIKI.mjs} +5 -5
- package/dist/chunk-BZ5J2FIV.js +570 -0
- package/dist/chunk-BZ5J2FIV.js.map +1 -0
- package/dist/{chunk-V5MI7RNB.mjs → chunk-DGWOVYEC.mjs} +2 -2
- package/dist/chunk-DOCIFE6A.mjs +212 -0
- package/dist/chunk-DOCIFE6A.mjs.map +1 -0
- package/dist/chunk-GB6DBFYY.js +25 -0
- package/dist/chunk-GB6DBFYY.js.map +1 -0
- package/dist/{chunk-EWCY6QJA.mjs → chunk-JJMMT24D.mjs} +37 -3
- package/dist/chunk-JJMMT24D.mjs.map +1 -0
- package/dist/{chunk-JTPRJNT2.js → chunk-KMW7UNL2.js} +38 -3
- package/dist/chunk-KMW7UNL2.js.map +1 -0
- package/dist/chunk-MHB36MYE.js +317 -0
- package/dist/chunk-MHB36MYE.js.map +1 -0
- package/dist/{chunk-FF4EIQ6H.js → chunk-MOGEGWXW.js} +1039 -1680
- package/dist/chunk-MOGEGWXW.js.map +1 -0
- package/dist/{chunk-RXQYEEKZ.mjs → chunk-N4RZZKL5.mjs} +58 -25
- package/dist/chunk-N4RZZKL5.mjs.map +1 -0
- package/dist/{chunk-AA52ORW2.mjs → chunk-P445QGOC.mjs} +61 -702
- package/dist/chunk-P445QGOC.mjs.map +1 -0
- package/dist/chunk-PV4Z5W6V.js +212 -0
- package/dist/chunk-PV4Z5W6V.js.map +1 -0
- package/dist/chunk-PXDUJ47N.js +1049 -0
- package/dist/chunk-PXDUJ47N.js.map +1 -0
- package/dist/{chunk-V3YDVJ63.js → chunk-PYRZCKC2.js} +1209 -1176
- package/dist/chunk-PYRZCKC2.js.map +1 -0
- package/dist/{chunk-GHE4VT2R.js → chunk-QKNGZDSQ.js} +2 -2
- package/dist/{chunk-GHE4VT2R.js.map → chunk-QKNGZDSQ.js.map} +1 -1
- package/dist/chunk-W62MHDWM.js +704 -0
- package/dist/chunk-W62MHDWM.js.map +1 -0
- package/dist/chunk-XXP3TQHF.mjs +25 -0
- package/dist/chunk-XXP3TQHF.mjs.map +1 -0
- package/dist/chunk-YRKIFYOE.mjs +214 -0
- package/dist/chunk-YRKIFYOE.mjs.map +1 -0
- package/dist/{chunk-O3PDL2CV.js → chunk-Z4POQWFG.js} +40 -6
- package/dist/chunk-Z4POQWFG.js.map +1 -0
- package/dist/{chunk-PI56A4DG.mjs → chunk-ZLTOUZN2.mjs} +37 -2
- package/dist/chunk-ZLTOUZN2.mjs.map +1 -0
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +14 -9
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +24 -19
- package/dist/components/index.d.mts +7 -1
- package/dist/components/index.d.ts +7 -1
- package/dist/components/index.js +11 -7
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +10 -6
- package/dist/contexts/index.d.mts +1 -1
- package/dist/contexts/index.d.ts +1 -1
- package/dist/contexts/index.js +11 -7
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +10 -6
- package/dist/core/index.d.mts +128 -2
- package/dist/core/index.d.ts +128 -2
- package/dist/core/index.js +17 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +22 -9
- package/dist/email-automations/index.js +101 -96
- package/dist/email-automations/index.js.map +1 -1
- package/dist/email-automations/index.mjs +13 -8
- package/dist/email-automations/index.mjs.map +1 -1
- package/dist/features/analytics/index.js +128 -123
- package/dist/features/analytics/index.js.map +1 -1
- package/dist/features/analytics/index.mjs +13 -8
- package/dist/features/analytics/index.mjs.map +1 -1
- package/dist/features/analytics/server.js +11 -8
- package/dist/features/analytics/server.js.map +1 -1
- package/dist/features/analytics/server.mjs +9 -6
- package/dist/features/analytics/server.mjs.map +1 -1
- package/dist/features/blog/index.js +158 -153
- package/dist/features/blog/index.js.map +1 -1
- package/dist/features/blog/index.mjs +14 -9
- package/dist/features/blog/index.mjs.map +1 -1
- package/dist/features/blog/server.js +30 -26
- package/dist/features/blog/server.js.map +1 -1
- package/dist/features/blog/server.mjs +10 -6
- package/dist/features/blog/server.mjs.map +1 -1
- package/dist/features/help/index.js +44 -40
- package/dist/features/help/index.js.map +1 -1
- package/dist/features/help/index.mjs +10 -6
- package/dist/features/help/index.mjs.map +1 -1
- package/dist/features/tokenusage/index.js +134 -129
- package/dist/features/tokenusage/index.js.map +1 -1
- package/dist/features/tokenusage/index.mjs +13 -8
- package/dist/features/tokenusage/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -11
- package/dist/lifecycle-4GUOTB46.js +23 -0
- package/dist/lifecycle-4GUOTB46.js.map +1 -0
- package/dist/lifecycle-LKGPRBS7.mjs +23 -0
- package/dist/local-transport-7H3SPZSA.js +15 -0
- package/dist/local-transport-7H3SPZSA.js.map +1 -0
- package/dist/local-transport-QYFKA6AN.mjs +15 -0
- package/dist/local-transport-QYFKA6AN.mjs.map +1 -0
- package/dist/offline/index.d.mts +607 -0
- package/dist/offline/index.d.ts +607 -0
- package/dist/offline/index.js +1052 -0
- package/dist/offline/index.js.map +1 -0
- package/dist/offline/index.mjs +1052 -0
- package/dist/offline/index.mjs.map +1 -0
- package/dist/offline/sqlite/device-worker.mjs +62 -0
- package/dist/offline/sqlite/index.mjs +17392 -0
- package/dist/offline/sqlite/sqlite3-opfs-async-proxy.js +881 -0
- package/dist/offline/sqlite/sqlite3.wasm +0 -0
- package/dist/request-KTBV6HHH.js +10 -0
- package/dist/request-KTBV6HHH.js.map +1 -0
- package/dist/request-SQOUNWZ4.mjs +10 -0
- package/dist/request-SQOUNWZ4.mjs.map +1 -0
- package/dist/server/cachedGet.d.mts +1 -1
- package/dist/server/cachedGet.d.ts +1 -1
- package/dist/server/cachedGet.stub.d.mts +1 -1
- package/dist/server/cachedGet.stub.d.ts +1 -1
- package/dist/server/index.d.mts +28 -2
- package/dist/server/index.d.ts +28 -2
- package/dist/server/index.js +18 -8
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +18 -8
- package/dist/{useSocket-IRHtk2xc.d.ts → useSocket-Bo9M0t0J.d.ts} +3 -1
- package/dist/{useSocket-C8fqU4od.d.mts → useSocket-J5qanQPN.d.mts} +3 -1
- package/dist/write-through-FDQ7N4YI.js +15 -0
- package/dist/write-through-FDQ7N4YI.js.map +1 -0
- package/dist/write-through-R25VQMH6.mjs +15 -0
- package/dist/write-through-R25VQMH6.mjs.map +1 -0
- package/package.json +7 -1
- package/pwa/sw.js +277 -26
- package/src/client/request.ts +37 -0
- package/src/components/editors/BlockNoteEditor.tsx +14 -0
- package/src/components/editors/__tests__/BlockNoteEditor.empty-change.spec.tsx +205 -0
- package/src/contexts/SocketContext.tsx +7 -1
- package/src/contexts/__tests__/SocketContext.session.spec.tsx +52 -5
- package/src/core/factories/RehydrationFactory.ts +28 -8
- package/src/core/factories/__tests__/RehydrationFactory.spec.ts +33 -0
- package/src/core/index.ts +3 -0
- package/src/core/interfaces/ApiData.ts +2 -0
- package/src/core/registry/ModuleRegistry.ts +3 -0
- package/src/features/auth/components/forms/Logout.tsx +8 -1
- package/src/features/auth/components/forms/__tests__/Logout.spec.tsx +62 -0
- package/src/features/onboarding/contexts/OnboardingContext.spec.tsx +42 -1
- package/src/features/onboarding/contexts/OnboardingContext.tsx +11 -1
- package/src/hooks/__tests__/useSocket.pause.spec.ts +39 -0
- package/src/hooks/useSocket.ts +20 -1
- package/src/offline/__tests__/answer-builders.spec.ts +176 -0
- package/src/offline/__tests__/device-switch.spec.ts +179 -0
- package/src/offline/__tests__/generic-answers.spec.ts +380 -0
- package/src/offline/__tests__/local-transport.spec.ts +150 -0
- package/src/offline/__tests__/logout.spec.ts +122 -0
- package/src/offline/__tests__/offline-fixtures.ts +89 -0
- package/src/offline/__tests__/router.spec.ts +56 -0
- package/src/offline/__tests__/state.spec.ts +62 -0
- package/src/offline/__tests__/write-through.spec.ts +137 -0
- package/src/offline/config.ts +229 -0
- package/src/offline/configure.ts +14 -0
- package/src/offline/device-switch.ts +75 -0
- package/src/offline/generic-answers.ts +706 -0
- package/src/offline/index.ts +67 -0
- package/src/offline/local-transport.ts +189 -0
- package/src/offline/logout.ts +41 -0
- package/src/offline/router.ts +41 -0
- package/src/offline/server/__tests__/build-offline-manifest.spec.ts +76 -0
- package/src/offline/server/build-offline-manifest.ts +49 -0
- package/src/offline/state-hooks.ts +10 -0
- package/src/offline/state.ts +143 -0
- package/src/offline/store/DeviceStore.ts +112 -0
- package/src/offline/store/__tests__/device-store-lifecycle.spec.ts +343 -0
- package/src/offline/store/__tests__/shared-database.spec.ts +151 -0
- package/src/offline/store/__tests__/sql-device-store.spec.ts +399 -0
- package/src/offline/store/lifecycle.ts +315 -0
- package/src/offline/store/schema.ts +101 -0
- package/src/offline/store/shared-database.ts +321 -0
- package/src/offline/store/sql-device-store.ts +469 -0
- package/src/offline/store/worker-client.ts +71 -0
- package/src/offline/store/worker.ts +87 -0
- package/src/offline/sync/__tests__/conflicts.spec.ts +185 -0
- package/src/offline/sync/__tests__/queue-drain.spec.ts +364 -0
- package/src/offline/sync/__tests__/runtime.spec.ts +337 -0
- package/src/offline/sync/__tests__/sync-fixtures.ts +103 -0
- package/src/offline/sync/__tests__/sync-root.spec.ts +727 -0
- package/src/offline/sync/artwork-cache.ts +67 -0
- package/src/offline/sync/conflicts.ts +211 -0
- package/src/offline/sync/counts.ts +39 -0
- package/src/offline/sync/drain-queue.ts +246 -0
- package/src/offline/sync/known-roots.ts +37 -0
- package/src/offline/sync/models/OfflineChangeSet.ts +34 -0
- package/src/offline/sync/models/OfflineChangeSetInterface.ts +54 -0
- package/src/offline/sync/models/RootSync.ts +68 -0
- package/src/offline/sync/models/RootSyncInterface.ts +20 -0
- package/src/offline/sync/models/__tests__/OfflineChangeSet.spec.ts +47 -0
- package/src/offline/sync/models/__tests__/RootSync.spec.ts +54 -0
- package/src/offline/sync/models/index.ts +5 -0
- package/src/offline/sync/models/offline.modules.ts +14 -0
- package/src/offline/sync/runtime.ts +226 -0
- package/src/offline/sync/session-refresh.ts +32 -0
- package/src/offline/sync/sync-root.ts +336 -0
- package/src/offline/sync/waiting.ts +40 -0
- package/src/offline/types.ts +115 -0
- package/src/offline/write-through.ts +189 -0
- package/src/pwa/__tests__/sw.spec.ts +358 -1
- package/src/server/index.ts +5 -0
- package/src/unified/__tests__/JsonApiRequest.offline.spec.ts +243 -0
- package/dist/BlockNoteEditor-75ED2CN2.mjs.map +0 -1
- package/dist/BlockNoteEditor-7M5QW2YQ.js.map +0 -1
- package/dist/BlockNoteViewer-WGCDB7IJ.js.map +0 -1
- package/dist/JsonApiRequest-FZGPLNUF.js +0 -26
- package/dist/chunk-AA52ORW2.mjs.map +0 -1
- package/dist/chunk-EWCY6QJA.mjs.map +0 -1
- package/dist/chunk-FF4EIQ6H.js.map +0 -1
- package/dist/chunk-JTPRJNT2.js.map +0 -1
- package/dist/chunk-L6PYFQTO.js.map +0 -1
- package/dist/chunk-O3PDL2CV.js.map +0 -1
- package/dist/chunk-PI56A4DG.mjs.map +0 -1
- package/dist/chunk-RXQYEEKZ.mjs.map +0 -1
- package/dist/chunk-V3YDVJ63.js.map +0 -1
- package/dist/request-5RLK2AM3.mjs +0 -9
- package/dist/request-EUGTI3BE.js +0 -9
- package/dist/request-EUGTI3BE.js.map +0 -1
- /package/dist/{JsonApiRequest-A66XK5WT.mjs.map → JsonApiRequest-F6TMJ42G.mjs.map} +0 -0
- /package/dist/{chunk-3YW35YHQ.mjs.map → chunk-BH6JWIKI.mjs.map} +0 -0
- /package/dist/{chunk-V5MI7RNB.mjs.map → chunk-DGWOVYEC.mjs.map} +0 -0
- /package/dist/{request-5RLK2AM3.mjs.map → lifecycle-LKGPRBS7.mjs.map} +0 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
type GetterKeys<T> = {
|
|
2
|
+
[K in keyof T]: T[K] extends () => any ? never : K;
|
|
3
|
+
}[keyof T];
|
|
4
|
+
type FieldSelector<T> = {
|
|
5
|
+
type: string;
|
|
6
|
+
fields: ReadonlyArray<GetterKeys<T>>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type ApiRequestDataTypeInterface = {
|
|
10
|
+
name: string;
|
|
11
|
+
cache?: string;
|
|
12
|
+
inclusions?: Record<string, {
|
|
13
|
+
types?: string[];
|
|
14
|
+
fields?: FieldSelector<any>[];
|
|
15
|
+
include?: string[];
|
|
16
|
+
}>;
|
|
17
|
+
model: new () => any;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type OfflineChangeResolution = {
|
|
21
|
+
kind: "mine" | "merged";
|
|
22
|
+
/** Per conflicting field: whose value wins. */
|
|
23
|
+
choices?: Record<string, "mine" | "theirs" | "merged">;
|
|
24
|
+
/** The model-built JSON:API document holding only the fields chosen "merged", with their hand-merged values. */
|
|
25
|
+
mergedBody?: Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type StoredRecord = {
|
|
29
|
+
type: string;
|
|
30
|
+
id: string;
|
|
31
|
+
rootId: string;
|
|
32
|
+
resource: any;
|
|
33
|
+
updatedAt: Date | null;
|
|
34
|
+
base: any | null;
|
|
35
|
+
localOnly: boolean;
|
|
36
|
+
};
|
|
37
|
+
type StoredLink = {
|
|
38
|
+
fromType: string;
|
|
39
|
+
fromId: string;
|
|
40
|
+
toType: string;
|
|
41
|
+
toId: string;
|
|
42
|
+
kind: string;
|
|
43
|
+
props: Record<string, unknown>;
|
|
44
|
+
};
|
|
45
|
+
type QueueStatus = "pending" | "sending" | "conflict" | "failed";
|
|
46
|
+
/** The `resolution` of a server-side `OfflineChange` (C1b): kind, per-field choices, merged body. */
|
|
47
|
+
type QueueResolution = OfflineChangeResolution;
|
|
48
|
+
type QueueEntry = {
|
|
49
|
+
seq: number;
|
|
50
|
+
rootId: string;
|
|
51
|
+
method: OfflineMethod;
|
|
52
|
+
path: string;
|
|
53
|
+
body?: any;
|
|
54
|
+
recordType?: string;
|
|
55
|
+
recordId?: string;
|
|
56
|
+
base: any | null;
|
|
57
|
+
resolution?: QueueResolution;
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
status: QueueStatus;
|
|
60
|
+
/**
|
|
61
|
+
* The id of the package (`offline-change-sets`) the entry was last sent in, kept until the
|
|
62
|
+
* server answers for it: a package that never got its answer is sent again with the same id,
|
|
63
|
+
* so the server applies it once.
|
|
64
|
+
*/
|
|
65
|
+
packageId?: string;
|
|
66
|
+
};
|
|
67
|
+
type ConflictKind = "fields" | "deleted-on-server" | "changed-on-server" | "could-not-apply";
|
|
68
|
+
type Conflict = {
|
|
69
|
+
id: string;
|
|
70
|
+
queueSeq: number;
|
|
71
|
+
kind: ConflictKind;
|
|
72
|
+
recordType?: string;
|
|
73
|
+
recordId?: string;
|
|
74
|
+
mine: any;
|
|
75
|
+
theirs: any;
|
|
76
|
+
base: any;
|
|
77
|
+
/** The merge the server settled, as a resource: its current resource with the merged attributes. */
|
|
78
|
+
merged?: any;
|
|
79
|
+
fields: string[];
|
|
80
|
+
blocks?: Record<string, string[]>;
|
|
81
|
+
reason?: string;
|
|
82
|
+
};
|
|
83
|
+
type SyncState = {
|
|
84
|
+
serverTime: Date | null;
|
|
85
|
+
locked: boolean;
|
|
86
|
+
lockedFrom: Date | null;
|
|
87
|
+
};
|
|
88
|
+
/** A record a sync could not read (403/404, or absent from the answer), with its `updatedAt` then. */
|
|
89
|
+
type SkippedRecord = {
|
|
90
|
+
type: string;
|
|
91
|
+
id: string;
|
|
92
|
+
updatedAt: Date;
|
|
93
|
+
};
|
|
94
|
+
interface DeviceStore {
|
|
95
|
+
getRecord(type: string, id: string): Promise<StoredRecord | undefined>;
|
|
96
|
+
/** With `rootId`: every record that root record uses, shared records included. */
|
|
97
|
+
listRecords(filter: {
|
|
98
|
+
type: string;
|
|
99
|
+
rootId?: string;
|
|
100
|
+
ids?: string[];
|
|
101
|
+
}): Promise<StoredRecord[]>;
|
|
102
|
+
putRecord(record: Omit<StoredRecord, "base" | "localOnly"> & {
|
|
103
|
+
base?: any | null;
|
|
104
|
+
localOnly?: boolean;
|
|
105
|
+
}): Promise<void>;
|
|
106
|
+
/** Removes the record (every root record's copy) and every link from or to it. */
|
|
107
|
+
deleteRecord(type: string, id: string): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* The record leaves `rootId`. A shared record (an artwork, a template) that another
|
|
110
|
+
* root record still uses is kept for that root record; it is deleted only when no root record keeps it.
|
|
111
|
+
*/
|
|
112
|
+
removeRecordFromRoot(type: string, id: string, rootId: string): Promise<void>;
|
|
113
|
+
linksFrom(fromId: string, kind?: string): Promise<StoredLink[]>;
|
|
114
|
+
linksTo(toId: string, kind?: string): Promise<StoredLink[]>;
|
|
115
|
+
replaceRootLinks(rootId: string, links: StoredLink[]): Promise<void>;
|
|
116
|
+
putLink(link: StoredLink): Promise<void>;
|
|
117
|
+
deleteLink(link: Pick<StoredLink, "fromId" | "toId" | "kind">): Promise<void>;
|
|
118
|
+
enqueue(entry: Omit<QueueEntry, "seq" | "createdAt" | "status">): Promise<number>;
|
|
119
|
+
queue(filter?: {
|
|
120
|
+
status?: QueueStatus;
|
|
121
|
+
}): Promise<QueueEntry[]>;
|
|
122
|
+
setQueueStatus(seq: number, status: QueueStatus): Promise<void>;
|
|
123
|
+
/** Stores (or with `null` clears) the package id of the given entries. */
|
|
124
|
+
setQueuePackage(seqs: number[], packageId: string | null): Promise<void>;
|
|
125
|
+
removeQueueEntry(seq: number): Promise<void>;
|
|
126
|
+
addConflict(c: Omit<Conflict, "id">): Promise<string>;
|
|
127
|
+
conflicts(): Promise<Conflict[]>;
|
|
128
|
+
removeConflict(id: string): Promise<void>;
|
|
129
|
+
getSyncState(rootId: string): Promise<SyncState>;
|
|
130
|
+
/** The store writes ISO strings and parses them back with `new Date`. */
|
|
131
|
+
setSyncState(rootId: string, s: SyncState): Promise<void>;
|
|
132
|
+
/** The records the last sync of `rootId` skipped: asked for again only once they change. */
|
|
133
|
+
skippedRecords(rootId: string): Promise<SkippedRecord[]>;
|
|
134
|
+
/** Replaces the skipped records of `rootId`. */
|
|
135
|
+
setSkippedRecords(rootId: string, records: SkippedRecord[]): Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Records, links, queue, conflicts, sync_state, skipped records — kept only when its queue is empty. A shared
|
|
138
|
+
* record another root record still uses is kept for that root record.
|
|
139
|
+
*/
|
|
140
|
+
removeRoot(rootId: string): Promise<void>;
|
|
141
|
+
transaction<T>(fn: (store: DeviceStore) => Promise<T>): Promise<T>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Header of a request the server runs as an offline replay. Never answered locally. */
|
|
145
|
+
declare const OFFLINE_REPLAY_HEADER = "x-offline-replay";
|
|
146
|
+
type OfflineMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
147
|
+
/** A Module (matched by its endpoint), a ":param" capture, or a literal segment ("playing", "merge"). */
|
|
148
|
+
type PatternSegment = ApiRequestDataTypeInterface | string;
|
|
149
|
+
/** `url` (beyond the contract, optional): the absolute URL the request was made to, for the `links` of a list. */
|
|
150
|
+
type OfflineRequest = {
|
|
151
|
+
method: OfflineMethod;
|
|
152
|
+
path: string;
|
|
153
|
+
query: URLSearchParams;
|
|
154
|
+
body?: any;
|
|
155
|
+
url?: string;
|
|
156
|
+
};
|
|
157
|
+
/** `body` is a JSON:API document, `undefined` for a 204. */
|
|
158
|
+
type OfflineAnswer = {
|
|
159
|
+
status: number;
|
|
160
|
+
body: any;
|
|
161
|
+
};
|
|
162
|
+
type LocalAnswerContext = {
|
|
163
|
+
request: OfflineRequest;
|
|
164
|
+
params: Record<string, string>;
|
|
165
|
+
store: DeviceStore;
|
|
166
|
+
};
|
|
167
|
+
type LocalAnswer = (ctx: LocalAnswerContext) => Promise<OfflineAnswer>;
|
|
168
|
+
type OfflineRoute = {
|
|
169
|
+
method: OfflineMethod;
|
|
170
|
+
pattern: PatternSegment[];
|
|
171
|
+
answer: LocalAnswer;
|
|
172
|
+
queue: boolean;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* A type the API reads only as a list per target record: each changed record's target is the
|
|
176
|
+
* other end of its sync link of `linkKind` (from the record to the target), and `endpoint`
|
|
177
|
+
* builds that target's list path with `EndpointCreator`.
|
|
178
|
+
*/
|
|
179
|
+
type OfflineLinkedListFetch = {
|
|
180
|
+
linkKind: string;
|
|
181
|
+
endpoint: (rootId: string, target: {
|
|
182
|
+
type: string;
|
|
183
|
+
id: string;
|
|
184
|
+
}) => string;
|
|
185
|
+
};
|
|
186
|
+
type OfflineTypeConfig = {
|
|
187
|
+
module: ApiRequestDataTypeInterface;
|
|
188
|
+
/**
|
|
189
|
+
* How the sync fetches the type's changed records: the root record's list (`root-list`), one
|
|
190
|
+
* GET per record (`by-id`), or (`linked-list`) the list of each record's target, found through
|
|
191
|
+
* the sync answer's links of `linkedList.linkKind` (e.g. player notes, read per target only).
|
|
192
|
+
*/
|
|
193
|
+
fetch: "root-list" | "by-id" | "linked-list";
|
|
194
|
+
/** Required with `fetch: "linked-list"`. */
|
|
195
|
+
linkedList?: OfflineLinkedListFetch;
|
|
196
|
+
readOnly: boolean;
|
|
197
|
+
serverOwnedFields: string[];
|
|
198
|
+
/**
|
|
199
|
+
* Relationship name → Neo4j relationship type (`faction` → `MEMBER_OF`). A link the device
|
|
200
|
+
* writes from a request's relationships carries the mapped type, the kind synced links carry;
|
|
201
|
+
* an unmapped name is stored as the name itself.
|
|
202
|
+
*/
|
|
203
|
+
relationshipKinds?: Record<string, string>;
|
|
204
|
+
/** The string attributes the server's fulltext index covers (default: every string attribute). */
|
|
205
|
+
searchFields?: string[];
|
|
206
|
+
/** The server's order with no `orderBy` (default `updatedAt DESC`). */
|
|
207
|
+
defaultOrderBy?: string;
|
|
208
|
+
};
|
|
209
|
+
type OfflineConfig = {
|
|
210
|
+
getUserId: () => string | undefined;
|
|
211
|
+
/**
|
|
212
|
+
* Offline mode is off on each device until the user turns it on (`turnOnOffline`), stored per
|
|
213
|
+
* user per device. Default (undefined/false): always on, every device keeps the copy.
|
|
214
|
+
*/
|
|
215
|
+
requireOptIn?: boolean;
|
|
216
|
+
/** The module whose records own every other record (in Wyrdli, Modules.Campaign). */
|
|
217
|
+
rootModule: ApiRequestDataTypeInterface;
|
|
218
|
+
types: OfflineTypeConfig[];
|
|
219
|
+
routes: OfflineRoute[];
|
|
220
|
+
/** Ids of the root records to keep on the device. */
|
|
221
|
+
listSyncRoots: () => Promise<string[]>;
|
|
222
|
+
shellPath: (locale: string) => string;
|
|
223
|
+
syncIntervalMs: number;
|
|
224
|
+
/**
|
|
225
|
+
* Where the device worker loads SQLite from at run time (default `/offline-sqlite/index.mjs`).
|
|
226
|
+
* SQLite is never bundled: its own helper worker uses a computed URL that app bundlers refuse.
|
|
227
|
+
* The app serves the files of `dist/offline/sqlite/` at this URL's folder.
|
|
228
|
+
*/
|
|
229
|
+
sqliteModuleUrl?: string;
|
|
230
|
+
/**
|
|
231
|
+
* Where the device-copy worker is started from (default `/offline-sqlite/device-worker.mjs`).
|
|
232
|
+
* It is a standalone file in `dist/offline/sqlite/`, never bundled by the app.
|
|
233
|
+
*/
|
|
234
|
+
deviceWorkerUrl?: string;
|
|
235
|
+
};
|
|
236
|
+
declare const DEFAULT_SQLITE_MODULE_URL = "/offline-sqlite/index.mjs";
|
|
237
|
+
declare const DEVICE_WORKER_FILE = "device-worker.mjs";
|
|
238
|
+
declare const DEFAULT_DEVICE_WORKER_URL = "/offline-sqlite/device-worker.mjs";
|
|
239
|
+
/**
|
|
240
|
+
* The files of `dist/offline/sqlite/` the app serves: the device worker and the SQLite files
|
|
241
|
+
* copied from @sqlite.org/sqlite-wasm.
|
|
242
|
+
*/
|
|
243
|
+
declare const OFFLINE_SQLITE_FILES: readonly ["device-worker.mjs", "index.mjs", "sqlite3.wasm", "sqlite3-opfs-async-proxy.js"];
|
|
244
|
+
/** Thrown by a local answer for something that only the server can do. Answered as a 503 "needs-connection". */
|
|
245
|
+
declare class NeedsConnectionError extends Error {
|
|
246
|
+
readonly status = 503;
|
|
247
|
+
readonly code = "needs-connection";
|
|
248
|
+
constructor(message?: string);
|
|
249
|
+
}
|
|
250
|
+
/** Segments joined with "/", modules by their endpoint. */
|
|
251
|
+
declare function patternToString(pattern: PatternSegment[]): string;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Opts the app into offline mode (the `./offline` entry's `configureOffline`). It also registers
|
|
255
|
+
* the browser device-store opener, so the store, the worker and SQLite are reachable only from
|
|
256
|
+
* this entry, never from the request path every other entry shares.
|
|
257
|
+
*/
|
|
258
|
+
declare function configureOffline(config: OfflineConfig): void;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The signed-in user's device store cannot be used: it could not be opened (opfs-sahpool holds
|
|
262
|
+
* one connection per origin, so a second tab cannot open it) or the user logged out. A request
|
|
263
|
+
* answered from the device copy then needs a connection (503 "needs-connection").
|
|
264
|
+
*/
|
|
265
|
+
declare class DeviceStoreUnavailableError extends NeedsConnectionError {
|
|
266
|
+
constructor(message?: string);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* This browser cannot keep the device copy at all: not a secure context (https or `localhost`),
|
|
270
|
+
* no private file storage (`navigator.storage.getDirectory`), or the worker reports the OPFS
|
|
271
|
+
* APIs missing. Offline mode switches off for the page (`markOfflineUnavailable`). A failure on
|
|
272
|
+
* a browser that can keep the copy (the file held by another tab) is a `DeviceStoreUnavailableError`.
|
|
273
|
+
*/
|
|
274
|
+
declare class OfflineUnavailableError extends DeviceStoreUnavailableError {
|
|
275
|
+
constructor(reason: string);
|
|
276
|
+
}
|
|
277
|
+
/** Whether this browser can keep the device copy (true until found otherwise). */
|
|
278
|
+
declare function isOfflineAvailable(): boolean;
|
|
279
|
+
/**
|
|
280
|
+
* Configured, the browser can keep the device copy (see `markOfflineUnavailable`), and offline
|
|
281
|
+
* mode is on on this device for the signed-in user (see `requireOptIn`).
|
|
282
|
+
*/
|
|
283
|
+
declare function isOfflineConfigured(): boolean;
|
|
284
|
+
/**
|
|
285
|
+
* Whether offline mode is on on this device for `userId`. Always true without
|
|
286
|
+
* `OfflineConfig.requireOptIn`; otherwise the stored switch, false with no user.
|
|
287
|
+
*/
|
|
288
|
+
declare function isOfflineEnabledOnDevice(userId?: string | null): boolean;
|
|
289
|
+
/**
|
|
290
|
+
* Reads the device switch and the last sync time of the signed-in user into the offline state
|
|
291
|
+
* (`enabled`, `lastSyncedAt`). Does nothing before `configureOffline`.
|
|
292
|
+
*/
|
|
293
|
+
declare function refreshOfflineDeviceState(): void;
|
|
294
|
+
declare function getOfflineConfig(): OfflineConfig;
|
|
295
|
+
/** Replaces how a user's device store is opened (tests pass `openMemoryDeviceStore`). */
|
|
296
|
+
declare function setDeviceStoreOpener(opener: ((userId: string) => Promise<DeviceStore>) | null): void;
|
|
297
|
+
/**
|
|
298
|
+
* The signed-in user's device store, opened once per user id. Only the signed-in user's file is
|
|
299
|
+
* ever opened; with no signed-in user the request needs a connection.
|
|
300
|
+
*/
|
|
301
|
+
declare function getOfflineStore(): Promise<DeviceStore>;
|
|
302
|
+
/** Forgets the opened store of a user (logout, deleted file). */
|
|
303
|
+
declare function forgetOfflineStore(userId: string): void;
|
|
304
|
+
|
|
305
|
+
type OfflineState = {
|
|
306
|
+
online: boolean;
|
|
307
|
+
pending: number;
|
|
308
|
+
conflicts: number;
|
|
309
|
+
draining: boolean;
|
|
310
|
+
lockedRootIds: string[];
|
|
311
|
+
/**
|
|
312
|
+
* False once this browser was found unable to keep the device copy (no secure context, no
|
|
313
|
+
* private file storage, or the worker reports the OPFS APIs missing). Offline mode is then off
|
|
314
|
+
* for the rest of the page: `isOfflineConfigured()` answers false and nothing offline runs.
|
|
315
|
+
*/
|
|
316
|
+
available: boolean;
|
|
317
|
+
/**
|
|
318
|
+
* Offline mode is on on this device for the signed-in user. Always true once configured
|
|
319
|
+
* without `OfflineConfig.requireOptIn`; otherwise the GM's per-device switch.
|
|
320
|
+
*/
|
|
321
|
+
enabled: boolean;
|
|
322
|
+
/** The running full sync: root records done out of the total, null when no sync runs. */
|
|
323
|
+
syncProgress: {
|
|
324
|
+
done: number;
|
|
325
|
+
total: number;
|
|
326
|
+
} | null;
|
|
327
|
+
/** When the last full sync on this device finished (epoch ms), null when never. */
|
|
328
|
+
lastSyncedAt: number | null;
|
|
329
|
+
/** How many root records the last full sync kept on this device, null when unknown. */
|
|
330
|
+
rootCount: number | null;
|
|
331
|
+
};
|
|
332
|
+
declare function getOfflineState(): OfflineState;
|
|
333
|
+
declare function subscribeOfflineState(listener: (s: OfflineState) => void): () => void;
|
|
334
|
+
/** Applies a partial change. Listeners run once, and only when a value actually changed. */
|
|
335
|
+
declare function setOfflineState(patch: Partial<OfflineState>): void;
|
|
336
|
+
|
|
337
|
+
declare function useOfflineState(): OfflineState;
|
|
338
|
+
declare function useNeedsConnection(): boolean;
|
|
339
|
+
|
|
340
|
+
type RouteMatch = {
|
|
341
|
+
route: OfflineRoute;
|
|
342
|
+
params: Record<string, string>;
|
|
343
|
+
};
|
|
344
|
+
declare function matchPattern(pattern: PatternSegment[], path: string): Record<string, string> | null;
|
|
345
|
+
/** The first route, in the order given, whose method and pattern match. The query string is ignored. */
|
|
346
|
+
declare function matchRoute(routes: OfflineRoute[], method: OfflineMethod, path: string): RouteMatch | null;
|
|
347
|
+
|
|
348
|
+
type ApiData = {
|
|
349
|
+
data: any;
|
|
350
|
+
ok: boolean;
|
|
351
|
+
status: number;
|
|
352
|
+
statusText: string;
|
|
353
|
+
token?: string;
|
|
354
|
+
refreshToken?: string;
|
|
355
|
+
/** Set by `directFetch` when the API could not be reached at all (the 503 it reports). */
|
|
356
|
+
networkError?: boolean;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
type LocalTransportRequest = {
|
|
360
|
+
method: string;
|
|
361
|
+
url: string;
|
|
362
|
+
body?: any;
|
|
363
|
+
files?: unknown;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Answers a request from the device copy: the app's custom routes first, then the generic ones.
|
|
367
|
+
* The result has the `ApiData` shape `directFetch` returns, so `translateResponse` and
|
|
368
|
+
* `rehydrate()` run unchanged.
|
|
369
|
+
*/
|
|
370
|
+
declare function localTransport(request: LocalTransportRequest): Promise<ApiData>;
|
|
371
|
+
|
|
372
|
+
type WriteThroughRequest = {
|
|
373
|
+
method: string;
|
|
374
|
+
url: string;
|
|
375
|
+
};
|
|
376
|
+
/**
|
|
377
|
+
* After a successful online write, copies the answer into the device copy so it is never behind
|
|
378
|
+
* the GM's own work: the resource and its `included` for POST/PUT/PATCH, the record removed for a
|
|
379
|
+
* DELETE. It never fails the request: a problem is logged and the next sync repairs the copy.
|
|
380
|
+
*/
|
|
381
|
+
declare function writeThrough(request: WriteThroughRequest, response: ApiData): Promise<void>;
|
|
382
|
+
|
|
383
|
+
/** A JSON:API errors document with one error, answered with `status`. */
|
|
384
|
+
declare function errorAnswer(status: number, title: string, detail: string, code?: string): OfflineAnswer;
|
|
385
|
+
declare function needsConnectionAnswer(detail?: string): OfflineAnswer;
|
|
386
|
+
declare function notFoundAnswer(detail: string): OfflineAnswer;
|
|
387
|
+
/** A single document of one stored record, with `included` (`includedFor`). */
|
|
388
|
+
declare function singleAnswer(store: DeviceStore, record: StoredRecord, status?: number): Promise<OfflineAnswer>;
|
|
389
|
+
/**
|
|
390
|
+
* The `included` of a local answer: the stored records linked to `records` (the synced links),
|
|
391
|
+
* then the stored records their relationships point to, and one level further the stored records
|
|
392
|
+
* the relationships of those included records point to (an NPC's root record, then the root record's
|
|
393
|
+
* owner), so a model reads the same nested relationships it reads from the server.
|
|
394
|
+
*
|
|
395
|
+
* Stored resources point at each other (a root record at its owner, the owner at the root record's
|
|
396
|
+
* company, the company back at the owner); `RehydrationFactory` skips a resource met again inside
|
|
397
|
+
* its own chain, so those circles never recurse.
|
|
398
|
+
*/
|
|
399
|
+
declare function includedFor(store: DeviceStore, records: StoredRecord[]): Promise<any[]>;
|
|
400
|
+
/** The server's default list order (`defineEntity` sets `defaultOrderBy: "updatedAt DESC"`). */
|
|
401
|
+
declare const SERVER_DEFAULT_ORDER_BY = "updatedAt DESC";
|
|
402
|
+
type ListOptions = {
|
|
403
|
+
/** The string attributes the server's fulltext index covers (default: every string attribute). */
|
|
404
|
+
searchFields?: string[];
|
|
405
|
+
/** The order with no `orderBy` (default `updatedAt DESC`, the server's). */
|
|
406
|
+
defaultOrderBy?: string;
|
|
407
|
+
};
|
|
408
|
+
/**
|
|
409
|
+
* `search` as the server runs it: the term is split into letter/digit tokens and a record
|
|
410
|
+
* matches when every token appears, case-insensitive, inside one of its indexed string
|
|
411
|
+
* attributes (the fulltext query `*token* AND ...`).
|
|
412
|
+
*/
|
|
413
|
+
declare function searchRecords(records: StoredRecord[], query: URLSearchParams, options?: ListOptions): StoredRecord[];
|
|
414
|
+
/**
|
|
415
|
+
* Orders records as the server's `ORDER BY` does for `orderBy` (`field` or `field ASC|DESC`),
|
|
416
|
+
* or for the default order when there is none. `null` when the server would refuse the
|
|
417
|
+
* `orderBy` (400).
|
|
418
|
+
*/
|
|
419
|
+
declare function orderRecords(records: StoredRecord[], query: URLSearchParams, options?: ListOptions): StoredRecord[] | null;
|
|
420
|
+
/**
|
|
421
|
+
* One list answer for a set of stored records, as the server answers a list: `search`, then
|
|
422
|
+
* `orderBy` (or the default order), then paged the way `JsonApiPaginator` pages (`fetchAll`,
|
|
423
|
+
* `page[size]`, `page[offset]`), with `included`. App answers pass the records they selected.
|
|
424
|
+
*/
|
|
425
|
+
declare function listAnswer(ctx: LocalAnswerContext, records: StoredRecord[], options?: ListOptions): Promise<OfflineAnswer>;
|
|
426
|
+
/**
|
|
427
|
+
* A list document of stored records in the order the caller settled, every record in one page,
|
|
428
|
+
* with `included`: for an app's custom list query that has its own `ORDER BY` and no paging.
|
|
429
|
+
*/
|
|
430
|
+
declare function orderedListAnswer(store: DeviceStore, records: StoredRecord[]): Promise<OfflineAnswer>;
|
|
431
|
+
/**
|
|
432
|
+
* A list document of resources the device computed rather than stored (a query-result
|
|
433
|
+
* projection such as a search hit, built by its model), answered as they are, nothing included.
|
|
434
|
+
*/
|
|
435
|
+
declare function resourceListAnswer(resources: any[]): OfflineAnswer;
|
|
436
|
+
/**
|
|
437
|
+
* The kind a link written on the device carries for relationship `name`: the Neo4j type the
|
|
438
|
+
* type config maps it to (the kind synced links carry), otherwise the name itself.
|
|
439
|
+
*/
|
|
440
|
+
declare function linkKindFor(typeConfig: OfflineTypeConfig | undefined, name: string): string;
|
|
441
|
+
/**
|
|
442
|
+
* Every kind a link of relationship `name` may carry on the device: the name (written on the
|
|
443
|
+
* device before a mapping existed) and the mapped Neo4j type (synced). `undefined` when the
|
|
444
|
+
* Neo4j type is not known: then a link of any kind may be the one.
|
|
445
|
+
*/
|
|
446
|
+
declare function linkKindsOf(typeConfig: OfflineTypeConfig | undefined, name: string): string[] | undefined;
|
|
447
|
+
/**
|
|
448
|
+
* The stored links of `id` whose kind is one of `kinds` (Neo4j types and/or relationship
|
|
449
|
+
* names). `out`: links from `id`; `in`: links to `id`; `both`: either.
|
|
450
|
+
*/
|
|
451
|
+
declare function linksOfKind(store: DeviceStore, id: string, kinds: string[], direction?: "out" | "in" | "both"): Promise<StoredLink[]>;
|
|
452
|
+
/** The `data` of a JSON:API document a model built (`createJsonApi()` or a dedicated method). */
|
|
453
|
+
type ResourceDocumentData = {
|
|
454
|
+
attributes?: Record<string, unknown>;
|
|
455
|
+
relationships?: Record<string, unknown>;
|
|
456
|
+
meta?: Record<string, unknown>;
|
|
457
|
+
};
|
|
458
|
+
/**
|
|
459
|
+
* The resource a create document stands for, under `identity`: the document's attributes,
|
|
460
|
+
* relationships and meta, as the device stores a record it creates.
|
|
461
|
+
*/
|
|
462
|
+
declare function resourceFromDocument(document: any, identity: {
|
|
463
|
+
type: string;
|
|
464
|
+
id: string;
|
|
465
|
+
}): any;
|
|
466
|
+
/**
|
|
467
|
+
* A stored resource with a document's `data` written over it: attributes and relationships merged
|
|
468
|
+
* by name, and meta merged when the document carries one, as the device applies an edit.
|
|
469
|
+
*/
|
|
470
|
+
declare function mergeResource(before: any, identity: {
|
|
471
|
+
type: string;
|
|
472
|
+
id: string;
|
|
473
|
+
}, data: ResourceDocumentData): any;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Opens the device copy of `userId` (`nja-offline-<userId>.sqlite3` in the OPFS,
|
|
477
|
+
* through the worker). Only one user's file is open at a time: opening another
|
|
478
|
+
* user's file closes the previous one first.
|
|
479
|
+
*/
|
|
480
|
+
declare function openDeviceStore(userId: string): Promise<DeviceStore>;
|
|
481
|
+
/** The same store and schema over an in-memory database, for tests. */
|
|
482
|
+
declare function openMemoryDeviceStore(): Promise<DeviceStore>;
|
|
483
|
+
/**
|
|
484
|
+
* Closes `userId`'s device copy in every page and removes every OPFS entry of it. The files can
|
|
485
|
+
* only be removed once no page holds them, so the removal waits for every page to let go.
|
|
486
|
+
*/
|
|
487
|
+
declare function deleteDeviceStore(userId: string): Promise<void>;
|
|
488
|
+
/** Whether `userId`'s device copy holds unsent changes. Never opens another user's file. */
|
|
489
|
+
declare function hasUnsentChanges(userId: string): Promise<boolean>;
|
|
490
|
+
/**
|
|
491
|
+
* A user signed in on this browser: their device copy may be opened again (it is blocked after
|
|
492
|
+
* a logout), and every other user's device copy is deleted when it has nothing unsent, and kept
|
|
493
|
+
* untouched (never opened) when it has. When the images cache was not this user's (another user
|
|
494
|
+
* signed in before, or no owner is known), it is deleted: the user's sync downloads their artwork.
|
|
495
|
+
*/
|
|
496
|
+
declare function onUserSignedIn(userId: string): Promise<void>;
|
|
497
|
+
/** Locked now: the server said so, or the lock date it gave has passed. */
|
|
498
|
+
declare function isRootLocked(s: {
|
|
499
|
+
locked: boolean;
|
|
500
|
+
lockedFrom: Date | null;
|
|
501
|
+
}, now: Date): boolean;
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Brings one root record's device copy up to date with the sync answer (C2): records changed since
|
|
505
|
+
* the stored server time are fetched (C3), records gone from the root record are deleted, the links
|
|
506
|
+
* are replaced, and artwork images are downloaded. The sync state is stored only once every
|
|
507
|
+
* fetch succeeded (or answered 403/404). A record with a waiting change is never overwritten.
|
|
508
|
+
*/
|
|
509
|
+
declare function syncRoot(rootId: string): Promise<void>;
|
|
510
|
+
/**
|
|
511
|
+
* Syncs every root record `listSyncRoots()` returns, then removes from the device the root records
|
|
512
|
+
* that are no longer roots (unless they still have waiting changes). Does nothing offline.
|
|
513
|
+
*/
|
|
514
|
+
declare function syncAll(): Promise<void>;
|
|
515
|
+
|
|
516
|
+
type DrainResult = {
|
|
517
|
+
applied: number;
|
|
518
|
+
conflicts: number;
|
|
519
|
+
stoppedForLogin: boolean;
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* Sends every waiting change of the unlocked root records in ONE `offline-change-sets` request,
|
|
523
|
+
* after renewing the sign-in. Applied entries are removed and their answer written to the
|
|
524
|
+
* device copy; conflicts become Conflict rows; waiting entries stay pending. A network error
|
|
525
|
+
* or an expired sign-in stops it and keeps every entry.
|
|
526
|
+
*/
|
|
527
|
+
declare function drainQueue(): Promise<DrainResult>;
|
|
528
|
+
|
|
529
|
+
/** The unresolved conflicts of the signed-in user's device. */
|
|
530
|
+
declare function useConflicts(): Conflict[];
|
|
531
|
+
|
|
532
|
+
type ConflictChoice = {
|
|
533
|
+
kind: "mine";
|
|
534
|
+
} | {
|
|
535
|
+
kind: "theirs";
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Per conflicting field: whose value wins. `mergedBody`: the JSON:API document the record
|
|
539
|
+
* type's model built with createJsonApi() holding only the fields chosen "merged".
|
|
540
|
+
*/
|
|
541
|
+
| {
|
|
542
|
+
kind: "fields";
|
|
543
|
+
choices: Record<string, "mine" | "theirs" | "merged">;
|
|
544
|
+
mergedBody?: any;
|
|
545
|
+
} | {
|
|
546
|
+
kind: "restore";
|
|
547
|
+
} | {
|
|
548
|
+
kind: "accept-deletion";
|
|
549
|
+
} | {
|
|
550
|
+
kind: "delete-anyway";
|
|
551
|
+
} | {
|
|
552
|
+
kind: "retry";
|
|
553
|
+
} | {
|
|
554
|
+
kind: "discard";
|
|
555
|
+
};
|
|
556
|
+
/**
|
|
557
|
+
* Applies the GM's choice for one conflict. "keep mine", per-field choices, "restore" and "delete anyway"
|
|
558
|
+
* queue one more change (sent with the next package); "keep theirs" and "let the deletion
|
|
559
|
+
* stand" only update the device. Online, the queue is sent right away.
|
|
560
|
+
*/
|
|
561
|
+
declare function resolveConflict(id: string, choice: ConflictChoice): Promise<void>;
|
|
562
|
+
|
|
563
|
+
/** Stops every running offline runtime (timers, listeners). Called on logout. */
|
|
564
|
+
declare function stopOfflineRuntime(): void;
|
|
565
|
+
/**
|
|
566
|
+
* Starts the offline runtime: drain-then-sync on open (online) and on every reconnect, syncAll
|
|
567
|
+
* every `syncIntervalMs` while online, a probe of the API while the session is offline but the
|
|
568
|
+
* browser is online (a probe that reaches the API runs the drain and the sync, and the session
|
|
569
|
+
* goes online once they succeeded), and the lock dates checked every minute. Returns the stop
|
|
570
|
+
* function.
|
|
571
|
+
*/
|
|
572
|
+
declare function startOfflineRuntime(): () => void;
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* How many changes the signed-in user's device still has to send (every queue entry: pending,
|
|
576
|
+
* sending, in conflict or failed). The app warns before logging out when this is above 0.
|
|
577
|
+
*/
|
|
578
|
+
declare function prepareOfflineLogout(): Promise<{
|
|
579
|
+
pending: number;
|
|
580
|
+
}>;
|
|
581
|
+
/**
|
|
582
|
+
* Deletes the user's device database and the cached artwork images. Call it right before logging
|
|
583
|
+
* out. The runtime is stopped first and the store blocked until the next sign-in
|
|
584
|
+
* (`onUserSignedIn`), so a sync still running cannot open, and so recreate, the deleted file.
|
|
585
|
+
* The device switch (`requireOptIn`) is turned off too: after a logout nothing is downloaded again
|
|
586
|
+
* until the user turns offline mode back on.
|
|
587
|
+
*/
|
|
588
|
+
declare function finishOfflineLogout(userId: string): Promise<void>;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Turns offline mode on on this device for the signed-in user (`OfflineConfig.requireOptIn`).
|
|
592
|
+
* It does not start the runtime: the app starts it once `enabled` is true in the offline state.
|
|
593
|
+
*/
|
|
594
|
+
declare function turnOnOffline(): Promise<void>;
|
|
595
|
+
/**
|
|
596
|
+
* Turns offline mode off on this device for the signed-in user and deletes this device's copy
|
|
597
|
+
* (database and cached images). Refuses, changing nothing, while the copy has changes still to
|
|
598
|
+
* send: the answer then carries how many.
|
|
599
|
+
*/
|
|
600
|
+
declare function turnOffOffline(): Promise<{
|
|
601
|
+
turnedOff: true;
|
|
602
|
+
} | {
|
|
603
|
+
turnedOff: false;
|
|
604
|
+
pending: number;
|
|
605
|
+
}>;
|
|
606
|
+
|
|
607
|
+
export { type Conflict, type ConflictChoice, type ConflictKind, DEFAULT_DEVICE_WORKER_URL, DEFAULT_SQLITE_MODULE_URL, DEVICE_WORKER_FILE, type DeviceStore, DeviceStoreUnavailableError, type DrainResult, type ListOptions, type LocalAnswer, type LocalAnswerContext, NeedsConnectionError, OFFLINE_REPLAY_HEADER, OFFLINE_SQLITE_FILES, type OfflineAnswer, type OfflineChangeResolution, type OfflineConfig, type OfflineLinkedListFetch, type OfflineMethod, type OfflineRequest, type OfflineRoute, type OfflineState, type OfflineTypeConfig, OfflineUnavailableError, type PatternSegment, type QueueEntry, type QueueResolution, type QueueStatus, type ResourceDocumentData, type RouteMatch, SERVER_DEFAULT_ORDER_BY, type SkippedRecord, type StoredLink, type StoredRecord, type SyncState, configureOffline, deleteDeviceStore, drainQueue, errorAnswer, finishOfflineLogout, forgetOfflineStore, getOfflineConfig, getOfflineState, getOfflineStore, hasUnsentChanges, includedFor, isOfflineAvailable, isOfflineConfigured, isOfflineEnabledOnDevice, isRootLocked, linkKindFor, linkKindsOf, linksOfKind, listAnswer, localTransport, matchPattern, matchRoute, mergeResource, needsConnectionAnswer, notFoundAnswer, onUserSignedIn, openDeviceStore, openMemoryDeviceStore, orderRecords, orderedListAnswer, patternToString, prepareOfflineLogout, refreshOfflineDeviceState, resolveConflict, resourceFromDocument, resourceListAnswer, searchRecords, setDeviceStoreOpener, setOfflineState, singleAnswer, startOfflineRuntime, stopOfflineRuntime, subscribeOfflineState, syncAll, syncRoot, turnOffOffline, turnOnOffline, useConflicts, useNeedsConnection, useOfflineState, writeThrough };
|