@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,343 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MdqlQueryObject,
|
|
3
|
+
MdqlFilterNode,
|
|
4
|
+
MdqlCondition,
|
|
5
|
+
MdqlLogicalGroup,
|
|
6
|
+
MdqlOperator,
|
|
7
|
+
MdqlOrderByClause,
|
|
8
|
+
MdqlSortDirection,
|
|
9
|
+
} from '@mobx-data/mdql';
|
|
10
|
+
|
|
11
|
+
interface Token {
|
|
12
|
+
type: 'keyword' | 'operator' | 'string' | 'number' | 'identifier' | 'null' | 'bracket';
|
|
13
|
+
value: string;
|
|
14
|
+
raw: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const KEYWORD_SET = new Set([
|
|
18
|
+
'where', 'and', 'or', 'not', 'order', 'by', 'sort',
|
|
19
|
+
'limit', 'offset', 'is', 'null', 'in', 'between',
|
|
20
|
+
'asc', 'desc', 'contains', 'startswith', 'endswith',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const OPERATOR_MAP: Record<string, MdqlOperator> = {
|
|
24
|
+
'=': 'equals',
|
|
25
|
+
'==': 'equals',
|
|
26
|
+
'!=': 'notEquals',
|
|
27
|
+
'<>': 'notEquals',
|
|
28
|
+
'>': 'greaterThan',
|
|
29
|
+
'>=': 'greaterThanOrEquals',
|
|
30
|
+
'<': 'lessThan',
|
|
31
|
+
'<=': 'lessThanOrEquals',
|
|
32
|
+
'~': 'contains',
|
|
33
|
+
'^=': 'startsWith',
|
|
34
|
+
'$=': 'endsWith',
|
|
35
|
+
'contains': 'contains',
|
|
36
|
+
'startswith': 'startsWith',
|
|
37
|
+
'endswith': 'endsWith',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const OPERATOR_CHARS = new Set(['=', '!', '<', '>', '~', '^', '$']);
|
|
41
|
+
|
|
42
|
+
export class QueryParser {
|
|
43
|
+
static parse(input: string): MdqlQueryObject {
|
|
44
|
+
const parser = new QueryParser(input);
|
|
45
|
+
return parser.parseQuery();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private tokens: Token[];
|
|
49
|
+
private position: number;
|
|
50
|
+
|
|
51
|
+
private constructor(input: string) {
|
|
52
|
+
this.tokens = QueryParser.tokenize(input);
|
|
53
|
+
this.position = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private static tokenize(input: string): Token[] {
|
|
57
|
+
const tokens: Token[] = [];
|
|
58
|
+
let index = 0;
|
|
59
|
+
|
|
60
|
+
while (index < input.length) {
|
|
61
|
+
if (input[index] === ' ' || input[index] === '\t') {
|
|
62
|
+
index++;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (input[index] === '[' || input[index] === ']' || input[index] === ',') {
|
|
67
|
+
tokens.push({ type: 'bracket', value: input[index]!, raw: input[index] });
|
|
68
|
+
index++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (input[index] === '"' || input[index] === "'") {
|
|
73
|
+
const quote = input[index]!;
|
|
74
|
+
let value = '';
|
|
75
|
+
index++;
|
|
76
|
+
while (index < input.length && input[index] !== quote) {
|
|
77
|
+
if (input[index] === '\\' && index + 1 < input.length) {
|
|
78
|
+
index++;
|
|
79
|
+
}
|
|
80
|
+
value += input[index];
|
|
81
|
+
index++;
|
|
82
|
+
}
|
|
83
|
+
index++;
|
|
84
|
+
tokens.push({ type: 'string', value, raw: value });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (OPERATOR_CHARS.has(input[index]!)) {
|
|
89
|
+
let operator = '';
|
|
90
|
+
while (index < input.length && OPERATOR_CHARS.has(input[index]!)) {
|
|
91
|
+
operator += input[index];
|
|
92
|
+
index++;
|
|
93
|
+
}
|
|
94
|
+
tokens.push({ type: 'operator', value: operator, raw: operator });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let word = '';
|
|
99
|
+
while (index < input.length && input[index] !== ' ' && input[index] !== '\t'
|
|
100
|
+
&& !OPERATOR_CHARS.has(input[index]!) && input[index] !== '[' && input[index] !== ']' && input[index] !== ',') {
|
|
101
|
+
word += input[index];
|
|
102
|
+
index++;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (word === '') {
|
|
106
|
+
index++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const lower = word.toLowerCase();
|
|
111
|
+
|
|
112
|
+
if (lower === 'null') {
|
|
113
|
+
tokens.push({ type: 'null', value: 'null', raw: null });
|
|
114
|
+
} else if (KEYWORD_SET.has(lower)) {
|
|
115
|
+
tokens.push({ type: 'keyword', value: lower, raw: word });
|
|
116
|
+
} else {
|
|
117
|
+
const numberValue = Number(word);
|
|
118
|
+
if (!Number.isNaN(numberValue) && word !== '') {
|
|
119
|
+
tokens.push({ type: 'number', value: word, raw: numberValue });
|
|
120
|
+
} else {
|
|
121
|
+
tokens.push({ type: 'identifier', value: word, raw: word });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return tokens;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private peek(): Token | null {
|
|
130
|
+
return this.tokens[this.position] ?? null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private advance(): Token | null {
|
|
134
|
+
const token = this.tokens[this.position] ?? null;
|
|
135
|
+
this.position++;
|
|
136
|
+
return token;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private expect(type: string, value?: string): Token {
|
|
140
|
+
const token = this.advance();
|
|
141
|
+
if (!token || token.type !== type || (value !== undefined && token.value !== value)) {
|
|
142
|
+
const got = token ? `${token.type}:${token.value}` : 'end of input';
|
|
143
|
+
throw new Error(`Expected ${type}${value ? `:${value}` : ''}, got ${got}`);
|
|
144
|
+
}
|
|
145
|
+
return token;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private expectNullToken(): void {
|
|
149
|
+
const token = this.advance();
|
|
150
|
+
if (!token || (token.type !== 'null' && token.value !== 'null')) {
|
|
151
|
+
const got = token ? `${token.type}:${token.value}` : 'end of input';
|
|
152
|
+
throw new Error(`Expected null, got ${got}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private matchKeyword(keyword: string): boolean {
|
|
157
|
+
const token = this.peek();
|
|
158
|
+
if (token?.type === 'keyword' && token.value === keyword) {
|
|
159
|
+
this.advance();
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private parseQuery(): MdqlQueryObject {
|
|
166
|
+
const modelToken = this.advance();
|
|
167
|
+
if (!modelToken) throw new Error('Expected model name');
|
|
168
|
+
const modelName = modelToken.value;
|
|
169
|
+
|
|
170
|
+
let filters: MdqlLogicalGroup = { kind: 'and', children: [] };
|
|
171
|
+
const orderBy: MdqlOrderByClause[] = [];
|
|
172
|
+
let limit: number | null = null;
|
|
173
|
+
let offset: number | null = null;
|
|
174
|
+
|
|
175
|
+
if (this.matchKeyword('where')) {
|
|
176
|
+
filters = this.parseFilterExpression();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.matchKeyword('order')) {
|
|
180
|
+
this.expect('keyword', 'by');
|
|
181
|
+
this.parseOrderBy(orderBy);
|
|
182
|
+
} else if (this.matchKeyword('sort')) {
|
|
183
|
+
this.parseOrderBy(orderBy);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (this.matchKeyword('limit')) {
|
|
187
|
+
const token = this.expect('number');
|
|
188
|
+
limit = token.raw as number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (this.matchKeyword('offset')) {
|
|
192
|
+
const token = this.expect('number');
|
|
193
|
+
offset = token.raw as number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { modelName, filters, orderBy, limit, offset, includes: [] };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private parseFilterExpression(): MdqlLogicalGroup {
|
|
200
|
+
const first = this.parseCondition();
|
|
201
|
+
const children: MdqlFilterNode[] = [first];
|
|
202
|
+
let groupKind: 'and' | 'or' = 'and';
|
|
203
|
+
|
|
204
|
+
while (this.peek()) {
|
|
205
|
+
const token = this.peek()!;
|
|
206
|
+
if (token.type === 'keyword' && token.value === 'and') {
|
|
207
|
+
this.advance();
|
|
208
|
+
groupKind = 'and';
|
|
209
|
+
children.push(this.parseCondition());
|
|
210
|
+
} else if (token.type === 'keyword' && token.value === 'or') {
|
|
211
|
+
this.advance();
|
|
212
|
+
groupKind = 'or';
|
|
213
|
+
children.push(this.parseCondition());
|
|
214
|
+
} else {
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (children.length === 1 && first.kind !== 'condition') {
|
|
220
|
+
return first as MdqlLogicalGroup;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { kind: groupKind, children };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private parseCondition(): MdqlFilterNode {
|
|
227
|
+
if (this.matchKeyword('not')) {
|
|
228
|
+
const child = this.parseCondition();
|
|
229
|
+
return { kind: 'not', children: [child] };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const fieldToken = this.advance();
|
|
233
|
+
if (!fieldToken) throw new Error('Expected field name');
|
|
234
|
+
const field = fieldToken.value;
|
|
235
|
+
|
|
236
|
+
if (this.peek()?.type === 'keyword' && this.peek()?.value === 'is') {
|
|
237
|
+
this.advance();
|
|
238
|
+
if (this.matchKeyword('not')) {
|
|
239
|
+
this.expectNullToken();
|
|
240
|
+
return { kind: 'condition', field, operator: 'isNotNull', value: undefined };
|
|
241
|
+
}
|
|
242
|
+
this.expectNullToken();
|
|
243
|
+
return { kind: 'condition', field, operator: 'isNull', value: undefined };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (this.peek()?.type === 'keyword' && this.peek()?.value === 'in') {
|
|
247
|
+
this.advance();
|
|
248
|
+
const values = this.parseArray();
|
|
249
|
+
return { kind: 'condition', field, operator: 'in', value: values };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (this.peek()?.type === 'keyword' && this.peek()?.value === 'between') {
|
|
253
|
+
this.advance();
|
|
254
|
+
const low = this.parseValue();
|
|
255
|
+
const high = this.parseValue();
|
|
256
|
+
return { kind: 'condition', field, operator: 'between', value: [low, high] };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const operatorToken = this.advance();
|
|
260
|
+
if (!operatorToken) throw new Error(`Expected operator after field "${field}"`);
|
|
261
|
+
|
|
262
|
+
let operator: MdqlOperator;
|
|
263
|
+
if (operatorToken.type === 'operator') {
|
|
264
|
+
operator = OPERATOR_MAP[operatorToken.value] as MdqlOperator;
|
|
265
|
+
if (!operator) throw new Error(`Unknown operator: ${operatorToken.value}`);
|
|
266
|
+
} else if (operatorToken.type === 'keyword' && OPERATOR_MAP[operatorToken.value]) {
|
|
267
|
+
operator = OPERATOR_MAP[operatorToken.value] as MdqlOperator;
|
|
268
|
+
} else {
|
|
269
|
+
throw new Error(`Expected operator, got ${operatorToken.type}:${operatorToken.value}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const value = this.parseValue();
|
|
273
|
+
|
|
274
|
+
return { kind: 'condition', field, operator, value } as MdqlCondition;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private parseOrderBy(orderBy: MdqlOrderByClause[]): void {
|
|
278
|
+
const fieldToken = this.advance();
|
|
279
|
+
if (!fieldToken) throw new Error('Expected field name after order by / sort');
|
|
280
|
+
orderBy.push({
|
|
281
|
+
field: fieldToken.value,
|
|
282
|
+
direction: this.parseDirection(),
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
while (this.peek()?.type === 'bracket' && this.peek()?.value === ',') {
|
|
286
|
+
this.advance();
|
|
287
|
+
const nextField = this.advance();
|
|
288
|
+
if (!nextField) break;
|
|
289
|
+
orderBy.push({
|
|
290
|
+
field: nextField.value,
|
|
291
|
+
direction: this.parseDirection(),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private parseDirection(): MdqlSortDirection {
|
|
297
|
+
const token = this.peek();
|
|
298
|
+
if (token?.type === 'keyword' && (token.value === 'asc' || token.value === 'desc')) {
|
|
299
|
+
this.advance();
|
|
300
|
+
return token.value as MdqlSortDirection;
|
|
301
|
+
}
|
|
302
|
+
return 'asc';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private parseValue(): unknown {
|
|
306
|
+
const token = this.advance();
|
|
307
|
+
if (!token) throw new Error('Expected value');
|
|
308
|
+
|
|
309
|
+
if (token.type === 'string') return token.raw;
|
|
310
|
+
if (token.type === 'number') return token.raw;
|
|
311
|
+
if (token.type === 'null') return null;
|
|
312
|
+
if (token.type === 'keyword' && token.value === 'null') return null;
|
|
313
|
+
if (token.type === 'identifier') return token.value;
|
|
314
|
+
|
|
315
|
+
throw new Error(`Unexpected value token: ${token.type}:${token.value}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private parseArray(): unknown[] {
|
|
319
|
+
const values: unknown[] = [];
|
|
320
|
+
if (this.peek()?.type === 'bracket' && this.peek()?.value === '[') {
|
|
321
|
+
this.advance();
|
|
322
|
+
while (this.peek() && !(this.peek()?.type === 'bracket' && this.peek()?.value === ']')) {
|
|
323
|
+
if (this.peek()?.type === 'bracket' && this.peek()?.value === ',') {
|
|
324
|
+
this.advance();
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
values.push(this.parseValue());
|
|
328
|
+
}
|
|
329
|
+
if (this.peek()?.type === 'bracket' && this.peek()?.value === ']') {
|
|
330
|
+
this.advance();
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
while (this.peek() && this.peek()?.type !== 'keyword') {
|
|
334
|
+
if (this.peek()?.type === 'bracket' && this.peek()?.value === ',') {
|
|
335
|
+
this.advance();
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
values.push(this.parseValue());
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return values;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { reaction } from 'mobx';
|
|
2
|
+
import type { Model } from '@mobx-data/model';
|
|
3
|
+
import type { Store } from '@mobx-data/store';
|
|
4
|
+
import type { MdqlQueryObject } from '@mobx-data/mdql';
|
|
5
|
+
import { MdqlMemoryExecutor } from '../mdql/MdqlMemoryExecutor.js';
|
|
6
|
+
import { QueryParser } from './QueryParser.js';
|
|
7
|
+
import type {
|
|
8
|
+
StoreSummary,
|
|
9
|
+
RecordSummary,
|
|
10
|
+
RecordDetail,
|
|
11
|
+
SchemaInfo,
|
|
12
|
+
InspectorChangeEvent,
|
|
13
|
+
} from './types.js';
|
|
14
|
+
import { summarizeRecord, detailRecord, extractSchemaInfo } from './serialization.js';
|
|
15
|
+
|
|
16
|
+
export class StoreInspector {
|
|
17
|
+
private readonly store: Store;
|
|
18
|
+
|
|
19
|
+
constructor(store: Store) {
|
|
20
|
+
this.store = store;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
summary(): StoreSummary {
|
|
24
|
+
const types: StoreSummary['types'] = [];
|
|
25
|
+
let totalRecords = 0;
|
|
26
|
+
for (const modelName of this.store.schema.registeredNames()) {
|
|
27
|
+
const count = this.store.peekAll(modelName).length;
|
|
28
|
+
if (count > 0) {
|
|
29
|
+
types.push({ modelName, count });
|
|
30
|
+
}
|
|
31
|
+
totalRecords += count;
|
|
32
|
+
}
|
|
33
|
+
types.sort((a, b) => a.modelName.localeCompare(b.modelName));
|
|
34
|
+
return { types, totalRecords };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
records(modelName: string): RecordSummary[] {
|
|
38
|
+
return this.store.peekAll(modelName).toArray().map(summarizeRecord);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
record(modelName: string, id: string): RecordDetail | null {
|
|
42
|
+
const found = this.store.identityMap.get(modelName, id);
|
|
43
|
+
if (!found) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return detailRecord(found);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
dirty(): RecordSummary[] {
|
|
50
|
+
return this.allRecordsWhere((r) => r.isDirty);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
newRecords(): RecordSummary[] {
|
|
54
|
+
return this.allRecordsWhere((r) => r.isNew);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
errored(): RecordSummary[] {
|
|
58
|
+
return this.allRecordsWhere((r) => r.isError || !r.isValid);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
saving(): RecordSummary[] {
|
|
62
|
+
return this.allRecordsWhere((r) => r.isSaving);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
schema(modelName: string): SchemaInfo {
|
|
66
|
+
return extractSchemaInfo(this.store, modelName);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
types(): string[] {
|
|
70
|
+
return this.store.schema.registeredNames().sort();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
query(text: string): Model[] {
|
|
74
|
+
const queryObject = QueryParser.parse(text);
|
|
75
|
+
const items = this.store.peekAll(queryObject.modelName).toArray();
|
|
76
|
+
return MdqlMemoryExecutor.executeMany(items, queryObject);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
queryWithMeta(text: string): { modelName: string; results: Model[] } {
|
|
80
|
+
const queryObject = QueryParser.parse(text);
|
|
81
|
+
const items = this.store.peekAll(queryObject.modelName).toArray();
|
|
82
|
+
return {
|
|
83
|
+
modelName: queryObject.modelName,
|
|
84
|
+
results: MdqlMemoryExecutor.executeMany(items, queryObject),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
liveModels(modelName: string): Model[] {
|
|
89
|
+
return this.store.peekAll(modelName).toArray();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
liveModel(modelName: string, id: string): Model | null {
|
|
93
|
+
return this.store.peekRecord(modelName, id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
snapshot(): { records: Record<string, unknown[]> } {
|
|
97
|
+
return this.store.serialize();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
observe(callback: (event: InspectorChangeEvent) => void): () => void {
|
|
101
|
+
let previousState = this.captureState();
|
|
102
|
+
|
|
103
|
+
return reaction(
|
|
104
|
+
() => this.captureState(),
|
|
105
|
+
(currentState) => {
|
|
106
|
+
this.diffAndEmit(previousState, currentState, callback);
|
|
107
|
+
previousState = currentState;
|
|
108
|
+
},
|
|
109
|
+
{ delay: 100 },
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private allRecordsWhere(predicate: (record: Model) => boolean): RecordSummary[] {
|
|
114
|
+
const results: RecordSummary[] = [];
|
|
115
|
+
for (const modelName of this.store.schema.registeredNames()) {
|
|
116
|
+
for (const record of this.store.peekAll(modelName).toArray()) {
|
|
117
|
+
if (predicate(record)) {
|
|
118
|
+
results.push(summarizeRecord(record));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return results;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private captureState(): Map<string, Map<string, string>> {
|
|
126
|
+
const state = new Map<string, Map<string, string>>();
|
|
127
|
+
for (const [modelName, bucket] of this.store.identityMap._buckets) {
|
|
128
|
+
const records = new Map<string, string>();
|
|
129
|
+
for (const [id, record] of bucket) {
|
|
130
|
+
records.set(id, record.currentState);
|
|
131
|
+
}
|
|
132
|
+
state.set(modelName, records);
|
|
133
|
+
}
|
|
134
|
+
return state;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private diffAndEmit(
|
|
138
|
+
previous: Map<string, Map<string, string>>,
|
|
139
|
+
current: Map<string, Map<string, string>>,
|
|
140
|
+
callback: (event: InspectorChangeEvent) => void,
|
|
141
|
+
): void {
|
|
142
|
+
for (const [modelName, currentBucket] of current) {
|
|
143
|
+
const previousBucket = previous.get(modelName);
|
|
144
|
+
for (const [id, currentStateValue] of currentBucket) {
|
|
145
|
+
if (!previousBucket?.has(id)) {
|
|
146
|
+
callback({ type: 'added', modelName, id });
|
|
147
|
+
} else if (previousBucket.get(id) !== currentStateValue) {
|
|
148
|
+
callback({ type: 'updated', modelName, id });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const [modelName, previousBucket] of previous) {
|
|
154
|
+
const currentBucket = current.get(modelName);
|
|
155
|
+
for (const [id] of previousBucket) {
|
|
156
|
+
if (!currentBucket?.has(id)) {
|
|
157
|
+
callback({ type: 'removed', modelName, id });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { StoreInspector } from './StoreInspector.js';
|
|
2
|
+
export { ConsoleInspector } from './ConsoleInspector.js';
|
|
3
|
+
export type { ResultSet, RecordResult } from './ConsoleInspector.js';
|
|
4
|
+
export { QueryParser } from './QueryParser.js';
|
|
5
|
+
export { DevToolsBridge } from './DevToolsBridge.js';
|
|
6
|
+
export type { DevToolsHook } from './DevToolsBridge.js';
|
|
7
|
+
export {
|
|
8
|
+
enableConsoleInspector,
|
|
9
|
+
enableDevTools,
|
|
10
|
+
enableInspector,
|
|
11
|
+
} from './integration.js';
|
|
12
|
+
export type {
|
|
13
|
+
StoreSummary,
|
|
14
|
+
RecordSummary,
|
|
15
|
+
RecordDetail,
|
|
16
|
+
SchemaInfo,
|
|
17
|
+
InspectorChangeEvent,
|
|
18
|
+
DevToolsMessage,
|
|
19
|
+
DevToolsPayload,
|
|
20
|
+
} from './types.js';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Store } from '@mobx-data/store';
|
|
2
|
+
import { ConsoleInspector } from './ConsoleInspector.js';
|
|
3
|
+
import { DevToolsBridge } from './DevToolsBridge.js';
|
|
4
|
+
|
|
5
|
+
let bridge: DevToolsBridge | null = null;
|
|
6
|
+
let storeCounter = 0;
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface Window {
|
|
10
|
+
$mobxData?: ConsoleInspector;
|
|
11
|
+
$m?: (input: string) => unknown;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function enableConsoleInspector(
|
|
16
|
+
store: Store,
|
|
17
|
+
name: string = 'default',
|
|
18
|
+
): ConsoleInspector {
|
|
19
|
+
const inspector = new ConsoleInspector(store, name);
|
|
20
|
+
if (typeof window !== 'undefined') {
|
|
21
|
+
window.$mobxData = inspector;
|
|
22
|
+
window.$m = (input: string) => inspector.command(input);
|
|
23
|
+
}
|
|
24
|
+
return inspector;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function enableDevTools(
|
|
28
|
+
store: Store,
|
|
29
|
+
storeId?: string,
|
|
30
|
+
): () => void {
|
|
31
|
+
if (typeof window === 'undefined') {
|
|
32
|
+
return () => {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!bridge) {
|
|
36
|
+
bridge = new DevToolsBridge();
|
|
37
|
+
bridge.install();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
storeCounter += 1;
|
|
41
|
+
const resolvedId = storeId ?? `store-${storeCounter}`;
|
|
42
|
+
bridge.registerStore(resolvedId, store);
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
bridge?.unregisterStore(resolvedId);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function enableInspector(
|
|
50
|
+
store: Store,
|
|
51
|
+
name: string = 'default',
|
|
52
|
+
): { consoleInspector: ConsoleInspector; cleanup: () => void } {
|
|
53
|
+
const consoleInspector = enableConsoleInspector(store, name);
|
|
54
|
+
const cleanup = enableDevTools(store, name);
|
|
55
|
+
return { consoleInspector, cleanup };
|
|
56
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Model, RelationshipRef } from '@mobx-data/model';
|
|
2
|
+
import type { Store } from '@mobx-data/store';
|
|
3
|
+
import type { RecordSummary, RecordDetail, SchemaInfo } from './types.js';
|
|
4
|
+
|
|
5
|
+
interface ModelInternals {
|
|
6
|
+
_data: Record<string, unknown>;
|
|
7
|
+
_originalData: Record<string, unknown>;
|
|
8
|
+
_relationships: Map<string, RelationshipRef>;
|
|
9
|
+
_clientId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function internals(record: Model): ModelInternals {
|
|
13
|
+
return record as unknown as ModelInternals;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function summarizeRecord(record: Model): RecordSummary {
|
|
17
|
+
return {
|
|
18
|
+
modelName: record.modelName,
|
|
19
|
+
id: record.id,
|
|
20
|
+
clientId: internals(record)._clientId,
|
|
21
|
+
currentState: record.currentState,
|
|
22
|
+
isNew: record.isNew,
|
|
23
|
+
isDirty: record.isDirty,
|
|
24
|
+
isDeleted: record.isDeleted,
|
|
25
|
+
isError: record.isError,
|
|
26
|
+
isLoading: record.isLoading,
|
|
27
|
+
isSaving: record.isSaving,
|
|
28
|
+
hasDirtyAttributes: record.hasDirtyAttributes,
|
|
29
|
+
hasErrors: !record.isValid,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function detailRecord(record: Model): RecordDetail {
|
|
34
|
+
const internal = internals(record);
|
|
35
|
+
const relationships: Record<string, unknown> = {};
|
|
36
|
+
if (internal._relationships) {
|
|
37
|
+
for (const [name, reference] of internal._relationships) {
|
|
38
|
+
relationships[name] = reference;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const errors: Array<{ attribute: string; messages: string[] }> = [];
|
|
43
|
+
for (const [attribute, errorMessages] of record.errors) {
|
|
44
|
+
errors.push({
|
|
45
|
+
attribute,
|
|
46
|
+
messages: errorMessages.map((e) => e.message),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const attributes = Object.create(null) as Record<string, unknown>;
|
|
51
|
+
for (const key of Object.keys(internal._data)) {
|
|
52
|
+
if (key === '__proto__' || key === 'constructor') continue;
|
|
53
|
+
attributes[key] = internal._data[key];
|
|
54
|
+
}
|
|
55
|
+
const originalAttributes = Object.create(null) as Record<string, unknown>;
|
|
56
|
+
for (const key of Object.keys(internal._originalData)) {
|
|
57
|
+
if (key === '__proto__' || key === 'constructor') continue;
|
|
58
|
+
originalAttributes[key] = internal._originalData[key];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
...summarizeRecord(record),
|
|
63
|
+
attributes,
|
|
64
|
+
originalAttributes,
|
|
65
|
+
changedAttributes: record.changedAttributes(),
|
|
66
|
+
relationships,
|
|
67
|
+
errors,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function extractSchemaInfo(store: Store, modelName: string): SchemaInfo {
|
|
72
|
+
const attributeDefinitions = store.schema.attributesDefinitionFor(modelName);
|
|
73
|
+
const relationshipDefinitions = store.schema.relationshipsDefinitionFor(modelName);
|
|
74
|
+
const discriminator = store.schema.discriminatorFor(modelName);
|
|
75
|
+
|
|
76
|
+
const attributes: SchemaInfo['attributes'] = [];
|
|
77
|
+
for (const [name, definition] of attributeDefinitions) {
|
|
78
|
+
attributes.push({ name, type: definition.type });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const relationships: SchemaInfo['relationships'] = [];
|
|
82
|
+
for (const [name, definition] of relationshipDefinitions) {
|
|
83
|
+
relationships.push({
|
|
84
|
+
name,
|
|
85
|
+
kind: definition.kind,
|
|
86
|
+
type: definition.type,
|
|
87
|
+
async: definition.options.async ?? false,
|
|
88
|
+
inverse: definition.options.inverse ?? null,
|
|
89
|
+
polymorphic: definition.options.polymorphic ?? false,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
modelName,
|
|
95
|
+
isAbstract: store.schema.isAbstract(modelName),
|
|
96
|
+
discriminator: discriminator ? { key: discriminator.key } : null,
|
|
97
|
+
attributes,
|
|
98
|
+
relationships,
|
|
99
|
+
};
|
|
100
|
+
}
|