@delma/fylo 2.1.0 → 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 (99) hide show
  1. package/README.md +27 -0
  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/{src/core/directory.ts → dist/core/directory.js} +28 -35
  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/{src → dist}/types/fylo.d.ts +14 -1
  35. package/package.json +2 -2
  36. package/.env.example +0 -16
  37. package/.github/copilot-instructions.md +0 -3
  38. package/.github/prompts/release.prompt.md +0 -10
  39. package/.github/workflows/ci.yml +0 -37
  40. package/.github/workflows/publish.yml +0 -91
  41. package/.prettierrc +0 -7
  42. package/AGENTS.md +0 -3
  43. package/CLAUDE.md +0 -3
  44. package/eslint.config.js +0 -32
  45. package/src/CLI +0 -39
  46. package/src/adapters/cipher.ts +0 -180
  47. package/src/core/collection.ts +0 -5
  48. package/src/core/extensions.ts +0 -21
  49. package/src/core/format.ts +0 -457
  50. package/src/core/parser.ts +0 -901
  51. package/src/core/query.ts +0 -53
  52. package/src/engines/s3-files/documents.ts +0 -65
  53. package/src/engines/s3-files/filesystem.ts +0 -172
  54. package/src/engines/s3-files/query.ts +0 -291
  55. package/src/engines/s3-files/types.ts +0 -42
  56. package/src/engines/s3-files.ts +0 -769
  57. package/src/engines/types.ts +0 -21
  58. package/src/index.ts +0 -632
  59. package/src/sync.ts +0 -58
  60. package/tests/collection/truncate.test.js +0 -36
  61. package/tests/data.js +0 -97
  62. package/tests/helpers/root.js +0 -7
  63. package/tests/integration/aws-s3-files.canary.test.js +0 -22
  64. package/tests/integration/create.test.js +0 -39
  65. package/tests/integration/delete.test.js +0 -97
  66. package/tests/integration/edge-cases.test.js +0 -162
  67. package/tests/integration/encryption.test.js +0 -148
  68. package/tests/integration/export.test.js +0 -46
  69. package/tests/integration/join-modes.test.js +0 -154
  70. package/tests/integration/nested.test.js +0 -144
  71. package/tests/integration/operators.test.js +0 -136
  72. package/tests/integration/read.test.js +0 -123
  73. package/tests/integration/rollback.test.js +0 -30
  74. package/tests/integration/s3-files.performance.test.js +0 -75
  75. package/tests/integration/s3-files.test.js +0 -205
  76. package/tests/integration/sync.test.js +0 -154
  77. package/tests/integration/update.test.js +0 -105
  78. package/tests/mocks/cipher.js +0 -40
  79. package/tests/schemas/album.d.ts +0 -5
  80. package/tests/schemas/album.json +0 -5
  81. package/tests/schemas/comment.d.ts +0 -7
  82. package/tests/schemas/comment.json +0 -7
  83. package/tests/schemas/photo.d.ts +0 -7
  84. package/tests/schemas/photo.json +0 -7
  85. package/tests/schemas/post.d.ts +0 -6
  86. package/tests/schemas/post.json +0 -6
  87. package/tests/schemas/tip.d.ts +0 -7
  88. package/tests/schemas/tip.json +0 -7
  89. package/tests/schemas/todo.d.ts +0 -6
  90. package/tests/schemas/todo.json +0 -6
  91. package/tests/schemas/user.d.ts +0 -23
  92. package/tests/schemas/user.json +0 -23
  93. package/tsconfig.json +0 -21
  94. package/tsconfig.typecheck.json +0 -31
  95. /package/{src → dist}/types/bun-runtime.d.ts +0 -0
  96. /package/{src → dist}/types/index.d.ts +0 -0
  97. /package/{src → dist}/types/node-runtime.d.ts +0 -0
  98. /package/{src → dist}/types/query.d.ts +0 -0
  99. /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