@carlonicora/nextjs-jsonapi 6.12.2 → 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.
Files changed (254) hide show
  1. package/dist/{ApiData-DPKNfY-9.d.mts → ApiData-Be8yxCOX.d.mts} +2 -0
  2. package/dist/{ApiData-DPKNfY-9.d.ts → ApiData-Be8yxCOX.d.ts} +2 -0
  3. package/dist/{BlockNoteEditor-75ED2CN2.mjs → BlockNoteEditor-22Q4DJBU.mjs} +20 -8
  4. package/dist/BlockNoteEditor-22Q4DJBU.mjs.map +1 -0
  5. package/dist/{BlockNoteEditor-7M5QW2YQ.js → BlockNoteEditor-2VLPSEKS.js} +34 -22
  6. package/dist/BlockNoteEditor-2VLPSEKS.js.map +1 -0
  7. package/dist/{BlockNoteViewer-WGCDB7IJ.js → BlockNoteViewer-2XYASA5Y.js} +8 -5
  8. package/dist/BlockNoteViewer-2XYASA5Y.js.map +1 -0
  9. package/dist/{BlockNoteViewer-MTGS6YJV.mjs → BlockNoteViewer-NBW2H6LC.mjs} +7 -4
  10. package/dist/{BlockNoteViewer-MTGS6YJV.mjs.map → BlockNoteViewer-NBW2H6LC.mjs.map} +1 -1
  11. package/dist/{JsonApiRequest-A66XK5WT.mjs → JsonApiRequest-F6TMJ42G.mjs} +2 -2
  12. package/dist/JsonApiRequest-WISJZSDE.js +26 -0
  13. package/dist/{JsonApiRequest-FZGPLNUF.js.map → JsonApiRequest-WISJZSDE.js.map} +1 -1
  14. package/dist/billing/index.js +499 -494
  15. package/dist/billing/index.js.map +1 -1
  16. package/dist/billing/index.mjs +13 -8
  17. package/dist/billing/index.mjs.map +1 -1
  18. package/dist/chunk-27LQDGNP.mjs +317 -0
  19. package/dist/chunk-27LQDGNP.mjs.map +1 -0
  20. package/dist/{chunk-V3YDVJ63.js → chunk-2H342HNN.js} +948 -921
  21. package/dist/chunk-2H342HNN.js.map +1 -0
  22. package/dist/{chunk-KSYQEKJX.js → chunk-2SV4SS6N.js} +14 -14
  23. package/dist/{chunk-KSYQEKJX.js.map → chunk-2SV4SS6N.js.map} +1 -1
  24. package/dist/{chunk-4FNGWALA.mjs → chunk-47BBY5KA.mjs} +6 -4
  25. package/dist/{chunk-4FNGWALA.mjs.map → chunk-47BBY5KA.mjs.map} +1 -1
  26. package/dist/chunk-6JUCIWDC.mjs +570 -0
  27. package/dist/chunk-6JUCIWDC.mjs.map +1 -0
  28. package/dist/chunk-7DBJTNSU.mjs +1049 -0
  29. package/dist/chunk-7DBJTNSU.mjs.map +1 -0
  30. package/dist/chunk-7JZHPMGW.mjs +704 -0
  31. package/dist/chunk-7JZHPMGW.mjs.map +1 -0
  32. package/dist/{chunk-L6PYFQTO.js → chunk-7ZHTPFFT.js} +16 -14
  33. package/dist/chunk-7ZHTPFFT.js.map +1 -0
  34. package/dist/chunk-B6JGKDP2.js +214 -0
  35. package/dist/chunk-B6JGKDP2.js.map +1 -0
  36. package/dist/{chunk-3YW35YHQ.mjs → chunk-BH6JWIKI.mjs} +5 -5
  37. package/dist/chunk-BZ5J2FIV.js +570 -0
  38. package/dist/chunk-BZ5J2FIV.js.map +1 -0
  39. package/dist/{chunk-V5MI7RNB.mjs → chunk-DGWOVYEC.mjs} +2 -2
  40. package/dist/chunk-DOCIFE6A.mjs +212 -0
  41. package/dist/chunk-DOCIFE6A.mjs.map +1 -0
  42. package/dist/chunk-GB6DBFYY.js +25 -0
  43. package/dist/chunk-GB6DBFYY.js.map +1 -0
  44. package/dist/{chunk-RXQYEEKZ.mjs → chunk-H4NLDX5G.mjs} +51 -24
  45. package/dist/chunk-H4NLDX5G.mjs.map +1 -0
  46. package/dist/{chunk-EWCY6QJA.mjs → chunk-JJMMT24D.mjs} +37 -3
  47. package/dist/chunk-JJMMT24D.mjs.map +1 -0
  48. package/dist/{chunk-JTPRJNT2.js → chunk-KMW7UNL2.js} +38 -3
  49. package/dist/chunk-KMW7UNL2.js.map +1 -0
  50. package/dist/chunk-MHB36MYE.js +317 -0
  51. package/dist/chunk-MHB36MYE.js.map +1 -0
  52. package/dist/{chunk-FF4EIQ6H.js → chunk-MOGEGWXW.js} +1039 -1680
  53. package/dist/chunk-MOGEGWXW.js.map +1 -0
  54. package/dist/{chunk-AA52ORW2.mjs → chunk-P445QGOC.mjs} +61 -702
  55. package/dist/chunk-P445QGOC.mjs.map +1 -0
  56. package/dist/chunk-PV4Z5W6V.js +212 -0
  57. package/dist/chunk-PV4Z5W6V.js.map +1 -0
  58. package/dist/chunk-PXDUJ47N.js +1049 -0
  59. package/dist/chunk-PXDUJ47N.js.map +1 -0
  60. package/dist/{chunk-GHE4VT2R.js → chunk-QKNGZDSQ.js} +2 -2
  61. package/dist/{chunk-GHE4VT2R.js.map → chunk-QKNGZDSQ.js.map} +1 -1
  62. package/dist/chunk-W62MHDWM.js +704 -0
  63. package/dist/chunk-W62MHDWM.js.map +1 -0
  64. package/dist/chunk-XXP3TQHF.mjs +25 -0
  65. package/dist/chunk-XXP3TQHF.mjs.map +1 -0
  66. package/dist/chunk-YRKIFYOE.mjs +214 -0
  67. package/dist/chunk-YRKIFYOE.mjs.map +1 -0
  68. package/dist/{chunk-O3PDL2CV.js → chunk-Z4POQWFG.js} +40 -6
  69. package/dist/chunk-Z4POQWFG.js.map +1 -0
  70. package/dist/{chunk-PI56A4DG.mjs → chunk-ZLTOUZN2.mjs} +37 -2
  71. package/dist/chunk-ZLTOUZN2.mjs.map +1 -0
  72. package/dist/client/index.d.mts +2 -2
  73. package/dist/client/index.d.ts +2 -2
  74. package/dist/client/index.js +14 -9
  75. package/dist/client/index.js.map +1 -1
  76. package/dist/client/index.mjs +24 -19
  77. package/dist/components/index.d.mts +7 -1
  78. package/dist/components/index.d.ts +7 -1
  79. package/dist/components/index.js +11 -7
  80. package/dist/components/index.js.map +1 -1
  81. package/dist/components/index.mjs +10 -6
  82. package/dist/contexts/index.d.mts +1 -1
  83. package/dist/contexts/index.d.ts +1 -1
  84. package/dist/contexts/index.js +11 -7
  85. package/dist/contexts/index.js.map +1 -1
  86. package/dist/contexts/index.mjs +10 -6
  87. package/dist/core/index.d.mts +128 -2
  88. package/dist/core/index.d.ts +128 -2
  89. package/dist/core/index.js +17 -4
  90. package/dist/core/index.js.map +1 -1
  91. package/dist/core/index.mjs +22 -9
  92. package/dist/email-automations/index.js +101 -96
  93. package/dist/email-automations/index.js.map +1 -1
  94. package/dist/email-automations/index.mjs +13 -8
  95. package/dist/email-automations/index.mjs.map +1 -1
  96. package/dist/features/analytics/index.js +128 -123
  97. package/dist/features/analytics/index.js.map +1 -1
  98. package/dist/features/analytics/index.mjs +13 -8
  99. package/dist/features/analytics/index.mjs.map +1 -1
  100. package/dist/features/analytics/server.js +11 -8
  101. package/dist/features/analytics/server.js.map +1 -1
  102. package/dist/features/analytics/server.mjs +9 -6
  103. package/dist/features/analytics/server.mjs.map +1 -1
  104. package/dist/features/blog/index.js +158 -153
  105. package/dist/features/blog/index.js.map +1 -1
  106. package/dist/features/blog/index.mjs +14 -9
  107. package/dist/features/blog/index.mjs.map +1 -1
  108. package/dist/features/blog/server.js +30 -26
  109. package/dist/features/blog/server.js.map +1 -1
  110. package/dist/features/blog/server.mjs +10 -6
  111. package/dist/features/blog/server.mjs.map +1 -1
  112. package/dist/features/help/index.js +44 -40
  113. package/dist/features/help/index.js.map +1 -1
  114. package/dist/features/help/index.mjs +10 -6
  115. package/dist/features/help/index.mjs.map +1 -1
  116. package/dist/features/tokenusage/index.js +134 -129
  117. package/dist/features/tokenusage/index.js.map +1 -1
  118. package/dist/features/tokenusage/index.mjs +13 -8
  119. package/dist/features/tokenusage/index.mjs.map +1 -1
  120. package/dist/index.d.mts +2 -2
  121. package/dist/index.d.ts +2 -2
  122. package/dist/index.js +18 -5
  123. package/dist/index.js.map +1 -1
  124. package/dist/index.mjs +24 -11
  125. package/dist/lifecycle-4GUOTB46.js +23 -0
  126. package/dist/lifecycle-4GUOTB46.js.map +1 -0
  127. package/dist/lifecycle-LKGPRBS7.mjs +23 -0
  128. package/dist/local-transport-7H3SPZSA.js +15 -0
  129. package/dist/local-transport-7H3SPZSA.js.map +1 -0
  130. package/dist/local-transport-QYFKA6AN.mjs +15 -0
  131. package/dist/local-transport-QYFKA6AN.mjs.map +1 -0
  132. package/dist/offline/index.d.mts +607 -0
  133. package/dist/offline/index.d.ts +607 -0
  134. package/dist/offline/index.js +1052 -0
  135. package/dist/offline/index.js.map +1 -0
  136. package/dist/offline/index.mjs +1052 -0
  137. package/dist/offline/index.mjs.map +1 -0
  138. package/dist/offline/sqlite/device-worker.mjs +62 -0
  139. package/dist/offline/sqlite/index.mjs +17392 -0
  140. package/dist/offline/sqlite/sqlite3-opfs-async-proxy.js +881 -0
  141. package/dist/offline/sqlite/sqlite3.wasm +0 -0
  142. package/dist/request-KTBV6HHH.js +10 -0
  143. package/dist/request-KTBV6HHH.js.map +1 -0
  144. package/dist/request-SQOUNWZ4.mjs +10 -0
  145. package/dist/request-SQOUNWZ4.mjs.map +1 -0
  146. package/dist/server/cachedGet.d.mts +1 -1
  147. package/dist/server/cachedGet.d.ts +1 -1
  148. package/dist/server/cachedGet.stub.d.mts +1 -1
  149. package/dist/server/cachedGet.stub.d.ts +1 -1
  150. package/dist/server/index.d.mts +28 -2
  151. package/dist/server/index.d.ts +28 -2
  152. package/dist/server/index.js +18 -8
  153. package/dist/server/index.js.map +1 -1
  154. package/dist/server/index.mjs +18 -8
  155. package/dist/{useSocket-IRHtk2xc.d.ts → useSocket-Bo9M0t0J.d.ts} +3 -1
  156. package/dist/{useSocket-C8fqU4od.d.mts → useSocket-J5qanQPN.d.mts} +3 -1
  157. package/dist/write-through-FDQ7N4YI.js +15 -0
  158. package/dist/write-through-FDQ7N4YI.js.map +1 -0
  159. package/dist/write-through-R25VQMH6.mjs +15 -0
  160. package/dist/write-through-R25VQMH6.mjs.map +1 -0
  161. package/package.json +7 -1
  162. package/pwa/sw.js +277 -26
  163. package/src/client/request.ts +37 -0
  164. package/src/components/editors/BlockNoteEditor.tsx +14 -0
  165. package/src/components/editors/__tests__/BlockNoteEditor.empty-change.spec.tsx +205 -0
  166. package/src/contexts/SocketContext.tsx +7 -1
  167. package/src/contexts/__tests__/SocketContext.session.spec.tsx +52 -5
  168. package/src/core/factories/RehydrationFactory.ts +28 -8
  169. package/src/core/factories/__tests__/RehydrationFactory.spec.ts +33 -0
  170. package/src/core/index.ts +3 -0
  171. package/src/core/interfaces/ApiData.ts +2 -0
  172. package/src/core/registry/ModuleRegistry.ts +3 -0
  173. package/src/features/auth/components/forms/Logout.tsx +8 -1
  174. package/src/features/auth/components/forms/__tests__/Logout.spec.tsx +62 -0
  175. package/src/hooks/__tests__/useSocket.pause.spec.ts +39 -0
  176. package/src/hooks/useSocket.ts +20 -1
  177. package/src/offline/__tests__/answer-builders.spec.ts +176 -0
  178. package/src/offline/__tests__/device-switch.spec.ts +179 -0
  179. package/src/offline/__tests__/generic-answers.spec.ts +380 -0
  180. package/src/offline/__tests__/local-transport.spec.ts +150 -0
  181. package/src/offline/__tests__/logout.spec.ts +122 -0
  182. package/src/offline/__tests__/offline-fixtures.ts +89 -0
  183. package/src/offline/__tests__/router.spec.ts +56 -0
  184. package/src/offline/__tests__/state.spec.ts +62 -0
  185. package/src/offline/__tests__/write-through.spec.ts +137 -0
  186. package/src/offline/config.ts +229 -0
  187. package/src/offline/configure.ts +14 -0
  188. package/src/offline/device-switch.ts +75 -0
  189. package/src/offline/generic-answers.ts +706 -0
  190. package/src/offline/index.ts +67 -0
  191. package/src/offline/local-transport.ts +189 -0
  192. package/src/offline/logout.ts +41 -0
  193. package/src/offline/router.ts +41 -0
  194. package/src/offline/server/__tests__/build-offline-manifest.spec.ts +76 -0
  195. package/src/offline/server/build-offline-manifest.ts +49 -0
  196. package/src/offline/state-hooks.ts +10 -0
  197. package/src/offline/state.ts +143 -0
  198. package/src/offline/store/DeviceStore.ts +112 -0
  199. package/src/offline/store/__tests__/device-store-lifecycle.spec.ts +343 -0
  200. package/src/offline/store/__tests__/shared-database.spec.ts +151 -0
  201. package/src/offline/store/__tests__/sql-device-store.spec.ts +399 -0
  202. package/src/offline/store/lifecycle.ts +315 -0
  203. package/src/offline/store/schema.ts +101 -0
  204. package/src/offline/store/shared-database.ts +321 -0
  205. package/src/offline/store/sql-device-store.ts +469 -0
  206. package/src/offline/store/worker-client.ts +71 -0
  207. package/src/offline/store/worker.ts +87 -0
  208. package/src/offline/sync/__tests__/conflicts.spec.ts +185 -0
  209. package/src/offline/sync/__tests__/queue-drain.spec.ts +364 -0
  210. package/src/offline/sync/__tests__/runtime.spec.ts +337 -0
  211. package/src/offline/sync/__tests__/sync-fixtures.ts +103 -0
  212. package/src/offline/sync/__tests__/sync-root.spec.ts +727 -0
  213. package/src/offline/sync/artwork-cache.ts +67 -0
  214. package/src/offline/sync/conflicts.ts +211 -0
  215. package/src/offline/sync/counts.ts +39 -0
  216. package/src/offline/sync/drain-queue.ts +246 -0
  217. package/src/offline/sync/known-roots.ts +37 -0
  218. package/src/offline/sync/models/OfflineChangeSet.ts +34 -0
  219. package/src/offline/sync/models/OfflineChangeSetInterface.ts +54 -0
  220. package/src/offline/sync/models/RootSync.ts +68 -0
  221. package/src/offline/sync/models/RootSyncInterface.ts +20 -0
  222. package/src/offline/sync/models/__tests__/OfflineChangeSet.spec.ts +47 -0
  223. package/src/offline/sync/models/__tests__/RootSync.spec.ts +54 -0
  224. package/src/offline/sync/models/index.ts +5 -0
  225. package/src/offline/sync/models/offline.modules.ts +14 -0
  226. package/src/offline/sync/runtime.ts +226 -0
  227. package/src/offline/sync/session-refresh.ts +32 -0
  228. package/src/offline/sync/sync-root.ts +336 -0
  229. package/src/offline/sync/waiting.ts +40 -0
  230. package/src/offline/types.ts +115 -0
  231. package/src/offline/write-through.ts +189 -0
  232. package/src/pwa/__tests__/sw.spec.ts +358 -1
  233. package/src/server/index.ts +5 -0
  234. package/src/unified/__tests__/JsonApiRequest.offline.spec.ts +243 -0
  235. package/dist/BlockNoteEditor-75ED2CN2.mjs.map +0 -1
  236. package/dist/BlockNoteEditor-7M5QW2YQ.js.map +0 -1
  237. package/dist/BlockNoteViewer-WGCDB7IJ.js.map +0 -1
  238. package/dist/JsonApiRequest-FZGPLNUF.js +0 -26
  239. package/dist/chunk-AA52ORW2.mjs.map +0 -1
  240. package/dist/chunk-EWCY6QJA.mjs.map +0 -1
  241. package/dist/chunk-FF4EIQ6H.js.map +0 -1
  242. package/dist/chunk-JTPRJNT2.js.map +0 -1
  243. package/dist/chunk-L6PYFQTO.js.map +0 -1
  244. package/dist/chunk-O3PDL2CV.js.map +0 -1
  245. package/dist/chunk-PI56A4DG.mjs.map +0 -1
  246. package/dist/chunk-RXQYEEKZ.mjs.map +0 -1
  247. package/dist/chunk-V3YDVJ63.js.map +0 -1
  248. package/dist/request-5RLK2AM3.mjs +0 -9
  249. package/dist/request-EUGTI3BE.js +0 -9
  250. package/dist/request-EUGTI3BE.js.map +0 -1
  251. /package/dist/{JsonApiRequest-A66XK5WT.mjs.map → JsonApiRequest-F6TMJ42G.mjs.map} +0 -0
  252. /package/dist/{chunk-3YW35YHQ.mjs.map → chunk-BH6JWIKI.mjs.map} +0 -0
  253. /package/dist/{chunk-V5MI7RNB.mjs.map → chunk-DGWOVYEC.mjs.map} +0 -0
  254. /package/dist/{request-5RLK2AM3.mjs.map → lifecycle-LKGPRBS7.mjs.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-PXDUJ47N.js","../src/offline/sync/artwork-cache.ts","../src/offline/store/schema.ts","../src/offline/store/sql-device-store.ts","../src/offline/store/shared-database.ts","../src/offline/store/worker-client.ts","../src/offline/store/lifecycle.ts"],"names":["newId"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACA;ACRA,IAAM,oBAAA,EAAsB,SAAA;AAC5B,IAAM,qBAAA,EAAuB,WAAA;AAE7B,MAAA,SAAe,eAAA,CAAA,EAA+C;AAC5D,EAAA,GAAA,CAAI,OAAO,OAAA,IAAW,WAAA,EAAa,OAAO,KAAA,CAAA;AAC1C,EAAA,MAAM,KAAA,EAAO,MAAM,MAAA,CAAO,IAAA,CAAK,CAAA;AAC/B,EAAA,MAAM,SAAA,EAAW,IAAA,CAAK,IAAA,CAAK,CAAC,GAAA,EAAA,GAAQ,GAAA,CAAI,QAAA,CAAS,mBAAmB,CAAC,CAAA;AACrE,EAAA,GAAA,CAAI,QAAA,EAAU,OAAO,QAAA;AAErB,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,CAAC,GAAA,EAAA,GAAQ,GAAA,CAAI,QAAA,CAAS,oBAAoB,CAAC,CAAA;AACrE,EAAA,GAAA,CAAI,CAAC,OAAA,EAAS,OAAO,KAAA,CAAA;AACrB,EAAA,OAAO,CAAA,EAAA;AACT;AATe;AAYC;AACR,EAAA;AACC,EAAA;AACT;AAHgB;AAUhB;AACQ,EAAA;AACD,EAAA;AACC,EAAA;AAEF,EAAA;AACA,EAAA;AAEA,EAAA;AACI,IAAA;AACD,IAAA;AACH,MAAA;AACA,MAAA;AACF,IAAA;AACM,IAAA;AACR,EAAA;AACE,IAAA;AACF,EAAA;AACF;AAlBsB;AAqBtB;AACQ,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACR;AALsB;AAQtB;AACM,EAAA;AACE,EAAA;AACA,EAAA;AACR;AAJsB;ADGZ;AACA;AEtDG;AAMA;AACX,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF;AAEa;AACX,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA;AAUA,EAAA;AACA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA;AAMA,EAAA;AACA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA;AAUA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA;AASA,EAAA;AACA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA;AAeA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA;AAMA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA;AAOA,EAAA;AACF;AFkDU;AACA;AG5HJ;AAEA;AACA;AACA;AAGA;AAKA;AACE,EAAA;AACF,EAAA;AACI,EAAA;AACR,EAAA;AACA,EAAA;AACM,EAAA;AACN,EAAA;AAPkB;AAUd;AACJ,EAAA;AACQ,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACC,EAAA;AANS;AASZ;AACE,EAAA;AACA,EAAA;AACC,IAAA;AACL,IAAA;AACA,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACF,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACD,EAAA;AAhBe;AAmBlB;AACE,EAAA;AACA,IAAA;AACJ,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACM,IAAA;AACN,IAAA;AACF,EAAA;AACQ,EAAA;AACA,EAAA;AACF,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACI,EAAA;AACD,EAAA;AAjBa;AAqBhB;AACE,EAAA;AACE,IAAA;AACJ,MAAA;AACC,MAAA;AACH,IAAA;AAJiB,EAAA;AAOb,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AAHuB,EAAA;AAMxB,EAAA;AACC,IAAA;AACJ,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACA,MAAA;AACJ,MAAA;AACA,MAAA;AACI,MAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AAIJ,MAAA;AACE,QAAA;AAAA;AAAA,iEAAA;AAGA,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACD,MAAA;AACG,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AAEM,IAAA;AACJ,MAAA;AAIA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AAIA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AAGJ,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAEM,IAAA;AACA,MAAA;AACJ,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACE,QAAA;AACC,QAAA;AACH,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AAGA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAEM,IAAA;AACA,MAAA;AACJ,MAAA;AACE,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AAAA,uDAAA;AAEA,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,2BAAA;AACA,2BAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,2BAAA;AACF,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACI,MAAA;AACJ,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACE,QAAA;AACA,QAAA;AACE,QAAA;AACF,QAAA;AACD,MAAA;AACH,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACJ,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACE,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AACF,IAAA;AAEM,IAAA;AAEJ,MAAA;AACI,MAAA;AAGJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AACF,MAAA;AACA,MAAA;AACE,QAAA;AACC,QAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AArQuB;AA2QnB;AACI,EAAA;AACF,EAAA;AACC,EAAA;AAHW;AAWpB;AACQ,EAAA;AAGA,EAAA;AACE,IAAA;AACF,IAAA;AACF,MAAA;AACF,IAAA;AACA,IAAA;AACD,EAAA;AAEK,EAAA;AACF,EAAA;AACE,EAAA;AACE,IAAA;AACJ,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AANiB,EAAA;AAQb,EAAA;AACA,IAAA;AADc,EAAA;AAId,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAEI,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AATc,IAAA;AAWlB,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAEI,MAAA;AACA,MAAA;AAHc,IAAA;AAKlB,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAEI,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AAVQ,IAAA;AAYZ,IAAA;AAEI,MAAA;AACI,MAAA;AACA,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AAbS,IAAA;AAef,EAAA;AACO,EAAA;AACT;AA5FsB;AHoJZ;AACA;AI7eJ;AAEA;AAEA;AAEA;AACA;AAEAA;AAGU;AAEZ,EAAA;AAIJ;AANgB;AASV;AAEM,EAAA;AAFY;AAKxB;AACQ,EAAA;AACN,EAAA;AACM,IAAA;AACF,MAAA;AACF,IAAA;AACM,MAAA;AACJ,MAAA;AACF,IAAA;AACF,EAAA;AACF;AAVe;AAaN;AACH,EAAA;AACO,EAAA;AACH,IAAA;AACN,IAAA;AACA,IAAA;AACF,EAAA;AACF;AAPS;AAaT;AAMQ,EAAA;AACA,EAAA;AACA,EAAA;AAEF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AAGA,EAAA;AAEA,EAAA;AAIA,EAAA;AAIF,EAAA;AAEE,EAAA;AAGA,EAAA;AACE,IAAA;AACD,IAAA;AACH,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACF,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAfiB,EAAA;AAkBX,EAAA;AACA,IAAA;AACF,IAAA;AACJ,IAAA;AACE,MAAA;AAEE,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACE,UAAA;AAEI,YAAA;AACA,YAAA;AACE,cAAA;AACA,cAAA;AACA,cAAA;AAAK,YAAA;AAEP,YAAA;AACA,YAAA;AACF,UAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AACE,wBAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AAGS,UAAA;AAEP,QAAA;AACF,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAA4C,QAAA;AAE5C,QAAA;AACF,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACJ,IAAA;AACF,EAAA;AAEM,EAAA;AACJ,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACK,IAAA;AARc,EAAA;AAYf,EAAA;AACA,IAAA;AACE,IAAA;AACA,IAAA;AAHS,EAAA;AAQb,EAAA;AACE,EAAA;AACJ,IAAA;AAEQ,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACF,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACA,MAAA;AAED,IAAA;AACJ,EAAA;AACG,EAAA;AACE,IAAA;AACF,MAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACK,EAAA;AACL,IAAA;AACO,MAAA;AACH,QAAA;AACH,IAAA;AACH,EAAA;AAEM,EAAA;AACA,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACN,IAAA;AACE,MAAA;AACE,QAAA;AACA,QAAA;AACC,MAAA;AACH,MAAA;AACE,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACD,MAAA;AACD,MAAA;AACD,IAAA;AArBwB,EAAA;AAwBrB,EAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACN,IAAA;AACE,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,MAAA;AACD,IAAA;AAXe,EAAA;AAcZ,EAAA;AAEN,EAAA;AACM,IAAA;AACJ,IAAA;AACA,IAAA;AACM,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACE,MAAA;AACN,IAAA;AACE,MAAA;AACA,sBAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAjBe,EAAA;AAmBR,EAAA;AACD,IAAA;AACF,MAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACF;AAhOsB;AAsOtB;AACO,EAAA;AACC,EAAA;AACE,EAAA;AACA,EAAA;AACD,EAAA;AACT;AANsB;AJ2bZ;AACA;AKzuBV;AAOQ,EAAA;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AAEG,EAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACL,IAAA;AACI,IAAA;AACC,IAAA;AACP,EAAA;AACO,EAAA;AACC,IAAA;AACN,IAAA;AACA,IAAA;AACF,EAAA;AAEM,EAAA;AACA,IAAA;AACE,IAAA;AACN,IAAA;AACE,MAAA;AACA,MAAA;AACD,IAAA;AANU,EAAA;AAST,EAAA;AACA,EAAA;AACF,IAAA;AACF,EAAA;AACE,IAAA;AACM,IAAA;AACR,EAAA;AAEO,EAAA;AACL,IAAA;AACM,IAAA;AACN,IAAA;AACM,MAAA;AACA,MAAA;AACF,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AAPK,IAAA;AAST,EAAA;AACF;AAzDsB;AL2xBZ;AACA;AMtwBJ;AACA;AAEA;AAEA;AACA;AACA;AACA;AAMA;AACA,EAAA;AACF,IAAA;AACF,EAAA;AACI,EAAA;AACF,IAAA;AACF,EAAA;AACO,EAAA;AAPoB;AAUvB;AACE,EAAA;AACF,EAAA;AAFsB;AAMtB;AAEA;AACJ,EAAA;AACE,IAAA;AACI,IAAA;AACF,MAAA;AACF,IAAA;AAEO,MAAA;AACH,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAVY,EAAA;AAYd,EAAA;AACE,IAAA;AACM,IAAA;AAEF,MAAA;AACA,MAAA;AAHc,IAAA;AAKd,IAAA;AAEE,MAAA;AACJ,MAAA;AACF,IAAA;AACM,MAAA;AACF,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAhBY,EAAA;AAkBd,EAAA;AACM,IAAA;AADc,EAAA;AAGtB;AAEI;AAGA;AAGY;AACd,EAAA;AACA,EAAA;AACF;AAHgB;AAKV;AACJ,EAAA;AACO,EAAA;AAFW;AAKd;AACA,EAAA;AACI,IAAA;AACR,EAAA;AACO,IAAA;AACP,EAAA;AALsB;AAQlB;AACE,EAAA;AACN,EAAA;AACO,EAAA;AAHS;AAUZ;AACC,EAAA;AACD,EAAA;AACF,IAAA;AACF,EAAA;AACQ,IAAA;AACN,IAAA;AACA,IAAA;AACF,EAAA;AARsB;AAWlB;AAEA;AACE,EAAA;AACN,EAAA;AACK,EAAA;AACD,EAAA;AACF,IAAA;AACM,EAAA;AAER,EAAA;AARmB;AAgBL;AACT,EAAA;AACD,EAAA;AACF,IAAA;AACF,EAAA;AACK,EAAA;AACH,IAAA;AACF,EAAA;AACI,EAAA;AAEE,EAAA;AACA,EAAA;AAGE,IAAA;AACE,IAAA;AACP,EAAA;AACG,EAAA;AACA,IAAA;AACJ,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AAFa,MAAA;AAIf,MAAA;AACE,QAAA;AACA,QAAA;AAFgB,MAAA;AAInB,IAAA;AACF,EAAA;AACK,EAAA;AACN,EAAA;AACM,EAAA;AACA,IAAA;AACL,EAAA;AACM,EAAA;AACT;AArCgB;AAuCZ;AAMJ;AACE,EAAA;AAAmB;AAAA;AAA6D,IAAA;AAA2B,EAAA;AACxG,IAAA;AACH,EAAA;AACM,EAAA;AACA,EAAA;AACC,EAAA;AACL,IAAA;AACM,IAAA;AAEF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AALE,IAAA;AAON,IAAA;AACK,MAAA;AADE,IAAA;AAGT,EAAA;AACF;AAnBsB;AAsBtB;AACQ,EAAA;AACC,EAAA;AACT;AAHsB;AASb;AACH,EAAA;AACJ,EAAA;AACA,EAAA;AACF;AAJS;AAUT;AACM,EAAA;AACE,EAAA;AACD,EAAA;AACC,EAAA;AACE,IAAA;AACA,IAAA;AACN,IAAA;AACM,IAAA;AAJO,EAAA;AAMX,EAAA;AACC,EAAA;AACP;AAZsB;AAetB;AACM,EAAA;AACI,IAAA;AACD,IAAA;AACP,EAAA;AACM,EAAA;AACD,EAAA;AACE,EAAA;AACT;AARsB;AAgBtB;AACE,EAAA;AACI,EAAA;AAEE,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACN,EAAA;AACM,IAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AACM,IAAA;AACF,IAAA;AACN,EAAA;AACK,EAAA;AACG,IAAA;AACN,IAAA;AACM,IAAA;AACR,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACR,EAAA;AACF;AAzBsB;AA4BN;AACR,EAAA;AACC,EAAA;AACT;AAHgB;ANktBN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-PXDUJ47N.js","sourcesContent":[null,"// Artwork images for the device copy, kept in Cache Storage under the exact URL the stored\n// artwork record holds (`attributes.imageUrl`). The service worker answers those URLs from the\n// same cache when the network fails. The image downloads are plain GETs of the URLs the API\n// already returned (decision recorded in the plan).\n\n// The service worker names its caches `${appId}-images` and `${appId}-offline-${revision}`\n// (pwa/sw.js). The page does not load sw-config.js, so it finds the app id from those names;\n// Cache Storage is per origin, so only this app's caches are listed.\nconst IMAGES_CACHE_SUFFIX = \"-images\";\nconst OFFLINE_CACHE_MARKER = \"-offline-\";\n\nasync function imagesCacheName(): Promise<string | undefined> {\n if (typeof caches === \"undefined\") return undefined;\n const keys = await caches.keys();\n const existing = keys.find((key) => key.endsWith(IMAGES_CACHE_SUFFIX));\n if (existing) return existing;\n\n const offline = keys.find((key) => key.includes(OFFLINE_CACHE_MARKER));\n if (!offline) return undefined;\n return `${offline.slice(0, offline.indexOf(OFFLINE_CACHE_MARKER))}${IMAGES_CACHE_SUFFIX}`;\n}\n\n/** The image URL a stored resource carries, if any. */\nexport function imageUrlOf(resource: any): string | undefined {\n const url = resource?.attributes?.imageUrl;\n return typeof url === \"string\" && url.length > 0 ? url : undefined;\n}\n\n/**\n * Makes sure `url` is in the images cache: downloaded when missing. When `previousUrl` is a\n * different URL of the same artwork, its entry is removed. A failed download is logged and\n * retried at the next sync.\n */\nexport async function cacheArtworkImage(url: string, previousUrl?: string): Promise<void> {\n const name = await imagesCacheName();\n if (!name) return;\n const cache = await caches.open(name);\n\n if (previousUrl && previousUrl !== url) await cache.delete(previousUrl);\n if (await cache.match(url)) return;\n\n try {\n const response = await fetch(url);\n if (!response.ok) {\n console.warn(\"[offline] could not download an artwork image\", url, response.status);\n return;\n }\n await cache.put(url, response);\n } catch (error) {\n console.warn(\"[offline] could not download an artwork image\", url, error);\n }\n}\n\n/** Removes the cached copy of `url`. */\nexport async function removeArtworkImage(url: string): Promise<void> {\n const name = await imagesCacheName();\n if (!name) return;\n const cache = await caches.open(name);\n await cache.delete(url);\n}\n\n/** Deletes this app's whole images cache (logout, or another user signing in). */\nexport async function deleteImagesCaches(): Promise<void> {\n if (typeof caches === \"undefined\") return;\n const keys = await caches.keys();\n await Promise.all(keys.filter((key) => key.endsWith(IMAGES_CACHE_SUFFIX)).map((key) => caches.delete(key)));\n}\n","/**\n * Device-copy schema (spec § 1). Every statement is idempotent so it runs on\n * every open; `user_version` records the layout. A file of an older layout is\n * recreated (pre-release: no migration), see `DEVICE_STORE_RESET`.\n *\n * 2: `record_campaigns` (a shared record, e.g. an artwork or a template, belongs\n * to every root record that uses it) and `queue.package_id`.\n * `skipped_records` was added later inside layout 2: `CREATE TABLE IF NOT EXISTS` adds it to\n * an existing file without a reset (a reset would drop unsent changes).\n * 3: campaign columns and tables renamed to root (`campaign_id` -> `root_id`,\n * `record_campaigns` -> `record_roots`).\n */\nexport const DEVICE_STORE_SCHEMA_VERSION = 3;\n\n/**\n * Drops every table of an older layout before the schema is created again. Layout 2's\n * `record_campaigns` is dropped too, so a layout-2 file is fully recreated.\n */\nexport const DEVICE_STORE_RESET: string[] = [\n `DROP TABLE IF EXISTS records`,\n `DROP TABLE IF EXISTS record_campaigns`,\n `DROP TABLE IF EXISTS record_roots`,\n `DROP TABLE IF EXISTS links`,\n `DROP TABLE IF EXISTS queue`,\n `DROP TABLE IF EXISTS conflicts`,\n `DROP TABLE IF EXISTS sync_state`,\n `DROP TABLE IF EXISTS skipped_records`,\n];\n\nexport const DEVICE_STORE_SCHEMA: string[] = [\n `CREATE TABLE IF NOT EXISTS records (\n type TEXT NOT NULL,\n id TEXT NOT NULL,\n root_id TEXT NOT NULL,\n resource TEXT NOT NULL,\n updated_at TEXT,\n base TEXT,\n local_only INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (type, id)\n )`,\n `CREATE INDEX IF NOT EXISTS records_type_root ON records (type, root_id)`,\n `CREATE TABLE IF NOT EXISTS record_roots (\n type TEXT NOT NULL,\n id TEXT NOT NULL,\n root_id TEXT NOT NULL,\n PRIMARY KEY (type, id, root_id)\n )`,\n `CREATE INDEX IF NOT EXISTS record_roots_root ON record_roots (root_id, type)`,\n `CREATE TABLE IF NOT EXISTS links (\n from_id TEXT NOT NULL,\n from_type TEXT NOT NULL,\n to_id TEXT NOT NULL,\n to_type TEXT NOT NULL,\n kind TEXT NOT NULL,\n props TEXT NOT NULL DEFAULT '{}',\n root_id TEXT,\n PRIMARY KEY (from_id, to_id, kind)\n )`,\n `CREATE INDEX IF NOT EXISTS links_from ON links (from_id)`,\n `CREATE INDEX IF NOT EXISTS links_to ON links (to_id)`,\n `CREATE INDEX IF NOT EXISTS links_root ON links (root_id)`,\n `CREATE TABLE IF NOT EXISTS queue (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n root_id TEXT NOT NULL,\n request TEXT NOT NULL,\n base TEXT,\n created_at TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n package_id TEXT\n )`,\n `CREATE INDEX IF NOT EXISTS queue_status_seq ON queue (status, seq)`,\n `CREATE TABLE IF NOT EXISTS conflicts (\n id TEXT PRIMARY KEY,\n queue_seq INTEGER NOT NULL,\n root_id TEXT,\n kind TEXT NOT NULL,\n record_type TEXT,\n record_id TEXT,\n mine TEXT,\n theirs TEXT,\n base TEXT,\n merged TEXT,\n fields TEXT NOT NULL DEFAULT '[]',\n blocks TEXT,\n reason TEXT\n )`,\n `CREATE TABLE IF NOT EXISTS sync_state (\n root_id TEXT PRIMARY KEY,\n server_time TEXT,\n locked INTEGER NOT NULL DEFAULT 0,\n locked_from TEXT\n )`,\n `CREATE TABLE IF NOT EXISTS skipped_records (\n root_id TEXT NOT NULL,\n type TEXT NOT NULL,\n id TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (root_id, type, id)\n )`,\n `PRAGMA user_version = ${DEVICE_STORE_SCHEMA_VERSION}`,\n];\n","import type {\n Conflict,\n DeviceStore,\n QueueEntry,\n QueueStatus,\n SkippedRecord,\n StoredLink,\n StoredRecord,\n SyncState,\n} from \"./DeviceStore\";\nimport { DEVICE_STORE_RESET, DEVICE_STORE_SCHEMA, DEVICE_STORE_SCHEMA_VERSION } from \"./schema\";\n\n/** Runs one SQL statement with positional `?` binds and answers its rows as objects. */\nexport type ExecFn = (sql: string, bind?: unknown[]) => Promise<Record<string, unknown>[]>;\n\nexport type SqlDeviceStoreHooks = {\n /** Runs before a queue entry is written (the lifecycle marks the file as holding unsent changes). */\n beforeEnqueue?: () => Promise<void>;\n /** Runs after a committed change that can shrink the queue, with the number of entries left. */\n afterQueueChange?: (entries: number) => Promise<void>;\n /**\n * Runs each operation with exclusive use of the database across pages (the shared device copy:\n * several pages send statements to one database, and a transaction must not interleave).\n */\n exclusive?: <T>(fn: () => Promise<T>) => Promise<T>;\n};\n\nconst toJson = (value: unknown): string | null =>\n value === undefined || value === null ? null : JSON.stringify(value);\nconst fromJson = (value: unknown): any => (value === null || value === undefined ? null : JSON.parse(String(value)));\nconst toIso = (value: Date | null | undefined): string | null => (value ? value.toISOString() : null);\nconst fromIso = (value: unknown): Date | null =>\n value === null || value === undefined ? null : new Date(String(value));\n\nconst newId = (): string =>\n typeof globalThis.crypto?.randomUUID === \"function\"\n ? globalThis.crypto.randomUUID()\n : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;\n\nconst rowToRecord = (row: Record<string, unknown>): StoredRecord => ({\n type: String(row.type),\n id: String(row.id),\n rootId: String(row.root_id),\n resource: fromJson(row.resource),\n updatedAt: fromIso(row.updated_at),\n base: fromJson(row.base),\n localOnly: Number(row.local_only) === 1,\n});\n\nconst rowToLink = (row: Record<string, unknown>): StoredLink => ({\n fromType: String(row.from_type),\n fromId: String(row.from_id),\n toType: String(row.to_type),\n toId: String(row.to_id),\n kind: String(row.kind),\n props: fromJson(row.props) ?? {},\n});\n\nconst rowToQueueEntry = (row: Record<string, unknown>): QueueEntry => {\n const request = fromJson(row.request) ?? {};\n const entry: QueueEntry = {\n seq: Number(row.seq),\n rootId: String(row.root_id),\n method: request.method,\n path: request.path,\n base: fromJson(row.base),\n createdAt: new Date(String(row.created_at)),\n status: String(row.status) as QueueStatus,\n };\n if (request.body !== undefined) entry.body = request.body;\n if (request.recordType !== undefined) entry.recordType = request.recordType;\n if (request.recordId !== undefined) entry.recordId = request.recordId;\n if (request.resolution !== undefined) entry.resolution = request.resolution;\n if (row.package_id !== null && row.package_id !== undefined) entry.packageId = String(row.package_id);\n return entry;\n};\n\nconst rowToConflict = (row: Record<string, unknown>): Conflict => {\n const conflict: Conflict = {\n id: String(row.id),\n queueSeq: Number(row.queue_seq),\n kind: String(row.kind) as Conflict[\"kind\"],\n mine: fromJson(row.mine),\n theirs: fromJson(row.theirs),\n base: fromJson(row.base),\n fields: fromJson(row.fields) ?? [],\n };\n if (row.record_type !== null && row.record_type !== undefined) conflict.recordType = String(row.record_type);\n if (row.record_id !== null && row.record_id !== undefined) conflict.recordId = String(row.record_id);\n const merged = fromJson(row.merged);\n if (merged !== null) conflict.merged = merged;\n const blocks = fromJson(row.blocks);\n if (blocks !== null) conflict.blocks = blocks;\n if (row.reason !== null && row.reason !== undefined) conflict.reason = String(row.reason);\n return conflict;\n};\n\n/** The statements of every store operation, run over an exec that is already serialised. */\nconst createOperations = (exec: ExecFn, hooks: SqlDeviceStoreHooks) => {\n const insertLink = async (link: StoredLink, rootId: string | null) => {\n await exec(\n `INSERT OR REPLACE INTO links (from_id, from_type, to_id, to_type, kind, props, root_id) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [link.fromId, link.fromType, link.toId, link.toType, link.kind, JSON.stringify(link.props ?? {}), rootId],\n );\n };\n\n const deleteRecordEverywhere = async (type: string, id: string) => {\n await exec(`DELETE FROM records WHERE type = ? AND id = ?`, [type, id]);\n await exec(`DELETE FROM record_roots WHERE type = ? AND id = ?`, [type, id]);\n await exec(`DELETE FROM links WHERE from_id = ? OR to_id = ?`, [id, id]);\n };\n\n return {\n async getRecord(type: string, id: string): Promise<StoredRecord | undefined> {\n const rows = await exec(`SELECT * FROM records WHERE type = ? AND id = ?`, [type, id]);\n return rows[0] ? rowToRecord(rows[0]) : undefined;\n },\n\n async listRecords(filter: { type: string; rootId?: string; ids?: string[] }): Promise<StoredRecord[]> {\n if (filter.ids && filter.ids.length === 0) return [];\n const where = [\"type = ?\"];\n const bind: unknown[] = [filter.type];\n if (filter.rootId !== undefined) {\n where.push(\n \"EXISTS (SELECT 1 FROM record_roots rc WHERE rc.type = records.type AND rc.id = records.id AND rc.root_id = ?)\",\n );\n bind.push(filter.rootId);\n }\n if (filter.ids) {\n where.push(`id IN (${filter.ids.map(() => \"?\").join(\", \")})`);\n bind.push(...filter.ids);\n }\n const rows = await exec(`SELECT * FROM records WHERE ${where.join(\" AND \")} ORDER BY rowid`, bind);\n return rows.map(rowToRecord);\n },\n\n async putRecord(\n record: Omit<StoredRecord, \"base\" | \"localOnly\"> & { base?: any | null; localOnly?: boolean },\n ): Promise<void> {\n // A record already stored keeps its first root record; the new one is added to its memberships.\n await exec(\n `INSERT INTO records (type, id, root_id, resource, updated_at, base, local_only) VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (type, id) DO UPDATE SET resource = excluded.resource, updated_at = excluded.updated_at,\n base = excluded.base, local_only = excluded.local_only`,\n [\n record.type,\n record.id,\n record.rootId,\n JSON.stringify(record.resource),\n toIso(record.updatedAt),\n toJson(record.base),\n record.localOnly ? 1 : 0,\n ],\n );\n await exec(`INSERT OR IGNORE INTO record_roots (type, id, root_id) VALUES (?, ?, ?)`, [\n record.type,\n record.id,\n record.rootId,\n ]);\n },\n\n async deleteRecord(type: string, id: string): Promise<void> {\n await deleteRecordEverywhere(type, id);\n },\n\n async removeRecordFromRoot(type: string, id: string, rootId: string): Promise<void> {\n await exec(`DELETE FROM record_roots WHERE type = ? AND id = ? AND root_id = ?`, [type, id, rootId]);\n const left = await exec(`SELECT root_id FROM record_roots WHERE type = ? AND id = ? ORDER BY root_id`, [\n type,\n id,\n ]);\n if (left.length === 0) {\n await deleteRecordEverywhere(type, id);\n return;\n }\n await exec(`UPDATE records SET root_id = ? WHERE type = ? AND id = ? AND root_id = ?`, [\n String(left[0].root_id),\n type,\n id,\n rootId,\n ]);\n },\n\n async linksFrom(fromId: string, kind?: string): Promise<StoredLink[]> {\n const rows =\n kind === undefined\n ? await exec(`SELECT * FROM links WHERE from_id = ? ORDER BY rowid`, [fromId])\n : await exec(`SELECT * FROM links WHERE from_id = ? AND kind = ? ORDER BY rowid`, [fromId, kind]);\n return rows.map(rowToLink);\n },\n\n async linksTo(toId: string, kind?: string): Promise<StoredLink[]> {\n const rows =\n kind === undefined\n ? await exec(`SELECT * FROM links WHERE to_id = ? ORDER BY rowid`, [toId])\n : await exec(`SELECT * FROM links WHERE to_id = ? AND kind = ? ORDER BY rowid`, [toId, kind]);\n return rows.map(rowToLink);\n },\n\n async replaceRootLinks(rootId: string, links: StoredLink[]): Promise<void> {\n await exec(`DELETE FROM links WHERE root_id = ?`, [rootId]);\n for (const link of links) await insertLink(link, rootId);\n },\n\n async putLink(link: StoredLink): Promise<void> {\n // A link belongs to the root record of the record at either end, so a later\n // replaceRootLinks / removeRoot of that root record reaches it.\n const owner = await exec(`SELECT root_id FROM records WHERE id IN (?, ?) ORDER BY id = ? DESC LIMIT 1`, [\n link.fromId,\n link.toId,\n link.fromId,\n ]);\n await insertLink(link, owner[0] ? String(owner[0].root_id) : null);\n },\n\n async deleteLink(link: Pick<StoredLink, \"fromId\" | \"toId\" | \"kind\">): Promise<void> {\n await exec(`DELETE FROM links WHERE from_id = ? AND to_id = ? AND kind = ?`, [link.fromId, link.toId, link.kind]);\n },\n\n async enqueue(entry: Omit<QueueEntry, \"seq\" | \"createdAt\" | \"status\">): Promise<number> {\n if (hooks.beforeEnqueue) await hooks.beforeEnqueue();\n const request = {\n method: entry.method,\n path: entry.path,\n body: entry.body,\n recordType: entry.recordType,\n recordId: entry.recordId,\n resolution: entry.resolution,\n };\n const rows = await exec(\n `INSERT INTO queue (root_id, request, base, created_at, status) VALUES (?, ?, ?, ?, 'pending') RETURNING seq`,\n [entry.rootId, JSON.stringify(request), toJson(entry.base), new Date().toISOString()],\n );\n return Number(rows[0].seq);\n },\n\n async queue(filter?: { status?: QueueStatus }): Promise<QueueEntry[]> {\n const rows = filter?.status\n ? await exec(`SELECT * FROM queue WHERE status = ? ORDER BY seq`, [filter.status])\n : await exec(`SELECT * FROM queue ORDER BY seq`);\n return rows.map(rowToQueueEntry);\n },\n\n async setQueueStatus(seq: number, status: QueueStatus): Promise<void> {\n await exec(`UPDATE queue SET status = ? WHERE seq = ?`, [status, seq]);\n },\n\n async setQueuePackage(seqs: number[], packageId: string | null): Promise<void> {\n if (seqs.length === 0) return;\n await exec(`UPDATE queue SET package_id = ? WHERE seq IN (${seqs.map(() => \"?\").join(\", \")})`, [\n packageId,\n ...seqs,\n ]);\n },\n\n async removeQueueEntry(seq: number): Promise<void> {\n await exec(`DELETE FROM queue WHERE seq = ?`, [seq]);\n },\n\n async addConflict(c: Omit<Conflict, \"id\">): Promise<string> {\n const id = newId();\n const owner = await exec(`SELECT root_id FROM queue WHERE seq = ?`, [c.queueSeq]);\n await exec(\n `INSERT INTO conflicts (id, queue_seq, root_id, kind, record_type, record_id, mine, theirs, base, merged, fields, blocks, reason)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n id,\n c.queueSeq,\n owner[0] ? String(owner[0].root_id) : null,\n c.kind,\n c.recordType ?? null,\n c.recordId ?? null,\n toJson(c.mine),\n toJson(c.theirs),\n toJson(c.base),\n toJson(c.merged),\n JSON.stringify(c.fields ?? []),\n toJson(c.blocks),\n c.reason ?? null,\n ],\n );\n return id;\n },\n\n async conflicts(): Promise<Conflict[]> {\n const rows = await exec(`SELECT * FROM conflicts ORDER BY queue_seq, rowid`);\n return rows.map(rowToConflict);\n },\n\n async removeConflict(id: string): Promise<void> {\n await exec(`DELETE FROM conflicts WHERE id = ?`, [id]);\n },\n\n async getSyncState(rootId: string): Promise<SyncState> {\n const rows = await exec(`SELECT * FROM sync_state WHERE root_id = ?`, [rootId]);\n if (!rows[0]) return { serverTime: null, locked: false, lockedFrom: null };\n return {\n serverTime: fromIso(rows[0].server_time),\n locked: Number(rows[0].locked) === 1,\n lockedFrom: fromIso(rows[0].locked_from),\n };\n },\n\n async setSyncState(rootId: string, s: SyncState): Promise<void> {\n await exec(`INSERT OR REPLACE INTO sync_state (root_id, server_time, locked, locked_from) VALUES (?, ?, ?, ?)`, [\n rootId,\n toIso(s.serverTime),\n s.locked ? 1 : 0,\n toIso(s.lockedFrom),\n ]);\n },\n\n async skippedRecords(rootId: string): Promise<SkippedRecord[]> {\n const rows = await exec(`SELECT * FROM skipped_records WHERE root_id = ? ORDER BY type, id`, [rootId]);\n return rows.map((row) => ({\n type: String(row.type),\n id: String(row.id),\n updatedAt: new Date(String(row.updated_at)),\n }));\n },\n\n async setSkippedRecords(rootId: string, records: SkippedRecord[]): Promise<void> {\n await exec(`DELETE FROM skipped_records WHERE root_id = ?`, [rootId]);\n for (const record of records) {\n await exec(`INSERT OR REPLACE INTO skipped_records (root_id, type, id, updated_at) VALUES (?, ?, ?, ?)`, [\n rootId,\n record.type,\n record.id,\n record.updatedAt.toISOString(),\n ]);\n }\n },\n\n async removeRoot(rootId: string): Promise<void> {\n // Waiting changes are never dropped: a root record with queue entries is kept whole.\n const waiting = await exec(`SELECT COUNT(*) AS n FROM queue WHERE root_id = ?`, [rootId]);\n if (Number(waiting[0]?.n ?? 0) > 0) return;\n // A shared record another root record still uses stays for that root record; every other\n // record of this root record goes, with its links.\n await exec(`DELETE FROM links WHERE root_id = ?`, [rootId]);\n await exec(`DELETE FROM record_roots WHERE root_id = ?`, [rootId]);\n const orphans = `SELECT id FROM records r WHERE NOT EXISTS (SELECT 1 FROM record_roots rc WHERE rc.type = r.type AND rc.id = r.id)`;\n await exec(`DELETE FROM links WHERE from_id IN (${orphans}) OR to_id IN (${orphans})`);\n await exec(\n `DELETE FROM records WHERE NOT EXISTS (SELECT 1 FROM record_roots rc WHERE rc.type = records.type AND rc.id = records.id)`,\n );\n await exec(\n `UPDATE records SET root_id = (SELECT MIN(rc.root_id) FROM record_roots rc WHERE rc.type = records.type AND rc.id = records.id) WHERE root_id = ?`,\n [rootId],\n );\n await exec(`DELETE FROM conflicts WHERE root_id = ?`, [rootId]);\n await exec(`DELETE FROM sync_state WHERE root_id = ?`, [rootId]);\n await exec(`DELETE FROM skipped_records WHERE root_id = ?`, [rootId]);\n },\n\n async queueSize(): Promise<number> {\n const rows = await exec(`SELECT COUNT(*) AS n FROM queue`);\n return Number(rows[0]?.n ?? 0);\n },\n };\n};\n\ntype Operations = ReturnType<typeof createOperations>;\n\n/** A store whose `transaction` runs `fn` directly: used inside an open transaction. */\nconst nestedStore = (ops: Operations): DeviceStore => {\n const { queueSize: _queueSize, ...rest } = ops;\n const store: DeviceStore = { ...rest, transaction: (fn) => fn(store) };\n return store;\n};\n\n/**\n * The device store over any SQLite `exec` (the worker's OPFS file, or an\n * in-memory database in tests). Every operation is serialised, so a\n * transaction never interleaves with another caller's statements.\n */\nexport async function createSqlDeviceStore(exec: ExecFn, hooks: SqlDeviceStoreHooks = {}): Promise<DeviceStore> {\n const exclusive = hooks.exclusive ?? (<T>(fn: () => Promise<T>) => fn());\n\n // A file of an older layout is recreated (pre-release: no migration of device files).\n await exclusive(async () => {\n const version = Number((await exec(`PRAGMA user_version`))[0]?.user_version ?? 0);\n if (version > 0 && version < DEVICE_STORE_SCHEMA_VERSION) {\n for (const statement of DEVICE_STORE_RESET) await exec(statement);\n }\n for (const statement of DEVICE_STORE_SCHEMA) await exec(statement);\n });\n\n const ops = createOperations(exec, hooks);\n let tail: Promise<unknown> = Promise.resolve();\n const serialised = <T>(fn: () => Promise<T>): Promise<T> => {\n const run = tail.then(\n () => exclusive(fn),\n () => exclusive(fn),\n );\n tail = run.catch(() => undefined);\n return run;\n };\n const settleQueue = async () => {\n if (hooks.afterQueueChange) await hooks.afterQueueChange(await ops.queueSize());\n };\n\n const store: DeviceStore = {\n getRecord: (type, id) => serialised(() => ops.getRecord(type, id)),\n listRecords: (filter) => serialised(() => ops.listRecords(filter)),\n putRecord: (record) => serialised(() => ops.putRecord(record)),\n deleteRecord: (type, id) => serialised(() => ops.deleteRecord(type, id)),\n removeRecordFromRoot: (type, id, rootId) => serialised(() => ops.removeRecordFromRoot(type, id, rootId)),\n linksFrom: (fromId, kind) => serialised(() => ops.linksFrom(fromId, kind)),\n linksTo: (toId, kind) => serialised(() => ops.linksTo(toId, kind)),\n replaceRootLinks: (rootId, links) =>\n serialised(async () => {\n await exec(\"BEGIN\");\n try {\n await ops.replaceRootLinks(rootId, links);\n await exec(\"COMMIT\");\n } catch (error) {\n await exec(\"ROLLBACK\");\n throw error;\n }\n }),\n putLink: (link) => serialised(() => ops.putLink(link)),\n deleteLink: (link) => serialised(() => ops.deleteLink(link)),\n enqueue: (entry) => serialised(() => ops.enqueue(entry)),\n queue: (filter) => serialised(() => ops.queue(filter)),\n setQueueStatus: (seq, status) => serialised(() => ops.setQueueStatus(seq, status)),\n setQueuePackage: (seqs, packageId) => serialised(() => ops.setQueuePackage(seqs, packageId)),\n removeQueueEntry: (seq) =>\n serialised(async () => {\n await ops.removeQueueEntry(seq);\n await settleQueue();\n }),\n addConflict: (c) => serialised(() => ops.addConflict(c)),\n conflicts: () => serialised(() => ops.conflicts()),\n removeConflict: (id) => serialised(() => ops.removeConflict(id)),\n getSyncState: (rootId) => serialised(() => ops.getSyncState(rootId)),\n setSyncState: (rootId, s) => serialised(() => ops.setSyncState(rootId, s)),\n skippedRecords: (rootId) => serialised(() => ops.skippedRecords(rootId)),\n setSkippedRecords: (rootId, records) => serialised(() => ops.setSkippedRecords(rootId, records)),\n removeRoot: (rootId) =>\n serialised(async () => {\n await exec(\"BEGIN\");\n try {\n await ops.removeRoot(rootId);\n await exec(\"COMMIT\");\n } catch (error) {\n await exec(\"ROLLBACK\");\n throw error;\n }\n await settleQueue();\n }),\n transaction: <T>(fn: (tx: DeviceStore) => Promise<T>) =>\n serialised(async () => {\n await exec(\"BEGIN\");\n let result: T;\n try {\n result = await fn(nestedStore(ops));\n await exec(\"COMMIT\");\n } catch (error) {\n await exec(\"ROLLBACK\");\n await settleQueue();\n throw error;\n }\n await settleQueue();\n return result;\n }),\n };\n return store;\n}\n","import type { ExecFn } from \"./sql-device-store\";\nimport type { OpenedDatabase } from \"./worker-client\";\n\n/**\n * One device copy shared by every page of the origin (tabs, the installed app, and the page that\n * is still unloading while the next one loads in the same tab).\n *\n * The browser lets only one page at a time hold the copy's files (opfs-sahpool: one set of access\n * handles per origin). So one page, the holder, opens the database; the others send their\n * statements to it over a BroadcastChannel. The holder is whoever holds the Web Lock\n * `nja-offline-db:<userId>`; every other page waits in that lock's queue and takes over when the\n * holder closes or goes away, retrying the open while the previous holder's files are released.\n *\n * Each store operation runs with exclusive use of the database across all pages (`exclusive`), so\n * a transaction of one page never interleaves with another page's statements.\n */\nexport type SharedDatabase = OpenedDatabase & {\n /** Runs `fn` with exclusive use of the database across every page. */\n exclusive: <T>(fn: () => Promise<T>) => Promise<T>;\n};\n\ntype Message =\n | { kind: \"holder\" }\n | { kind: \"lease\"; id: string }\n | { kind: \"granted\"; id: string }\n | { kind: \"release\"; id: string }\n | { kind: \"exec\"; id: string; lease: string; sql: string; bind?: unknown[] }\n | { kind: \"result\"; id: string; ok: true; rows: Record<string, unknown>[] }\n | { kind: \"result\"; id: string; ok: false; error: string }\n | { kind: \"shutdown\" };\n\n/** How long the holder keeps a lease for a page that never releases it (the page closed). */\nconst LEASE_TIMEOUT_MS = 30_000;\n/** How long a page waits for an answer before giving up on a statement. */\nconst EXEC_TIMEOUT_MS = 30_000;\n/** How long the next holder keeps retrying the open while the previous holder's files are released. */\nconst OPEN_RETRY_MS = 10_000;\n\nconst lockName = (userId: string) => `nja-offline-db:${userId}`;\nconst channelName = (userId: string) => `nja-offline-db:${userId}`;\n\nconst newId = (): string => globalThis.crypto.randomUUID();\n\n/** Whether this browser offers what sharing needs; without it the page opens the copy directly. */\nexport function canShareDatabase(): boolean {\n return (\n typeof BroadcastChannel !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n typeof navigator.locks?.request === \"function\"\n );\n}\n\n/** The files are still held by the page that had them (it is closing): worth retrying. */\nconst isHeldElsewhere = (error: unknown): boolean =>\n /NoModificationAllowedError|Access Handles cannot be created|createSyncAccessHandle/i.test(\n String((error as Error)?.message ?? error),\n );\n\nasync function openWithRetry(openLocal: () => Promise<OpenedDatabase>): Promise<OpenedDatabase> {\n const deadline = Date.now() + OPEN_RETRY_MS;\n for (let wait = 100; ; wait = Math.min(wait * 2, 1000)) {\n try {\n return await openLocal();\n } catch (error) {\n if (!isHeldElsewhere(error) || Date.now() >= deadline) throw error;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n }\n}\n\n/** A promise chain: one `fn` at a time. */\nfunction createMutex() {\n let tail: Promise<unknown> = Promise.resolve();\n return <T>(fn: () => Promise<T>): Promise<T> => {\n const run = tail.then(fn, fn);\n tail = run.catch(() => undefined);\n return run;\n };\n}\n\n/**\n * Opens the shared device copy of `userId`. `openLocal` opens the database files in this page\n * (the worker); it runs only in the page that holds the lock.\n */\nexport async function openSharedDatabase(\n userId: string,\n openLocal: () => Promise<OpenedDatabase>,\n /** Called when another page asks every page to close the copy (logout, offline mode off). */\n onShutdown?: () => void,\n): Promise<SharedDatabase> {\n const channel = new BroadcastChannel(channelName(userId));\n const post = (message: Message) => channel.postMessage(message);\n const mutex = createMutex();\n\n let local: OpenedDatabase | undefined;\n let created = false;\n let closed = false;\n let releaseLock: (() => void) | undefined;\n const lockAbort = new AbortController();\n\n // Holder side: leases granted to other pages, released by them (or after a timeout).\n const leases = new Map<string, () => void>();\n // Page side: leases asked for and not yet granted, and statements waiting for an answer.\n const waitingLeases = new Map<\n string,\n { run: () => Promise<unknown>; resolve: (v: unknown) => void; reject: (e: Error) => void }\n >();\n const waitingResults = new Map<\n string,\n { resolve: (rows: Record<string, unknown>[]) => void; reject: (e: Error) => void }\n >();\n let currentLease: string | undefined;\n\n const isHolder = () => local !== undefined;\n\n /** Runs a lease granted by the holder: `run` sends its statements under that lease. */\n const runGranted = async (id: string) => {\n const waiting = waitingLeases.get(id);\n if (!waiting) {\n post({ kind: \"release\", id });\n return;\n }\n waitingLeases.delete(id);\n currentLease = id;\n try {\n waiting.resolve(await waiting.run());\n } catch (error) {\n waiting.reject(error as Error);\n } finally {\n currentLease = undefined;\n post({ kind: \"release\", id });\n }\n };\n\n channel.onmessage = (event: MessageEvent<Message>) => {\n const message = event.data;\n if (closed) return;\n switch (message.kind) {\n case \"holder\":\n // A new holder: the lease requests the old one never answered are asked again.\n if (!isHolder()) for (const id of waitingLeases.keys()) post({ kind: \"lease\", id });\n return;\n case \"lease\":\n if (!isHolder() || leases.has(message.id)) return;\n void mutex(\n () =>\n new Promise<void>((done) => {\n const timer = setTimeout(() => finish(), LEASE_TIMEOUT_MS);\n const finish = () => {\n clearTimeout(timer);\n leases.delete(message.id);\n done();\n };\n leases.set(message.id, finish);\n post({ kind: \"granted\", id: message.id });\n }),\n );\n return;\n case \"granted\":\n if (!isHolder()) void runGranted(message.id);\n return;\n case \"release\":\n leases.get(message.id)?.();\n return;\n case \"exec\":\n if (!isHolder() || !leases.has(message.lease)) return;\n local!\n .exec(message.sql, message.bind)\n .then((rows) => post({ kind: \"result\", id: message.id, ok: true, rows }))\n .catch((error) =>\n post({ kind: \"result\", id: message.id, ok: false, error: String(error?.message ?? error) }),\n );\n return;\n case \"result\": {\n const waiting = waitingResults.get(message.id);\n if (!waiting) return;\n waitingResults.delete(message.id);\n if (message.ok) waiting.resolve(message.rows);\n else waiting.reject(new Error(message.error));\n return;\n }\n case \"shutdown\":\n void close().finally(() => onShutdown?.());\n return;\n }\n };\n\n const becomeHolder = async () => {\n local = await openWithRetry(openLocal);\n created = local.created;\n // Leases this page asked for and nobody granted: they run here now.\n for (const [id, waiting] of waitingLeases) {\n waitingLeases.delete(id);\n mutex(waiting.run).then(waiting.resolve, waiting.reject);\n }\n post({ kind: \"holder\" });\n };\n\n /** Holds the lock as the holder until `close`; the lock passes to the next page when this ends. */\n const holdLock = async (): Promise<void> => {\n if (closed) return;\n await becomeHolder();\n await new Promise<void>((resolve) => (releaseLock = resolve));\n };\n\n // Free right now: this page is the holder. Held by another page: this page starts as its client\n // and queues for the lock, to take over when the holder leaves.\n let opened: Promise<void> | undefined;\n const isFree = await new Promise<boolean>((answer) => {\n navigator.locks\n .request(lockName(userId), { ifAvailable: true }, async (lock) => {\n if (!lock) {\n answer(false);\n return;\n }\n answer(true);\n opened = becomeHolder();\n try {\n await opened;\n } catch {\n return; // Could not open: the lock is released for the next page.\n }\n await new Promise<void>((resolve) => (releaseLock = resolve));\n })\n .catch(() => answer(false));\n });\n if (isFree) {\n try {\n await opened;\n } catch (error) {\n closed = true;\n channel.close();\n throw error;\n }\n } else {\n navigator.locks.request(lockName(userId), { signal: lockAbort.signal }, holdLock).catch((error) => {\n if ((error as { name?: string })?.name !== \"AbortError\")\n console.warn(\"[offline] taking over the device copy failed\", error);\n });\n }\n\n const remoteExec: ExecFn = (sql, bind) => {\n if (closed) return Promise.reject(new Error(\"The device database is closed.\"));\n const lease = currentLease;\n if (!lease) return exclusive(() => remoteExec(sql, bind));\n const id = newId();\n return new Promise<Record<string, unknown>[]>((resolve, reject) => {\n const timer = setTimeout(() => {\n waitingResults.delete(id);\n reject(new Error(\"The page holding the device copy did not answer.\"));\n }, EXEC_TIMEOUT_MS);\n waitingResults.set(id, {\n resolve: (rows) => {\n clearTimeout(timer);\n resolve(rows);\n },\n reject: (error) => {\n clearTimeout(timer);\n reject(error);\n },\n });\n post({ kind: \"exec\", id, lease, sql, bind });\n });\n };\n\n const exclusive = <T>(fn: () => Promise<T>): Promise<T> => {\n if (closed) return Promise.reject(new Error(\"The device database is closed.\"));\n if (isHolder()) return mutex(fn);\n const id = newId();\n return new Promise<T>((resolve, reject) => {\n waitingLeases.set(id, {\n run: fn as () => Promise<unknown>,\n resolve: resolve as (v: unknown) => void,\n reject,\n });\n post({ kind: \"lease\", id });\n });\n };\n\n const exec: ExecFn = (sql, bind) => (isHolder() ? local!.exec(sql, bind) : remoteExec(sql, bind));\n\n async function close(): Promise<void> {\n if (closed) return;\n closed = true;\n lockAbort.abort();\n const error = new Error(\"The device database is closed.\");\n for (const waiting of waitingLeases.values()) waiting.reject(error);\n for (const waiting of waitingResults.values()) waiting.reject(error);\n waitingLeases.clear();\n waitingResults.clear();\n for (const finish of [...leases.values()]) finish();\n try {\n if (local) await local.close();\n } finally {\n local = undefined;\n releaseLock?.();\n channel.close();\n }\n }\n\n return {\n get created() {\n return created;\n },\n exec,\n exclusive,\n close,\n };\n}\n\n/**\n * Asks every page to close the shared copy of `userId`, then runs `fn` once no page holds it\n * (logout, offline mode turned off: the files can only be deleted when nobody has them open).\n */\nexport async function withDatabaseReleased<T>(userId: string, fn: () => Promise<T>): Promise<T> {\n if (!canShareDatabase()) return fn();\n const channel = new BroadcastChannel(channelName(userId));\n channel.postMessage({ kind: \"shutdown\" } satisfies Message);\n channel.close();\n return navigator.locks.request(lockName(userId), () => fn()) as Promise<T>;\n}\n","import type { ExecFn } from \"./sql-device-store\";\nimport type { WorkerRequest, WorkerResponse } from \"./worker\";\n\nexport type OpenedDatabase = {\n exec: ExecFn;\n close: () => Promise<void>;\n /** True when the file did not exist before this open. */\n created: boolean;\n};\n\ntype Pending = { resolve: (value: unknown) => void; reject: (error: Error) => void };\n\n/** Starts the device-copy worker and opens `fileName` in it for `userId`. */\nexport async function openWorkerDatabase(\n userId: string,\n fileName: string,\n urls: { workerUrl: string; sqliteModuleUrl: string },\n): Promise<OpenedDatabase> {\n // A plain URL, so the app's bundler leaves it alone: the worker is a standalone file the app\n // serves (OfflineConfig.deviceWorkerUrl), precached by the service worker for offline use.\n const worker = new Worker(urls.workerUrl, { type: \"module\" });\n const { sqliteModuleUrl } = urls;\n const pending = new Map<number, Pending>();\n let nextId = 1;\n let closed = false;\n\n worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n const response = event.data;\n const waiting = pending.get(response.id);\n if (!waiting) return;\n pending.delete(response.id);\n if (response.ok) waiting.resolve(response.result);\n else waiting.reject(new Error(response.error));\n };\n worker.onerror = (event: ErrorEvent) => {\n const error = new Error(event.message || \"The device database worker failed.\");\n for (const waiting of pending.values()) waiting.reject(error);\n pending.clear();\n };\n\n const send = <T>(request: Omit<WorkerRequest, \"id\"> & { op: WorkerRequest[\"op\"] }): Promise<T> => {\n if (closed) return Promise.reject(new Error(\"The device database is closed.\"));\n const id = nextId++;\n return new Promise<T>((resolve, reject) => {\n pending.set(id, { resolve: resolve as (value: unknown) => void, reject });\n worker.postMessage({ ...request, id } as WorkerRequest);\n });\n };\n\n let opened: { created: boolean };\n try {\n opened = await send<{ created: boolean }>({ op: \"open\", userId, fileName, sqliteModuleUrl } as WorkerRequest);\n } catch (error) {\n worker.terminate();\n throw error;\n }\n\n return {\n created: opened.created,\n exec: (sql, bind) => send<Record<string, unknown>[]>({ op: \"exec\", sql, bind } as WorkerRequest),\n close: async () => {\n if (closed) return;\n try {\n await send({ op: \"close\" } as WorkerRequest);\n } finally {\n closed = true;\n worker.terminate();\n }\n },\n };\n}\n","import type { Sqlite3Static } from \"@sqlite.org/sqlite-wasm\";\nimport {\n DeviceStoreUnavailableError,\n forgetOfflineStore,\n getDeviceWorkerUrl,\n getSqliteModuleUrl,\n isOfflineAvailable,\n isOfflineStoreBlocked,\n markOfflineUnavailable,\n OfflineUnavailableError,\n refreshOfflineDeviceState,\n unblockOfflineStore,\n} from \"../config\";\nimport { deleteImagesCaches } from \"../sync/artwork-cache\";\nimport type { DeviceStore } from \"./DeviceStore\";\nimport { createSqlDeviceStore } from \"./sql-device-store\";\nimport { canShareDatabase, openSharedDatabase, withDatabaseReleased, type SharedDatabase } from \"./shared-database\";\nimport { openWorkerDatabase, type OpenedDatabase } from \"./worker-client\";\n\n/** The part of an OPFS `FileSystemDirectoryHandle` the lifecycle uses (faked in tests). */\nexport type OfflineDirectory = {\n keys(): AsyncIterable<string>;\n getFileHandle(name: string, options?: { create?: boolean }): Promise<unknown>;\n removeEntry(name: string, options?: { recursive?: boolean }): Promise<void>;\n};\n\nexport type DeviceStoreBackend = {\n /** The OPFS root that holds every user's device file. */\n getDirectory: () => Promise<OfflineDirectory>;\n /** Opens `fileName` for `userId` (the worker in the browser). */\n openDatabase: (userId: string, fileName: string) => Promise<OpenedDatabase>;\n /** Asks the browser to keep the device copy (`navigator.storage.persist()`). */\n requestPersistence: () => Promise<void>;\n};\n\nconst FILE_PREFIX = \"nja-offline-\";\nconst deviceFileName = (userId: string) => `${FILE_PREFIX}${userId}.sqlite3`;\n/** Present while the user's file holds unsent changes; read without opening the file. */\nconst pendingMarkerName = (userId: string) => `${FILE_PREFIX}${userId}.pending`;\n/** Names the user whose artwork the images cache holds (the images cache is not per user). */\nconst IMAGES_OWNER_PREFIX = \"nja-offline-images-owner-\";\nconst imagesOwnerName = (userId: string) => `${IMAGES_OWNER_PREFIX}${userId}`;\nconst FILE_PATTERN = /^nja-offline-(.+)\\.(?:sqlite3(?:-journal|-wal|-shm)?|pending)$/;\nconst POOL_PATTERN = /^\\.nja-offline-(.+)$/;\n\n/**\n * Why this browser cannot keep the device copy, or null when it may: browsers expose the private\n * file storage only in a secure context (https, or `localhost`). Checked before any worker starts.\n */\nconst missingDeviceStorage = (): string | null => {\n if (typeof window !== \"undefined\" && window.isSecureContext === false) {\n return \"the page is not served from a secure origin (https or localhost)\";\n }\n if (typeof navigator === \"undefined\" || typeof navigator.storage?.getDirectory !== \"function\") {\n return \"the browser offers no private file storage\";\n }\n return null;\n};\n\nconst assertDeviceStorage = (): void => {\n const reason = missingDeviceStorage();\n if (reason) throw new OfflineUnavailableError(reason);\n};\n\n/** What the SQLite worker answers when the browser lacks the OPFS sync-access handles. */\nconst OPFS_MISSING = /Missing required OPFS APIs/i;\n\nconst defaultBackend: DeviceStoreBackend = {\n getDirectory: async () => {\n assertDeviceStorage();\n try {\n return (await navigator.storage.getDirectory()) as unknown as OfflineDirectory;\n } catch (error) {\n // Storage refused outright (some private windows).\n if ((error as { name?: string })?.name === \"SecurityError\") {\n throw new OfflineUnavailableError(\"the browser refused its private file storage\");\n }\n throw error;\n }\n },\n openDatabase: async (userId, fileName) => {\n assertDeviceStorage();\n const openLocal = () =>\n openWorkerDatabase(userId, fileName, {\n workerUrl: getDeviceWorkerUrl(),\n sqliteModuleUrl: getSqliteModuleUrl(),\n });\n try {\n // Shared by every page of the origin: only one page at a time can hold the files.\n if (!canShareDatabase()) return await openLocal();\n return await openSharedDatabase(userId, openLocal, () => forgetClosedStore(userId));\n } catch (error) {\n if (OPFS_MISSING.test(String((error as Error)?.message ?? error))) {\n throw new OfflineUnavailableError(\"the browser lacks the file access the device copy needs (OPFS)\");\n }\n throw error;\n }\n },\n requestPersistence: async () => {\n if (typeof navigator !== \"undefined\" && navigator.storage?.persist) await navigator.storage.persist();\n },\n};\n\nlet backend: DeviceStoreBackend = defaultBackend;\n\ntype OpenStore = { userId: string; store: Promise<DeviceStore>; database: Promise<OpenedDatabase> };\nlet current: OpenStore | undefined;\n\n/** Replaces the OPFS directory / database opener (tests); `null` restores the browser one. */\nexport function setDeviceStoreBackend(next: Partial<DeviceStoreBackend> | null): void {\n backend = next ? { ...defaultBackend, ...next } : defaultBackend;\n current = undefined;\n}\n\nconst entryExists = async (directory: OfflineDirectory, name: string): Promise<boolean> => {\n for await (const key of directory.keys()) if (key === name) return true;\n return false;\n};\n\nconst removeIfPresent = async (directory: OfflineDirectory, name: string): Promise<void> => {\n try {\n await directory.removeEntry(name, { recursive: true });\n } catch (error) {\n if ((error as { name?: string })?.name !== \"NotFoundError\") throw error;\n }\n};\n\nconst entriesOf = async (directory: OfflineDirectory, userId: string): Promise<string[]> => {\n const names: string[] = [];\n for await (const key of directory.keys()) if (userIdOf(key) === userId) names.push(key);\n return names;\n};\n\n/**\n * The OPFS root, or undefined when this browser cannot keep the device copy: offline mode is then\n * switched off (`markOfflineUnavailable`). Any other failure is thrown as before.\n */\nconst deviceDirectory = async (): Promise<OfflineDirectory | undefined> => {\n if (!isOfflineAvailable()) return undefined;\n try {\n return await backend.getDirectory();\n } catch (error) {\n if (!(error instanceof OfflineUnavailableError)) throw error;\n markOfflineUnavailable(error);\n return undefined;\n }\n};\n\nconst userIdOf = (name: string): string | undefined => FILE_PATTERN.exec(name)?.[1] ?? POOL_PATTERN.exec(name)?.[1];\n\nconst closeCurrent = async (): Promise<void> => {\n const open = current;\n current = undefined;\n if (!open) return;\n try {\n await (await open.database).close();\n } catch {\n // A database that never opened has nothing to close.\n }\n};\n\n/**\n * Opens the device copy of `userId` (`nja-offline-<userId>.sqlite3` in the OPFS,\n * through the worker). Only one user's file is open at a time: opening another\n * user's file closes the previous one first.\n */\nexport function openDeviceStore(userId: string): Promise<DeviceStore> {\n if (!userId) return Promise.reject(new Error(\"openDeviceStore needs the signed-in user id.\"));\n if (isOfflineStoreBlocked(userId)) {\n return Promise.reject(new DeviceStoreUnavailableError(\"The device copy was deleted at logout\"));\n }\n if (!isOfflineAvailable()) {\n return Promise.reject(new OfflineUnavailableError(\"it was found unable to keep the device copy\"));\n }\n if (current?.userId === userId) return current.store;\n\n const previous = closeCurrent();\n const database = previous\n .then(() => backend.openDatabase(userId, deviceFileName(userId)))\n .catch((error) => {\n if (error instanceof OfflineUnavailableError) markOfflineUnavailable(error);\n throw error;\n });\n const store = database.then(async (opened) => {\n if (opened.created) await backend.requestPersistence();\n return createSqlDeviceStore(opened.exec, {\n exclusive: (opened as Partial<SharedDatabase>).exclusive,\n beforeEnqueue: async () => {\n const directory = await backend.getDirectory();\n await directory.getFileHandle(pendingMarkerName(userId), { create: true });\n },\n afterQueueChange: async (entries) => {\n if (entries > 0) return;\n await removeIfPresent(await backend.getDirectory(), pendingMarkerName(userId));\n },\n });\n });\n const open: OpenStore = { userId, store, database };\n current = open;\n store.catch(() => {\n if (current === open) current = undefined;\n });\n return store;\n}\n\nlet sqlite3Promise: Promise<Sqlite3Static> | undefined;\n\n/**\n * An in-memory SQLite database (no worker, no OPFS), for tests in Node. The package is imported\n * here at run time and hidden from app bundlers (see `OfflineConfig.sqliteModuleUrl`).\n */\nexport async function openMemoryDatabase(): Promise<OpenedDatabase> {\n sqlite3Promise ??= import(/* webpackIgnore: true */ /* turbopackIgnore: true */ \"@sqlite.org/sqlite-wasm\").then(\n (module) => module.default(),\n );\n const sqlite3 = await sqlite3Promise;\n const db = new sqlite3.oo1.DB(\":memory:\", \"c\");\n return {\n created: true,\n exec: async (sql, bind) =>\n db.exec({\n sql,\n bind: bind && bind.length ? (bind as any) : undefined,\n rowMode: \"object\",\n returnValue: \"resultRows\",\n }) as Record<string, unknown>[],\n close: async () => {\n db.close();\n },\n };\n}\n\n/** The same store and schema over an in-memory database, for tests. */\nexport async function openMemoryDeviceStore(): Promise<DeviceStore> {\n const database = await openMemoryDatabase();\n return createSqlDeviceStore(database.exec);\n}\n\n/**\n * Another page closed every page's copy of `userId` (logout, offline mode off): this page forgets\n * its store, and reads the device switch again before anything opens the copy.\n */\nfunction forgetClosedStore(userId: string): void {\n if (current?.userId === userId) current = undefined;\n forgetOfflineStore(userId);\n refreshOfflineDeviceState();\n}\n\n/**\n * Closes `userId`'s device copy in every page and removes every OPFS entry of it. The files can\n * only be removed once no page holds them, so the removal waits for every page to let go.\n */\nexport async function deleteDeviceStore(userId: string): Promise<void> {\n if (current?.userId === userId) await closeCurrent();\n const directory = await deviceDirectory();\n if (!directory) return;\n const remove = async () => {\n const names = await entriesOf(directory, userId);\n const marker = pendingMarkerName(userId);\n for (const name of names) if (name !== marker) await removeIfPresent(directory, name);\n await removeIfPresent(directory, marker);\n };\n if (backend === defaultBackend) await withDatabaseReleased(userId, remove);\n else await remove();\n}\n\n/** Whether `userId`'s device copy holds unsent changes. Never opens another user's file. */\nexport async function hasUnsentChanges(userId: string): Promise<boolean> {\n if (current?.userId === userId) {\n const store = await current.store;\n if ((await store.queue()).length > 0) return true;\n }\n const directory = await deviceDirectory();\n if (!directory) return false;\n return entryExists(directory, pendingMarkerName(userId));\n}\n\n/**\n * A user signed in on this browser: their device copy may be opened again (it is blocked after\n * a logout), and every other user's device copy is deleted when it has nothing unsent, and kept\n * untouched (never opened) when it has. When the images cache was not this user's (another user\n * signed in before, or no owner is known), it is deleted: the user's sync downloads their artwork.\n */\nexport async function onUserSignedIn(userId: string): Promise<void> {\n unblockOfflineStore(userId);\n if (current && current.userId !== userId) await closeCurrent();\n // A browser that cannot keep the device copy: offline mode switches off here, before the runtime starts.\n const directory = await deviceDirectory();\n if (!directory) return;\n const others = new Set<string>();\n const imageOwners: string[] = [];\n for await (const key of directory.keys()) {\n if (key.startsWith(IMAGES_OWNER_PREFIX)) {\n imageOwners.push(key);\n continue;\n }\n const owner = userIdOf(key);\n if (owner && owner !== userId) others.add(owner);\n }\n if (!imageOwners.includes(imagesOwnerName(userId))) {\n await deleteImagesCaches();\n for (const name of imageOwners) await removeIfPresent(directory, name);\n await directory.getFileHandle(imagesOwnerName(userId), { create: true });\n }\n for (const other of others) {\n if (await entryExists(directory, pendingMarkerName(other))) continue;\n await deleteDeviceStore(other);\n }\n}\n\n/** Locked now: the server said so, or the lock date it gave has passed. */\nexport function isRootLocked(s: { locked: boolean; lockedFrom: Date | null }, now: Date): boolean {\n if (s.locked) return true;\n return s.lockedFrom !== null && s.lockedFrom.getTime() <= now.getTime();\n}\n"]}
