@carlonicora/nextjs-jsonapi 6.20.0 → 6.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/{BlockNoteEditor-7JRJ3XWV.mjs → BlockNoteEditor-S7EVHYWT.mjs} +5 -5
  2. package/dist/{BlockNoteEditor-MH2D5L4A.js → BlockNoteEditor-V76V5XFY.js} +20 -20
  3. package/dist/{BlockNoteEditor-MH2D5L4A.js.map → BlockNoteEditor-V76V5XFY.js.map} +1 -1
  4. package/dist/{BlockNoteViewer-UKYEU3E3.mjs → BlockNoteViewer-G3L46SGQ.mjs} +3 -3
  5. package/dist/{BlockNoteViewer-72J3CBAH.js → BlockNoteViewer-ZFJ3DZXN.js} +4 -4
  6. package/dist/{BlockNoteViewer-72J3CBAH.js.map → BlockNoteViewer-ZFJ3DZXN.js.map} +1 -1
  7. package/dist/billing/index.js +492 -492
  8. package/dist/billing/index.mjs +4 -4
  9. package/dist/{chunk-MVAIYPIN.js → chunk-2CR7HWVS.js} +9 -9
  10. package/dist/{chunk-MVAIYPIN.js.map → chunk-2CR7HWVS.js.map} +1 -1
  11. package/dist/{chunk-IN2F6HI2.js → chunk-2CXF5XBU.js} +906 -906
  12. package/dist/{chunk-IN2F6HI2.js.map → chunk-2CXF5XBU.js.map} +1 -1
  13. package/dist/{chunk-ASORDSWQ.mjs → chunk-4GB35M62.mjs} +2 -2
  14. package/dist/{chunk-SGLFQ55U.js → chunk-CMNJENSJ.js} +862 -862
  15. package/dist/{chunk-SGLFQ55U.js.map → chunk-CMNJENSJ.js.map} +1 -1
  16. package/dist/{chunk-2U57XVNF.mjs → chunk-JSHLBRFV.mjs} +35 -6
  17. package/dist/chunk-JSHLBRFV.mjs.map +1 -0
  18. package/dist/{chunk-SJ3K3ATF.mjs → chunk-QSNZ6HPY.mjs} +3 -3
  19. package/dist/{chunk-NKXNOGMH.mjs → chunk-QWQZUFFA.mjs} +6 -6
  20. package/dist/{chunk-JAOBCIE7.js → chunk-SFDOUTEV.js} +4 -4
  21. package/dist/{chunk-JAOBCIE7.js.map → chunk-SFDOUTEV.js.map} +1 -1
  22. package/dist/{chunk-LSZZIQJ5.mjs → chunk-WU3UXTDS.mjs} +2 -2
  23. package/dist/{chunk-LSZZIQJ5.mjs.map → chunk-WU3UXTDS.mjs.map} +1 -1
  24. package/dist/{chunk-V7CHD7QT.js → chunk-YWLSIIE6.js} +47 -18
  25. package/dist/chunk-YWLSIIE6.js.map +1 -0
  26. package/dist/client/index.js +5 -5
  27. package/dist/client/index.mjs +4 -4
  28. package/dist/components/index.js +5 -5
  29. package/dist/components/index.mjs +4 -4
  30. package/dist/contexts/index.js +5 -5
  31. package/dist/contexts/index.mjs +4 -4
  32. package/dist/core/index.js +3 -3
  33. package/dist/core/index.mjs +2 -2
  34. package/dist/email-automations/index.js +118 -118
  35. package/dist/email-automations/index.mjs +4 -4
  36. package/dist/features/analytics/index.js +136 -136
  37. package/dist/features/analytics/index.mjs +4 -4
  38. package/dist/features/analytics/server.js +5 -5
  39. package/dist/features/analytics/server.mjs +3 -3
  40. package/dist/features/blog/index.js +151 -151
  41. package/dist/features/blog/index.mjs +4 -4
  42. package/dist/features/blog/server.js +17 -17
  43. package/dist/features/blog/server.mjs +2 -2
  44. package/dist/features/help/index.js +38 -38
  45. package/dist/features/help/index.mjs +4 -4
  46. package/dist/features/tokenusage/index.js +127 -127
  47. package/dist/features/tokenusage/index.mjs +4 -4
  48. package/dist/index.js +4 -4
  49. package/dist/index.mjs +3 -3
  50. package/dist/offline/index.d.mts +7 -3
  51. package/dist/offline/index.d.ts +7 -3
  52. package/dist/offline/index.js +77 -32
  53. package/dist/offline/index.js.map +1 -1
  54. package/dist/offline/index.mjs +65 -20
  55. package/dist/offline/index.mjs.map +1 -1
  56. package/dist/server/index.d.mts +4 -3
  57. package/dist/server/index.d.ts +4 -3
  58. package/dist/server/index.js +4 -4
  59. package/dist/server/index.mjs +3 -3
  60. package/package.json +1 -1
  61. package/pwa/sw.js +17 -1
  62. package/src/core/factories/RehydrationFactory.ts +47 -5
  63. package/src/core/factories/__tests__/RehydrationFactory.spec.ts +86 -0
  64. package/src/offline/server/build-offline-manifest.ts +4 -3
  65. package/src/offline/sync/__tests__/sync-root.spec.ts +92 -10
  66. package/src/offline/sync/sync-root.ts +79 -23
  67. package/src/pwa/__tests__/sw.spec.ts +33 -3
  68. package/dist/chunk-2U57XVNF.mjs.map +0 -1
  69. package/dist/chunk-V7CHD7QT.js.map +0 -1
  70. /package/dist/{BlockNoteEditor-7JRJ3XWV.mjs.map → BlockNoteEditor-S7EVHYWT.mjs.map} +0 -0
  71. /package/dist/{BlockNoteViewer-UKYEU3E3.mjs.map → BlockNoteViewer-G3L46SGQ.mjs.map} +0 -0
  72. /package/dist/{chunk-ASORDSWQ.mjs.map → chunk-4GB35M62.mjs.map} +0 -0
  73. /package/dist/{chunk-SJ3K3ATF.mjs.map → chunk-QSNZ6HPY.mjs.map} +0 -0
  74. /package/dist/{chunk-NKXNOGMH.mjs.map → chunk-QWQZUFFA.mjs.map} +0 -0
@@ -182,4 +182,90 @@ describe("RehydrationFactory", () => {
182
182
  );
183
183
  });
184
184
  });
