@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,162 @@
1
+ /**
2
+ * Immutable point-in-time view of a model record.
3
+ *
4
+ * A `Snapshot` is created immediately before an adapter call so that adapter
5
+ * and serializer code reads a consistent, frozen picture of the record
6
+ * regardless of mutations that happen after the call begins.
7
+ *
8
+ * Key guarantees:
9
+ * - Attribute data is copied at construction time; later record mutations do
10
+ * not bleed into the snapshot.
11
+ * - Relationship data is captured via a `Map` copy for the same reason.
12
+ * - `changedAttributes()` compares the snapshot-time data against the
13
+ * record's original (server-received) data, not the current live state.
14
+ * - `eachAttribute` / `eachRelationship` iterate the merged schema definitions
15
+ * walked at construction from the prototype chain.
16
+ */
17
+
18
+ import 'reflect-metadata';
19
+ import {
20
+ ATTRIBUTES_META_KEY,
21
+ RELATIONSHIPS_META_KEY,
22
+ type AttributeDef,
23
+ type RelationshipDef,
24
+ } from '@mobx-data/schema';
25
+ import type { Model, RelationshipRef } from './Model.js';
26
+
27
+ /** Shape of a serialised `belongsTo` reference as stored in a snapshot. */
28
+ export interface BelongsToReference {
29
+ id: string | null;
30
+ type: string;
31
+ }
32
+
33
+ /** Shape of a serialised `hasMany` item reference as stored in a snapshot. */
34
+ export interface HasManyReference {
35
+ id: string;
36
+ type: string;
37
+ }
38
+
39
+ /**
40
+ * Walks the prototype chain from root to leaf, merging own-metadata entries
41
+ * so that subclass definitions override parent definitions.
42
+ */
43
+ function walk<V>(proto: object | null, key: symbol): Map<string, V> {
44
+ const chain: object[] = [];
45
+ let current: object | null = proto;
46
+ while (current && current !== Object.prototype) {
47
+ chain.push(current);
48
+ current = Object.getPrototypeOf(current);
49
+ }
50
+ const merged = new Map<string, V>();
51
+ for (const entry of chain.reverse()) {
52
+ const local = Reflect.getOwnMetadata(key, entry) as Map<string, V> | undefined;
53
+ if (local) {
54
+ for (const [name, meta] of local) {
55
+ merged.set(name, meta);
56
+ }
57
+ }
58
+ }
59
+ return merged;
60
+ }
61
+
62
+ export class Snapshot<T extends Model = Model> {
63
+ /** Server-assigned id at snapshot time, or `null` for new records. */
64
+ readonly id: string | null;
65
+ /** `modelName` of the snapshotted record. */
66
+ readonly modelName: string;
67
+ /** Reference to the live record (read-only from adapter/serializer code). */
68
+ readonly record: T;
69
+
70
+ private readonly _attributes: Record<string, unknown>;
71
+ private readonly _relationships: Map<string, RelationshipRef>;
72
+ private readonly _changedAttributes: Record<string, [unknown, unknown]>;
73
+ private readonly _attributeDefinitions: Map<string, AttributeDef>;
74
+ private readonly _relationshipDefinitions: Map<string, RelationshipDef>;
75
+
76
+ constructor(record: T) {
77
+ this.record = record;
78
+ this.id = record.id;
79
+ this.modelName = record.modelName;
80
+
81
+ const internal = record as unknown as {
82
+ _data: Record<string, unknown>;
83
+ _relationships: Map<string, RelationshipRef>;
84
+ };
85
+ // Freeze a copy so later record mutations don't bleed in.
86
+ this._attributes = { ...internal._data };
87
+ this._relationships = new Map(internal._relationships);
88
+ this._changedAttributes = record.changedAttributes();
89
+
90
+ const proto = Object.getPrototypeOf(record) as object;
91
+ this._attributeDefinitions = walk<AttributeDef>(proto, ATTRIBUTES_META_KEY);
92
+ this._relationshipDefinitions = walk<RelationshipDef>(proto, RELATIONSHIPS_META_KEY);
93
+ }
94
+
95
+ /** Returns the snapshot-time value for an attribute key. */
96
+ attr<K extends keyof T>(key: K): T[K] {
97
+ return this._attributes[key as string] as T[K];
98
+ }
99
+
100
+ /**
101
+ * Returns the `belongsTo` reference for `key`.
102
+ * When `{ id: true }` is passed, returns only the id string; otherwise
103
+ * returns a `BelongsToReference` `{ id, type }` object, or `null` when the
104
+ * relationship is empty.
105
+ */
106
+ belongsTo(
107
+ key: string,
108
+ options?: { id: boolean },
109
+ ): BelongsToReference | string | null {
110
+ const relationship = this._relationships.get(key);
111
+ if (!relationship || relationship.data === null) {
112
+ return null;
113
+ }
114
+ const data = relationship.data as { id: string; type: string };
115
+ if (options?.id) {
116
+ return data.id;
117
+ }
118
+ return { id: data.id, type: data.type };
119
+ }
120
+
121
+ /**
122
+ * Returns the `hasMany` references for `key`.
123
+ * When `{ ids: true }` is passed, returns a plain string array of ids;
124
+ * otherwise returns an array of `HasManyReference` objects.
125
+ */
126
+ hasMany(
127
+ key: string,
128
+ options?: { ids: boolean },
129
+ ): HasManyReference[] | string[] {
130
+ const relationship = this._relationships.get(key);
131
+ if (!relationship || !Array.isArray(relationship.data)) {
132
+ return [];
133
+ }
134
+ const list = relationship.data as Array<{ id: string; type: string }>;
135
+ if (options?.ids) {
136
+ return list.map((reference) => reference.id);
137
+ }
138
+ return list.map((reference) => ({ id: reference.id, type: reference.type }));
139
+ }
140
+
141
+ /**
142
+ * Returns a `{ [key]: [original, current] }` map of attributes that
143
+ * differ from the server-received values at snapshot time.
144
+ */
145
+ changedAttributes(): Record<string, [unknown, unknown]> {
146
+ return { ...this._changedAttributes };
147
+ }
148
+
149
+ /** Iterates over every attribute definition, calling `callback` for each. */
150
+ eachAttribute(callback: (key: string, meta: AttributeDef) => void): void {
151
+ for (const [key, meta] of this._attributeDefinitions) {
152
+ callback(key, meta);
153
+ }
154
+ }
155
+
156
+ /** Iterates over every relationship definition, calling `callback` for each. */
157
+ eachRelationship(callback: (key: string, meta: RelationshipDef) => void): void {
158
+ for (const [key, meta] of this._relationshipDefinitions) {
159
+ callback(key, meta);
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Finite-state machine that tracks the lifecycle of a single model record.
3
+ *
4
+ * States are organised in a dot-separated hierarchy that mirrors Ember Data's
5
+ * record state machine. The transition table (`TABLE`) is the single source
6
+ * of truth; illegal transitions throw immediately rather than silently
7
+ * degrading into an unexpected state.
8
+ *
9
+ * State hierarchy overview:
10
+ * ```
11
+ * root.empty
12
+ * root.loading
13
+ * root.loaded
14
+ * .saved
15
+ * .created.uncommitted ← new record, not yet sent to server
16
+ * .created.inFlight ← POST in progress
17
+ * .updated.uncommitted ← dirty record, not yet sent to server
18
+ * .updated.inFlight ← PUT/PATCH in progress
19
+ * root.deleted
20
+ * .uncommitted ← deleteRecord() called locally
21
+ * .inFlight ← DELETE in progress
22
+ * .saved ← server confirmed deletion
23
+ * root.error ← adapter threw an unrecoverable error
24
+ * ```
25
+ *
26
+ * `current` is MobX-observable so computed properties that depend on
27
+ * `isNew`, `isDirty`, etc. react automatically.
28
+ */
29
+
30
+ import { makeObservable, observable, action } from 'mobx';
31
+
32
+ /** All valid states the record can be in. */
33
+ export type RecordState =
34
+ | 'root.empty'
35
+ | 'root.loading'
36
+ | 'root.loaded.saved'
37
+ | 'root.loaded.created.uncommitted'
38
+ | 'root.loaded.created.inFlight'
39
+ | 'root.loaded.updated.uncommitted'
40
+ | 'root.loaded.updated.inFlight'
41
+ | 'root.deleted.uncommitted'
42
+ | 'root.deleted.inFlight'
43
+ | 'root.deleted.saved'
44
+ | 'root.error';
45
+
46
+ /** Events that trigger state transitions. */
47
+ export type RecordEvent =
48
+ | 'loadingData'
49
+ | 'pushedData'
50
+ | 'becameError'
51
+ | 'didSetProperty'
52
+ | 'willCommit'
53
+ | 'didCommit'
54
+ | 'becameInvalid'
55
+ | 'deleteRecord'
56
+ | 'rolledBack'
57
+ | 'unloadRecord';
58
+
59
+ /** Per-state map of allowed events → destination states. */
60
+ type Transitions = Partial<Record<RecordEvent, RecordState>>;
61
+
62
+ /** Complete transition table. A missing entry means the transition is invalid. */
63
+ const TABLE: Record<RecordState, Transitions> = {
64
+ 'root.empty': {
65
+ loadingData: 'root.loading',
66
+ pushedData: 'root.loaded.saved',
67
+ },
68
+ 'root.loading': {
69
+ pushedData: 'root.loaded.saved',
70
+ becameError: 'root.error',
71
+ },
72
+ 'root.loaded.saved': {
73
+ didSetProperty: 'root.loaded.updated.uncommitted',
74
+ deleteRecord: 'root.deleted.uncommitted',
75
+ loadingData: 'root.loading',
76
+ pushedData: 'root.loaded.saved',
77
+ unloadRecord: 'root.empty',
78
+ },
79
+ 'root.loaded.created.uncommitted': {
80
+ willCommit: 'root.loaded.created.inFlight',
81
+ rolledBack: 'root.empty',
82
+ deleteRecord: 'root.deleted.uncommitted',
83
+ didSetProperty: 'root.loaded.created.uncommitted',
84
+ unloadRecord: 'root.empty',
85
+ },
86
+ 'root.loaded.created.inFlight': {
87
+ didCommit: 'root.loaded.saved',
88
+ becameInvalid: 'root.loaded.created.uncommitted',
89
+ becameError: 'root.error',
90
+ },
91
+ 'root.loaded.updated.uncommitted': {
92
+ willCommit: 'root.loaded.updated.inFlight',
93
+ rolledBack: 'root.loaded.saved',
94
+ didSetProperty: 'root.loaded.updated.uncommitted',
95
+ deleteRecord: 'root.deleted.uncommitted',
96
+ unloadRecord: 'root.empty',
97
+ },
98
+ 'root.loaded.updated.inFlight': {
99
+ didCommit: 'root.loaded.saved',
100
+ becameInvalid: 'root.loaded.updated.uncommitted',
101
+ becameError: 'root.error',
102
+ },
103
+ 'root.deleted.uncommitted': {
104
+ willCommit: 'root.deleted.inFlight',
105
+ rolledBack: 'root.loaded.saved',
106
+ unloadRecord: 'root.empty',
107
+ },
108
+ 'root.deleted.inFlight': {
109
+ didCommit: 'root.deleted.saved',
110
+ becameError: 'root.error',
111
+ },
112
+ 'root.deleted.saved': {
113
+ unloadRecord: 'root.empty',
114
+ },
115
+ 'root.error': {
116
+ rolledBack: 'root.loaded.saved',
117
+ unloadRecord: 'root.empty',
118
+ },
119
+ };
120
+
121
+ export class StateMachine {
122
+ /** The current state of the record. Observable so computed props react. */
123
+ current: RecordState;
124
+
125
+ constructor(initial: RecordState = 'root.empty') {
126
+ this.current = initial;
127
+ makeObservable(this, {
128
+ current: observable,
129
+ transition: action,
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Applies `event` to the current state, updates `current`, and returns the
135
+ * new state.
136
+ *
137
+ * @throws `Error` when `event` is not permitted from the current state.
138
+ */
139
+ transition(event: RecordEvent): RecordState {
140
+ const next = TABLE[this.current][event];
141
+ if (!next) {
142
+ throw new Error(
143
+ `Invalid transition: event "${event}" not allowed from state "${this.current}"`,
144
+ );
145
+ }
146
+ this.current = next;
147
+ return next;
148
+ }
149
+ }
@@ -0,0 +1,20 @@
1
+ export {
2
+ Model,
3
+ type ModelConstructorOptions,
4
+ type ModelStoreLike,
5
+ type PushOptions,
6
+ type RelationshipRef,
7
+ type SaveOptions,
8
+ } from './Model.js';
9
+ export { Errors, type ErrorMessage } from './Errors.js';
10
+ export {
11
+ Snapshot,
12
+ type BelongsToReference,
13
+ type HasManyReference,
14
+ } from './Snapshot.js';
15
+ export {
16
+ StateMachine,
17
+ type RecordState,
18
+ type RecordEvent,
19
+ } from './StateMachine.js';
20
+ export { ManyArray, AsyncBelongsTo, AsyncHasMany } from './relationships.js';