@iamjulianacosta/mobx-data 1.1.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +273 -102
- package/dist/{CacheHandler-BTU_rYkv.js → CacheHandler-BhfbVHed.js} +17 -20
- package/dist/CacheHandler-BhfbVHed.js.map +1 -0
- package/dist/{CacheHandler-CXgY9IJo.cjs → CacheHandler-Q5VXOgh9.cjs} +2 -2
- package/dist/CacheHandler-Q5VXOgh9.cjs.map +1 -0
- package/dist/EmbeddedRecordsMixin-6mSCXsJ3.js +173 -0
- package/dist/EmbeddedRecordsMixin-6mSCXsJ3.js.map +1 -0
- package/dist/EmbeddedRecordsMixin-BkF7MdbY.cjs +2 -0
- package/dist/EmbeddedRecordsMixin-BkF7MdbY.cjs.map +1 -0
- package/dist/{JsonApiSerializer-wndq5a1n.js → JsonApiSerializer-BV61cFAZ.js} +3 -3
- package/dist/JsonApiSerializer-BV61cFAZ.js.map +1 -0
- package/dist/{JsonApiSerializer-Bc4iQB0d.cjs → JsonApiSerializer-Dt_Y_FIo.cjs} +2 -2
- package/dist/JsonApiSerializer-Dt_Y_FIo.cjs.map +1 -0
- package/dist/JsonSerializer-BzUCyUSf.cjs +2 -0
- package/dist/JsonSerializer-BzUCyUSf.cjs.map +1 -0
- package/dist/JsonSerializer-CFqo6GjC.js +98 -0
- package/dist/JsonSerializer-CFqo6GjC.js.map +1 -0
- package/dist/MdqlMemoryExecutor-BUlsalKm.cjs +2 -0
- package/dist/MdqlMemoryExecutor-BUlsalKm.cjs.map +1 -0
- package/dist/MdqlMemoryExecutor-BWMP31zG.js +127 -0
- package/dist/MdqlMemoryExecutor-BWMP31zG.js.map +1 -0
- package/dist/{MemoryAdapter-ni25N4H0.js → MemoryAdapter-BW1HKixm.js} +2 -2
- package/dist/{MemoryAdapter-ni25N4H0.js.map → MemoryAdapter-BW1HKixm.js.map} +1 -1
- package/dist/{MemoryAdapter-BTK2D64s.cjs → MemoryAdapter-C8iXAa2v.cjs} +2 -2
- package/dist/{MemoryAdapter-BTK2D64s.cjs.map → MemoryAdapter-C8iXAa2v.cjs.map} +1 -1
- package/dist/{ODataAdapter-DAja_jKM.js → ODataAdapter-CeBJblLQ.js} +25 -22
- package/dist/ODataAdapter-CeBJblLQ.js.map +1 -0
- package/dist/{ODataAdapter-lMifLyLD.cjs → ODataAdapter-DdE6MWkG.cjs} +2 -2
- package/dist/ODataAdapter-DdE6MWkG.cjs.map +1 -0
- package/dist/RestAdapter-D7GSrsJo.cjs +2 -0
- package/dist/RestAdapter-D7GSrsJo.cjs.map +1 -0
- package/dist/{RestAdapter-CGWqOR_G.js → RestAdapter-DYUoyV5h.js} +112 -77
- package/dist/RestAdapter-DYUoyV5h.js.map +1 -0
- package/dist/SchemaService-C_pkh-vI.js +180 -0
- package/dist/SchemaService-C_pkh-vI.js.map +1 -0
- package/dist/SchemaService-DbJLoYb9.cjs +2 -0
- package/dist/SchemaService-DbJLoYb9.cjs.map +1 -0
- package/dist/Serializer-Bap9U-kR.cjs +2 -0
- package/dist/Serializer-Bap9U-kR.cjs.map +1 -0
- package/dist/{Serializer-FxJbsZ50.js → Serializer-Ca6w_QNQ.js} +63 -49
- package/dist/Serializer-Ca6w_QNQ.js.map +1 -0
- package/dist/adapter/index.cjs +1 -1
- package/dist/adapter/index.js +2 -2
- package/dist/cache/cache-utils.d.ts +1 -1
- package/dist/cache/cache-utils.d.ts.map +1 -1
- package/dist/createStore-7PecKT54.cjs +2 -0
- package/dist/createStore-7PecKT54.cjs.map +1 -0
- package/dist/createStore-BfmRfZ_2.js +1229 -0
- package/dist/createStore-BfmRfZ_2.js.map +1 -0
- package/dist/date-Bj4O2W1F.js.map +1 -1
- package/dist/date-CRCe-9gf.cjs.map +1 -1
- package/dist/decorators-CKneHgoF.js +56 -0
- package/dist/decorators-CKneHgoF.js.map +1 -0
- package/dist/decorators-DCVYKzrL.cjs +2 -0
- package/dist/decorators-DCVYKzrL.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -90
- package/dist/index.js.map +1 -1
- package/dist/inspector/ConsoleInspector.d.ts +49 -0
- package/dist/inspector/ConsoleInspector.d.ts.map +1 -0
- package/dist/inspector/DevToolsBridge.d.ts +21 -0
- package/dist/inspector/DevToolsBridge.d.ts.map +1 -0
- package/dist/inspector/QueryParser.d.ts +21 -0
- package/dist/inspector/QueryParser.d.ts.map +1 -0
- package/dist/inspector/StoreInspector.d.ts +31 -0
- package/dist/inspector/StoreInspector.d.ts.map +1 -0
- package/dist/inspector/index.cjs +17 -0
- package/dist/inspector/index.cjs.map +1 -0
- package/dist/inspector/index.d.ts +9 -0
- package/dist/inspector/index.d.ts.map +1 -0
- package/dist/inspector/index.js +896 -0
- package/dist/inspector/index.js.map +1 -0
- package/dist/inspector/integration.d.ts +15 -0
- package/dist/inspector/integration.d.ts.map +1 -0
- package/dist/inspector/serialization.d.ts +7 -0
- package/dist/inspector/serialization.d.ts.map +1 -0
- package/dist/inspector/types.d.ts +139 -0
- package/dist/inspector/types.d.ts.map +1 -0
- package/dist/json-api/index.cjs +1 -1
- package/dist/json-api/index.js +1 -1
- package/dist/mdql/MdqlMemoryExecutor.d.ts +17 -0
- package/dist/mdql/MdqlMemoryExecutor.d.ts.map +1 -0
- package/dist/mdql/MdqlQueryBuilder.d.ts +38 -0
- package/dist/mdql/MdqlQueryBuilder.d.ts.map +1 -0
- package/dist/mdql/MdqlValidator.d.ts +13 -0
- package/dist/mdql/MdqlValidator.d.ts.map +1 -0
- package/dist/mdql/index.d.ts +6 -0
- package/dist/mdql/index.d.ts.map +1 -0
- package/dist/mdql/types.d.ts +48 -0
- package/dist/mdql/types.d.ts.map +1 -0
- package/dist/model/Model.d.ts +4 -0
- package/dist/model/Model.d.ts.map +1 -1
- package/dist/model/Snapshot.d.ts +2 -0
- package/dist/model/Snapshot.d.ts.map +1 -1
- package/dist/model/index.cjs +1 -1
- package/dist/model/index.js +1 -1
- package/dist/odata/ODataAdapter.d.ts.map +1 -1
- package/dist/odata/index.cjs +1 -1
- package/dist/odata/index.js +1 -1
- package/dist/relationships-BgM0NKdb.cjs +2 -0
- package/dist/relationships-BgM0NKdb.cjs.map +1 -0
- package/dist/{relationships-BEXANmWg.js → relationships-DvSi8fVN.js} +37 -28
- package/dist/relationships-DvSi8fVN.js.map +1 -0
- package/dist/request/CacheHandler.d.ts.map +1 -1
- package/dist/request/index.cjs +1 -1
- package/dist/request/index.js +1 -1
- package/dist/schema/SchemaService.d.ts +38 -1
- package/dist/schema/SchemaService.d.ts.map +1 -1
- package/dist/schema/decorators.d.ts +20 -1
- package/dist/schema/decorators.d.ts.map +1 -1
- package/dist/schema/index.cjs +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +10 -8
- package/dist/schema/types.d.ts +31 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/serializer/JsonSerializer.d.ts +2 -0
- package/dist/serializer/JsonSerializer.d.ts.map +1 -1
- package/dist/serializer/Serializer.d.ts +9 -0
- package/dist/serializer/Serializer.d.ts.map +1 -1
- package/dist/serializer/index.cjs +1 -1
- package/dist/serializer/index.js +6 -5
- package/dist/serializer/index.js.map +1 -1
- package/dist/store/Store.d.ts +3 -0
- package/dist/store/Store.d.ts.map +1 -1
- package/dist/store/createStore.d.ts +12 -0
- package/dist/store/createStore.d.ts.map +1 -0
- package/dist/store/index.cjs +1 -1
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +5 -4
- package/dist/types-CC2fG3FP.js +8 -0
- package/dist/types-CC2fG3FP.js.map +1 -0
- package/dist/types-DCLy5XYj.cjs +2 -0
- package/dist/types-DCLy5XYj.cjs.map +1 -0
- package/package.json +7 -1
- package/src/cache/cache-utils.ts +4 -4
- package/src/index.ts +3 -0
- package/src/inspector/ConsoleInspector.ts +470 -0
- package/src/inspector/DevToolsBridge.ts +214 -0
- package/src/inspector/QueryParser.ts +343 -0
- package/src/inspector/StoreInspector.ts +162 -0
- package/src/inspector/index.ts +20 -0
- package/src/inspector/integration.ts +56 -0
- package/src/inspector/serialization.ts +100 -0
- package/src/inspector/types.ts +161 -0
- package/src/mdql/MdqlMemoryExecutor.ts +229 -0
- package/src/mdql/MdqlQueryBuilder.ts +170 -0
- package/src/mdql/MdqlValidator.ts +193 -0
- package/src/mdql/index.ts +21 -0
- package/src/mdql/types.ts +107 -0
- package/src/model/Model.ts +15 -0
- package/src/model/Snapshot.ts +3 -0
- package/src/odata/ODataAdapter.ts +4 -1
- package/src/request/CacheHandler.ts +2 -6
- package/src/schema/SchemaService.ts +123 -1
- package/src/schema/decorators.ts +29 -0
- package/src/schema/index.ts +1 -1
- package/src/schema/types.ts +34 -0
- package/src/serializer/JsonSerializer.ts +14 -2
- package/src/serializer/Serializer.ts +24 -1
- package/src/store/Store.ts +61 -18
- package/src/store/createStore.ts +39 -0
- package/src/store/index.ts +1 -0
- package/dist/CacheHandler-BTU_rYkv.js.map +0 -1
- package/dist/CacheHandler-CXgY9IJo.cjs.map +0 -1
- package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs +0 -2
- package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs.map +0 -1
- package/dist/EmbeddedRecordsMixin-VoHluHCT.js +0 -261
- package/dist/EmbeddedRecordsMixin-VoHluHCT.js.map +0 -1
- package/dist/JsonApiSerializer-Bc4iQB0d.cjs.map +0 -1
- package/dist/JsonApiSerializer-wndq5a1n.js.map +0 -1
- package/dist/ODataAdapter-DAja_jKM.js.map +0 -1
- package/dist/ODataAdapter-lMifLyLD.cjs.map +0 -1
- package/dist/RestAdapter-1V94stW-.cjs +0 -2
- package/dist/RestAdapter-1V94stW-.cjs.map +0 -1
- package/dist/RestAdapter-CGWqOR_G.js.map +0 -1
- package/dist/SchemaService-DZwkFgZu.js +0 -102
- package/dist/SchemaService-DZwkFgZu.js.map +0 -1
- package/dist/SchemaService-Di_yjVzU.cjs +0 -2
- package/dist/SchemaService-Di_yjVzU.cjs.map +0 -1
- package/dist/Serializer-95gi5edy.cjs +0 -2
- package/dist/Serializer-95gi5edy.cjs.map +0 -1
- package/dist/Serializer-FxJbsZ50.js.map +0 -1
- package/dist/Store-KvjmBTQ9.cjs +0 -2
- package/dist/Store-KvjmBTQ9.cjs.map +0 -1
- package/dist/Store-mvrDLQEZ.js +0 -957
- package/dist/Store-mvrDLQEZ.js.map +0 -1
- package/dist/cache-utils-2lswvJ87.cjs +0 -2
- package/dist/cache-utils-2lswvJ87.cjs.map +0 -1
- package/dist/cache-utils-38Dqu4Qf.js +0 -39
- package/dist/cache-utils-38Dqu4Qf.js.map +0 -1
- package/dist/decorators-HQ1KnRdh.cjs +0 -2
- package/dist/decorators-HQ1KnRdh.cjs.map +0 -1
- package/dist/decorators-Zr35qr6A.js +0 -50
- package/dist/decorators-Zr35qr6A.js.map +0 -1
- package/dist/relationships-B55LBaCW.cjs +0 -2
- package/dist/relationships-B55LBaCW.cjs.map +0 -1
- package/dist/relationships-BEXANmWg.js.map +0 -1
- package/dist/types-C9NB2gRj.js +0 -7
- package/dist/types-C9NB2gRj.js.map +0 -1
- package/dist/types-uWOXMPWW.cjs +0 -2
- package/dist/types-uWOXMPWW.cjs.map +0 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { SchemaService } from '@mobx-data/schema';
|
|
2
|
+
import {
|
|
3
|
+
ALL_OPERATORS,
|
|
4
|
+
OPERATORS_FOR_TYPE,
|
|
5
|
+
type MdqlFilterNode,
|
|
6
|
+
type MdqlQueryObject,
|
|
7
|
+
type MdqlValidationError,
|
|
8
|
+
} from './types.js';
|
|
9
|
+
|
|
10
|
+
export class MdqlValidationException extends Error {
|
|
11
|
+
public readonly errors: MdqlValidationError[];
|
|
12
|
+
|
|
13
|
+
constructor(errors: MdqlValidationError[]) {
|
|
14
|
+
const messages = errors.map((error) => `${error.path}: ${error.message}`);
|
|
15
|
+
super(`MDQL validation failed: ${messages.join('; ')}`);
|
|
16
|
+
this.name = 'MdqlValidationException';
|
|
17
|
+
this.errors = errors;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class MdqlValidator {
|
|
22
|
+
static validate(query: MdqlQueryObject, schema: SchemaService): void {
|
|
23
|
+
const errors = MdqlValidator.validateQuiet(query, schema);
|
|
24
|
+
if (errors.length > 0) {
|
|
25
|
+
throw new MdqlValidationException(errors);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static validateQuiet(
|
|
30
|
+
query: MdqlQueryObject,
|
|
31
|
+
schema: SchemaService,
|
|
32
|
+
): MdqlValidationError[] {
|
|
33
|
+
const errors: MdqlValidationError[] = [];
|
|
34
|
+
|
|
35
|
+
if (!schema.doesTypeExist(query.modelName)) {
|
|
36
|
+
errors.push({
|
|
37
|
+
path: 'modelName',
|
|
38
|
+
message: `Unknown model type "${query.modelName}".`,
|
|
39
|
+
});
|
|
40
|
+
return errors;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const attributes = schema.attributesDefinitionFor(query.modelName);
|
|
44
|
+
const relationships = schema.relationshipsDefinitionFor(query.modelName);
|
|
45
|
+
|
|
46
|
+
MdqlValidator.validateFilterNode(
|
|
47
|
+
query.filters,
|
|
48
|
+
'filters',
|
|
49
|
+
errors,
|
|
50
|
+
schema,
|
|
51
|
+
query.modelName,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
for (let index = 0; index < query.orderBy.length; index++) {
|
|
55
|
+
const clause = query.orderBy[index]!;
|
|
56
|
+
if (clause.field.includes('.')) {
|
|
57
|
+
const resolved = MdqlValidator.resolveFieldAttribute(clause.field, query.modelName, schema);
|
|
58
|
+
if (!resolved) {
|
|
59
|
+
errors.push({
|
|
60
|
+
path: `orderBy[${index}]`,
|
|
61
|
+
message: `Unknown attribute path "${clause.field}".`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
} else if (clause.field !== 'id' && !attributes.has(clause.field)) {
|
|
65
|
+
errors.push({
|
|
66
|
+
path: `orderBy[${index}]`,
|
|
67
|
+
message: `Unknown attribute "${clause.field}" on "${query.modelName}".`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const include of query.includes) {
|
|
73
|
+
if (!relationships.has(include)) {
|
|
74
|
+
errors.push({
|
|
75
|
+
path: 'includes',
|
|
76
|
+
message: `Unknown relationship "${include}" on "${query.modelName}".`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (query.limit !== null) {
|
|
82
|
+
if (!Number.isInteger(query.limit) || query.limit < 1) {
|
|
83
|
+
errors.push({
|
|
84
|
+
path: 'limit',
|
|
85
|
+
message: 'Limit must be a positive integer.',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (query.offset !== null) {
|
|
91
|
+
if (!Number.isInteger(query.offset) || query.offset < 0) {
|
|
92
|
+
errors.push({
|
|
93
|
+
path: 'offset',
|
|
94
|
+
message: 'Offset must be a non-negative integer.',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return errors;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private static resolveFieldAttribute(
|
|
103
|
+
field: string,
|
|
104
|
+
modelName: string,
|
|
105
|
+
schema: SchemaService,
|
|
106
|
+
): { name: string; type: string | null } | null {
|
|
107
|
+
const parts = field.split('.');
|
|
108
|
+
if (parts.length === 1) {
|
|
109
|
+
if (field === 'id') return { name: 'id', type: 'string' };
|
|
110
|
+
const attributes = schema.attributesDefinitionFor(modelName);
|
|
111
|
+
return attributes.get(field) ?? null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let currentModel = modelName;
|
|
115
|
+
for (let index = 0; index < parts.length - 1; index++) {
|
|
116
|
+
const relationships = schema.relationshipsDefinitionFor(currentModel);
|
|
117
|
+
const rel = relationships.get(parts[index]!);
|
|
118
|
+
if (!rel) return null;
|
|
119
|
+
currentModel = rel.type;
|
|
120
|
+
if (!schema.doesTypeExist(currentModel)) return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const lastPart = parts[parts.length - 1]!;
|
|
124
|
+
if (lastPart === 'id') {
|
|
125
|
+
return { name: 'id', type: 'string' };
|
|
126
|
+
}
|
|
127
|
+
const finalAttributes = schema.attributesDefinitionFor(currentModel);
|
|
128
|
+
return finalAttributes.get(lastPart) ?? null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private static validateFilterNode(
|
|
132
|
+
node: MdqlFilterNode,
|
|
133
|
+
path: string,
|
|
134
|
+
errors: MdqlValidationError[],
|
|
135
|
+
schema: SchemaService,
|
|
136
|
+
modelName: string,
|
|
137
|
+
): void {
|
|
138
|
+
if (node.kind === 'condition') {
|
|
139
|
+
const attributeDefinition = MdqlValidator.resolveFieldAttribute(
|
|
140
|
+
node.field, modelName, schema,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (!attributeDefinition) {
|
|
144
|
+
const message = node.field.includes('.')
|
|
145
|
+
? `Unknown attribute path "${node.field}".`
|
|
146
|
+
: `Unknown attribute "${node.field}".`;
|
|
147
|
+
errors.push({ path, message });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const allowedOperators = attributeDefinition.type
|
|
152
|
+
? OPERATORS_FOR_TYPE[attributeDefinition.type] ?? ALL_OPERATORS
|
|
153
|
+
: ALL_OPERATORS;
|
|
154
|
+
|
|
155
|
+
if (!allowedOperators.has(node.operator)) {
|
|
156
|
+
errors.push({
|
|
157
|
+
path,
|
|
158
|
+
message: `Operator "${node.operator}" is not valid for type "${attributeDefinition.type}".`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (node.operator === 'between') {
|
|
163
|
+
if (!Array.isArray(node.value) || node.value.length !== 2) {
|
|
164
|
+
errors.push({
|
|
165
|
+
path,
|
|
166
|
+
message: 'Operator "between" requires a value of [min, max].',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (node.operator === 'in' || node.operator === 'notIn') {
|
|
172
|
+
if (!Array.isArray(node.value)) {
|
|
173
|
+
errors.push({
|
|
174
|
+
path,
|
|
175
|
+
message: `Operator "${node.operator}" requires an array value.`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let index = 0; index < node.children.length; index++) {
|
|
184
|
+
MdqlValidator.validateFilterNode(
|
|
185
|
+
node.children[index]!,
|
|
186
|
+
`${path}.${node.kind}[${index}]`,
|
|
187
|
+
errors,
|
|
188
|
+
schema,
|
|
189
|
+
modelName,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { MdqlQueryBuilder } from './MdqlQueryBuilder.js';
|
|
2
|
+
export { MdqlValidator, MdqlValidationException } from './MdqlValidator.js';
|
|
3
|
+
export { MdqlMemoryExecutor } from './MdqlMemoryExecutor.js';
|
|
4
|
+
export type {
|
|
5
|
+
MdqlQueryObject,
|
|
6
|
+
MdqlFilterNode,
|
|
7
|
+
MdqlCondition,
|
|
8
|
+
MdqlLogicalGroup,
|
|
9
|
+
MdqlOperator,
|
|
10
|
+
MdqlUnaryOperator,
|
|
11
|
+
MdqlSortDirection,
|
|
12
|
+
MdqlOrderByClause,
|
|
13
|
+
MdqlValidationError,
|
|
14
|
+
ModelFields,
|
|
15
|
+
MdqlOperatorFor,
|
|
16
|
+
MdqlValueFor,
|
|
17
|
+
MdqlStringOperator,
|
|
18
|
+
MdqlNumberOperator,
|
|
19
|
+
MdqlBooleanOperator,
|
|
20
|
+
} from './types.js';
|
|
21
|
+
export { ALL_OPERATORS, OPERATORS_FOR_TYPE } from './types.js';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export type MdqlOperator =
|
|
2
|
+
| 'equals'
|
|
3
|
+
| 'notEquals'
|
|
4
|
+
| 'in'
|
|
5
|
+
| 'notIn'
|
|
6
|
+
| 'isNull'
|
|
7
|
+
| 'isNotNull'
|
|
8
|
+
| 'contains'
|
|
9
|
+
| 'startsWith'
|
|
10
|
+
| 'endsWith'
|
|
11
|
+
| 'greaterThan'
|
|
12
|
+
| 'greaterThanOrEquals'
|
|
13
|
+
| 'lessThan'
|
|
14
|
+
| 'lessThanOrEquals'
|
|
15
|
+
| 'between';
|
|
16
|
+
|
|
17
|
+
export type MdqlUnaryOperator = 'isNull' | 'isNotNull';
|
|
18
|
+
|
|
19
|
+
export type MdqlSortDirection = 'asc' | 'desc';
|
|
20
|
+
|
|
21
|
+
export interface MdqlCondition {
|
|
22
|
+
kind: 'condition';
|
|
23
|
+
field: string;
|
|
24
|
+
operator: MdqlOperator;
|
|
25
|
+
value: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MdqlLogicalGroup {
|
|
29
|
+
kind: 'and' | 'or' | 'not';
|
|
30
|
+
children: MdqlFilterNode[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type MdqlFilterNode = MdqlCondition | MdqlLogicalGroup;
|
|
34
|
+
|
|
35
|
+
export interface MdqlOrderByClause {
|
|
36
|
+
field: string;
|
|
37
|
+
direction: MdqlSortDirection;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MdqlQueryObject {
|
|
41
|
+
modelName: string;
|
|
42
|
+
filters: MdqlLogicalGroup;
|
|
43
|
+
orderBy: MdqlOrderByClause[];
|
|
44
|
+
limit: number | null;
|
|
45
|
+
offset: number | null;
|
|
46
|
+
includes: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface MdqlValidationError {
|
|
50
|
+
path: string;
|
|
51
|
+
message: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const COMPARABLE_OPERATORS: MdqlOperator[] = [
|
|
55
|
+
'equals', 'notEquals', 'in', 'notIn', 'isNull', 'isNotNull',
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const STRING_OPERATORS: MdqlOperator[] = [
|
|
59
|
+
...COMPARABLE_OPERATORS, 'contains', 'startsWith', 'endsWith',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const NUMERIC_OPERATORS: MdqlOperator[] = [
|
|
63
|
+
...COMPARABLE_OPERATORS,
|
|
64
|
+
'greaterThan', 'greaterThanOrEquals', 'lessThan', 'lessThanOrEquals', 'between',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export const OPERATORS_FOR_TYPE: Record<string, ReadonlySet<MdqlOperator>> = {
|
|
68
|
+
string: new Set(STRING_OPERATORS),
|
|
69
|
+
number: new Set(NUMERIC_OPERATORS),
|
|
70
|
+
date: new Set(NUMERIC_OPERATORS),
|
|
71
|
+
boolean: new Set(['equals', 'notEquals', 'isNull', 'isNotNull']),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const ALL_OPERATORS: ReadonlySet<MdqlOperator> = new Set<MdqlOperator>([
|
|
75
|
+
'equals', 'notEquals', 'in', 'notIn', 'isNull', 'isNotNull',
|
|
76
|
+
'contains', 'startsWith', 'endsWith',
|
|
77
|
+
'greaterThan', 'greaterThanOrEquals', 'lessThan', 'lessThanOrEquals', 'between',
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
// --- Type-level operator & value inference ---
|
|
81
|
+
|
|
82
|
+
import type { Model } from '@mobx-data/model';
|
|
83
|
+
|
|
84
|
+
export type ModelFields<T extends Model> = Omit<T, keyof Model>;
|
|
85
|
+
|
|
86
|
+
export type MdqlStringOperator =
|
|
87
|
+
| 'equals' | 'notEquals' | 'in' | 'notIn' | 'isNull' | 'isNotNull'
|
|
88
|
+
| 'contains' | 'startsWith' | 'endsWith';
|
|
89
|
+
|
|
90
|
+
export type MdqlNumberOperator =
|
|
91
|
+
| 'equals' | 'notEquals' | 'in' | 'notIn' | 'isNull' | 'isNotNull'
|
|
92
|
+
| 'greaterThan' | 'greaterThanOrEquals' | 'lessThan' | 'lessThanOrEquals' | 'between';
|
|
93
|
+
|
|
94
|
+
export type MdqlBooleanOperator = 'equals' | 'notEquals' | 'isNull' | 'isNotNull';
|
|
95
|
+
|
|
96
|
+
export type MdqlOperatorFor<T> =
|
|
97
|
+
[T] extends [string] ? MdqlStringOperator :
|
|
98
|
+
[T] extends [number] ? MdqlNumberOperator :
|
|
99
|
+
[T] extends [boolean] ? MdqlBooleanOperator :
|
|
100
|
+
[T] extends [Date] ? MdqlNumberOperator :
|
|
101
|
+
MdqlOperator;
|
|
102
|
+
|
|
103
|
+
export type MdqlValueFor<TField, TOp extends MdqlOperator> =
|
|
104
|
+
TOp extends 'isNull' | 'isNotNull' ? undefined :
|
|
105
|
+
TOp extends 'in' | 'notIn' ? TField[] :
|
|
106
|
+
TOp extends 'between' ? [TField, TField] :
|
|
107
|
+
TField;
|
package/src/model/Model.ts
CHANGED
|
@@ -45,6 +45,13 @@ import { Errors } from './Errors.js';
|
|
|
45
45
|
import { StateMachine, type RecordState, type RecordEvent } from './StateMachine.js';
|
|
46
46
|
import { Snapshot } from './Snapshot.js';
|
|
47
47
|
|
|
48
|
+
let clientIdCounter = 0;
|
|
49
|
+
|
|
50
|
+
function generateClientId(): string {
|
|
51
|
+
clientIdCounter += 1;
|
|
52
|
+
return `client-${clientIdCounter}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
/**
|
|
49
56
|
* Raw relationship reference stored on the record.
|
|
50
57
|
* Contains either a single `{ type, id }` object (belongsTo),
|
|
@@ -238,6 +245,8 @@ export abstract class Model {
|
|
|
238
245
|
protected _relationships: Map<string, RelationshipRef> = new Map();
|
|
239
246
|
/** Server-assigned id, or `null` for new records. */
|
|
240
247
|
protected _id: string | null = null;
|
|
248
|
+
/** Client-generated identifier for new records that don't yet have a server id. */
|
|
249
|
+
readonly _clientId: string = generateClientId();
|
|
241
250
|
/** Internal lifecycle state machine. */
|
|
242
251
|
protected _stateMachine: StateMachine;
|
|
243
252
|
|
|
@@ -287,6 +296,7 @@ export abstract class Model {
|
|
|
287
296
|
_relationships: observable.shallow,
|
|
288
297
|
_id: observable,
|
|
289
298
|
id: computed,
|
|
299
|
+
uniqueId: computed,
|
|
290
300
|
currentState: computed,
|
|
291
301
|
isLoading: computed,
|
|
292
302
|
isLoaded: computed,
|
|
@@ -318,6 +328,11 @@ export abstract class Model {
|
|
|
318
328
|
});
|
|
319
329
|
}
|
|
320
330
|
|
|
331
|
+
/** Returns a stable identifier: the server-assigned `id` if available, otherwise the client-generated `_clientId`. */
|
|
332
|
+
get uniqueId(): string {
|
|
333
|
+
return this._id ?? this._clientId;
|
|
334
|
+
}
|
|
335
|
+
|
|
321
336
|
/** Returns the static `modelName` from the concrete subclass constructor. */
|
|
322
337
|
get modelName(): string {
|
|
323
338
|
return (this.constructor as typeof Model).modelName;
|
package/src/model/Snapshot.ts
CHANGED
|
@@ -62,6 +62,8 @@ function walk<V>(proto: object | null, key: symbol): Map<string, V> {
|
|
|
62
62
|
export class Snapshot<T extends Model = Model> {
|
|
63
63
|
/** Server-assigned id at snapshot time, or `null` for new records. */
|
|
64
64
|
readonly id: string | null;
|
|
65
|
+
/** Client-generated identifier, always present. */
|
|
66
|
+
readonly clientId: string;
|
|
65
67
|
/** `modelName` of the snapshotted record. */
|
|
66
68
|
readonly modelName: string;
|
|
67
69
|
/** Reference to the live record (read-only from adapter/serializer code). */
|
|
@@ -76,6 +78,7 @@ export class Snapshot<T extends Model = Model> {
|
|
|
76
78
|
constructor(record: T) {
|
|
77
79
|
this.record = record;
|
|
78
80
|
this.id = record.id;
|
|
81
|
+
this.clientId = record._clientId;
|
|
79
82
|
this.modelName = record.modelName;
|
|
80
83
|
|
|
81
84
|
const internal = record as unknown as {
|
|
@@ -195,7 +195,10 @@ export class ODataAdapter extends RestAdapter {
|
|
|
195
195
|
const data = record._data;
|
|
196
196
|
const body: Record<string, unknown> = {};
|
|
197
197
|
if (data) {
|
|
198
|
-
Object.
|
|
198
|
+
for (const key of Object.keys(data)) {
|
|
199
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
|
|
200
|
+
body[key] = data[key];
|
|
201
|
+
}
|
|
199
202
|
}
|
|
200
203
|
return body;
|
|
201
204
|
}
|
|
@@ -47,12 +47,8 @@ export class CacheHandler implements Handler {
|
|
|
47
47
|
|
|
48
48
|
private evictLRU(): void {
|
|
49
49
|
while (this.cache.size > this.maxSize) {
|
|
50
|
-
const firstKey = this.cache.keys().next().value;
|
|
51
|
-
|
|
52
|
-
this.cache.delete(firstKey);
|
|
53
|
-
} else {
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
50
|
+
const firstKey = this.cache.keys().next().value as string;
|
|
51
|
+
this.cache.delete(firstKey);
|
|
56
52
|
}
|
|
57
53
|
}
|
|
58
54
|
|
|
@@ -16,8 +16,11 @@ import { singleton } from 'tsyringe';
|
|
|
16
16
|
import {
|
|
17
17
|
ATTRIBUTES_META_KEY,
|
|
18
18
|
RELATIONSHIPS_META_KEY,
|
|
19
|
+
MODEL_OPTIONS_META_KEY,
|
|
19
20
|
type AttributeDef,
|
|
20
21
|
type AttributeDefinitionsMap,
|
|
22
|
+
type DiscriminatorDef,
|
|
23
|
+
type ModelOptions,
|
|
21
24
|
type RelationshipDef,
|
|
22
25
|
type RelationshipDefinitionsMap,
|
|
23
26
|
} from './types.js';
|
|
@@ -36,6 +39,14 @@ interface Entry {
|
|
|
36
39
|
attributes: AttributeDefinitionsMap;
|
|
37
40
|
/** Merged relationship definitions (ancestors → leaf, leaf wins). */
|
|
38
41
|
relationships: RelationshipDefinitionsMap;
|
|
42
|
+
/** When `true`, the model is abstract and cannot be instantiated directly. */
|
|
43
|
+
abstract?: boolean;
|
|
44
|
+
/** Discriminator configuration for polymorphic hierarchies. */
|
|
45
|
+
discriminator?: DiscriminatorDef;
|
|
46
|
+
/** Model name of the polymorphic root when this is a concrete child. */
|
|
47
|
+
polymorphicRoot?: string;
|
|
48
|
+
/** When `true`, the client-generated id is sent to the server on create. */
|
|
49
|
+
clientGeneratedIds?: boolean;
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
/**
|
|
@@ -87,7 +98,46 @@ export class SchemaService {
|
|
|
87
98
|
modelClass.prototype as object,
|
|
88
99
|
RELATIONSHIPS_META_KEY,
|
|
89
100
|
);
|
|
90
|
-
|
|
101
|
+
const entry: Entry = { modelClass, attributes, relationships };
|
|
102
|
+
|
|
103
|
+
const options = Reflect.getOwnMetadata(MODEL_OPTIONS_META_KEY, modelClass) as
|
|
104
|
+
| ModelOptions
|
|
105
|
+
| undefined;
|
|
106
|
+
if (options?.abstract) {
|
|
107
|
+
entry.abstract = true;
|
|
108
|
+
}
|
|
109
|
+
if (options?.discriminator) {
|
|
110
|
+
entry.discriminator = {
|
|
111
|
+
key: options.discriminator.key ?? 'type',
|
|
112
|
+
map: options.discriminator.map,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (options?.clientGeneratedIds) {
|
|
116
|
+
entry.clientGeneratedIds = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.entries.set(modelName, entry);
|
|
120
|
+
this.linkPolymorphicChild(modelName, modelClass);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* After registering a model, checks whether any already-registered
|
|
125
|
+
* polymorphic parent lists this model in its discriminator map and, if so,
|
|
126
|
+
* stores the `polymorphicRoot` back-link.
|
|
127
|
+
*/
|
|
128
|
+
private linkPolymorphicChild(childName: string, childClass: ModelClass): void {
|
|
129
|
+
for (const [parentName, parentEntry] of this.entries) {
|
|
130
|
+
if (!parentEntry.discriminator || parentName === childName) continue;
|
|
131
|
+
for (const factory of Object.values(parentEntry.discriminator.map)) {
|
|
132
|
+
if (factory() === childClass) {
|
|
133
|
+
const childEntry = this.entries.get(childName);
|
|
134
|
+
if (childEntry) {
|
|
135
|
+
childEntry.polymorphicRoot = parentName;
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
91
141
|
}
|
|
92
142
|
|
|
93
143
|
/**
|
|
@@ -102,6 +152,11 @@ export class SchemaService {
|
|
|
102
152
|
return entry.modelClass;
|
|
103
153
|
}
|
|
104
154
|
|
|
155
|
+
/** Returns all registered model names. */
|
|
156
|
+
registeredNames(): string[] {
|
|
157
|
+
return Array.from(this.entries.keys());
|
|
158
|
+
}
|
|
159
|
+
|
|
105
160
|
/** Returns `true` when a model class has been registered for `modelName`. */
|
|
106
161
|
doesTypeExist(modelName: string): boolean {
|
|
107
162
|
return this.entries.has(modelName);
|
|
@@ -158,4 +213,71 @@ export class SchemaService {
|
|
|
158
213
|
callback(name, meta);
|
|
159
214
|
}
|
|
160
215
|
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Returns the discriminator definition for `modelName`, or `undefined`
|
|
219
|
+
* when the model is not polymorphic.
|
|
220
|
+
*/
|
|
221
|
+
discriminatorFor(modelName: string): DiscriminatorDef | undefined {
|
|
222
|
+
return this.entries.get(modelName)?.discriminator;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Returns the polymorphic root model name for a concrete child, or `null`
|
|
227
|
+
* when `modelName` is not part of a polymorphic hierarchy.
|
|
228
|
+
*/
|
|
229
|
+
polymorphicRootFor(modelName: string): string | null {
|
|
230
|
+
return this.entries.get(modelName)?.polymorphicRoot ?? null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Returns `true` when `modelName` is declared abstract.
|
|
235
|
+
*/
|
|
236
|
+
isAbstract(modelName: string): boolean {
|
|
237
|
+
return this.entries.get(modelName)?.abstract === true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Returns `true` when the model uses client-generated ids. */
|
|
241
|
+
hasClientGeneratedIds(modelName: string): boolean {
|
|
242
|
+
return this.entries.get(modelName)?.clientGeneratedIds === true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Resolves the concrete model class for a polymorphic parent given a raw
|
|
247
|
+
* payload. Reads the discriminator key from the payload and returns the
|
|
248
|
+
* resolved model name and class.
|
|
249
|
+
*
|
|
250
|
+
* @throws when the discriminator key is missing from the payload.
|
|
251
|
+
* @throws when the discriminator value is not in the map.
|
|
252
|
+
* @returns `null` when `modelName` has no discriminator (not polymorphic).
|
|
253
|
+
*/
|
|
254
|
+
resolveConcreteModel(
|
|
255
|
+
modelName: string,
|
|
256
|
+
payload: Record<string, unknown>,
|
|
257
|
+
): { modelName: string; modelClass: ModelClass } | null {
|
|
258
|
+
const entry = this.entries.get(modelName);
|
|
259
|
+
if (!entry?.discriminator) return null;
|
|
260
|
+
|
|
261
|
+
const { key, map } = entry.discriminator;
|
|
262
|
+
const discriminatorValue = payload[key];
|
|
263
|
+
if (discriminatorValue === undefined || discriminatorValue === null) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`Missing discriminator key "${key}" in payload for polymorphic model "${modelName}".`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const valueString = String(discriminatorValue);
|
|
270
|
+
const factory = map[valueString];
|
|
271
|
+
if (!factory) {
|
|
272
|
+
const knownValues = Object.keys(map).join(', ');
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Unknown discriminator value "${valueString}" for model "${modelName}" `
|
|
275
|
+
+ `(key: "${key}"). Known values: ${knownValues}.`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const concreteClass = factory() as ModelClass;
|
|
280
|
+
const concreteName = concreteClass.modelName ?? valueString;
|
|
281
|
+
return { modelName: concreteName, modelClass: concreteClass };
|
|
282
|
+
}
|
|
161
283
|
}
|
package/src/schema/decorators.ts
CHANGED
|
@@ -14,8 +14,10 @@ import 'reflect-metadata';
|
|
|
14
14
|
import {
|
|
15
15
|
ATTRIBUTES_META_KEY,
|
|
16
16
|
RELATIONSHIPS_META_KEY,
|
|
17
|
+
MODEL_OPTIONS_META_KEY,
|
|
17
18
|
type AttributeDef,
|
|
18
19
|
type AttributeOptions,
|
|
20
|
+
type ModelOptions,
|
|
19
21
|
type RelationshipDef,
|
|
20
22
|
type RelationshipOptions,
|
|
21
23
|
} from './types.js';
|
|
@@ -160,3 +162,30 @@ export function hasMany(
|
|
|
160
162
|
): PropertyDecorator {
|
|
161
163
|
return makeRelationship('hasMany', type, options);
|
|
162
164
|
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Class decorator that attaches model options (name, abstract, discriminator)
|
|
168
|
+
* as reflect-metadata on the constructor. `SchemaService.registerModel` reads
|
|
169
|
+
* this metadata at registration time.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* @model({
|
|
174
|
+
* name: 'vehicle',
|
|
175
|
+
* abstract: true,
|
|
176
|
+
* discriminator: {
|
|
177
|
+
* key: 'type',
|
|
178
|
+
* map: { car: () => Car, motorcycle: () => Motorcycle },
|
|
179
|
+
* },
|
|
180
|
+
* })
|
|
181
|
+
* abstract class Vehicle extends Model { … }
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export function model(options: ModelOptions = {}): ClassDecorator {
|
|
185
|
+
return (target) => {
|
|
186
|
+
if (options.name) {
|
|
187
|
+
(target as unknown as { modelName: string }).modelName = options.name;
|
|
188
|
+
}
|
|
189
|
+
Reflect.defineMetadata(MODEL_OPTIONS_META_KEY, options, target);
|
|
190
|
+
};
|
|
191
|
+
}
|
package/src/schema/index.ts
CHANGED
package/src/schema/types.ts
CHANGED
|
@@ -58,9 +58,43 @@ export type AttributeDefinitionsMap = Map<string, AttributeDef>;
|
|
|
58
58
|
/** Relationship definitions keyed by property name. */
|
|
59
59
|
export type RelationshipDefinitionsMap = Map<string, RelationshipDef>;
|
|
60
60
|
|
|
61
|
+
/** Constructor type accepted by the discriminator map. */
|
|
62
|
+
export interface ModelConstructor {
|
|
63
|
+
new (...args: never[]): unknown;
|
|
64
|
+
modelName?: string;
|
|
65
|
+
prototype: unknown;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Discriminator configuration for polymorphic model hierarchies. */
|
|
69
|
+
export interface DiscriminatorDef {
|
|
70
|
+
/** Payload field that identifies the concrete type (defaults to `"type"`). */
|
|
71
|
+
key: string;
|
|
72
|
+
/** Maps discriminator values to lazy model constructor references. */
|
|
73
|
+
map: Record<string, () => ModelConstructor>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Options accepted by the `@model` class decorator. */
|
|
77
|
+
export interface ModelOptions {
|
|
78
|
+
/** Registered model name. */
|
|
79
|
+
name?: string;
|
|
80
|
+
/** When `true`, the model cannot be instantiated directly. */
|
|
81
|
+
abstract?: boolean;
|
|
82
|
+
/** Discriminator configuration for selecting concrete subclass at deserialization. */
|
|
83
|
+
discriminator?: {
|
|
84
|
+
/** Payload field that identifies the concrete type (defaults to `"type"`). */
|
|
85
|
+
key?: string;
|
|
86
|
+
/** Maps discriminator values to lazy model constructor references. */
|
|
87
|
+
map: Record<string, () => ModelConstructor>;
|
|
88
|
+
};
|
|
89
|
+
/** When `true`, the client-generated `_clientId` is sent to the server as the record id on create. */
|
|
90
|
+
clientGeneratedIds?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
61
93
|
/** Reflect-metadata key used to store attribute definitions on a prototype. */
|
|
62
94
|
export const ATTRIBUTES_META_KEY = Symbol('mobx-data:attributes');
|
|
63
95
|
/** Reflect-metadata key used to store relationship definitions on a prototype. */
|
|
64
96
|
export const RELATIONSHIPS_META_KEY = Symbol('mobx-data:relationships');
|
|
65
97
|
/** Reflect-metadata key used to store the registered model name on a class. */
|
|
66
98
|
export const MODEL_NAME_META_KEY = Symbol('mobx-data:modelName');
|
|
99
|
+
/** Reflect-metadata key used to store `@model` options on a class constructor. */
|
|
100
|
+
export const MODEL_OPTIONS_META_KEY = Symbol('mobx-data:modelOptions');
|