185
+
186
+ describe("a resource reached through several relationships", () => {
187
+ // A model that reads every resource its "links" relationship points to from included.
188
+ let calls = 0;
189
+ class Node implements Partial<ApiDataInterface> {
190
+ id = "";
191
+ links: Node[] = [];
192
+ rehydrate(data: JsonApiHydratedDataInterface) {
193
+ calls++;
194
+ this.id = data.jsonApi.id;
195
+ for (const ref of data.jsonApi.relationships?.links?.data ?? []) {
196
+ const target = data.included.find((item: any) => item.type === ref.type && item.id === ref.id);
197
+ if (!target) continue;
198
+ const node = RehydrationFactory.rehydrate<any>(nodeModule as any, {
199
+ jsonApi: target,
200
+ included: data.included,
201
+ });
202
+ if (node) this.links.push(node);
203
+ }
204
+ return this;
205
+ }
206
+ }
207
+ const nodeModule = { name: "nodes", model: Node };
208
+ const node = (id: string, links: string[]) => ({
209
+ type: "nodes",
210
+ id,
211
+ relationships: { links: { data: links.map((link) => ({ type: "nodes", id: link })) } },
212
+ });
213
+
214
+ beforeEach(() => {
215
+ calls = 0;
216
+ DataClassRegistry.registerObjectClass(nodeModule as any, Node as any);
217
+ });
218
+
219
+ it("is rehydrated once and the same model is reused", () => {
220
+ // root -> left, right; left -> shared; right -> shared
221
+ const included = [node("left", ["shared"]), node("right", ["shared"]), node("shared", [])];
222
+ const result = RehydrationFactory.rehydrate<any>(nodeModule as any, {
223
+ jsonApi: node("root", ["left", "right"]),
224
+ included,
225
+ });
226
+
227
+ expect(result.links[0].links[0].id).toBe("shared");
228
+ expect(result.links[0].links[0]).toBe(result.links[1].links[0]);
229
+ expect(calls).toBe(4);
230
+ });
231
+
232
+ it("densely linked included data is rehydrated in linear, not exponential, work", () => {
233
+ // Twelve resources, each linked to every other one: every path through them would be 11! chains.
234
+ const ids = Array.from({ length: 12 }, (_, index) => `n${index}`);
235
+ const included = ids.map((id) =>
236
+ node(
237
+ id,
238
+ ids.filter((other) => other !== id),
239
+ ),
240
+ );
241
+
242
+ const result = RehydrationFactory.rehydrate<any>(nodeModule as any, { jsonApi: included[0], included });
243
+
244
+ expect(result.links).toHaveLength(11);
245
+ expect(calls).toBe(12);
246
+ });
247
+
248
+ it("every top-level resource of a list is rehydrated whole, even one already reached as a relationship", () => {
249
+ // a -> b, b -> a: inside a, b's link back to a is skipped; b as its own list entry has it.
250
+ const a = node("a", ["b"]);
251
+ const b = node("b", ["a"]);
252
+ const [first, second] = RehydrationFactory.rehydrateList<any>(nodeModule as any, [
253
+ { jsonApi: a, included: [a, b] },
254
+ { jsonApi: b, included: [a, b] },
255
+ ]);
256
+
257
+ expect(first.links[0].id).toBe("b");
258
+ expect(first.links[0].links).toHaveLength(0);
259
+ expect(second.links[0].id).toBe("a");
260
+ expect(second).not.toBe(first.links[0]);
261
+ });
262
+
263
+ it("nothing is reused across separate calls", () => {
264
+ const included = [node("shared", [])];
265
+ const one = RehydrationFactory.rehydrate<any>(nodeModule as any, { jsonApi: node("x", ["shared"]), included });
266
+ const two = RehydrationFactory.rehydrate<any>(nodeModule as any, { jsonApi: node("y", ["shared"]), included });
267
+
268
+ expect(one.links[0]).not.toBe(two.links[0]);
269
+ });
270
+ });
185
271
  });
