@delma/fylo 2.0.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +206 -261
  2. package/dist/adapters/cipher.js +155 -0
  3. package/dist/adapters/cipher.js.map +1 -0
  4. package/dist/core/collection.js +6 -0
  5. package/dist/core/collection.js.map +1 -0
  6. package/dist/core/directory.js +48 -0
  7. package/dist/core/directory.js.map +1 -0
  8. package/dist/core/doc-id.js +15 -0
  9. package/dist/core/doc-id.js.map +1 -0
  10. package/dist/core/extensions.js +16 -0
  11. package/dist/core/extensions.js.map +1 -0
  12. package/dist/core/format.js +355 -0
  13. package/dist/core/format.js.map +1 -0
  14. package/dist/core/parser.js +764 -0
  15. package/dist/core/parser.js.map +1 -0
  16. package/dist/core/query.js +47 -0
  17. package/dist/core/query.js.map +1 -0
  18. package/dist/engines/s3-files/documents.js +62 -0
  19. package/dist/engines/s3-files/documents.js.map +1 -0
  20. package/dist/engines/s3-files/filesystem.js +165 -0
  21. package/dist/engines/s3-files/filesystem.js.map +1 -0
  22. package/dist/engines/s3-files/query.js +235 -0
  23. package/dist/engines/s3-files/query.js.map +1 -0
  24. package/dist/engines/s3-files/types.js +2 -0
  25. package/dist/engines/s3-files/types.js.map +1 -0
  26. package/dist/engines/s3-files.js +629 -0
  27. package/dist/engines/s3-files.js.map +1 -0
  28. package/dist/engines/types.js +2 -0
  29. package/dist/engines/types.js.map +1 -0
  30. package/dist/index.js +562 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/sync.js +18 -0
  33. package/dist/sync.js.map +1 -0
  34. package/dist/types/fylo.d.ts +179 -0
  35. package/{src → dist}/types/node-runtime.d.ts +1 -0
  36. package/package.json +3 -6
  37. package/.env.example +0 -16
  38. package/.github/copilot-instructions.md +0 -3
  39. package/.github/prompts/release.prompt.md +0 -10
  40. package/.github/workflows/ci.yml +0 -37
  41. package/.github/workflows/publish.yml +0 -91
  42. package/.prettierrc +0 -7
  43. package/AGENTS.md +0 -3
  44. package/CLAUDE.md +0 -3
  45. package/eslint.config.js +0 -32
  46. package/src/CLI +0 -39
  47. package/src/adapters/cipher.ts +0 -180
  48. package/src/adapters/redis.ts +0 -487
  49. package/src/adapters/s3.ts +0 -61
  50. package/src/core/collection.ts +0 -5
  51. package/src/core/directory.ts +0 -387
  52. package/src/core/extensions.ts +0 -21
  53. package/src/core/format.ts +0 -457
  54. package/src/core/parser.ts +0 -901
  55. package/src/core/query.ts +0 -53
  56. package/src/core/walker.ts +0 -174
  57. package/src/core/write-queue.ts +0 -59
  58. package/src/engines/s3-files.ts +0 -1068
  59. package/src/engines/types.ts +0 -21
  60. package/src/index.ts +0 -1727
  61. package/src/migrate-cli.ts +0 -22
  62. package/src/migrate.ts +0 -74
  63. package/src/types/fylo.d.ts +0 -261
  64. package/src/types/write-queue.ts +0 -42
  65. package/src/worker.ts +0 -18
  66. package/src/workers/write-worker.ts +0 -120
  67. package/tests/collection/truncate.test.js +0 -35
  68. package/tests/data.js +0 -97
  69. package/tests/index.js +0 -14
  70. package/tests/integration/aws-s3-files.canary.test.js +0 -22
  71. package/tests/integration/create.test.js +0 -39
  72. package/tests/integration/delete.test.js +0 -95
  73. package/tests/integration/edge-cases.test.js +0 -158
  74. package/tests/integration/encryption.test.js +0 -131
  75. package/tests/integration/export.test.js +0 -46
  76. package/tests/integration/join-modes.test.js +0 -154
  77. package/tests/integration/migration.test.js +0 -38
  78. package/tests/integration/nested.test.js +0 -142
  79. package/tests/integration/operators.test.js +0 -122
  80. package/tests/integration/queue.test.js +0 -83
  81. package/tests/integration/read.test.js +0 -119
  82. package/tests/integration/rollback.test.js +0 -60
  83. package/tests/integration/s3-files.test.js +0 -192
  84. package/tests/integration/update.test.js +0 -99
  85. package/tests/mocks/cipher.js +0 -40
  86. package/tests/mocks/redis.js +0 -123
  87. package/tests/mocks/s3.js +0 -80
  88. package/tests/schemas/album.d.ts +0 -5
  89. package/tests/schemas/album.json +0 -5
  90. package/tests/schemas/comment.d.ts +0 -7
  91. package/tests/schemas/comment.json +0 -7
  92. package/tests/schemas/photo.d.ts +0 -7
  93. package/tests/schemas/photo.json +0 -7
  94. package/tests/schemas/post.d.ts +0 -6
  95. package/tests/schemas/post.json +0 -6
  96. package/tests/schemas/tip.d.ts +0 -7
  97. package/tests/schemas/tip.json +0 -7
  98. package/tests/schemas/todo.d.ts +0 -6
  99. package/tests/schemas/todo.json +0 -6
  100. package/tests/schemas/user.d.ts +0 -23
  101. package/tests/schemas/user.json +0 -23
  102. package/tsconfig.json +0 -21
  103. package/tsconfig.typecheck.json +0 -31
  104. /package/{src → dist}/types/bun-runtime.d.ts +0 -0
  105. /package/{src → dist}/types/index.d.ts +0 -0
  106. /package/{src → dist}/types/query.d.ts +0 -0
  107. /package/{src → dist}/types/vendor-modules.d.ts +0 -0
