mongo-query-dsl 1.0.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 (59) hide show
  1. package/README.md +339 -0
  2. package/dist/examples/usage.d.ts +13 -0
  3. package/dist/examples/usage.d.ts.map +1 -0
  4. package/dist/examples/usage.js +162 -0
  5. package/dist/examples/usage.js.map +1 -0
  6. package/dist/src/__tests__/executor.test.d.ts +2 -0
  7. package/dist/src/__tests__/executor.test.d.ts.map +1 -0
  8. package/dist/src/__tests__/executor.test.js +602 -0
  9. package/dist/src/__tests__/executor.test.js.map +1 -0
  10. package/dist/src/__tests__/integration.test.d.ts +2 -0
  11. package/dist/src/__tests__/integration.test.d.ts.map +1 -0
  12. package/dist/src/__tests__/integration.test.js +320 -0
  13. package/dist/src/__tests__/integration.test.js.map +1 -0
  14. package/dist/src/__tests__/parser.test.d.ts +2 -0
  15. package/dist/src/__tests__/parser.test.d.ts.map +1 -0
  16. package/dist/src/__tests__/parser.test.js +320 -0
  17. package/dist/src/__tests__/parser.test.js.map +1 -0
  18. package/dist/src/__tests__/resolver.test.d.ts +2 -0
  19. package/dist/src/__tests__/resolver.test.d.ts.map +1 -0
  20. package/dist/src/__tests__/resolver.test.js +178 -0
  21. package/dist/src/__tests__/resolver.test.js.map +1 -0
  22. package/dist/src/errors.d.ts +26 -0
  23. package/dist/src/errors.d.ts.map +1 -0
  24. package/dist/src/errors.js +44 -0
  25. package/dist/src/errors.js.map +1 -0
  26. package/dist/src/executor.d.ts +32 -0
  27. package/dist/src/executor.d.ts.map +1 -0
  28. package/dist/src/executor.js +214 -0
  29. package/dist/src/executor.js.map +1 -0
  30. package/dist/src/index.d.ts +43 -0
  31. package/dist/src/index.d.ts.map +1 -0
  32. package/dist/src/index.js +120 -0
  33. package/dist/src/index.js.map +1 -0
  34. package/dist/src/parser.d.ts +42 -0
  35. package/dist/src/parser.d.ts.map +1 -0
  36. package/dist/src/parser.js +194 -0
  37. package/dist/src/parser.js.map +1 -0
  38. package/dist/src/resolver.d.ts +22 -0
  39. package/dist/src/resolver.d.ts.map +1 -0
  40. package/dist/src/resolver.js +55 -0
  41. package/dist/src/resolver.js.map +1 -0
  42. package/dist/src/types.d.ts +92 -0
  43. package/dist/src/types.d.ts.map +1 -0
  44. package/dist/src/types.js +3 -0
  45. package/dist/src/types.js.map +1 -0
  46. package/examples/usage.ts +190 -0
  47. package/jest.config.js +13 -0
  48. package/package.json +34 -0
  49. package/src/__tests__/executor.test.ts +694 -0
  50. package/src/__tests__/integration.test.ts +392 -0
  51. package/src/__tests__/parser.test.ts +377 -0
  52. package/src/__tests__/resolver.test.ts +218 -0
  53. package/src/errors.ts +47 -0
  54. package/src/executor.ts +276 -0
  55. package/src/index.ts +118 -0
  56. package/src/parser.ts +216 -0
  57. package/src/resolver.ts +58 -0
  58. package/src/types.ts +107 -0
  59. package/tsconfig.json +30 -0