@@ -19,9 +19,10 @@ async function listFiles(dir: string): Promise<string[]> {
19
19
  * Describes the running build for the service worker's offline shell precache
20
20
  * (`/offline-manifest.json`): the build id from `<distDir>/BUILD_ID` and every
21
21
  * file under `<distDir>/static`, as the `/_next/static/...` URL Next serves it at.
22
- * `next dev` writes no BUILD_ID (its folder is `.next/dev`): the id is then derived from the
23
- * asset list, whose chunk names carry content hashes, so an edit gives a new id and the
24
- * service worker refreshes the shell.
22
+ * `next dev` writes no BUILD_ID (its folder is `.next/dev`): the id is then `dev-` plus a hash
23
+ * of the asset list. Dev chunk names do NOT carry content hashes (an edit rewrites a chunk under
24
+ * the same name), so the id changes only when files appear or disappear; the service worker
25
+ * therefore serves `/_next/static/` network first for a `dev-` build (pwa/sw.js).
25
26
  * `distDir` must be the value the app's next.config uses. `extraAssets` are further same-origin
26
27
  * URLs the shell needs with no network (the SQLite files the device worker loads).
27
28
  */
@@ -23,7 +23,7 @@ vi.mock("../../../unified/JsonApiRequest", () => ({
23
23
  buildUrl: (endpoint: string) => `http://api.test/${endpoint}`,
24
24
  }));
25
25
 
26
- import { syncAll, syncRoot } from "../sync-root";
26
+ import { SYNC_FETCH_CONCURRENCY, syncAll, syncRoot } from "../sync-root";
27
27
  import { getOfflineState, subscribeOfflineState } from "../../state";
28
28
 
29
29
  type Answer = { ok: boolean; response: number; raw?: any; data?: any };
@@ -169,9 +169,9 @@ describe("syncRoot", () => {
169
169
  await syncRoot("C");
170
170
 
171
171
  const endpoints = JsonApiGet.mock.calls.map((call) => call[0].endpoint);
172
- expect(endpoints).toEqual([
173
- "roots/C/sync",
174
- "roots/C",
172
+ // The sync answer, then the root record; the other reads run concurrently, in any order.
173
+ expect(endpoints.slice(0, 2)).toEqual(["roots/C/sync", "roots/C"]);
174
+ expect([...endpoints.slice(2)].sort()).toEqual([
175
175
  "roots/C/npcs?fetchAll=true&updatedSince=2026-10-31T00:00:00.000Z",
176
176
  "templates/t1",
177
177
  ]);
@@ -612,12 +612,15 @@ describe("syncRoot", () => {
612
612
  await syncRoot("C");
613
613
 
614
614
  const calls = JsonApiGet.mock.calls.map((call) => call[0]);
615
- expect(calls.map((call) => call.endpoint)).toEqual([
616
- "roots/C/sync",
617
- "roots/C/sessions/s1/player-notes",
618
- "roots/C/clues/k1/player-notes",
619
- ]);
620
- expect(calls[1].classKey).toBe(PlayerNotes);
615
+ // The target reads run concurrently, in any order.
616
+ expect(calls[0].endpoint).toBe("roots/C/sync");
617
+ expect(
618
+ calls
619
+ .slice(1)
620
+ .map((call) => call.endpoint)
621
+ .sort(),
622
+ ).toEqual(["roots/C/clues/k1/player-notes", "roots/C/sessions/s1/player-notes"]);
623
+ expect(calls.slice(1).every((call) => call.classKey === PlayerNotes)).toBe(true);
621
624
  expect((await store.getRecord("player-notes", "p1"))?.updatedAt).toEqual(new Date("2026-10-31T10:00:00.000Z"));
622
625
  expect(await store.getRecord("player-notes", "p3")).toBeDefined();
623
626
  expect(await store.getRecord("player-notes", "p2")).toBeUndefined();
@@ -626,6 +629,85 @@ describe("syncRoot", () => {
626
629
  warn.mockRestore();
627
630
  });
628
631
 
632
+ it(`keeps at most SYNC_FETCH_CONCURRENCY (${SYNC_FETCH_CONCURRENCY}) record reads in flight at once`, async () => {
633
+ const ids = ["t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8"];
634
+ const pending: (() => void)[] = [];
635
+ let inFlight = 0;
636
+ let maxInFlight = 0;
637
+ JsonApiGet.mockImplementation(async ({ endpoint }: { endpoint: string }) => {
638
+ if (endpoint === "roots/C/sync") return syncAnswer({ entries: ids.map((id) => entry("templates", id)) });
639
+ inFlight += 1;
640
+ maxInFlight = Math.max(maxInFlight, inFlight);
641
+ await new Promise<void>((resolve) => pending.push(resolve));
642
+ inFlight -= 1;
643
+ const id = endpoint.split("/")[1];
644
+ return oneAnswer({ type: "templates", id, attributes: { name: id } });
645
+ });
646
+
647
+ const running = syncRoot("C");
648
+ await vi.waitFor(() => expect(pending).toHaveLength(SYNC_FETCH_CONCURRENCY));
649
+ for (let released = 0; released < ids.length; released += 1) {
650
+ await vi.waitFor(() => expect(pending.length).toBeGreaterThan(0));
651
+ pending.shift()!();
652
+ }
653
+ await running;
654
+
655
+ expect(maxInFlight).toBe(SYNC_FETCH_CONCURRENCY);
656
+ expect(JsonApiGet).toHaveBeenCalledTimes(1 + ids.length);
657
+ for (const id of ids) expect((await store.getRecord("templates", id))?.resource.attributes.name).toBe(id);
658
+ });
659
+
660
+ it("finishes the root record's read before any other type's read starts", async () => {
661
+ const events: string[] = [];
662
+ JsonApiGet.mockImplementation(async ({ endpoint }: { endpoint: string }) => {
663
+ if (endpoint === "roots/C/sync") {
664
+ return syncAnswer({ entries: [entry("roots", "C"), entry("npcs", "n1"), entry("templates", "t1")] });
665
+ }
666
+ events.push(`start ${endpoint}`);
667
+ if (endpoint === "roots/C") {
668
+ await new Promise((resolve) => setTimeout(resolve, 5));
669
+ events.push(`end ${endpoint}`);
670
+ return oneAnswer({ type: "roots", id: "C", attributes: { name: "Camp" } });
671
+ }
672
+ events.push(`end ${endpoint}`);
673
+ if (endpoint === "roots/C/npcs?fetchAll=true") return listAnswer([npc("n1", "Alda")]);
674
+ if (endpoint === "templates/t1") return oneAnswer({ type: "templates", id: "t1", attributes: { name: "T" } });
675
+ throw new Error(`unexpected GET ${endpoint}`);
676
+ });
677
+
678
+ await syncRoot("C");
679
+
680
+ expect(events.slice(0, 2)).toEqual(["start roots/C", "end roots/C"]);
681
+ expect(events).toHaveLength(6);
682
+ expect(await store.getRecord("npcs", "n1")).toBeDefined();
683
+ expect(await store.getRecord("templates", "t1")).toBeDefined();
684
+ });
685
+
686
+ it("a failing read (not 403/404) stops the sync: no new read starts and nothing is written", async () => {
687
+ const ids = ["t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8"];
688
+ const pending: (() => void)[] = [];
689
+ JsonApiGet.mockImplementation(async ({ endpoint }: { endpoint: string }) => {
690
+ if (endpoint === "roots/C/sync") {
691
+ return syncAnswer({ entries: [entry("npcs", "n1"), ...ids.map((id) => entry("templates", id))] });
692
+ }
693
+ if (endpoint === "templates/t1") return statusAnswer(500);
694
+ await new Promise<void>((resolve) => pending.push(resolve));
695
+ if (endpoint === "roots/C/npcs?fetchAll=true") return listAnswer([npc("n1", "Alda")]);
696
+ const id = endpoint.split("/")[1];
697
+ return oneAnswer({ type: "templates", id, attributes: { name: id } });
698
+ });
699
+
700
+ await expect(syncRoot("C")).rejects.toThrow("templates/t1 answered 500");
701
+
702
+ // The reads already in flight finish; their results are discarded and nothing more is asked.
703
+ for (const release of pending.splice(0)) release();
704
+ await new Promise((resolve) => setTimeout(resolve, 5));
705
+ expect(JsonApiGet).toHaveBeenCalledTimes(1 + SYNC_FETCH_CONCURRENCY);
706
+ expect((await store.getSyncState("C")).serverTime).toBeNull();
707
+ expect(await store.getRecord("npcs", "n1")).toBeUndefined();
708
+ for (const id of ids) expect(await store.getRecord("templates", id)).toBeUndefined();
709
+ });
710
+
629
711
  it("a locked answer stores locked and downloads nothing", async () => {
630
712
  serve({
631
713
  "roots/C/sync": syncAnswer({ locked: true, lockedFrom: "2026-10-30T00:00:00.000Z", entries: [], links: [] }),
@@ -46,6 +46,49 @@ async function fetchResources(
46
46
  return Array.isArray(data) ? data : [data];
47
47
  }
48
48
 
49
+ /** How many record reads one root record's sync keeps in flight at once (after the root record). */
50
+ export const SYNC_FETCH_CONCURRENCY = 4;
51
+
52
+ type FetchTask = () => Promise<void>;
53
+
54
+ /**
55
+ * Runs the tasks with at most `limit` in flight, starting them in list order. After the first
56
+ * rejection no new task starts and the returned promise rejects with that error; tasks already in
57
+ * flight may still finish, their outcome is ignored.
58
+ */
59
+ async function runWithConcurrency(tasks: FetchTask[], limit: number): Promise<void> {
60
+ if (tasks.length === 0) return;
61
+ await new Promise<void>((resolve, reject) => {
62
+ let next = 0;
63
+ let active = 0;
64
+ let failed = false;
65
+ const launch = (): void => {
66
+ if (failed) return;
67
+ if (next >= tasks.length && active === 0) {
68
+ resolve();
69
+ return;
70
+ }
71
+ while (active < limit && next < tasks.length) {
72
+ const task = tasks[next++];
73
+ active += 1;
74
+ new Promise<void>((done) => done(task())).then(
75
+ () => {
76
+ active -= 1;
77
+ launch();
78
+ },
79
+ (error) => {
80
+ active -= 1;
81
+ if (failed) return;
82
+ failed = true;
83
+ reject(error);
84
+ },
85
+ );
86
+ }
87
+ };
88
+ launch();
89
+ });
90
+ }
91
+
49
92
  async function imagesOfRoot(store: DeviceStore, rootId: string): Promise<{ type: string; id: string; url: string }[]> {
50
93
  const images: { type: string; id: string; url: string }[] = [];
51
94
  for (const typeConfig of getOfflineConfig().types) {
@@ -73,8 +116,10 @@ async function removeRootIfIdle(store: DeviceStore, rootId: string): Promise<voi
73
116
  /**
74
117
  * Brings one root record's device copy up to date with the sync answer (C2): records changed since
75
118
  * the stored server time are fetched (C3), records gone from the root record are deleted, the links
76
- * are replaced, and artwork images are downloaded. The sync state is stored only once every
77
- * fetch succeeded (or answered 403/404). A record with a waiting change is never overwritten.
119
+ * are replaced, and artwork images are downloaded. The root record is fetched first; the other
120
+ * reads then run up to SYNC_FETCH_CONCURRENCY at a time. The sync state is stored only once every
121
+ * fetch succeeded (or answered 403/404); the first failure stops the sync before anything is
122
+ * written. A record with a waiting change is never overwritten.
78
123
  */
79
124
  export async function syncRoot(rootId: string, kind?: OfflineRootKind): Promise<void> {
80
125
  const config = getOfflineConfig();
@@ -148,13 +193,10 @@ export async function syncRoot(rootId: string, kind?: OfflineRootKind): Promise<
148
193
  // Records the answers include whose type the device does not sync itself (the root record's
149
194
  // owner): kept for display only, so a local answer can include them as the server does.
150
195
  const included: any[] = [];
151
- // The root record of the kind is fetched before the other types: a kind's `typeEndpoints` are
152
- // built from it.
196
+ // The root record of the kind is fetched first and alone: a kind's `typeEndpoints` are built
197
+ // from it. Every other read (each type's list, each by-id record, each linked-list target) then
198
+ // runs as one flat task list, at most SYNC_FETCH_CONCURRENCY in flight.
153
199
  const rootTypeName = rootKind.module.name;
154
- const orderedTypes = [
155
- ...config.types.filter((typeConfig) => typeConfig.module.name === rootTypeName),
156
- ...config.types.filter((typeConfig) => typeConfig.module.name !== rootTypeName),
157
- ];
158
200
  const rootResource = async (): Promise<StoredRecord["resource"]> => {
159
201
  const fresh = fetched.find((resource) => resource?.type === rootTypeName && resource?.id === rootId);
160
202
  if (fresh) return fresh;
@@ -162,16 +204,21 @@ export async function syncRoot(rootId: string, kind?: OfflineRootKind): Promise<
162
204
  if (storedRoot) return storedRoot.resource;
163
205
  throw new Error(`[offline] ${rootTypeName} ${rootId}: the root record is needed to build the type endpoints`);
164
206
  };
165
- for (const typeConfig of orderedTypes) {
207
+ const fetchTasksOf = (typeConfig: (typeof config.types)[number]): FetchTask[] => {
166
208
  const changedOfType = changed.filter((entry) => entry.type === typeConfig.module.name);
167
- if (changedOfType.length === 0) continue;
209
+ if (changedOfType.length === 0) return [];
168
210
 
169
211
  const typeEndpoint = rootKind.typeEndpoints?.find((candidate) => candidate.module.name === typeConfig.module.name);
170
212
  if (typeEndpoint) {
171
213
  // One full list read under this kind, never with `updatedSince`.
172
- const endpoint = typeEndpoint.endpoint({ id: rootId, resource: await rootResource() });
173
- fetched.push(...(await fetchResources(typeConfig.module, endpoint, skipped, included)));
174
- } else if (typeConfig.fetch === "root-list") {
214
+ return [
215
+ async () => {
216
+ const endpoint = typeEndpoint.endpoint({ id: rootId, resource: await rootResource() });
217
+ fetched.push(...(await fetchResources(typeConfig.module, endpoint, skipped, included)));
218
+ },
219
+ ];
220
+ }
221
+ if (typeConfig.fetch === "root-list") {
175
222
  // One list read per type. With `updatedSince` when every changed record is inside the
176
223
  // window; without it when a record older than the window is missing or stale on the
177
224
  // device, so the whole list comes in the same single request (never one read per record).
@@ -181,8 +228,14 @@ export async function syncRoot(rootId: string, kind?: OfflineRootKind): Promise<
181
228
  childEndpoint: typeConfig.module,
182
229
  }).addAdditionalParam("fetchAll", "true");
183
230
  if (since && changedOfType.every(inWindow)) endpoint.addAdditionalParam("updatedSince", since.toISOString());
184
- fetched.push(...(await fetchResources(typeConfig.module, endpoint.generate(), skipped, included)));
185
- } else if (typeConfig.fetch === "linked-list") {
231
+ const generated = endpoint.generate();
232
+ return [
233
+ async () => {
234
+ fetched.push(...(await fetchResources(typeConfig.module, generated, skipped, included)));
235
+ },
236
+ ];
237
+ }
238
+ if (typeConfig.fetch === "linked-list") {
186
239
  // Read only as a list per target: the targets are the other ends of the changed records'
187
240
  // sync links of `linkKind`; each target's list is read once, and only the type's entries kept.
188
241
  if (!typeConfig.linkedList) throw new Error(`[offline] ${typeConfig.module.name}: linked-list needs linkedList`);
@@ -194,19 +247,22 @@ export async function syncRoot(rootId: string, kind?: OfflineRootKind): Promise<
194
247
  continue;
195
248
  targets.set(keyOf(link.toType, link.toId), { type: link.toType, id: link.toId });
196
249
  }
197
- for (const target of targets.values()) {
250
+ return [...targets.values()].map((target) => async () => {
198
251
  const resources = await fetchResources(typeConfig.module, endpoint(rootId, target), skipped, included);
199
252
  fetched.push(
200
253
  ...resources.filter((resource) => resource?.type === typeConfig.module.name && changedIds.has(resource.id)),
201
254
  );
202
- }
203
- } else {
204
- for (const entry of changedOfType) {
205
- const endpoint = new EndpointCreator({ endpoint: typeConfig.module, id: entry.id }).generate();
206
- fetched.push(...(await fetchResources(typeConfig.module, endpoint, skipped, included)));
207
- }
255
+ });
208
256
  }
209
- }
257
+ return changedOfType.map((entry) => async () => {
258
+ const endpoint = new EndpointCreator({ endpoint: typeConfig.module, id: entry.id }).generate();
259
+ fetched.push(...(await fetchResources(typeConfig.module, endpoint, skipped, included)));
260
+ });
261
+ };
262
+ const rootTypes = config.types.filter((typeConfig) => typeConfig.module.name === rootTypeName);
263
+ const otherTypes = config.types.filter((typeConfig) => typeConfig.module.name !== rootTypeName);
264
+ await runWithConcurrency(rootTypes.flatMap(fetchTasksOf), SYNC_FETCH_CONCURRENCY);
265
+ await runWithConcurrency(otherTypes.flatMap(fetchTasksOf), SYNC_FETCH_CONCURRENCY);
210
266
 
211
267
  const fetchedKeys = new Set(fetched.map((resource) => keyOf(resource?.type, resource?.id)));
212
268
  const previousImageUrls = new Map<string, string>();
@@ -463,14 +463,18 @@ describe("pwa/sw.js", () => {
463
463
  expect(deleted).toEqual(["test-images", "test-shell-build-1"]);
464
464
  });
465
465
 
466
- function shellWorker(shellEntries: Record<string, Response | undefined>, offlinePage?: Response) {
466
+ function shellWorker(
467
+ shellEntries: Record<string, Response | undefined>,
468
+ offlinePage?: Response,
469
+ buildId: string = "build-2",
470
+ ) {
467
471
  const worker = loadWorker(SHELL_CONFIG);
468
472
  const entries: Record<string, Response | undefined> = { [SHELL_COMPLETE_URL]: new Response(""), ...shellEntries };
469
473
  const shellCache = makeCache({ match: vi.fn(async (url: string) => entries[url]) });
470
474
  const offlineCache = makeCache({ match: vi.fn().mockResolvedValue(offlinePage) });
471
- worker.sandbox.caches.keys.mockResolvedValue(["test-offline-abc12345", "test-shell-build-2"]);
475
+ worker.sandbox.caches.keys.mockResolvedValue(["test-offline-abc12345", `test-shell-${buildId}`]);
472
476
  worker.sandbox.caches.open.mockImplementation(async (name: string) =>
473
- name === "test-shell-build-2" ? shellCache : offlineCache,
477
+ name === `test-shell-${buildId}` ? shellCache : offlineCache,
474
478
  );
475
479
  worker.sandbox.fetch.mockRejectedValue(new TypeError("Failed to fetch"));
476
480
  return { ...worker, shellCache, offlineCache };
@@ -563,6 +567,32 @@ describe("pwa/sw.js", () => {
563
567
  expect(sandbox.fetch).toHaveBeenCalledWith(missRequest);
564
568
  });
565
569
 
570
+ it("under next dev (a dev- build id) a GET under /_next/static/ goes to the network first, keeps the cache current, and falls back to the cache offline", async () => {
571
+ // next dev keeps a chunk's file name when its content changes: answering from the cache
572
+ // would hand the page a chunk of an earlier build next to the server's current ones.
573
+ const url = `${ORIGIN}/_next/static/chunks/a.js`;
574
+ const cachedChunk = new Response("cached chunk");
575
+ const { sandbox, dispatch, shellCache } = shellWorker({ [url]: cachedChunk }, undefined, "dev-0123456789abcdef");
576
+ const request = { method: "GET", mode: "no-cors", destination: "script", url };
577
+
578
+ const network = new Response("network chunk");
579
+ sandbox.fetch.mockResolvedValue(network);
580
+ const online = dispatch("fetch", { request });
581
+ expect(online.respondWith).toHaveBeenCalledTimes(1);
582
+ expect(await online.respondWithPromise).toBe(network);
583
+ expect(sandbox.fetch).toHaveBeenCalledWith(request);
584
+ expect(shellCache.put).toHaveBeenCalledTimes(1);
585
+ expect(shellCache.put.mock.calls[0][0]).toBe(url);
586
+ expect(await (shellCache.put.mock.calls[0][1] as Response).text()).toBe("network chunk");
587
+
588
+ sandbox.fetch.mockRejectedValue(new TypeError("Failed to fetch"));
589
+ const offline = dispatch("fetch", { request });
590
+ expect(await offline.respondWithPromise).toBe(cachedChunk);
591
+
592
+ const missing = dispatch("fetch", { request: { ...request, url: `${ORIGIN}/_next/static/chunks/missing.js` } });
593
+ expect(((await missing.respondWithPromise) as Response).type).toBe("error");
594
+ });
595
+
566
596
  it("another same-origin GET in the shell cache (the device worker, SQLite) goes to the network first and is answered from the cache when the network fails", async () => {
567
597
  const workerUrl = `${ORIGIN}/offline-sqlite/device-worker.mjs`;
568
598
  const cachedWorker = new Response("cached worker");
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/factories/RehydrationFactory.ts","../src/core/abstracts/AbstractApiData.ts","../src/i18n/config.ts","../src/core/registry/ModuleRegistry.ts","../src/core/endpoint/EndpointCreator.ts","../src/offline/sync/models/RootSync.ts"],"sourcesContent":["import { ApiDataInterface } from \"../interfaces/ApiDataInterface\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\nimport { JsonApiHydratedDataInterface } from \"../interfaces/JsonApiHydratedDataInterface\";\nimport { DataClassRegistry } from \"../registry/DataClassRegistry\";\n\n/**\n * The resources being rehydrated right now, along the current chain of relationships. A resource\n * met again inside its own chain (A's relationship leads to B, B's back to A) is a circle in the\n * `included` data: following it would recurse until the stack overflows, so it is skipped there.\n */\nconst inProgress = new Set<string>();\n\nconst keyOf = (data: JsonApiHydratedDataInterface): string | undefined => {\n const resource = data?.jsonApi;\n return resource?.type && resource?.id ? `${resource.type}:${resource.id}` : undefined;\n};\n\nfunction guarded<T>(data: JsonApiHydratedDataInterface, run: () => T): T {\n const key = keyOf(data);\n if (!key) return run();\n if (inProgress.has(key)) return undefined as T;\n inProgress.add(key);\n try {\n return run();\n } finally {\n inProgress.delete(key);\n }\n}\n\nexport class RehydrationFactory {\n public static rehydrate<T extends ApiDataInterface>(\n classKey: ApiRequestDataTypeInterface,\n data: JsonApiHydratedDataInterface,\n ): T {\n const factoryClass = DataClassRegistry.get(classKey);\n\n return guarded(data, () => new factoryClass().rehydrate(data) as T);\n }\n\n public static rehydrateList<T extends ApiDataInterface>(\n classKey: ApiRequestDataTypeInterface,\n data: JsonApiHydratedDataInterface[],\n ): T[] {\n const factoryClass = DataClassRegistry.get(classKey);\n\n return data.map((item: JsonApiHydratedDataInterface) =>\n guarded(item, () => new factoryClass().rehydrate(item) as T),\n );\n }\n}\n","import { RehydrationFactory } from \"../factories/RehydrationFactory\";\nimport { ApiDataInterface } from \"../interfaces/ApiDataInterface\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\nimport { JsonApiHydratedDataInterface } from \"../interfaces/JsonApiHydratedDataInterface\";\n\nexport abstract class AbstractApiData implements ApiDataInterface {\n static identifierFields: string[] = [\"name\"];\n\n get identifier(): string {\n const fields = (this.constructor as typeof AbstractApiData).identifierFields;\n return fields\n .map((field) => this._jsonApi?.attributes?.[field])\n .filter((v) => v != null && v !== \"\")\n .join(\" \");\n }\n\n protected _jsonApi?: any;\n protected _included?: any[];\n\n protected _id?: string;\n protected _type?: string;\n protected _createdAt?: Date;\n protected _updatedAt?: Date;\n\n protected _self?: string;\n\n get type(): string {\n if (!this._type) throw new Error(\"Type is not set.\");\n return this._type;\n }\n\n get id(): string {\n if (!this._id) throw new Error(\"Id is not set.\");\n return this._id;\n }\n\n get self(): string | undefined {\n return this._self;\n }\n\n get createdAt(): Date {\n return this._createdAt ?? new Date();\n }\n\n get updatedAt(): Date {\n return this._updatedAt ?? new Date();\n }\n\n get included(): any[] {\n return this._included ?? [];\n }\n\n get jsonApi(): any {\n return this._jsonApi;\n }\n\n ingestJsonApi(_data: JsonApiHydratedDataInterface): void {}\n\n generateApiUrl(_params?: any): string {\n throw new Error(\"Method not implemented.\");\n }\n\n createJsonApi(_data?: any): any {\n throw new Error(\"Method not implemented.\");\n }\n\n protected _readIncluded<T extends ApiDataInterface>(\n data: JsonApiHydratedDataInterface,\n type: string,\n dataType: ApiRequestDataTypeInterface,\n ): T | T[] | undefined {\n if (\n data.included === undefined ||\n data.included.length === 0 ||\n data.jsonApi.relationships === undefined ||\n data.jsonApi.relationships[type] === undefined ||\n data.jsonApi.relationships[type].data === undefined\n ) {\n return undefined;\n }\n\n if (Array.isArray(data.jsonApi.relationships[type].data)) {\n const response: T[] = data.jsonApi.relationships[type].data.map((jsonApiData: any) => {\n const includedData = data.included.find(\n (includedData: any) => includedData.id === jsonApiData.id && includedData.type === jsonApiData.type,\n );\n\n if (includedData === undefined) return undefined;\n\n return RehydrationFactory.rehydrate(dataType, {\n jsonApi: includedData,\n included: data.included,\n }) as T;\n });\n\n return response.filter((item: T | undefined) => item !== undefined) as T[];\n }\n\n const includedData = data.included.find(\n (includedData: any) =>\n includedData.id === data.jsonApi.relationships[type].data.id &&\n includedData.type === data.jsonApi.relationships[type].data.type,\n );\n\n if (includedData === undefined && data.allData !== undefined) {\n // Try to find in allData as a fallback\n const fallbackData = data.allData.find(\n (includedData: any) =>\n includedData.id === data.jsonApi.relationships[type].data.id &&\n includedData.type === data.jsonApi.relationships[type].data.type,\n );\n if (fallbackData !== undefined) {\n return RehydrationFactory.rehydrate(dataType, {\n jsonApi: fallbackData,\n included: data.included,\n }) as T;\n }\n }\n\n if (includedData === undefined) return undefined;\n\n return RehydrationFactory.rehydrate(dataType, {\n jsonApi: includedData,\n included: data.included,\n }) as T;\n }\n\n /**\n * Read included relationships with polymorphic type resolution.\n * Determines the correct model class based on the JSON:API type in included data.\n */\n protected _readIncludedPolymorphic<T extends ApiDataInterface>(\n data: JsonApiHydratedDataInterface,\n relationshipKey: string,\n candidateModules: ApiRequestDataTypeInterface[],\n ): T | T[] | undefined {\n // Validate relationship exists\n if (!data.included?.length || !data.jsonApi.relationships?.[relationshipKey]?.data) {\n return undefined;\n }\n\n const relData = data.jsonApi.relationships[relationshipKey].data;\n\n // Build lookup map: JSON:API type -> module\n const typeToModule = new Map<string, ApiRequestDataTypeInterface>();\n for (const mod of candidateModules) {\n typeToModule.set(mod.name, mod);\n }\n\n if (Array.isArray(relData)) {\n // Many relationship\n const result: T[] = [];\n for (const item of relData) {\n const includedItem = data.included.find((inc: any) => inc.id === item.id && inc.type === item.type);\n if (!includedItem) continue;\n\n // Resolve module by JSON:API type\n const module = typeToModule.get(includedItem.type);\n if (!module) continue;\n\n result.push(\n RehydrationFactory.rehydrate(module, {\n jsonApi: includedItem,\n included: data.included,\n }) as T,\n );\n }\n return result.length > 0 ? result : undefined;\n }\n\n // Single relationship\n const includedItem = data.included.find((inc: any) => inc.id === relData.id && inc.type === relData.type);\n if (!includedItem) return undefined;\n\n const module = typeToModule.get(includedItem.type);\n if (!module) return undefined;\n\n return RehydrationFactory.rehydrate(module, {\n jsonApi: includedItem,\n included: data.included,\n }) as T;\n }\n\n /**\n * Read included relationship data and augment with relationship meta properties.\n * Handles both single relationships (one-to-one) and array relationships (one-to-many).\n *\n * For single relationships: meta is read from `relationships[type].meta`\n * For array relationships: per-item meta is read from `relationships[type].data[].meta`\n *\n * @param data - Hydrated JSON:API data\n * @param type - Relationship type key (e.g., \"guide\", \"persons\")\n * @param dataType - Module reference for rehydration\n * @returns Related object(s) augmented with meta properties, or undefined\n */\n protected _readIncludedWithMeta<T extends ApiDataInterface, M extends Record<string, any>>(\n data: JsonApiHydratedDataInterface,\n type: string,\n dataType: ApiRequestDataTypeInterface,\n ): (T & M) | (T & M)[] | undefined {\n // Check if relationship exists\n if (\n data.included === undefined ||\n data.included.length === 0 ||\n data.jsonApi.relationships === undefined ||\n data.jsonApi.relationships[type] === undefined ||\n data.jsonApi.relationships[type].data === undefined\n ) {\n return undefined;\n }\n\n const relationshipData = data.jsonApi.relationships[type].data;\n\n // Handle array relationships with per-item meta\n if (Array.isArray(relationshipData)) {\n const result: (T & M)[] = [];\n\n for (const item of relationshipData) {\n const includedData = data.included.find((inc: any) => inc.id === item.id && inc.type === item.type);\n\n if (!includedData) continue;\n\n const entity = RehydrationFactory.rehydrate(dataType, {\n jsonApi: includedData,\n included: data.included,\n }) as T;\n\n // Merge per-item meta from relationships[type].data[].meta\n if (item.meta) {\n AbstractApiData._mergeMeta(entity, item.meta);\n }\n\n result.push(entity as T & M);\n }\n\n return result;\n }\n\n // Handle single relationship\n const includedData = data.included.find(\n (inc: any) => inc.id === relationshipData.id && inc.type === relationshipData.type,\n );\n\n if (!includedData) {\n // Try to find in allData as a fallback\n if (data.allData !== undefined) {\n const fallbackData = data.allData.find(\n (inc: any) => inc.id === relationshipData.id && inc.type === relationshipData.type,\n );\n if (fallbackData) {\n const entity = RehydrationFactory.rehydrate(dataType, {\n jsonApi: fallbackData,\n included: data.included,\n }) as T;\n\n // Merge relationship-level meta for single relationships\n const relationshipMeta = data.jsonApi.relationships[type].meta;\n if (relationshipMeta) {\n AbstractApiData._mergeMeta(entity, relationshipMeta);\n }\n return entity as T & M;\n }\n }\n return undefined;\n }\n\n const entity = RehydrationFactory.rehydrate(dataType, {\n jsonApi: includedData,\n included: data.included,\n }) as T;\n\n // Merge relationship-level meta for single relationships\n const relationshipMeta = data.jsonApi.relationships[type].meta;\n if (relationshipMeta) {\n AbstractApiData._mergeMeta(entity, relationshipMeta);\n }\n\n return entity as T & M;\n }\n\n /**\n * Copies relationship meta onto a rehydrated entity as own data properties. Object.assign throws\n * when the model declares a getter-only accessor of the same name (an app's `Chunk` with\n * `get relevance()`); an own property shadows that getter, so the model reads the meta value.\n */\n private static _mergeMeta(entity: object, meta: Record<string, any>): void {\n for (const [key, value] of Object.entries(meta)) {\n Object.defineProperty(entity, key, { value, writable: true, enumerable: true, configurable: true });\n }\n }\n\n dehydrate(): JsonApiHydratedDataInterface {\n return {\n jsonApi: this._jsonApi,\n included: this._included ?? [],\n };\n }\n\n rehydrate(data: JsonApiHydratedDataInterface): this {\n this._jsonApi = data.jsonApi;\n this._included = data.included;\n\n this._type = this._jsonApi.type;\n this._id = this._jsonApi.id;\n this._createdAt = this._jsonApi.meta?.createdAt !== undefined ? new Date(this._jsonApi.meta.createdAt) : undefined;\n this._updatedAt = this._jsonApi.meta?.updatedAt !== undefined ? new Date(this._jsonApi.meta.updatedAt) : undefined;\n\n this._self = this._jsonApi.links?.self ?? undefined;\n\n return this;\n }\n}\n","import { ComponentType } from \"react\";\n\n// Types for injected hooks\nexport interface I18nRouter {\n push: (href: string) => void;\n replace: (href: string) => void;\n back: () => void;\n forward: () => void;\n refresh: () => void;\n prefetch: (href: string) => void;\n}\n\nexport type UseRouterHook = () => I18nRouter;\nexport type UseTranslationsHook = (namespace?: string) => (key: string, values?: Record<string, any>) => string;\nexport type UseLocaleHook = () => string;\nexport type GetLocaleFunction = () => Promise<string>;\n\nexport type UseDateFnsLocaleHook = () => any; // date-fns Locale type\nexport type LinkComponent = ComponentType<{ href: string; children: React.ReactNode; [key: string]: any }>;\n\nexport interface I18nConfig {\n useRouter: UseRouterHook;\n useTranslations: UseTranslationsHook;\n useLocale?: UseLocaleHook;\n useDateFnsLocale?: UseDateFnsLocaleHook;\n Link: LinkComponent;\n usePathname: () => string;\n /**\n * Server-side locale for API requests (pass next-intl's `getLocale` from `next-intl/server`).\n *\n * The library never imports next-intl itself: instrumentation loads the library, and\n * next-intl's `getLocale` pulls in the app's `i18n/request.ts`, which reads\n * `next/root-params` — a module only allowed inside the app directory.\n */\n getLocale: GetLocaleFunction;\n /**\n * Locales the app supports. The first entry is the fallback.\n * Defaults to `[\"en\"]` when omitted.\n */\n supportedLocales?: string[];\n}\n\n// Private storage\nlet _config: I18nConfig | null = null;\n\n// Configuration function (called by app at startup)\nexport function configureI18n(config: I18nConfig): void {\n _config = config;\n}\n\n// Hooks for library components to use\nexport function useI18nRouter(): I18nRouter {\n if (!_config?.useRouter) {\n throw new Error(\"i18n not configured. Call configureI18n() at app startup.\");\n }\n return _config.useRouter();\n}\n\nexport function useI18nTranslations(namespace?: string): (key: string, values?: Record<string, any>) => string {\n if (!_config?.useTranslations) {\n // Fallback: return key as-is (safe for server/client)\n return (key: string) => key;\n }\n return _config.useTranslations(namespace);\n}\n\nexport function getI18nLink(): LinkComponent {\n if (!_config?.Link) {\n throw new Error(\"i18n not configured. Call configureI18n() at app startup.\");\n }\n return _config.Link;\n}\n\nexport function useI18nLocale(): string {\n if (_config?.useLocale) {\n return _config.useLocale();\n }\n // Fallback to English (safe for server/client)\n return \"en\";\n}\n\n/** The server-side locale from the app's `getLocale`. Throws when `configureI18n` has not run. */\nexport async function getI18nServerLocale(): Promise<string> {\n if (!_config?.getLocale) {\n throw new Error(\"i18n not configured. Call configureI18n() with getLocale at app startup.\");\n }\n return _config.getLocale();\n}\n\nexport function useI18nDateFnsLocale(): any {\n if (_config?.useDateFnsLocale) {\n return _config.useDateFnsLocale();\n }\n // Fallback to undefined (Calendar will use default)\n return undefined;\n}\n\n/** The app's supported locales. Defaults to `[\"en\"]` when not configured. */\nexport function getI18nSupportedLocales(): string[] {\n if (_config?.supportedLocales && _config.supportedLocales.length > 0) {\n return _config.supportedLocales;\n }\n return [\"en\"];\n}\n\n/** The app's default locale: the first supported locale (`\"en\"` when not configured). */\nexport function getI18nDefaultLocale(): string {\n return getI18nSupportedLocales()[0];\n}\n","import { ModuleWithPermissions } from \"../../permissions/types\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\nimport { hasBootstrapper, tryBootstrap } from \"./bootstrapStore\";\n\n// Foundation module types - defined by LIBRARY\nexport interface FoundationModuleDefinitions {\n S3: ModuleWithPermissions;\n Auth: ModuleWithPermissions;\n User: ModuleWithPermissions;\n Author: ModuleWithPermissions;\n Company: ModuleWithPermissions;\n Role: ModuleWithPermissions;\n Notification: ModuleWithPermissions;\n Push: ModuleWithPermissions;\n Feature: ModuleWithPermissions;\n Module: ModuleWithPermissions;\n Content: ModuleWithPermissions;\n HowTo: ModuleWithPermissions;\n HandbookPage: ModuleWithPermissions;\n HandbookSection: ModuleWithPermissions;\n HandbookThread: ModuleWithPermissions;\n HandbookThreadMessage: ModuleWithPermissions;\n Assistant: ModuleWithPermissions;\n AssistantMessage: ModuleWithPermissions;\n AssistantAction: ModuleWithPermissions;\n Chunk: ModuleWithPermissions;\n // Billing modules - READ: all users, UPDATE: CompanyAdministrator, ADMIN: Administrator\n Billing: ModuleWithPermissions;\n StripeCustomer: ModuleWithPermissions;\n StripePaymentMethod: ModuleWithPermissions;\n StripeSubscription: ModuleWithPermissions;\n StripeInvoice: ModuleWithPermissions;\n StripeProduct: ModuleWithPermissions;\n StripePrice: ModuleWithPermissions;\n StripeUsage: ModuleWithPermissions;\n StripePromotionCode: ModuleWithPermissions;\n StripeCoupon: ModuleWithPermissions;\n StripeCouponCode: ModuleWithPermissions;\n StripeCouponOffer: ModuleWithPermissions;\n // AI connection administration\n AiConnection: ModuleWithPermissions;\n // OAuth modules\n OAuth: ModuleWithPermissions;\n // Waitlist modules\n Waitlist: ModuleWithPermissions;\n WaitlistStats: ModuleWithPermissions;\n // Email automation modules\n EmailAutomation: ModuleWithPermissions;\n EmailAutomationStep: ModuleWithPermissions;\n EmailAutomationEnrollment: ModuleWithPermissions;\n EmailAutomationTrigger: ModuleWithPermissions;\n EmailAutomationCandidate: ModuleWithPermissions;\n // Email blast modules\n EmailBlast: ModuleWithPermissions;\n EmailBlastDelivery: ModuleWithPermissions;\n EmailBlastAudience: ModuleWithPermissions;\n // Blog modules\n BlogPost: ModuleWithPermissions;\n BlogPostTranslation: ModuleWithPermissions;\n BlogCategory: ModuleWithPermissions;\n BlogCategoryTranslation: ModuleWithPermissions;\n BlogSettings: ModuleWithPermissions;\n // Referral modules\n Referral: ModuleWithPermissions;\n ReferralStats: ModuleWithPermissions;\n // Referral link visits and the administrative referral report (opt-in: registered by apps that spread referralAdminModules)\n ReferralVisit: ModuleWithPermissions;\n ReferralAdminSummary: ModuleWithPermissions;\n ReferralAdminReferrer: ModuleWithPermissions;\n ReferralAdminReferral: ModuleWithPermissions;\n // Two-factor authentication modules\n TotpAuthenticator: ModuleWithPermissions;\n TotpSetup: ModuleWithPermissions;\n TotpVerify: ModuleWithPermissions;\n TotpVerifyLogin: ModuleWithPermissions;\n Passkey: ModuleWithPermissions;\n PasskeyRegistrationOptions: ModuleWithPermissions;\n PasskeyRegistrationVerify: ModuleWithPermissions;\n PasskeyRename: ModuleWithPermissions;\n PasskeyVerifyLogin: ModuleWithPermissions;\n PasskeyAuthenticationOptions: ModuleWithPermissions;\n TwoFactorEnable: ModuleWithPermissions;\n TwoFactorChallenge: ModuleWithPermissions;\n TwoFactorStatus: ModuleWithPermissions;\n BackupCodeVerify: ModuleWithPermissions;\n // OAuth sign-up awaiting consent\n PendingRegistration: ModuleWithPermissions;\n // RBAC modules\n PermissionMapping: ModuleWithPermissions;\n ModulePaths: ModuleWithPermissions;\n RbacMatrix: ModuleWithPermissions;\n // Audit modules\n AuditLog: ModuleWithPermissions;\n // Administrative token-usage reporting modules\n TokenUsageAdminSummary: ModuleWithPermissions;\n TokenUsageAdminTimeline: ModuleWithPermissions;\n TokenUsageAdminBreakdown: ModuleWithPermissions;\n // Self-service token-usage reporting modules\n TokenUsageReportSummary: ModuleWithPermissions;\n TokenUsageReportTimeline: ModuleWithPermissions;\n TokenUsageReportBreakdown: ModuleWithPermissions;\n // First-party web analytics modules (tracker + administrative dashboard)\n AnalyticsEvent: ModuleWithPermissions;\n AnalyticsSummary: ModuleWithPermissions;\n AnalyticsTimeline: ModuleWithPermissions;\n AnalyticsDailyUniqueTimeline: ModuleWithPermissions;\n AnalyticsBreakdown: ModuleWithPermissions;\n AnalyticsSession: ModuleWithPermissions;\n AnalyticsPageView: ModuleWithPermissions;\n // Account deletion (opt-in: registered only by apps that enable it)\n AccountDeletion: ModuleWithPermissions;\n // Offline mode (opt-in: registered by apps that spread offlineModules)\n OfflineChangeSet: ModuleWithPermissions;\n RootSync: ModuleWithPermissions;\n}\n\n// App-specific modules - apps will augment this interface ONLY\n\nexport interface AppModuleDefinitions {}\n\n// Combined type for full autocompletion\nexport type ModuleDefinitions = FoundationModuleDefinitions & AppModuleDefinitions;\n\n// Symbol key to avoid conflicts with other globals\nconst MODULES_KEY = Symbol.for(\"nextjs-jsonapi:modules\");\n\n// Use globalThis to persist module registry across HMR reloads\nconst globalStore = globalThis as unknown as {\n [MODULES_KEY]?: Map<string, ApiRequestDataTypeInterface>;\n};\n\n// Initialize global modules map if not set\nif (!globalStore[MODULES_KEY]) {\n globalStore[MODULES_KEY] = new Map();\n}\n\nclass ModuleRegistryClass {\n private get _modules(): Map<string, ApiRequestDataTypeInterface> {\n return globalStore[MODULES_KEY]!;\n }\n\n register<K extends string>(name: K, module: ApiRequestDataTypeInterface): void {\n this._modules.set(name, module);\n }\n\n get<K extends keyof ModuleDefinitions>(name: K): ModuleDefinitions[K] {\n let module = this._modules.get(name as string);\n\n // Self-healing: if module not found, try bootstrapping first\n if (!module) {\n const didBootstrap = tryBootstrap();\n if (didBootstrap) {\n // Retry after bootstrap\n module = this._modules.get(name as string);\n }\n }\n\n if (!module) {\n // Provide helpful error message based on state\n const hint = hasBootstrapper()\n ? \"Bootstrap was called but module still not found. Check module registration.\"\n : \"No bootstrapper registered. Ensure configureJsonApi({ bootstrapper }) is called before accessing modules.\";\n throw new Error(`Module \"${String(name)}\" not registered. ${hint}`);\n }\n\n return module as ModuleDefinitions[K];\n }\n\n /**\n * True when a module is registered under `name`. Never throws, so a component\n * can check for an opt-in module the app may not have registered.\n */\n has(name: string): boolean {\n // Bootstrap only an empty registry (as findByName does): an app that never\n // registers `name` must not re-run its bootstrapper on every check.\n if (this._modules.size === 0) tryBootstrap();\n return this._modules.has(name);\n }\n\n findByName(moduleName: string): ModuleWithPermissions {\n // Self-healing: try bootstrap if registry is empty\n if (this._modules.size === 0) {\n tryBootstrap();\n }\n\n // Search by module's name property (e.g., \"topics\", \"articles\")\n for (const module of this._modules.values()) {\n if ((module as ModuleWithPermissions).name === moduleName) {\n return module as ModuleWithPermissions;\n }\n }\n throw new Error(`Module not found: ${moduleName}`);\n }\n\n getAllPageUrls(): { id: string; text: string }[] {\n if (this._modules.size === 0) {\n tryBootstrap();\n }\n\n const seen = new Set<string>();\n const result: { id: string; text: string }[] = [];\n for (const [key, module] of this._modules.entries()) {\n const m = module as ModuleWithPermissions;\n if (m.pageUrl && !seen.has(m.pageUrl)) {\n seen.add(m.pageUrl);\n result.push({ id: m.pageUrl, text: key });\n result.push({ id: `${m.pageUrl}/:id`, text: `${key} (detail)` });\n }\n }\n return result.sort((a, b) => a.text.localeCompare(b.text));\n }\n\n findByModelName(modelName: string): ModuleWithPermissions {\n // Direct lookup by registry key (e.g., \"Article\", \"Document\")\n let module = this._modules.get(modelName);\n\n // Self-healing: if not found, try bootstrapping\n if (!module) {\n const didBootstrap = tryBootstrap();\n if (didBootstrap) {\n module = this._modules.get(modelName);\n }\n }\n\n if (!module) {\n throw new Error(`Module not found for model: ${modelName}`);\n }\n return module as ModuleWithPermissions;\n }\n\n findByFeature(feature: string): ModuleWithPermissions[] {\n const response: ModuleWithPermissions[] = [];\n\n for (const module of this._modules.values()) {\n const m = module as ModuleWithPermissions;\n if (m.feature === feature) {\n response.push(m);\n }\n }\n\n return response;\n }\n\n getAll(): ApiRequestDataTypeInterface[] {\n if (this._modules.size === 0) {\n tryBootstrap();\n }\n return Array.from(this._modules.values());\n }\n}\n\nexport const ModuleRegistry = new ModuleRegistryClass();\n\n// Proxy object for Modules.X syntax with autocompletion\nexport const Modules = new Proxy({} as ModuleDefinitions, {\n get(_, prop: string) {\n if (prop === \"findByName\") {\n return (name: string) => ModuleRegistry.findByName(name);\n }\n if (prop === \"findByModelName\") {\n return (name: string) => ModuleRegistry.findByModelName(name);\n }\n if (prop === \"findByFeature\") {\n return (feature: string) => ModuleRegistry.findByFeature(feature);\n }\n return ModuleRegistry.get(prop as keyof ModuleDefinitions);\n },\n}) as ModuleDefinitions & {\n findByName: (name: string) => ModuleWithPermissions;\n findByModelName: (name: string) => ModuleWithPermissions;\n findByFeature: (feature: string) => ModuleWithPermissions[];\n};\n","import { FieldSelector } from \"../fields/FieldSelector\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\n\nexport type EndpointQuery = {\n endpoint: ApiRequestDataTypeInterface | string;\n id?: string;\n childEndpoint?: ApiRequestDataTypeInterface | string;\n childId?: string;\n additionalParams?: { key: string; value: string | string[] }[];\n};\n\nexport class EndpointCreator {\n private _endpoint: EndpointQuery;\n\n constructor(params: {\n endpoint: ApiRequestDataTypeInterface | string;\n id?: string;\n childEndpoint?: ApiRequestDataTypeInterface | string;\n childId?: string;\n additionalParams?: { key: string; value: string }[];\n }) {\n this._endpoint = {\n endpoint: params.endpoint,\n id: params.id,\n childEndpoint: params.childEndpoint,\n childId: params.childId,\n additionalParams: params.additionalParams ?? [],\n };\n }\n\n endpoint(value: ApiRequestDataTypeInterface | string): EndpointCreator {\n this._endpoint.endpoint = value;\n return this;\n }\n\n id(value: string): EndpointCreator {\n this._endpoint.id = value;\n return this;\n }\n\n childEndpoint(value: ApiRequestDataTypeInterface | string): EndpointCreator {\n this._endpoint.childEndpoint = value;\n return this;\n }\n\n childId(value: string): EndpointCreator {\n this._endpoint.childId = value;\n return this;\n }\n\n set additionalParams(value: { key: string; value: string }[]) {\n this._endpoint.additionalParams = value;\n }\n\n addAdditionalParam(key: string, value: string | string[]): EndpointCreator {\n if (!this._endpoint.additionalParams) this._endpoint.additionalParams = [];\n this._endpoint.additionalParams.push({ key, value });\n return this;\n }\n\n limitToType(selectors: string[]): this {\n this.addAdditionalParam(`include`, selectors.join(\",\"));\n return this;\n }\n\n limitToFields(selectors: FieldSelector<any>[]): this {\n if (selectors.length === 0) return this;\n\n selectors.forEach((selector) => {\n const fieldString = selector.fields.join(\",\");\n this.addAdditionalParam(`fields[${selector.type}]`, fieldString);\n });\n\n return this;\n }\n\n generate(): string {\n let additionalParams = \"\";\n if (this._endpoint.additionalParams) {\n additionalParams = this._endpoint.additionalParams.map((param) => `${param.key}=${param.value}`).join(\"&\");\n }\n\n let response = ``;\n response += `${\n typeof this._endpoint.endpoint === \"string\" ? this._endpoint.endpoint : this._endpoint.endpoint.name\n }`;\n if (this._endpoint.id) response += `/${this._endpoint.id}`;\n if (this._endpoint.childEndpoint) {\n response += `/${\n typeof this._endpoint.childEndpoint === \"string\"\n ? this._endpoint.childEndpoint\n : this._endpoint.childEndpoint.name\n }`;\n }\n if (this._endpoint.childId) response += `/${this._endpoint.childId}`;\n if (additionalParams) response += `?${additionalParams}`;\n\n return response;\n }\n}\n","import { AbstractApiData } from \"../../../core/abstracts/AbstractApiData\";\nimport { JsonApiHydratedDataInterface } from \"../../../core/interfaces/JsonApiHydratedDataInterface\";\nimport { RootSyncEntry, RootSyncInterface, RootSyncLink } from \"./RootSyncInterface\";\n\n/**\n * Child path of the sync answer under the root module record\n * (`GET /<root endpoint>/:id/sync`), the same value as the app API's sync meta endpoint\n * (in Wyrdli, `campaignSyncMeta.endpoint`).\n */\nexport const ROOT_SYNC_ENDPOINT = \"sync\";\n\n/** The sync answer for one root record (`root-syncs`, id = root record id). Read-only. */\nexport class RootSync extends AbstractApiData implements RootSyncInterface {\n private _serverTime?: Date;\n private _locked: boolean = false;\n private _lockedFrom?: Date;\n private _entries?: RootSyncEntry[];\n private _links?: RootSyncLink[];\n\n get serverTime(): Date {\n if (!this._serverTime) throw new Error(\"serverTime is not set.\");\n return this._serverTime;\n }\n\n get locked(): boolean {\n return this._locked;\n }\n\n get lockedFrom(): Date | undefined {\n return this._lockedFrom;\n }\n\n get entries(): RootSyncEntry[] {\n return this._entries ?? [];\n }\n\n get links(): RootSyncLink[] {\n return this._links ?? [];\n }\n\n rehydrate(data: JsonApiHydratedDataInterface): this {\n super.rehydrate(data);\n\n const attributes = data.jsonApi.attributes ?? {};\n this._serverTime = attributes.serverTime ? new Date(attributes.serverTime) : undefined;\n this._locked = Boolean(attributes.locked);\n this._lockedFrom = attributes.lockedFrom ? new Date(attributes.lockedFrom) : undefined;\n this._entries = (attributes.entries ?? []).map((entry: { type: string; id: string; updatedAt: string }) => ({\n type: entry.type,\n id: entry.id,\n updatedAt: new Date(entry.updatedAt),\n }));\n this._links = (attributes.links ?? []).map((link: RootSyncLink) => ({\n fromType: link.fromType,\n fromId: link.fromId,\n toType: link.toType,\n toId: link.toId,\n kind: link.kind,\n props: link.props ?? {},\n }));\n\n return this;\n }\n\n createJsonApi(_data?: any): any {\n throw new Error(\"RootSync is read-only and cannot be created\");\n }\n}\n"],"mappings":"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","names":["includedData","includedItem","module","entity","relationshipMeta"]}