@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.
- package/LICENSE +21 -0
- package/README.md +366 -0
- package/dist/CacheHandler-BTU_rYkv.js +208 -0
- package/dist/CacheHandler-BTU_rYkv.js.map +1 -0
- package/dist/CacheHandler-CXgY9IJo.cjs +2 -0
- package/dist/CacheHandler-CXgY9IJo.cjs.map +1 -0
- package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs +2 -0
- package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs.map +1 -0
- package/dist/EmbeddedRecordsMixin-VoHluHCT.js +261 -0
- package/dist/EmbeddedRecordsMixin-VoHluHCT.js.map +1 -0
- package/dist/JsonApiSerializer-CC5HXp4b.js +194 -0
- package/dist/JsonApiSerializer-CC5HXp4b.js.map +1 -0
- package/dist/JsonApiSerializer-CKB02AgP.cjs +2 -0
- package/dist/JsonApiSerializer-CKB02AgP.cjs.map +1 -0
- package/dist/MemoryAdapter-Bx1e7ndV.js +123 -0
- package/dist/MemoryAdapter-Bx1e7ndV.js.map +1 -0
- package/dist/MemoryAdapter-D1cTyydm.cjs +2 -0
- package/dist/MemoryAdapter-D1cTyydm.cjs.map +1 -0
- package/dist/ODataAdapter-C4IHK4BK.js +157 -0
- package/dist/ODataAdapter-C4IHK4BK.js.map +1 -0
- package/dist/ODataAdapter-DyyF1sdA.cjs +2 -0
- package/dist/ODataAdapter-DyyF1sdA.cjs.map +1 -0
- package/dist/RestAdapter-B4aRvs4m.js +355 -0
- package/dist/RestAdapter-B4aRvs4m.js.map +1 -0
- package/dist/RestAdapter-CJOwTsKK.cjs +2 -0
- package/dist/RestAdapter-CJOwTsKK.cjs.map +1 -0
- package/dist/SchemaService-DZwkFgZu.js +102 -0
- package/dist/SchemaService-DZwkFgZu.js.map +1 -0
- package/dist/SchemaService-Di_yjVzU.cjs +2 -0
- package/dist/SchemaService-Di_yjVzU.cjs.map +1 -0
- package/dist/Serializer-95gi5edy.cjs +2 -0
- package/dist/Serializer-95gi5edy.cjs.map +1 -0
- package/dist/Serializer-FxJbsZ50.js +139 -0
- package/dist/Serializer-FxJbsZ50.js.map +1 -0
- package/dist/Store-BdwMrbDi.cjs +2 -0
- package/dist/Store-BdwMrbDi.cjs.map +1 -0
- package/dist/Store-CZ7Z-Nme.js +912 -0
- package/dist/Store-CZ7Z-Nme.js.map +1 -0
- package/dist/adapter/Adapter.d.ts +146 -0
- package/dist/adapter/Adapter.d.ts.map +1 -0
- package/dist/adapter/MemoryAdapter.d.ts +44 -0
- package/dist/adapter/MemoryAdapter.d.ts.map +1 -0
- package/dist/adapter/RestAdapter.d.ts +57 -0
- package/dist/adapter/RestAdapter.d.ts.map +1 -0
- package/dist/adapter/index.cjs +2 -0
- package/dist/adapter/index.cjs.map +1 -0
- package/dist/adapter/index.d.ts +4 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +8 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/date-Bj4O2W1F.js +107 -0
- package/dist/date-Bj4O2W1F.js.map +1 -0
- package/dist/date-CRCe-9gf.cjs +2 -0
- package/dist/date-CRCe-9gf.cjs.map +1 -0
- package/dist/decorators-HQ1KnRdh.cjs +2 -0
- package/dist/decorators-HQ1KnRdh.cjs.map +1 -0
- package/dist/decorators-Zr35qr6A.js +50 -0
- package/dist/decorators-Zr35qr6A.js.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/json-api/JsonApiAdapter.d.ts +38 -0
- package/dist/json-api/JsonApiAdapter.d.ts.map +1 -0
- package/dist/json-api/JsonApiSerializer.d.ts +73 -0
- package/dist/json-api/JsonApiSerializer.d.ts.map +1 -0
- package/dist/json-api/index.cjs +2 -0
- package/dist/json-api/index.cjs.map +1 -0
- package/dist/json-api/index.d.ts +3 -0
- package/dist/json-api/index.d.ts.map +1 -0
- package/dist/json-api/index.js +6 -0
- package/dist/json-api/index.js.map +1 -0
- package/dist/model/Errors.d.ts +46 -0
- package/dist/model/Errors.d.ts.map +1 -0
- package/dist/model/Model.d.ts +226 -0
- package/dist/model/Model.d.ts.map +1 -0
- package/dist/model/Snapshot.d.ts +72 -0
- package/dist/model/Snapshot.d.ts.map +1 -0
- package/dist/model/StateMachine.d.ts +45 -0
- package/dist/model/StateMachine.d.ts.map +1 -0
- package/dist/model/index.cjs +2 -0
- package/dist/model/index.cjs.map +1 -0
- package/dist/model/index.d.ts +6 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +11 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/relationships.d.ts +182 -0
- package/dist/model/relationships.d.ts.map +1 -0
- package/dist/odata/ODataAdapter.d.ts +67 -0
- package/dist/odata/ODataAdapter.d.ts.map +1 -0
- package/dist/odata/index.cjs +2 -0
- package/dist/odata/index.cjs.map +1 -0
- package/dist/odata/index.d.ts +2 -0
- package/dist/odata/index.d.ts.map +1 -0
- package/dist/odata/index.js +5 -0
- package/dist/odata/index.js.map +1 -0
- package/dist/relationships-B55LBaCW.cjs +2 -0
- package/dist/relationships-B55LBaCW.cjs.map +1 -0
- package/dist/relationships-BEXANmWg.js +821 -0
- package/dist/relationships-BEXANmWg.js.map +1 -0
- package/dist/request/CacheHandler.d.ts +50 -0
- package/dist/request/CacheHandler.d.ts.map +1 -0
- package/dist/request/FetchHandler.d.ts +41 -0
- package/dist/request/FetchHandler.d.ts.map +1 -0
- package/dist/request/RequestManager.d.ts +52 -0
- package/dist/request/RequestManager.d.ts.map +1 -0
- package/dist/request/index.cjs +2 -0
- package/dist/request/index.cjs.map +1 -0
- package/dist/request/index.d.ts +5 -0
- package/dist/request/index.d.ts.map +1 -0
- package/dist/request/index.js +7 -0
- package/dist/request/index.js.map +1 -0
- package/dist/request/types.d.ts +111 -0
- package/dist/request/types.d.ts.map +1 -0
- package/dist/schema/SchemaService.d.ts +58 -0
- package/dist/schema/SchemaService.d.ts.map +1 -0
- package/dist/schema/decorators.d.ts +50 -0
- package/dist/schema/decorators.d.ts.map +1 -0
- package/dist/schema/index.cjs +2 -0
- package/dist/schema/index.cjs.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +13 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/types.d.ts +61 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/serializer/EmbeddedRecordsMixin.d.ts +80 -0
- package/dist/serializer/EmbeddedRecordsMixin.d.ts.map +1 -0
- package/dist/serializer/JsonSerializer.d.ts +52 -0
- package/dist/serializer/JsonSerializer.d.ts.map +1 -0
- package/dist/serializer/RestSerializer.d.ts +43 -0
- package/dist/serializer/RestSerializer.d.ts.map +1 -0
- package/dist/serializer/Serializer.d.ts +202 -0
- package/dist/serializer/Serializer.d.ts.map +1 -0
- package/dist/serializer/index.cjs +2 -0
- package/dist/serializer/index.cjs.map +1 -0
- package/dist/serializer/index.d.ts +5 -0
- package/dist/serializer/index.d.ts.map +1 -0
- package/dist/serializer/index.js +9 -0
- package/dist/serializer/index.js.map +1 -0
- package/dist/store/IdentityMap.d.ts +53 -0
- package/dist/store/IdentityMap.d.ts.map +1 -0
- package/dist/store/RecordArray.d.ts +114 -0
- package/dist/store/RecordArray.d.ts.map +1 -0
- package/dist/store/Store.d.ts +395 -0
- package/dist/store/Store.d.ts.map +1 -0
- package/dist/store/index.cjs +2 -0
- package/dist/store/index.cjs.map +1 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +8 -0
- package/dist/store/index.js.map +1 -0
- package/dist/transforms/Transform.d.ts +49 -0
- package/dist/transforms/Transform.d.ts.map +1 -0
- package/dist/transforms/boolean.d.ts +26 -0
- package/dist/transforms/boolean.d.ts.map +1 -0
- package/dist/transforms/date.d.ts +22 -0
- package/dist/transforms/date.d.ts.map +1 -0
- package/dist/transforms/index.cjs +2 -0
- package/dist/transforms/index.cjs.map +1 -0
- package/dist/transforms/index.d.ts +6 -0
- package/dist/transforms/index.d.ts.map +1 -0
- package/dist/transforms/index.js +9 -0
- package/dist/transforms/index.js.map +1 -0
- package/dist/transforms/number.d.ts +17 -0
- package/dist/transforms/number.d.ts.map +1 -0
- package/dist/transforms/string.d.ts +18 -0
- package/dist/transforms/string.d.ts.map +1 -0
- package/dist/types-C9NB2gRj.js +7 -0
- package/dist/types-C9NB2gRj.js.map +1 -0
- package/dist/types-uWOXMPWW.cjs +2 -0
- package/dist/types-uWOXMPWW.cjs.map +1 -0
- package/package.json +140 -0
- package/src/adapter/Adapter.ts +320 -0
- package/src/adapter/MemoryAdapter.ts +216 -0
- package/src/adapter/RestAdapter.ts +248 -0
- package/src/adapter/index.ts +7 -0
- package/src/index.ts +17 -0
- package/src/json-api/JsonApiAdapter.ts +93 -0
- package/src/json-api/JsonApiSerializer.ts +245 -0
- package/src/json-api/index.ts +2 -0
- package/src/model/Errors.ts +100 -0
- package/src/model/Model.ts +683 -0
- package/src/model/Snapshot.ts +162 -0
- package/src/model/StateMachine.ts +149 -0
- package/src/model/index.ts +20 -0
- package/src/model/relationships.ts +484 -0
- package/src/odata/ODataAdapter.ts +245 -0
- package/src/odata/index.ts +1 -0
- package/src/request/CacheHandler.ts +125 -0
- package/src/request/FetchHandler.ts +119 -0
- package/src/request/RequestManager.ts +112 -0
- package/src/request/index.ts +4 -0
- package/src/request/types.ts +139 -0
- package/src/schema/SchemaService.ts +161 -0
- package/src/schema/decorators.ts +162 -0
- package/src/schema/index.ts +3 -0
- package/src/schema/types.ts +66 -0
- package/src/serializer/EmbeddedRecordsMixin.ts +257 -0
- package/src/serializer/JsonSerializer.ts +173 -0
- package/src/serializer/RestSerializer.ts +138 -0
- package/src/serializer/Serializer.ts +397 -0
- package/src/serializer/index.ts +15 -0
- package/src/store/IdentityMap.ts +110 -0
- package/src/store/RecordArray.ts +210 -0
- package/src/store/Store.ts +1391 -0
- package/src/store/index.ts +11 -0
- package/src/transforms/Transform.ts +52 -0
- package/src/transforms/boolean.ts +57 -0
- package/src/transforms/date.ts +48 -0
- package/src/transforms/index.ts +5 -0
- package/src/transforms/number.ts +42 -0
- 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';
|