@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
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Shared type definitions for the request pipeline.
3
+ *
4
+ * The request pipeline models each network call as a `StoreRequest` flowing
5
+ * through a chain of `Handler` instances. Each handler can inspect, mutate,
6
+ * or short-circuit the request before passing it to `next`.
7
+ *
8
+ * Flow:
9
+ * ```
10
+ * RequestManager.request(StoreRequest)
11
+ * → CacheHandler.request(context, next)
12
+ * → FetchHandler.request(context, next) ← terminal
13
+ * ```
14
+ */
15
+
16
+ /** HTTP methods supported by the request pipeline. */
17
+ export type HttpMethod =
18
+ | 'GET'
19
+ | 'POST'
20
+ | 'PATCH'
21
+ | 'PUT'
22
+ | 'DELETE'
23
+ | 'HEAD'
24
+ | 'OPTIONS';
25
+
26
+ /** Store-level operation that triggered this request. */
27
+ export type RequestOp =
28
+ | 'findRecord'
29
+ | 'findAll'
30
+ | 'findMany'
31
+ | 'query'
32
+ | 'queryRecord'
33
+ | 'createRecord'
34
+ | 'updateRecord'
35
+ | 'deleteRecord'
36
+ | 'custom';
37
+
38
+ /** Stable identifier for a record — type + id pair. */
39
+ export interface RecordIdentifier {
40
+ /** Model name. */
41
+ type: string;
42
+ /** Server-assigned id, or `null` for new records. */
43
+ id: string | null;
44
+ /** Optional local id for identity tracking before the server assigns an id. */
45
+ lid?: string;
46
+ }
47
+
48
+ /**
49
+ * A fully-described outgoing HTTP request.
50
+ *
51
+ * Passed to the first handler in the chain and forwarded (possibly mutated)
52
+ * to subsequent handlers via `next`.
53
+ */
54
+ export interface StoreRequest {
55
+ /** HTTP method. */
56
+ method: HttpMethod;
57
+ /** Fully-qualified URL. */
58
+ url: string;
59
+ /** HTTP headers. */
60
+ headers?: Record<string, string>;
61
+ /** Request body. `null` / `undefined` = no body. */
62
+ body?: string | FormData | Blob | ArrayBuffer | null;
63
+ /** Store operation that initiated this request. */
64
+ op?: RequestOp;
65
+ /** Records involved in this request (for cache invalidation, etc.). */
66
+ records?: RecordIdentifier[];
67
+ /** Store reference — passed through so handlers can access it if needed. */
68
+ store?: unknown;
69
+ /** Cache behavior overrides. */
70
+ cacheOptions?: {
71
+ /** When `true`, bypass the cache and always hit the network. */
72
+ reload?: boolean;
73
+ /** When `true`, return the cached value and refetch in the background. */
74
+ backgroundReload?: boolean;
75
+ /** Custom cache key. Defaults to `"${method} ${url}"`. */
76
+ key?: string;
77
+ };
78
+ /** `AbortSignal` for request cancellation. */
79
+ signal?: AbortSignal;
80
+ /** Arbitrary additional metadata forwarded to handlers. */
81
+ [key: string]: unknown;
82
+ }
83
+
84
+ /** The response produced by the terminal handler and returned up the chain. */
85
+ export interface StoreResponse<T = unknown> {
86
+ /** Parsed response body. */
87
+ content: T;
88
+ /** HTTP status code. */
89
+ status?: number;
90
+ /** Response headers. */
91
+ headers?: Record<string, string>;
92
+ /** The originating request. */
93
+ request?: StoreRequest;
94
+ }
95
+
96
+ /**
97
+ * Context object passed to each handler.
98
+ * Handlers read `request`, may call `setResponse` to stash an intermediate
99
+ * result, and call `next` to forward to the next handler.
100
+ */
101
+ export interface RequestContext<T = unknown> {
102
+ /** The current request (may have been mutated by earlier handlers). */
103
+ request: StoreRequest;
104
+ /** Response set by a previous handler, if any. */
105
+ response?: StoreResponse<T>;
106
+ /** Allows a handler to record the response without ending the chain. */
107
+ setResponse(response: StoreResponse<T>): void;
108
+ }
109
+
110
+ /**
111
+ * Function passed to each handler that invokes the next handler in the chain.
112
+ * Handlers call `next(request)` to continue, or return a response directly to
113
+ * short-circuit remaining handlers.
114
+ */
115
+ export type NextFn<T = unknown> = (
116
+ request: StoreRequest,
117
+ ) => Promise<StoreResponse<T>>;
118
+
119
+ /**
120
+ * Interface that every request handler must implement.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * class LoggingHandler implements Handler {
125
+ * async request(context, next) {
126
+ * console.log('→', context.request.method, context.request.url);
127
+ * const response = await next(context.request);
128
+ * console.log('←', response.status);
129
+ * return response;
130
+ * }
131
+ * }
132
+ * ```
133
+ */
134
+ export interface Handler<T = unknown> {
135
+ request(
136
+ context: RequestContext<T>,
137
+ next: NextFn<T>,
138
+ ): Promise<StoreResponse<T>> | StoreResponse<T>;
139
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Central registry for model classes and their schema metadata.
3
+ *
4
+ * `SchemaService` is a tsyringe singleton that acts as the authoritative
5
+ * source of truth for every model registered with the store. At registration
6
+ * time it walks the full prototype chain (deepest ancestor first) and merges
7
+ * all `@attr` / `@belongsTo` / `@hasMany` definitions so subclasses
8
+ * transparently inherit parent schema.
9
+ *
10
+ * Consumers (Store, serializers, snapshot helpers) call `attributesDefinitionFor`
11
+ * and `relationshipsDefinitionFor` rather than reading reflect-metadata directly.
12
+ */
13
+
14
+ import 'reflect-metadata';
15
+ import { singleton } from 'tsyringe';
16
+ import {
17
+ ATTRIBUTES_META_KEY,
18
+ RELATIONSHIPS_META_KEY,
19
+ type AttributeDef,
20
+ type AttributeDefinitionsMap,
21
+ type RelationshipDef,
22
+ type RelationshipDefinitionsMap,
23
+ } from './types.js';
24
+
25
+ /** Minimal shape of a model constructor that SchemaService can register. */
26
+ export interface ModelClass {
27
+ modelName?: string;
28
+ prototype: unknown;
29
+ new (...args: never[]): unknown;
30
+ }
31
+
32
+ /** Internal entry stored per registered model name. */
33
+ interface Entry {
34
+ modelClass: ModelClass;
35
+ /** Merged attribute definitions (ancestors → leaf, leaf wins). */
36
+ attributes: AttributeDefinitionsMap;
37
+ /** Merged relationship definitions (ancestors → leaf, leaf wins). */
38
+ relationships: RelationshipDefinitionsMap;
39
+ }
40
+
41
+ /**
42
+ * Walks the prototype chain from the class root down to the leaf, collecting
43
+ * own-metadata from each level and merging into a single Map. Later entries
44
+ * (i.e. the subclass) override earlier ones so subclass declarations win.
45
+ */
46
+ function walkPrototypeChain<V>(
47
+ prototype: object | null,
48
+ metadataKey: symbol,
49
+ ): Map<string, V> {
50
+ // Walk from Object root down to the leaf so subclass entries override parents.
51
+ const chain: object[] = [];
52
+ let current: object | null = prototype;
53
+ while (current && current !== Object.prototype) {
54
+ chain.push(current);
55
+ current = Object.getPrototypeOf(current);
56
+ }
57
+ const merged = new Map<string, V>();
58
+ for (const proto of chain.reverse()) {
59
+ const local = Reflect.getOwnMetadata(metadataKey, proto) as
60
+ | Map<string, V>
61
+ | undefined;
62
+ if (local) {
63
+ for (const [key, value] of local) {
64
+ merged.set(key, value);
65
+ }
66
+ }
67
+ }
68
+ return merged;
69
+ }
70
+
71
+ @singleton()
72
+ export class SchemaService {
73
+ private entries = new Map<string, Entry>();
74
+
75
+ /**
76
+ * Registers a model class under `modelName`.
77
+ *
78
+ * Walks the prototype chain at registration time so lookups are O(1).
79
+ * Calling this a second time for the same `modelName` replaces the entry.
80
+ */
81
+ registerModel(modelName: string, modelClass: ModelClass): void {
82
+ const attributes = walkPrototypeChain<AttributeDef>(
83
+ modelClass.prototype as object,
84
+ ATTRIBUTES_META_KEY,
85
+ );
86
+ const relationships = walkPrototypeChain<RelationshipDef>(
87
+ modelClass.prototype as object,
88
+ RELATIONSHIPS_META_KEY,
89
+ );
90
+ this.entries.set(modelName, { modelClass, attributes, relationships });
91
+ }
92
+
93
+ /**
94
+ * Returns the constructor for the given `modelName`.
95
+ * @throws if the model has not been registered.
96
+ */
97
+ modelFor(modelName: string): ModelClass {
98
+ const entry = this.entries.get(modelName);
99
+ if (!entry) {
100
+ throw new Error(`No model registered for type "${modelName}"`);
101
+ }
102
+ return entry.modelClass;
103
+ }
104
+
105
+ /** Returns `true` when a model class has been registered for `modelName`. */
106
+ doesTypeExist(modelName: string): boolean {
107
+ return this.entries.has(modelName);
108
+ }
109
+
110
+ /**
111
+ * Returns the merged attribute definitions for `modelName`.
112
+ * @throws if the model has not been registered.
113
+ */
114
+ attributesDefinitionFor(modelName: string): AttributeDefinitionsMap {
115
+ const entry = this.entries.get(modelName);
116
+ if (!entry) {
117
+ throw new Error(`No model registered for type "${modelName}"`);
118
+ }
119
+ return entry.attributes;
120
+ }
121
+
122
+ /**
123
+ * Returns the merged relationship definitions for `modelName`.
124
+ * @throws if the model has not been registered.
125
+ */
126
+ relationshipsDefinitionFor(modelName: string): RelationshipDefinitionsMap {
127
+ const entry = this.entries.get(modelName);
128
+ if (!entry) {
129
+ throw new Error(`No model registered for type "${modelName}"`);
130
+ }
131
+ return entry.relationships;
132
+ }
133
+
134
+ /**
135
+ * Iterates over every attribute definition for `modelName`, invoking
136
+ * `callback` with the attribute name and its `AttributeDef`.
137
+ */
138
+ eachAttribute(
139
+ modelName: string,
140
+ callback: (name: string, meta: AttributeDef) => void,
141
+ ): void {
142
+ const attributes = this.attributesDefinitionFor(modelName);
143
+ for (const [name, meta] of attributes) {
144
+ callback(name, meta);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Iterates over every relationship definition for `modelName`, invoking
150
+ * `callback` with the relationship name and its `RelationshipDef`.
151
+ */
152
+ eachRelationship(
153
+ modelName: string,
154
+ callback: (name: string, meta: RelationshipDef) => void,
155
+ ): void {
156
+ const relationships = this.relationshipsDefinitionFor(modelName);
157
+ for (const [name, meta] of relationships) {
158
+ callback(name, meta);
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Property decorators for declaring model attributes and relationships.
3
+ *
4
+ * `@attr` marks a property as a data attribute that is serialized to/from the
5
+ * server payload. `@belongsTo` and `@hasMany` declare the two supported
6
+ * association directions.
7
+ *
8
+ * All decorators write their metadata onto the class prototype via
9
+ * `reflect-metadata` so `SchemaService` can walk the prototype chain and
10
+ * merge inherited definitions.
11
+ */
12
+
13
+ import 'reflect-metadata';
14
+ import {
15
+ ATTRIBUTES_META_KEY,
16
+ RELATIONSHIPS_META_KEY,
17
+ type AttributeDef,
18
+ type AttributeOptions,
19
+ type RelationshipDef,
20
+ type RelationshipOptions,
21
+ } from './types.js';
22
+
23
+ /**
24
+ * Returns the own-prototype attribute map for `target`, creating one if it
25
+ * doesn't exist yet. We deliberately avoid inheriting the parent map so each
26
+ * class level stores only its own declarations; `SchemaService.walkPrototypeChain`
27
+ * handles merging.
28
+ */
29
+ function getOwnAttrMap(target: object): Map<string, AttributeDef> {
30
+ let map = Reflect.getOwnMetadata(ATTRIBUTES_META_KEY, target) as
31
+ | Map<string, AttributeDef>
32
+ | undefined;
33
+ if (!map) {
34
+ map = new Map();
35
+ Reflect.defineMetadata(ATTRIBUTES_META_KEY, map, target);
36
+ }
37
+ return map;
38
+ }
39
+
40
+ /**
41
+ * Returns the own-prototype relationship map for `target`, creating one if
42
+ * it doesn't exist yet.
43
+ */
44
+ function getOwnRelMap(target: object): Map<string, RelationshipDef> {
45
+ let map = Reflect.getOwnMetadata(RELATIONSHIPS_META_KEY, target) as
46
+ | Map<string, RelationshipDef>
47
+ | undefined;
48
+ if (!map) {
49
+ map = new Map();
50
+ Reflect.defineMetadata(RELATIONSHIPS_META_KEY, map, target);
51
+ }
52
+ return map;
53
+ }
54
+
55
+ /**
56
+ * Marks a class property as a serializable attribute.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * @attr('string') name!: string;
61
+ * @attr('number', { defaultValue: 0 }) age!: number;
62
+ * @attr({ defaultValue: () => [] }) tags!: string[];
63
+ * ```
64
+ */
65
+ export function attr(
66
+ type?: string | null,
67
+ options?: AttributeOptions,
68
+ ): PropertyDecorator;
69
+ export function attr(options: AttributeOptions): PropertyDecorator;
70
+ export function attr(
71
+ typeOrOptions?: string | null | AttributeOptions,
72
+ options?: AttributeOptions,
73
+ ): PropertyDecorator {
74
+ let resolvedType: string | null = null;
75
+ let resolvedOptions: AttributeOptions = {};
76
+
77
+ if (typeof typeOrOptions === 'string') {
78
+ resolvedType = typeOrOptions;
79
+ resolvedOptions = options ?? {};
80
+ } else if (typeOrOptions === null) {
81
+ resolvedType = null;
82
+ resolvedOptions = options ?? {};
83
+ } else if (typeof typeOrOptions === 'object' && typeOrOptions !== null) {
84
+ resolvedOptions = typeOrOptions;
85
+ }
86
+
87
+ return (target, propertyKey) => {
88
+ const map = getOwnAttrMap(target as object);
89
+ map.set(propertyKey as string, {
90
+ name: propertyKey as string,
91
+ type: resolvedType,
92
+ options: resolvedOptions,
93
+ isAttribute: true,
94
+ });
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Builds and returns a `PropertyDecorator` that registers a relationship
100
+ * definition on the class prototype. Shared by `@belongsTo` and `@hasMany`.
101
+ *
102
+ * Options are normalised with sensible defaults so consumers can rely on
103
+ * `async`, `inverse`, and `polymorphic` always being present.
104
+ */
105
+ function makeRelationship(
106
+ kind: 'belongsTo' | 'hasMany',
107
+ type: string,
108
+ options: RelationshipOptions = {},
109
+ ): PropertyDecorator {
110
+ const normalized: RelationshipOptions = {
111
+ async: options.async ?? false,
112
+ inverse: options.inverse === undefined ? null : options.inverse,
113
+ polymorphic: options.polymorphic ?? false,
114
+ ...options,
115
+ };
116
+ return (target, propertyKey) => {
117
+ const map = getOwnRelMap(target as object);
118
+ map.set(propertyKey as string, {
119
+ name: propertyKey as string,
120
+ kind,
121
+ type,
122
+ options: normalized,
123
+ isRelationship: true,
124
+ });
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Declares a `belongsTo` (many-to-one) association.
130
+ *
131
+ * @param type - `modelName` of the related model.
132
+ * @param options - Optional relationship options (async, inverse, …).
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * @belongsTo('user') author!: User;
137
+ * ```
138
+ */
139
+ export function belongsTo(
140
+ type: string,
141
+ options: RelationshipOptions = {},
142
+ ): PropertyDecorator {
143
+ return makeRelationship('belongsTo', type, options);
144
+ }
145
+
146
+ /**
147
+ * Declares a `hasMany` (one-to-many) association.
148
+ *
149
+ * @param type - `modelName` of the related model.
150
+ * @param options - Optional relationship options (async, inverse, …).
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * @hasMany('comment') comments!: Comment[];
155
+ * ```
156
+ */
157
+ export function hasMany(
158
+ type: string,
159
+ options: RelationshipOptions = {},
160
+ ): PropertyDecorator {
161
+ return makeRelationship('hasMany', type, options);
162
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export { attr, belongsTo, hasMany } from './decorators.js';
3
+ export { SchemaService, type ModelClass } from './SchemaService.js';
@@ -0,0 +1,66 @@
1
+ /** Shape of a single `@attr` decoration on a model class property. */
2
+ export interface AttributeDef {
3
+ /** Property name on the model class. */
4
+ name: string;
5
+ /** Registered transform type (e.g. `'string'`, `'number'`), or `null` for pass-through. */
6
+ type: string | null;
7
+ /** Extra options passed by the decorator (default value, etc.). */
8
+ options: AttributeOptions;
9
+ /** Discriminant – always `true`; used to narrow union types. */
10
+ isAttribute: true;
11
+ }
12
+
13
+ /** Options accepted by the `@attr` decorator. */
14
+ export interface AttributeOptions {
15
+ /** Static value or factory function called when the attribute has no server value. */
16
+ defaultValue?: unknown | (() => unknown);
17
+ /** Arbitrary custom options forwarded to the transform. */
18
+ [key: string]: unknown;
19
+ }
20
+
21
+ /** Identifies the direction of a relationship from the owning model's perspective. */
22
+ export type RelationshipKind = 'belongsTo' | 'hasMany';
23
+
24
+ /** Shape of a single `@belongsTo` / `@hasMany` decoration on a model class property. */
25
+ export interface RelationshipDef {
26
+ /** Property name on the model class. */
27
+ name: string;
28
+ /** Direction from the owning model's perspective. */
29
+ kind: RelationshipKind;
30
+ /** `modelName` of the related model. */
31
+ type: string;
32
+ /** Normalised options (async, inverse, polymorphic, …). */
33
+ options: RelationshipOptions;
34
+ /** Discriminant – always `true`; used to narrow union types. */
35
+ isRelationship: true;
36
+ }
37
+
38
+ /** Options accepted by `@belongsTo` and `@hasMany` decorators. */
39
+ export interface RelationshipOptions {
40
+ /**
41
+ * When `true` the relationship proxy returns an `AsyncBelongsTo` / `AsyncHasMany`
42
+ * that must be awaited before the related record(s) are available.
43
+ */
44
+ async?: boolean;
45
+ /**
46
+ * Name of the inverse property on the related model.
47
+ * Set to `null` to explicitly opt out of inverse tracking.
48
+ */
49
+ inverse?: string | null;
50
+ /** When `true` the relationship can point to records of multiple model types. */
51
+ polymorphic?: boolean;
52
+ /** Arbitrary custom options. */
53
+ [key: string]: unknown;
54
+ }
55
+
56
+ /** Attribute definitions keyed by property name. */
57
+ export type AttributeDefinitionsMap = Map<string, AttributeDef>;
58
+ /** Relationship definitions keyed by property name. */
59
+ export type RelationshipDefinitionsMap = Map<string, RelationshipDef>;
60
+
61
+ /** Reflect-metadata key used to store attribute definitions on a prototype. */
62
+ export const ATTRIBUTES_META_KEY = Symbol('mobx-data:attributes');
63
+ /** Reflect-metadata key used to store relationship definitions on a prototype. */
64
+ export const RELATIONSHIPS_META_KEY = Symbol('mobx-data:relationships');
65
+ /** Reflect-metadata key used to store the registered model name on a class. */
66
+ export const MODEL_NAME_META_KEY = Symbol('mobx-data:modelName');