@@ -0,0 +1,392 @@
1
+ import { NoQL } from "../index";
2
+ import { ParseError } from "../errors";
3
+ import { DatabaseProvider, JsonQuery } from "../types";
4
+
5
+ // Mock database provider for testing NoQL integration
6
+ class MockNoQLDatabaseProvider implements DatabaseProvider {
7
+ private mockData: Map<string, Map<string, Record<string, any>[]>> = new Map();
8
+
9
+ constructor() {
10
+ // Initialize with sample data
11
+ this.addData("testDb", "users", [
12
+ { _id: "user_1", name: "Alice", email: "alice@test.com", role: "admin" },
13
+ { _id: "user_2", name: "Bob", email: "bob@test.com", role: "user" },
14
+ ]);
15
+
16
+ this.addData("testDb", "posts", [
17
+ { _id: "post_1", title: "First Post", authorId: "user_1" },
18
+ { _id: "post_2", title: "Second Post", authorId: "user_2" },
19
+ ]);
20
+ }
21
+
22
+ addData(db: string, collection: string, documents: Record<string, any>[]) {
23
+ if (!this.mockData.has(db)) {
24
+ this.mockData.set(db, new Map());
25
+ }
26
+ const dbMap = this.mockData.get(db)!;
27
+ dbMap.set(collection, documents);
28
+ }
29
+
30
+ async findOne(
31
+ dbName: string,
32
+ collectionName: string,
33
+ query: Record<string, any>,
34
+ ): Promise<Record<string, any> | null> {
35
+ const db = this.mockData.get(dbName);
36
+ if (!db) return null;
37
+
38
+ const collection = db.get(collectionName);
39
+ if (!collection) return null;
40
+
41
+ const doc = collection.find((doc) => {
42
+ return Object.entries(query).every(([key, value]) => doc[key] === value);
43
+ });
44
+
45
+ return doc || null;
46
+ }
47
+
48
+ async close(): Promise<void> {
49
+ // Mock close
50
+ }
51
+ }
52
+
53
+ // Helper to create NoQL with mock provider
54
+ function createMockNoQL(): NoQL {
55
+ const mockProvider = new MockNoQLDatabaseProvider();
56
+ // Use private constructor trick via type casting
57
+ const noql = Object.create(NoQL.prototype);
58
+ noql["dbProvider"] = mockProvider;
59
+ noql["parser"] = new (require("../parser").DSLParser)();
60
+ return noql;
61
+ }
62
+
63
+ describe("NoQL Integration Tests", () => {
64
+ let noql: NoQL;
65
+
66
+ beforeEach(() => {
67
+ noql = createMockNoQL();
68
+ });
69
+
70
+ afterEach(async () => {
71
+ await noql.close();
72
+ });
73
+
74
+ describe("parseDSL", () => {
75
+ it("should parse simple DSL query", () => {
76
+ const dsl = 'FROM users IN testDb WHERE _id = "user_1"';
77
+ const result = noql.parseDSL(dsl);
78
+
79
+ expect(result).toMatchObject({
80
+ start: {
81
+ collection: "users",
82
+ db: "testDb",
83
+ where: { _id: "user_1" },
84
+ },
85
+ steps: [],
86
+ });
87
+ });
88
+
89
+ it("should parse DSL query with WITH clause", () => {
90
+ const dsl = `
91
+ WITH output: "trace"
92
+ FROM users IN testDb WHERE _id = "user_1"
93
+ `.trim();
94
+
95
+ const result = noql.parseDSL(dsl);
96
+
97
+ expect(result.output).toBe("trace");
98
+ });
99
+
100
+ it("should parse DSL query with steps", () => {
101
+ const dsl = `
102
+ FROM posts IN testDb WHERE _id = "post_1"
103
+ -> LOOKUP users IN testDb BY authorId
104
+ -> GET name, email
105
+ `.trim();
106
+
107
+ const result = noql.parseDSL(dsl);
108
+
109
+ expect(result.steps).toHaveLength(2);
110
+ expect(result.steps[0].type).toBe("lookup");
111
+ expect(result.steps[1].type).toBe("get");
112
+ });
113
+
114
+ it("should throw ParseError for invalid DSL", () => {
115
+ const dsl = "INVALID SYNTAX";
116
+ expect(() => noql.parseDSL(dsl)).toThrow(ParseError);
117
+ });
118
+ });
119
+
120
+ describe("executeJson", () => {
121
+ it("should execute simple JSON query", async () => {
122
+ const query: JsonQuery = {
123
+ start: {
124
+ collection: "users",
125
+ db: "testDb",
126
+ where: { _id: "user_1" },
127
+ },
128
+ steps: [],
129
+ };
130
+
131
+ const result = await noql.executeJson(query);
132
+
133
+ expect(result.success).toBe(true);
134
+ expect(result.result).toMatchObject({
135
+ _id: "user_1",
136
+ name: "Alice",
137
+ });
138
+ });
139
+
140
+ it("should execute JSON query with GET step", async () => {
141
+ const query: JsonQuery = {
142
+ start: {
143
+ collection: "users",
144
+ db: "testDb",
145
+ where: { _id: "user_1" },
146
+ },
147
+ steps: [{ type: "get", fields: ["name", "email"] }],
148
+ };
149
+
150
+ const result = await noql.executeJson(query);
151
+
152
+ expect(result.success).toBe(true);
153
+ expect(result.result).toEqual({
154
+ name: "Alice",
155
+ email: "alice@test.com",
156
+ });
157
+ });
158
+
159
+ it("should execute JSON query with LOOKUP step", async () => {
160
+ const query: JsonQuery = {
161
+ start: {
162
+ collection: "posts",
163
+ db: "testDb",
164
+ where: { _id: "post_1" },
165
+ },
166
+ steps: [
167
+ { type: "lookup", collection: "users", db: "testDb", by: "authorId" },
168
+ { type: "get", fields: ["name"] },
169
+ ],
170
+ };
171
+
172
+ const result = await noql.executeJson(query);
173
+
174
+ expect(result.success).toBe(true);
175
+ expect(result.result).toEqual({ name: "Alice" });
176
+ });
177
+
178
+ it("should return error for document not found", async () => {
179
+ const query: JsonQuery = {
180
+ start: {
181
+ collection: "users",
182
+ db: "testDb",
183
+ where: { _id: "nonexistent" },
184
+ },
185
+ steps: [],
186
+ };
187
+
188
+ const result = await noql.executeJson(query);
189
+
190
+ expect(result.success).toBe(false);
191
+ expect(result.error).toBeDefined();
192
+ expect(result.error).toContain("Document not found");
193
+ });
194
+
195
+ it("should execute query with trace output", async () => {
196
+ const query: JsonQuery = {
197
+ output: "trace",
198
+ start: {
199
+ collection: "users",
200
+ db: "testDb",
201
+ where: { _id: "user_1" },
202
+ },
203
+ steps: [{ type: "get", fields: ["name"] }],
204
+ };
205
+
206
+ const result = await noql.executeJson(query);
207
+
208
+ expect(result.success).toBe(true);
209
+ expect(result.trace).toBeDefined();
210
+ expect(result.trace).toHaveLength(2);
211
+ });
212
+ });
213
+
214
+ describe("executeDSL", () => {
215
+ it("should execute simple DSL query", async () => {
216
+ const dsl = 'FROM users IN testDb WHERE _id = "user_1"';
217
+ const result = await noql.executeDSL(dsl);
218
+
219
+ expect(result.success).toBe(true);
220
+ expect(result.result).toMatchObject({
221
+ _id: "user_1",
222
+ name: "Alice",
223
+ });
224
+ });
225
+
226
+ it("should execute DSL query with GET step", async () => {
227
+ const dsl = `
228
+ FROM users IN testDb WHERE _id = "user_1"
229
+ -> GET name, email
230
+ `.trim();
231
+
232
+ const result = await noql.executeDSL(dsl);
233
+
234
+ expect(result.success).toBe(true);
235
+ expect(result.result).toEqual({
236
+ name: "Alice",
237
+ email: "alice@test.com",
238
+ });
239
+ });
240
+
241
+ it("should execute DSL query with LOOKUP step", async () => {
242
+ const dsl = `
243
+ FROM posts IN testDb WHERE _id = "post_1"
244
+ -> LOOKUP users IN testDb BY authorId
245
+ -> GET name, email
246
+ `.trim();
247
+
248
+ const result = await noql.executeDSL(dsl);
249
+
250
+ expect(result.success).toBe(true);
251
+ expect(result.result).toEqual({
252
+ name: "Alice",
253
+ email: "alice@test.com",
254
+ });
255
+ });
256
+
257
+ it("should execute DSL query with output format", async () => {
258
+ const dsl = `
259
+ WITH output: "trace"
260
+ FROM users IN testDb WHERE _id = "user_1"
261
+ -> GET name
262
+ `.trim();
263
+
264
+ const result = await noql.executeDSL(dsl);
265
+
266
+ expect(result.success).toBe(true);
267
+ expect(result.trace).toBeDefined();
268
+ });
269
+
270
+ it("should return error for invalid DSL syntax", async () => {
271
+ const dsl = "INVALID SYNTAX";
272
+ const result = await noql.executeDSL(dsl);
273
+
274
+ expect(result.success).toBe(false);
275
+ expect(result.error).toBeDefined();
276
+ });
277
+
278
+ it("should return error for document not found", async () => {
279
+ const dsl = 'FROM users IN testDb WHERE _id = "nonexistent"';
280
+ const result = await noql.executeDSL(dsl);
281
+
282
+ expect(result.success).toBe(false);
283
+ expect(result.error).toContain("Document not found");
284
+ });
285
+
286
+ it("should execute complex multi-step DSL query", async () => {
287
+ const dsl = `
288
+ FROM posts IN testDb WHERE _id = "post_2"
289
+ -> LOOKUP users IN testDb BY authorId
290
+ -> GET name, role
291
+ `.trim();
292
+
293
+ const result = await noql.executeDSL(dsl);
294
+
295
+ expect(result.success).toBe(true);
296
+ expect(result.result).toEqual({
297
+ name: "Bob",
298
+ role: "user",
299
+ });
300
+ });
301
+
302
+ it("should handle WHERE conditions with different value types", async () => {
303
+ const dsl = `
304
+ FROM users IN testDb WHERE role = "admin"
305
+ -> GET name
306
+ `.trim();
307
+
308
+ const result = await noql.executeDSL(dsl);
309
+
310
+ expect(result.success).toBe(true);
311
+ expect(result.result).toMatchObject({
312
+ name: "Alice",
313
+ });
314
+ });
315
+ });
316
+
317
+ describe("End-to-end DSL to execution flow", () => {
318
+ it("should parse and execute in one flow", async () => {
319
+ const dsl = `
320
+ WITH output: "value"
321
+ FROM posts IN testDb WHERE _id = "post_1"
322
+ -> LOOKUP users IN testDb BY authorId
323
+ -> GET name, email
324
+ `.trim();
325
+
326
+ // First parse to verify structure
327
+ const parsed = noql.parseDSL(dsl);
328
+ expect(parsed.output).toBe("value");
329
+ expect(parsed.steps).toHaveLength(2);
330
+
331
+ // Then execute
332
+ const result = await noql.executeDSL(dsl);
333
+ expect(result.success).toBe(true);
334
+ expect(result.result).toEqual({
335
+ name: "Alice",
336
+ email: "alice@test.com",
337
+ });
338
+ });
339
+
340
+ it("should handle document format output", async () => {
341
+ const dsl = `
342
+ WITH output: "document"
343
+ FROM users IN testDb WHERE _id = "user_2"
344
+ -> GET name
345
+ `.trim();
346
+
347
+ const result = await noql.executeDSL(dsl);
348
+
349
+ expect(result.success).toBe(true);
350
+ expect(result.result).toEqual({ name: "Bob" });
351
+ });
352
+
353
+ it("should provide detailed trace for debugging", async () => {
354
+ const dsl = `
355
+ WITH output: "trace"
356
+ FROM posts IN testDb WHERE _id = "post_1"
357
+ -> LOOKUP users IN testDb BY authorId
358
+ -> GET name
359
+ `.trim();
360
+
361
+ const result = await noql.executeDSL(dsl);
362
+
363
+ expect(result.success).toBe(true);
364
+ expect(result.trace).toBeDefined();
365
+ expect(result.trace!.length).toBeGreaterThan(0);
366
+
367
+ // Verify trace structure
368
+ const startStep = result.trace![0];
369
+ expect(startStep.stepType).toBe("start");
370
+ expect(startStep.collection).toBe("posts");
371
+ expect(startStep.documentFound).toBeDefined();
372
+ });
373
+ });
374
+
375
+ describe("Error handling", () => {
376
+ it("should handle parse errors gracefully in executeDSL", async () => {
377
+ const dsl = "FROM users";
378
+ const result = await noql.executeDSL(dsl);
379
+
380
+ expect(result.success).toBe(false);
381
+ expect(result.error).toBeDefined();
382
+ });
383
+
384
+ it("should handle execution errors gracefully", async () => {
385
+ const dsl = 'FROM nonexistent IN testDb WHERE _id = "test"';
386
+ const result = await noql.executeDSL(dsl);
387
+
388
+ expect(result.success).toBe(false);
389
+ expect(result.error).toBeDefined();
390
+ });
391
+ });
392
+ });