@iamjulianacosta/mobx-data 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +366 -0
  3. package/dist/CacheHandler-BTU_rYkv.js +208 -0
  4. package/dist/CacheHandler-BTU_rYkv.js.map +1 -0
  5. package/dist/CacheHandler-CXgY9IJo.cjs +2 -0
  6. package/dist/CacheHandler-CXgY9IJo.cjs.map +1 -0
  7. package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs +2 -0
  8. package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs.map +1 -0
  9. package/dist/EmbeddedRecordsMixin-VoHluHCT.js +261 -0
  10. package/dist/EmbeddedRecordsMixin-VoHluHCT.js.map +1 -0
  11. package/dist/JsonApiSerializer-CC5HXp4b.js +194 -0
  12. package/dist/JsonApiSerializer-CC5HXp4b.js.map +1 -0
  13. package/dist/JsonApiSerializer-CKB02AgP.cjs +2 -0
  14. package/dist/JsonApiSerializer-CKB02AgP.cjs.map +1 -0
  15. package/dist/MemoryAdapter-Bx1e7ndV.js +123 -0
  16. package/dist/MemoryAdapter-Bx1e7ndV.js.map +1 -0
  17. package/dist/MemoryAdapter-D1cTyydm.cjs +2 -0
  18. package/dist/MemoryAdapter-D1cTyydm.cjs.map +1 -0
  19. package/dist/ODataAdapter-C4IHK4BK.js +157 -0
  20. package/dist/ODataAdapter-C4IHK4BK.js.map +1 -0
  21. package/dist/ODataAdapter-DyyF1sdA.cjs +2 -0
  22. package/dist/ODataAdapter-DyyF1sdA.cjs.map +1 -0
  23. package/dist/RestAdapter-B4aRvs4m.js +355 -0
  24. package/dist/RestAdapter-B4aRvs4m.js.map +1 -0
  25. package/dist/RestAdapter-CJOwTsKK.cjs +2 -0
  26. package/dist/RestAdapter-CJOwTsKK.cjs.map +1 -0
  27. package/dist/SchemaService-DZwkFgZu.js +102 -0
  28. package/dist/SchemaService-DZwkFgZu.js.map +1 -0
  29. package/dist/SchemaService-Di_yjVzU.cjs +2 -0
  30. package/dist/SchemaService-Di_yjVzU.cjs.map +1 -0
  31. package/dist/Serializer-95gi5edy.cjs +2 -0
  32. package/dist/Serializer-95gi5edy.cjs.map +1 -0
  33. package/dist/Serializer-FxJbsZ50.js +139 -0
  34. package/dist/Serializer-FxJbsZ50.js.map +1 -0
  35. package/dist/Store-BdwMrbDi.cjs +2 -0
  36. package/dist/Store-BdwMrbDi.cjs.map +1 -0
  37. package/dist/Store-CZ7Z-Nme.js +912 -0
  38. package/dist/Store-CZ7Z-Nme.js.map +1 -0
  39. package/dist/adapter/Adapter.d.ts +146 -0
  40. package/dist/adapter/Adapter.d.ts.map +1 -0
  41. package/dist/adapter/MemoryAdapter.d.ts +44 -0
  42. package/dist/adapter/MemoryAdapter.d.ts.map +1 -0
  43. package/dist/adapter/RestAdapter.d.ts +57 -0
  44. package/dist/adapter/RestAdapter.d.ts.map +1 -0
  45. package/dist/adapter/index.cjs +2 -0
  46. package/dist/adapter/index.cjs.map +1 -0
  47. package/dist/adapter/index.d.ts +4 -0
  48. package/dist/adapter/index.d.ts.map +1 -0
  49. package/dist/adapter/index.js +8 -0
  50. package/dist/adapter/index.js.map +1 -0
  51. package/dist/date-Bj4O2W1F.js +107 -0
  52. package/dist/date-Bj4O2W1F.js.map +1 -0
  53. package/dist/date-CRCe-9gf.cjs +2 -0
  54. package/dist/date-CRCe-9gf.cjs.map +1 -0
  55. package/dist/decorators-HQ1KnRdh.cjs +2 -0
  56. package/dist/decorators-HQ1KnRdh.cjs.map +1 -0
  57. package/dist/decorators-Zr35qr6A.js +50 -0
  58. package/dist/decorators-Zr35qr6A.js.map +1 -0
  59. package/dist/index.cjs +2 -0
  60. package/dist/index.cjs.map +1 -0
  61. package/dist/index.d.ts +10 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +52 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/json-api/JsonApiAdapter.d.ts +38 -0
  66. package/dist/json-api/JsonApiAdapter.d.ts.map +1 -0
  67. package/dist/json-api/JsonApiSerializer.d.ts +73 -0
  68. package/dist/json-api/JsonApiSerializer.d.ts.map +1 -0
  69. package/dist/json-api/index.cjs +2 -0
  70. package/dist/json-api/index.cjs.map +1 -0
  71. package/dist/json-api/index.d.ts +3 -0
  72. package/dist/json-api/index.d.ts.map +1 -0
  73. package/dist/json-api/index.js +6 -0
  74. package/dist/json-api/index.js.map +1 -0
  75. package/dist/model/Errors.d.ts +46 -0
  76. package/dist/model/Errors.d.ts.map +1 -0
  77. package/dist/model/Model.d.ts +226 -0
  78. package/dist/model/Model.d.ts.map +1 -0
  79. package/dist/model/Snapshot.d.ts +72 -0
  80. package/dist/model/Snapshot.d.ts.map +1 -0
  81. package/dist/model/StateMachine.d.ts +45 -0
  82. package/dist/model/StateMachine.d.ts.map +1 -0
  83. package/dist/model/index.cjs +2 -0
  84. package/dist/model/index.cjs.map +1 -0
  85. package/dist/model/index.d.ts +6 -0
  86. package/dist/model/index.d.ts.map +1 -0
  87. package/dist/model/index.js +11 -0
  88. package/dist/model/index.js.map +1 -0
  89. package/dist/model/relationships.d.ts +182 -0
  90. package/dist/model/relationships.d.ts.map +1 -0
  91. package/dist/odata/ODataAdapter.d.ts +67 -0
  92. package/dist/odata/ODataAdapter.d.ts.map +1 -0
  93. package/dist/odata/index.cjs +2 -0
  94. package/dist/odata/index.cjs.map +1 -0
  95. package/dist/odata/index.d.ts +2 -0
  96. package/dist/odata/index.d.ts.map +1 -0
  97. package/dist/odata/index.js +5 -0
  98. package/dist/odata/index.js.map +1 -0
  99. package/dist/relationships-B55LBaCW.cjs +2 -0
  100. package/dist/relationships-B55LBaCW.cjs.map +1 -0
  101. package/dist/relationships-BEXANmWg.js +821 -0
  102. package/dist/relationships-BEXANmWg.js.map +1 -0
  103. package/dist/request/CacheHandler.d.ts +50 -0
  104. package/dist/request/CacheHandler.d.ts.map +1 -0
  105. package/dist/request/FetchHandler.d.ts +41 -0
  106. package/dist/request/FetchHandler.d.ts.map +1 -0
  107. package/dist/request/RequestManager.d.ts +52 -0
  108. package/dist/request/RequestManager.d.ts.map +1 -0
  109. package/dist/request/index.cjs +2 -0
  110. package/dist/request/index.cjs.map +1 -0
  111. package/dist/request/index.d.ts +5 -0
  112. package/dist/request/index.d.ts.map +1 -0
  113. package/dist/request/index.js +7 -0
  114. package/dist/request/index.js.map +1 -0
  115. package/dist/request/types.d.ts +111 -0
  116. package/dist/request/types.d.ts.map +1 -0
  117. package/dist/schema/SchemaService.d.ts +58 -0
  118. package/dist/schema/SchemaService.d.ts.map +1 -0
  119. package/dist/schema/decorators.d.ts +50 -0
  120. package/dist/schema/decorators.d.ts.map +1 -0
  121. package/dist/schema/index.cjs +2 -0
  122. package/dist/schema/index.cjs.map +1 -0
  123. package/dist/schema/index.d.ts +4 -0
  124. package/dist/schema/index.d.ts.map +1 -0
  125. package/dist/schema/index.js +13 -0
  126. package/dist/schema/index.js.map +1 -0
  127. package/dist/schema/types.d.ts +61 -0
  128. package/dist/schema/types.d.ts.map +1 -0
  129. package/dist/serializer/EmbeddedRecordsMixin.d.ts +80 -0
  130. package/dist/serializer/EmbeddedRecordsMixin.d.ts.map +1 -0
  131. package/dist/serializer/JsonSerializer.d.ts +52 -0
  132. package/dist/serializer/JsonSerializer.d.ts.map +1 -0
  133. package/dist/serializer/RestSerializer.d.ts +43 -0
  134. package/dist/serializer/RestSerializer.d.ts.map +1 -0
  135. package/dist/serializer/Serializer.d.ts +202 -0
  136. package/dist/serializer/Serializer.d.ts.map +1 -0
  137. package/dist/serializer/index.cjs +2 -0
  138. package/dist/serializer/index.cjs.map +1 -0
  139. package/dist/serializer/index.d.ts +5 -0
  140. package/dist/serializer/index.d.ts.map +1 -0
  141. package/dist/serializer/index.js +9 -0
  142. package/dist/serializer/index.js.map +1 -0
  143. package/dist/store/IdentityMap.d.ts +53 -0
  144. package/dist/store/IdentityMap.d.ts.map +1 -0
  145. package/dist/store/RecordArray.d.ts +114 -0
  146. package/dist/store/RecordArray.d.ts.map +1 -0
  147. package/dist/store/Store.d.ts +395 -0
  148. package/dist/store/Store.d.ts.map +1 -0
  149. package/dist/store/index.cjs +2 -0
  150. package/dist/store/index.cjs.map +1 -0
  151. package/dist/store/index.d.ts +5 -0
  152. package/dist/store/index.d.ts.map +1 -0
  153. package/dist/store/index.js +8 -0
  154. package/dist/store/index.js.map +1 -0
  155. package/dist/transforms/Transform.d.ts +49 -0
  156. package/dist/transforms/Transform.d.ts.map +1 -0
  157. package/dist/transforms/boolean.d.ts +26 -0
  158. package/dist/transforms/boolean.d.ts.map +1 -0
  159. package/dist/transforms/date.d.ts +22 -0
  160. package/dist/transforms/date.d.ts.map +1 -0
  161. package/dist/transforms/index.cjs +2 -0
  162. package/dist/transforms/index.cjs.map +1 -0
  163. package/dist/transforms/index.d.ts +6 -0
  164. package/dist/transforms/index.d.ts.map +1 -0
  165. package/dist/transforms/index.js +9 -0
  166. package/dist/transforms/index.js.map +1 -0
  167. package/dist/transforms/number.d.ts +17 -0
  168. package/dist/transforms/number.d.ts.map +1 -0
  169. package/dist/transforms/string.d.ts +18 -0
  170. package/dist/transforms/string.d.ts.map +1 -0
  171. package/dist/types-C9NB2gRj.js +7 -0
  172. package/dist/types-C9NB2gRj.js.map +1 -0
  173. package/dist/types-uWOXMPWW.cjs +2 -0
  174. package/dist/types-uWOXMPWW.cjs.map +1 -0
  175. package/package.json +140 -0
  176. package/src/adapter/Adapter.ts +320 -0
  177. package/src/adapter/MemoryAdapter.ts +216 -0
  178. package/src/adapter/RestAdapter.ts +248 -0
  179. package/src/adapter/index.ts +7 -0
  180. package/src/index.ts +17 -0
  181. package/src/json-api/JsonApiAdapter.ts +93 -0
  182. package/src/json-api/JsonApiSerializer.ts +245 -0
  183. package/src/json-api/index.ts +2 -0
  184. package/src/model/Errors.ts +100 -0
  185. package/src/model/Model.ts +683 -0
  186. package/src/model/Snapshot.ts +162 -0
  187. package/src/model/StateMachine.ts +149 -0
  188. package/src/model/index.ts +20 -0
  189. package/src/model/relationships.ts +484 -0
  190. package/src/odata/ODataAdapter.ts +245 -0
  191. package/src/odata/index.ts +1 -0
  192. package/src/request/CacheHandler.ts +125 -0
  193. package/src/request/FetchHandler.ts +119 -0
  194. package/src/request/RequestManager.ts +112 -0
  195. package/src/request/index.ts +4 -0
  196. package/src/request/types.ts +139 -0
  197. package/src/schema/SchemaService.ts +161 -0
  198. package/src/schema/decorators.ts +162 -0
  199. package/src/schema/index.ts +3 -0
  200. package/src/schema/types.ts +66 -0
  201. package/src/serializer/EmbeddedRecordsMixin.ts +257 -0
  202. package/src/serializer/JsonSerializer.ts +173 -0
  203. package/src/serializer/RestSerializer.ts +138 -0
  204. package/src/serializer/Serializer.ts +397 -0
  205. package/src/serializer/index.ts +15 -0
  206. package/src/store/IdentityMap.ts +110 -0
  207. package/src/store/RecordArray.ts +210 -0
  208. package/src/store/Store.ts +1391 -0
  209. package/src/store/index.ts +11 -0
  210. package/src/transforms/Transform.ts +52 -0
  211. package/src/transforms/boolean.ts +57 -0
  212. package/src/transforms/date.ts +48 -0
  213. package/src/transforms/index.ts +5 -0
  214. package/src/transforms/number.ts +42 -0
  215. package/src/transforms/string.ts +35 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Julián Acosta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,366 @@