@@ -292,7 +292,7 @@ _chunk7QVYU63Ejs.__name.call(void 0, getToken, "getToken");
292
292
  async function makeRequest(params) {
293
293
  if (typeof window !== "undefined") {
294
294
  if (!_directFetch) {
295
- const clientModule = await Promise.resolve().then(() => _interopRequireWildcard(require("./request-EUGTI3BE.js")));
295
+ const clientModule = await Promise.resolve().then(() => _interopRequireWildcard(require("./request-KTBV6HHH.js")));
296
296
  _directFetch = clientModule.directFetch;
297
297
  }
298
298
  return _directFetch({
@@ -516,4 +516,4 @@ _chunk7QVYU63Ejs.__name.call(void 0, JsonApiDelete, "JsonApiDelete");
516
516
 
517
517
 
518
518
  exports.ENV = ENV; exports.DataClassRegistry = DataClassRegistry; exports.JsonApiDataFactory = JsonApiDataFactory; exports.setBootstrapper = setBootstrapper; exports.getBootstrapper = getBootstrapper; exports.tryBootstrap = tryBootstrap; exports.hasBootstrapper = hasBootstrapper; exports.resetBootstrapStore = resetBootstrapStore; exports.translateData = translateData; exports.translateResponse = translateResponse; exports.configureJsonApi = configureJsonApi; exports.getApiUrl = getApiUrl; exports.getAppUrl = getAppUrl; exports.getTrackablePages = getTrackablePages; exports.buildUrl = buildUrl; exports.JsonApiGet = JsonApiGet; exports.JsonApiPost = JsonApiPost; exports.JsonApiPut = JsonApiPut; exports.JsonApiPatch = JsonApiPatch; exports.JsonApiDelete = JsonApiDelete;
519
- //# sourceMappingURL=chunk-GHE4VT2R.js.map
519
+ //# sourceMappingURL=chunk-QKNGZDSQ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-GHE4VT2R.js","../src/core/env.ts","../src/core/registry/DataClassRegistry.ts","../src/core/factories/JsonApiDataFactory.ts","../src/core/registry/bootstrapStore.ts","../src/core/utils/translateResponse.ts","../src/unified/JsonApiRequest.ts"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-GHE4VT2R.js","sourcesContent":[null,"/**\n * The single place in this package that reads `process.env`.\n *\n * Everything else imports {@link ENV}. Centralising the reads keeps every\n * default in one place — the same contract `base.config.ts` provides on the\n * NestJS side — instead of scattering `process.env.X || \"…\"` across hooks,\n * clients and components where the fallbacks silently drift apart.\n *\n * WHY THE READS MUST STAY LITERAL: Next.js inlines `NEXT_PUBLIC_*` by textual\n * substitution at build time, replacing the exact expression\n * `process.env.NEXT_PUBLIC_FOO` with a string literal. A computed lookup\n * (`process.env[key]`) is NOT substituted and reads as `undefined` in the\n * browser bundle. Every entry below therefore spells its variable out in full,\n * and new entries must do the same.\n *\n * WHY GETTERS AND NOT PLAIN PROPERTIES: every call site this replaced read\n * `process.env` at CALL time, not at import time. A plain property would freeze\n * the value when the module is first imported, which breaks any consumer that\n * assigns the variable after boot — the package's own tests do exactly that.\n * Getters keep the read lazy while leaving the expression literal, so the build\n * time substitution still applies.\n *\n * Because that substitution happens before the code runs, no reference to\n * `process` survives into the client bundle — so this module is safe to import\n * from client components, server components, route handlers and middleware\n * alike. It deliberately carries no `\"use client\"` directive for that reason.\n *\n * Values are normalised (empty string rather than `undefined`, booleans rather\n * than string comparisons) so callers never repeat the parsing. Callers that\n * must distinguish \"unset\" from \"set to empty\" — the URL getters, which throw a\n * configuration error — check for an empty string.\n */\nexport const ENV = {\n /** Public API base URL (NEXT_PUBLIC_API_URL). Empty when unset. */\n get API_URL(): string {\n return process.env.NEXT_PUBLIC_API_URL ?? \"\";\n },\n /** Public app base URL (NEXT_PUBLIC_ADDRESS). Empty when unset. */\n get APP_URL(): string {\n return process.env.NEXT_PUBLIC_ADDRESS ?? \"\";\n },\n /**\n * Alternative app base URL (NEXT_PUBLIC_APP_URL), used only by\n * `client/config.ts`'s `getAppUrl()` before it falls back to\n * `window.location.origin`. Empty when unset.\n */\n get APP_URL_ALTERNATE(): string {\n return process.env.NEXT_PUBLIC_APP_URL ?? \"\";\n },\n /** Web-push VAPID public key (NEXT_PUBLIC_VAPID_PUBLIC_KEY). Empty when unset. */\n get VAPID_PUBLIC_KEY(): string {\n return process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY ?? \"\";\n },\n /** Google Maps JS key (NEXT_PUBLIC_GOOGLE_MAPS_API_KEY). Empty disables autocomplete. */\n get GOOGLE_MAPS_API_KEY(): string {\n return process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? \"\";\n },\n /**\n * NEXT_PUBLIC_PRIVATE_INSTALLATION === \"true\", case-insensitively\n * (default false). A private installation is a single-tenant deployment.\n */\n get PRIVATE_INSTALLATION(): boolean {\n return process.env.NEXT_PUBLIC_PRIVATE_INSTALLATION?.toLowerCase() === \"true\";\n },\n /** NODE_ENV === \"production\" — set by the Next build, not by `.env`. */\n get IS_PRODUCTION(): boolean {\n return process.env.NODE_ENV === \"production\";\n },\n /** NODE_ENV === \"development\" — gates verbose client-side diagnostics. */\n get IS_DEVELOPMENT(): boolean {\n return process.env.NODE_ENV === \"development\";\n },\n};\n","import { ApiDataInterface } from \"../interfaces/ApiDataInterface\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\n\nexport class DataClassRegistry {\n // Use Map with string key (module.name) for reliable lookup\n // String keys are stable across HMR and module re-evaluations\n // Note: WeakMap with object identity was tried but fails during Next.js navigation\n // because module re-evaluation creates new object instances with different identity\n private static _map = new Map<string, { new (): ApiDataInterface }>();\n\n public static registerObjectClass(\n key: ApiRequestDataTypeInterface,\n classConstructor: { new (): ApiDataInterface },\n ): void {\n // Last registration wins. An earlier version skipped re-registration, which\n // silently pinned the registry to the FIRST constructor seen by the process.\n // In dev that made a long-lived `next dev` server keep rehydrating with the\n // pre-edit class after any model change, so SSR produced a different tree\n // from the freshly bundled client and React threw a hydration mismatch.\n // Production is unaffected either way: bootstrap runs once per process.\n this._map.set(key.name, classConstructor);\n }\n\n public static get(classKey: ApiRequestDataTypeInterface): {\n new (): ApiDataInterface;\n } {\n const response = this._map.get(classKey.name);\n if (!response) {\n throw new Error(\n `Class not registered for key: ${typeof classKey === \"string\" ? classKey : classKey.name}. Ensure bootstrap() was called.`,\n );\n }\n\n return response;\n }\n\n /**\n * Get class constructor by JSON:API type name.\n * Used for polymorphic rehydration where the type is determined at runtime.\n */\n public static getByJsonApiType(typeName: string): { new (): ApiDataInterface } | undefined {\n return this._map.get(typeName);\n }\n\n /**\n * Bootstrap the registry with all modules.\n * This is a convenience method for apps to register all their modules at once.\n *\n * @param modules - An object with module definitions (like the app's Modules class)\n */\n public static bootstrap(modules: Record<string, ApiRequestDataTypeInterface>): void {\n Object.values(modules).forEach((module) => {\n if (module && module.model) {\n this.registerObjectClass(module, module.model);\n }\n });\n }\n\n /**\n * Clear all registered classes. Useful for testing.\n */\n public static clear(): void {\n this._map.clear();\n }\n}\n\n// Export alias for backward compatibility\nexport { DataClassRegistry as DataClass };\n","import { ApiDataInterface } from \"../interfaces/ApiDataInterface\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\nimport { DataClassRegistry } from \"../registry/DataClassRegistry\";\n\nexport class JsonApiDataFactory {\n public static create(classKey: ApiRequestDataTypeInterface, data: any): any {\n const factoryClass = DataClassRegistry.get(classKey);\n\n const instance = new factoryClass() as ApiDataInterface;\n const result = instance.createJsonApi(data);\n return result;\n }\n}\n","/**\n * Centralized bootstrap store accessible from ModuleRegistry.\n * This file has NO external dependencies to avoid circular imports.\n *\n * The bootstrap store allows ModuleRegistry to call the app's bootstrapper\n * when modules are accessed before bootstrap() was called, providing\n * self-healing behavior for module evaluation order issues.\n *\n * IMPORTANT: We use globalThis to persist state across HMR/Turbopack\n * module re-evaluations in development mode. Without this, the bootstrapper\n * reference would be lost when modules are hot-reloaded, causing\n * \"Module not registered\" errors during navigation.\n */\n\ntype BootstrapperFn = () => void;\n\n// Symbol key to avoid conflicts with other globals\nconst BOOTSTRAP_KEY = Symbol.for(\"nextjs-jsonapi:bootstrapper\");\nconst BOOTSTRAP_ATTEMPTED_KEY = Symbol.for(\"nextjs-jsonapi:bootstrapAttempted\");\n\n// Use globalThis to persist across HMR reloads\nconst globalStore = globalThis as unknown as {\n [BOOTSTRAP_KEY]?: BootstrapperFn | null;\n [BOOTSTRAP_ATTEMPTED_KEY]?: boolean;\n};\n\n// Initialize from global if not set\nif (globalStore[BOOTSTRAP_KEY] === undefined) {\n globalStore[BOOTSTRAP_KEY] = null;\n}\nif (globalStore[BOOTSTRAP_ATTEMPTED_KEY] === undefined) {\n globalStore[BOOTSTRAP_ATTEMPTED_KEY] = false;\n}\n\n// Getters/setters that use the global store\nfunction getBootstrapperInternal(): BootstrapperFn | null {\n return globalStore[BOOTSTRAP_KEY] ?? null;\n}\n\nfunction setBootstrapperInternal(fn: BootstrapperFn | null): void {\n globalStore[BOOTSTRAP_KEY] = fn;\n}\n\nfunction getBootstrapAttempted(): boolean {\n return globalStore[BOOTSTRAP_ATTEMPTED_KEY] ?? false;\n}\n\nfunction setBootstrapAttempted(value: boolean): void {\n globalStore[BOOTSTRAP_ATTEMPTED_KEY] = value;\n}\n\n/**\n * Register the bootstrapper function.\n * Called by configureJsonApi() from client/config, client/JsonApiClient, or unified/JsonApiRequest.\n */\nexport function setBootstrapper(fn: BootstrapperFn): void {\n setBootstrapperInternal(fn);\n}\n\n/**\n * Get the registered bootstrapper function.\n * Returns null if no bootstrapper has been registered.\n */\nexport function getBootstrapper(): BootstrapperFn | null {\n return getBootstrapperInternal();\n}\n\n/**\n * Attempt to run the bootstrapper if one is registered.\n * Returns true if bootstrapper was executed, false if not available.\n * Safe to call multiple times - bootstrapper is expected to be idempotent.\n */\nexport function tryBootstrap(): boolean {\n const bootstrapper = getBootstrapperInternal();\n const attempted = getBootstrapAttempted();\n\n if (attempted && !bootstrapper) {\n // Already tried and no bootstrapper available\n return false;\n }\n setBootstrapAttempted(true);\n\n if (bootstrapper) {\n bootstrapper();\n return true;\n }\n return false;\n}\n\n/**\n * Check if a bootstrapper has been registered.\n */\nexport function hasBootstrapper(): boolean {\n return getBootstrapperInternal() !== null;\n}\n\n/**\n * Reset the bootstrap store. Useful for testing.\n */\nexport function resetBootstrapStore(): void {\n setBootstrapperInternal(null);\n setBootstrapAttempted(false);\n}\n","import { ApiData } from \"../interfaces/ApiData\";\nimport { ApiDataInterface } from \"../interfaces/ApiDataInterface\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\nimport { ApiResponseInterface } from \"../interfaces/ApiResponseInterface\";\nimport { DataClassRegistry } from \"../registry/DataClassRegistry\";\n\n/**\n * Translates raw JSON:API data into typed objects.\n * Does not require API response metadata.\n */\nexport function translateData<T extends ApiDataInterface>(params: {\n classKey: ApiRequestDataTypeInterface;\n data: any;\n}): T | T[] {\n const factoryClass = DataClassRegistry.get(params.classKey);\n\n if (!factoryClass) {\n throw new Error(\n `Class not registered for key: ${typeof params.classKey === \"string\" ? params.classKey : params.classKey.name}`,\n );\n }\n\n const included: any = params.data.included ?? [];\n\n if (Array.isArray(params.data.data)) {\n const responseData: T[] = [];\n\n for (const data of params.data.data) {\n const object = new factoryClass();\n object.rehydrate({ jsonApi: data, included: included, allData: params.data.data });\n responseData.push(object as T);\n }\n\n return responseData as T[];\n } else {\n const responseData = new factoryClass();\n responseData.rehydrate({\n jsonApi: params.data.data,\n included: included,\n });\n\n return responseData as T;\n }\n}\n\n/**\n * Translates a full API response into a typed ApiResponseInterface.\n * Includes pagination support.\n */\nexport async function translateResponse<T extends ApiDataInterface>(params: {\n classKey: ApiRequestDataTypeInterface;\n apiResponse: ApiData;\n companyId?: string;\n language: string;\n paginationHandler?: (endpoint: string) => Promise<ApiResponseInterface>;\n}): Promise<ApiResponseInterface> {\n const response: ApiResponseInterface = {\n ok: true,\n response: 0,\n data: [],\n error: \"\",\n };\n\n const factoryClass = DataClassRegistry.get(params.classKey);\n\n if (!factoryClass) {\n throw new Error(\n `Class not registered for key: ${typeof params.classKey === \"string\" ? params.classKey : params.classKey.name}`,\n );\n }\n\n response.ok = params.apiResponse.ok;\n response.response = params.apiResponse.status;\n\n if (!params.apiResponse.ok) {\n // The nestjs-neo4jsonapi `HttpExceptionFilter` emits a JSON:API error document\n // (`errors: [{ status, title, detail, ... }]`), so `detail` is read first, then the legacy\n // `message`/`error` keys, then `statusText` (empty over HTTP/2).\n response.error =\n params.apiResponse?.data?.errors?.[0]?.detail ??\n params.apiResponse?.data?.message ??\n params.apiResponse?.data?.error ??\n params.apiResponse.statusText;\n response.raw = params.apiResponse.data;\n return response;\n }\n\n if (params.apiResponse.status === 204) return response;\n\n // A success with no body (e.g. a 200 from a handler that sends nothing —\n // @HttpCode is inert when a controller injects @Res, so \"204\" endpoints can\n // arrive as empty 200s) translates to an empty success, not a crash on\n // `.included` below.\n if (params.apiResponse.data === undefined || params.apiResponse.data === null) return response;\n\n response.raw = params.apiResponse.data;\n\n // Extract meta from JSON:API response\n if (params.apiResponse.data?.meta) {\n response.meta = params.apiResponse.data.meta;\n }\n\n try {\n // Check if response is JSON:API formatted (has a 'data' property)\n // If not, return the raw response data directly (e.g., { url: \"...\" } or { clientSecret: \"...\" })\n if (\n params.apiResponse.data &&\n typeof params.apiResponse.data === \"object\" &&\n !Array.isArray(params.apiResponse.data) &&\n params.apiResponse.data.data === undefined\n ) {\n response.data = params.apiResponse.data;\n return response;\n }\n\n const included: any = params.apiResponse.data.included ?? [];\n\n if (params.apiResponse.data.links) {\n response.self = params.apiResponse.data.links.self;\n\n if (params.apiResponse.data.links.next && params.paginationHandler) {\n response.next = params.apiResponse.data.links.next;\n response.nextPage = async () => params.paginationHandler!(params.apiResponse.data.links.next);\n }\n\n if (params.apiResponse.data.links.prev && params.paginationHandler) {\n response.prev = params.apiResponse.data.links.prev;\n response.prevPage = async () => params.paginationHandler!(params.apiResponse.data.links.prev);\n }\n }\n\n if (Array.isArray(params.apiResponse.data.data)) {\n const responseData: T[] = [];\n\n for (const data of params.apiResponse.data.data) {\n const object = new factoryClass();\n object.rehydrate({ jsonApi: data, included: included, allData: params.apiResponse.data.data });\n responseData.push(object as T);\n }\n\n response.data = responseData;\n } else {\n const responseData = new factoryClass();\n responseData.rehydrate({\n jsonApi: params.apiResponse.data.data,\n included: included,\n });\n\n response.data = responseData;\n }\n } catch (e) {\n console.error(e);\n }\n\n return response;\n}\n","import { ENV } from \"../core/env\";\nimport { JsonApiDataFactory } from \"../core/factories/JsonApiDataFactory\";\nimport { ApiData } from \"../core/interfaces/ApiData\";\nimport { ApiRequestDataTypeInterface } from \"../core/interfaces/ApiRequestDataTypeInterface\";\nimport { ApiResponseInterface } from \"../core/interfaces/ApiResponseInterface\";\nimport { setBootstrapper } from \"../core/registry/bootstrapStore\";\nimport { translateResponse } from \"../core/utils/translateResponse\";\nimport { ModuleWithPermissions } from \"../permissions/types\";\n\n// Type definitions for dynamically imported functions (avoiding typeof import to prevent bundling)\ntype DirectFetchFn = (params: {\n method: string;\n url: string;\n token?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n companyId?: string;\n language: string;\n additionalHeaders?: Record<string, string>;\n}) => Promise<ApiData>;\n\ntype ServerRequestFn = (params: {\n method: string;\n url: string;\n token?: string;\n cache?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n companyId?: string;\n language: string;\n additionalHeaders?: Record<string, string>;\n}) => Promise<ApiData>;\n\ntype GetTokenFn = () => Promise<string | undefined>;\n\n// These will be dynamically imported based on environment\nlet _directFetch: DirectFetchFn;\nlet _serverRequest: ServerRequestFn;\nlet _getClientToken: GetTokenFn;\nlet _getServerToken: GetTokenFn;\n\n// Config storage for non-React contexts\nlet _staticConfig: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n} | null = null;\n\n/**\n * Configure the JSON:API client for non-React contexts (e.g., server components).\n * For React contexts, use JsonApiProvider instead.\n */\nexport function configureJsonApi(config: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n}): void {\n _staticConfig = config;\n // Register and call bootstrapper to register all modules\n if (config.bootstrapper) {\n setBootstrapper(config.bootstrapper);\n config.bootstrapper();\n }\n}\n\nasync function getToken(): Promise<string | undefined> {\n if (typeof window === \"undefined\") {\n // Server-side\n if (!_getServerToken) {\n const serverModule = await import(\"../server/token\");\n _getServerToken = serverModule.getServerToken;\n }\n return _getServerToken();\n } else {\n // Client-side\n if (!_getClientToken) {\n const clientModule = await import(\"../client/token\");\n _getClientToken = clientModule.getClientToken;\n }\n return _getClientToken();\n }\n}\n\nasync function makeRequest(params: {\n method: string;\n url: string;\n token?: string;\n cache?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n companyId?: string;\n language: string;\n additionalHeaders?: Record<string, string>;\n}): Promise<ApiData> {\n if (typeof window !== \"undefined\") {\n // Client-side: use direct fetch\n if (!_directFetch) {\n const clientModule = await import(\"../client/request\");\n _directFetch = clientModule.directFetch;\n }\n return _directFetch({\n method: params.method,\n url: params.url,\n token: params.token,\n body: params.body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: params.additionalHeaders,\n });\n } else {\n // Server-side: use server request with caching\n if (!_serverRequest) {\n const serverModule = await import(\"../server/request\");\n _serverRequest = serverModule.serverRequest;\n }\n return _serverRequest({\n method: params.method,\n url: params.url,\n token: params.token,\n cache: params.cache,\n body: params.body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: params.additionalHeaders,\n });\n }\n}\n\nexport function getApiUrl(): string {\n if (_staticConfig?.apiUrl) {\n return _staticConfig.apiUrl;\n }\n // Fallback to environment variable\n const envUrl = ENV.API_URL;\n if (!envUrl) {\n throw new Error(\"API URL not configured. Use configureJsonApi() or set NEXT_PUBLIC_API_URL environment variable.\");\n }\n return envUrl;\n}\n\nexport function getAppUrl(): string {\n if (_staticConfig?.appUrl) {\n return _staticConfig.appUrl;\n }\n // Fallback to environment variable\n const envUrl = ENV.APP_URL;\n if (!envUrl) {\n throw new Error(\n \"App URL not configured. Use configureJsonApi({ appUrl }) or set NEXT_PUBLIC_ADDRESS environment variable.\",\n );\n }\n return envUrl.trim().replace(/\\/+$/, \"\");\n}\n\nexport function getTrackablePages(): ModuleWithPermissions[] {\n return _staticConfig?.trackablePages ?? [];\n}\n\nfunction runBootstrapper(): void {\n if (_staticConfig?.bootstrapper) {\n _staticConfig.bootstrapper();\n }\n}\n\n/**\n * Resolve the final request URL for an endpoint.\n *\n * - `endpoint` starting with \"http\" is always passed through unchanged (existing behaviour).\n * - Otherwise, an explicit `baseUrl` (per-call override) takes precedence over the global\n * `getApiUrl()` resolution. Omitting `baseUrl` preserves today's behaviour exactly.\n */\nexport function buildUrl(endpoint: string, baseUrl?: string): string {\n if (endpoint.startsWith(\"http\")) return endpoint;\n const apiUrl = baseUrl ?? getApiUrl();\n return `${apiUrl}${endpoint}`;\n}\n\nexport async function JsonApiGet(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n language: string;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n // A cached server GET never sends the token, so it must not read the cookie either:\n // the read would make a public page dynamic.\n const isServerCached = typeof window === \"undefined\" && !!params.classKey.cache;\n const token = isServerCached ? undefined : (params.token ?? (await getToken()));\n\n const apiResponse = await makeRequest({\n method: \"GET\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n cache: params.classKey.cache,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n paginationHandler: async (endpoint: string) =>\n JsonApiGet({\n classKey: params.classKey,\n endpoint,\n companyId: params.companyId,\n language: params.language,\n token: params.token,\n baseUrl: params.baseUrl,\n }),\n });\n}\n\nexport async function JsonApiPost(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n overridesJsonApiCreation?: boolean;\n files?: { [key: string]: File | Blob } | File | Blob;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n const token = params.token ?? (await getToken());\n\n let body = params.body;\n if (!body) {\n body = {};\n } else if (params.overridesJsonApiCreation !== true) {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await makeRequest({\n method: \"POST\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function JsonApiPut(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n const token = params.token ?? (await getToken());\n\n let body = params.body;\n if (!body) {\n body = {};\n } else {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await makeRequest({\n method: \"PUT\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function JsonApiPatch(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n overridesJsonApiCreation?: boolean;\n responseType?: ApiRequestDataTypeInterface;\n language: string;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n const token = params.token ?? (await getToken());\n\n let body = params.body;\n if (!body) {\n body = {};\n } else if (params.overridesJsonApiCreation !== true) {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await makeRequest({\n method: \"PATCH\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function JsonApiDelete(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n const token = params.token ?? (await getToken());\n\n const apiResponse = await makeRequest({\n method: \"DELETE\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-QKNGZDSQ.js","../src/core/env.ts","../src/core/registry/DataClassRegistry.ts","../src/core/factories/JsonApiDataFactory.ts","../src/core/registry/bootstrapStore.ts","../src/core/utils/translateResponse.ts","../src/unified/JsonApiRequest.ts"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-QKNGZDSQ.js","sourcesContent":[null,"/**\n * The single place in this package that reads `process.env`.\n *\n * Everything else imports {@link ENV}. Centralising the reads keeps every\n * default in one place — the same contract `base.config.ts` provides on the\n * NestJS side — instead of scattering `process.env.X || \"…\"` across hooks,\n * clients and components where the fallbacks silently drift apart.\n *\n * WHY THE READS MUST STAY LITERAL: Next.js inlines `NEXT_PUBLIC_*` by textual\n * substitution at build time, replacing the exact expression\n * `process.env.NEXT_PUBLIC_FOO` with a string literal. A computed lookup\n * (`process.env[key]`) is NOT substituted and reads as `undefined` in the\n * browser bundle. Every entry below therefore spells its variable out in full,\n * and new entries must do the same.\n *\n * WHY GETTERS AND NOT PLAIN PROPERTIES: every call site this replaced read\n * `process.env` at CALL time, not at import time. A plain property would freeze\n * the value when the module is first imported, which breaks any consumer that\n * assigns the variable after boot — the package's own tests do exactly that.\n * Getters keep the read lazy while leaving the expression literal, so the build\n * time substitution still applies.\n *\n * Because that substitution happens before the code runs, no reference to\n * `process` survives into the client bundle — so this module is safe to import\n * from client components, server components, route handlers and middleware\n * alike. It deliberately carries no `\"use client\"` directive for that reason.\n *\n * Values are normalised (empty string rather than `undefined`, booleans rather\n * than string comparisons) so callers never repeat the parsing. Callers that\n * must distinguish \"unset\" from \"set to empty\" — the URL getters, which throw a\n * configuration error — check for an empty string.\n */\nexport const ENV = {\n /** Public API base URL (NEXT_PUBLIC_API_URL). Empty when unset. */\n get API_URL(): string {\n return process.env.NEXT_PUBLIC_API_URL ?? \"\";\n },\n /** Public app base URL (NEXT_PUBLIC_ADDRESS). Empty when unset. */\n get APP_URL(): string {\n return process.env.NEXT_PUBLIC_ADDRESS ?? \"\";\n },\n /**\n * Alternative app base URL (NEXT_PUBLIC_APP_URL), used only by\n * `client/config.ts`'s `getAppUrl()` before it falls back to\n * `window.location.origin`. Empty when unset.\n */\n get APP_URL_ALTERNATE(): string {\n return process.env.NEXT_PUBLIC_APP_URL ?? \"\";\n },\n /** Web-push VAPID public key (NEXT_PUBLIC_VAPID_PUBLIC_KEY). Empty when unset. */\n get VAPID_PUBLIC_KEY(): string {\n return process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY ?? \"\";\n },\n /** Google Maps JS key (NEXT_PUBLIC_GOOGLE_MAPS_API_KEY). Empty disables autocomplete. */\n get GOOGLE_MAPS_API_KEY(): string {\n return process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? \"\";\n },\n /**\n * NEXT_PUBLIC_PRIVATE_INSTALLATION === \"true\", case-insensitively\n * (default false). A private installation is a single-tenant deployment.\n */\n get PRIVATE_INSTALLATION(): boolean {\n return process.env.NEXT_PUBLIC_PRIVATE_INSTALLATION?.toLowerCase() === \"true\";\n },\n /** NODE_ENV === \"production\" — set by the Next build, not by `.env`. */\n get IS_PRODUCTION(): boolean {\n return process.env.NODE_ENV === \"production\";\n },\n /** NODE_ENV === \"development\" — gates verbose client-side diagnostics. */\n get IS_DEVELOPMENT(): boolean {\n return process.env.NODE_ENV === \"development\";\n },\n};\n","import { ApiDataInterface } from \"../interfaces/ApiDataInterface\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\n\nexport class DataClassRegistry {\n // Use Map with string key (module.name) for reliable lookup\n // String keys are stable across HMR and module re-evaluations\n // Note: WeakMap with object identity was tried but fails during Next.js navigation\n // because module re-evaluation creates new object instances with different identity\n private static _map = new Map<string, { new (): ApiDataInterface }>();\n\n public static registerObjectClass(\n key: ApiRequestDataTypeInterface,\n classConstructor: { new (): ApiDataInterface },\n ): void {\n // Last registration wins. An earlier version skipped re-registration, which\n // silently pinned the registry to the FIRST constructor seen by the process.\n // In dev that made a long-lived `next dev` server keep rehydrating with the\n // pre-edit class after any model change, so SSR produced a different tree\n // from the freshly bundled client and React threw a hydration mismatch.\n // Production is unaffected either way: bootstrap runs once per process.\n this._map.set(key.name, classConstructor);\n }\n\n public static get(classKey: ApiRequestDataTypeInterface): {\n new (): ApiDataInterface;\n } {\n const response = this._map.get(classKey.name);\n if (!response) {\n throw new Error(\n `Class not registered for key: ${typeof classKey === \"string\" ? classKey : classKey.name}. Ensure bootstrap() was called.`,\n );\n }\n\n return response;\n }\n\n /**\n * Get class constructor by JSON:API type name.\n * Used for polymorphic rehydration where the type is determined at runtime.\n */\n public static getByJsonApiType(typeName: string): { new (): ApiDataInterface } | undefined {\n return this._map.get(typeName);\n }\n\n /**\n * Bootstrap the registry with all modules.\n * This is a convenience method for apps to register all their modules at once.\n *\n * @param modules - An object with module definitions (like the app's Modules class)\n */\n public static bootstrap(modules: Record<string, ApiRequestDataTypeInterface>): void {\n Object.values(modules).forEach((module) => {\n if (module && module.model) {\n this.registerObjectClass(module, module.model);\n }\n });\n }\n\n /**\n * Clear all registered classes. Useful for testing.\n */\n public static clear(): void {\n this._map.clear();\n }\n}\n\n// Export alias for backward compatibility\nexport { DataClassRegistry as DataClass };\n","import { ApiDataInterface } from \"../interfaces/ApiDataInterface\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\nimport { DataClassRegistry } from \"../registry/DataClassRegistry\";\n\nexport class JsonApiDataFactory {\n public static create(classKey: ApiRequestDataTypeInterface, data: any): any {\n const factoryClass = DataClassRegistry.get(classKey);\n\n const instance = new factoryClass() as ApiDataInterface;\n const result = instance.createJsonApi(data);\n return result;\n }\n}\n","/**\n * Centralized bootstrap store accessible from ModuleRegistry.\n * This file has NO external dependencies to avoid circular imports.\n *\n * The bootstrap store allows ModuleRegistry to call the app's bootstrapper\n * when modules are accessed before bootstrap() was called, providing\n * self-healing behavior for module evaluation order issues.\n *\n * IMPORTANT: We use globalThis to persist state across HMR/Turbopack\n * module re-evaluations in development mode. Without this, the bootstrapper\n * reference would be lost when modules are hot-reloaded, causing\n * \"Module not registered\" errors during navigation.\n */\n\ntype BootstrapperFn = () => void;\n\n// Symbol key to avoid conflicts with other globals\nconst BOOTSTRAP_KEY = Symbol.for(\"nextjs-jsonapi:bootstrapper\");\nconst BOOTSTRAP_ATTEMPTED_KEY = Symbol.for(\"nextjs-jsonapi:bootstrapAttempted\");\n\n// Use globalThis to persist across HMR reloads\nconst globalStore = globalThis as unknown as {\n [BOOTSTRAP_KEY]?: BootstrapperFn | null;\n [BOOTSTRAP_ATTEMPTED_KEY]?: boolean;\n};\n\n// Initialize from global if not set\nif (globalStore[BOOTSTRAP_KEY] === undefined) {\n globalStore[BOOTSTRAP_KEY] = null;\n}\nif (globalStore[BOOTSTRAP_ATTEMPTED_KEY] === undefined) {\n globalStore[BOOTSTRAP_ATTEMPTED_KEY] = false;\n}\n\n// Getters/setters that use the global store\nfunction getBootstrapperInternal(): BootstrapperFn | null {\n return globalStore[BOOTSTRAP_KEY] ?? null;\n}\n\nfunction setBootstrapperInternal(fn: BootstrapperFn | null): void {\n globalStore[BOOTSTRAP_KEY] = fn;\n}\n\nfunction getBootstrapAttempted(): boolean {\n return globalStore[BOOTSTRAP_ATTEMPTED_KEY] ?? false;\n}\n\nfunction setBootstrapAttempted(value: boolean): void {\n globalStore[BOOTSTRAP_ATTEMPTED_KEY] = value;\n}\n\n/**\n * Register the bootstrapper function.\n * Called by configureJsonApi() from client/config, client/JsonApiClient, or unified/JsonApiRequest.\n */\nexport function setBootstrapper(fn: BootstrapperFn): void {\n setBootstrapperInternal(fn);\n}\n\n/**\n * Get the registered bootstrapper function.\n * Returns null if no bootstrapper has been registered.\n */\nexport function getBootstrapper(): BootstrapperFn | null {\n return getBootstrapperInternal();\n}\n\n/**\n * Attempt to run the bootstrapper if one is registered.\n * Returns true if bootstrapper was executed, false if not available.\n * Safe to call multiple times - bootstrapper is expected to be idempotent.\n */\nexport function tryBootstrap(): boolean {\n const bootstrapper = getBootstrapperInternal();\n const attempted = getBootstrapAttempted();\n\n if (attempted && !bootstrapper) {\n // Already tried and no bootstrapper available\n return false;\n }\n setBootstrapAttempted(true);\n\n if (bootstrapper) {\n bootstrapper();\n return true;\n }\n return false;\n}\n\n/**\n * Check if a bootstrapper has been registered.\n */\nexport function hasBootstrapper(): boolean {\n return getBootstrapperInternal() !== null;\n}\n\n/**\n * Reset the bootstrap store. Useful for testing.\n */\nexport function resetBootstrapStore(): void {\n setBootstrapperInternal(null);\n setBootstrapAttempted(false);\n}\n","import { ApiData } from \"../interfaces/ApiData\";\nimport { ApiDataInterface } from \"../interfaces/ApiDataInterface\";\nimport { ApiRequestDataTypeInterface } from \"../interfaces/ApiRequestDataTypeInterface\";\nimport { ApiResponseInterface } from \"../interfaces/ApiResponseInterface\";\nimport { DataClassRegistry } from \"../registry/DataClassRegistry\";\n\n/**\n * Translates raw JSON:API data into typed objects.\n * Does not require API response metadata.\n */\nexport function translateData<T extends ApiDataInterface>(params: {\n classKey: ApiRequestDataTypeInterface;\n data: any;\n}): T | T[] {\n const factoryClass = DataClassRegistry.get(params.classKey);\n\n if (!factoryClass) {\n throw new Error(\n `Class not registered for key: ${typeof params.classKey === \"string\" ? params.classKey : params.classKey.name}`,\n );\n }\n\n const included: any = params.data.included ?? [];\n\n if (Array.isArray(params.data.data)) {\n const responseData: T[] = [];\n\n for (const data of params.data.data) {\n const object = new factoryClass();\n object.rehydrate({ jsonApi: data, included: included, allData: params.data.data });\n responseData.push(object as T);\n }\n\n return responseData as T[];\n } else {\n const responseData = new factoryClass();\n responseData.rehydrate({\n jsonApi: params.data.data,\n included: included,\n });\n\n return responseData as T;\n }\n}\n\n/**\n * Translates a full API response into a typed ApiResponseInterface.\n * Includes pagination support.\n */\nexport async function translateResponse<T extends ApiDataInterface>(params: {\n classKey: ApiRequestDataTypeInterface;\n apiResponse: ApiData;\n companyId?: string;\n language: string;\n paginationHandler?: (endpoint: string) => Promise<ApiResponseInterface>;\n}): Promise<ApiResponseInterface> {\n const response: ApiResponseInterface = {\n ok: true,\n response: 0,\n data: [],\n error: \"\",\n };\n\n const factoryClass = DataClassRegistry.get(params.classKey);\n\n if (!factoryClass) {\n throw new Error(\n `Class not registered for key: ${typeof params.classKey === \"string\" ? params.classKey : params.classKey.name}`,\n );\n }\n\n response.ok = params.apiResponse.ok;\n response.response = params.apiResponse.status;\n\n if (!params.apiResponse.ok) {\n // The nestjs-neo4jsonapi `HttpExceptionFilter` emits a JSON:API error document\n // (`errors: [{ status, title, detail, ... }]`), so `detail` is read first, then the legacy\n // `message`/`error` keys, then `statusText` (empty over HTTP/2).\n response.error =\n params.apiResponse?.data?.errors?.[0]?.detail ??\n params.apiResponse?.data?.message ??\n params.apiResponse?.data?.error ??\n params.apiResponse.statusText;\n response.raw = params.apiResponse.data;\n return response;\n }\n\n if (params.apiResponse.status === 204) return response;\n\n // A success with no body (e.g. a 200 from a handler that sends nothing —\n // @HttpCode is inert when a controller injects @Res, so \"204\" endpoints can\n // arrive as empty 200s) translates to an empty success, not a crash on\n // `.included` below.\n if (params.apiResponse.data === undefined || params.apiResponse.data === null) return response;\n\n response.raw = params.apiResponse.data;\n\n // Extract meta from JSON:API response\n if (params.apiResponse.data?.meta) {\n response.meta = params.apiResponse.data.meta;\n }\n\n try {\n // Check if response is JSON:API formatted (has a 'data' property)\n // If not, return the raw response data directly (e.g., { url: \"...\" } or { clientSecret: \"...\" })\n if (\n params.apiResponse.data &&\n typeof params.apiResponse.data === \"object\" &&\n !Array.isArray(params.apiResponse.data) &&\n params.apiResponse.data.data === undefined\n ) {\n response.data = params.apiResponse.data;\n return response;\n }\n\n const included: any = params.apiResponse.data.included ?? [];\n\n if (params.apiResponse.data.links) {\n response.self = params.apiResponse.data.links.self;\n\n if (params.apiResponse.data.links.next && params.paginationHandler) {\n response.next = params.apiResponse.data.links.next;\n response.nextPage = async () => params.paginationHandler!(params.apiResponse.data.links.next);\n }\n\n if (params.apiResponse.data.links.prev && params.paginationHandler) {\n response.prev = params.apiResponse.data.links.prev;\n response.prevPage = async () => params.paginationHandler!(params.apiResponse.data.links.prev);\n }\n }\n\n if (Array.isArray(params.apiResponse.data.data)) {\n const responseData: T[] = [];\n\n for (const data of params.apiResponse.data.data) {\n const object = new factoryClass();\n object.rehydrate({ jsonApi: data, included: included, allData: params.apiResponse.data.data });\n responseData.push(object as T);\n }\n\n response.data = responseData;\n } else {\n const responseData = new factoryClass();\n responseData.rehydrate({\n jsonApi: params.apiResponse.data.data,\n included: included,\n });\n\n response.data = responseData;\n }\n } catch (e) {\n console.error(e);\n }\n\n return response;\n}\n","import { ENV } from \"../core/env\";\nimport { JsonApiDataFactory } from \"../core/factories/JsonApiDataFactory\";\nimport { ApiData } from \"../core/interfaces/ApiData\";\nimport { ApiRequestDataTypeInterface } from \"../core/interfaces/ApiRequestDataTypeInterface\";\nimport { ApiResponseInterface } from \"../core/interfaces/ApiResponseInterface\";\nimport { setBootstrapper } from \"../core/registry/bootstrapStore\";\nimport { translateResponse } from \"../core/utils/translateResponse\";\nimport { ModuleWithPermissions } from \"../permissions/types\";\n\n// Type definitions for dynamically imported functions (avoiding typeof import to prevent bundling)\ntype DirectFetchFn = (params: {\n method: string;\n url: string;\n token?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n companyId?: string;\n language: string;\n additionalHeaders?: Record<string, string>;\n}) => Promise<ApiData>;\n\ntype ServerRequestFn = (params: {\n method: string;\n url: string;\n token?: string;\n cache?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n companyId?: string;\n language: string;\n additionalHeaders?: Record<string, string>;\n}) => Promise<ApiData>;\n\ntype GetTokenFn = () => Promise<string | undefined>;\n\n// These will be dynamically imported based on environment\nlet _directFetch: DirectFetchFn;\nlet _serverRequest: ServerRequestFn;\nlet _getClientToken: GetTokenFn;\nlet _getServerToken: GetTokenFn;\n\n// Config storage for non-React contexts\nlet _staticConfig: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n} | null = null;\n\n/**\n * Configure the JSON:API client for non-React contexts (e.g., server components).\n * For React contexts, use JsonApiProvider instead.\n */\nexport function configureJsonApi(config: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n}): void {\n _staticConfig = config;\n // Register and call bootstrapper to register all modules\n if (config.bootstrapper) {\n setBootstrapper(config.bootstrapper);\n config.bootstrapper();\n }\n}\n\nasync function getToken(): Promise<string | undefined> {\n if (typeof window === \"undefined\") {\n // Server-side\n if (!_getServerToken) {\n const serverModule = await import(\"../server/token\");\n _getServerToken = serverModule.getServerToken;\n }\n return _getServerToken();\n } else {\n // Client-side\n if (!_getClientToken) {\n const clientModule = await import(\"../client/token\");\n _getClientToken = clientModule.getClientToken;\n }\n return _getClientToken();\n }\n}\n\nasync function makeRequest(params: {\n method: string;\n url: string;\n token?: string;\n cache?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n companyId?: string;\n language: string;\n additionalHeaders?: Record<string, string>;\n}): Promise<ApiData> {\n if (typeof window !== \"undefined\") {\n // Client-side: use direct fetch\n if (!_directFetch) {\n const clientModule = await import(\"../client/request\");\n _directFetch = clientModule.directFetch;\n }\n return _directFetch({\n method: params.method,\n url: params.url,\n token: params.token,\n body: params.body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: params.additionalHeaders,\n });\n } else {\n // Server-side: use server request with caching\n if (!_serverRequest) {\n const serverModule = await import(\"../server/request\");\n _serverRequest = serverModule.serverRequest;\n }\n return _serverRequest({\n method: params.method,\n url: params.url,\n token: params.token,\n cache: params.cache,\n body: params.body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: params.additionalHeaders,\n });\n }\n}\n\nexport function getApiUrl(): string {\n if (_staticConfig?.apiUrl) {\n return _staticConfig.apiUrl;\n }\n // Fallback to environment variable\n const envUrl = ENV.API_URL;\n if (!envUrl) {\n throw new Error(\"API URL not configured. Use configureJsonApi() or set NEXT_PUBLIC_API_URL environment variable.\");\n }\n return envUrl;\n}\n\nexport function getAppUrl(): string {\n if (_staticConfig?.appUrl) {\n return _staticConfig.appUrl;\n }\n // Fallback to environment variable\n const envUrl = ENV.APP_URL;\n if (!envUrl) {\n throw new Error(\n \"App URL not configured. Use configureJsonApi({ appUrl }) or set NEXT_PUBLIC_ADDRESS environment variable.\",\n );\n }\n return envUrl.trim().replace(/\\/+$/, \"\");\n}\n\nexport function getTrackablePages(): ModuleWithPermissions[] {\n return _staticConfig?.trackablePages ?? [];\n}\n\nfunction runBootstrapper(): void {\n if (_staticConfig?.bootstrapper) {\n _staticConfig.bootstrapper();\n }\n}\n\n/**\n * Resolve the final request URL for an endpoint.\n *\n * - `endpoint` starting with \"http\" is always passed through unchanged (existing behaviour).\n * - Otherwise, an explicit `baseUrl` (per-call override) takes precedence over the global\n * `getApiUrl()` resolution. Omitting `baseUrl` preserves today's behaviour exactly.\n */\nexport function buildUrl(endpoint: string, baseUrl?: string): string {\n if (endpoint.startsWith(\"http\")) return endpoint;\n const apiUrl = baseUrl ?? getApiUrl();\n return `${apiUrl}${endpoint}`;\n}\n\nexport async function JsonApiGet(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n language: string;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n // A cached server GET never sends the token, so it must not read the cookie either:\n // the read would make a public page dynamic.\n const isServerCached = typeof window === \"undefined\" && !!params.classKey.cache;\n const token = isServerCached ? undefined : (params.token ?? (await getToken()));\n\n const apiResponse = await makeRequest({\n method: \"GET\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n cache: params.classKey.cache,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n paginationHandler: async (endpoint: string) =>\n JsonApiGet({\n classKey: params.classKey,\n endpoint,\n companyId: params.companyId,\n language: params.language,\n token: params.token,\n baseUrl: params.baseUrl,\n }),\n });\n}\n\nexport async function JsonApiPost(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n overridesJsonApiCreation?: boolean;\n files?: { [key: string]: File | Blob } | File | Blob;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n const token = params.token ?? (await getToken());\n\n let body = params.body;\n if (!body) {\n body = {};\n } else if (params.overridesJsonApiCreation !== true) {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await makeRequest({\n method: \"POST\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function JsonApiPut(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n const token = params.token ?? (await getToken());\n\n let body = params.body;\n if (!body) {\n body = {};\n } else {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await makeRequest({\n method: \"PUT\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function JsonApiPatch(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n overridesJsonApiCreation?: boolean;\n responseType?: ApiRequestDataTypeInterface;\n language: string;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n const token = params.token ?? (await getToken());\n\n let body = params.body;\n if (!body) {\n body = {};\n } else if (params.overridesJsonApiCreation !== true) {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await makeRequest({\n method: \"PATCH\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function JsonApiDelete(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n token?: string;\n baseUrl?: string;\n}): Promise<ApiResponseInterface> {\n runBootstrapper();\n const token = params.token ?? (await getToken());\n\n const apiResponse = await makeRequest({\n method: \"DELETE\",\n url: buildUrl(params.endpoint, params.baseUrl),\n token,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _staticConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n"]}