@iamjulianacosta/mobx-data 1.1.1 → 1.4.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 (206) hide show
  1. package/README.md +273 -102
  2. package/dist/{CacheHandler-BTU_rYkv.js → CacheHandler-BhfbVHed.js} +17 -20
  3. package/dist/CacheHandler-BhfbVHed.js.map +1 -0
  4. package/dist/{CacheHandler-CXgY9IJo.cjs → CacheHandler-Q5VXOgh9.cjs} +2 -2
  5. package/dist/CacheHandler-Q5VXOgh9.cjs.map +1 -0
  6. package/dist/EmbeddedRecordsMixin-6mSCXsJ3.js +173 -0
  7. package/dist/EmbeddedRecordsMixin-6mSCXsJ3.js.map +1 -0
  8. package/dist/EmbeddedRecordsMixin-BkF7MdbY.cjs +2 -0
  9. package/dist/EmbeddedRecordsMixin-BkF7MdbY.cjs.map +1 -0
  10. package/dist/{JsonApiSerializer-wndq5a1n.js → JsonApiSerializer-BV61cFAZ.js} +3 -3
  11. package/dist/JsonApiSerializer-BV61cFAZ.js.map +1 -0
  12. package/dist/{JsonApiSerializer-Bc4iQB0d.cjs → JsonApiSerializer-Dt_Y_FIo.cjs} +2 -2
  13. package/dist/JsonApiSerializer-Dt_Y_FIo.cjs.map +1 -0
  14. package/dist/JsonSerializer-BzUCyUSf.cjs +2 -0
  15. package/dist/JsonSerializer-BzUCyUSf.cjs.map +1 -0
  16. package/dist/JsonSerializer-CFqo6GjC.js +98 -0
  17. package/dist/JsonSerializer-CFqo6GjC.js.map +1 -0
  18. package/dist/MdqlMemoryExecutor-BUlsalKm.cjs +2 -0
  19. package/dist/MdqlMemoryExecutor-BUlsalKm.cjs.map +1 -0
  20. package/dist/MdqlMemoryExecutor-BWMP31zG.js +127 -0
  21. package/dist/MdqlMemoryExecutor-BWMP31zG.js.map +1 -0
  22. package/dist/{MemoryAdapter-ni25N4H0.js → MemoryAdapter-BW1HKixm.js} +2 -2
  23. package/dist/{MemoryAdapter-ni25N4H0.js.map → MemoryAdapter-BW1HKixm.js.map} +1 -1
  24. package/dist/{MemoryAdapter-BTK2D64s.cjs → MemoryAdapter-C8iXAa2v.cjs} +2 -2
  25. package/dist/{MemoryAdapter-BTK2D64s.cjs.map → MemoryAdapter-C8iXAa2v.cjs.map} +1 -1
  26. package/dist/{ODataAdapter-DAja_jKM.js → ODataAdapter-CeBJblLQ.js} +25 -22
  27. package/dist/ODataAdapter-CeBJblLQ.js.map +1 -0
  28. package/dist/{ODataAdapter-lMifLyLD.cjs → ODataAdapter-DdE6MWkG.cjs} +2 -2
  29. package/dist/ODataAdapter-DdE6MWkG.cjs.map +1 -0
  30. package/dist/RestAdapter-D7GSrsJo.cjs +2 -0
  31. package/dist/RestAdapter-D7GSrsJo.cjs.map +1 -0
  32. package/dist/{RestAdapter-CGWqOR_G.js → RestAdapter-DYUoyV5h.js} +112 -77
  33. package/dist/RestAdapter-DYUoyV5h.js.map +1 -0
  34. package/dist/SchemaService-C_pkh-vI.js +180 -0
  35. package/dist/SchemaService-C_pkh-vI.js.map +1 -0
  36. package/dist/SchemaService-DbJLoYb9.cjs +2 -0
  37. package/dist/SchemaService-DbJLoYb9.cjs.map +1 -0
  38. package/dist/Serializer-Bap9U-kR.cjs +2 -0
  39. package/dist/Serializer-Bap9U-kR.cjs.map +1 -0
  40. package/dist/{Serializer-FxJbsZ50.js → Serializer-Ca6w_QNQ.js} +63 -49
  41. package/dist/Serializer-Ca6w_QNQ.js.map +1 -0
  42. package/dist/adapter/index.cjs +1 -1
  43. package/dist/adapter/index.js +2 -2
  44. package/dist/cache/cache-utils.d.ts +1 -1
  45. package/dist/cache/cache-utils.d.ts.map +1 -1
  46. package/dist/createStore-7PecKT54.cjs +2 -0
  47. package/dist/createStore-7PecKT54.cjs.map +1 -0
  48. package/dist/createStore-BfmRfZ_2.js +1229 -0
  49. package/dist/createStore-BfmRfZ_2.js.map +1 -0
  50. package/dist/date-Bj4O2W1F.js.map +1 -1
  51. package/dist/date-CRCe-9gf.cjs.map +1 -1
  52. package/dist/decorators-CKneHgoF.js +56 -0
  53. package/dist/decorators-CKneHgoF.js.map +1 -0
  54. package/dist/decorators-DCVYKzrL.cjs +2 -0
  55. package/dist/decorators-DCVYKzrL.cjs.map +1 -0
  56. package/dist/index.cjs +1 -1
  57. package/dist/index.cjs.map +1 -1
  58. package/dist/index.d.ts +2 -1
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +100 -90
  61. package/dist/index.js.map +1 -1
  62. package/dist/inspector/ConsoleInspector.d.ts +49 -0
  63. package/dist/inspector/ConsoleInspector.d.ts.map +1 -0
  64. package/dist/inspector/DevToolsBridge.d.ts +21 -0
  65. package/dist/inspector/DevToolsBridge.d.ts.map +1 -0
  66. package/dist/inspector/QueryParser.d.ts +21 -0
  67. package/dist/inspector/QueryParser.d.ts.map +1 -0
  68. package/dist/inspector/StoreInspector.d.ts +31 -0
  69. package/dist/inspector/StoreInspector.d.ts.map +1 -0
  70. package/dist/inspector/index.cjs +17 -0
  71. package/dist/inspector/index.cjs.map +1 -0
  72. package/dist/inspector/index.d.ts +9 -0
  73. package/dist/inspector/index.d.ts.map +1 -0
  74. package/dist/inspector/index.js +896 -0
  75. package/dist/inspector/index.js.map +1 -0
  76. package/dist/inspector/integration.d.ts +15 -0
  77. package/dist/inspector/integration.d.ts.map +1 -0
  78. package/dist/inspector/serialization.d.ts +7 -0
  79. package/dist/inspector/serialization.d.ts.map +1 -0
  80. package/dist/inspector/types.d.ts +139 -0
  81. package/dist/inspector/types.d.ts.map +1 -0
  82. package/dist/json-api/index.cjs +1 -1
  83. package/dist/json-api/index.js +1 -1
  84. package/dist/mdql/MdqlMemoryExecutor.d.ts +17 -0
  85. package/dist/mdql/MdqlMemoryExecutor.d.ts.map +1 -0
  86. package/dist/mdql/MdqlQueryBuilder.d.ts +38 -0
  87. package/dist/mdql/MdqlQueryBuilder.d.ts.map +1 -0
  88. package/dist/mdql/MdqlValidator.d.ts +13 -0
  89. package/dist/mdql/MdqlValidator.d.ts.map +1 -0
  90. package/dist/mdql/index.d.ts +6 -0
  91. package/dist/mdql/index.d.ts.map +1 -0
  92. package/dist/mdql/types.d.ts +48 -0
  93. package/dist/mdql/types.d.ts.map +1 -0
  94. package/dist/model/Model.d.ts +4 -0
  95. package/dist/model/Model.d.ts.map +1 -1
  96. package/dist/model/Snapshot.d.ts +2 -0
  97. package/dist/model/Snapshot.d.ts.map +1 -1
  98. package/dist/model/index.cjs +1 -1
  99. package/dist/model/index.js +1 -1
  100. package/dist/odata/ODataAdapter.d.ts.map +1 -1
  101. package/dist/odata/index.cjs +1 -1
  102. package/dist/odata/index.js +1 -1
  103. package/dist/relationships-BgM0NKdb.cjs +2 -0
  104. package/dist/relationships-BgM0NKdb.cjs.map +1 -0
  105. package/dist/{relationships-BEXANmWg.js → relationships-DvSi8fVN.js} +37 -28
  106. package/dist/relationships-DvSi8fVN.js.map +1 -0
  107. package/dist/request/CacheHandler.d.ts.map +1 -1
  108. package/dist/request/index.cjs +1 -1
  109. package/dist/request/index.js +1 -1
  110. package/dist/schema/SchemaService.d.ts +38 -1
  111. package/dist/schema/SchemaService.d.ts.map +1 -1
  112. package/dist/schema/decorators.d.ts +20 -1
  113. package/dist/schema/decorators.d.ts.map +1 -1
  114. package/dist/schema/index.cjs +1 -1
  115. package/dist/schema/index.d.ts +1 -1
  116. package/dist/schema/index.d.ts.map +1 -1
  117. package/dist/schema/index.js +10 -8
  118. package/dist/schema/types.d.ts +31 -0
  119. package/dist/schema/types.d.ts.map +1 -1
  120. package/dist/serializer/JsonSerializer.d.ts +2 -0
  121. package/dist/serializer/JsonSerializer.d.ts.map +1 -1
  122. package/dist/serializer/Serializer.d.ts +9 -0
  123. package/dist/serializer/Serializer.d.ts.map +1 -1
  124. package/dist/serializer/index.cjs +1 -1
  125. package/dist/serializer/index.js +6 -5
  126. package/dist/serializer/index.js.map +1 -1
  127. package/dist/store/Store.d.ts +3 -0
  128. package/dist/store/Store.d.ts.map +1 -1
  129. package/dist/store/createStore.d.ts +12 -0
  130. package/dist/store/createStore.d.ts.map +1 -0
  131. package/dist/store/index.cjs +1 -1
  132. package/dist/store/index.d.ts +1 -0
  133. package/dist/store/index.d.ts.map +1 -1
  134. package/dist/store/index.js +5 -4
  135. package/dist/types-CC2fG3FP.js +8 -0
  136. package/dist/types-CC2fG3FP.js.map +1 -0
  137. package/dist/types-DCLy5XYj.cjs +2 -0
  138. package/dist/types-DCLy5XYj.cjs.map +1 -0
  139. package/package.json +7 -1
  140. package/src/cache/cache-utils.ts +4 -4
  141. package/src/index.ts +3 -0
  142. package/src/inspector/ConsoleInspector.ts +470 -0
  143. package/src/inspector/DevToolsBridge.ts +214 -0
  144. package/src/inspector/QueryParser.ts +343 -0
  145. package/src/inspector/StoreInspector.ts +162 -0
  146. package/src/inspector/index.ts +20 -0
  147. package/src/inspector/integration.ts +56 -0
  148. package/src/inspector/serialization.ts +100 -0
  149. package/src/inspector/types.ts +161 -0
  150. package/src/mdql/MdqlMemoryExecutor.ts +229 -0
  151. package/src/mdql/MdqlQueryBuilder.ts +170 -0
  152. package/src/mdql/MdqlValidator.ts +193 -0
  153. package/src/mdql/index.ts +21 -0
  154. package/src/mdql/types.ts +107 -0
  155. package/src/model/Model.ts +15 -0
  156. package/src/model/Snapshot.ts +3 -0
  157. package/src/odata/ODataAdapter.ts +4 -1
  158. package/src/request/CacheHandler.ts +2 -6
  159. package/src/schema/SchemaService.ts +123 -1
  160. package/src/schema/decorators.ts +29 -0
  161. package/src/schema/index.ts +1 -1
  162. package/src/schema/types.ts +34 -0
  163. package/src/serializer/JsonSerializer.ts +14 -2
  164. package/src/serializer/Serializer.ts +24 -1
  165. package/src/store/Store.ts +61 -18
  166. package/src/store/createStore.ts +39 -0
  167. package/src/store/index.ts +1 -0
  168. package/dist/CacheHandler-BTU_rYkv.js.map +0 -1
  169. package/dist/CacheHandler-CXgY9IJo.cjs.map +0 -1
  170. package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs +0 -2
  171. package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs.map +0 -1
  172. package/dist/EmbeddedRecordsMixin-VoHluHCT.js +0 -261
  173. package/dist/EmbeddedRecordsMixin-VoHluHCT.js.map +0 -1
  174. package/dist/JsonApiSerializer-Bc4iQB0d.cjs.map +0 -1
  175. package/dist/JsonApiSerializer-wndq5a1n.js.map +0 -1
  176. package/dist/ODataAdapter-DAja_jKM.js.map +0 -1
  177. package/dist/ODataAdapter-lMifLyLD.cjs.map +0 -1
  178. package/dist/RestAdapter-1V94stW-.cjs +0 -2
  179. package/dist/RestAdapter-1V94stW-.cjs.map +0 -1
  180. package/dist/RestAdapter-CGWqOR_G.js.map +0 -1
  181. package/dist/SchemaService-DZwkFgZu.js +0 -102
  182. package/dist/SchemaService-DZwkFgZu.js.map +0 -1
  183. package/dist/SchemaService-Di_yjVzU.cjs +0 -2
  184. package/dist/SchemaService-Di_yjVzU.cjs.map +0 -1
  185. package/dist/Serializer-95gi5edy.cjs +0 -2
  186. package/dist/Serializer-95gi5edy.cjs.map +0 -1
  187. package/dist/Serializer-FxJbsZ50.js.map +0 -1
  188. package/dist/Store-KvjmBTQ9.cjs +0 -2
  189. package/dist/Store-KvjmBTQ9.cjs.map +0 -1
  190. package/dist/Store-mvrDLQEZ.js +0 -957
  191. package/dist/Store-mvrDLQEZ.js.map +0 -1
  192. package/dist/cache-utils-2lswvJ87.cjs +0 -2
  193. package/dist/cache-utils-2lswvJ87.cjs.map +0 -1
  194. package/dist/cache-utils-38Dqu4Qf.js +0 -39
  195. package/dist/cache-utils-38Dqu4Qf.js.map +0 -1
  196. package/dist/decorators-HQ1KnRdh.cjs +0 -2
  197. package/dist/decorators-HQ1KnRdh.cjs.map +0 -1
  198. package/dist/decorators-Zr35qr6A.js +0 -50
  199. package/dist/decorators-Zr35qr6A.js.map +0 -1
  200. package/dist/relationships-B55LBaCW.cjs +0 -2
  201. package/dist/relationships-B55LBaCW.cjs.map +0 -1
  202. package/dist/relationships-BEXANmWg.js.map +0 -1
  203. package/dist/types-C9NB2gRj.js +0 -7
  204. package/dist/types-C9NB2gRj.js.map +0 -1
  205. package/dist/types-uWOXMPWW.cjs +0 -2
  206. package/dist/types-uWOXMPWW.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CacheHandler-BhfbVHed.js","sources":["../src/request/RequestManager.ts","../src/request/FetchHandler.ts","../src/request/CacheHandler.ts"],"sourcesContent":["/**\n * Middleware-style manager that runs a `StoreRequest` through an ordered chain\n * of `Handler` instances.\n *\n * Handlers are invoked in registration order. Each handler receives a\n * `RequestContext` and a `next` function; it may:\n * - Call `next(request)` to pass control to the next handler.\n * - Return a `StoreResponse` directly to short-circuit the chain.\n * - Wrap `next` to inspect or transform the response (e.g. logging).\n *\n * An optional *cache handler* registered via `useCache()` is always prepended\n * to the chain so it runs before every other handler.\n *\n * Usage:\n * ```ts\n * const manager = new RequestManager()\n * .useCache(new CacheHandler())\n * .use([new AuthHandler(), new FetchHandler()]);\n *\n * const response = await manager.request({ method: 'GET', url: '/posts' });\n * ```\n */\n\nimport { injectable } from 'tsyringe';\nimport type {\n Handler,\n NextFn,\n RequestContext,\n StoreRequest,\n StoreResponse,\n} from './types.js';\n\n@injectable()\nexport class RequestManager {\n private _handlers: Handler[] = [];\n private _cache: Handler | null = null;\n\n /**\n * Appends one or more handlers to the pipeline.\n * Returns `this` for chaining.\n */\n use(handlers: Handler[] | Handler): this {\n if (Array.isArray(handlers)) {\n this._handlers.push(...handlers);\n } else {\n this._handlers.push(handlers);\n }\n return this;\n }\n\n /**\n * Registers the cache handler. It is always inserted at the front of\n * the chain so it can intercept requests before any other handler sees them.\n * Returns `this` for chaining.\n */\n useCache(handler: Handler): this {\n this._cache = handler;\n return this;\n }\n\n /** All non-cache handlers in registration order. */\n get handlers(): readonly Handler[] {\n return this._handlers;\n }\n\n /** The registered cache handler, or `null`. */\n get cacheHandler(): Handler | null {\n return this._cache;\n }\n\n /**\n * Executes `request` through the full handler chain and returns the final\n * `StoreResponse`.\n *\n * @throws when no handlers have been registered.\n * @throws when the chain ends without any handler returning a response\n * (i.e. no terminal handler such as `FetchHandler`).\n */\n async request<T = unknown>(request: StoreRequest): Promise<StoreResponse<T>> {\n const chain: Handler[] = this._cache\n ? [this._cache, ...this._handlers]\n : [...this._handlers];\n\n if (chain.length === 0) {\n throw new Error(\n 'RequestManager has no handlers registered — cannot complete request',\n );\n }\n\n let index = 0;\n const invoke = async (req: StoreRequest): Promise<StoreResponse<T>> => {\n const handler = chain[index];\n if (!handler) {\n throw new Error(\n 'Handler chain ended without producing a response (no terminal handler)',\n );\n }\n index++;\n const context: RequestContext<T> = {\n request: req,\n response: undefined,\n setResponse(r) {\n this.response = r;\n },\n };\n const next: NextFn<T> = (nextRequest) => invoke(nextRequest);\n return handler.request(context, next) as Promise<StoreResponse<T>>;\n };\n\n return invoke(request);\n }\n}\n","/**\r\n * Terminal request handler that executes HTTP calls via the browser / Node\r\n * `fetch` API.\r\n *\r\n * `FetchHandler` is designed to sit at the end of the `RequestManager` chain.\r\n * It does not call `next` — it issues the network request and returns the\r\n * parsed response directly.\r\n *\r\n * Response body parsing:\r\n * - `204 No Content` or `Content-Length: 0` → `null`\r\n * - `application/json` or `application/vnd.api+json` → parsed JSON (falls\r\n * back to raw text if parsing fails)\r\n * - Everything else → raw text string\r\n *\r\n * Error handling:\r\n * - Non-2xx responses throw a `FetchError` that includes `status`, `content`,\r\n * and `headers` for downstream error handling.\r\n */\r\n\r\nimport { injectable } from 'tsyringe';\r\nimport type {\r\n Handler,\r\n NextFn,\r\n RequestContext,\r\n StoreResponse,\r\n} from './types.js';\r\n\r\n/**\r\n * Error thrown by `FetchHandler` for non-2xx HTTP responses.\r\n * Carries the status code, parsed body content, and response headers.\r\n */\r\nexport class FetchError extends Error {\r\n readonly status: number;\r\n readonly content: unknown;\r\n readonly headers: Record<string, string>;\r\n constructor(\r\n status: number,\r\n content: unknown,\r\n headers: Record<string, string>,\r\n message?: string,\r\n ) {\r\n super(message ?? `Request failed with status ${status}`);\r\n this.status = status;\r\n this.content = content;\r\n this.headers = headers;\r\n }\r\n}\r\n\r\n@injectable()\r\nexport class FetchHandler implements Handler {\r\n static headersToObject(headers: Headers): Record<string, string> {\r\n const out: Record<string, string> = {};\r\n headers.forEach((value, key) => {\r\n out[key] = value;\r\n });\r\n return out;\r\n }\r\n\r\n static async parseBody(response: Response): Promise<unknown> {\r\n const contentType = response.headers.get('Content-Type') ?? '';\r\n if (\r\n response.status === 204\r\n || response.headers.get('Content-Length') === '0'\r\n ) {\r\n return null;\r\n }\r\n const isJson = contentType.includes('application/json')\r\n || contentType.includes('application/vnd.api+json');\r\n if (isJson) {\r\n const text = await response.text();\r\n if (!text) {\r\n return null;\r\n }\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return text;\r\n }\r\n }\r\n return response.text();\r\n }\r\n\r\n /**\r\n * Issues the HTTP request and returns a `StoreResponse`.\r\n * This is a terminal handler — it never calls `next`.\r\n *\r\n * @throws `FetchError` on non-2xx responses.\r\n */\r\n async request<T = unknown>(\r\n context: RequestContext<T>,\r\n _next: NextFn<T>,\r\n ): Promise<StoreResponse<T>> {\r\n const req = context.request;\r\n const init: RequestInit = {\r\n method: req.method,\r\n headers: req.headers,\r\n };\r\n if (req.body !== undefined && req.body !== null) {\r\n init.body = req.body;\r\n }\r\n if (req.signal) {\r\n init.signal = req.signal;\r\n }\r\n\r\n const response = await fetch(req.url, init);\r\n const content = await FetchHandler.parseBody(response);\r\n const headers = FetchHandler.headersToObject(response.headers);\r\n\r\n if (!response.ok) {\r\n throw new FetchError(response.status, content, headers);\r\n }\r\n return {\r\n content: content as T,\r\n status: response.status,\r\n headers,\r\n request: req,\r\n };\r\n }\r\n}\r\n","/**\r\n * In-memory caching handler for the request pipeline.\r\n *\r\n * `CacheHandler` is registered via `RequestManager.useCache()` so it always\r\n * runs first in the chain. It caches `GET` responses and replays them on\r\n * subsequent requests with the same key — unless `cacheOptions.reload: true`\r\n * is set, in which case it bypasses the cache and stores the fresh response.\r\n *\r\n * Cache key: `cacheOptions.key` when provided, otherwise `\"<METHOD> <URL>\"`.\r\n *\r\n * Only `GET` requests are cached; mutations (POST, PUT, PATCH, DELETE) are\r\n * always forwarded to `next` without touching the cache.\r\n *\r\n * Usage:\r\n * ```ts\r\n * const manager = new RequestManager()\r\n * .useCache(new CacheHandler())\r\n * .use(new FetchHandler());\r\n * ```\r\n */\r\n\r\nimport { injectable } from 'tsyringe';\r\nimport type {\r\n Handler,\r\n NextFn,\r\n RequestContext,\r\n StoreRequest,\r\n StoreResponse,\r\n} from './types.js';\r\n\r\ninterface CacheEntry {\r\n response: StoreResponse;\r\n cachedAt: number;\r\n}\r\n\r\n@injectable()\r\nexport class CacheHandler implements Handler {\r\n private cache = new Map<string, CacheEntry>();\r\n\r\n maxSize: number = 256;\r\n\r\n ttl: number = 300_000;\r\n\r\n private isExpired(entry: CacheEntry): boolean {\r\n return Date.now() - entry.cachedAt > this.ttl;\r\n }\r\n\r\n private evictLRU(): void {\r\n while (this.cache.size > this.maxSize) {\r\n const firstKey = this.cache.keys().next().value as string;\r\n this.cache.delete(firstKey);\r\n }\r\n }\r\n\r\n static keyFor(req: StoreRequest): string {\r\n return req.cacheOptions?.key ?? `${req.method} ${req.url}`;\r\n }\r\n\r\n static isCacheable(req: StoreRequest): boolean {\r\n return req.method === 'GET';\r\n }\r\n\r\n /**\r\n * Handles a request by checking the in-memory cache before forwarding to\r\n * the next handler.\r\n *\r\n * - Non-GET requests skip the cache entirely.\r\n * - `cacheOptions.reload: true` forces a network request and refreshes the entry.\r\n * - Cached entries expire after `ttl` milliseconds (default 5 minutes).\r\n * - The cache uses LRU eviction when it exceeds `maxSize` entries (default 256).\r\n */\r\n async request<T = unknown>(\r\n context: RequestContext<T>,\r\n next: NextFn<T>,\r\n ): Promise<StoreResponse<T>> {\r\n const req = context.request;\r\n if (!CacheHandler.isCacheable(req)) {\r\n return next(req);\r\n }\r\n\r\n const key = CacheHandler.keyFor(req);\r\n const reload = req.cacheOptions?.reload === true;\r\n\r\n if (!reload) {\r\n const hit = this.cache.get(key);\r\n if (hit) {\r\n if (this.isExpired(hit)) {\r\n this.cache.delete(key);\r\n } else {\r\n // LRU promotion: delete and re-set to move to end\r\n this.cache.delete(key);\r\n this.cache.set(key, hit);\r\n return hit.response as StoreResponse<T>;\r\n }\r\n }\r\n }\r\n\r\n const response = await next(req);\r\n this.cache.set(key, { response: response as StoreResponse, cachedAt: Date.now() });\r\n this.evictLRU();\r\n return response;\r\n }\r\n\r\n /** Returns the number of entries currently in the cache. */\r\n get size(): number {\r\n return this.cache.size;\r\n }\r\n\r\n /** Removes all entries from the cache. */\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n /**\r\n * Removes a single entry by key.\r\n * @returns `true` when the entry existed and was deleted.\r\n */\r\n delete(key: string): boolean {\r\n return this.cache.delete(key);\r\n }\r\n}\r\n"],"names":["RequestManager","handlers","handler","request","chain","index","invoke","req","context","r","next","nextRequest","__decorateClass","injectable","FetchError","status","content","headers","message","FetchHandler","out","value","key","response","contentType","text","_next","init","CacheHandler","entry","firstKey","_a","hit"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";const l=require("tsyringe");var u=Object.getOwnPropertyDescriptor,d=(c,e,r,s)=>{for(var t=s>1?void 0:s?u(e,r):e,a=c.length-1,n;a>=0;a--)(n=c[a])&&(t=n(t)||t);return t};exports.RequestManager=class{constructor(){this._handlers=[],this._cache=null}use(e){return Array.isArray(e)?this._handlers.push(...e):this._handlers.push(e),this}useCache(e){return this._cache=e,this}get handlers(){return this._handlers}get cacheHandler(){return this._cache}async request(e){const r=this._cache?[this._cache,...this._handlers]:[...this._handlers];if(r.length===0)throw new Error("RequestManager has no handlers registered — cannot complete request");let s=0;const t=async a=>{const n=r[s];if(!n)throw new Error("Handler chain ended without producing a response (no terminal handler)");s++;const h={request:a,response:void 0,setResponse(o){this.response=o}},i=o=>t(o);return n.request(h,i)};return t(e)}};exports.RequestManager=d([l.injectable()],exports.RequestManager);var p=Object.getOwnPropertyDescriptor,y=(c,e,r,s)=>{for(var t=s>1?void 0:s?p(e,r):e,a=c.length-1,n;a>=0;a--)(n=c[a])&&(t=n(t)||t);return t};class g extends Error{constructor(e,r,s,t){super(t??`Request failed with status ${e}`),this.status=e,this.content=r,this.headers=s}}exports.FetchHandler=class{static headersToObject(e){const r={};return e.forEach((s,t)=>{r[t]=s}),r}static async parseBody(e){const r=e.headers.get("Content-Type")??"";if(e.status===204||e.headers.get("Content-Length")==="0")return null;if(r.includes("application/json")||r.includes("application/vnd.api+json")){const t=await e.text();if(!t)return null;try{return JSON.parse(t)}catch{return t}}return e.text()}async request(e,r){const s=e.request,t={method:s.method,headers:s.headers};s.body!==void 0&&s.body!==null&&(t.body=s.body),s.signal&&(t.signal=s.signal);const a=await fetch(s.url,t),n=await exports.FetchHandler.parseBody(a),h=exports.FetchHandler.headersToObject(a.headers);if(!a.ok)throw new g(a.status,n,h);return{content:n,status:a.status,headers:h,request:s}}};exports.FetchHandler=y([l.injectable()],exports.FetchHandler);var f=Object.getOwnPropertyDescriptor,_=(c,e,r,s)=>{for(var t=s>1?void 0:s?f(e,r):e,a=c.length-1,n;a>=0;a--)(n=c[a])&&(t=n(t)||t);return t};exports.CacheHandler=class{constructor(){this.cache=new Map,this.maxSize=256,this.ttl=3e5}isExpired(e){return Date.now()-e.cachedAt>this.ttl}evictLRU(){for(;this.cache.size>this.maxSize;){const e=this.cache.keys().next().value;if(e!==void 0)this.cache.delete(e);else break}}static keyFor(e){var r;return((r=e.cacheOptions)==null?void 0:r.key)??`${e.method} ${e.url}`}static isCacheable(e){return e.method==="GET"}async request(e,r){var h;const s=e.request;if(!exports.CacheHandler.isCacheable(s))return r(s);const t=exports.CacheHandler.keyFor(s);if(!(((h=s.cacheOptions)==null?void 0:h.reload)===!0)){const i=this.cache.get(t);if(i)if(this.isExpired(i))this.cache.delete(t);else return this.cache.delete(t),this.cache.set(t,i),i.response}const n=await r(s);return this.cache.set(t,{response:n,cachedAt:Date.now()}),this.evictLRU(),n}get size(){return this.cache.size}clear(){this.cache.clear()}delete(e){return this.cache.delete(e)}};exports.CacheHandler=_([l.injectable()],exports.CacheHandler);
2
- //# sourceMappingURL=CacheHandler-CXgY9IJo.cjs.map
1
+ "use strict";const l=require("tsyringe");var u=Object.getOwnPropertyDescriptor,d=(c,e,r,s)=>{for(var t=s>1?void 0:s?u(e,r):e,a=c.length-1,n;a>=0;a--)(n=c[a])&&(t=n(t)||t);return t};exports.RequestManager=class{constructor(){this._handlers=[],this._cache=null}use(e){return Array.isArray(e)?this._handlers.push(...e):this._handlers.push(e),this}useCache(e){return this._cache=e,this}get handlers(){return this._handlers}get cacheHandler(){return this._cache}async request(e){const r=this._cache?[this._cache,...this._handlers]:[...this._handlers];if(r.length===0)throw new Error("RequestManager has no handlers registered — cannot complete request");let s=0;const t=async a=>{const n=r[s];if(!n)throw new Error("Handler chain ended without producing a response (no terminal handler)");s++;const h={request:a,response:void 0,setResponse(o){this.response=o}},i=o=>t(o);return n.request(h,i)};return t(e)}};exports.RequestManager=d([l.injectable()],exports.RequestManager);var p=Object.getOwnPropertyDescriptor,y=(c,e,r,s)=>{for(var t=s>1?void 0:s?p(e,r):e,a=c.length-1,n;a>=0;a--)(n=c[a])&&(t=n(t)||t);return t};class g extends Error{constructor(e,r,s,t){super(t??`Request failed with status ${e}`),this.status=e,this.content=r,this.headers=s}}exports.FetchHandler=class{static headersToObject(e){const r={};return e.forEach((s,t)=>{r[t]=s}),r}static async parseBody(e){const r=e.headers.get("Content-Type")??"";if(e.status===204||e.headers.get("Content-Length")==="0")return null;if(r.includes("application/json")||r.includes("application/vnd.api+json")){const t=await e.text();if(!t)return null;try{return JSON.parse(t)}catch{return t}}return e.text()}async request(e,r){const s=e.request,t={method:s.method,headers:s.headers};s.body!==void 0&&s.body!==null&&(t.body=s.body),s.signal&&(t.signal=s.signal);const a=await fetch(s.url,t),n=await exports.FetchHandler.parseBody(a),h=exports.FetchHandler.headersToObject(a.headers);if(!a.ok)throw new g(a.status,n,h);return{content:n,status:a.status,headers:h,request:s}}};exports.FetchHandler=y([l.injectable()],exports.FetchHandler);var _=Object.getOwnPropertyDescriptor,f=(c,e,r,s)=>{for(var t=s>1?void 0:s?_(e,r):e,a=c.length-1,n;a>=0;a--)(n=c[a])&&(t=n(t)||t);return t};exports.CacheHandler=class{constructor(){this.cache=new Map,this.maxSize=256,this.ttl=3e5}isExpired(e){return Date.now()-e.cachedAt>this.ttl}evictLRU(){for(;this.cache.size>this.maxSize;){const e=this.cache.keys().next().value;this.cache.delete(e)}}static keyFor(e){var r;return((r=e.cacheOptions)==null?void 0:r.key)??`${e.method} ${e.url}`}static isCacheable(e){return e.method==="GET"}async request(e,r){var h;const s=e.request;if(!exports.CacheHandler.isCacheable(s))return r(s);const t=exports.CacheHandler.keyFor(s);if(!(((h=s.cacheOptions)==null?void 0:h.reload)===!0)){const i=this.cache.get(t);if(i)if(this.isExpired(i))this.cache.delete(t);else return this.cache.delete(t),this.cache.set(t,i),i.response}const n=await r(s);return this.cache.set(t,{response:n,cachedAt:Date.now()}),this.evictLRU(),n}get size(){return this.cache.size}clear(){this.cache.clear()}delete(e){return this.cache.delete(e)}};exports.CacheHandler=f([l.injectable()],exports.CacheHandler);
2
+ //# sourceMappingURL=CacheHandler-Q5VXOgh9.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CacheHandler-Q5VXOgh9.cjs","sources":["../src/request/RequestManager.ts","../src/request/FetchHandler.ts","../src/request/CacheHandler.ts"],"sourcesContent":["/**\n * Middleware-style manager that runs a `StoreRequest` through an ordered chain\n * of `Handler` instances.\n *\n * Handlers are invoked in registration order. Each handler receives a\n * `RequestContext` and a `next` function; it may:\n * - Call `next(request)` to pass control to the next handler.\n * - Return a `StoreResponse` directly to short-circuit the chain.\n * - Wrap `next` to inspect or transform the response (e.g. logging).\n *\n * An optional *cache handler* registered via `useCache()` is always prepended\n * to the chain so it runs before every other handler.\n *\n * Usage:\n * ```ts\n * const manager = new RequestManager()\n * .useCache(new CacheHandler())\n * .use([new AuthHandler(), new FetchHandler()]);\n *\n * const response = await manager.request({ method: 'GET', url: '/posts' });\n * ```\n */\n\nimport { injectable } from 'tsyringe';\nimport type {\n Handler,\n NextFn,\n RequestContext,\n StoreRequest,\n StoreResponse,\n} from './types.js';\n\n@injectable()\nexport class RequestManager {\n private _handlers: Handler[] = [];\n private _cache: Handler | null = null;\n\n /**\n * Appends one or more handlers to the pipeline.\n * Returns `this` for chaining.\n */\n use(handlers: Handler[] | Handler): this {\n if (Array.isArray(handlers)) {\n this._handlers.push(...handlers);\n } else {\n this._handlers.push(handlers);\n }\n return this;\n }\n\n /**\n * Registers the cache handler. It is always inserted at the front of\n * the chain so it can intercept requests before any other handler sees them.\n * Returns `this` for chaining.\n */\n useCache(handler: Handler): this {\n this._cache = handler;\n return this;\n }\n\n /** All non-cache handlers in registration order. */\n get handlers(): readonly Handler[] {\n return this._handlers;\n }\n\n /** The registered cache handler, or `null`. */\n get cacheHandler(): Handler | null {\n return this._cache;\n }\n\n /**\n * Executes `request` through the full handler chain and returns the final\n * `StoreResponse`.\n *\n * @throws when no handlers have been registered.\n * @throws when the chain ends without any handler returning a response\n * (i.e. no terminal handler such as `FetchHandler`).\n */\n async request<T = unknown>(request: StoreRequest): Promise<StoreResponse<T>> {\n const chain: Handler[] = this._cache\n ? [this._cache, ...this._handlers]\n : [...this._handlers];\n\n if (chain.length === 0) {\n throw new Error(\n 'RequestManager has no handlers registered — cannot complete request',\n );\n }\n\n let index = 0;\n const invoke = async (req: StoreRequest): Promise<StoreResponse<T>> => {\n const handler = chain[index];\n if (!handler) {\n throw new Error(\n 'Handler chain ended without producing a response (no terminal handler)',\n );\n }\n index++;\n const context: RequestContext<T> = {\n request: req,\n response: undefined,\n setResponse(r) {\n this.response = r;\n },\n };\n const next: NextFn<T> = (nextRequest) => invoke(nextRequest);\n return handler.request(context, next) as Promise<StoreResponse<T>>;\n };\n\n return invoke(request);\n }\n}\n","/**\r\n * Terminal request handler that executes HTTP calls via the browser / Node\r\n * `fetch` API.\r\n *\r\n * `FetchHandler` is designed to sit at the end of the `RequestManager` chain.\r\n * It does not call `next` — it issues the network request and returns the\r\n * parsed response directly.\r\n *\r\n * Response body parsing:\r\n * - `204 No Content` or `Content-Length: 0` → `null`\r\n * - `application/json` or `application/vnd.api+json` → parsed JSON (falls\r\n * back to raw text if parsing fails)\r\n * - Everything else → raw text string\r\n *\r\n * Error handling:\r\n * - Non-2xx responses throw a `FetchError` that includes `status`, `content`,\r\n * and `headers` for downstream error handling.\r\n */\r\n\r\nimport { injectable } from 'tsyringe';\r\nimport type {\r\n Handler,\r\n NextFn,\r\n RequestContext,\r\n StoreResponse,\r\n} from './types.js';\r\n\r\n/**\r\n * Error thrown by `FetchHandler` for non-2xx HTTP responses.\r\n * Carries the status code, parsed body content, and response headers.\r\n */\r\nexport class FetchError extends Error {\r\n readonly status: number;\r\n readonly content: unknown;\r\n readonly headers: Record<string, string>;\r\n constructor(\r\n status: number,\r\n content: unknown,\r\n headers: Record<string, string>,\r\n message?: string,\r\n ) {\r\n super(message ?? `Request failed with status ${status}`);\r\n this.status = status;\r\n this.content = content;\r\n this.headers = headers;\r\n }\r\n}\r\n\r\n@injectable()\r\nexport class FetchHandler implements Handler {\r\n static headersToObject(headers: Headers): Record<string, string> {\r\n const out: Record<string, string> = {};\r\n headers.forEach((value, key) => {\r\n out[key] = value;\r\n });\r\n return out;\r\n }\r\n\r\n static async parseBody(response: Response): Promise<unknown> {\r\n const contentType = response.headers.get('Content-Type') ?? '';\r\n if (\r\n response.status === 204\r\n || response.headers.get('Content-Length') === '0'\r\n ) {\r\n return null;\r\n }\r\n const isJson = contentType.includes('application/json')\r\n || contentType.includes('application/vnd.api+json');\r\n if (isJson) {\r\n const text = await response.text();\r\n if (!text) {\r\n return null;\r\n }\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return text;\r\n }\r\n }\r\n return response.text();\r\n }\r\n\r\n /**\r\n * Issues the HTTP request and returns a `StoreResponse`.\r\n * This is a terminal handler — it never calls `next`.\r\n *\r\n * @throws `FetchError` on non-2xx responses.\r\n */\r\n async request<T = unknown>(\r\n context: RequestContext<T>,\r\n _next: NextFn<T>,\r\n ): Promise<StoreResponse<T>> {\r\n const req = context.request;\r\n const init: RequestInit = {\r\n method: req.method,\r\n headers: req.headers,\r\n };\r\n if (req.body !== undefined && req.body !== null) {\r\n init.body = req.body;\r\n }\r\n if (req.signal) {\r\n init.signal = req.signal;\r\n }\r\n\r\n const response = await fetch(req.url, init);\r\n const content = await FetchHandler.parseBody(response);\r\n const headers = FetchHandler.headersToObject(response.headers);\r\n\r\n if (!response.ok) {\r\n throw new FetchError(response.status, content, headers);\r\n }\r\n return {\r\n content: content as T,\r\n status: response.status,\r\n headers,\r\n request: req,\r\n };\r\n }\r\n}\r\n","/**\r\n * In-memory caching handler for the request pipeline.\r\n *\r\n * `CacheHandler` is registered via `RequestManager.useCache()` so it always\r\n * runs first in the chain. It caches `GET` responses and replays them on\r\n * subsequent requests with the same key — unless `cacheOptions.reload: true`\r\n * is set, in which case it bypasses the cache and stores the fresh response.\r\n *\r\n * Cache key: `cacheOptions.key` when provided, otherwise `\"<METHOD> <URL>\"`.\r\n *\r\n * Only `GET` requests are cached; mutations (POST, PUT, PATCH, DELETE) are\r\n * always forwarded to `next` without touching the cache.\r\n *\r\n * Usage:\r\n * ```ts\r\n * const manager = new RequestManager()\r\n * .useCache(new CacheHandler())\r\n * .use(new FetchHandler());\r\n * ```\r\n */\r\n\r\nimport { injectable } from 'tsyringe';\r\nimport type {\r\n Handler,\r\n NextFn,\r\n RequestContext,\r\n StoreRequest,\r\n StoreResponse,\r\n} from './types.js';\r\n\r\ninterface CacheEntry {\r\n response: StoreResponse;\r\n cachedAt: number;\r\n}\r\n\r\n@injectable()\r\nexport class CacheHandler implements Handler {\r\n private cache = new Map<string, CacheEntry>();\r\n\r\n maxSize: number = 256;\r\n\r\n ttl: number = 300_000;\r\n\r\n private isExpired(entry: CacheEntry): boolean {\r\n return Date.now() - entry.cachedAt > this.ttl;\r\n }\r\n\r\n private evictLRU(): void {\r\n while (this.cache.size > this.maxSize) {\r\n const firstKey = this.cache.keys().next().value as string;\r\n this.cache.delete(firstKey);\r\n }\r\n }\r\n\r\n static keyFor(req: StoreRequest): string {\r\n return req.cacheOptions?.key ?? `${req.method} ${req.url}`;\r\n }\r\n\r\n static isCacheable(req: StoreRequest): boolean {\r\n return req.method === 'GET';\r\n }\r\n\r\n /**\r\n * Handles a request by checking the in-memory cache before forwarding to\r\n * the next handler.\r\n *\r\n * - Non-GET requests skip the cache entirely.\r\n * - `cacheOptions.reload: true` forces a network request and refreshes the entry.\r\n * - Cached entries expire after `ttl` milliseconds (default 5 minutes).\r\n * - The cache uses LRU eviction when it exceeds `maxSize` entries (default 256).\r\n */\r\n async request<T = unknown>(\r\n context: RequestContext<T>,\r\n next: NextFn<T>,\r\n ): Promise<StoreResponse<T>> {\r\n const req = context.request;\r\n if (!CacheHandler.isCacheable(req)) {\r\n return next(req);\r\n }\r\n\r\n const key = CacheHandler.keyFor(req);\r\n const reload = req.cacheOptions?.reload === true;\r\n\r\n if (!reload) {\r\n const hit = this.cache.get(key);\r\n if (hit) {\r\n if (this.isExpired(hit)) {\r\n this.cache.delete(key);\r\n } else {\r\n // LRU promotion: delete and re-set to move to end\r\n this.cache.delete(key);\r\n this.cache.set(key, hit);\r\n return hit.response as StoreResponse<T>;\r\n }\r\n }\r\n }\r\n\r\n const response = await next(req);\r\n this.cache.set(key, { response: response as StoreResponse, cachedAt: Date.now() });\r\n this.evictLRU();\r\n return response;\r\n }\r\n\r\n /** Returns the number of entries currently in the cache. */\r\n get size(): number {\r\n return this.cache.size;\r\n }\r\n\r\n /** Removes all entries from the cache. */\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n /**\r\n * Removes a single entry by key.\r\n * @returns `true` when the entry existed and was deleted.\r\n */\r\n delete(key: string): boolean {\r\n return this.cache.delete(key);\r\n }\r\n}\r\n"],"names":["RequestManager","handlers","handler","request","chain","index","invoke","req","context","r","next","nextRequest","__decorateClass","injectable","FetchError","status","content","headers","message","FetchHandler","out","value","key","response","contentType","text","_next","init","CacheHandler","entry","firstKey","_a","hit"],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ import { injectable as A } from "tsyringe";
2
+ import b from "pluralize";
3
+ import { J as w } from "./JsonSerializer-CFqo6GjC.js";
4
+ var M = Object.getOwnPropertyDescriptor, R = (a, c, h, t) => {
5
+ for (var i = t > 1 ? void 0 : t ? M(c, h) : c, e = a.length - 1, n; e >= 0; e--)
6
+ (n = a[e]) && (i = n(i) || i);
7
+ return i;
8
+ };
9
+ let g = class extends w {
10
+ /**
11
+ * Returns the plural payload key for a model name.
12
+ * e.g. `'post'` → `'posts'`
13
+ */
14
+ payloadKeyFromModelName(a) {
15
+ return b.plural(a);
16
+ }
17
+ /**
18
+ * Returns the model name for a payload root key.
19
+ * e.g. `'posts'` → `'post'`
20
+ */
21
+ modelNameFromPayloadKey(a) {
22
+ return b.singular(a);
23
+ }
24
+ /**
25
+ * Builds a `NormalizedDocument` from a root-key REST payload.
26
+ *
27
+ * - Looks for the model data under the singular or plural root key.
28
+ * - Treats every other non-reserved key as a sideloaded type.
29
+ * - Preserves `meta` and `links` from the root.
30
+ */
31
+ _buildDocument(a, c, h, t, i) {
32
+ if (h == null || typeof h != "object")
33
+ return { data: null };
34
+ const e = h, n = c.modelName, l = this.payloadKeyFromModelName(n);
35
+ let s = null;
36
+ const d = /* @__PURE__ */ new Set();
37
+ if (n in e) {
38
+ d.add(n);
39
+ const o = e[n];
40
+ Array.isArray(o) ? s = o.map((r) => this.normalize(a, c, r)).filter((r) => r !== null) : s = this.normalize(a, c, o);
41
+ } else if (l in e) {
42
+ d.add(l);
43
+ const o = e[l];
44
+ Array.isArray(o) ? s = o.map((r) => this.normalize(a, c, r)).filter((r) => r !== null) : s = this.normalize(a, c, o);
45
+ }
46
+ const u = [];
47
+ for (const [o, r] of Object.entries(e)) {
48
+ if (d.has(o) || o === "meta" || o === "links")
49
+ continue;
50
+ const y = {
51
+ modelName: this.modelNameFromPayloadKey(o),
52
+ attributes: /* @__PURE__ */ new Map(),
53
+ relationships: /* @__PURE__ */ new Map()
54
+ };
55
+ if (Array.isArray(r))
56
+ for (const f of r) {
57
+ const z = this.normalize(a, y, f);
58
+ z && u.push(z);
59
+ }
60
+ else if (r && typeof r == "object") {
61
+ const f = this.normalize(a, y, r);
62
+ f && u.push(f);
63
+ }
64
+ }
65
+ const m = { data: s };
66
+ return u.length > 0 && (m.included = u), e.meta && typeof e.meta == "object" && (m.meta = e.meta), e.links && typeof e.links == "object" && (m.links = e.links), m;
67
+ }
68
+ };
69
+ g = R([
70
+ A()
71
+ ], g);
72
+ function v(a) {
73
+ class c extends a {
74
+ constructor() {
75
+ super(...arguments), this.pendingIncluded = [];
76
+ }
77
+ /**
78
+ * Intercepts raw payload hashes to extract embedded records.
79
+ *
80
+ * For each relationship configured with `embedded: 'always'`:
81
+ * - Recursively normalizes the embedded object(s) and adds them to
82
+ * `pendingIncluded`.
83
+ * - Replaces the embedded value in the hash with the extracted id(s) so
84
+ * `super.normalize` treats it as a plain id reference.
85
+ */
86
+ normalize(t, i, e, n) {
87
+ if (!e || typeof e != "object" || Array.isArray(e))
88
+ return super.normalize(t, i, e, n);
89
+ const l = e, s = this.attrs ?? {}, d = { ...l };
90
+ for (const [u, m] of i.relationships) {
91
+ const o = s[u];
92
+ if (!o || o.embedded !== "always")
93
+ continue;
94
+ const r = d[u];
95
+ if (r != null) {
96
+ if (m.kind === "hasMany" && Array.isArray(r)) {
97
+ const p = [];
98
+ for (const y of r) {
99
+ const f = this.extractEmbeddedResource(t, m, y);
100
+ f && f.id !== null && (p.push(f.id), this.pendingIncluded.push(f));
101
+ }
102
+ d[u] = p;
103
+ } else if (m.kind === "belongsTo" && typeof r == "object" && !Array.isArray(r)) {
104
+ const p = this.extractEmbeddedResource(t, m, r);
105
+ p && p.id !== null && (this.pendingIncluded.push(p), d[u] = p.id);
106
+ }
107
+ }
108
+ }
109
+ return super.normalize(t, i, d, n);
110
+ }
111
+ /**
112
+ * Resets `pendingIncluded`, delegates to `super.normalizeResponse`, then
113
+ * appends any extracted embedded resources to `document.included`.
114
+ */
115
+ normalizeResponse(t, i, e, n, l) {
116
+ this.pendingIncluded = [];
117
+ const s = super.normalizeResponse(t, i, e, n, l);
118
+ if (this.pendingIncluded.length > 0) {
119
+ const d = s.included ? [...s.included] : [];
120
+ d.push(...this.pendingIncluded), s.included = d, this.pendingIncluded = [];
121
+ }
122
+ return s;
123
+ }
124
+ /**
125
+ * When `serialize: 'records'` is configured, writes the full related
126
+ * record objects instead of just ids.
127
+ */
128
+ serializeHasMany(t, i, e) {
129
+ var l;
130
+ const n = (l = this.attrs) == null ? void 0 : l[e.name];
131
+ if ((n == null ? void 0 : n.serialize) === "records") {
132
+ const s = t.hasMany(e.name);
133
+ i[this.keyForRelationship(e.name)] = s ?? [];
134
+ return;
135
+ }
136
+ super.serializeHasMany(t, i, e);
137
+ }
138
+ /**
139
+ * When `serialize: 'records'` is configured, writes the full related
140
+ * record object instead of just the id.
141
+ */
142
+ serializeBelongsTo(t, i, e) {
143
+ var l;
144
+ const n = (l = this.attrs) == null ? void 0 : l[e.name];
145
+ if ((n == null ? void 0 : n.serialize) === "records") {
146
+ const s = t.belongsTo(e.name);
147
+ i[this.keyForRelationship(e.name)] = s ?? null;
148
+ return;
149
+ }
150
+ super.serializeBelongsTo(t, i, e);
151
+ }
152
+ /**
153
+ * Normalizes a single embedded resource object using the related model's
154
+ * type as a placeholder `ModelClassMeta`.
155
+ */
156
+ extractEmbeddedResource(t, i, e) {
157
+ if (!e || typeof e != "object" || Array.isArray(e))
158
+ return null;
159
+ const n = {
160
+ modelName: i.type,
161
+ attributes: /* @__PURE__ */ new Map(),
162
+ relationships: /* @__PURE__ */ new Map()
163
+ };
164
+ return this.normalize(t, n, e);
165
+ }
166
+ }
167
+ return c;
168
+ }
169
+ export {
170
+ v as E,
171
+ g as R
172
+ };
173
+ //# sourceMappingURL=EmbeddedRecordsMixin-6mSCXsJ3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmbeddedRecordsMixin-6mSCXsJ3.js","sources":["../src/serializer/RestSerializer.ts","../src/serializer/EmbeddedRecordsMixin.ts"],"sourcesContent":["/**\n * REST serializer for root-key payloads with optional sideloading.\n *\n * `RestSerializer` expects the primary data to be wrapped under a root key\n * that matches either the singular or plural model name:\n *\n * ```json\n * { \"post\": { \"id\": \"1\", \"title\": \"Hello\" } }\n * { \"posts\": [{ \"id\": \"1\", … }, { \"id\": \"2\", … }] }\n * ```\n *\n * Any additional keys in the payload are treated as sideloaded (compound)\n * data. `meta` and `links` keys are reserved and forwarded to the document.\n *\n * Sideloaded types are inferred from the payload key via `modelNameFromPayloadKey`\n * (default: singularization).\n *\n * Extends `JsonSerializer` so it inherits flat `normalize` and `serialize`\n * behaviour and only overrides document-level parsing via `_buildDocument`.\n */\n\nimport { injectable } from 'tsyringe';\nimport pluralize from 'pluralize';\nimport {\n type ModelClassMeta,\n type NormalizeRequestType,\n type NormalizedDocument,\n type NormalizedResource,\n} from './Serializer.js';\nimport { JsonSerializer } from './JsonSerializer.js';\n\n@injectable()\nexport class RestSerializer extends JsonSerializer {\n /**\n * Returns the plural payload key for a model name.\n * e.g. `'post'` → `'posts'`\n */\n payloadKeyFromModelName(modelName: string): string {\n return pluralize.plural(modelName);\n }\n\n /**\n * Returns the model name for a payload root key.\n * e.g. `'posts'` → `'post'`\n */\n modelNameFromPayloadKey(key: string): string {\n return pluralize.singular(key);\n }\n\n /**\n * Builds a `NormalizedDocument` from a root-key REST payload.\n *\n * - Looks for the model data under the singular or plural root key.\n * - Treats every other non-reserved key as a sideloaded type.\n * - Preserves `meta` and `links` from the root.\n */\n protected override _buildDocument(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n _id: string | null,\n _requestType: NormalizeRequestType,\n ): NormalizedDocument {\n if (payload === null || payload === undefined || typeof payload !== 'object') {\n return { data: null };\n }\n const hash = payload as Record<string, unknown>;\n const singular = modelClass.modelName;\n const plural = this.payloadKeyFromModelName(singular);\n\n let data: NormalizedResource | NormalizedResource[] | null = null;\n const primaryKeys = new Set<string>();\n\n if (singular in hash) {\n primaryKeys.add(singular);\n const raw = hash[singular];\n if (Array.isArray(raw)) {\n data = raw\n .map((entry) => this.normalize(store, modelClass, entry))\n .filter((resource): resource is NormalizedResource => resource !== null);\n } else {\n data = this.normalize(store, modelClass, raw);\n }\n } else if (plural in hash) {\n primaryKeys.add(plural);\n const raw = hash[plural];\n if (Array.isArray(raw)) {\n data = raw\n .map((entry) => this.normalize(store, modelClass, entry))\n .filter((resource): resource is NormalizedResource => resource !== null);\n } else {\n data = this.normalize(store, modelClass, raw);\n }\n }\n\n // Collect sideloaded records from all remaining root keys.\n const included: NormalizedResource[] = [];\n for (const [key, value] of Object.entries(hash)) {\n if (primaryKeys.has(key)) {\n continue;\n }\n if (key === 'meta' || key === 'links') {\n continue;\n }\n const sideloadType = this.modelNameFromPayloadKey(key);\n const sideloadClass: ModelClassMeta = {\n modelName: sideloadType,\n attributes: new Map(),\n relationships: new Map(),\n };\n if (Array.isArray(value)) {\n for (const item of value) {\n const normalized = this.normalize(store, sideloadClass, item);\n if (normalized) {\n included.push(normalized);\n }\n }\n } else if (value && typeof value === 'object') {\n const normalized = this.normalize(store, sideloadClass, value);\n if (normalized) {\n included.push(normalized);\n }\n }\n }\n\n const doc: NormalizedDocument = { data };\n if (included.length > 0) {\n doc.included = included;\n }\n if (hash.meta && typeof hash.meta === 'object') {\n doc.meta = hash.meta as Record<string, unknown>;\n }\n if (hash.links && typeof hash.links === 'object') {\n doc.links = hash.links as Record<string, string>;\n }\n return doc;\n }\n}\n","/**\n * Mixin that adds support for embedded (nested) records in REST payloads.\n *\n * Apply with:\n * ```ts\n * class PostSerializer extends EmbeddedRecordsMixin(RestSerializer) {\n * attrs = {\n * comments: { embedded: 'always' },\n * author: { embedded: 'always', serialize: 'records' },\n * };\n * }\n * ```\n *\n * ## How it works\n *\n * ### Deserialization\n * When `embedded: 'always'` is set for a relationship, `normalize()` intercepts\n * the raw payload before passing it to `super.normalize()`:\n * - For `hasMany`: replaces the nested array with a plain id array and queues\n * each nested object as an `included` resource.\n * - For `belongsTo`: replaces the nested object with the extracted id and\n * queues the nested object as an `included` resource.\n *\n * Queued resources are collected during `normalizeResponse()` and appended to\n * `document.included` so the `Store` can push them into the identity map.\n *\n * ### Serialization\n * When `serialize: 'records'` is set, `serializeHasMany` / `serializeBelongsTo`\n * write the full related record(s) into the payload instead of just ids.\n *\n * ## Configuration\n *\n * Each entry in `attrs` is an `EmbeddedAttrConfig`:\n * - `embedded: 'always'` — enable embedded deserialization.\n * - `serialize: 'records'` — write full records on serialize.\n * - `serialize: 'ids'` — write id array on serialize (default).\n * - `serialize: false` — omit relationship from serialized payload.\n * - `deserialize: 'records'` — same as `embedded: 'always'`.\n * - `deserialize: false` — ignore this relationship during normalization.\n */\n\nimport type { RelationshipDef } from '@mobx-data/schema';\nimport type {\n ModelClassMeta,\n NormalizeRequestType,\n NormalizedDocument,\n NormalizedResource,\n SerializerSnapshot,\n} from './Serializer.js';\n\n/** Per-relationship embedding configuration. */\nexport interface EmbeddedAttrConfig {\n /** `'always'` to deserialize embedded records; `'never'` to skip. */\n embedded?: 'always' | 'never';\n /** `'records'` to serialize full objects; `'ids'` for ids only; `false` to omit. */\n serialize?: 'records' | 'ids' | false;\n /** `'records'` to deserialize embedded objects; `'ids'` for plain ids; `false` to skip. */\n deserialize?: 'records' | 'ids' | false;\n}\n\n/** Map of relationship name → embedding config. */\nexport interface EmbeddedRecordsAttrs {\n [key: string]: EmbeddedAttrConfig;\n}\n\n/**\n * The minimal serializer interface required by the mixin.\n * Any class extending `JsonSerializer` satisfies this.\n */\nexport interface JsonSerializerLike {\n attrs?: EmbeddedRecordsAttrs;\n primaryKey: string;\n normalize(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n prop?: string,\n ): NormalizedResource | null;\n normalizeResponse(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n id: string | null,\n requestType: NormalizeRequestType,\n ): NormalizedDocument;\n serialize(\n snapshot: SerializerSnapshot,\n options?: { includeId?: boolean },\n ): Record<string, unknown>;\n serializeHasMany(\n snapshot: SerializerSnapshot,\n json: Record<string, unknown>,\n relationship: RelationshipDef,\n ): void;\n serializeBelongsTo(\n snapshot: SerializerSnapshot,\n json: Record<string, unknown>,\n relationship: RelationshipDef,\n ): void;\n keyForAttribute(key: string): string;\n keyForRelationship(key: string): string;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Constructor<T> = new (...args: any[]) => T;\n\n/**\n * Returns a new class that extends `Base` with embedded-record support.\n * `Base` must be (or extend) `JsonSerializer`.\n */\nexport function EmbeddedRecordsMixin<\n TBase extends Constructor<JsonSerializerLike>,\n>(Base: TBase): TBase {\n class WithEmbeddedRecords extends Base {\n override attrs?: EmbeddedRecordsAttrs;\n\n /** Accumulates extracted embedded resources during a `normalizeResponse` call. */\n private pendingIncluded: NormalizedResource[] = [];\n\n /**\n * Intercepts raw payload hashes to extract embedded records.\n *\n * For each relationship configured with `embedded: 'always'`:\n * - Recursively normalizes the embedded object(s) and adds them to\n * `pendingIncluded`.\n * - Replaces the embedded value in the hash with the extracted id(s) so\n * `super.normalize` treats it as a plain id reference.\n */\n override normalize(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n prop?: string,\n ): NormalizedResource | null {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return super.normalize(store, modelClass, payload, prop);\n }\n const src = payload as Record<string, unknown>;\n const attrs = this.attrs ?? {};\n const hash: Record<string, unknown> = { ...src };\n\n for (const [name, rel] of modelClass.relationships) {\n const cfg = attrs[name];\n if (!cfg || cfg.embedded !== 'always') {\n continue;\n }\n const raw = hash[name];\n if (raw === undefined || raw === null) {\n continue;\n }\n\n if (rel.kind === 'hasMany' && Array.isArray(raw)) {\n const ids: string[] = [];\n for (const item of raw) {\n const extracted = this.extractEmbeddedResource(store, rel, item);\n if (extracted && extracted.id !== null) {\n ids.push(extracted.id);\n this.pendingIncluded.push(extracted);\n }\n }\n hash[name] = ids;\n } else if (\n rel.kind === 'belongsTo'\n && typeof raw === 'object'\n && !Array.isArray(raw)\n ) {\n const extracted = this.extractEmbeddedResource(store, rel, raw);\n if (extracted && extracted.id !== null) {\n this.pendingIncluded.push(extracted);\n hash[name] = extracted.id;\n }\n }\n }\n\n return super.normalize(store, modelClass, hash, prop);\n }\n\n /**\n * Resets `pendingIncluded`, delegates to `super.normalizeResponse`, then\n * appends any extracted embedded resources to `document.included`.\n */\n override normalizeResponse(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n id: string | null,\n requestType: NormalizeRequestType,\n ): NormalizedDocument {\n this.pendingIncluded = [];\n const doc = super.normalizeResponse(store, modelClass, payload, id, requestType);\n if (this.pendingIncluded.length > 0) {\n const included = doc.included ? [...doc.included] : [];\n included.push(...this.pendingIncluded);\n doc.included = included;\n this.pendingIncluded = [];\n }\n return doc;\n }\n\n /**\n * When `serialize: 'records'` is configured, writes the full related\n * record objects instead of just ids.\n */\n override serializeHasMany(\n snapshot: SerializerSnapshot,\n json: Record<string, unknown>,\n relationship: RelationshipDef,\n ): void {\n const cfg = this.attrs?.[relationship.name];\n if (cfg?.serialize === 'records') {\n const records = snapshot.hasMany(relationship.name) as unknown[];\n json[this.keyForRelationship(relationship.name)] = records ?? [];\n return;\n }\n super.serializeHasMany(snapshot, json, relationship);\n }\n\n /**\n * When `serialize: 'records'` is configured, writes the full related\n * record object instead of just the id.\n */\n override serializeBelongsTo(\n snapshot: SerializerSnapshot,\n json: Record<string, unknown>,\n relationship: RelationshipDef,\n ): void {\n const cfg = this.attrs?.[relationship.name];\n if (cfg?.serialize === 'records') {\n const record = snapshot.belongsTo(relationship.name);\n json[this.keyForRelationship(relationship.name)] = record ?? null;\n return;\n }\n super.serializeBelongsTo(snapshot, json, relationship);\n }\n\n /**\n * Normalizes a single embedded resource object using the related model's\n * type as a placeholder `ModelClassMeta`.\n */\n private extractEmbeddedResource(\n store: unknown,\n rel: RelationshipDef,\n item: unknown,\n ): NormalizedResource | null {\n if (!item || typeof item !== 'object' || Array.isArray(item)) {\n return null;\n }\n const embeddedClass: ModelClassMeta = {\n modelName: rel.type,\n attributes: new Map(),\n relationships: new Map(),\n };\n return this.normalize(store, embeddedClass, item);\n }\n }\n return WithEmbeddedRecords as unknown as TBase;\n}\n"],"names":["RestSerializer","JsonSerializer","modelName","pluralize","key","store","modelClass","payload","_id","_requestType","hash","singular","plural","data","primaryKeys","raw","entry","resource","included","value","sideloadClass","item","normalized","doc","__decorateClass","injectable","EmbeddedRecordsMixin","Base","WithEmbeddedRecords","prop","src","attrs","name","rel","cfg","ids","extracted","id","requestType","snapshot","json","relationship","_a","records","record","embeddedClass"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";const A=require("tsyringe"),g=require("pluralize"),R=require("./JsonSerializer-BzUCyUSf.cjs");var M=Object.getOwnPropertyDescriptor,w=(h,a,u,i)=>{for(var s=i>1?void 0:i?M(a,u):a,n=h.length-1,e;n>=0;n--)(e=h[n])&&(s=e(s)||s);return s};exports.RestSerializer=class extends R.JsonSerializer{payloadKeyFromModelName(a){return g.plural(a)}modelNameFromPayloadKey(a){return g.singular(a)}_buildDocument(a,u,i,s,n){if(i==null||typeof i!="object")return{data:null};const e=i,o=u.modelName,d=this.payloadKeyFromModelName(o);let l=null;const m=new Set;if(o in e){m.add(o);const t=e[o];Array.isArray(t)?l=t.map(r=>this.normalize(a,u,r)).filter(r=>r!==null):l=this.normalize(a,u,t)}else if(d in e){m.add(d);const t=e[d];Array.isArray(t)?l=t.map(r=>this.normalize(a,u,r)).filter(r=>r!==null):l=this.normalize(a,u,t)}const c=[];for(const[t,r]of Object.entries(e)){if(m.has(t)||t==="meta"||t==="links")continue;const y={modelName:this.modelNameFromPayloadKey(t),attributes:new Map,relationships:new Map};if(Array.isArray(r))for(const p of r){const b=this.normalize(a,y,p);b&&c.push(b)}else if(r&&typeof r=="object"){const p=this.normalize(a,y,r);p&&c.push(p)}}const f={data:l};return c.length>0&&(f.included=c),e.meta&&typeof e.meta=="object"&&(f.meta=e.meta),e.links&&typeof e.links=="object"&&(f.links=e.links),f}};exports.RestSerializer=w([A.injectable()],exports.RestSerializer);function k(h){class a extends h{constructor(){super(...arguments),this.pendingIncluded=[]}normalize(i,s,n,e){if(!n||typeof n!="object"||Array.isArray(n))return super.normalize(i,s,n,e);const o=n,d=this.attrs??{},l={...o};for(const[m,c]of s.relationships){const f=d[m];if(!f||f.embedded!=="always")continue;const t=l[m];if(t!=null){if(c.kind==="hasMany"&&Array.isArray(t)){const r=[];for(const z of t){const y=this.extractEmbeddedResource(i,c,z);y&&y.id!==null&&(r.push(y.id),this.pendingIncluded.push(y))}l[m]=r}else if(c.kind==="belongsTo"&&typeof t=="object"&&!Array.isArray(t)){const r=this.extractEmbeddedResource(i,c,t);r&&r.id!==null&&(this.pendingIncluded.push(r),l[m]=r.id)}}}return super.normalize(i,s,l,e)}normalizeResponse(i,s,n,e,o){this.pendingIncluded=[];const d=super.normalizeResponse(i,s,n,e,o);if(this.pendingIncluded.length>0){const l=d.included?[...d.included]:[];l.push(...this.pendingIncluded),d.included=l,this.pendingIncluded=[]}return d}serializeHasMany(i,s,n){var o;const e=(o=this.attrs)==null?void 0:o[n.name];if((e==null?void 0:e.serialize)==="records"){const d=i.hasMany(n.name);s[this.keyForRelationship(n.name)]=d??[];return}super.serializeHasMany(i,s,n)}serializeBelongsTo(i,s,n){var o;const e=(o=this.attrs)==null?void 0:o[n.name];if((e==null?void 0:e.serialize)==="records"){const d=i.belongsTo(n.name);s[this.keyForRelationship(n.name)]=d??null;return}super.serializeBelongsTo(i,s,n)}extractEmbeddedResource(i,s,n){if(!n||typeof n!="object"||Array.isArray(n))return null;const e={modelName:s.type,attributes:new Map,relationships:new Map};return this.normalize(i,e,n)}}return a}exports.EmbeddedRecordsMixin=k;
2
+ //# sourceMappingURL=EmbeddedRecordsMixin-BkF7MdbY.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmbeddedRecordsMixin-BkF7MdbY.cjs","sources":["../src/serializer/RestSerializer.ts","../src/serializer/EmbeddedRecordsMixin.ts"],"sourcesContent":["/**\n * REST serializer for root-key payloads with optional sideloading.\n *\n * `RestSerializer` expects the primary data to be wrapped under a root key\n * that matches either the singular or plural model name:\n *\n * ```json\n * { \"post\": { \"id\": \"1\", \"title\": \"Hello\" } }\n * { \"posts\": [{ \"id\": \"1\", … }, { \"id\": \"2\", … }] }\n * ```\n *\n * Any additional keys in the payload are treated as sideloaded (compound)\n * data. `meta` and `links` keys are reserved and forwarded to the document.\n *\n * Sideloaded types are inferred from the payload key via `modelNameFromPayloadKey`\n * (default: singularization).\n *\n * Extends `JsonSerializer` so it inherits flat `normalize` and `serialize`\n * behaviour and only overrides document-level parsing via `_buildDocument`.\n */\n\nimport { injectable } from 'tsyringe';\nimport pluralize from 'pluralize';\nimport {\n type ModelClassMeta,\n type NormalizeRequestType,\n type NormalizedDocument,\n type NormalizedResource,\n} from './Serializer.js';\nimport { JsonSerializer } from './JsonSerializer.js';\n\n@injectable()\nexport class RestSerializer extends JsonSerializer {\n /**\n * Returns the plural payload key for a model name.\n * e.g. `'post'` → `'posts'`\n */\n payloadKeyFromModelName(modelName: string): string {\n return pluralize.plural(modelName);\n }\n\n /**\n * Returns the model name for a payload root key.\n * e.g. `'posts'` → `'post'`\n */\n modelNameFromPayloadKey(key: string): string {\n return pluralize.singular(key);\n }\n\n /**\n * Builds a `NormalizedDocument` from a root-key REST payload.\n *\n * - Looks for the model data under the singular or plural root key.\n * - Treats every other non-reserved key as a sideloaded type.\n * - Preserves `meta` and `links` from the root.\n */\n protected override _buildDocument(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n _id: string | null,\n _requestType: NormalizeRequestType,\n ): NormalizedDocument {\n if (payload === null || payload === undefined || typeof payload !== 'object') {\n return { data: null };\n }\n const hash = payload as Record<string, unknown>;\n const singular = modelClass.modelName;\n const plural = this.payloadKeyFromModelName(singular);\n\n let data: NormalizedResource | NormalizedResource[] | null = null;\n const primaryKeys = new Set<string>();\n\n if (singular in hash) {\n primaryKeys.add(singular);\n const raw = hash[singular];\n if (Array.isArray(raw)) {\n data = raw\n .map((entry) => this.normalize(store, modelClass, entry))\n .filter((resource): resource is NormalizedResource => resource !== null);\n } else {\n data = this.normalize(store, modelClass, raw);\n }\n } else if (plural in hash) {\n primaryKeys.add(plural);\n const raw = hash[plural];\n if (Array.isArray(raw)) {\n data = raw\n .map((entry) => this.normalize(store, modelClass, entry))\n .filter((resource): resource is NormalizedResource => resource !== null);\n } else {\n data = this.normalize(store, modelClass, raw);\n }\n }\n\n // Collect sideloaded records from all remaining root keys.\n const included: NormalizedResource[] = [];\n for (const [key, value] of Object.entries(hash)) {\n if (primaryKeys.has(key)) {\n continue;\n }\n if (key === 'meta' || key === 'links') {\n continue;\n }\n const sideloadType = this.modelNameFromPayloadKey(key);\n const sideloadClass: ModelClassMeta = {\n modelName: sideloadType,\n attributes: new Map(),\n relationships: new Map(),\n };\n if (Array.isArray(value)) {\n for (const item of value) {\n const normalized = this.normalize(store, sideloadClass, item);\n if (normalized) {\n included.push(normalized);\n }\n }\n } else if (value && typeof value === 'object') {\n const normalized = this.normalize(store, sideloadClass, value);\n if (normalized) {\n included.push(normalized);\n }\n }\n }\n\n const doc: NormalizedDocument = { data };\n if (included.length > 0) {\n doc.included = included;\n }\n if (hash.meta && typeof hash.meta === 'object') {\n doc.meta = hash.meta as Record<string, unknown>;\n }\n if (hash.links && typeof hash.links === 'object') {\n doc.links = hash.links as Record<string, string>;\n }\n return doc;\n }\n}\n","/**\n * Mixin that adds support for embedded (nested) records in REST payloads.\n *\n * Apply with:\n * ```ts\n * class PostSerializer extends EmbeddedRecordsMixin(RestSerializer) {\n * attrs = {\n * comments: { embedded: 'always' },\n * author: { embedded: 'always', serialize: 'records' },\n * };\n * }\n * ```\n *\n * ## How it works\n *\n * ### Deserialization\n * When `embedded: 'always'` is set for a relationship, `normalize()` intercepts\n * the raw payload before passing it to `super.normalize()`:\n * - For `hasMany`: replaces the nested array with a plain id array and queues\n * each nested object as an `included` resource.\n * - For `belongsTo`: replaces the nested object with the extracted id and\n * queues the nested object as an `included` resource.\n *\n * Queued resources are collected during `normalizeResponse()` and appended to\n * `document.included` so the `Store` can push them into the identity map.\n *\n * ### Serialization\n * When `serialize: 'records'` is set, `serializeHasMany` / `serializeBelongsTo`\n * write the full related record(s) into the payload instead of just ids.\n *\n * ## Configuration\n *\n * Each entry in `attrs` is an `EmbeddedAttrConfig`:\n * - `embedded: 'always'` — enable embedded deserialization.\n * - `serialize: 'records'` — write full records on serialize.\n * - `serialize: 'ids'` — write id array on serialize (default).\n * - `serialize: false` — omit relationship from serialized payload.\n * - `deserialize: 'records'` — same as `embedded: 'always'`.\n * - `deserialize: false` — ignore this relationship during normalization.\n */\n\nimport type { RelationshipDef } from '@mobx-data/schema';\nimport type {\n ModelClassMeta,\n NormalizeRequestType,\n NormalizedDocument,\n NormalizedResource,\n SerializerSnapshot,\n} from './Serializer.js';\n\n/** Per-relationship embedding configuration. */\nexport interface EmbeddedAttrConfig {\n /** `'always'` to deserialize embedded records; `'never'` to skip. */\n embedded?: 'always' | 'never';\n /** `'records'` to serialize full objects; `'ids'` for ids only; `false` to omit. */\n serialize?: 'records' | 'ids' | false;\n /** `'records'` to deserialize embedded objects; `'ids'` for plain ids; `false` to skip. */\n deserialize?: 'records' | 'ids' | false;\n}\n\n/** Map of relationship name → embedding config. */\nexport interface EmbeddedRecordsAttrs {\n [key: string]: EmbeddedAttrConfig;\n}\n\n/**\n * The minimal serializer interface required by the mixin.\n * Any class extending `JsonSerializer` satisfies this.\n */\nexport interface JsonSerializerLike {\n attrs?: EmbeddedRecordsAttrs;\n primaryKey: string;\n normalize(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n prop?: string,\n ): NormalizedResource | null;\n normalizeResponse(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n id: string | null,\n requestType: NormalizeRequestType,\n ): NormalizedDocument;\n serialize(\n snapshot: SerializerSnapshot,\n options?: { includeId?: boolean },\n ): Record<string, unknown>;\n serializeHasMany(\n snapshot: SerializerSnapshot,\n json: Record<string, unknown>,\n relationship: RelationshipDef,\n ): void;\n serializeBelongsTo(\n snapshot: SerializerSnapshot,\n json: Record<string, unknown>,\n relationship: RelationshipDef,\n ): void;\n keyForAttribute(key: string): string;\n keyForRelationship(key: string): string;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Constructor<T> = new (...args: any[]) => T;\n\n/**\n * Returns a new class that extends `Base` with embedded-record support.\n * `Base` must be (or extend) `JsonSerializer`.\n */\nexport function EmbeddedRecordsMixin<\n TBase extends Constructor<JsonSerializerLike>,\n>(Base: TBase): TBase {\n class WithEmbeddedRecords extends Base {\n override attrs?: EmbeddedRecordsAttrs;\n\n /** Accumulates extracted embedded resources during a `normalizeResponse` call. */\n private pendingIncluded: NormalizedResource[] = [];\n\n /**\n * Intercepts raw payload hashes to extract embedded records.\n *\n * For each relationship configured with `embedded: 'always'`:\n * - Recursively normalizes the embedded object(s) and adds them to\n * `pendingIncluded`.\n * - Replaces the embedded value in the hash with the extracted id(s) so\n * `super.normalize` treats it as a plain id reference.\n */\n override normalize(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n prop?: string,\n ): NormalizedResource | null {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return super.normalize(store, modelClass, payload, prop);\n }\n const src = payload as Record<string, unknown>;\n const attrs = this.attrs ?? {};\n const hash: Record<string, unknown> = { ...src };\n\n for (const [name, rel] of modelClass.relationships) {\n const cfg = attrs[name];\n if (!cfg || cfg.embedded !== 'always') {\n continue;\n }\n const raw = hash[name];\n if (raw === undefined || raw === null) {\n continue;\n }\n\n if (rel.kind === 'hasMany' && Array.isArray(raw)) {\n const ids: string[] = [];\n for (const item of raw) {\n const extracted = this.extractEmbeddedResource(store, rel, item);\n if (extracted && extracted.id !== null) {\n ids.push(extracted.id);\n this.pendingIncluded.push(extracted);\n }\n }\n hash[name] = ids;\n } else if (\n rel.kind === 'belongsTo'\n && typeof raw === 'object'\n && !Array.isArray(raw)\n ) {\n const extracted = this.extractEmbeddedResource(store, rel, raw);\n if (extracted && extracted.id !== null) {\n this.pendingIncluded.push(extracted);\n hash[name] = extracted.id;\n }\n }\n }\n\n return super.normalize(store, modelClass, hash, prop);\n }\n\n /**\n * Resets `pendingIncluded`, delegates to `super.normalizeResponse`, then\n * appends any extracted embedded resources to `document.included`.\n */\n override normalizeResponse(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n id: string | null,\n requestType: NormalizeRequestType,\n ): NormalizedDocument {\n this.pendingIncluded = [];\n const doc = super.normalizeResponse(store, modelClass, payload, id, requestType);\n if (this.pendingIncluded.length > 0) {\n const included = doc.included ? [...doc.included] : [];\n included.push(...this.pendingIncluded);\n doc.included = included;\n this.pendingIncluded = [];\n }\n return doc;\n }\n\n /**\n * When `serialize: 'records'` is configured, writes the full related\n * record objects instead of just ids.\n */\n override serializeHasMany(\n snapshot: SerializerSnapshot,\n json: Record<string, unknown>,\n relationship: RelationshipDef,\n ): void {\n const cfg = this.attrs?.[relationship.name];\n if (cfg?.serialize === 'records') {\n const records = snapshot.hasMany(relationship.name) as unknown[];\n json[this.keyForRelationship(relationship.name)] = records ?? [];\n return;\n }\n super.serializeHasMany(snapshot, json, relationship);\n }\n\n /**\n * When `serialize: 'records'` is configured, writes the full related\n * record object instead of just the id.\n */\n override serializeBelongsTo(\n snapshot: SerializerSnapshot,\n json: Record<string, unknown>,\n relationship: RelationshipDef,\n ): void {\n const cfg = this.attrs?.[relationship.name];\n if (cfg?.serialize === 'records') {\n const record = snapshot.belongsTo(relationship.name);\n json[this.keyForRelationship(relationship.name)] = record ?? null;\n return;\n }\n super.serializeBelongsTo(snapshot, json, relationship);\n }\n\n /**\n * Normalizes a single embedded resource object using the related model's\n * type as a placeholder `ModelClassMeta`.\n */\n private extractEmbeddedResource(\n store: unknown,\n rel: RelationshipDef,\n item: unknown,\n ): NormalizedResource | null {\n if (!item || typeof item !== 'object' || Array.isArray(item)) {\n return null;\n }\n const embeddedClass: ModelClassMeta = {\n modelName: rel.type,\n attributes: new Map(),\n relationships: new Map(),\n };\n return this.normalize(store, embeddedClass, item);\n }\n }\n return WithEmbeddedRecords as unknown as TBase;\n}\n"],"names":["RestSerializer","JsonSerializer","modelName","pluralize","key","store","modelClass","payload","_id","_requestType","hash","singular","plural","data","primaryKeys","raw","entry","resource","included","value","sideloadClass","item","normalized","doc","__decorateClass","injectable","EmbeddedRecordsMixin","Base","WithEmbeddedRecords","prop","src","attrs","name","rel","cfg","ids","extracted","id","requestType","snapshot","json","relationship","_a","records","record","embeddedClass"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { injectable as f } from "tsyringe";
2
- import { R as h } from "./RestAdapter-CGWqOR_G.js";
2
+ import { R as h } from "./RestAdapter-DYUoyV5h.js";
3
3
  import m from "pluralize";
4
- import { S as b } from "./Serializer-FxJbsZ50.js";
4
+ import { S as b } from "./Serializer-Ca6w_QNQ.js";
5
5
  var _ = Object.getOwnPropertyDescriptor, g = (t, i, a, s) => {
6
6
  for (var r = s > 1 ? void 0 : s ? _(i, a) : i, e = t.length - 1, n; e >= 0; e--)
7
7
  (n = t[e]) && (r = n(r) || r);
@@ -191,4 +191,4 @@ export {
191
191
  p as J,
192
192
  y as a
193
193
  };
194
- //# sourceMappingURL=JsonApiSerializer-wndq5a1n.js.map
194
+ //# sourceMappingURL=JsonApiSerializer-BV61cFAZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonApiSerializer-BV61cFAZ.js","sources":["../src/json-api/JsonApiAdapter.ts","../src/json-api/JsonApiSerializer.ts"],"sourcesContent":["/**\r\n * Adapter that implements the [JSON:API](https://jsonapi.org) specification.\r\n *\r\n * Extends `RestAdapter` with the following differences:\r\n *\r\n * - **MIME types** — `Accept` and `Content-Type` headers are set to\r\n * `application/vnd.api+json` as required by the spec.\r\n * - **PATCH for updates** — `updateRecord` uses `PATCH` instead of `PUT`.\r\n * - **`coalesceFindRequests: true`** — the store coalesces separate\r\n * `findRecord` calls into a single `findMany` request.\r\n *\r\n * URL construction is inherited from `RestAdapter` / `Adapter` unchanged;\r\n * override `pathForType` or `urlFor*` methods to customize.\r\n */\r\n\r\nimport { injectable } from 'tsyringe';\r\nimport { RestAdapter, type AdapterSnapshot } from '@mobx-data/adapter';\r\n\r\n@injectable()\r\nexport class JsonApiAdapter extends RestAdapter {\r\n /** Always coalesce `findRecord` calls into a single `findMany` request. */\r\n override coalesceFindRequests = true;\r\n\r\n /** Returns JSON:API `Accept` header alongside any custom headers. */\r\n override defaultHeaders(): Record<string, string> {\r\n return {\r\n Accept: 'application/vnd.api+json',\r\n ...this.headers,\r\n };\r\n }\r\n\r\n /** Returns JSON:API `Content-Type` header for mutation requests. */\r\n override mutationHeaders(): Record<string, string> {\r\n return {\r\n 'Content-Type': 'application/vnd.api+json',\r\n ...this.defaultHeaders(),\r\n };\r\n }\r\n\r\n /**\r\n * Sends a PATCH request to update a record (JSON:API mandates PATCH, not PUT).\r\n */\r\n override async updateRecord(\r\n _store: unknown,\r\n modelName: string,\r\n snapshot: AdapterSnapshot,\r\n ): Promise<unknown> {\r\n const url = this.buildURL(modelName, snapshot.id, snapshot, 'updateRecord');\r\n return this._fetchJSON(url, {\r\n method: 'PATCH',\r\n headers: this.mutationHeaders(),\r\n body: JSON.stringify(this._serializeForUpdate(snapshot)),\r\n });\r\n }\r\n\r\n /**\r\n * Sends a PATCH request with only the changed attributes (partial update).\r\n * Produces a JSON:API document with only the dirty attribute keys.\r\n */\r\n override async patchRecord(\r\n _store: unknown,\r\n modelName: string,\r\n snapshot: AdapterSnapshot,\r\n ): Promise<unknown> {\r\n const url = this.buildURL(modelName, snapshot.id, snapshot, 'updateRecord');\r\n const changed = snapshot.changedAttributes();\r\n const attributes: Record<string, unknown> = {};\r\n for (const [key, [, current]] of Object.entries(changed)) {\r\n attributes[key] = current;\r\n }\r\n const body = {\r\n data: {\r\n type: modelName,\r\n id: snapshot.id,\r\n attributes,\r\n },\r\n };\r\n return this._fetchJSON(url, {\r\n method: 'PATCH',\r\n headers: this.mutationHeaders(),\r\n body: JSON.stringify(body),\r\n });\r\n }\r\n\r\n /**\r\n * Extracts the raw data object from the snapshot for use as the PATCH body.\r\n * Subclasses may override this to produce a full JSON:API `{ data: … }` document.\r\n */\r\n protected _serializeForUpdate(snapshot: AdapterSnapshot): Record<string, unknown> {\r\n const rec = snapshot.record as { _data?: Record<string, unknown> };\r\n return rec._data ? { ...rec._data } : {};\r\n }\r\n}\r\n","/**\n * Serializer that implements the [JSON:API](https://jsonapi.org) specification.\n *\n * ## Normalization\n *\n * Parses a JSON:API compound document `{ data, included, meta, links }` into\n * a `NormalizedDocument`.\n *\n * For each resource object:\n * - `type` is converted from JSON:API plural form to model name via\n * `modelNameFromPayloadKey` (singularization).\n * - `id` is coerced to a string.\n * - `attributes` are copied directly.\n * - `relationships` data refs (`{ type, id }`) are normalized the same way.\n *\n * Side-loaded records in `included` are normalized individually, each using\n * its own `type` as the model class placeholder.\n *\n * ## Serialization\n *\n * Produces a JSON:API resource object wrapped in `{ data: … }`:\n *\n * ```json\n * {\n * \"data\": {\n * \"type\": \"posts\",\n * \"attributes\": { \"title\": \"Hello\" },\n * \"relationships\": {\n * \"author\": { \"data\": { \"type\": \"users\", \"id\": \"1\" } }\n * }\n * }\n * }\n * ```\n *\n * `id` is included when `options.includeId` is `true`.\n */\n\nimport { injectable } from 'tsyringe';\nimport pluralize from 'pluralize';\nimport {\n Serializer,\n type ModelClassMeta,\n type NormalizeRequestType,\n type NormalizedDocument,\n type NormalizedResource,\n type SerializerSnapshot,\n} from '@mobx-data/serializer';\n\n/** Internal shape of a raw JSON:API resource object. */\ninterface JsonApiResource {\n type: string;\n id?: string | number | null;\n attributes?: Record<string, unknown>;\n relationships?: Record<string, {\n data:\n | { type: string; id: string | number }\n | Array<{ type: string; id: string | number }>\n | null;\n }>;\n}\n\n@injectable()\nexport class JsonApiSerializer extends Serializer {\n /**\n * Returns the plural JSON:API `type` string for a model name.\n * e.g. `'post'` → `'posts'`\n */\n payloadKeyFromModelName(modelName: string): string {\n return pluralize.plural(modelName);\n }\n\n /**\n * Returns the model name for a JSON:API `type` string.\n * e.g. `'posts'` → `'post'`\n */\n modelNameFromPayloadKey(key: string): string {\n return pluralize.singular(key);\n }\n\n /**\n * Normalizes a single JSON:API resource object into a `NormalizedResource`.\n * Returns `null` for absent, non-object, or type-less payloads.\n */\n override normalize(\n _store: unknown,\n _modelClass: ModelClassMeta,\n payload: unknown,\n _prop?: string,\n ): NormalizedResource | null {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return null;\n }\n const resource = payload as JsonApiResource;\n if (!resource.type) {\n return null;\n }\n const normalized: NormalizedResource = {\n type: this.modelNameFromPayloadKey(resource.type),\n id: resource.id === null || resource.id === undefined ? null : String(resource.id),\n };\n if (resource.attributes) {\n normalized.attributes = { ...resource.attributes };\n }\n if (resource.relationships) {\n const relationships: NonNullable<NormalizedResource['relationships']> = {};\n for (const [name, rel] of Object.entries(resource.relationships)) {\n if (rel.data === null) {\n relationships[name] = { data: null };\n } else if (Array.isArray(rel.data)) {\n relationships[name] = {\n data: rel.data.map((ref) => ({\n type: this.modelNameFromPayloadKey(ref.type),\n id: String(ref.id),\n })),\n };\n } else {\n relationships[name] = {\n data: {\n type: this.modelNameFromPayloadKey(rel.data.type),\n id: String(rel.data.id),\n },\n };\n }\n }\n normalized.relationships = relationships;\n }\n return normalized;\n }\n\n /**\n * Normalizes a full JSON:API compound document.\n *\n * - `data` (single or array) → primary resources\n * - `included` → side-loaded resources pushed to `normalizedDoc.included`\n * - `meta` and `links` → forwarded as-is\n */\n override normalizeResponse(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n _id: string | null,\n _requestType: NormalizeRequestType,\n ): NormalizedDocument {\n if (!payload || typeof payload !== 'object') {\n return { data: null };\n }\n const doc = payload as {\n data?: JsonApiResource | JsonApiResource[] | null;\n included?: JsonApiResource[];\n meta?: Record<string, unknown>;\n links?: Record<string, string>;\n };\n\n let data: NormalizedResource | NormalizedResource[] | null = null;\n if (doc.data === null || doc.data === undefined) {\n data = null;\n } else if (Array.isArray(doc.data)) {\n data = doc.data\n .map((resource) => this.normalize(store, modelClass, resource))\n .filter((resource): resource is NormalizedResource => resource !== null);\n } else {\n data = this.normalize(store, modelClass, doc.data);\n }\n\n const normalizedDoc: NormalizedDocument = { data };\n if (doc.included && doc.included.length > 0) {\n const included: NormalizedResource[] = [];\n for (const resource of doc.included) {\n const placeholder: ModelClassMeta = {\n modelName: this.modelNameFromPayloadKey(resource.type),\n attributes: new Map(),\n relationships: new Map(),\n };\n const normalized = this.normalize(store, placeholder, resource);\n if (normalized) {\n included.push(normalized);\n }\n }\n normalizedDoc.included = included;\n }\n if (doc.meta) {\n normalizedDoc.meta = doc.meta;\n }\n if (doc.links) {\n normalizedDoc.links = doc.links;\n }\n return normalizedDoc;\n }\n\n /**\n * Serializes a record snapshot to a JSON:API `{ data: … }` document.\n *\n * Attributes are placed in `data.attributes`; relationships are placed in\n * `data.relationships` with proper `{ data: { type, id } }` structure.\n * `id` is included in `data` when `options.includeId` is `true`.\n */\n override serialize(\n snapshot: SerializerSnapshot,\n options?: { includeId?: boolean },\n ): Record<string, unknown> {\n const attributes: Record<string, unknown> = {};\n snapshot.eachAttribute((key) => {\n attributes[this.keyForAttribute(key)] = snapshot.attr(key);\n });\n\n const relationships: Record<string, { data: unknown }> = {};\n snapshot.eachRelationship((_key, rel) => {\n const { name } = rel;\n const payloadKey = this.keyForRelationship(name);\n const payloadType = this.payloadKeyFromModelName(rel.type);\n if (rel.kind === 'belongsTo') {\n const target = snapshot.belongsTo(name) as\n | { id: string; type?: string }\n | null\n | undefined;\n relationships[payloadKey] = target && target.id != null\n ? { data: { type: payloadType, id: String(target.id) } }\n : { data: null };\n } else {\n const targets = (snapshot.hasMany(name) as Array<{\n id: string; type?: string;\n }> | null) ?? [];\n relationships[payloadKey] = {\n data: targets.map((target) => ({\n type: payloadType,\n id: String(target.id),\n })),\n };\n }\n });\n\n const data: Record<string, unknown> = {\n type: this.payloadKeyFromModelName(snapshot.modelName),\n attributes,\n };\n if (options?.includeId && snapshot.id !== null) {\n data.id = snapshot.id;\n }\n if (Object.keys(relationships).length > 0) {\n data.relationships = relationships;\n }\n\n return { data };\n }\n}\n"],"names":["JsonApiAdapter","RestAdapter","_store","modelName","snapshot","url","changed","attributes","key","current","body","rec","__decorateClass","injectable","JsonApiSerializer","Serializer","pluralize","_modelClass","payload","_prop","resource","normalized","relationships","name","rel","ref","store","modelClass","_id","_requestType","doc","data","normalizedDoc","included","placeholder","options","_key","payloadKey","payloadType","target","targets"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";const m=require("tsyringe"),h=require("./RestAdapter-1V94stW-.cjs"),y=require("pluralize"),f=require("./Serializer-95gi5edy.cjs");var A=Object.getOwnPropertyDescriptor,b=(c,t,n,r)=>{for(var a=r>1?void 0:r?A(t,n):t,i=c.length-1,e;i>=0;i--)(e=c[i])&&(a=e(a)||a);return a};exports.JsonApiAdapter=class extends h.RestAdapter{constructor(){super(...arguments),this.coalesceFindRequests=!0}defaultHeaders(){return{Accept:"application/vnd.api+json",...this.headers}}mutationHeaders(){return{"Content-Type":"application/vnd.api+json",...this.defaultHeaders()}}async updateRecord(t,n,r){const a=this.buildURL(n,r.id,r,"updateRecord");return this._fetchJSON(a,{method:"PATCH",headers:this.mutationHeaders(),body:JSON.stringify(this._serializeForUpdate(r))})}async patchRecord(t,n,r){const a=this.buildURL(n,r.id,r,"updateRecord"),i=r.changedAttributes(),e={};for(const[l,[,s]]of Object.entries(i))e[l]=s;const d={data:{type:n,id:r.id,attributes:e}};return this._fetchJSON(a,{method:"PATCH",headers:this.mutationHeaders(),body:JSON.stringify(d)})}_serializeForUpdate(t){const n=t.record;return n._data?{...n._data}:{}}};exports.JsonApiAdapter=b([m.injectable()],exports.JsonApiAdapter);var g=Object.getOwnPropertyDescriptor,_=(c,t,n,r)=>{for(var a=r>1?void 0:r?g(t,n):t,i=c.length-1,e;i>=0;i--)(e=c[i])&&(a=e(a)||a);return a};exports.JsonApiSerializer=class extends f.Serializer{payloadKeyFromModelName(t){return y.plural(t)}modelNameFromPayloadKey(t){return y.singular(t)}normalize(t,n,r,a){if(!r||typeof r!="object"||Array.isArray(r))return null;const i=r;if(!i.type)return null;const e={type:this.modelNameFromPayloadKey(i.type),id:i.id===null||i.id===void 0?null:String(i.id)};if(i.attributes&&(e.attributes={...i.attributes}),i.relationships){const d={};for(const[l,s]of Object.entries(i.relationships))s.data===null?d[l]={data:null}:Array.isArray(s.data)?d[l]={data:s.data.map(o=>({type:this.modelNameFromPayloadKey(o.type),id:String(o.id)}))}:d[l]={data:{type:this.modelNameFromPayloadKey(s.data.type),id:String(s.data.id)}};e.relationships=d}return e}normalizeResponse(t,n,r,a,i){if(!r||typeof r!="object")return{data:null};const e=r;let d=null;e.data===null||e.data===void 0?d=null:Array.isArray(e.data)?d=e.data.map(s=>this.normalize(t,n,s)).filter(s=>s!==null):d=this.normalize(t,n,e.data);const l={data:d};if(e.included&&e.included.length>0){const s=[];for(const o of e.included){const u={modelName:this.modelNameFromPayloadKey(o.type),attributes:new Map,relationships:new Map},p=this.normalize(t,u,o);p&&s.push(p)}l.included=s}return e.meta&&(l.meta=e.meta),e.links&&(l.links=e.links),l}serialize(t,n){const r={};t.eachAttribute(e=>{r[this.keyForAttribute(e)]=t.attr(e)});const a={};t.eachRelationship((e,d)=>{const{name:l}=d,s=this.keyForRelationship(l),o=this.payloadKeyFromModelName(d.type);if(d.kind==="belongsTo"){const u=t.belongsTo(l);a[s]=u&&u.id!=null?{data:{type:o,id:String(u.id)}}:{data:null}}else{const u=t.hasMany(l)??[];a[s]={data:u.map(p=>({type:o,id:String(p.id)}))}}});const i={type:this.payloadKeyFromModelName(t.modelName),attributes:r};return n!=null&&n.includeId&&t.id!==null&&(i.id=t.id),Object.keys(a).length>0&&(i.relationships=a),{data:i}}};exports.JsonApiSerializer=_([m.injectable()],exports.JsonApiSerializer);
2
- //# sourceMappingURL=JsonApiSerializer-Bc4iQB0d.cjs.map
1
+ "use strict";const m=require("tsyringe"),h=require("./RestAdapter-D7GSrsJo.cjs"),y=require("pluralize"),f=require("./Serializer-Bap9U-kR.cjs");var A=Object.getOwnPropertyDescriptor,b=(c,t,n,r)=>{for(var a=r>1?void 0:r?A(t,n):t,i=c.length-1,e;i>=0;i--)(e=c[i])&&(a=e(a)||a);return a};exports.JsonApiAdapter=class extends h.RestAdapter{constructor(){super(...arguments),this.coalesceFindRequests=!0}defaultHeaders(){return{Accept:"application/vnd.api+json",...this.headers}}mutationHeaders(){return{"Content-Type":"application/vnd.api+json",...this.defaultHeaders()}}async updateRecord(t,n,r){const a=this.buildURL(n,r.id,r,"updateRecord");return this._fetchJSON(a,{method:"PATCH",headers:this.mutationHeaders(),body:JSON.stringify(this._serializeForUpdate(r))})}async patchRecord(t,n,r){const a=this.buildURL(n,r.id,r,"updateRecord"),i=r.changedAttributes(),e={};for(const[l,[,s]]of Object.entries(i))e[l]=s;const d={data:{type:n,id:r.id,attributes:e}};return this._fetchJSON(a,{method:"PATCH",headers:this.mutationHeaders(),body:JSON.stringify(d)})}_serializeForUpdate(t){const n=t.record;return n._data?{...n._data}:{}}};exports.JsonApiAdapter=b([m.injectable()],exports.JsonApiAdapter);var g=Object.getOwnPropertyDescriptor,_=(c,t,n,r)=>{for(var a=r>1?void 0:r?g(t,n):t,i=c.length-1,e;i>=0;i--)(e=c[i])&&(a=e(a)||a);return a};exports.JsonApiSerializer=class extends f.Serializer{payloadKeyFromModelName(t){return y.plural(t)}modelNameFromPayloadKey(t){return y.singular(t)}normalize(t,n,r,a){if(!r||typeof r!="object"||Array.isArray(r))return null;const i=r;if(!i.type)return null;const e={type:this.modelNameFromPayloadKey(i.type),id:i.id===null||i.id===void 0?null:String(i.id)};if(i.attributes&&(e.attributes={...i.attributes}),i.relationships){const d={};for(const[l,s]of Object.entries(i.relationships))s.data===null?d[l]={data:null}:Array.isArray(s.data)?d[l]={data:s.data.map(o=>({type:this.modelNameFromPayloadKey(o.type),id:String(o.id)}))}:d[l]={data:{type:this.modelNameFromPayloadKey(s.data.type),id:String(s.data.id)}};e.relationships=d}return e}normalizeResponse(t,n,r,a,i){if(!r||typeof r!="object")return{data:null};const e=r;let d=null;e.data===null||e.data===void 0?d=null:Array.isArray(e.data)?d=e.data.map(s=>this.normalize(t,n,s)).filter(s=>s!==null):d=this.normalize(t,n,e.data);const l={data:d};if(e.included&&e.included.length>0){const s=[];for(const o of e.included){const u={modelName:this.modelNameFromPayloadKey(o.type),attributes:new Map,relationships:new Map},p=this.normalize(t,u,o);p&&s.push(p)}l.included=s}return e.meta&&(l.meta=e.meta),e.links&&(l.links=e.links),l}serialize(t,n){const r={};t.eachAttribute(e=>{r[this.keyForAttribute(e)]=t.attr(e)});const a={};t.eachRelationship((e,d)=>{const{name:l}=d,s=this.keyForRelationship(l),o=this.payloadKeyFromModelName(d.type);if(d.kind==="belongsTo"){const u=t.belongsTo(l);a[s]=u&&u.id!=null?{data:{type:o,id:String(u.id)}}:{data:null}}else{const u=t.hasMany(l)??[];a[s]={data:u.map(p=>({type:o,id:String(p.id)}))}}});const i={type:this.payloadKeyFromModelName(t.modelName),attributes:r};return n!=null&&n.includeId&&t.id!==null&&(i.id=t.id),Object.keys(a).length>0&&(i.relationships=a),{data:i}}};exports.JsonApiSerializer=_([m.injectable()],exports.JsonApiSerializer);
2
+ //# sourceMappingURL=JsonApiSerializer-Dt_Y_FIo.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonApiSerializer-Dt_Y_FIo.cjs","sources":["../src/json-api/JsonApiAdapter.ts","../src/json-api/JsonApiSerializer.ts"],"sourcesContent":["/**\r\n * Adapter that implements the [JSON:API](https://jsonapi.org) specification.\r\n *\r\n * Extends `RestAdapter` with the following differences:\r\n *\r\n * - **MIME types** — `Accept` and `Content-Type` headers are set to\r\n * `application/vnd.api+json` as required by the spec.\r\n * - **PATCH for updates** — `updateRecord` uses `PATCH` instead of `PUT`.\r\n * - **`coalesceFindRequests: true`** — the store coalesces separate\r\n * `findRecord` calls into a single `findMany` request.\r\n *\r\n * URL construction is inherited from `RestAdapter` / `Adapter` unchanged;\r\n * override `pathForType` or `urlFor*` methods to customize.\r\n */\r\n\r\nimport { injectable } from 'tsyringe';\r\nimport { RestAdapter, type AdapterSnapshot } from '@mobx-data/adapter';\r\n\r\n@injectable()\r\nexport class JsonApiAdapter extends RestAdapter {\r\n /** Always coalesce `findRecord` calls into a single `findMany` request. */\r\n override coalesceFindRequests = true;\r\n\r\n /** Returns JSON:API `Accept` header alongside any custom headers. */\r\n override defaultHeaders(): Record<string, string> {\r\n return {\r\n Accept: 'application/vnd.api+json',\r\n ...this.headers,\r\n };\r\n }\r\n\r\n /** Returns JSON:API `Content-Type` header for mutation requests. */\r\n override mutationHeaders(): Record<string, string> {\r\n return {\r\n 'Content-Type': 'application/vnd.api+json',\r\n ...this.defaultHeaders(),\r\n };\r\n }\r\n\r\n /**\r\n * Sends a PATCH request to update a record (JSON:API mandates PATCH, not PUT).\r\n */\r\n override async updateRecord(\r\n _store: unknown,\r\n modelName: string,\r\n snapshot: AdapterSnapshot,\r\n ): Promise<unknown> {\r\n const url = this.buildURL(modelName, snapshot.id, snapshot, 'updateRecord');\r\n return this._fetchJSON(url, {\r\n method: 'PATCH',\r\n headers: this.mutationHeaders(),\r\n body: JSON.stringify(this._serializeForUpdate(snapshot)),\r\n });\r\n }\r\n\r\n /**\r\n * Sends a PATCH request with only the changed attributes (partial update).\r\n * Produces a JSON:API document with only the dirty attribute keys.\r\n */\r\n override async patchRecord(\r\n _store: unknown,\r\n modelName: string,\r\n snapshot: AdapterSnapshot,\r\n ): Promise<unknown> {\r\n const url = this.buildURL(modelName, snapshot.id, snapshot, 'updateRecord');\r\n const changed = snapshot.changedAttributes();\r\n const attributes: Record<string, unknown> = {};\r\n for (const [key, [, current]] of Object.entries(changed)) {\r\n attributes[key] = current;\r\n }\r\n const body = {\r\n data: {\r\n type: modelName,\r\n id: snapshot.id,\r\n attributes,\r\n },\r\n };\r\n return this._fetchJSON(url, {\r\n method: 'PATCH',\r\n headers: this.mutationHeaders(),\r\n body: JSON.stringify(body),\r\n });\r\n }\r\n\r\n /**\r\n * Extracts the raw data object from the snapshot for use as the PATCH body.\r\n * Subclasses may override this to produce a full JSON:API `{ data: … }` document.\r\n */\r\n protected _serializeForUpdate(snapshot: AdapterSnapshot): Record<string, unknown> {\r\n const rec = snapshot.record as { _data?: Record<string, unknown> };\r\n return rec._data ? { ...rec._data } : {};\r\n }\r\n}\r\n","/**\n * Serializer that implements the [JSON:API](https://jsonapi.org) specification.\n *\n * ## Normalization\n *\n * Parses a JSON:API compound document `{ data, included, meta, links }` into\n * a `NormalizedDocument`.\n *\n * For each resource object:\n * - `type` is converted from JSON:API plural form to model name via\n * `modelNameFromPayloadKey` (singularization).\n * - `id` is coerced to a string.\n * - `attributes` are copied directly.\n * - `relationships` data refs (`{ type, id }`) are normalized the same way.\n *\n * Side-loaded records in `included` are normalized individually, each using\n * its own `type` as the model class placeholder.\n *\n * ## Serialization\n *\n * Produces a JSON:API resource object wrapped in `{ data: … }`:\n *\n * ```json\n * {\n * \"data\": {\n * \"type\": \"posts\",\n * \"attributes\": { \"title\": \"Hello\" },\n * \"relationships\": {\n * \"author\": { \"data\": { \"type\": \"users\", \"id\": \"1\" } }\n * }\n * }\n * }\n * ```\n *\n * `id` is included when `options.includeId` is `true`.\n */\n\nimport { injectable } from 'tsyringe';\nimport pluralize from 'pluralize';\nimport {\n Serializer,\n type ModelClassMeta,\n type NormalizeRequestType,\n type NormalizedDocument,\n type NormalizedResource,\n type SerializerSnapshot,\n} from '@mobx-data/serializer';\n\n/** Internal shape of a raw JSON:API resource object. */\ninterface JsonApiResource {\n type: string;\n id?: string | number | null;\n attributes?: Record<string, unknown>;\n relationships?: Record<string, {\n data:\n | { type: string; id: string | number }\n | Array<{ type: string; id: string | number }>\n | null;\n }>;\n}\n\n@injectable()\nexport class JsonApiSerializer extends Serializer {\n /**\n * Returns the plural JSON:API `type` string for a model name.\n * e.g. `'post'` → `'posts'`\n */\n payloadKeyFromModelName(modelName: string): string {\n return pluralize.plural(modelName);\n }\n\n /**\n * Returns the model name for a JSON:API `type` string.\n * e.g. `'posts'` → `'post'`\n */\n modelNameFromPayloadKey(key: string): string {\n return pluralize.singular(key);\n }\n\n /**\n * Normalizes a single JSON:API resource object into a `NormalizedResource`.\n * Returns `null` for absent, non-object, or type-less payloads.\n */\n override normalize(\n _store: unknown,\n _modelClass: ModelClassMeta,\n payload: unknown,\n _prop?: string,\n ): NormalizedResource | null {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return null;\n }\n const resource = payload as JsonApiResource;\n if (!resource.type) {\n return null;\n }\n const normalized: NormalizedResource = {\n type: this.modelNameFromPayloadKey(resource.type),\n id: resource.id === null || resource.id === undefined ? null : String(resource.id),\n };\n if (resource.attributes) {\n normalized.attributes = { ...resource.attributes };\n }\n if (resource.relationships) {\n const relationships: NonNullable<NormalizedResource['relationships']> = {};\n for (const [name, rel] of Object.entries(resource.relationships)) {\n if (rel.data === null) {\n relationships[name] = { data: null };\n } else if (Array.isArray(rel.data)) {\n relationships[name] = {\n data: rel.data.map((ref) => ({\n type: this.modelNameFromPayloadKey(ref.type),\n id: String(ref.id),\n })),\n };\n } else {\n relationships[name] = {\n data: {\n type: this.modelNameFromPayloadKey(rel.data.type),\n id: String(rel.data.id),\n },\n };\n }\n }\n normalized.relationships = relationships;\n }\n return normalized;\n }\n\n /**\n * Normalizes a full JSON:API compound document.\n *\n * - `data` (single or array) → primary resources\n * - `included` → side-loaded resources pushed to `normalizedDoc.included`\n * - `meta` and `links` → forwarded as-is\n */\n override normalizeResponse(\n store: unknown,\n modelClass: ModelClassMeta,\n payload: unknown,\n _id: string | null,\n _requestType: NormalizeRequestType,\n ): NormalizedDocument {\n if (!payload || typeof payload !== 'object') {\n return { data: null };\n }\n const doc = payload as {\n data?: JsonApiResource | JsonApiResource[] | null;\n included?: JsonApiResource[];\n meta?: Record<string, unknown>;\n links?: Record<string, string>;\n };\n\n let data: NormalizedResource | NormalizedResource[] | null = null;\n if (doc.data === null || doc.data === undefined) {\n data = null;\n } else if (Array.isArray(doc.data)) {\n data = doc.data\n .map((resource) => this.normalize(store, modelClass, resource))\n .filter((resource): resource is NormalizedResource => resource !== null);\n } else {\n data = this.normalize(store, modelClass, doc.data);\n }\n\n const normalizedDoc: NormalizedDocument = { data };\n if (doc.included && doc.included.length > 0) {\n const included: NormalizedResource[] = [];\n for (const resource of doc.included) {\n const placeholder: ModelClassMeta = {\n modelName: this.modelNameFromPayloadKey(resource.type),\n attributes: new Map(),\n relationships: new Map(),\n };\n const normalized = this.normalize(store, placeholder, resource);\n if (normalized) {\n included.push(normalized);\n }\n }\n normalizedDoc.included = included;\n }\n if (doc.meta) {\n normalizedDoc.meta = doc.meta;\n }\n if (doc.links) {\n normalizedDoc.links = doc.links;\n }\n return normalizedDoc;\n }\n\n /**\n * Serializes a record snapshot to a JSON:API `{ data: … }` document.\n *\n * Attributes are placed in `data.attributes`; relationships are placed in\n * `data.relationships` with proper `{ data: { type, id } }` structure.\n * `id` is included in `data` when `options.includeId` is `true`.\n */\n override serialize(\n snapshot: SerializerSnapshot,\n options?: { includeId?: boolean },\n ): Record<string, unknown> {\n const attributes: Record<string, unknown> = {};\n snapshot.eachAttribute((key) => {\n attributes[this.keyForAttribute(key)] = snapshot.attr(key);\n });\n\n const relationships: Record<string, { data: unknown }> = {};\n snapshot.eachRelationship((_key, rel) => {\n const { name } = rel;\n const payloadKey = this.keyForRelationship(name);\n const payloadType = this.payloadKeyFromModelName(rel.type);\n if (rel.kind === 'belongsTo') {\n const target = snapshot.belongsTo(name) as\n | { id: string; type?: string }\n | null\n | undefined;\n relationships[payloadKey] = target && target.id != null\n ? { data: { type: payloadType, id: String(target.id) } }\n : { data: null };\n } else {\n const targets = (snapshot.hasMany(name) as Array<{\n id: string; type?: string;\n }> | null) ?? [];\n relationships[payloadKey] = {\n data: targets.map((target) => ({\n type: payloadType,\n id: String(target.id),\n })),\n };\n }\n });\n\n const data: Record<string, unknown> = {\n type: this.payloadKeyFromModelName(snapshot.modelName),\n attributes,\n };\n if (options?.includeId && snapshot.id !== null) {\n data.id = snapshot.id;\n }\n if (Object.keys(relationships).length > 0) {\n data.relationships = relationships;\n }\n\n return { data };\n }\n}\n"],"names":["JsonApiAdapter","RestAdapter","_store","modelName","snapshot","url","changed","attributes","key","current","body","rec","__decorateClass","injectable","JsonApiSerializer","Serializer","pluralize","_modelClass","payload","_prop","resource","normalized","relationships","name","rel","ref","store","modelClass","_id","_requestType","doc","data","normalizedDoc","included","placeholder","options","_key","payloadKey","payloadType","target","targets"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";require("reflect-metadata");const z=require("tsyringe"),h=require("./types-DCLy5XYj.cjs"),d=require("./Serializer-Bap9U-kR.cjs");var R=Object.getOwnPropertyDescriptor,f=(l,r,t,e)=>{for(var n=e>1?void 0:e?R(r,t):r,i=l.length-1,c;i>=0;i--)(c=l[i])&&(n=c(n)||n);return n};exports.JsonSerializer=class extends d.Serializer{static dispatchMethodName(r){switch(r){case"findRecord":return"normalizeFindRecordResponse";case"findAll":return"normalizeFindAllResponse";case"query":return"normalizeQueryResponse";case"queryRecord":return"normalizeQueryRecordResponse";case"createRecord":return"normalizeCreateRecordResponse";case"updateRecord":return"normalizeUpdateRecordResponse";case"deleteRecord":return"normalizeDeleteRecordResponse";default:return null}}normalize(r,t,e,n){if(e==null||typeof e!="object")return null;const i=e,c=this.extractId(t,i),s=Reflect.getOwnMetadata(h.MODEL_OPTIONS_META_KEY,t),a=s!=null&&s.discriminator?this.extractAllAttributes(t,i):this.extractAttributes(t,i),u=this.extractRelationships(t,i),o={type:t.modelName,id:c,attributes:a};return u&&Object.keys(u).length>0&&(o.relationships=u),o}normalizeResponse(r,t,e,n,i){const c=exports.JsonSerializer.dispatchMethodName(i);if(c){const s=this[c],a=d.Serializer.prototype[c];if(typeof s=="function"&&s!==a)return s.call(this,r,t,e,n,i)}return this._buildDocument(r,t,e,n,i)}_buildDocument(r,t,e,n,i){return e==null?{data:null}:Array.isArray(e)?{data:e.map(a=>this.normalize(r,t,a)).filter(a=>a!==null)}:{data:this.normalize(r,t,e)}}serialize(r,t){const e={};return t!=null&&t.includeId&&r.id!==null?e[this.primaryKey]=r.id:t!=null&&t.clientGeneratedIds&&r.id===null&&(e[this.primaryKey]=r.clientId),r.eachAttribute((n,i)=>{this.serializeAttribute(r,e,n,i)}),r.eachRelationship((n,i)=>{i.kind==="belongsTo"?this.serializeBelongsTo(r,e,i):this.serializeHasMany(r,e,i)}),e}};exports.JsonSerializer=f([z.injectable()],exports.JsonSerializer);
2
+ //# sourceMappingURL=JsonSerializer-BzUCyUSf.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonSerializer-BzUCyUSf.cjs","sources":["../src/serializer/JsonSerializer.ts"],"sourcesContent":["/**\r\n * Flat-JSON serializer for simple REST APIs.\r\n *\r\n * `JsonSerializer` expects payloads to be plain JSON objects or arrays with\r\n * no root-key wrapping. It is the lowest-level concrete serializer and the\r\n * base that `RestSerializer` extends.\r\n *\r\n * Normalization:\r\n * - Array payload → `{ data: [NormalizedResource, …] }`\r\n * - Object payload → `{ data: NormalizedResource }`\r\n * - `null` / `undefined` → `{ data: null }`\r\n *\r\n * Serialization:\r\n * - Iterates `@attr` → writes via `serializeAttribute`\r\n * - Iterates `@belongsTo` / `@hasMany` → writes via `serializeBelongsTo` /\r\n * `serializeHasMany`\r\n *\r\n * `normalizeResponse` automatically dispatches to the correct per-operation\r\n * hook (`normalizeFindRecordResponse`, etc.) when a subclass overrides them.\r\n * If no override exists it falls through to `_buildDocument`.\r\n */\r\n\r\nimport 'reflect-metadata';\r\nimport { injectable } from 'tsyringe';\r\nimport { MODEL_OPTIONS_META_KEY, type ModelOptions } from '../schema/types.js';\r\nimport {\r\n Serializer,\r\n type ModelClassMeta,\r\n type NormalizeRequestType,\r\n type NormalizedDocument,\r\n type NormalizedResource,\r\n type SerializerSnapshot,\r\n} from './Serializer.js';\r\n\r\n@injectable()\r\nexport class JsonSerializer extends Serializer {\r\n static dispatchMethodName(\r\n requestType: NormalizeRequestType,\r\n ): keyof Serializer | null {\r\n switch (requestType) {\r\n case 'findRecord':\r\n return 'normalizeFindRecordResponse';\r\n case 'findAll':\r\n return 'normalizeFindAllResponse';\r\n case 'query':\r\n return 'normalizeQueryResponse';\r\n case 'queryRecord':\r\n return 'normalizeQueryRecordResponse';\r\n case 'createRecord':\r\n return 'normalizeCreateRecordResponse';\r\n case 'updateRecord':\r\n return 'normalizeUpdateRecordResponse';\r\n case 'deleteRecord':\r\n return 'normalizeDeleteRecordResponse';\r\n default:\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Normalizes a single flat JSON object into a `NormalizedResource`.\r\n * Returns `null` for absent or non-object payloads.\r\n */\r\n override normalize(\r\n _store: unknown,\r\n modelClass: ModelClassMeta,\r\n payload: unknown,\r\n _prop?: string,\r\n ): NormalizedResource | null {\r\n if (payload === null || payload === undefined) {\r\n return null;\r\n }\r\n if (typeof payload !== 'object') {\r\n return null;\r\n }\r\n const hash = payload as Record<string, unknown>;\r\n const id = this.extractId(modelClass, hash);\r\n\r\n const modelOptions = Reflect.getOwnMetadata(\r\n MODEL_OPTIONS_META_KEY,\r\n modelClass,\r\n ) as ModelOptions | undefined;\r\n const attributes = modelOptions?.discriminator\r\n ? this.extractAllAttributes(modelClass, hash)\r\n : this.extractAttributes(modelClass, hash);\r\n\r\n const relationships = this.extractRelationships(modelClass, hash);\r\n\r\n const resource: NormalizedResource = {\r\n type: modelClass.modelName,\r\n id,\r\n attributes,\r\n };\r\n if (relationships && Object.keys(relationships).length > 0) {\r\n resource.relationships = relationships;\r\n }\r\n return resource;\r\n }\r\n\r\n /**\r\n * Entry point for normalization.\r\n *\r\n * Checks whether a subclass has overridden the relevant per-operation hook\r\n * (e.g. `normalizeFindRecordResponse`). If so, calls it; otherwise falls\r\n * through to `_buildDocument`.\r\n */\r\n override normalizeResponse(\r\n store: unknown,\r\n modelClass: ModelClassMeta,\r\n payload: unknown,\r\n id: string | null,\r\n requestType: NormalizeRequestType,\r\n ): NormalizedDocument {\r\n const methodName = JsonSerializer.dispatchMethodName(requestType);\r\n if (methodName) {\r\n const override = (this as unknown as Record<string, unknown>)[methodName];\r\n const baseImpl = (Serializer.prototype as unknown as Record<string, unknown>)[\r\n methodName\r\n ];\r\n if (typeof override === 'function' && override !== baseImpl) {\r\n return (override as (...args: unknown[]) => NormalizedDocument).call(\r\n this,\r\n store,\r\n modelClass,\r\n payload,\r\n id,\r\n requestType,\r\n );\r\n }\r\n }\r\n return this._buildDocument(store, modelClass, payload, id, requestType);\r\n }\r\n\r\n /**\r\n * Builds a `NormalizedDocument` from a raw payload.\r\n * Arrays are normalized item by item; plain objects are normalized as a\r\n * single resource.\r\n */\r\n protected _buildDocument(\r\n store: unknown,\r\n modelClass: ModelClassMeta,\r\n payload: unknown,\r\n _id: string | null,\r\n _requestType: NormalizeRequestType,\r\n ): NormalizedDocument {\r\n if (payload === null || payload === undefined) {\r\n return { data: null };\r\n }\r\n if (Array.isArray(payload)) {\r\n const data = payload\r\n .map((p) => this.normalize(store, modelClass, p))\r\n .filter((r): r is NormalizedResource => r !== null);\r\n return { data };\r\n }\r\n const data = this.normalize(store, modelClass, payload);\r\n return { data };\r\n }\r\n\r\n /**\r\n * Serializes a snapshot to a flat JSON object.\r\n * Includes `id` when `options.includeId` is `true`.\r\n */\r\n override serialize(\r\n snapshot: SerializerSnapshot,\r\n options?: { includeId?: boolean; clientGeneratedIds?: boolean },\r\n ): Record<string, unknown> {\r\n const json: Record<string, unknown> = {};\r\n if (options?.includeId && snapshot.id !== null) {\r\n json[this.primaryKey] = snapshot.id;\r\n } else if (options?.clientGeneratedIds && snapshot.id === null) {\r\n json[this.primaryKey] = snapshot.clientId;\r\n }\r\n snapshot.eachAttribute((key, meta) => {\r\n this.serializeAttribute(snapshot, json, key, meta);\r\n });\r\n snapshot.eachRelationship((_key, rel) => {\r\n if (rel.kind === 'belongsTo') {\r\n this.serializeBelongsTo(snapshot, json, rel);\r\n } else {\r\n this.serializeHasMany(snapshot, json, rel);\r\n }\r\n });\r\n return json;\r\n }\r\n}\r\n"],"names":["JsonSerializer","Serializer","requestType","_store","modelClass","payload","_prop","hash","id","modelOptions","MODEL_OPTIONS_META_KEY","attributes","relationships","resource","store","methodName","override","baseImpl","_id","_requestType","p","r","data","snapshot","options","json","key","meta","_key","rel","__decorateClass","injectable"],"mappings":"0RAmCaA,QAAAA,eAAN,cAA6BC,EAAAA,UAAW,CAC7C,OAAO,mBACLC,EACyB,CACzB,OAAQA,EAAA,CACN,IAAK,aACH,MAAO,8BACT,IAAK,UACH,MAAO,2BACT,IAAK,QACH,MAAO,yBACT,IAAK,cACH,MAAO,+BACT,IAAK,eACH,MAAO,gCACT,IAAK,eACH,MAAO,gCACT,IAAK,eACH,MAAO,gCACT,QACE,OAAO,IAAA,CAEb,CAMS,UACPC,EACAC,EACAC,EACAC,EAC2B,CAI3B,GAHID,GAAY,MAGZ,OAAOA,GAAY,SACrB,OAAO,KAET,MAAME,EAAOF,EACPG,EAAK,KAAK,UAAUJ,EAAYG,CAAI,EAEpCE,EAAe,QAAQ,eAC3BC,EAAAA,uBACAN,CAAA,EAEIO,EAAaF,GAAA,MAAAA,EAAc,cAC7B,KAAK,qBAAqBL,EAAYG,CAAI,EAC1C,KAAK,kBAAkBH,EAAYG,CAAI,EAErCK,EAAgB,KAAK,qBAAqBR,EAAYG,CAAI,EAE1DM,EAA+B,CACnC,KAAMT,EAAW,UACjB,GAAAI,EACA,WAAAG,CAAA,EAEF,OAAIC,GAAiB,OAAO,KAAKA,CAAa,EAAE,OAAS,IACvDC,EAAS,cAAgBD,GAEpBC,CACT,CASS,kBACPC,EACAV,EACAC,EACAG,EACAN,EACoB,CACpB,MAAMa,EAAaf,QAAAA,eAAe,mBAAmBE,CAAW,EAChE,GAAIa,EAAY,CACd,MAAMC,EAAY,KAA4CD,CAAU,EAClEE,EAAYhB,EAAAA,WAAW,UAC3Bc,CACF,EACA,GAAI,OAAOC,GAAa,YAAcA,IAAaC,EACjD,OAAQD,EAAwD,KAC9D,KACAF,EACAV,EACAC,EACAG,EACAN,CAAA,CAGN,CACA,OAAO,KAAK,eAAeY,EAAOV,EAAYC,EAASG,EAAIN,CAAW,CACxE,CAOU,eACRY,EACAV,EACAC,EACAa,EACAC,EACoB,CACpB,OAAId,GAAY,KACP,CAAE,KAAM,IAAA,EAEb,MAAM,QAAQA,CAAO,EAIhB,CAAE,KAHIA,EACV,IAAKe,GAAM,KAAK,UAAUN,EAAOV,EAAYgB,CAAC,CAAC,EAC/C,OAAQC,GAA+BA,IAAM,IAAI,CAC3CC,EAGJ,CAAE,KADI,KAAK,UAAUR,EAAOV,EAAYC,CAAO,CAC7C,CACX,CAMS,UACPkB,EACAC,EACyB,CACzB,MAAMC,EAAgC,CAAA,EACtC,OAAID,GAAA,MAAAA,EAAS,WAAaD,EAAS,KAAO,KACxCE,EAAK,KAAK,UAAU,EAAIF,EAAS,GACxBC,GAAA,MAAAA,EAAS,oBAAsBD,EAAS,KAAO,OACxDE,EAAK,KAAK,UAAU,EAAIF,EAAS,UAEnCA,EAAS,cAAc,CAACG,EAAKC,IAAS,CACpC,KAAK,mBAAmBJ,EAAUE,EAAMC,EAAKC,CAAI,CACnD,CAAC,EACDJ,EAAS,iBAAiB,CAACK,EAAMC,IAAQ,CACnCA,EAAI,OAAS,YACf,KAAK,mBAAmBN,EAAUE,EAAMI,CAAG,EAE3C,KAAK,iBAAiBN,EAAUE,EAAMI,CAAG,CAE7C,CAAC,EACMJ,CACT,CACF,EArJazB,QAAAA,eAAN8B,EAAA,CADNC,EAAAA,WAAA,CAAW,EACC/B,sBAAA"}
@@ -0,0 +1,98 @@
1
+ import "reflect-metadata";
2
+ import { injectable as f } from "tsyringe";
3
+ import { a as m } from "./types-CC2fG3FP.js";
4
+ import { S as d } from "./Serializer-Ca6w_QNQ.js";
5
+ var h = Object.getOwnPropertyDescriptor, R = (t, e, r, c) => {
6
+ for (var i = c > 1 ? void 0 : c ? h(e, r) : e, a = t.length - 1, n; a >= 0; a--)
7
+ (n = t[a]) && (i = n(i) || i);
8
+ return i;
9
+ };
10
+ let u = class extends d {
11
+ static dispatchMethodName(t) {
12
+ switch (t) {
13
+ case "findRecord":
14
+ return "normalizeFindRecordResponse";
15
+ case "findAll":
16
+ return "normalizeFindAllResponse";
17
+ case "query":
18
+ return "normalizeQueryResponse";
19
+ case "queryRecord":
20
+ return "normalizeQueryRecordResponse";
21
+ case "createRecord":
22
+ return "normalizeCreateRecordResponse";
23
+ case "updateRecord":
24
+ return "normalizeUpdateRecordResponse";
25
+ case "deleteRecord":
26
+ return "normalizeDeleteRecordResponse";
27
+ default:
28
+ return null;
29
+ }
30
+ }
31
+ /**
32
+ * Normalizes a single flat JSON object into a `NormalizedResource`.
33
+ * Returns `null` for absent or non-object payloads.
34
+ */
35
+ normalize(t, e, r, c) {
36
+ if (r == null || typeof r != "object")
37
+ return null;
38
+ const i = r, a = this.extractId(e, i), n = Reflect.getOwnMetadata(
39
+ m,
40
+ e
41
+ ), s = n != null && n.discriminator ? this.extractAllAttributes(e, i) : this.extractAttributes(e, i), l = this.extractRelationships(e, i), o = {
42
+ type: e.modelName,
43
+ id: a,
44
+ attributes: s
45
+ };
46
+ return l && Object.keys(l).length > 0 && (o.relationships = l), o;
47
+ }
48
+ /**
49
+ * Entry point for normalization.
50
+ *
51
+ * Checks whether a subclass has overridden the relevant per-operation hook
52
+ * (e.g. `normalizeFindRecordResponse`). If so, calls it; otherwise falls
53
+ * through to `_buildDocument`.
54
+ */
55
+ normalizeResponse(t, e, r, c, i) {
56
+ const a = u.dispatchMethodName(i);
57
+ if (a) {
58
+ const n = this[a], s = d.prototype[a];
59
+ if (typeof n == "function" && n !== s)
60
+ return n.call(
61
+ this,
62
+ t,
63
+ e,
64
+ r,
65
+ c,
66
+ i
67
+ );
68
+ }
69
+ return this._buildDocument(t, e, r, c, i);
70
+ }
71
+ /**
72
+ * Builds a `NormalizedDocument` from a raw payload.
73
+ * Arrays are normalized item by item; plain objects are normalized as a
74
+ * single resource.
75
+ */
76
+ _buildDocument(t, e, r, c, i) {
77
+ return r == null ? { data: null } : Array.isArray(r) ? { data: r.map((s) => this.normalize(t, e, s)).filter((s) => s !== null) } : { data: this.normalize(t, e, r) };
78
+ }
79
+ /**
80
+ * Serializes a snapshot to a flat JSON object.
81
+ * Includes `id` when `options.includeId` is `true`.
82
+ */
83
+ serialize(t, e) {
84
+ const r = {};
85
+ return e != null && e.includeId && t.id !== null ? r[this.primaryKey] = t.id : e != null && e.clientGeneratedIds && t.id === null && (r[this.primaryKey] = t.clientId), t.eachAttribute((c, i) => {
86
+ this.serializeAttribute(t, r, c, i);
87
+ }), t.eachRelationship((c, i) => {
88
+ i.kind === "belongsTo" ? this.serializeBelongsTo(t, r, i) : this.serializeHasMany(t, r, i);
89
+ }), r;
90
+ }
91
+ };
92
+ u = R([
93
+ f()
94
+ ], u);
95
+ export {
96
+ u as J
97
+ };
98
+ //# sourceMappingURL=JsonSerializer-CFqo6GjC.js.map