1
+ # mobx-data
2
+
3
+ [![build](https://img.shields.io/endpoint?url=https://iamjulianacosta.github.io/mobx-data/build-badge.json)](https://github.com/IAmJulianAcosta/mobx-data)
4
+ [![coverage](https://img.shields.io/endpoint?url=https://iamjulianacosta.github.io/mobx-data/coverage-badge.json)](https://github.com/IAmJulianAcosta/mobx-data)
5
+ [![version](https://img.shields.io/endpoint?url=https://iamjulianacosta.github.io/mobx-data/version-badge.json)](https://github.com/IAmJulianAcosta/mobx-data/releases)
6
+ [![license](https://img.shields.io/endpoint?url=https://iamjulianacosta.github.io/mobx-data/license-badge.json)](./LICENSE)
7
+
8
+ A feature-complete port of Ember Data to MobX — framework-agnostic, TypeScript-first, fully observable.
9
+
10
+ ---
11
+
12
+ ## Overview
13
+
14
+ `mobx-data` brings the battle-tested Ember Data mental model (Identity Map, pluggable adapters/serializers, record state machines, async relationships) to any MobX application. All state is observable; no manual invalidation or subscriptions are needed.
15
+
16
+ ### Design principles
17
+
18
+ - **Framework-agnostic** — works with React, Vue, Solid, or plain JS
19
+ - **MobX-native** — every piece of state is a MobX `observable`; computed props react automatically
20
+ - **TypeScript-first** — full generic types throughout
21
+ - **Ember Data parity** — near 1-to-1 API mapping so Ember Data users feel at home
22
+
23
+ ---
24
+
25
+ ## Features
26
+
27
+ | Feature | Status |
28
+ |---------|--------|
29
+ | `Store` with Identity Map | ✅ |
30
+ | `Model` base class + lifecycle hooks | ✅ |
31
+ | `@attr`, `@belongsTo`, `@hasMany` decorators | ✅ |
32
+ | Record state machine (`isNew`, `isDirty`, `isSaving`, …) | ✅ |
33
+ | Dirty tracking & `rollbackAttributes` | ✅ |
34
+ | `RecordArray` / `AdapterPopulatedRecordArray` | ✅ |
35
+ | `RestAdapter` | ✅ |
36
+ | `JsonApiAdapter` | ✅ |
37
+ | `ODataAdapter` (v4) | ✅ |
38
+ | `JsonSerializer` / `RestSerializer` / `JsonApiSerializer` | ✅ |
39
+ | `EmbeddedRecordsMixin` | ✅ |
40
+ | Async & sync `belongsTo` / `hasMany` | ✅ |
41
+ | Inverse relationship tracking | ✅ |
42
+ | `Snapshot` | ✅ |
43
+ | `RequestManager` + handler chain | ✅ |
44
+ | `FetchHandler` / `CacheHandler` | ✅ |
45
+ | Built-in transforms (`string`, `number`, `boolean`, `date`) | ✅ |
46
+ | `SchemaService` | ✅ |
47
+ | `Errors` (field-level validation) | ✅ |
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pnpm add mobx-data mobx reflect-metadata tsyringe
55
+ ```
56
+
57
+ Enable decorator metadata in your `tsconfig.json`:
58
+
59
+ ```json
60
+ {
61
+ "compilerOptions": {
62
+ "experimentalDecorators": true,
63
+ "emitDecoratorMetadata": true
64
+ }
65
+ }
66
+ ```
67
+
68
+ Import `reflect-metadata` once at your application entry point:
69
+
70
+ ```ts
71
+ import 'reflect-metadata';
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Quick start
77
+
78
+ ### 1. Define models
79
+
80
+ ```ts
81
+ import { Model, attr, belongsTo, hasMany } from 'mobx-data/model';
82
+
83
+ class User extends Model {
84
+ static modelName = 'user';
85
+
86
+ @attr('string') name!: string;
87
+ @attr('string') email!: string;
88
+
89
+ @hasMany('post', { async: false, inverse: 'author' })
90
+ posts!: ManyArray<Post>;
91
+ }
92
+
93
+ class Post extends Model {
94
+ static modelName = 'post';
95
+
96
+ @attr('string') title!: string;
97
+ @attr('date') publishedAt!: Date | null;
98
+
99
+ @belongsTo('user', { async: false, inverse: 'posts' })
100
+ author!: User | null;
101
+ }
102
+ ```
103
+
104
+ ### 2. Create a store
105
+
106
+ ```ts
107
+ import 'reflect-metadata';
108
+ import { container } from 'tsyringe';
109
+ import { Store } from 'mobx-data/store';
110
+ import { SchemaService } from 'mobx-data/schema';
111
+ import { RestAdapter } from 'mobx-data/adapter';
112
+ import { JsonSerializer } from 'mobx-data/serializer';
113
+
114
+ const schema = container.resolve(SchemaService);
115
+ schema.registerModel('user', User);
116
+ schema.registerModel('post', Post);
117
+
118
+ const store = container.resolve(Store);
119
+
120
+ const adapter = new RestAdapter();
121
+ adapter.host = 'https://api.example.com';
122
+
123
+ store.registerAdapter('application', adapter);
124
+ store.registerSerializer('application', new JsonSerializer());
125
+ ```
126
+
127
+ ### 3. Use the store
128
+
129
+ ```ts
130
+ // Find a single record
131
+ const user = await store.findRecord('user', '1');
132
+ console.log(user.name); // 'Alice'
133
+
134
+ // Find all
135
+ const users = await store.findAll('user');
136
+
137
+ // Query
138
+ const posts = await store.query('post', { filter: { published: true } });
139
+
140
+ // Create
141
+ const post = store.createRecord('post', { title: 'Hello World' });
142
+ await post.save();
143
+
144
+ // Update
145
+ user.name = 'Bob';
146
+ await user.save();
147
+
148
+ // Delete
149
+ await post.destroyRecord();
150
+
151
+ // Push raw data
152
+ store.push({
153
+ data: { type: 'user', id: '2', attributes: { name: 'Carol' } }
154
+ });
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Adapters
160
+
161
+ ### RestAdapter
162
+
163
+ Standard REST conventions (`GET /users/1`, `POST /users`, `PUT /users/1`, `DELETE /users/1`).
164
+
165
+ ```ts
166
+ const adapter = new RestAdapter();
167
+ adapter.host = 'https://api.example.com';
168
+ adapter.namespace = 'api/v2';
169
+ adapter.headers = { Authorization: 'Bearer …' };
170
+ ```
171
+
172
+ ### JsonApiAdapter
173
+
174
+ Implements the [JSON:API](https://jsonapi.org) specification, including `application/vnd.api+json` MIME type and PATCH for updates.
175
+
176
+ ```ts
177
+ const adapter = new JsonApiAdapter();
178
+ adapter.host = 'https://api.example.com';
179
+ ```
180
+
181
+ ### ODataAdapter
182
+
183
+ Implements OData v4 conventions (PascalCase entity sets, key-in-parentheses URLs, `$filter` / `$expand` / `$select` / `$top` / `$skip` / `$orderby` / `$count`).
184
+
185
+ ```ts
186
+ import { ODataAdapter } from 'mobx-data/odata';
187
+
188
+ const adapter = new ODataAdapter();
189
+ adapter.host = 'https://api.example.com/odata';
190
+
191
+ // $expand navigation properties
192
+ const posts = await adapter.query(store, 'post', {
193
+ $expand: 'author,comments',
194
+ $filter: "publishedAt gt '2026-01-01'",
195
+ $orderby: 'title asc',
196
+ });
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Serializers
202
+
203
+ | Class | Format |
204
+ |-------|--------|
205
+ | `JsonSerializer` | Flat JSON objects / arrays |
206
+ | `RestSerializer` | Root-key format with optional sideloading |
207
+ | `JsonApiSerializer` | [JSON:API](https://jsonapi.org) compound documents |
208
+
209
+ ### EmbeddedRecordsMixin
210
+
211
+ ```ts
212
+ import { RestSerializer, EmbeddedRecordsMixin } from 'mobx-data/serializer';
213
+
214
+ class PostSerializer extends EmbeddedRecordsMixin(RestSerializer) {
215
+ attrs = {
216
+ comments: { embedded: 'always' },
217
+ };
218
+ }
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Relationships
224
+
225
+ ```ts
226
+ // Sync (records must already be in the store)
227
+ @belongsTo('user', { async: false }) author!: User | null;
228
+ @hasMany('tag', { async: false }) tags!: ManyArray<Tag>;
229
+
230
+ // Async (loaded on demand)
231
+ @belongsTo('user', { async: true }) author!: AsyncBelongsTo<User>;
232
+ @hasMany('comment', { async: true }) comments!: AsyncHasMany<Comment>;
233
+ ```
234
+
235
+ Async wrappers are `PromiseLike` — they can be `await`ed or used in MobX reactions:
236
+
237
+ ```ts
238
+ const author = await post.author; // AsyncBelongsTo<User>
239
+ console.log(post.author.isLoaded); // true
240
+ ```
241
+
242
+ Inverse tracking is automatic when `inverse` is specified:
243
+
244
+ ```ts
245
+ @belongsTo('user', { async: false, inverse: 'posts' }) author!: User | null;
246
+ // Setting post.author automatically adds post to user.posts
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Transforms
252
+
253
+ ```ts
254
+ @attr('string') name!: string;
255
+ @attr('number') age!: number;
256
+ @attr('boolean') active!: boolean;
257
+ @attr('date') createdAt!: Date | null;
258
+ @attr() raw!: unknown; // pass-through
259
+ ```
260
+
261
+ ---
262
+
263
+ ## Request pipeline
264
+
265
+ The `RequestManager` provides a middleware-style handler chain:
266
+
267
+ ```ts
268
+ import { RequestManager, FetchHandler, CacheHandler } from 'mobx-data/request';
269
+
270
+ const manager = new RequestManager()
271
+ .useCache(new CacheHandler())
272
+ .use(new FetchHandler());
273
+ ```
274
+
275
+ Implement custom handlers for authentication, logging, retry logic, etc.:
276
+
277
+ ```ts
278
+ class AuthHandler {
279
+ async request(context, next) {
280
+ context.request.headers = {
281
+ ...context.request.headers,
282
+ Authorization: `Bearer ${getToken()}`,
283
+ };
284
+ return next(context.request);
285
+ }
286
+ }
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Record state
292
+
293
+ Every record exposes observable boolean flags derived from its internal state machine:
294
+
295
+ | Property | Description |
296
+ |----------|-------------|
297
+ | `isNew` | Created locally, never saved |
298
+ | `isDirty` | Has unsaved local changes |
299
+ | `isSaving` | Adapter request in flight |
300
+ | `isLoading` | Being fetched from server |
301
+ | `isLoaded` | Loaded and available |
302
+ | `isDeleted` | Marked for deletion |
303
+ | `isError` | Last operation failed |
304
+ | `isValid` | No validation errors |
305
+
306
+ ```ts
307
+ const post = store.createRecord('post', { title: 'Draft' });
308
+ post.isNew; // true
309
+ post.isDirty; // true
310
+
311
+ await post.save();
312
+ post.isNew; // false
313
+ post.isDirty; // false
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Testing
319
+
320
+ The project includes a full end-to-end test suite against an in-process OData server built with [`simple-odata-server`](https://github.com/pofider/simple-odata-server).
321
+
322
+ ```bash
323
+ pnpm test # run all tests once
324
+ pnpm test:watch # watch mode
325
+ pnpm typecheck # TypeScript type check
326
+ ```
327
+
328
+ To run the test OData server manually:
329
+
330
+ ```bash
331
+ bun tests/e2e/fixtures/run-bun.ts
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Project structure
337
+
338
+ ```
339
+ src/
340
+ adapter/ — Adapter, RestAdapter
341
+ json-api/ — JsonApiAdapter, JsonApiSerializer
342
+ model/ — Model, StateMachine, Errors, Snapshot, relationships
343
+ odata/ — ODataAdapter
344
+ request/ — RequestManager, FetchHandler, CacheHandler, types
345
+ schema/ — SchemaService, decorators (@attr, @belongsTo, @hasMany), types
346
+ serializer/ — Serializer, JsonSerializer, RestSerializer, EmbeddedRecordsMixin
347
+ store/ — Store, IdentityMap, RecordArray
348
+ transforms/ — Transform, BooleanTransform, DateTransform, NumberTransform, StringTransform
349
+ tests/
350
+ e2e/ — end-to-end tests against a live OData server
351
+ fixtures/ — in-process OData server, seed data, filter engine
352
+ adapter/ — adapter unit tests
353
+ model/ — model unit tests
354
+ (…)
355
+ ```
356
+
357
+ ---
358
+
359
+ ## License
360
+
361
+ MIT
362
+
363
+ ---
364
+
365
+ > **Note:** This project is developed internally and published as open source.
366
+ > Bug reports and issues are welcome.
@@ -0,0 +1,208 @@
1
+ import { injectable as u } from "tsyringe";
2
+ var p = Object.getOwnPropertyDescriptor, f = (e, r, t, s) => {
3
+ for (var n = s > 1 ? void 0 : s ? p(r, t) : r, a = e.length - 1, c; a >= 0; a--)
4
+ (c = e[a]) && (n = c(n) || n);
5
+ return n;
6
+ };
7
+ let d = class {
8
+ constructor() {
9
+ this._handlers = [], this._cache = null;
10
+ }
11
+ /**
12
+ * Appends one or more handlers to the pipeline.
13
+ * Returns `this` for chaining.
14
+ */
15
+ use(e) {
16
+ return Array.isArray(e) ? this._handlers.push(...e) : this._handlers.push(e), this;
17
+ }
18
+ /**
19
+ * Registers the cache handler. It is always inserted at the front of
20
+ * the chain so it can intercept requests before any other handler sees them.
21
+ * Returns `this` for chaining.
22
+ */
23
+ useCache(e) {
24
+ return this._cache = e, this;
25
+ }
26
+ /** All non-cache handlers in registration order. */
27
+ get handlers() {
28
+ return this._handlers;
29
+ }
30
+ /** The registered cache handler, or `null`. */
31
+ get cacheHandler() {
32
+ return this._cache;
33
+ }
34
+ /**
35
+ * Executes `request` through the full handler chain and returns the final
36
+ * `StoreResponse`.
37
+ *
38
+ * @throws when no handlers have been registered.
39
+ * @throws when the chain ends without any handler returning a response
40
+ * (i.e. no terminal handler such as `FetchHandler`).
41
+ */
42
+ async request(e) {
43
+ const r = this._cache ? [this._cache, ...this._handlers] : [...this._handlers];
44
+ if (r.length === 0)
45
+ throw new Error(
46
+ "RequestManager has no handlers registered — cannot complete request"
47
+ );
48
+ let t = 0;
49
+ const s = async (n) => {
50
+ const a = r[t];
51
+ if (!a)
52
+ throw new Error(
53
+ "Handler chain ended without producing a response (no terminal handler)"
54
+ );
55
+ t++;
56
+ const c = {
57
+ request: n,
58
+ response: void 0,
59
+ setResponse(l) {
60
+ this.response = l;
61
+ }
62
+ }, i = (l) => s(l);
63
+ return a.request(c, i);
64
+ };
65
+ return s(e);
66
+ }
67
+ };
68
+ d = f([
69
+ u()
70
+ ], d);
71
+ var y = Object.getOwnPropertyDescriptor, _ = (e, r, t, s) => {
72
+ for (var n = s > 1 ? void 0 : s ? y(r, t) : r, a = e.length - 1, c; a >= 0; a--)
73
+ (c = e[a]) && (n = c(n) || n);
74
+ return n;
75
+ };
76
+ class v extends Error {
77
+ constructor(r, t, s, n) {
78
+ super(n ?? `Request failed with status ${r}`), this.status = r, this.content = t, this.headers = s;
79
+ }
80
+ }
81
+ let h = class {
82
+ static headersToObject(e) {
83
+ const r = {};
84
+ return e.forEach((t, s) => {
85
+ r[s] = t;
86
+ }), r;
87
+ }
88
+ static async parseBody(e) {
89
+ const r = e.headers.get("Content-Type") ?? "";
90
+ if (e.status === 204 || e.headers.get("Content-Length") === "0")
91
+ return null;
92
+ if (r.includes("application/json") || r.includes("application/vnd.api+json")) {
93
+ const s = await e.text();
94
+ if (!s)
95
+ return null;
96
+ try {
97
+ return JSON.parse(s);
98
+ } catch {
99
+ return s;
100
+ }
101
+ }
102
+ return e.text();
103
+ }
104
+ /**
105
+ * Issues the HTTP request and returns a `StoreResponse`.
106
+ * This is a terminal handler — it never calls `next`.
107
+ *
108
+ * @throws `FetchError` on non-2xx responses.
109
+ */
110
+ async request(e, r) {
111
+ const t = e.request, s = {
112
+ method: t.method,
113
+ headers: t.headers
114
+ };
115
+ t.body !== void 0 && t.body !== null && (s.body = t.body), t.signal && (s.signal = t.signal);
116
+ const n = await fetch(t.url, s), a = await h.parseBody(n), c = h.headersToObject(n.headers);
117
+ if (!n.ok)
118
+ throw new v(n.status, a, c);
119
+ return {
120
+ content: a,
121
+ status: n.status,
122
+ headers: c,
123
+ request: t
124
+ };
125
+ }
126
+ };
127
+ h = _([
128
+ u()
129
+ ], h);
130
+ var w = Object.getOwnPropertyDescriptor, g = (e, r, t, s) => {
131
+ for (var n = s > 1 ? void 0 : s ? w(r, t) : r, a = e.length - 1, c; a >= 0; a--)
132
+ (c = e[a]) && (n = c(n) || n);
133
+ return n;
134
+ };
135
+ let o = class {
136
+ constructor() {
137
+ this.cache = /* @__PURE__ */ new Map(), this.maxSize = 256, this.ttl = 3e5;
138
+ }
139
+ isExpired(e) {
140
+ return Date.now() - e.cachedAt > this.ttl;
141
+ }
142
+ evictLRU() {
143
+ for (; this.cache.size > this.maxSize; ) {
144
+ const e = this.cache.keys().next().value;
145
+ if (e !== void 0)
146
+ this.cache.delete(e);
147
+ else
148
+ break;
149
+ }
150
+ }
151
+ static keyFor(e) {
152
+ var r;
153
+ return ((r = e.cacheOptions) == null ? void 0 : r.key) ?? `${e.method} ${e.url}`;
154
+ }
155
+ static isCacheable(e) {
156
+ return e.method === "GET";
157
+ }
158
+ /**
159
+ * Handles a request by checking the in-memory cache before forwarding to
160
+ * the next handler.
161
+ *
162
+ * - Non-GET requests skip the cache entirely.
163
+ * - `cacheOptions.reload: true` forces a network request and refreshes the entry.
164
+ * - Cached entries expire after `ttl` milliseconds (default 5 minutes).
165
+ * - The cache uses LRU eviction when it exceeds `maxSize` entries (default 256).
166
+ */
167
+ async request(e, r) {
168
+ var c;
169
+ const t = e.request;
170
+ if (!o.isCacheable(t))
171
+ return r(t);
172
+ const s = o.keyFor(t);
173
+ if (!(((c = t.cacheOptions) == null ? void 0 : c.reload) === !0)) {
174
+ const i = this.cache.get(s);
175
+ if (i)
176
+ if (this.isExpired(i))
177
+ this.cache.delete(s);
178
+ else
179
+ return this.cache.delete(s), this.cache.set(s, i), i.response;
180
+ }
181
+ const a = await r(t);
182
+ return this.cache.set(s, { response: a, cachedAt: Date.now() }), this.evictLRU(), a;
183
+ }
184
+ /** Returns the number of entries currently in the cache. */
185
+ get size() {
186
+ return this.cache.size;
187
+ }
188
+ /** Removes all entries from the cache. */
189
+ clear() {
190
+ this.cache.clear();
191
+ }
192
+ /**
193
+ * Removes a single entry by key.
194
+ * @returns `true` when the entry existed and was deleted.
195
+ */
196
+ delete(e) {
197
+ return this.cache.delete(e);
198
+ }
199
+ };
200
+ o = g([
201
+ u()
202
+ ], o);
203
+ export {
204
+ o as C,
205
+ h as F,
206
+ d as R
207
+ };
208
+ //# sourceMappingURL=CacheHandler-BTU_rYkv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CacheHandler-BTU_rYkv.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","/**\n * Terminal request handler that executes HTTP calls via the browser / Node\n * `fetch` API.\n *\n * `FetchHandler` is designed to sit at the end of the `RequestManager` chain.\n * It does not call `next` — it issues the network request and returns the\n * parsed response directly.\n *\n * Response body parsing:\n * - `204 No Content` or `Content-Length: 0` → `null`\n * - `application/json` or `application/vnd.api+json` → parsed JSON (falls\n * back to raw text if parsing fails)\n * - Everything else → raw text string\n *\n * Error handling:\n * - Non-2xx responses throw a `FetchError` that includes `status`, `content`,\n * and `headers` for downstream error handling.\n */\n\nimport { injectable } from 'tsyringe';\nimport type {\n Handler,\n NextFn,\n RequestContext,\n StoreResponse,\n} from './types.js';\n\n/**\n * Error thrown by `FetchHandler` for non-2xx HTTP responses.\n * Carries the status code, parsed body content, and response headers.\n */\nexport class FetchError extends Error {\n readonly status: number;\n readonly content: unknown;\n readonly headers: Record<string, string>;\n constructor(\n status: number,\n content: unknown,\n headers: Record<string, string>,\n message?: string,\n ) {\n super(message ?? `Request failed with status ${status}`);\n this.status = status;\n this.content = content;\n this.headers = headers;\n }\n}\n\n@injectable()\nexport class FetchHandler implements Handler {\n static headersToObject(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n }\n\n static async parseBody(response: Response): Promise<unknown> {\n const contentType = response.headers.get('Content-Type') ?? '';\n if (\n response.status === 204\n || response.headers.get('Content-Length') === '0'\n ) {\n return null;\n }\n const isJson = contentType.includes('application/json')\n || contentType.includes('application/vnd.api+json');\n if (isJson) {\n const text = await response.text();\n if (!text) {\n return null;\n }\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n return response.text();\n }\n\n /**\n * Issues the HTTP request and returns a `StoreResponse`.\n * This is a terminal handler — it never calls `next`.\n *\n * @throws `FetchError` on non-2xx responses.\n */\n async request<T = unknown>(\n context: RequestContext<T>,\n _next: NextFn<T>,\n ): Promise<StoreResponse<T>> {\n const req = context.request;\n const init: RequestInit = {\n method: req.method,\n headers: req.headers,\n };\n if (req.body !== undefined && req.body !== null) {\n init.body = req.body;\n }\n if (req.signal) {\n init.signal = req.signal;\n }\n\n const response = await fetch(req.url, init);\n const content = await FetchHandler.parseBody(response);\n const headers = FetchHandler.headersToObject(response.headers);\n\n if (!response.ok) {\n throw new FetchError(response.status, content, headers);\n }\n return {\n content: content as T,\n status: response.status,\n headers,\n request: req,\n };\n }\n}\n","/**\n * In-memory caching handler for the request pipeline.\n *\n * `CacheHandler` is registered via `RequestManager.useCache()` so it always\n * runs first in the chain. It caches `GET` responses and replays them on\n * subsequent requests with the same key — unless `cacheOptions.reload: true`\n * is set, in which case it bypasses the cache and stores the fresh response.\n *\n * Cache key: `cacheOptions.key` when provided, otherwise `\"<METHOD> <URL>\"`.\n *\n * Only `GET` requests are cached; mutations (POST, PUT, PATCH, DELETE) are\n * always forwarded to `next` without touching the cache.\n *\n * Usage:\n * ```ts\n * const manager = new RequestManager()\n * .useCache(new CacheHandler())\n * .use(new FetchHandler());\n * ```\n */\n\nimport { injectable } from 'tsyringe';\nimport type {\n Handler,\n NextFn,\n RequestContext,\n StoreRequest,\n StoreResponse,\n} from './types.js';\n\ninterface CacheEntry {\n response: StoreResponse;\n cachedAt: number;\n}\n\n@injectable()\nexport class CacheHandler implements Handler {\n private cache = new Map<string, CacheEntry>();\n\n maxSize: number = 256;\n\n ttl: number = 300_000;\n\n private isExpired(entry: CacheEntry): boolean {\n return Date.now() - entry.cachedAt > this.ttl;\n }\n\n private evictLRU(): void {\n while (this.cache.size > this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n if (firstKey !== undefined) {\n this.cache.delete(firstKey);\n } else {\n break;\n }\n }\n }\n\n static keyFor(req: StoreRequest): string {\n return req.cacheOptions?.key ?? `${req.method} ${req.url}`;\n }\n\n static isCacheable(req: StoreRequest): boolean {\n return req.method === 'GET';\n }\n\n /**\n * Handles a request by checking the in-memory cache before forwarding to\n * the next handler.\n *\n * - Non-GET requests skip the cache entirely.\n * - `cacheOptions.reload: true` forces a network request and refreshes the entry.\n * - Cached entries expire after `ttl` milliseconds (default 5 minutes).\n * - The cache uses LRU eviction when it exceeds `maxSize` entries (default 256).\n */\n async request<T = unknown>(\n context: RequestContext<T>,\n next: NextFn<T>,\n ): Promise<StoreResponse<T>> {\n const req = context.request;\n if (!CacheHandler.isCacheable(req)) {\n return next(req);\n }\n\n const key = CacheHandler.keyFor(req);\n const reload = req.cacheOptions?.reload === true;\n\n if (!reload) {\n const hit = this.cache.get(key);\n if (hit) {\n if (this.isExpired(hit)) {\n this.cache.delete(key);\n } else {\n // LRU promotion: delete and re-set to move to end\n this.cache.delete(key);\n this.cache.set(key, hit);\n return hit.response as StoreResponse<T>;\n }\n }\n }\n\n const response = await next(req);\n this.cache.set(key, { response: response as StoreResponse, cachedAt: Date.now() });\n this.evictLRU();\n return response;\n }\n\n /** Returns the number of entries currently in the cache. */\n get size(): number {\n return this.cache.size;\n }\n\n /** Removes all entries from the cache. */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Removes a single entry by key.\n * @returns `true` when the entry existed and was deleted.\n */\n delete(key: string): boolean {\n return this.cache.delete(key);\n }\n}\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,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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CacheHandler-CXgY9IJo.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","/**\n * Terminal request handler that executes HTTP calls via the browser / Node\n * `fetch` API.\n *\n * `FetchHandler` is designed to sit at the end of the `RequestManager` chain.\n * It does not call `next` — it issues the network request and returns the\n * parsed response directly.\n *\n * Response body parsing:\n * - `204 No Content` or `Content-Length: 0` → `null`\n * - `application/json` or `application/vnd.api+json` → parsed JSON (falls\n * back to raw text if parsing fails)\n * - Everything else → raw text string\n *\n * Error handling:\n * - Non-2xx responses throw a `FetchError` that includes `status`, `content`,\n * and `headers` for downstream error handling.\n */\n\nimport { injectable } from 'tsyringe';\nimport type {\n Handler,\n NextFn,\n RequestContext,\n StoreResponse,\n} from './types.js';\n\n/**\n * Error thrown by `FetchHandler` for non-2xx HTTP responses.\n * Carries the status code, parsed body content, and response headers.\n */\nexport class FetchError extends Error {\n readonly status: number;\n readonly content: unknown;\n readonly headers: Record<string, string>;\n constructor(\n status: number,\n content: unknown,\n headers: Record<string, string>,\n message?: string,\n ) {\n super(message ?? `Request failed with status ${status}`);\n this.status = status;\n this.content = content;\n this.headers = headers;\n }\n}\n\n@injectable()\nexport class FetchHandler implements Handler {\n static headersToObject(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n }\n\n static async parseBody(response: Response): Promise<unknown> {\n const contentType = response.headers.get('Content-Type') ?? '';\n if (\n response.status === 204\n || response.headers.get('Content-Length') === '0'\n ) {\n return null;\n }\n const isJson = contentType.includes('application/json')\n || contentType.includes('application/vnd.api+json');\n if (isJson) {\n const text = await response.text();\n if (!text) {\n return null;\n }\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n return response.text();\n }\n\n /**\n * Issues the HTTP request and returns a `StoreResponse`.\n * This is a terminal handler — it never calls `next`.\n *\n * @throws `FetchError` on non-2xx responses.\n */\n async request<T = unknown>(\n context: RequestContext<T>,\n _next: NextFn<T>,\n ): Promise<StoreResponse<T>> {\n const req = context.request;\n const init: RequestInit = {\n method: req.method,\n headers: req.headers,\n };\n if (req.body !== undefined && req.body !== null) {\n init.body = req.body;\n }\n if (req.signal) {\n init.signal = req.signal;\n }\n\n const response = await fetch(req.url, init);\n const content = await FetchHandler.parseBody(response);\n const headers = FetchHandler.headersToObject(response.headers);\n\n if (!response.ok) {\n throw new FetchError(response.status, content, headers);\n }\n return {\n content: content as T,\n status: response.status,\n headers,\n request: req,\n };\n }\n}\n","/**\n * In-memory caching handler for the request pipeline.\n *\n * `CacheHandler` is registered via `RequestManager.useCache()` so it always\n * runs first in the chain. It caches `GET` responses and replays them on\n * subsequent requests with the same key — unless `cacheOptions.reload: true`\n * is set, in which case it bypasses the cache and stores the fresh response.\n *\n * Cache key: `cacheOptions.key` when provided, otherwise `\"<METHOD> <URL>\"`.\n *\n * Only `GET` requests are cached; mutations (POST, PUT, PATCH, DELETE) are\n * always forwarded to `next` without touching the cache.\n *\n * Usage:\n * ```ts\n * const manager = new RequestManager()\n * .useCache(new CacheHandler())\n * .use(new FetchHandler());\n * ```\n */\n\nimport { injectable } from 'tsyringe';\nimport type {\n Handler,\n NextFn,\n RequestContext,\n StoreRequest,\n StoreResponse,\n} from './types.js';\n\ninterface CacheEntry {\n response: StoreResponse;\n cachedAt: number;\n}\n\n@injectable()\nexport class CacheHandler implements Handler {\n private cache = new Map<string, CacheEntry>();\n\n maxSize: number = 256;\n\n ttl: number = 300_000;\n\n private isExpired(entry: CacheEntry): boolean {\n return Date.now() - entry.cachedAt > this.ttl;\n }\n\n private evictLRU(): void {\n while (this.cache.size > this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n if (firstKey !== undefined) {\n this.cache.delete(firstKey);\n } else {\n break;\n }\n }\n }\n\n static keyFor(req: StoreRequest): string {\n return req.cacheOptions?.key ?? `${req.method} ${req.url}`;\n }\n\n static isCacheable(req: StoreRequest): boolean {\n return req.method === 'GET';\n }\n\n /**\n * Handles a request by checking the in-memory cache before forwarding to\n * the next handler.\n *\n * - Non-GET requests skip the cache entirely.\n * - `cacheOptions.reload: true` forces a network request and refreshes the entry.\n * - Cached entries expire after `ttl` milliseconds (default 5 minutes).\n * - The cache uses LRU eviction when it exceeds `maxSize` entries (default 256).\n */\n async request<T = unknown>(\n context: RequestContext<T>,\n next: NextFn<T>,\n ): Promise<StoreResponse<T>> {\n const req = context.request;\n if (!CacheHandler.isCacheable(req)) {\n return next(req);\n }\n\n const key = CacheHandler.keyFor(req);\n const reload = req.cacheOptions?.reload === true;\n\n if (!reload) {\n const hit = this.cache.get(key);\n if (hit) {\n if (this.isExpired(hit)) {\n this.cache.delete(key);\n } else {\n // LRU promotion: delete and re-set to move to end\n this.cache.delete(key);\n this.cache.set(key, hit);\n return hit.response as StoreResponse<T>;\n }\n }\n }\n\n const response = await next(req);\n this.cache.set(key, { response: response as StoreResponse, cachedAt: Date.now() });\n this.evictLRU();\n return response;\n }\n\n /** Returns the number of entries currently in the cache. */\n get size(): number {\n return this.cache.size;\n }\n\n /** Removes all entries from the cache. */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Removes a single entry by key.\n * @returns `true` when the entry existed and was deleted.\n */\n delete(key: string): boolean {\n return this.cache.delete(key);\n }\n}\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,2 @@
1
+ "use strict";const A=require("tsyringe"),R=require("pluralize"),g=require("./Serializer-95gi5edy.cjs");var _=Object.getOwnPropertyDescriptor,v=(u,t,s,r)=>{for(var n=r>1?void 0:r?_(t,s):t,e=u.length-1,i;e>=0;e--)(i=u[e])&&(n=i(n)||n);return n};exports.JsonSerializer=class extends g.Serializer{static dispatchMethodName(t){switch(t){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(t,s,r,n){if(r==null||typeof r!="object")return null;const e=r,i=this.extractId(s,e),o=this.extractAttributes(s,e),a=this.extractRelationships(s,e),d={type:s.modelName,id:i,attributes:o};return a&&Object.keys(a).length>0&&(d.relationships=a),d}normalizeResponse(t,s,r,n,e){const i=exports.JsonSerializer.dispatchMethodName(e);if(i){const o=this[i],a=g.Serializer.prototype[i];if(typeof o=="function"&&o!==a)return o.call(this,t,s,r,n,e)}return this._buildDocument(t,s,r,n,e)}_buildDocument(t,s,r,n,e){return r==null?{data:null}:Array.isArray(r)?{data:r.map(a=>this.normalize(t,s,a)).filter(a=>a!==null)}:{data:this.normalize(t,s,r)}}serialize(t,s){const r={};return s!=null&&s.includeId&&t.id!==null&&(r[this.primaryKey]=t.id),t.eachAttribute((n,e)=>{this.serializeAttribute(t,r,n,e)}),t.eachRelationship((n,e)=>{e.kind==="belongsTo"?this.serializeBelongsTo(t,r,e):this.serializeHasMany(t,r,e)}),r}};exports.JsonSerializer=v([A.injectable()],exports.JsonSerializer);var M=Object.getOwnPropertyDescriptor,w=(u,t,s,r)=>{for(var n=r>1?void 0:r?M(t,s):t,e=u.length-1,i;e>=0;e--)(i=u[e])&&(n=i(n)||n);return n};exports.RestSerializer=class extends exports.JsonSerializer{payloadKeyFromModelName(t){return R.plural(t)}modelNameFromPayloadKey(t){return R.singular(t)}_buildDocument(t,s,r,n,e){if(r==null||typeof r!="object")return{data:null};const i=r,o=s.modelName,a=this.payloadKeyFromModelName(o);let d=null;const f=new Set;if(o in i){f.add(o);const c=i[o];Array.isArray(c)?d=c.map(l=>this.normalize(t,s,l)).filter(l=>l!==null):d=this.normalize(t,s,c)}else if(a in i){f.add(a);const c=i[a];Array.isArray(c)?d=c.map(l=>this.normalize(t,s,l)).filter(l=>l!==null):d=this.normalize(t,s,c)}const m=[];for(const[c,l]of Object.entries(i)){if(f.has(c)||c==="meta"||c==="links")continue;const z={modelName:this.modelNameFromPayloadKey(c),attributes:new Map,relationships:new Map};if(Array.isArray(l))for(const p of l){const b=this.normalize(t,z,p);b&&m.push(b)}else if(l&&typeof l=="object"){const p=this.normalize(t,z,l);p&&m.push(p)}}const h={data:d};return m.length>0&&(h.included=m),i.meta&&typeof i.meta=="object"&&(h.meta=i.meta),i.links&&typeof i.links=="object"&&(h.links=i.links),h}};exports.RestSerializer=w([A.injectable()],exports.RestSerializer);function S(u){class t extends u{constructor(){super(...arguments),this.pendingIncluded=[]}normalize(r,n,e,i){if(!e||typeof e!="object"||Array.isArray(e))return super.normalize(r,n,e,i);const o=e,a=this.attrs??{},d={...o};for(const[f,m]of n.relationships){const h=a[f];if(!h||h.embedded!=="always")continue;const c=d[f];if(c!=null){if(m.kind==="hasMany"&&Array.isArray(c)){const l=[];for(const y of c){const z=this.extractEmbeddedResource(r,m,y);z&&z.id!==null&&(l.push(z.id),this.pendingIncluded.push(z))}d[f]=l}else if(m.kind==="belongsTo"&&typeof c=="object"&&!Array.isArray(c)){const l=this.extractEmbeddedResource(r,m,c);l&&l.id!==null&&(this.pendingIncluded.push(l),d[f]=l.id)}}}return super.normalize(r,n,d,i)}normalizeResponse(r,n,e,i,o){this.pendingIncluded=[];const a=super.normalizeResponse(r,n,e,i,o);if(this.pendingIncluded.length>0){const d=a.included?[...a.included]:[];d.push(...this.pendingIncluded),a.included=d,this.pendingIncluded=[]}return a}serializeHasMany(r,n,e){var o;const i=(o=this.attrs)==null?void 0:o[e.name];if((i==null?void 0:i.serialize)==="records"){const a=r.hasMany(e.name);n[this.keyForRelationship(e.name)]=a??[];return}super.serializeHasMany(r,n,e)}serializeBelongsTo(r,n,e){var o;const i=(o=this.attrs)==null?void 0:o[e.name];if((i==null?void 0:i.serialize)==="records"){const a=r.belongsTo(e.name);n[this.keyForRelationship(e.name)]=a??null;return}super.serializeBelongsTo(r,n,e)}extractEmbeddedResource(r,n,e){if(!e||typeof e!="object"||Array.isArray(e))return null;const i={modelName:n.type,attributes:new Map,relationships:new Map};return this.normalize(r,i,e)}}return t}exports.EmbeddedRecordsMixin=S;
2
+ //# sourceMappingURL=EmbeddedRecordsMixin-CBvqNdgC.cjs.map