@carlonicora/nextjs-jsonapi 6.12.1 → 6.13.0
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-N2NBYCPC.mjs → BlockNoteEditor-22Q4DJBU.mjs} +20 -8
- package/dist/BlockNoteEditor-22Q4DJBU.mjs.map +1 -0
- package/dist/{BlockNoteEditor-CBYKEXAQ.js → BlockNoteEditor-2VLPSEKS.js} +34 -22
- package/dist/BlockNoteEditor-2VLPSEKS.js.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-6NZKWIOR.js → chunk-2H342HNN.js} +956 -929
- package/dist/chunk-2H342HNN.js.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-6XJOQFCP.mjs → chunk-H4NLDX5G.mjs} +59 -32
- package/dist/chunk-H4NLDX5G.mjs.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-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-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/containers/RoundPageContainer.tsx +8 -8
- 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/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-CBYKEXAQ.js.map +0 -1
- package/dist/BlockNoteEditor-N2NBYCPC.mjs.map +0 -1
- package/dist/BlockNoteViewer-WGCDB7IJ.js.map +0 -1
- package/dist/JsonApiRequest-FZGPLNUF.js +0 -26
- package/dist/chunk-6NZKWIOR.js.map +0 -1
- package/dist/chunk-6XJOQFCP.mjs.map +0 -1
- 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/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 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/offline/generic-answers.ts","../src/offline/router.ts","../src/offline/local-transport.ts"],"sourcesContent":["import { CURRENT_USER_STORAGE_KEY } from \"../features/user/data/user.storage\";\nimport { getOfflineConfig } from \"./config\";\nimport { getOfflineState } from \"./state\";\nimport type { DeviceStore, StoredLink, StoredRecord } from \"./store/DeviceStore\";\nimport type {\n LocalAnswerContext,\n OfflineAnswer,\n OfflineConfig,\n OfflineMethod,\n OfflineRoute,\n OfflineTypeConfig,\n} from \"./types\";\n\nconst DEFAULT_PAGE_SIZE = 25;\n\n// ---------------------------------------------------------------------------------------------\n// Answers shared by every route\n// ---------------------------------------------------------------------------------------------\n\n/** A JSON:API errors document with one error, answered with `status`. */\nexport function errorAnswer(status: number, title: string, detail: string, code?: string): OfflineAnswer {\n return {\n status,\n body: { errors: [{ status: String(status), ...(code ? { code } : {}), title, detail }] },\n };\n}\n\nexport function needsConnectionAnswer(detail: string = \"Needs a connection\"): OfflineAnswer {\n return errorAnswer(503, \"Needs a connection\", detail, \"needs-connection\");\n}\n\nexport function notFoundAnswer(detail: string): OfflineAnswer {\n return errorAnswer(404, \"Not found\", detail);\n}\n\n/** A single document of one stored record, with `included` (`includedFor`). */\nexport async function singleAnswer(\n store: DeviceStore,\n record: StoredRecord,\n status: number = 200,\n): Promise<OfflineAnswer> {\n return { status, body: { data: record.resource, included: await includedFor(store, [record]) } };\n}\n\nexport function isRootWriteLocked(rootId: string | undefined): boolean {\n return !!rootId && getOfflineState().lockedRootIds.includes(rootId);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Resource helpers\n// ---------------------------------------------------------------------------------------------\n\ntype ResourceIdentifier = { type: string; id: string };\n\nfunction identifiersOf(relationship: any): ResourceIdentifier[] {\n const data = relationship?.data;\n if (!data) return [];\n return (Array.isArray(data) ? data : [data]).filter((item: any) => item && item.type && item.id);\n}\n\nfunction linkedIds(links: StoredLink[], id: string): ResourceIdentifier[] {\n return links.map((link) =>\n link.fromId === id ? { type: link.toType, id: link.toId } : { type: link.fromType, id: link.fromId },\n );\n}\n\n/** Every stored resource linked to one of `ids`, in either direction, without duplicates or the records themselves. */\n/** The `{ type, id }` every relationship of a JSON:API resource points to. */\nfunction relationshipTargets(resource: any): { type: string; id: string }[] {\n const targets: { type: string; id: string }[] = [];\n for (const relationship of Object.values<any>(resource?.relationships ?? {})) {\n const data = relationship?.data;\n for (const ref of Array.isArray(data) ? data : data ? [data] : []) {\n if (ref?.type && ref?.id) targets.push({ type: ref.type, id: ref.id });\n }\n }\n return targets;\n}\n\n/**\n * The `included` of a local answer: the stored records linked to `records` (the synced links),\n * then the stored records their relationships point to, and one level further the stored records\n * the relationships of those included records point to (an NPC's root record, then the root record's\n * owner), so a model reads the same nested relationships it reads from the server.\n *\n * Stored resources point at each other (a root record at its owner, the owner at the root record's\n * company, the company back at the owner); `RehydrationFactory` skips a resource met again inside\n * its own chain, so those circles never recurse.\n */\nexport async function includedFor(store: DeviceStore, records: StoredRecord[]): Promise<any[]> {\n // 0: the answer's own records; 1: what they link to; 2: what those link to.\n const level = new Map<string, number>(records.map((record) => [`${record.type}:${record.id}`, 0]));\n const included: { resource: any; level: number }[] = [];\n\n const add = async (target: { type: string; id: string }, atLevel: number): Promise<void> => {\n const key = `${target.type}:${target.id}`;\n if (level.has(key)) return;\n level.set(key, atLevel);\n const stored = await store.getRecord(target.type, target.id);\n const resource = stored?.resource ?? signedInUserResource(target);\n if (resource) included.push({ resource, level: atLevel });\n };\n\n for (const record of records) {\n const links = [...(await store.linksFrom(record.id)), ...(await store.linksTo(record.id))];\n for (const target of linkedIds(links, record.id)) await add(target, 1);\n }\n for (const record of records) {\n for (const target of relationshipTargets(record.resource)) await add(target, 1);\n }\n for (const entry of included.filter((candidate) => candidate.level === 1)) {\n for (const target of relationshipTargets(entry.resource)) await add(target, 2);\n }\n return included.map((entry) => entry.resource);\n}\n\n/**\n * The signed-in user as a resource, when `target` is that user. Users are not kept on the device,\n * but a relationship to the signed-in user (every downloaded root record's owner) must still resolve:\n * the app tells a GM from a player by comparing the root record's owner with the signed-in user. The\n * resource comes from the copy CurrentUserProvider keeps in localStorage, without relationships.\n */\nfunction signedInUserResource(target: { type: string; id: string }): any | undefined {\n try {\n if (target.id !== getOfflineConfig().getUserId()) return undefined;\n const stored = JSON.parse(window.localStorage.getItem(CURRENT_USER_STORAGE_KEY) ?? \"null\");\n const user = stored?.jsonApi;\n if (user?.type !== target.type || user?.id !== target.id) return undefined;\n return { type: user.type, id: user.id, attributes: user.attributes ?? {}, meta: user.meta };\n } catch {\n return undefined;\n }\n}\n\nfunction attributeOf(record: StoredRecord, name: string): unknown {\n const value = record.resource?.attributes?.[name];\n if (value !== undefined && value !== null) return value;\n // The server value the sync keeps beside the resource.\n if (name === \"updatedAt\") return record.updatedAt ?? null;\n return value;\n}\n\n/** The server's default list order (`defineEntity` sets `defaultOrderBy: \"updatedAt DESC\"`). */\nexport const SERVER_DEFAULT_ORDER_BY = \"updatedAt DESC\";\n\n/** The only `orderBy` the server accepts (`buildLegacyOrderByClause`): one property, optional ASC/DESC. */\nconst ORDER_BY = /^([A-Za-z_][A-Za-z0-9_]*)(?:\\s+(ASC|DESC))?$/i;\n\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}(T|$)/;\n\nfunction comparable(value: unknown): unknown {\n if (value instanceof Date) return value.getTime();\n if (typeof value === \"string\" && ISO_DATE.test(value)) {\n const time = Date.parse(value);\n if (!Number.isNaN(time)) return time;\n }\n return value;\n}\n\n/** Cypher ORDER BY on one property, ascending: null sorts after every value. */\nfunction compareAscending(left: unknown, right: unknown): number {\n const a = comparable(left);\n const b = comparable(right);\n const aNull = a === undefined || a === null;\n const bNull = b === undefined || b === null;\n if (aNull || bNull) return aNull === bNull ? 0 : aNull ? 1 : -1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") return Number(a) - Number(b);\n const x = String(a);\n const y = String(b);\n return x < y ? -1 : x > y ? 1 : 0;\n}\n\nexport type ListOptions = {\n /** The string attributes the server's fulltext index covers (default: every string attribute). */\n searchFields?: string[];\n /** The order with no `orderBy` (default `updatedAt DESC`, the server's). */\n defaultOrderBy?: string;\n};\n\n/**\n * `search` as the server runs it: the term is split into letter/digit tokens and a record\n * matches when every token appears, case-insensitive, inside one of its indexed string\n * attributes (the fulltext query `*token* AND ...`).\n */\nexport function searchRecords(\n records: StoredRecord[],\n query: URLSearchParams,\n options: ListOptions = {},\n): StoredRecord[] {\n const term = query.get(\"search\");\n if (term === null || term === undefined || term.trim() === \"\") return records;\n const tokens = term\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n if (tokens.length === 0) return records;\n\n return records.filter((record) => {\n const attributes = record.resource?.attributes ?? {};\n const fields = options.searchFields ?? Object.keys(attributes);\n const texts = fields\n .map((field) => attributes[field])\n .filter((value): value is string => typeof value === \"string\")\n .map((value) => value.toLowerCase());\n return tokens.every((token) => texts.some((text) => text.includes(token)));\n });\n}\n\n/**\n * Orders records as the server's `ORDER BY` does for `orderBy` (`field` or `field ASC|DESC`),\n * or for the default order when there is none. `null` when the server would refuse the\n * `orderBy` (400).\n */\nexport function orderRecords(\n records: StoredRecord[],\n query: URLSearchParams,\n options: ListOptions = {},\n): StoredRecord[] | null {\n const requested = query.get(\"orderBy\")?.trim();\n const value = requested || options.defaultOrderBy?.trim() || SERVER_DEFAULT_ORDER_BY;\n const match = ORDER_BY.exec(value);\n if (!match) return null;\n const field = match[1];\n const desc = match[2]?.toUpperCase() === \"DESC\";\n return [...records].sort((left, right) => {\n const order = compareAscending(attributeOf(left, field), attributeOf(right, field));\n return desc ? -order : order;\n });\n}\n\nfunction linkUrl(base: string, query: URLSearchParams, changes: Record<string, string | undefined>): string {\n const params = new URLSearchParams(query);\n for (const [key, value] of Object.entries(changes)) {\n if (value === undefined) params.delete(key);\n else params.set(key, value);\n }\n const search = params.toString().replace(/%5B/g, \"[\").replace(/%5D/g, \"]\").replace(/%2C/g, \",\");\n return search ? `${base}?${search}` : base;\n}\n\n/**\n * One list answer for a set of stored records, as the server answers a list: `search`, then\n * `orderBy` (or the default order), then paged the way `JsonApiPaginator` pages (`fetchAll`,\n * `page[size]`, `page[offset]`), with `included`. App answers pass the records they selected.\n */\nexport async function listAnswer(\n ctx: LocalAnswerContext,\n records: StoredRecord[],\n options: ListOptions = {},\n): Promise<OfflineAnswer> {\n const { request, store } = ctx;\n const base = request.url ? request.url.split(\"?\")[0] : request.path;\n const sorted = orderRecords(searchRecords(records, request.query, options), request.query, options);\n if (!sorted) {\n return errorAnswer(400, \"Bad Request\", \"Invalid orderBy\");\n }\n\n if (request.query.get(\"fetchAll\") === \"true\") {\n return {\n status: 200,\n body: {\n data: sorted.map((record) => record.resource),\n included: await includedFor(store, sorted),\n links: { self: linkUrl(base, request.query, {}) },\n meta: { total: sorted.length },\n },\n };\n }\n\n const size = Number(request.query.get(\"page[size]\")) || DEFAULT_PAGE_SIZE;\n const offset = Number(request.query.get(\"page[offset]\")) || 0;\n const page = sorted.slice(offset, offset + size);\n\n const links: Record<string, string> = {\n self: linkUrl(base, request.query, { \"page[size]\": String(size), \"page[offset]\": undefined }),\n };\n if (offset + size < sorted.length) {\n links.next = linkUrl(base, request.query, { \"page[size]\": String(size), \"page[offset]\": String(offset + size) });\n }\n if (offset > 0) {\n links.prev = linkUrl(base, request.query, {\n \"page[size]\": String(size),\n \"page[offset]\": String(Math.max(0, offset - size)),\n });\n // The server names it `previous`; the client reads `prev`.\n links.previous = links.prev;\n }\n\n return {\n status: 200,\n body: {\n data: page.map((record) => record.resource),\n included: await includedFor(store, page),\n links,\n meta: { total: sorted.length },\n },\n };\n}\n\n/**\n * A list document of stored records in the order the caller settled, every record in one page,\n * with `included`: for an app's custom list query that has its own `ORDER BY` and no paging.\n */\nexport async function orderedListAnswer(store: DeviceStore, records: StoredRecord[]): Promise<OfflineAnswer> {\n return {\n status: 200,\n body: {\n data: records.map((record) => record.resource),\n included: await includedFor(store, records),\n meta: { total: records.length },\n },\n };\n}\n\n/**\n * A list document of resources the device computed rather than stored (a query-result\n * projection such as a search hit, built by its model), answered as they are, nothing included.\n */\nexport function resourceListAnswer(resources: any[]): OfflineAnswer {\n return { status: 200, body: { data: resources, included: [] } };\n}\n\nfunction listOptionsOf(typeConfig: OfflineTypeConfig): ListOptions {\n return { searchFields: typeConfig.searchFields, defaultOrderBy: typeConfig.defaultOrderBy };\n}\n\n// ---------------------------------------------------------------------------------------------\n// Link kinds\n// ---------------------------------------------------------------------------------------------\n\n/**\n * The kind a link written on the device carries for relationship `name`: the Neo4j type the\n * type config maps it to (the kind synced links carry), otherwise the name itself.\n */\nexport function linkKindFor(typeConfig: OfflineTypeConfig | undefined, name: string): string {\n return typeConfig?.relationshipKinds?.[name] ?? name;\n}\n\n/**\n * Every kind a link of relationship `name` may carry on the device: the name (written on the\n * device before a mapping existed) and the mapped Neo4j type (synced). `undefined` when the\n * Neo4j type is not known: then a link of any kind may be the one.\n */\nexport function linkKindsOf(typeConfig: OfflineTypeConfig | undefined, name: string): string[] | undefined {\n const mapped = typeConfig?.relationshipKinds?.[name];\n return mapped ? [name, mapped] : undefined;\n}\n\n/**\n * The stored links of `id` whose kind is one of `kinds` (Neo4j types and/or relationship\n * names). `out`: links from `id`; `in`: links to `id`; `both`: either.\n */\nexport async function linksOfKind(\n store: DeviceStore,\n id: string,\n kinds: string[],\n direction: \"out\" | \"in\" | \"both\" = \"both\",\n): Promise<StoredLink[]> {\n const result: StoredLink[] = [];\n const seen = new Set<string>();\n for (const kind of new Set(kinds)) {\n const links = [\n ...(direction !== \"in\" ? await store.linksFrom(id, kind) : []),\n ...(direction !== \"out\" ? await store.linksTo(id, kind) : []),\n ];\n for (const link of links) {\n const key = `${link.fromId}|${link.toId}|${link.kind}`;\n if (seen.has(key)) continue;\n seen.add(key);\n result.push(link);\n }\n }\n return result;\n}\n\nfunction typeConfigOf(config: OfflineConfig, type: string): OfflineTypeConfig | undefined {\n return config.types.find((typeConfig) => typeConfig.module.name === type);\n}\n\n/** The relationship names of `resource` that point to a record of `type`. */\nfunction relationshipNamesToward(resource: any, type: string): string[] {\n return Object.entries<any>(resource?.relationships ?? {})\n .filter(([, relationship]) => identifiersOf(relationship).some((identifier) => identifier.type === type))\n .map(([name]) => name);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Reads\n// ---------------------------------------------------------------------------------------------\n\n/** A record that is not on the device: only the server can answer for it. */\nfunction notOnDeviceAnswer(type: string, id: string): OfflineAnswer {\n return needsConnectionAnswer(`${type} ${id} is not on this device`);\n}\n\nasync function getOne(ctx: LocalAnswerContext, type: string): Promise<OfflineAnswer> {\n const record = await ctx.store.getRecord(type, ctx.params.id);\n if (!record) return notOnDeviceAnswer(type, ctx.params.id);\n return singleAnswer(ctx.store, record);\n}\n\nasync function rootList(\n ctx: LocalAnswerContext,\n config: OfflineConfig,\n typeConfig: OfflineTypeConfig,\n): Promise<OfflineAnswer> {\n const rootType = config.rootModule.name;\n const rootId = ctx.params.rootId;\n if (!(await ctx.store.getRecord(rootType, rootId))) return notOnDeviceAnswer(rootType, rootId);\n const records = await ctx.store.listRecords({ type: typeConfig.module.name, rootId });\n return listAnswer(ctx, records, listOptionsOf(typeConfig));\n}\n\n/**\n * `GET <parent>/:parentId/<type>`: the stored records of `type` linked to the parent by the\n * relationship between the two types. That relationship is the one whose name, on either\n * record, points to the other type; its links are matched by name and by mapped Neo4j type.\n * When its Neo4j type is not known, links of any kind count.\n */\nasync function relatedList(\n ctx: LocalAnswerContext,\n config: OfflineConfig,\n typeConfig: OfflineTypeConfig,\n): Promise<OfflineAnswer> {\n const type = typeConfig.module.name;\n const { parentType, parentId } = ctx.params;\n const parent = await ctx.store.getRecord(parentType, parentId);\n if (!parent) return notOnDeviceAnswer(parentType, parentId);\n\n const links = [...(await ctx.store.linksFrom(parentId)), ...(await ctx.store.linksTo(parentId))].filter((link) =>\n link.fromId === parentId ? link.toType === type : link.fromType === type,\n );\n const candidates = await ctx.store.listRecords({\n type,\n ids: [...new Set(links.map((link) => (link.fromId === parentId ? link.toId : link.fromId)))],\n });\n\n const kinds = new Set<string>();\n let anyKind = false;\n const collect = (owner: OfflineTypeConfig | undefined, names: string[]) => {\n for (const name of names) {\n const known = linkKindsOf(owner, name);\n if (known) known.forEach((kind) => kinds.add(kind));\n else anyKind = true;\n }\n };\n collect(typeConfig, [\n ...new Set(candidates.flatMap((record) => relationshipNamesToward(record.resource, parentType))),\n ]);\n collect(typeConfigOf(config, parentType), relationshipNamesToward(parent.resource, type));\n\n const accepted = anyKind || kinds.size === 0 ? links : links.filter((link) => kinds.has(link.kind));\n const ids = new Set(accepted.map((link) => (link.fromId === parentId ? link.toId : link.fromId)));\n return listAnswer(\n ctx,\n candidates.filter((record) => ids.has(record.id)),\n listOptionsOf(typeConfig),\n );\n}\n\n// ---------------------------------------------------------------------------------------------\n// Writes\n// ---------------------------------------------------------------------------------------------\n\n/** The root record a record belongs to: itself (root type), a relationship to the root, or a related stored record. */\nexport async function rootIdForResource(\n store: DeviceStore,\n config: OfflineConfig,\n resource: any,\n): Promise<string | undefined> {\n const rootType = config.rootModule.name;\n if (resource?.type === rootType && resource.id) return resource.id;\n\n const identifiers = Object.values(resource?.relationships ?? {}).flatMap(identifiersOf);\n const root = identifiers.find((identifier) => identifier.type === rootType);\n if (root) return root.id;\n\n for (const identifier of identifiers) {\n const stored = await store.getRecord(identifier.type, identifier.id);\n if (stored?.rootId) return stored.rootId;\n }\n return undefined;\n}\n\nasync function linkRelationships(\n store: DeviceStore,\n typeConfig: OfflineTypeConfig | undefined,\n record: { type: string; id: string },\n before: Record<string, any>,\n after: Record<string, any>,\n): Promise<void> {\n for (const [name, relationship] of Object.entries(after)) {\n const previous = new Map(identifiersOf(before[name]).map((identifier) => [identifier.id, identifier]));\n const next = new Map(identifiersOf(relationship).map((identifier) => [identifier.id, identifier]));\n const kinds = linkKindsOf(typeConfig, name);\n\n for (const [id] of previous) {\n if (next.has(id)) continue;\n // Only the links of this relationship (by name or Neo4j type); any kind when it is not known.\n const links = kinds\n ? await linksOfKind(store, record.id, kinds, \"both\")\n : [...(await store.linksFrom(record.id)), ...(await store.linksTo(record.id))];\n for (const link of links) {\n if (link.fromId === id || link.toId === id) {\n await store.deleteLink({ fromId: link.fromId, toId: link.toId, kind: link.kind });\n }\n }\n }\n for (const [id, identifier] of next) {\n if (previous.has(id)) continue;\n await store.putLink({\n fromType: record.type,\n fromId: record.id,\n toType: identifier.type,\n toId: id,\n kind: linkKindFor(typeConfig, name),\n props: {},\n });\n }\n }\n}\n\n/** The `data` of a JSON:API document a model built (`createJsonApi()` or a dedicated method). */\nexport type ResourceDocumentData = {\n attributes?: Record<string, unknown>;\n relationships?: Record<string, unknown>;\n meta?: Record<string, unknown>;\n};\n\n/**\n * The resource a create document stands for, under `identity`: the document's attributes,\n * relationships and meta, as the device stores a record it creates.\n */\nexport function resourceFromDocument(document: any, identity: { type: string; id: string }): any {\n const data: ResourceDocumentData = document?.data ?? {};\n const resource: any = { type: identity.type, id: identity.id };\n if (data.attributes) resource.attributes = { ...data.attributes };\n if (data.relationships) resource.relationships = { ...data.relationships };\n if (data.meta) resource.meta = { ...data.meta };\n return resource;\n}\n\n/**\n * A stored resource with a document's `data` written over it: attributes and relationships merged\n * by name, and meta merged when the document carries one, as the device applies an edit.\n */\nexport function mergeResource(before: any, identity: { type: string; id: string }, data: ResourceDocumentData): any {\n const previous = before ?? identity;\n const resource: any = {\n ...previous,\n type: identity.type,\n id: identity.id,\n attributes: { ...(previous.attributes ?? {}), ...(data?.attributes ?? {}) },\n relationships: { ...(previous.relationships ?? {}), ...(data?.relationships ?? {}) },\n };\n if (data?.meta) resource.meta = { ...(previous.meta ?? {}), ...data.meta };\n return resource;\n}\n\nfunction newId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") return crypto.randomUUID();\n return `local-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n}\n\nasync function create(ctx: LocalAnswerContext, config: OfflineConfig, type: string): Promise<OfflineAnswer> {\n const typeConfig = typeConfigOf(config, type);\n const id: string = ctx.request.body?.data?.id ?? newId();\n const resource = resourceFromDocument(ctx.request.body, { type, id });\n const rootId = await rootIdForResource(ctx.store, config, resource);\n if (!rootId) return needsConnectionAnswer(`A new ${type} that belongs to no root record needs a connection`);\n if (isRootWriteLocked(rootId)) return needsConnectionAnswer(\"The root record is locked\");\n\n const record: StoredRecord = { type, id, rootId, resource, updatedAt: null, base: null, localOnly: true };\n await ctx.store.transaction(async (store) => {\n await store.putRecord(record);\n await linkRelationships(store, typeConfig, { type, id }, {}, resource.relationships ?? {});\n await store.enqueue({\n rootId,\n method: ctx.request.method,\n path: queuePath(ctx),\n body: ctx.request.body,\n base: null,\n });\n });\n\n return singleAnswer(ctx.store, record, 201);\n}\n\nasync function update(ctx: LocalAnswerContext, config: OfflineConfig, type: string): Promise<OfflineAnswer> {\n const id = ctx.params.id;\n const existing = await ctx.store.getRecord(type, id);\n if (!existing) return notOnDeviceAnswer(type, id);\n if (isRootWriteLocked(existing.rootId)) return needsConnectionAnswer(\"The root record is locked\");\n\n const data = ctx.request.body?.data ?? {};\n const before = existing.resource ?? { type, id };\n const resource = mergeResource(before, { type, id }, data);\n // A record created on this device has no server version to merge against.\n const base = existing.localOnly ? null : (existing.base ?? before);\n\n const record: StoredRecord = { ...existing, resource, base, localOnly: existing.localOnly };\n await ctx.store.transaction(async (store) => {\n await store.putRecord(record);\n await linkRelationships(\n store,\n typeConfigOf(config, type),\n { type, id },\n before.relationships ?? {},\n data.relationships ?? {},\n );\n await store.enqueue({\n rootId: existing.rootId,\n method: ctx.request.method,\n path: queuePath(ctx),\n body: ctx.request.body,\n recordType: type,\n recordId: id,\n base,\n });\n });\n\n return singleAnswer(ctx.store, record);\n}\n\nasync function remove(ctx: LocalAnswerContext, type: string): Promise<OfflineAnswer> {\n const id = ctx.params.id;\n const existing = await ctx.store.getRecord(type, id);\n if (!existing) return notOnDeviceAnswer(type, id);\n if (isRootWriteLocked(existing.rootId)) return needsConnectionAnswer(\"The root record is locked\");\n\n const base = existing.localOnly ? null : (existing.base ?? existing.resource);\n\n await ctx.store.transaction(async (store) => {\n const links = [...(await store.linksFrom(id)), ...(await store.linksTo(id))];\n for (const link of links) await store.deleteLink({ fromId: link.fromId, toId: link.toId, kind: link.kind });\n await store.deleteRecord(type, id);\n await store.enqueue({\n rootId: existing.rootId,\n method: \"DELETE\",\n path: queuePath(ctx),\n recordType: type,\n recordId: id,\n base,\n });\n });\n\n return { status: 204, body: undefined };\n}\n\n/** The request path as `EndpointCreator` built it, query string included. */\nexport function queuePath(ctx: Pick<LocalAnswerContext, \"request\">): string {\n const query = ctx.request.query.toString();\n return query ? `${ctx.request.path}?${query}` : ctx.request.path;\n}\n\n// ---------------------------------------------------------------------------------------------\n// The generic route list\n// ---------------------------------------------------------------------------------------------\n\nfunction readOnlyGuard(typeConfig: OfflineTypeConfig, answer: (ctx: LocalAnswerContext) => Promise<OfflineAnswer>) {\n return async (ctx: LocalAnswerContext): Promise<OfflineAnswer> =>\n typeConfig.readOnly ? needsConnectionAnswer(`${typeConfig.module.name} is read-only offline`) : answer(ctx);\n}\n\n/**\n * One set of routes per configured type, in matching order: the root record list before the\n * related list (both are `x/:id/<type>`), the related list last. These generic routes are not\n * queued by the layer: each write answer queues itself, with its base.\n */\nexport function buildGenericRoutes(config: OfflineConfig): OfflineRoute[] {\n const routes: OfflineRoute[] = [];\n const route = (method: OfflineMethod, pattern: OfflineRoute[\"pattern\"], answer: OfflineRoute[\"answer\"]) =>\n routes.push({ method, pattern, answer, queue: false });\n\n for (const typeConfig of config.types) {\n const type = typeConfig.module.name;\n route(\"GET\", [typeConfig.module, \":id\"], (ctx) => getOne(ctx, type));\n route(\"GET\", [config.rootModule, \":rootId\", typeConfig.module], (ctx) => rootList(ctx, config, typeConfig));\n route(\n \"POST\",\n [typeConfig.module],\n readOnlyGuard(typeConfig, (ctx) => create(ctx, config, type)),\n );\n route(\n \"PUT\",\n [typeConfig.module, \":id\"],\n readOnlyGuard(typeConfig, (ctx) => update(ctx, config, type)),\n );\n route(\n \"PATCH\",\n [typeConfig.module, \":id\"],\n readOnlyGuard(typeConfig, (ctx) => update(ctx, config, type)),\n );\n route(\n \"DELETE\",\n [typeConfig.module, \":id\"],\n readOnlyGuard(typeConfig, (ctx) => remove(ctx, type)),\n );\n }\n for (const typeConfig of config.types) {\n route(\"GET\", [\":parentType\", \":parentId\", typeConfig.module], (ctx) => relatedList(ctx, config, typeConfig));\n }\n\n return routes;\n}\n","import type { OfflineMethod, OfflineRoute, PatternSegment } from \"./types\";\n\nexport type RouteMatch = { route: OfflineRoute; params: Record<string, string> };\n\nfunction splitPath(path: string): string[] {\n const withoutQuery = path.split(\"?\")[0].split(\"#\")[0];\n return withoutQuery.split(\"/\").filter((segment) => segment.length > 0);\n}\n\n/** A Module segment may itself hold slashes (\"roots/played\"), so each one is expanded. */\nfunction expandPattern(pattern: PatternSegment[]): string[] {\n return pattern.flatMap((segment) => splitPath(typeof segment === \"string\" ? segment : segment.name));\n}\n\nexport function matchPattern(pattern: PatternSegment[], path: string): Record<string, string> | null {\n const expected = expandPattern(pattern);\n const actual = splitPath(path);\n if (expected.length !== actual.length) return null;\n\n const params: Record<string, string> = {};\n for (let index = 0; index < expected.length; index++) {\n const segment = expected[index];\n const value = decodeURIComponent(actual[index]);\n if (segment.startsWith(\":\")) {\n params[segment.slice(1)] = value;\n } else if (segment !== value) {\n return null;\n }\n }\n return params;\n}\n\n/** The first route, in the order given, whose method and pattern match. The query string is ignored. */\nexport function matchRoute(routes: OfflineRoute[], method: OfflineMethod, path: string): RouteMatch | null {\n for (const route of routes) {\n if (route.method !== method) continue;\n const params = matchPattern(route.pattern, path);\n if (params) return { route, params };\n }\n return null;\n}\n","import { getClientApiUrl } from \"../client/JsonApiClient\";\nimport type { ApiData } from \"../core/interfaces/ApiData\";\nimport { getApiUrl } from \"../unified/JsonApiRequest\";\nimport { getOfflineConfig, getOfflineStore } from \"./config\";\nimport {\n buildGenericRoutes,\n rootIdForResource,\n isRootWriteLocked,\n needsConnectionAnswer,\n queuePath,\n} from \"./generic-answers\";\nimport { matchRoute } from \"./router\";\nimport { setOfflineState } from \"./state\";\nimport type { DeviceStore } from \"./store/DeviceStore\";\nimport {\n NeedsConnectionError,\n type OfflineAnswer,\n type OfflineMethod,\n type OfflineRequest,\n type OfflineRoute,\n} from \"./types\";\n\nexport type LocalTransportRequest = {\n method: string;\n url: string;\n body?: any;\n files?: unknown;\n};\n\nconst METHODS: OfflineMethod[] = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\nfunction apiBases(): string[] {\n const bases: string[] = [];\n for (const read of [getApiUrl, getClientApiUrl]) {\n try {\n const base = read();\n if (base) bases.push(base);\n } catch {\n // not configured on this side\n }\n }\n return bases;\n}\n\n/** The path relative to the API base, as `EndpointCreator` built it, and its query. */\nexport function splitApiUrl(url: string): { path: string; query: URLSearchParams } {\n let relative: string | undefined;\n for (const base of apiBases()) {\n if (url.startsWith(base)) {\n relative = url.slice(base.length);\n break;\n }\n }\n if (relative === undefined) {\n try {\n const parsed = new URL(url);\n relative = `${parsed.pathname}${parsed.search}`;\n } catch {\n relative = url;\n }\n }\n\n const [pathPart, ...queryParts] = relative.split(\"?\");\n return {\n path: pathPart.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\"),\n query: new URLSearchParams(queryParts.join(\"?\")),\n };\n}\n\nfunction toApiData(answer: OfflineAnswer): ApiData {\n const ok = answer.status >= 200 && answer.status < 300;\n return {\n ok,\n status: answer.status,\n statusText: ok ? \"OK\" : answer.status === 503 ? \"Service Unavailable\" : \"Error\",\n data: answer.body,\n };\n}\n\n/** The root record a custom write belongs to: a `<root>/:param` capture, a stored record named by a param, or the body. */\nasync function rootIdForCustomWrite(\n store: DeviceStore,\n pattern: OfflineRequest,\n params: Record<string, string>,\n rootSegmentParam: string | undefined,\n): Promise<string | undefined> {\n const config = getOfflineConfig();\n if (rootSegmentParam && params[rootSegmentParam]) return params[rootSegmentParam];\n\n for (const value of Object.values(params)) {\n for (const typeConfig of config.types) {\n const stored = await store.getRecord(typeConfig.module.name, value);\n if (stored?.rootId) return stored.rootId;\n }\n }\n return rootIdForResource(store, config, pattern.body?.data);\n}\n\nasync function refreshPendingCount(store: DeviceStore): Promise<void> {\n const entries = await store.queue();\n setOfflineState({ pending: entries.filter((entry) => entry.status !== \"conflict\").length });\n}\n\n/**\n * Answers a request from the device copy: the app's custom routes first, then the generic ones.\n * The result has the `ApiData` shape `directFetch` returns, so `translateResponse` and\n * `rehydrate()` run unchanged.\n */\nexport async function localTransport(request: LocalTransportRequest): Promise<ApiData> {\n try {\n const method = request.method.toUpperCase() as OfflineMethod;\n if (!METHODS.includes(method)) return toApiData(needsConnectionAnswer());\n if (request.files) return toApiData(needsConnectionAnswer(\"Uploading files needs a connection\"));\n\n const config = getOfflineConfig();\n const { path, query } = splitApiUrl(request.url);\n const offlineRequest: OfflineRequest = { method, path, query, body: request.body, url: request.url };\n\n const custom = matchRoute(config.routes, method, path);\n const match = custom ?? matchRoute(buildGenericRoutes(config), method, path);\n if (!match) return toApiData(needsConnectionAnswer(`${method} ${path} has no offline answer`));\n\n let store: DeviceStore;\n try {\n store = await getOfflineStore();\n } catch (error) {\n // The device copy cannot be opened here (another tab holds the file, storage refused,\n // no signed-in user): only the server can answer.\n if (error instanceof NeedsConnectionError) return toApiData(needsConnectionAnswer(error.message));\n console.warn(\"[offline] the device copy could not be opened\", error);\n return toApiData(needsConnectionAnswer(\"The device copy is not available in this tab\"));\n }\n const ctx = { request: offlineRequest, params: match.params, store };\n\n // The root record of a custom write (a `<root>/:param` capture, a stored record, or the body).\n const customRootId = async (route: OfflineRoute): Promise<string | undefined> => {\n const rootIndex = route.pattern.findIndex(\n (segment) => typeof segment !== \"string\" && segment.name === config.rootModule.name,\n );\n const next = rootIndex >= 0 ? route.pattern[rootIndex + 1] : undefined;\n const rootParam = typeof next === \"string\" && next.startsWith(\":\") ? next.slice(1) : undefined;\n return rootIdForCustomWrite(store, offlineRequest, match.params, rootParam);\n };\n\n if (!custom || !custom.route.queue) {\n // A custom write that is not queued is refused on a locked root record too (GETs stay allowed).\n if (custom && method !== \"GET\") {\n const rootId = await customRootId(custom.route);\n if (rootId && isRootWriteLocked(rootId)) {\n return toApiData(needsConnectionAnswer(\"The root record is locked\"));\n }\n }\n const answer = await match.route.answer(ctx);\n if (method !== \"GET\") await refreshPendingCount(store);\n return toApiData(answer);\n }\n\n // A custom write the server must run too: refused on a locked root record, otherwise the answer\n // updates the device copy and the original request is queued in the same transaction.\n const rootId = await customRootId(custom.route);\n if (!rootId) return toApiData(needsConnectionAnswer(`${method} ${path} belongs to no root record on this device`));\n if (isRootWriteLocked(rootId)) return toApiData(needsConnectionAnswer(\"The root record is locked\"));\n\n const answer = await store.transaction(async (transactionStore) => {\n const result = await custom.route.answer({ ...ctx, store: transactionStore });\n if (result.status >= 200 && result.status < 300) {\n await transactionStore.enqueue({\n rootId,\n method,\n path: queuePath(ctx),\n body: request.body,\n base: null,\n });\n }\n return result;\n });\n await refreshPendingCount(store);\n return toApiData(answer);\n } catch (error) {\n if (error instanceof NeedsConnectionError) return toApiData(needsConnectionAnswer(error.message));\n console.error(\"[offline] local answer failed\", error);\n return toApiData({\n status: 500,\n body: {\n errors: [{ status: \"500\", title: \"Offline answer failed\", detail: String((error as Error)?.message ?? error) }],\n },\n });\n }\n}\n"],"mappings":"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","names":["rootId","answer"]}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
|
+
var _chunkMOGEGWXWjs = require('./chunk-MOGEGWXW.js');
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
var _chunkFF4EIQ6Hjs = require('./chunk-FF4EIQ6H.js');
|
|
7
7
|
|
|
8
|
+
var _chunkBZ5J2FIVjs = require('./chunk-BZ5J2FIV.js');
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
var _chunkQKNGZDSQjs = require('./chunk-QKNGZDSQ.js');
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
var _chunk7QVYU63Ejs = require('./chunk-7QVYU63E.js');
|
|
@@ -36,7 +38,7 @@ function configureJsonApi(config) {
|
|
|
36
38
|
_setStaticHelpContent(helpContent);
|
|
37
39
|
}
|
|
38
40
|
if (config.bootstrapper) {
|
|
39
|
-
|
|
41
|
+
_chunkQKNGZDSQjs.setBootstrapper.call(void 0, config.bootstrapper);
|
|
40
42
|
config.bootstrapper();
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -49,19 +51,19 @@ function getApiUrl() {
|
|
|
49
51
|
if (_optionalChain([_clientConfig, 'optionalAccess', _ => _.apiUrl])) {
|
|
50
52
|
return _clientConfig.apiUrl;
|
|
51
53
|
}
|
|
52
|
-
return
|
|
54
|
+
return _chunkQKNGZDSQjs.ENV.API_URL;
|
|
53
55
|
}
|
|
54
56
|
_chunk7QVYU63Ejs.__name.call(void 0, getApiUrl, "getApiUrl");
|
|
55
57
|
function getPublicApiUrl() {
|
|
56
|
-
return
|
|
58
|
+
return _chunkQKNGZDSQjs.ENV.API_URL;
|
|
57
59
|
}
|
|
58
60
|
_chunk7QVYU63Ejs.__name.call(void 0, getPublicApiUrl, "getPublicApiUrl");
|
|
59
61
|
function getAppUrl() {
|
|
60
62
|
if (_optionalChain([_clientConfig, 'optionalAccess', _2 => _2.appUrl])) {
|
|
61
63
|
return _clientConfig.appUrl;
|
|
62
64
|
}
|
|
63
|
-
if (
|
|
64
|
-
return
|
|
65
|
+
if (_chunkQKNGZDSQjs.ENV.APP_URL_ALTERNATE) {
|
|
66
|
+
return _chunkQKNGZDSQjs.ENV.APP_URL_ALTERNATE;
|
|
65
67
|
}
|
|
66
68
|
if (typeof window !== "undefined") {
|
|
67
69
|
return window.location.origin;
|
|
@@ -124,7 +126,7 @@ function getWaitlistConfig() {
|
|
|
124
126
|
}
|
|
125
127
|
_chunk7QVYU63Ejs.__name.call(void 0, getWaitlistConfig, "getWaitlistConfig");
|
|
126
128
|
function useWaitlistText() {
|
|
127
|
-
const t =
|
|
129
|
+
const t = _chunkBZ5J2FIVjs.useI18nTranslations.call(void 0, );
|
|
128
130
|
const translate = getWaitlistConfig().translate === true;
|
|
129
131
|
return (value) => {
|
|
130
132
|
if (!value) return void 0;
|
|
@@ -262,7 +264,7 @@ var COMPANY_ADMINISTRATOR_ROLE_ID = "2e1eee00-6cba-4506-9059-ccd24e4ea5b0";
|
|
|
262
264
|
var ACTION_TYPES = ["read", "create", "update", "delete"];
|
|
263
265
|
|
|
264
266
|
// src/features/rbac/data/RbacService.ts
|
|
265
|
-
var RbacService = class extends
|
|
267
|
+
var RbacService = class extends _chunkMOGEGWXWjs.AbstractService {
|
|
266
268
|
static {
|
|
267
269
|
_chunk7QVYU63Ejs.__name.call(void 0, this, "RbacService");
|
|
268
270
|
}
|
|
@@ -273,9 +275,9 @@ var RbacService = class extends _chunkFF4EIQ6Hjs.AbstractService {
|
|
|
273
275
|
* Dev-only endpoint — see class header.
|
|
274
276
|
*/
|
|
275
277
|
static async fetchMatrix() {
|
|
276
|
-
const endpoint = new (0,
|
|
278
|
+
const endpoint = new (0, _chunkBZ5J2FIVjs.EndpointCreator)({ endpoint: _chunkBZ5J2FIVjs.Modules.RbacMatrix }).generate();
|
|
277
279
|
const model = await this.callApi({
|
|
278
|
-
type:
|
|
280
|
+
type: _chunkBZ5J2FIVjs.Modules.RbacMatrix,
|
|
279
281
|
method: "GET" /* GET */,
|
|
280
282
|
endpoint
|
|
281
283
|
});
|
|
@@ -295,9 +297,9 @@ var RbacService = class extends _chunkFF4EIQ6Hjs.AbstractService {
|
|
|
295
297
|
* Dev-only endpoint — see class header.
|
|
296
298
|
*/
|
|
297
299
|
static async saveMatrix(args) {
|
|
298
|
-
const endpoint = new (0,
|
|
300
|
+
const endpoint = new (0, _chunkBZ5J2FIVjs.EndpointCreator)({ endpoint: _chunkBZ5J2FIVjs.Modules.RbacMatrix }).generate();
|
|
299
301
|
const model = await this.callApi({
|
|
300
|
-
type:
|
|
302
|
+
type: _chunkBZ5J2FIVjs.Modules.RbacMatrix,
|
|
301
303
|
method: "PUT" /* PUT */,
|
|
302
304
|
endpoint,
|
|
303
305
|
input: args
|
|
@@ -340,4 +342,4 @@ var RbacService = class extends _chunkFF4EIQ6Hjs.AbstractService {
|
|
|
340
342
|
|
|
341
343
|
|
|
342
344
|
exports._getStaticHelpContent = _getStaticHelpContent; exports.configureJsonApi = configureJsonApi; exports.configureClientConfig = configureClientConfig; exports.getApiUrl = getApiUrl; exports.getPublicApiUrl = getPublicApiUrl; exports.getAppUrl = getAppUrl; exports.getTrackablePages = getTrackablePages; exports.getStripePublishableKey = getStripePublishableKey; exports.configureLogin = configureLogin; exports.isDiscordAuthEnabled = isDiscordAuthEnabled; exports.isGoogleAuthEnabled = isGoogleAuthEnabled; exports.isInternalAuthEnabled = isInternalAuthEnabled; exports.isRegistrationAllowed = isRegistrationAllowed; exports.getRegistrationMode = getRegistrationMode; exports.configureWaitlist = configureWaitlist; exports.getWaitlistConfig = getWaitlistConfig; exports.useWaitlistText = useWaitlistText; exports.configureReferral = configureReferral; exports.getReferralConfig = getReferralConfig; exports.isReferralEnabled = isReferralEnabled; exports.SIGNUP_SOURCE_COOKIE = SIGNUP_SOURCE_COOKIE; exports.SIGNUP_SOURCE_COOKIE_MAX_AGE = SIGNUP_SOURCE_COOKIE_MAX_AGE; exports.SIGNUP_SOURCE_FIELDS = SIGNUP_SOURCE_FIELDS; exports.readSignupSourceFromLocation = readSignupSourceFromLocation; exports.captureSignupSource = captureSignupSource; exports.getSignupSource = getSignupSource; exports.COMPANY_ADMINISTRATOR_ROLE_ID = COMPANY_ADMINISTRATOR_ROLE_ID; exports.ACTION_TYPES = ACTION_TYPES; exports.RbacService = RbacService;
|
|
343
|
-
//# sourceMappingURL=chunk-
|
|
345
|
+
//# sourceMappingURL=chunk-7ZHTPFFT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-7ZHTPFFT.js","../src/core/registry/helpStore.ts","../src/client/config.ts","../src/login/config.ts","../src/features/waitlist/config/waitlist.config.ts","../src/features/referral/config.ts","../src/features/signup-source/signup-source.ts","../src/features/rbac/data/RbacTypes.ts","../src/features/rbac/data/RbacService.ts"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-7ZHTPFFT.js","sourcesContent":[null,"/**\n * Centralized help-content store accessible from client and server contexts.\n * Uses a globalThis Symbol key to persist across HMR/Turbopack reloads and to\n * bridge between the client-side `configureJsonApi` (in `client/config.ts`)\n * and the HelpProvider context. Holds the brand-only HelpContentConfig.\n *\n * NO external dependencies to avoid circular imports.\n *\n * Pattern mirrors `bootstrapStore.ts`.\n */\n\nconst HELP_CONTENT_KEY = Symbol.for(\"nextjs-jsonapi:helpContent\");\n\nconst globalStore = globalThis as unknown as {\n [HELP_CONTENT_KEY]?: unknown | null;\n};\n\nif (globalStore[HELP_CONTENT_KEY] === undefined) {\n globalStore[HELP_CONTENT_KEY] = null;\n}\n\nexport function _setStaticHelpContent(cfg: unknown | null): void {\n globalStore[HELP_CONTENT_KEY] = cfg;\n}\n\nexport function _getStaticHelpContent<T = unknown>(): T | null {\n return (globalStore[HELP_CONTENT_KEY] as T | null) ?? null;\n}\n","\"use client\";\n\nimport { ModuleWithPermissions } from \"../permissions/types\";\nimport { ENV } from \"../core/env\";\nimport { setBootstrapper } from \"../core/registry/bootstrapStore\";\nimport { _setStaticHelpContent } from \"../core/registry/helpStore\";\nimport type { HelpContentConfig } from \"../features/help/interfaces/help-content-config.interface\";\n\n// Config storage for client-side contexts\nlet _clientConfig: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n stripePublishableKey?: string;\n} | null = null;\n\n/**\n * Configure the JSON:API client. This is the main configuration function.\n * This is typically called during app initialization.\n * @param config.helpContent - Optional help-content config (brand only). Forwarded to the help feature's globalThis-backed store; not stored on the client config.\n */\nexport function configureJsonApi(config: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n stripePublishableKey?: string;\n helpContent?: HelpContentConfig;\n}): void {\n const { helpContent, ...rest } = config;\n _clientConfig = rest;\n if (helpContent) {\n _setStaticHelpContent(helpContent);\n }\n // Register and call bootstrapper to register all modules\n if (config.bootstrapper) {\n setBootstrapper(config.bootstrapper);\n config.bootstrapper();\n }\n}\n\n/**\n * Configure the client config. This is typically called during app initialization.\n * @deprecated Use configureJsonApi instead\n */\nexport function configureClientConfig(config: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n}): void {\n _clientConfig = config;\n}\n\n/**\n * Get the configured API URL.\n *\n * This may resolve to an internal/private host (e.g. a docker-network\n * hostname) when configured to do so for SSR fetches. Do NOT use this for\n * URLs that are rendered into HTML and followed by the browser — use\n * `getPublicApiUrl()` instead.\n */\nexport function getApiUrl(): string {\n if (_clientConfig?.apiUrl) {\n return _clientConfig.apiUrl;\n }\n return ENV.API_URL;\n}\n\n/**\n * Get the public-facing API URL.\n *\n * Always sourced from `NEXT_PUBLIC_API_URL` so it is identical on server\n * and client, and reachable from the user's browser. Use for any URL that\n * gets rendered into HTML the browser will navigate to (links, redirects,\n * OAuth hrefs).\n */\nexport function getPublicApiUrl(): string {\n return ENV.API_URL;\n}\n\n/**\n * Get the configured app URL.\n */\nexport function getAppUrl(): string {\n if (_clientConfig?.appUrl) {\n return _clientConfig.appUrl;\n }\n if (ENV.APP_URL_ALTERNATE) {\n return ENV.APP_URL_ALTERNATE;\n }\n if (typeof window !== \"undefined\") {\n return window.location.origin;\n }\n return \"\";\n}\n\n/**\n * Get the configured trackable pages.\n */\nexport function getTrackablePages(): ModuleWithPermissions[] {\n return _clientConfig?.trackablePages ?? [];\n}\n\n/**\n * Get the configured Stripe publishable key.\n */\nexport function getStripePublishableKey(): string | undefined {\n return _clientConfig?.stripePublishableKey;\n}\n","let _useDiscordAuth: boolean = false;\nlet _useGoogleAuth: boolean = false;\nlet _useInternalAuth: boolean = true;\nlet _allowRegistration: boolean = true;\nlet _registrationMode: \"open\" | \"closed\" | \"waitlist\" = \"open\";\n\nexport type RegistrationMode = \"open\" | \"closed\" | \"waitlist\";\n\nexport interface LoginConfig {\n discordClientId?: string;\n googleClientId?: string;\n useInternalAuth?: boolean;\n allowRegistration?: boolean;\n registrationMode?: RegistrationMode;\n}\n\nexport function configureLogin(params: LoginConfig): void {\n _useDiscordAuth = !!params.discordClientId;\n _useGoogleAuth = !!params.googleClientId;\n _useInternalAuth = params.useInternalAuth ?? true;\n _allowRegistration = params.allowRegistration ?? true;\n _registrationMode = params.registrationMode ?? \"open\";\n}\n\nexport function isDiscordAuthEnabled(): boolean {\n return _useDiscordAuth;\n}\n\nexport function isGoogleAuthEnabled(): boolean {\n return _useGoogleAuth;\n}\n\nexport function isInternalAuthEnabled(): boolean {\n return _useInternalAuth;\n}\n\nexport function isRegistrationAllowed(): boolean {\n return _allowRegistration;\n}\n\nexport function getRegistrationMode(): RegistrationMode {\n return _registrationMode;\n}\n","import { useI18nTranslations } from \"../../../i18n\";\n\nexport type QuestionnaireFieldType = \"text\" | \"textarea\" | \"select\" | \"checkbox\";\n\nexport interface QuestionnaireOption {\n value: string;\n label: string;\n description?: string;\n}\n\nexport interface QuestionnaireField {\n id: string;\n type: QuestionnaireFieldType;\n label: string;\n description?: string;\n placeholder?: string;\n required?: boolean;\n options?: QuestionnaireOption[];\n}\n\nexport interface WaitlistConfig {\n questionnaire?: QuestionnaireField[];\n heroTitle?: string;\n heroSubtitle?: string;\n heroDescription?: string;\n benefits?: string[];\n /**\n * When true, every text above (hero title, subtitle, description, benefits and\n * the questionnaire's labels, descriptions, placeholders and option labels) is\n * a message key and renders through `t()`. Defaults to false: the strings\n * render as given.\n */\n translate?: boolean;\n}\n\nlet _waitlistConfig: WaitlistConfig = {};\n\nexport function configureWaitlist(config: WaitlistConfig): void {\n _waitlistConfig = config;\n}\n\nexport function getWaitlistConfig(): WaitlistConfig {\n return _waitlistConfig;\n}\n\n/**\n * Resolves a waitlist config text: `t(value)` when the config sets\n * `translate`, the value itself otherwise. Empty values stay undefined.\n */\nexport function useWaitlistText(): (value?: string) => string | undefined {\n const t = useI18nTranslations();\n const translate = getWaitlistConfig().translate === true;\n\n return (value?: string) => {\n if (!value) return undefined;\n return translate ? t(value) : value;\n };\n}\n","/**\n * Configuration interface for frontend referral feature.\n */\nexport interface ReferralConfig {\n /**\n * Whether the referral feature is enabled.\n * When false, components render nothing and hooks return null.\n * @default false\n */\n enabled?: boolean;\n\n /**\n * Name of the cookie used to store referral codes.\n * @default \"referral_code\"\n */\n cookieName?: string;\n\n /**\n * Number of days the referral cookie is valid.\n * @default 30\n */\n cookieDays?: number;\n\n /**\n * Query parameter name for referral code in URL.\n * @default \"ref\"\n */\n urlParamName?: string;\n\n /**\n * Base URL for referral links.\n * @default window.location.origin (client-side only)\n */\n referralUrlBase?: string;\n\n /**\n * Path to append to base URL for referral links.\n * @default \"/\"\n */\n referralPath?: string;\n}\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_REFERRAL_CONFIG: Required<ReferralConfig> = {\n enabled: false,\n cookieName: \"referral_code\",\n cookieDays: 30,\n urlParamName: \"ref\",\n referralUrlBase: \"\",\n referralPath: \"/\",\n};\n\n// Private storage for configuration\nlet _referralConfig: Required<ReferralConfig> = { ...DEFAULT_REFERRAL_CONFIG };\n\n/**\n * Configure referral feature settings.\n * Call this at app startup to enable and configure referral functionality.\n *\n * @example\n * ```typescript\n * import { configureReferral } from \"@carlonicora/nextjs-jsonapi\";\n *\n * configureReferral({\n * enabled: process.env.NEXT_PUBLIC_REFERRAL_ENABLED === 'true',\n * cookieDays: 30,\n * });\n * ```\n */\nexport function configureReferral(config: ReferralConfig): void {\n _referralConfig = { ...DEFAULT_REFERRAL_CONFIG, ...config };\n}\n\n/**\n * Get the current referral configuration.\n * @internal\n */\nexport function getReferralConfig(): Required<ReferralConfig> {\n return _referralConfig;\n}\n\n/**\n * Check if referral feature is enabled.\n */\nexport function isReferralEnabled(): boolean {\n return _referralConfig.enabled;\n}\n","export type SignupSource = {\n sourceRef?: string;\n sourceUtmSource?: string;\n sourceUtmMedium?: string;\n sourceUtmCampaign?: string;\n sourceReferrer?: string;\n sourceLandingPath?: string;\n};\n\nexport const SIGNUP_SOURCE_COOKIE = \"signup_source\";\nexport const SIGNUP_SOURCE_COOKIE_MAX_AGE = 2592000;\n\n/** The six source fields, in display order. */\nexport const SIGNUP_SOURCE_FIELDS: readonly (keyof SignupSource)[] = [\n \"sourceRef\",\n \"sourceUtmSource\",\n \"sourceUtmMedium\",\n \"sourceUtmCampaign\",\n \"sourceReferrer\",\n \"sourceLandingPath\",\n];\n\nconst SIGNUP_SOURCE_MAX_LENGTH = 200;\n\n// First touch for the current page session. Survives client navigations, lost on a full reload.\nlet memorySource: SignupSource | undefined;\n\nfunction clean(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n return trimmed.slice(0, SIGNUP_SOURCE_MAX_LENGTH);\n}\n\nfunction sanitise(input: unknown): SignupSource | undefined {\n if (!input || typeof input !== \"object\") return undefined;\n const result: SignupSource = {};\n for (const field of SIGNUP_SOURCE_FIELDS) {\n const value = clean((input as Record<string, unknown>)[field]);\n if (value !== undefined) result[field] = value;\n }\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n/**\n * Pure. Reads ref and utm_source/medium/campaign from the URL, the referrer host when it is not ownHost,\n * and the landing path. Returns undefined for a direct visit (no ref, no UTM, no external referrer).\n */\nexport function readSignupSourceFromLocation(params: {\n url: string;\n referrer: string;\n ownHost: string;\n}): SignupSource | undefined {\n let url: URL;\n try {\n url = new URL(params.url);\n } catch {\n return undefined;\n }\n\n const sourceRef = clean(url.searchParams.get(\"ref\"));\n const sourceUtmSource = clean(url.searchParams.get(\"utm_source\"));\n const sourceUtmMedium = clean(url.searchParams.get(\"utm_medium\"));\n const sourceUtmCampaign = clean(url.searchParams.get(\"utm_campaign\"));\n\n let sourceReferrer: string | undefined;\n if (params.referrer) {\n try {\n const referrerHost = new URL(params.referrer).host;\n if (referrerHost && referrerHost !== params.ownHost) sourceReferrer = clean(referrerHost);\n } catch {\n sourceReferrer = undefined;\n }\n }\n\n if (!sourceRef && !sourceUtmSource && !sourceUtmMedium && !sourceUtmCampaign && !sourceReferrer) return undefined;\n\n return sanitise({\n sourceRef,\n sourceUtmSource,\n sourceUtmMedium,\n sourceUtmCampaign,\n sourceReferrer,\n sourceLandingPath: url.pathname,\n });\n}\n\nfunction readSignupSourceCookie(): SignupSource | undefined {\n if (typeof document === \"undefined\") return undefined;\n const cookies = document.cookie ? document.cookie.split(\"; \") : [];\n for (const cookie of cookies) {\n const separator = cookie.indexOf(\"=\");\n if (separator === -1) continue;\n if (cookie.slice(0, separator) !== SIGNUP_SOURCE_COOKIE) continue;\n const value = cookie.slice(separator + 1);\n if (!value) return undefined;\n try {\n return sanitise(JSON.parse(decodeURIComponent(value)));\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\nfunction writeSignupSourceCookie(source: SignupSource): void {\n document.cookie = `${SIGNUP_SOURCE_COOKIE}=${encodeURIComponent(JSON.stringify(source))}; path=/; max-age=${SIGNUP_SOURCE_COOKIE_MAX_AGE}; SameSite=Lax`;\n}\n\n/**\n * First touch: an existing in-memory value or cookie is never overwritten.\n * Writes the cookie only when persist is true and no cookie exists yet.\n */\nexport function captureSignupSource(params: { persist: boolean }): void {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return;\n\n const existingCookie = readSignupSourceCookie();\n\n if (!memorySource) {\n memorySource =\n existingCookie ??\n readSignupSourceFromLocation({\n url: window.location.href,\n referrer: document.referrer,\n ownHost: window.location.host,\n });\n }\n\n if (params.persist && !existingCookie && memorySource) writeSignupSourceCookie(memorySource);\n}\n\n/** The in-memory value, else the cookie value, else undefined. */\nexport function getSignupSource(): SignupSource | undefined {\n return memorySource ?? readSignupSourceCookie();\n}\n","export const COMPANY_ADMINISTRATOR_ROLE_ID = \"2e1eee00-6cba-4506-9059-ccd24e4ea5b0\";\n\nexport type PermissionValue = boolean | string;\n\nexport type ActionType = \"read\" | \"create\" | \"update\" | \"delete\";\n\nexport const ACTION_TYPES: ActionType[] = [\"read\", \"create\", \"update\", \"delete\"];\n\n/** The permissions object shape used by both Module and PermissionMapping entities */\nexport type PermissionsMap = {\n create?: PermissionValue;\n read?: PermissionValue;\n update?: PermissionValue;\n delete?: PermissionValue;\n};\n\n/**\n * Declarative-RBAC matrix types.\n *\n * Mirror of the library types defined in\n * `packages/nestjs-neo4jsonapi/src/foundations/rbac/dsl/types.ts`.\n * Frontend does not import from backend, so the shape is redefined here.\n *\n * A `PermToken` represents a single permission entry:\n * - `scope: true` → unconditional (e.g. full read of the module)\n * - `scope: false` → nothing (rarely used, mostly a placeholder)\n * - `scope: \"path\"` → scoped by relationship path (e.g. \"orders.account\")\n */\nexport type PermToken = { action: string; scope: boolean | string };\n\n/**\n * A per-module block of the matrix. Always has a `default` row (permissions\n * granted to every role). Additional keys are role IDs → role-specific\n * permission tokens that are unioned with `default` to produce the effective\n * permissions for that role in that module.\n */\nexport type RbacModuleBlock = { default: PermToken[] } & Record<string, PermToken[]>;\n\n/**\n * The full RBAC matrix as served by the dev endpoint `GET /_dev/rbac/matrix`.\n * Keys are module IDs; values are module blocks.\n */\nexport type RbacMatrix = Record<string, RbacModuleBlock>;\n","import { AbstractService, EndpointCreator, HttpMethod, Modules } from \"../../../core\";\nimport type { RbacMatrixModel } from \"./RbacMatrixModel\";\nimport type { RbacMatrix } from \"./RbacTypes\";\n\n/**\n * RbacService — fetches RBAC configuration for the admin UI.\n *\n * Declarative-matrix methods (`fetchMatrix`, `saveMatrix`) talk to the\n * dev-only endpoints added in\n * `packages/nestjs-neo4jsonapi/.../rbac-dev.controller.ts`. The controller\n * speaks JSON:API (singleton resource with `type: \"rbac-matrix\"`, `id:\n * \"singleton\"`), so these methods go through the standard `callApi()`\n * pipeline like every other service in the codebase.\n *\n * The backend only registers these routes when `devMode` is enabled on\n * `RbacModule.register` (see `apps/api/src/features/features.modules.ts`).\n * In production the routes return 404; callers should guard with a dev-mode\n * check.\n */\nexport class RbacService extends AbstractService {\n /**\n * Fetch the current RBAC matrix plus each module's known BFS relationship\n * paths (used by the permission picker as scope suggestions).\n *\n * Dev-only endpoint — see class header.\n */\n static async fetchMatrix(): Promise<{\n matrix: RbacMatrix;\n modulePaths: Record<string, readonly string[]>;\n }> {\n const endpoint = new EndpointCreator({ endpoint: Modules.RbacMatrix }).generate();\n\n const model = await this.callApi<RbacMatrixModel>({\n type: Modules.RbacMatrix,\n method: HttpMethod.GET,\n endpoint,\n });\n\n return {\n matrix: model.matrix ?? {},\n modulePaths: model.modulePaths ?? {},\n };\n }\n\n /**\n * Persist a matrix back to the declarative `permissions.ts` file.\n *\n * The backend serializes the matrix to formatted TypeScript using the\n * provided `roleNames` / `moduleNames` lookup tables (so the emitted file\n * references `RoleId.X` / `ModuleId.X` rather than raw UUIDs) and writes\n * it to `outputPath` (absolute, or relative to the repo root).\n *\n * Dev-only endpoint — see class header.\n */\n static async saveMatrix(args: {\n matrix: RbacMatrix;\n roleNames: Record<string, string>;\n moduleNames: Record<string, string>;\n outputPath: string;\n }): Promise<{ bytesWritten: number; path: string }> {\n const endpoint = new EndpointCreator({ endpoint: Modules.RbacMatrix }).generate();\n\n const model = await this.callApi<RbacMatrixModel>({\n type: Modules.RbacMatrix,\n method: HttpMethod.PUT,\n endpoint,\n input: args,\n });\n\n return {\n bytesWritten: model.bytesWritten ?? 0,\n path: model.path ?? \"\",\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } async function _asyncOptionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
var _chunkPXDUJ47Njs = require('./chunk-PXDUJ47N.js');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
var _chunkW62MHDWMjs = require('./chunk-W62MHDWM.js');
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
var _chunkMHB36MYEjs = require('./chunk-MHB36MYE.js');
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
var _chunk7QVYU63Ejs = require('./chunk-7QVYU63E.js');
|
|
23
|
+
|
|
24
|
+
// src/offline/sync/known-roots.ts
|
|
25
|
+
var _lastSyncRoots = [];
|
|
26
|
+
function rememberSyncRoots(ids) {
|
|
27
|
+
_lastSyncRoots = [...ids];
|
|
28
|
+
}
|
|
29
|
+
_chunk7QVYU63Ejs.__name.call(void 0, rememberSyncRoots, "rememberSyncRoots");
|
|
30
|
+
async function knownRootIds(store) {
|
|
31
|
+
const config = _chunkMHB36MYEjs.getOfflineConfig.call(void 0, );
|
|
32
|
+
const ids = new Set(_lastSyncRoots);
|
|
33
|
+
for (const record of await store.listRecords({ type: config.rootModule.name })) ids.add(record.id);
|
|
34
|
+
for (const entry of await store.queue()) ids.add(entry.rootId);
|
|
35
|
+
return [...ids].sort();
|
|
36
|
+
}
|
|
37
|
+
_chunk7QVYU63Ejs.__name.call(void 0, knownRootIds, "knownRootIds");
|
|
38
|
+
async function refreshLockedRoots() {
|
|
39
|
+
const config = _chunkMHB36MYEjs.getOfflineConfig.call(void 0, );
|
|
40
|
+
if (!config.getUserId()) return;
|
|
41
|
+
const store = await _chunkMHB36MYEjs.getOfflineStore.call(void 0, );
|
|
42
|
+
const now = /* @__PURE__ */ new Date();
|
|
43
|
+
const locked = [];
|
|
44
|
+
for (const id of await knownRootIds(store)) {
|
|
45
|
+
if (_chunkPXDUJ47Njs.isRootLocked.call(void 0, await store.getSyncState(id), now)) locked.push(id);
|
|
46
|
+
}
|
|
47
|
+
_chunkMHB36MYEjs.setOfflineState.call(void 0, { lockedRootIds: locked });
|
|
48
|
+
}
|
|
49
|
+
_chunk7QVYU63Ejs.__name.call(void 0, refreshLockedRoots, "refreshLockedRoots");
|
|
50
|
+
|
|
51
|
+
// src/offline/sync/waiting.ts
|
|
52
|
+
var recordKeyOf = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, (type, id) => `${type}:${id}`, "recordKeyOf");
|
|
53
|
+
function recordKeysOfEntry(entry, typeNames) {
|
|
54
|
+
const keys = [];
|
|
55
|
+
if (entry.recordType && entry.recordId) keys.push(recordKeyOf(entry.recordType, entry.recordId));
|
|
56
|
+
const data = _optionalChain([entry, 'access', _ => _.body, 'optionalAccess', _2 => _2.data]);
|
|
57
|
+
if (data && !Array.isArray(data) && typeof data.type === "string" && typeof data.id === "string") {
|
|
58
|
+
keys.push(recordKeyOf(data.type, data.id));
|
|
59
|
+
}
|
|
60
|
+
const segments = String(_nullishCoalesce(entry.path, () => ( ""))).split("?")[0].split("/").filter(Boolean);
|
|
61
|
+
for (let index = segments.length - 1; index >= 0; index--) {
|
|
62
|
+
if (!typeNames.has(segments[index])) continue;
|
|
63
|
+
if (index + 1 < segments.length) keys.push(recordKeyOf(segments[index], segments[index + 1]));
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return keys;
|
|
67
|
+
}
|
|
68
|
+
_chunk7QVYU63Ejs.__name.call(void 0, recordKeysOfEntry, "recordKeysOfEntry");
|
|
69
|
+
function waitingRecordKeys(queue, typeNames) {
|
|
70
|
+
const keys = /* @__PURE__ */ new Set();
|
|
71
|
+
for (const entry of queue) for (const key of recordKeysOfEntry(entry, typeNames)) keys.add(key);
|
|
72
|
+
return keys;
|
|
73
|
+
}
|
|
74
|
+
_chunk7QVYU63Ejs.__name.call(void 0, waitingRecordKeys, "waitingRecordKeys");
|
|
75
|
+
|
|
76
|
+
// src/offline/write-through.ts
|
|
77
|
+
function updatedAtOf(resource) {
|
|
78
|
+
const value = _optionalChain([resource, 'optionalAccess', _3 => _3.attributes, 'optionalAccess', _4 => _4.updatedAt]);
|
|
79
|
+
if (!value) return null;
|
|
80
|
+
const date = new Date(value);
|
|
81
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
82
|
+
}
|
|
83
|
+
_chunk7QVYU63Ejs.__name.call(void 0, updatedAtOf, "updatedAtOf");
|
|
84
|
+
function identifiersOf(relationship) {
|
|
85
|
+
const data = _optionalChain([relationship, 'optionalAccess', _5 => _5.data]);
|
|
86
|
+
if (!data) return [];
|
|
87
|
+
return (Array.isArray(data) ? data : [data]).filter((item) => item && item.type && item.id);
|
|
88
|
+
}
|
|
89
|
+
_chunk7QVYU63Ejs.__name.call(void 0, identifiersOf, "identifiersOf");
|
|
90
|
+
async function updateLinks(store, typeConfig, record, before, after) {
|
|
91
|
+
const otherEnd = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, (link) => link.fromId === record.id ? link.toId : link.fromId, "otherEnd");
|
|
92
|
+
const allLinks = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, async () => [
|
|
93
|
+
...await store.linksFrom(record.id),
|
|
94
|
+
...await store.linksTo(record.id)
|
|
95
|
+
], "allLinks");
|
|
96
|
+
for (const [name, relationship] of Object.entries(_nullishCoalesce(after, () => ( {})))) {
|
|
97
|
+
if (!relationship || !("data" in relationship)) continue;
|
|
98
|
+
const next = new Map(identifiersOf(relationship).map((identifier) => [identifier.id, identifier]));
|
|
99
|
+
const previous = new Set(identifiersOf(_optionalChain([before, 'optionalAccess', _6 => _6[name]])).map((identifier) => identifier.id));
|
|
100
|
+
const kinds = _nullishCoalesce(_chunkW62MHDWMjs.linkKindsOf.call(void 0, typeConfig, name), () => ( [name]));
|
|
101
|
+
const ofRelationship = await _chunkW62MHDWMjs.linksOfKind.call(void 0, store, record.id, kinds, "both");
|
|
102
|
+
const mapped = _chunkW62MHDWMjs.linkKindsOf.call(void 0, typeConfig, name) !== void 0;
|
|
103
|
+
const stale = [
|
|
104
|
+
...ofRelationship.filter((link) => !next.has(otherEnd(link))),
|
|
105
|
+
// Unmapped: the link to an identifier the record held before may carry any kind.
|
|
106
|
+
...mapped ? [] : (await allLinks()).filter((link) => previous.has(otherEnd(link)) && !next.has(otherEnd(link)))
|
|
107
|
+
];
|
|
108
|
+
for (const link of stale) await store.deleteLink({ fromId: link.fromId, toId: link.toId, kind: link.kind });
|
|
109
|
+
const linked = new Set(
|
|
110
|
+
(mapped ? await _chunkW62MHDWMjs.linksOfKind.call(void 0, store, record.id, kinds, "both") : await allLinks()).map(otherEnd)
|
|
111
|
+
);
|
|
112
|
+
for (const [id, identifier] of next) {
|
|
113
|
+
if (linked.has(id)) continue;
|
|
114
|
+
await store.putLink({
|
|
115
|
+
fromType: record.type,
|
|
116
|
+
fromId: record.id,
|
|
117
|
+
toType: identifier.type,
|
|
118
|
+
toId: id,
|
|
119
|
+
kind: _chunkW62MHDWMjs.linkKindFor.call(void 0, typeConfig, name),
|
|
120
|
+
props: {}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
_chunk7QVYU63Ejs.__name.call(void 0, updateLinks, "updateLinks");
|
|
126
|
+
async function storeResource(store, resource, pathRootId, waiting, kept, primary, images) {
|
|
127
|
+
const config = _chunkMHB36MYEjs.getOfflineConfig.call(void 0, );
|
|
128
|
+
if (!_optionalChain([resource, 'optionalAccess', _7 => _7.type]) || !_optionalChain([resource, 'optionalAccess', _8 => _8.id])) return;
|
|
129
|
+
if (!config.types.some((typeConfig) => typeConfig.module.name === resource.type)) return;
|
|
130
|
+
if (waiting.has(recordKeyOf(resource.type, resource.id))) return;
|
|
131
|
+
const existing = await store.getRecord(resource.type, resource.id);
|
|
132
|
+
if (existing && existing.base !== null) return;
|
|
133
|
+
const rootId = await _asyncNullishCoalesce(await _asyncNullishCoalesce(await _asyncOptionalChain([existing, 'optionalAccess', async _9 => _9.rootId]), async () => ( await _chunkW62MHDWMjs.rootIdForResource.call(void 0, store, config, resource))), async () => ( pathRootId));
|
|
134
|
+
if (!rootId) return;
|
|
135
|
+
if (!kept.has(rootId)) return;
|
|
136
|
+
const before = _nullishCoalesce(_optionalChain([existing, 'optionalAccess', _10 => _10.resource]), () => ( {}));
|
|
137
|
+
const merged = {
|
|
138
|
+
...before,
|
|
139
|
+
...resource,
|
|
140
|
+
attributes: { ..._nullishCoalesce(before.attributes, () => ( {})), ..._nullishCoalesce(resource.attributes, () => ( {})) },
|
|
141
|
+
relationships: { ..._nullishCoalesce(before.relationships, () => ( {})), ..._nullishCoalesce(resource.relationships, () => ( {})) }
|
|
142
|
+
};
|
|
143
|
+
await store.putRecord({
|
|
144
|
+
type: resource.type,
|
|
145
|
+
id: resource.id,
|
|
146
|
+
rootId,
|
|
147
|
+
resource: merged,
|
|
148
|
+
updatedAt: _nullishCoalesce(_nullishCoalesce(updatedAtOf(resource), () => ( _optionalChain([existing, 'optionalAccess', _11 => _11.updatedAt]))), () => ( null)),
|
|
149
|
+
base: null,
|
|
150
|
+
localOnly: false
|
|
151
|
+
});
|
|
152
|
+
if (primary) {
|
|
153
|
+
const typeConfig = config.types.find((candidate) => candidate.module.name === resource.type);
|
|
154
|
+
await updateLinks(store, typeConfig, resource, before.relationships, resource.relationships);
|
|
155
|
+
}
|
|
156
|
+
const url = _chunkPXDUJ47Njs.imageUrlOf.call(void 0, merged);
|
|
157
|
+
const previousUrl = _chunkPXDUJ47Njs.imageUrlOf.call(void 0, _optionalChain([existing, 'optionalAccess', _12 => _12.resource]));
|
|
158
|
+
if (url && url !== previousUrl) images.push({ url, previousUrl });
|
|
159
|
+
}
|
|
160
|
+
_chunk7QVYU63Ejs.__name.call(void 0, storeResource, "storeResource");
|
|
161
|
+
async function writeThrough(request, response) {
|
|
162
|
+
const method = request.method.toUpperCase();
|
|
163
|
+
if (method === "GET" || !response.ok) return;
|
|
164
|
+
try {
|
|
165
|
+
const config = _chunkMHB36MYEjs.getOfflineConfig.call(void 0, );
|
|
166
|
+
if (!config.getUserId()) return;
|
|
167
|
+
const { path } = _chunkW62MHDWMjs.splitApiUrl.call(void 0, request.url);
|
|
168
|
+
const store = await _chunkMHB36MYEjs.getOfflineStore.call(void 0, );
|
|
169
|
+
if (method === "DELETE") {
|
|
170
|
+
for (const typeConfig of config.types) {
|
|
171
|
+
const params = _chunkW62MHDWMjs.matchPattern.call(void 0, [typeConfig.module, ":id"], path);
|
|
172
|
+
if (params) {
|
|
173
|
+
await store.deleteRecord(typeConfig.module.name, params.id);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const rootParams = _chunkW62MHDWMjs.matchPattern.call(void 0, [config.rootModule, ":rootId"], path.split("/").slice(0, 2).join("/"));
|
|
180
|
+
const pathRootId = _optionalChain([rootParams, 'optionalAccess', _13 => _13.rootId]);
|
|
181
|
+
const document = response.data;
|
|
182
|
+
const primary = Array.isArray(_optionalChain([document, 'optionalAccess', _14 => _14.data])) ? document.data : _optionalChain([document, 'optionalAccess', _15 => _15.data]) ? [document.data] : [];
|
|
183
|
+
const waiting = waitingRecordKeys(
|
|
184
|
+
await store.queue(),
|
|
185
|
+
new Set(config.types.map((typeConfig) => typeConfig.module.name))
|
|
186
|
+
);
|
|
187
|
+
const kept = new Set(await knownRootIds(store));
|
|
188
|
+
const images = [];
|
|
189
|
+
await store.transaction(async (transactionStore) => {
|
|
190
|
+
for (const resource of primary) {
|
|
191
|
+
await storeResource(transactionStore, resource, pathRootId, waiting, kept, true, images);
|
|
192
|
+
}
|
|
193
|
+
for (const resource of _nullishCoalesce(_optionalChain([document, 'optionalAccess', _16 => _16.included]), () => ( []))) {
|
|
194
|
+
await storeResource(transactionStore, resource, pathRootId, waiting, kept, false, images);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
for (const image of images) await _chunkPXDUJ47Njs.cacheArtworkImage.call(void 0, image.url, image.previousUrl);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error instanceof _chunkMHB36MYEjs.OfflineUnavailableError) return;
|
|
200
|
+
console.warn("[offline] could not copy the answer to the device", error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
_chunk7QVYU63Ejs.__name.call(void 0, writeThrough, "writeThrough");
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
exports.rememberSyncRoots = rememberSyncRoots; exports.knownRootIds = knownRootIds; exports.refreshLockedRoots = refreshLockedRoots; exports.recordKeyOf = recordKeyOf; exports.recordKeysOfEntry = recordKeysOfEntry; exports.waitingRecordKeys = waitingRecordKeys; exports.writeThrough = writeThrough;
|
|
214
|
+
//# sourceMappingURL=chunk-B6JGKDP2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-B6JGKDP2.js","../src/offline/sync/known-roots.ts","../src/offline/sync/waiting.ts","../src/offline/write-through.ts"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-B6JGKDP2.js","sourcesContent":[null,"import { getOfflineConfig, getOfflineStore } from \"../config\";\nimport { setOfflineState } from \"../state\";\nimport type { DeviceStore } from \"../store/DeviceStore\";\nimport { isRootLocked } from \"../store/lifecycle\";\n\nlet _lastSyncRoots: string[] = [];\n\n/** Remembers the root records the last online `listSyncRoots()` returned. */\nexport function rememberSyncRoots(ids: string[]): void {\n _lastSyncRoots = [...ids];\n}\n\n/**\n * Every root record the device knows of: its stored root records, the root records with waiting\n * changes, and the last sync roots (a locked root record is never downloaded, so it may have\n * no stored record).\n */\nexport async function knownRootIds(store: DeviceStore): Promise<string[]> {\n const config = getOfflineConfig();\n const ids = new Set<string>(_lastSyncRoots);\n for (const record of await store.listRecords({ type: config.rootModule.name })) ids.add(record.id);\n for (const entry of await store.queue()) ids.add(entry.rootId);\n return [...ids].sort();\n}\n\n/** Recomputes `lockedRootIds` from the stored sync states, now. */\nexport async function refreshLockedRoots(): Promise<void> {\n const config = getOfflineConfig();\n if (!config.getUserId()) return;\n const store = await getOfflineStore();\n const now = new Date();\n const locked: string[] = [];\n for (const id of await knownRootIds(store)) {\n if (isRootLocked(await store.getSyncState(id), now)) locked.push(id);\n }\n setOfflineState({ lockedRootIds: locked });\n}\n","import type { QueueEntry } from \"../store/DeviceStore\";\n\nexport const recordKeyOf = (type: string, id: string) => `${type}:${id}`;\n\n/**\n * The records a queue entry targets: its `recordType`/`recordId`, the `data.type`/`data.id` of\n * its body (a record created on this device), and the `<type>/<id>` of its path (the last\n * segment naming a synced type, when an id follows it: `npcs/n1`, `npcs/n1/merge`; a collection\n * path such as `roots/C/npcs` targets no record).\n */\nexport function recordKeysOfEntry(entry: QueueEntry, typeNames: Set<string>): string[] {\n const keys: string[] = [];\n if (entry.recordType && entry.recordId) keys.push(recordKeyOf(entry.recordType, entry.recordId));\n\n const data = entry.body?.data;\n if (data && !Array.isArray(data) && typeof data.type === \"string\" && typeof data.id === \"string\") {\n keys.push(recordKeyOf(data.type, data.id));\n }\n\n const segments = String(entry.path ?? \"\")\n .split(\"?\")[0]\n .split(\"/\")\n .filter(Boolean);\n for (let index = segments.length - 1; index >= 0; index--) {\n if (!typeNames.has(segments[index])) continue;\n if (index + 1 < segments.length) keys.push(recordKeyOf(segments[index], segments[index + 1]));\n break;\n }\n return keys;\n}\n\n/**\n * Every record with a waiting change: targeted by a queue entry that is pending, sending, in\n * conflict or failed. Such a record is never overwritten or deleted by a sync or a write-through.\n */\nexport function waitingRecordKeys(queue: QueueEntry[], typeNames: Set<string>): Set<string> {\n const keys = new Set<string>();\n for (const entry of queue) for (const key of recordKeysOfEntry(entry, typeNames)) keys.add(key);\n return keys;\n}\n","import type { ApiData } from \"../core/interfaces/ApiData\";\nimport { getOfflineConfig, getOfflineStore, OfflineUnavailableError } from \"./config\";\nimport { rootIdForResource, linkKindFor, linkKindsOf, linksOfKind } from \"./generic-answers\";\nimport { splitApiUrl } from \"./local-transport\";\nimport { matchPattern } from \"./router\";\nimport type { DeviceStore, StoredLink } from \"./store/DeviceStore\";\nimport type { OfflineTypeConfig } from \"./types\";\nimport { cacheArtworkImage, imageUrlOf } from \"./sync/artwork-cache\";\nimport { knownRootIds } from \"./sync/known-roots\";\nimport { recordKeyOf, waitingRecordKeys } from \"./sync/waiting\";\n\nexport type WriteThroughRequest = { method: string; url: string };\n\nfunction updatedAtOf(resource: any): Date | null {\n const value = resource?.attributes?.updatedAt;\n if (!value) return null;\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\ntype Identifier = { type: string; id: string };\n\nfunction identifiersOf(relationship: any): Identifier[] {\n const data = relationship?.data;\n if (!data) return [];\n return (Array.isArray(data) ? data : [data]).filter((item: any) => item && item.type && item.id);\n}\n\n/**\n * Brings the stored links of the written record in line with the relationships the answer\n * carries (only those whose `data` it holds), as the local answers do: a link of the\n * relationship (its name or mapped Neo4j type; any kind when the type is not mapped, for an\n * identifier the record held before) to an identifier no longer there is removed, and a new\n * identifier gets a link of `linkKindFor` the relationship.\n */\nasync function updateLinks(\n store: DeviceStore,\n typeConfig: OfflineTypeConfig | undefined,\n record: Identifier,\n before: any,\n after: any,\n): Promise<void> {\n const otherEnd = (link: StoredLink) => (link.fromId === record.id ? link.toId : link.fromId);\n const allLinks = async (): Promise<StoredLink[]> => [\n ...(await store.linksFrom(record.id)),\n ...(await store.linksTo(record.id)),\n ];\n\n for (const [name, relationship] of Object.entries<any>(after ?? {})) {\n if (!relationship || !(\"data\" in relationship)) continue;\n const next = new Map(identifiersOf(relationship).map((identifier) => [identifier.id, identifier]));\n const previous = new Set(identifiersOf(before?.[name]).map((identifier) => identifier.id));\n const kinds = linkKindsOf(typeConfig, name) ?? [name];\n const ofRelationship = await linksOfKind(store, record.id, kinds, \"both\");\n const mapped = linkKindsOf(typeConfig, name) !== undefined;\n\n const stale = [\n ...ofRelationship.filter((link) => !next.has(otherEnd(link))),\n // Unmapped: the link to an identifier the record held before may carry any kind.\n ...(mapped ? [] : (await allLinks()).filter((link) => previous.has(otherEnd(link)) && !next.has(otherEnd(link)))),\n ];\n for (const link of stale) await store.deleteLink({ fromId: link.fromId, toId: link.toId, kind: link.kind });\n\n const linked = new Set(\n (mapped ? await linksOfKind(store, record.id, kinds, \"both\") : await allLinks()).map(otherEnd),\n );\n for (const [id, identifier] of next) {\n if (linked.has(id)) continue;\n await store.putLink({\n fromType: record.type,\n fromId: record.id,\n toType: identifier.type,\n toId: id,\n kind: linkKindFor(typeConfig, name),\n props: {},\n });\n }\n }\n}\n\ntype ImageChange = { url: string; previousUrl?: string };\n\nasync function storeResource(\n store: DeviceStore,\n resource: any,\n pathRootId: string | undefined,\n waiting: Set<string>,\n kept: Set<string>,\n primary: boolean,\n images: ImageChange[],\n): Promise<void> {\n const config = getOfflineConfig();\n if (!resource?.type || !resource?.id) return;\n if (!config.types.some((typeConfig) => typeConfig.module.name === resource.type)) return;\n // A record with unsent local changes keeps them: the next sync, once they are sent, brings\n // the server's version.\n if (waiting.has(recordKeyOf(resource.type, resource.id))) return;\n\n const existing = await store.getRecord(resource.type, resource.id);\n if (existing && existing.base !== null) return;\n const rootId = existing?.rootId ?? (await rootIdForResource(store, config, resource)) ?? pathRootId;\n // A record that belongs to no root record on this device waits for the next sync.\n if (!rootId) return;\n // Only the root records the device keeps are written through (never a player's root record).\n if (!kept.has(rootId)) return;\n\n // An answer can carry a sparse resource (fields limited): it is merged onto what is stored.\n const before = existing?.resource ?? {};\n const merged = {\n ...before,\n ...resource,\n attributes: { ...(before.attributes ?? {}), ...(resource.attributes ?? {}) },\n relationships: { ...(before.relationships ?? {}), ...(resource.relationships ?? {}) },\n };\n\n await store.putRecord({\n type: resource.type,\n id: resource.id,\n rootId,\n resource: merged,\n updatedAt: updatedAtOf(resource) ?? existing?.updatedAt ?? null,\n base: null,\n localOnly: false,\n });\n if (primary) {\n const typeConfig = config.types.find((candidate) => candidate.module.name === resource.type);\n await updateLinks(store, typeConfig, resource, before.relationships, resource.relationships);\n }\n\n const url = imageUrlOf(merged);\n const previousUrl = imageUrlOf(existing?.resource);\n if (url && url !== previousUrl) images.push({ url, previousUrl });\n}\n\n/**\n * After a successful online write, copies the answer into the device copy so it is never behind\n * the GM's own work: the resource and its `included` for POST/PUT/PATCH, the record removed for a\n * DELETE. It never fails the request: a problem is logged and the next sync repairs the copy.\n */\nexport async function writeThrough(request: WriteThroughRequest, response: ApiData): Promise<void> {\n const method = request.method.toUpperCase();\n if (method === \"GET\" || !response.ok) return;\n\n try {\n const config = getOfflineConfig();\n if (!config.getUserId()) return;\n const { path } = splitApiUrl(request.url);\n const store = await getOfflineStore();\n\n if (method === \"DELETE\") {\n for (const typeConfig of config.types) {\n const params = matchPattern([typeConfig.module, \":id\"], path);\n if (params) {\n await store.deleteRecord(typeConfig.module.name, params.id);\n return;\n }\n }\n return;\n }\n\n const rootParams = matchPattern([config.rootModule, \":rootId\"], path.split(\"/\").slice(0, 2).join(\"/\"));\n const pathRootId = rootParams?.rootId;\n\n const document = response.data;\n const primary = Array.isArray(document?.data) ? document.data : document?.data ? [document.data] : [];\n const waiting = waitingRecordKeys(\n await store.queue(),\n new Set(config.types.map((typeConfig) => typeConfig.module.name)),\n );\n // The root records this device keeps for the signed-in user (stored roots, waiting changes, last\n // sync roots): a write in any other root record (a player's) stores nothing.\n const kept = new Set(await knownRootIds(store));\n const images: ImageChange[] = [];\n await store.transaction(async (transactionStore) => {\n for (const resource of primary) {\n await storeResource(transactionStore, resource, pathRootId, waiting, kept, true, images);\n }\n for (const resource of document?.included ?? []) {\n await storeResource(transactionStore, resource, pathRootId, waiting, kept, false, images);\n }\n });\n // A new image URL of an artwork: downloaded, the old cached image removed (as the sync does).\n for (const image of images) await cacheArtworkImage(image.url, image.previousUrl);\n } catch (error) {\n // A browser that cannot keep the device copy was already warned about once.\n if (error instanceof OfflineUnavailableError) return;\n console.warn(\"[offline] could not copy the answer to the device\", error);\n }\n}\n"]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getServerToken
|
|
3
|
-
} from "./chunk-F5UNXZ3J.mjs";
|
|
4
1
|
import {
|
|
5
2
|
serverRequest
|
|
6
3
|
} from "./chunk-E2LENTUH.mjs";
|
|
4
|
+
import {
|
|
5
|
+
getServerToken
|
|
6
|
+
} from "./chunk-F5UNXZ3J.mjs";
|
|
7
7
|
import {
|
|
8
8
|
ENV,
|
|
9
9
|
JsonApiDataFactory,
|
|
10
10
|
translateResponse
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DGWOVYEC.mjs";
|
|
12
12
|
import {
|
|
13
13
|
__name
|
|
14
14
|
} from "./chunk-PAWJFY3S.mjs";
|
|
@@ -201,4 +201,4 @@ export {
|
|
|
201
201
|
ServerJsonApiPatch,
|
|
202
202
|
ServerJsonApiDelete
|
|
203
203
|
};
|
|
204
|
-
//# sourceMappingURL=chunk-
|
|
204
|
+
//# sourceMappingURL=chunk-BH6JWIKI.mjs.map
|