@@ -0,0 +1,764 @@
1
+ // Token types for SQL lexing
2
+ var TokenType;
3
+ (function (TokenType) {
4
+ TokenType["CREATE"] = "CREATE";
5
+ TokenType["DROP"] = "DROP";
6
+ TokenType["SELECT"] = "SELECT";
7
+ TokenType["FROM"] = "FROM";
8
+ TokenType["WHERE"] = "WHERE";
9
+ TokenType["INSERT"] = "INSERT";
10
+ TokenType["INTO"] = "INTO";
11
+ TokenType["VALUES"] = "VALUES";
12
+ TokenType["UPDATE"] = "UPDATE";
13
+ TokenType["SET"] = "SET";
14
+ TokenType["DELETE"] = "DELETE";
15
+ TokenType["JOIN"] = "JOIN";
16
+ TokenType["INNER"] = "INNER";
17
+ TokenType["LEFT"] = "LEFT";
18
+ TokenType["RIGHT"] = "RIGHT";
19
+ TokenType["OUTER"] = "OUTER";
20
+ TokenType["ON"] = "ON";
21
+ TokenType["GROUP"] = "GROUP";
22
+ TokenType["BY"] = "BY";
23
+ TokenType["ORDER"] = "ORDER";
24
+ TokenType["LIMIT"] = "LIMIT";
25
+ TokenType["AS"] = "AS";
26
+ TokenType["AND"] = "AND";
27
+ TokenType["OR"] = "OR";
28
+ TokenType["EQUALS"] = "=";
29
+ TokenType["NOT_EQUALS"] = "!=";
30
+ TokenType["GREATER_THAN"] = ">";
31
+ TokenType["LESS_THAN"] = "<";
32
+ TokenType["GREATER_EQUAL"] = ">=";
33
+ TokenType["LESS_EQUAL"] = "<=";
34
+ TokenType["LIKE"] = "LIKE";
35
+ TokenType["IDENTIFIER"] = "IDENTIFIER";
36
+ TokenType["STRING"] = "STRING";
37
+ TokenType["NUMBER"] = "NUMBER";
38
+ TokenType["BOOLEAN"] = "BOOLEAN";
39
+ TokenType["NULL"] = "NULL";
40
+ TokenType["COMMA"] = ",";
41
+ TokenType["SEMICOLON"] = ";";
42
+ TokenType["LPAREN"] = "(";
43
+ TokenType["RPAREN"] = ")";
44
+ TokenType["ASTERISK"] = "*";
45
+ TokenType["EOF"] = "EOF";
46
+ })(TokenType || (TokenType = {}));
47
+ // SQL Lexer
48
+ class SQLLexer {
49
+ input;
50
+ position = 0;
51
+ current = null;
52
+ constructor(input) {
53
+ this.input = input.trim();
54
+ this.current = this.input[0] || null;
55
+ }
56
+ advance() {
57
+ this.position++;
58
+ this.current = this.position < this.input.length ? this.input[this.position] : null;
59
+ }
60
+ skipWhitespace() {
61
+ while (this.current && /\s/.test(this.current)) {
62
+ this.advance();
63
+ }
64
+ }
65
+ readString() {
66
+ let result = '';
67
+ const quote = this.current;
68
+ this.advance(); // Skip opening quote
69
+ while (this.current && this.current !== quote) {
70
+ result += this.current;
71
+ this.advance();
72
+ }
73
+ if (this.current === quote) {
74
+ this.advance(); // Skip closing quote
75
+ }
76
+ return result;
77
+ }
78
+ readNumber() {
79
+ let result = '';
80
+ while (this.current && /[\d.]/.test(this.current)) {
81
+ result += this.current;
82
+ this.advance();
83
+ }
84
+ return result;
85
+ }
86
+ readIdentifier() {
87
+ let result = '';
88
+ while (this.current && /[a-zA-Z0-9_\-]/.test(this.current)) {
89
+ result += this.current;
90
+ this.advance();
91
+ }
92
+ return result;
93
+ }
94
+ getKeywordType(word) {
95
+ const keywords = {
96
+ SELECT: TokenType.SELECT,
97
+ FROM: TokenType.FROM,
98
+ WHERE: TokenType.WHERE,
99
+ INSERT: TokenType.INSERT,
100
+ INTO: TokenType.INTO,
101
+ VALUES: TokenType.VALUES,
102
+ UPDATE: TokenType.UPDATE,
103
+ SET: TokenType.SET,
104
+ DELETE: TokenType.DELETE,
105
+ JOIN: TokenType.JOIN,
106
+ INNER: TokenType.INNER,
107
+ LEFT: TokenType.LEFT,
108
+ RIGHT: TokenType.RIGHT,
109
+ OUTER: TokenType.OUTER,
110
+ ON: TokenType.ON,
111
+ GROUP: TokenType.GROUP,
112
+ BY: TokenType.BY,
113
+ ORDER: TokenType.ORDER,
114
+ LIMIT: TokenType.LIMIT,
115
+ AS: TokenType.AS,
116
+ AND: TokenType.AND,
117
+ OR: TokenType.OR,
118
+ LIKE: TokenType.LIKE,
119
+ TRUE: TokenType.BOOLEAN,
120
+ FALSE: TokenType.BOOLEAN,
121
+ NULL: TokenType.NULL
122
+ };
123
+ return keywords[word.toUpperCase()] || TokenType.IDENTIFIER;
124
+ }
125
+ tokenize() {
126
+ const tokens = [];
127
+ while (this.current) {
128
+ this.skipWhitespace();
129
+ if (!this.current)
130
+ break;
131
+ const position = this.position;
132
+ // String literals
133
+ if (this.current === "'" || this.current === '"') {
134
+ const value = this.readString();
135
+ tokens.push({ type: TokenType.STRING, value, position });
136
+ continue;
137
+ }
138
+ // Numbers
139
+ if (/\d/.test(this.current)) {
140
+ const value = this.readNumber();
141
+ tokens.push({ type: TokenType.NUMBER, value, position });
142
+ continue;
143
+ }
144
+ // Identifiers and keywords
145
+ if (/[a-zA-Z_]/.test(this.current)) {
146
+ let value = this.readIdentifier();
147
+ // Support dot notation for nested fields (e.g. address.city → address/city)
148
+ while (this.current === '.' &&
149
+ this.position + 1 < this.input.length &&
150
+ /[a-zA-Z_]/.test(this.input[this.position + 1])) {
151
+ this.advance(); // skip '.'
152
+ value += '/' + this.readIdentifier();
153
+ }
154
+ const type = this.getKeywordType(value);
155
+ tokens.push({ type, value, position });
156
+ continue;
157
+ }
158
+ // Operators and punctuation
159
+ switch (this.current) {
160
+ case '=':
161
+ tokens.push({ type: TokenType.EQUALS, value: '=', position });
162
+ this.advance();
163
+ break;
164
+ case '!':
165
+ if (this.input[this.position + 1] === '=') {
166
+ tokens.push({ type: TokenType.NOT_EQUALS, value: '!=', position });
167
+ this.advance();
168
+ this.advance();
169
+ }
170
+ else {
171
+ this.advance();
172
+ }
173
+ break;
174
+ case '>':
175
+ if (this.input[this.position + 1] === '=') {
176
+ tokens.push({ type: TokenType.GREATER_EQUAL, value: '>=', position });
177
+ this.advance();
178
+ this.advance();
179
+ }
180
+ else {
181
+ tokens.push({ type: TokenType.GREATER_THAN, value: '>', position });
182
+ this.advance();
183
+ }
184
+ break;
185
+ case '<':
186
+ if (this.input[this.position + 1] === '=') {
187
+ tokens.push({ type: TokenType.LESS_EQUAL, value: '<=', position });
188
+ this.advance();
189
+ this.advance();
190
+ }
191
+ else {
192
+ tokens.push({ type: TokenType.LESS_THAN, value: '<', position });
193
+ this.advance();
194
+ }
195
+ break;
196
+ case ',':
197
+ tokens.push({ type: TokenType.COMMA, value: ',', position });
198
+ this.advance();
199
+ break;
200
+ case ';':
201
+ tokens.push({ type: TokenType.SEMICOLON, value: ';', position });
202
+ this.advance();
203
+ break;
204
+ case '(':
205
+ tokens.push({ type: TokenType.LPAREN, value: '(', position });
206
+ this.advance();
207
+ break;
208
+ case ')':
209
+ tokens.push({ type: TokenType.RPAREN, value: ')', position });
210
+ this.advance();
211
+ break;
212
+ case '*':
213
+ tokens.push({ type: TokenType.ASTERISK, value: '*', position });
214
+ this.advance();
215
+ break;
216
+ default:
217
+ this.advance();
218
+ break;
219
+ }
220
+ }
221
+ tokens.push({ type: TokenType.EOF, value: '', position: this.position });
222
+ return tokens;
223
+ }
224
+ }
225
+ // SQL Parser
226
+ class SQLParser {
227
+ tokens;
228
+ position = 0;
229
+ current;
230
+ constructor(tokens) {
231
+ this.tokens = tokens;
232
+ this.current = tokens[0];
233
+ }
234
+ advance() {
235
+ this.position++;
236
+ this.current = this.tokens[this.position] || {
237
+ type: TokenType.EOF,
238
+ value: '',
239
+ position: -1
240
+ };
241
+ }
242
+ expect(type) {
243
+ if (this.current.type !== type) {
244
+ throw new Error('Invalid SQL syntax');
245
+ }
246
+ const token = this.current;
247
+ this.advance();
248
+ return token;
249
+ }
250
+ match(...types) {
251
+ return types.includes(this.current.type);
252
+ }
253
+ parseValue() {
254
+ if (this.current.type === TokenType.STRING) {
255
+ const value = this.current.value;
256
+ this.advance();
257
+ return value;
258
+ }
259
+ if (this.current.type === TokenType.NUMBER) {
260
+ const value = parseFloat(this.current.value);
261
+ this.advance();
262
+ return value;
263
+ }
264
+ if (this.current.type === TokenType.BOOLEAN) {
265
+ const value = this.current.value.toLowerCase() === 'true';
266
+ this.advance();
267
+ return value;
268
+ }
269
+ if (this.current.type === TokenType.NULL) {
270
+ this.advance();
271
+ return null;
272
+ }
273
+ throw new Error(`Unexpected value type: ${this.current.type}`);
274
+ }
275
+ parseOperator() {
276
+ const operatorMap = {
277
+ [TokenType.EQUALS]: '$eq',
278
+ [TokenType.NOT_EQUALS]: '$ne',
279
+ [TokenType.GREATER_THAN]: '$gt',
280
+ [TokenType.LESS_THAN]: '$lt',
281
+ [TokenType.GREATER_EQUAL]: '$gte',
282
+ [TokenType.LESS_EQUAL]: '$lte',
283
+ [TokenType.LIKE]: '$like'
284
+ };
285
+ if (operatorMap[this.current.type]) {
286
+ const op = operatorMap[this.current.type];
287
+ this.advance();
288
+ return op ?? '';
289
+ }
290
+ throw new Error(`Unknown operator: ${this.current.type}`);
291
+ }
292
+ parseCondition() {
293
+ const column = this.expect(TokenType.IDENTIFIER).value;
294
+ const operator = this.parseOperator();
295
+ const value = this.parseValue();
296
+ return { column, operator, value };
297
+ }
298
+ parseWhereClause() {
299
+ this.expect(TokenType.WHERE);
300
+ const conditions = [];
301
+ do {
302
+ const condition = this.parseCondition();
303
+ const op = {
304
+ [condition.column]: {
305
+ [condition.operator]: condition.value
306
+ }
307
+ };
308
+ conditions.push(op);
309
+ if (this.match(TokenType.AND, TokenType.OR)) {
310
+ this.advance();
311
+ }
312
+ else {
313
+ break;
314
+ }
315
+ } while (true);
316
+ return conditions;
317
+ }
318
+ parseSelectClause() {
319
+ this.expect(TokenType.SELECT);
320
+ const columns = [];
321
+ if (this.current.type === TokenType.ASTERISK) {
322
+ this.advance();
323
+ return ['*'];
324
+ }
325
+ do {
326
+ columns.push(this.expect(TokenType.IDENTIFIER).value);
327
+ if (this.current.type === TokenType.COMMA) {
328
+ this.advance();
329
+ }
330
+ else {
331
+ break;
332
+ }
333
+ } while (true);
334
+ return columns;
335
+ }
336
+ parseSelect() {
337
+ const select = this.parseSelectClause();
338
+ this.expect(TokenType.FROM);
339
+ const collection = this.expect(TokenType.IDENTIFIER).value;
340
+ // Check if this is a JOIN query
341
+ if (this.match(TokenType.JOIN, TokenType.INNER, TokenType.LEFT, TokenType.RIGHT, TokenType.OUTER)) {
342
+ return this.parseJoinQuery(select, collection);
343
+ }
344
+ const query = {
345
+ $collection: collection,
346
+ $select: select.includes('*') ? undefined : select,
347
+ $onlyIds: select.includes('_id')
348
+ };
349
+ if (this.match(TokenType.WHERE)) {
350
+ query.$ops = this.parseWhereClause();
351
+ }
352
+ if (this.match(TokenType.GROUP)) {
353
+ this.advance();
354
+ this.expect(TokenType.BY);
355
+ query.$groupby = this.expect(TokenType.IDENTIFIER).value;
356
+ }
357
+ if (this.match(TokenType.LIMIT)) {
358
+ this.advance();
359
+ query.$limit = parseInt(this.expect(TokenType.NUMBER).value);
360
+ }
361
+ return query;
362
+ }
363
+ parseJoinQuery(select, leftCollection) {
364
+ // Parse join type
365
+ let joinMode = 'inner';
366
+ if (this.match(TokenType.INNER)) {
367
+ this.advance();
368
+ joinMode = 'inner';
369
+ }
370
+ else if (this.match(TokenType.LEFT)) {
371
+ this.advance();
372
+ joinMode = 'left';
373
+ }
374
+ else if (this.match(TokenType.RIGHT)) {
375
+ this.advance();
376
+ joinMode = 'right';
377
+ }
378
+ else if (this.match(TokenType.OUTER)) {
379
+ this.advance();
380
+ joinMode = 'outer';
381
+ }
382
+ this.expect(TokenType.JOIN);
383
+ const rightCollection = this.expect(TokenType.IDENTIFIER).value;
384
+ this.expect(TokenType.ON);
385
+ // Parse join conditions
386
+ const onConditions = this.parseJoinConditions();
387
+ const joinQuery = {
388
+ $leftCollection: leftCollection,
389
+ $rightCollection: rightCollection,
390
+ $mode: joinMode,
391
+ $on: onConditions,
392
+ $select: select.includes('*') ? undefined : select
393
+ };
394
+ // Parse additional clauses
395
+ if (this.match(TokenType.WHERE)) {
396
+ // For joins, WHERE conditions would need to be handled differently
397
+ // Skip for now as it's complex with joined tables
398
+ this.parseWhereClause();
399
+ }
400
+ if (this.match(TokenType.GROUP)) {
401
+ this.advance();
402
+ this.expect(TokenType.BY);
403
+ joinQuery.$groupby = this.expect(TokenType.IDENTIFIER).value;
404
+ }
405
+ if (this.match(TokenType.LIMIT)) {
406
+ this.advance();
407
+ joinQuery.$limit = parseInt(this.expect(TokenType.NUMBER).value);
408
+ }
409
+ return joinQuery;
410
+ }
411
+ parseJoinConditions() {
412
+ const conditions = {};
413
+ do {
414
+ // Parse: table1.column = table2.column
415
+ const leftSide = this.parseJoinColumn();
416
+ const operator = this.parseJoinOperator();
417
+ const rightSide = this.parseJoinColumn();
418
+ // Build the join condition
419
+ const leftColumn = leftSide.column;
420
+ const rightColumn = rightSide.column;
421
+ if (!conditions[leftColumn]) {
422
+ conditions[leftColumn] = {};
423
+ }
424
+ ;
425
+ conditions[leftColumn][operator] = rightColumn;
426
+ if (this.match(TokenType.AND)) {
427
+ this.advance();
428
+ }
429
+ else {
430
+ break;
431
+ }
432
+ } while (true);
433
+ return conditions;
434
+ }
435
+ parseJoinColumn() {
436
+ const identifier = this.expect(TokenType.IDENTIFIER).value;
437
+ // Check if it's table.column format
438
+ if (this.current.type === TokenType.IDENTIFIER) {
439
+ // This might be a qualified column name, but we'll treat it as simple for now
440
+ return { column: identifier };
441
+ }
442
+ return { column: identifier };
443
+ }
444
+ parseJoinOperator() {
445
+ const operatorMap = {
446
+ [TokenType.EQUALS]: '$eq',
447
+ [TokenType.NOT_EQUALS]: '$ne',
448
+ [TokenType.GREATER_THAN]: '$gt',
449
+ [TokenType.LESS_THAN]: '$lt',
450
+ [TokenType.GREATER_EQUAL]: '$gte',
451
+ [TokenType.LESS_EQUAL]: '$lte'
452
+ };
453
+ if (operatorMap[this.current.type]) {
454
+ const op = operatorMap[this.current.type];
455
+ this.advance();
456
+ return op;
457
+ }
458
+ throw new Error(`Unknown join operator: ${this.current.type}`);
459
+ }
460
+ parseInsert() {
461
+ this.expect(TokenType.INSERT);
462
+ this.expect(TokenType.INTO);
463
+ const collection = this.expect(TokenType.IDENTIFIER).value;
464
+ // Parse column list
465
+ let columns = [];
466
+ if (this.current.type === TokenType.LPAREN) {
467
+ this.advance();
468
+ do {
469
+ columns.push(this.expect(TokenType.IDENTIFIER).value);
470
+ // @ts-expect-error - current may be undefined at end of input
471
+ if (this.current.type === TokenType.COMMA) {
472
+ this.advance();
473
+ }
474
+ else {
475
+ break;
476
+ }
477
+ } while (true);
478
+ this.expect(TokenType.RPAREN);
479
+ }
480
+ this.expect(TokenType.VALUES);
481
+ this.expect(TokenType.LPAREN);
482
+ const values = {};
483
+ let valueIndex = 0;
484
+ do {
485
+ const value = this.parseValue();
486
+ const column = columns[valueIndex] || `col${valueIndex}`;
487
+ values[column] = value;
488
+ valueIndex++;
489
+ if (this.current.type === TokenType.COMMA) {
490
+ this.advance();
491
+ }
492
+ else {
493
+ break;
494
+ }
495
+ } while (true);
496
+ this.expect(TokenType.RPAREN);
497
+ return {
498
+ $collection: collection,
499
+ $values: values
500
+ };
501
+ }
502
+ parseUpdate() {
503
+ this.expect(TokenType.UPDATE);
504
+ const collection = this.expect(TokenType.IDENTIFIER).value;
505
+ this.expect(TokenType.SET);
506
+ const set = {};
507
+ do {
508
+ const column = this.expect(TokenType.IDENTIFIER).value;
509
+ this.expect(TokenType.EQUALS);
510
+ const value = this.parseValue();
511
+ set[column] = value;
512
+ if (this.current.type === TokenType.COMMA) {
513
+ this.advance();
514
+ }
515
+ else {
516
+ break;
517
+ }
518
+ } while (true);
519
+ const update = {
520
+ $collection: collection,
521
+ $set: set
522
+ };
523
+ if (this.match(TokenType.WHERE)) {
524
+ const whereQuery = {
525
+ $collection: collection,
526
+ $ops: this.parseWhereClause()
527
+ };
528
+ update.$where = whereQuery;
529
+ }
530
+ return update;
531
+ }
532
+ parseDelete() {
533
+ this.expect(TokenType.DELETE);
534
+ this.expect(TokenType.FROM);
535
+ const collection = this.expect(TokenType.IDENTIFIER).value;
536
+ const deleteQuery = {
537
+ $collection: collection
538
+ };
539
+ if (this.match(TokenType.WHERE)) {
540
+ deleteQuery.$ops = this.parseWhereClause();
541
+ }
542
+ return deleteQuery;
543
+ }
544
+ }
545
+ // Main SQL to AST converter
546
+ export class Parser {
547
+ static parse(sql) {
548
+ const lexer = new SQLLexer(sql);
549
+ const tokens = lexer.tokenize();
550
+ const parser = new SQLParser(tokens);
551
+ // Determine query type based on first token
552
+ const firstToken = tokens[0];
553
+ switch (firstToken.value) {
554
+ case TokenType.CREATE:
555
+ return { $collection: tokens[2].value };
556
+ case TokenType.SELECT:
557
+ return parser.parseSelect();
558
+ case TokenType.INSERT:
559
+ return parser.parseInsert();
560
+ case TokenType.UPDATE:
561
+ return parser.parseUpdate();
562
+ case TokenType.DELETE:
563
+ return parser.parseDelete();
564
+ case TokenType.DROP:
565
+ return { $collection: tokens[2].value };
566
+ default:
567
+ throw new Error(`Unsupported SQL statement type: ${firstToken.value}`);
568
+ }
569
+ }
570
+ // Bun SQL inspired query builder methods
571
+ static query(collection) {
572
+ return new QueryBuilder(collection);
573
+ }
574
+ // Join query builder
575
+ static join(leftCollection, rightCollection) {
576
+ return new JoinBuilder(leftCollection, rightCollection);
577
+ }
578
+ }
579
+ // Bun SQL inspired query builder
580
+ export class QueryBuilder {
581
+ collection;
582
+ queryAst = {};
583
+ constructor(collection) {
584
+ this.collection = collection;
585
+ this.queryAst.$collection = collection;
586
+ }
587
+ select(...columns) {
588
+ this.queryAst.$select = columns;
589
+ return this;
590
+ }
591
+ where(conditions) {
592
+ this.queryAst.$ops = conditions;
593
+ return this;
594
+ }
595
+ limit(count) {
596
+ this.queryAst.$limit = count;
597
+ return this;
598
+ }
599
+ groupBy(column) {
600
+ this.queryAst.$groupby = column;
601
+ return this;
602
+ }
603
+ onlyIds() {
604
+ this.queryAst.$onlyIds = true;
605
+ return this;
606
+ }
607
+ build() {
608
+ return this.queryAst;
609
+ }
610
+ // Convert to SQL string (reverse operation)
611
+ toSQL() {
612
+ let sql = 'SELECT ';
613
+ if (this.queryAst.$select) {
614
+ sql += this.queryAst.$select.join(', ');
615
+ }
616
+ else {
617
+ sql += '*';
618
+ }
619
+ sql += ` FROM ${this.collection}`;
620
+ if (this.queryAst.$ops && this.queryAst.$ops.length > 0) {
621
+ sql += ' WHERE ';
622
+ const conditions = this.queryAst.$ops
623
+ .map((op) => {
624
+ const entries = Object.entries(op);
625
+ return entries
626
+ .map(([column, operand]) => {
627
+ const opEntries = Object.entries(operand);
628
+ return opEntries
629
+ .map(([operator, value]) => {
630
+ const sqlOp = this.operatorToSQL(operator);
631
+ const sqlValue = typeof value === 'string' ? `'${value}'` : value;
632
+ return `${column} ${sqlOp} ${sqlValue}`;
633
+ })
634
+ .join(' AND ');
635
+ })
636
+ .join(' AND ');
637
+ })
638
+ .join(' AND ');
639
+ sql += conditions;
640
+ }
641
+ if (this.queryAst.$groupby) {
642
+ sql += ` GROUP BY ${String(this.queryAst.$groupby)}`;
643
+ }
644
+ if (this.queryAst.$limit) {
645
+ sql += ` LIMIT ${this.queryAst.$limit}`;
646
+ }
647
+ return sql;
648
+ }
649
+ operatorToSQL(operator) {
650
+ const opMap = {
651
+ $eq: '=',
652
+ $ne: '!=',
653
+ $gt: '>',
654
+ $lt: '<',
655
+ $gte: '>=',
656
+ $lte: '<=',
657
+ $like: 'LIKE'
658
+ };
659
+ return opMap[operator] || '=';
660
+ }
661
+ }
662
+ // Join query builder
663
+ export class JoinBuilder {
664
+ joinAst = {};
665
+ constructor(leftCollection, rightCollection) {
666
+ this.joinAst.$leftCollection = leftCollection;
667
+ this.joinAst.$rightCollection = rightCollection;
668
+ this.joinAst.$mode = 'inner'; // default
669
+ }
670
+ select(...columns) {
671
+ this.joinAst.$select = columns;
672
+ return this;
673
+ }
674
+ innerJoin() {
675
+ this.joinAst.$mode = 'inner';
676
+ return this;
677
+ }
678
+ leftJoin() {
679
+ this.joinAst.$mode = 'left';
680
+ return this;
681
+ }
682
+ rightJoin() {
683
+ this.joinAst.$mode = 'right';
684
+ return this;
685
+ }
686
+ outerJoin() {
687
+ this.joinAst.$mode = 'outer';
688
+ return this;
689
+ }
690
+ on(conditions) {
691
+ this.joinAst.$on = conditions;
692
+ return this;
693
+ }
694
+ limit(count) {
695
+ this.joinAst.$limit = count;
696
+ return this;
697
+ }
698
+ groupBy(column) {
699
+ this.joinAst.$groupby = column;
700
+ return this;
701
+ }
702
+ onlyIds() {
703
+ this.joinAst.$onlyIds = true;
704
+ return this;
705
+ }
706
+ rename(mapping) {
707
+ this.joinAst.$rename = mapping;
708
+ return this;
709
+ }
710
+ build() {
711
+ if (!this.joinAst.$on) {
712
+ throw new Error('JOIN query must have ON conditions');
713
+ }
714
+ return this.joinAst;
715
+ }
716
+ // Convert to SQL string
717
+ toSQL() {
718
+ let sql = 'SELECT ';
719
+ if (this.joinAst.$select) {
720
+ sql += this.joinAst.$select.join(', ');
721
+ }
722
+ else {
723
+ sql += '*';
724
+ }
725
+ sql += ` FROM ${this.joinAst.$leftCollection}`;
726
+ // Add join type
727
+ const joinType = this.joinAst.$mode?.toUpperCase() || 'INNER';
728
+ sql += ` ${joinType} JOIN ${this.joinAst.$rightCollection}`;
729
+ // Add ON conditions
730
+ if (this.joinAst.$on) {
731
+ sql += ' ON ';
732
+ const conditions = Object.entries(this.joinAst.$on)
733
+ .map(([leftCol, operand]) => {
734
+ return Object.entries(operand)
735
+ .map(([operator, rightCol]) => {
736
+ const sqlOp = this.operatorToSQL(operator);
737
+ return `${this.joinAst.$leftCollection}.${leftCol} ${sqlOp} ${this.joinAst.$rightCollection}.${String(rightCol)}`;
738
+ })
739
+ .join(' AND ');
740
+ })
741
+ .join(' AND ');
742
+ sql += conditions;
743
+ }
744
+ if (this.joinAst.$groupby) {
745
+ sql += ` GROUP BY ${String(this.joinAst.$groupby)}`;
746
+ }
747
+ if (this.joinAst.$limit) {
748
+ sql += ` LIMIT ${this.joinAst.$limit}`;
749
+ }
750
+ return sql;
751
+ }
752
+ operatorToSQL(operator) {
753
+ const opMap = {
754
+ $eq: '=',
755
+ $ne: '!=',
756
+ $gt: '>',
757
+ $lt: '<',
758
+ $gte: '>=',
759
+ $lte: '<='
760
+ };
761
+ return opMap[operator] || '=';
762
+ }
763
+ }
764
+ //# sourceMappingURL=parser.js.map