@iamjulianacosta/mobx-data 1.1.0 → 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.
Files changed (203) hide show
  1. package/README.md +273 -102
  2. package/dist/{CacheHandler-BTU_rYkv.js → CacheHandler-BhfbVHed.js} +17 -20
  3. package/dist/CacheHandler-BhfbVHed.js.map +1 -0
  4. package/dist/{CacheHandler-CXgY9IJo.cjs → CacheHandler-Q5VXOgh9.cjs} +2 -2
  5. package/dist/CacheHandler-Q5VXOgh9.cjs.map +1 -0
  6. package/dist/EmbeddedRecordsMixin-6mSCXsJ3.js +173 -0
  7. package/dist/EmbeddedRecordsMixin-6mSCXsJ3.js.map +1 -0
  8. package/dist/EmbeddedRecordsMixin-BkF7MdbY.cjs +2 -0
  9. package/dist/EmbeddedRecordsMixin-BkF7MdbY.cjs.map +1 -0
  10. package/dist/{JsonApiSerializer-BLoE046A.js → JsonApiSerializer-BV61cFAZ.js} +3 -3
  11. package/dist/JsonApiSerializer-BV61cFAZ.js.map +1 -0
  12. package/dist/{JsonApiSerializer-DKemcyw-.cjs → JsonApiSerializer-Dt_Y_FIo.cjs} +2 -2
  13. package/dist/JsonApiSerializer-Dt_Y_FIo.cjs.map +1 -0
  14. package/dist/JsonSerializer-BzUCyUSf.cjs +2 -0
  15. package/dist/JsonSerializer-BzUCyUSf.cjs.map +1 -0
  16. package/dist/JsonSerializer-CFqo6GjC.js +98 -0
  17. package/dist/JsonSerializer-CFqo6GjC.js.map +1 -0
  18. package/dist/MdqlMemoryExecutor-BUlsalKm.cjs +2 -0
  19. package/dist/MdqlMemoryExecutor-BUlsalKm.cjs.map +1 -0
  20. package/dist/MdqlMemoryExecutor-BWMP31zG.js +127 -0
  21. package/dist/MdqlMemoryExecutor-BWMP31zG.js.map +1 -0
  22. package/dist/{MemoryAdapter-Bp-BGHH3.js → MemoryAdapter-BW1HKixm.js} +2 -2
  23. package/dist/{MemoryAdapter-Bp-BGHH3.js.map → MemoryAdapter-BW1HKixm.js.map} +1 -1
  24. package/dist/{MemoryAdapter-DH-gzSSl.cjs → MemoryAdapter-C8iXAa2v.cjs} +2 -2
  25. package/dist/{MemoryAdapter-DH-gzSSl.cjs.map → MemoryAdapter-C8iXAa2v.cjs.map} +1 -1
  26. package/dist/{ODataAdapter-RQUjVTcf.js → ODataAdapter-CeBJblLQ.js} +25 -22
  27. package/dist/ODataAdapter-CeBJblLQ.js.map +1 -0
  28. package/dist/{ODataAdapter-CrDFvBEZ.cjs → ODataAdapter-DdE6MWkG.cjs} +2 -2
  29. package/dist/ODataAdapter-DdE6MWkG.cjs.map +1 -0
  30. package/dist/RestAdapter-D7GSrsJo.cjs +2 -0
  31. package/dist/RestAdapter-D7GSrsJo.cjs.map +1 -0
  32. package/dist/{RestAdapter-D6bGIHZT.js → RestAdapter-DYUoyV5h.js} +112 -77
  33. package/dist/RestAdapter-DYUoyV5h.js.map +1 -0
  34. package/dist/SchemaService-C_pkh-vI.js +180 -0
  35. package/dist/SchemaService-C_pkh-vI.js.map +1 -0
  36. package/dist/SchemaService-DbJLoYb9.cjs +2 -0
  37. package/dist/SchemaService-DbJLoYb9.cjs.map +1 -0
  38. package/dist/Serializer-Bap9U-kR.cjs +2 -0
  39. package/dist/Serializer-Bap9U-kR.cjs.map +1 -0
  40. package/dist/{Serializer-FxJbsZ50.js → Serializer-Ca6w_QNQ.js} +63 -49
  41. package/dist/Serializer-Ca6w_QNQ.js.map +1 -0
  42. package/dist/adapter/index.cjs +1 -1
  43. package/dist/adapter/index.js +2 -2
  44. package/dist/createStore-7PecKT54.cjs +2 -0
  45. package/dist/createStore-7PecKT54.cjs.map +1 -0
  46. package/dist/createStore-BfmRfZ_2.js +1229 -0
  47. package/dist/createStore-BfmRfZ_2.js.map +1 -0
  48. package/dist/date-Bj4O2W1F.js.map +1 -1
  49. package/dist/date-CRCe-9gf.cjs.map +1 -1
  50. package/dist/decorators-CKneHgoF.js +56 -0
  51. package/dist/decorators-CKneHgoF.js.map +1 -0
  52. package/dist/decorators-DCVYKzrL.cjs +2 -0
  53. package/dist/decorators-DCVYKzrL.cjs.map +1 -0
  54. package/dist/index.cjs +1 -1
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.ts +2 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +100 -90
  59. package/dist/index.js.map +1 -1
  60. package/dist/inspector/ConsoleInspector.d.ts +49 -0
  61. package/dist/inspector/ConsoleInspector.d.ts.map +1 -0
  62. package/dist/inspector/DevToolsBridge.d.ts +21 -0
  63. package/dist/inspector/DevToolsBridge.d.ts.map +1 -0
  64. package/dist/inspector/QueryParser.d.ts +21 -0
  65. package/dist/inspector/QueryParser.d.ts.map +1 -0
  66. package/dist/inspector/StoreInspector.d.ts +31 -0
  67. package/dist/inspector/StoreInspector.d.ts.map +1 -0
  68. package/dist/inspector/index.cjs +17 -0
  69. package/dist/inspector/index.cjs.map +1 -0
  70. package/dist/inspector/index.d.ts +9 -0
  71. package/dist/inspector/index.d.ts.map +1 -0
  72. package/dist/inspector/index.js +896 -0
  73. package/dist/inspector/index.js.map +1 -0
  74. package/dist/inspector/integration.d.ts +15 -0
  75. package/dist/inspector/integration.d.ts.map +1 -0
  76. package/dist/inspector/serialization.d.ts +7 -0
  77. package/dist/inspector/serialization.d.ts.map +1 -0
  78. package/dist/inspector/types.d.ts +139 -0
  79. package/dist/inspector/types.d.ts.map +1 -0
  80. package/dist/json-api/index.cjs +1 -1
  81. package/dist/json-api/index.js +1 -1
  82. package/dist/mdql/MdqlMemoryExecutor.d.ts +17 -0
  83. package/dist/mdql/MdqlMemoryExecutor.d.ts.map +1 -0
  84. package/dist/mdql/MdqlQueryBuilder.d.ts +38 -0
  85. package/dist/mdql/MdqlQueryBuilder.d.ts.map +1 -0
  86. package/dist/mdql/MdqlValidator.d.ts +13 -0
  87. package/dist/mdql/MdqlValidator.d.ts.map +1 -0
  88. package/dist/mdql/index.d.ts +6 -0
  89. package/dist/mdql/index.d.ts.map +1 -0
  90. package/dist/mdql/types.d.ts +48 -0
  91. package/dist/mdql/types.d.ts.map +1 -0
  92. package/dist/model/Model.d.ts +4 -0
  93. package/dist/model/Model.d.ts.map +1 -1
  94. package/dist/model/Snapshot.d.ts +2 -0
  95. package/dist/model/Snapshot.d.ts.map +1 -1
  96. package/dist/model/index.cjs +1 -1
  97. package/dist/model/index.js +1 -1
  98. package/dist/odata/ODataAdapter.d.ts.map +1 -1
  99. package/dist/odata/index.cjs +1 -1
  100. package/dist/odata/index.js +1 -1
  101. package/dist/relationships-BgM0NKdb.cjs +2 -0
  102. package/dist/relationships-BgM0NKdb.cjs.map +1 -0
  103. package/dist/{relationships-BEXANmWg.js → relationships-DvSi8fVN.js} +37 -28
  104. package/dist/relationships-DvSi8fVN.js.map +1 -0
  105. package/dist/request/CacheHandler.d.ts.map +1 -1
  106. package/dist/request/index.cjs +1 -1
  107. package/dist/request/index.js +1 -1
  108. package/dist/schema/SchemaService.d.ts +38 -1
  109. package/dist/schema/SchemaService.d.ts.map +1 -1
  110. package/dist/schema/decorators.d.ts +20 -1
  111. package/dist/schema/decorators.d.ts.map +1 -1
  112. package/dist/schema/index.cjs +1 -1
  113. package/dist/schema/index.d.ts +1 -1
  114. package/dist/schema/index.d.ts.map +1 -1
  115. package/dist/schema/index.js +10 -8
  116. package/dist/schema/types.d.ts +31 -0
  117. package/dist/schema/types.d.ts.map +1 -1
  118. package/dist/serializer/JsonSerializer.d.ts +2 -0
  119. package/dist/serializer/JsonSerializer.d.ts.map +1 -1
  120. package/dist/serializer/Serializer.d.ts +9 -0
  121. package/dist/serializer/Serializer.d.ts.map +1 -1
  122. package/dist/serializer/index.cjs +1 -1
  123. package/dist/serializer/index.js +6 -5
  124. package/dist/serializer/index.js.map +1 -1
  125. package/dist/store/Store.d.ts +3 -0
  126. package/dist/store/Store.d.ts.map +1 -1
  127. package/dist/store/createStore.d.ts +12 -0
  128. package/dist/store/createStore.d.ts.map +1 -0
  129. package/dist/store/index.cjs +1 -1
  130. package/dist/store/index.d.ts +1 -0
  131. package/dist/store/index.d.ts.map +1 -1
  132. package/dist/store/index.js +5 -4
  133. package/dist/types-CC2fG3FP.js +8 -0
  134. package/dist/types-CC2fG3FP.js.map +1 -0
  135. package/dist/types-DCLy5XYj.cjs +2 -0
  136. package/dist/types-DCLy5XYj.cjs.map +1 -0
  137. package/package.json +7 -1
  138. package/src/index.ts +3 -0
  139. package/src/inspector/ConsoleInspector.ts +470 -0
  140. package/src/inspector/DevToolsBridge.ts +214 -0
  141. package/src/inspector/QueryParser.ts +343 -0
  142. package/src/inspector/StoreInspector.ts +162 -0
  143. package/src/inspector/index.ts +20 -0
  144. package/src/inspector/integration.ts +56 -0
  145. package/src/inspector/serialization.ts +100 -0
  146. package/src/inspector/types.ts +161 -0
  147. package/src/mdql/MdqlMemoryExecutor.ts +229 -0
  148. package/src/mdql/MdqlQueryBuilder.ts +170 -0
  149. package/src/mdql/MdqlValidator.ts +193 -0
  150. package/src/mdql/index.ts +21 -0
  151. package/src/mdql/types.ts +107 -0
  152. package/src/model/Model.ts +15 -0
  153. package/src/model/Snapshot.ts +3 -0
  154. package/src/odata/ODataAdapter.ts +4 -1
  155. package/src/request/CacheHandler.ts +2 -6
  156. package/src/schema/SchemaService.ts +123 -1
  157. package/src/schema/decorators.ts +29 -0
  158. package/src/schema/index.ts +1 -1
  159. package/src/schema/types.ts +34 -0
  160. package/src/serializer/JsonSerializer.ts +14 -2
  161. package/src/serializer/Serializer.ts +24 -1
  162. package/src/store/Store.ts +57 -14
  163. package/src/store/createStore.ts +39 -0
  164. package/src/store/index.ts +1 -0
  165. package/dist/CacheHandler-BTU_rYkv.js.map +0 -1
  166. package/dist/CacheHandler-CXgY9IJo.cjs.map +0 -1
  167. package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs +0 -2
  168. package/dist/EmbeddedRecordsMixin-CBvqNdgC.cjs.map +0 -1
  169. package/dist/EmbeddedRecordsMixin-VoHluHCT.js +0 -261
  170. package/dist/EmbeddedRecordsMixin-VoHluHCT.js.map +0 -1
  171. package/dist/JsonApiSerializer-BLoE046A.js.map +0 -1
  172. package/dist/JsonApiSerializer-DKemcyw-.cjs.map +0 -1
  173. package/dist/ODataAdapter-CrDFvBEZ.cjs.map +0 -1
  174. package/dist/ODataAdapter-RQUjVTcf.js.map +0 -1
  175. package/dist/RestAdapter-CSoJg7D2.cjs +0 -2
  176. package/dist/RestAdapter-CSoJg7D2.cjs.map +0 -1
  177. package/dist/RestAdapter-D6bGIHZT.js.map +0 -1
  178. package/dist/SchemaService-DZwkFgZu.js +0 -102
  179. package/dist/SchemaService-DZwkFgZu.js.map +0 -1
  180. package/dist/SchemaService-Di_yjVzU.cjs +0 -2
  181. package/dist/SchemaService-Di_yjVzU.cjs.map +0 -1
  182. package/dist/Serializer-95gi5edy.cjs +0 -2
  183. package/dist/Serializer-95gi5edy.cjs.map +0 -1
  184. package/dist/Serializer-FxJbsZ50.js.map +0 -1
  185. package/dist/Store-Bm5JivTc.js +0 -957
  186. package/dist/Store-Bm5JivTc.js.map +0 -1
  187. package/dist/Store-DX9D0Mmy.cjs +0 -2
  188. package/dist/Store-DX9D0Mmy.cjs.map +0 -1
  189. package/dist/cache-utils-B2wFhisx.js +0 -39
  190. package/dist/cache-utils-B2wFhisx.js.map +0 -1
  191. package/dist/cache-utils-CSwsqOi3.cjs +0 -2
  192. package/dist/cache-utils-CSwsqOi3.cjs.map +0 -1
  193. package/dist/decorators-HQ1KnRdh.cjs +0 -2
  194. package/dist/decorators-HQ1KnRdh.cjs.map +0 -1
  195. package/dist/decorators-Zr35qr6A.js +0 -50
  196. package/dist/decorators-Zr35qr6A.js.map +0 -1
  197. package/dist/relationships-B55LBaCW.cjs +0 -2
  198. package/dist/relationships-B55LBaCW.cjs.map +0 -1
  199. package/dist/relationships-BEXANmWg.js.map +0 -1
  200. package/dist/types-C9NB2gRj.js +0 -7
  201. package/dist/types-C9NB2gRj.js.map +0 -1
  202. package/dist/types-uWOXMPWW.cjs +0 -2
  203. 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
+ }