schematic-pg 0.1.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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1019 -0
  3. package/dist/api/auth/errors.d.ts +6 -0
  4. package/dist/api/auth/errors.js +12 -0
  5. package/dist/api/auth/jwt-resolver.d.ts +7 -0
  6. package/dist/api/auth/jwt-resolver.js +59 -0
  7. package/dist/api/auth/middleware.d.ts +4 -0
  8. package/dist/api/auth/middleware.js +10 -0
  9. package/dist/api/auth/policy.d.ts +7 -0
  10. package/dist/api/auth/policy.js +95 -0
  11. package/dist/api/auth/template.d.ts +2 -0
  12. package/dist/api/auth/template.js +24 -0
  13. package/dist/api/auth/types.d.ts +12 -0
  14. package/dist/api/auth/types.js +1 -0
  15. package/dist/api/middleware/db.d.ts +8 -0
  16. package/dist/api/middleware/db.js +12 -0
  17. package/dist/api/middleware/errors.d.ts +5 -0
  18. package/dist/api/middleware/errors.js +27 -0
  19. package/dist/api/middleware/validate.d.ts +23 -0
  20. package/dist/api/middleware/validate.js +13 -0
  21. package/dist/api/types.d.ts +8 -0
  22. package/dist/api/types.js +1 -0
  23. package/dist/api/utils/route-naming.d.ts +3 -0
  24. package/dist/api/utils/route-naming.js +25 -0
  25. package/dist/api-generator/app-generator.d.ts +11 -0
  26. package/dist/api-generator/app-generator.js +79 -0
  27. package/dist/api-generator/custom-route-scanner.d.ts +6 -0
  28. package/dist/api-generator/custom-route-scanner.js +42 -0
  29. package/dist/api-generator/generate-api-cli.d.ts +1 -0
  30. package/dist/api-generator/generate-api-cli.js +28 -0
  31. package/dist/api-generator/index.d.ts +11 -0
  32. package/dist/api-generator/index.js +15 -0
  33. package/dist/api-generator/policy-generator.d.ts +9 -0
  34. package/dist/api-generator/policy-generator.js +33 -0
  35. package/dist/api-generator/route-generator.d.ts +20 -0
  36. package/dist/api-generator/route-generator.js +198 -0
  37. package/dist/api-generator/utils/policy.d.ts +18 -0
  38. package/dist/api-generator/utils/policy.js +72 -0
  39. package/dist/api-generator/zod-schema-generator.d.ts +16 -0
  40. package/dist/api-generator/zod-schema-generator.js +145 -0
  41. package/dist/cli/db.d.ts +4 -0
  42. package/dist/cli/db.js +144 -0
  43. package/dist/cli/dev.d.ts +1 -0
  44. package/dist/cli/dev.js +10 -0
  45. package/dist/cli/generate.d.ts +4 -0
  46. package/dist/cli/generate.js +50 -0
  47. package/dist/cli/init.d.ts +1 -0
  48. package/dist/cli/init.js +57 -0
  49. package/dist/cli/paths.d.ts +5 -0
  50. package/dist/cli/paths.js +10 -0
  51. package/dist/cli/templates.d.ts +6 -0
  52. package/dist/cli/templates.js +85 -0
  53. package/dist/cli.d.ts +2 -0
  54. package/dist/cli.js +81 -0
  55. package/dist/constants.d.ts +1 -0
  56. package/dist/constants.js +1 -0
  57. package/dist/db/bootstrap-cli.d.ts +1 -0
  58. package/dist/db/bootstrap-cli.js +17 -0
  59. package/dist/db/bootstrap.d.ts +3 -0
  60. package/dist/db/bootstrap.js +16 -0
  61. package/dist/db/cli.d.ts +1 -0
  62. package/dist/db/cli.js +20 -0
  63. package/dist/db/client.d.ts +8 -0
  64. package/dist/db/client.js +23 -0
  65. package/dist/db/config.d.ts +1 -0
  66. package/dist/db/config.js +10 -0
  67. package/dist/db/db-client-generator.d.ts +13 -0
  68. package/dist/db/db-client-generator.js +70 -0
  69. package/dist/db/diff-cli.d.ts +1 -0
  70. package/dist/db/diff-cli.js +46 -0
  71. package/dist/db/diff.d.ts +9 -0
  72. package/dist/db/diff.js +30 -0
  73. package/dist/db/errors.d.ts +34 -0
  74. package/dist/db/errors.js +88 -0
  75. package/dist/db/generate-client-cli.d.ts +1 -0
  76. package/dist/db/generate-client-cli.js +21 -0
  77. package/dist/db/index.d.ts +19 -0
  78. package/dist/db/index.js +17 -0
  79. package/dist/db/load-env.d.ts +3 -0
  80. package/dist/db/load-env.js +19 -0
  81. package/dist/db/migrate-cli.d.ts +1 -0
  82. package/dist/db/migrate-cli.js +88 -0
  83. package/dist/db/migrate.d.ts +3 -0
  84. package/dist/db/migrate.js +32 -0
  85. package/dist/db/migrations.d.ts +17 -0
  86. package/dist/db/migrations.js +81 -0
  87. package/dist/db/model-client.d.ts +36 -0
  88. package/dist/db/model-client.js +83 -0
  89. package/dist/db/model-meta.d.ts +36 -0
  90. package/dist/db/model-meta.js +57 -0
  91. package/dist/db/query-builder.d.ts +33 -0
  92. package/dist/db/query-builder.js +97 -0
  93. package/dist/db/reset-database.d.ts +4 -0
  94. package/dist/db/reset-database.js +9 -0
  95. package/dist/db/row-mapper.d.ts +3 -0
  96. package/dist/db/row-mapper.js +41 -0
  97. package/dist/db/schema-state.d.ts +7 -0
  98. package/dist/db/schema-state.js +32 -0
  99. package/dist/db/type-generator.d.ts +19 -0
  100. package/dist/db/type-generator.js +136 -0
  101. package/dist/db/utils/naming.d.ts +6 -0
  102. package/dist/db/utils/naming.js +23 -0
  103. package/dist/db/where-translator.d.ts +20 -0
  104. package/dist/db/where-translator.js +141 -0
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.js +1 -0
  107. package/dist/routes/health.d.ts +4 -0
  108. package/dist/routes/health.js +4 -0
  109. package/dist/schema-dsl/ast.d.ts +108 -0
  110. package/dist/schema-dsl/ast.js +1 -0
  111. package/dist/schema-dsl/cli.d.ts +1 -0
  112. package/dist/schema-dsl/cli.js +25 -0
  113. package/dist/schema-dsl/index.d.ts +8 -0
  114. package/dist/schema-dsl/index.js +16 -0
  115. package/dist/schema-dsl/inspect.d.ts +1 -0
  116. package/dist/schema-dsl/inspect.js +9 -0
  117. package/dist/schema-dsl/lexer.d.ts +31 -0
  118. package/dist/schema-dsl/lexer.js +216 -0
  119. package/dist/schema-dsl/parser.d.ts +49 -0
  120. package/dist/schema-dsl/parser.js +372 -0
  121. package/dist/schema-dsl/tokens.d.ts +30 -0
  122. package/dist/schema-dsl/tokens.js +35 -0
  123. package/dist/sql-generator/cli.d.ts +1 -0
  124. package/dist/sql-generator/cli.js +7 -0
  125. package/dist/sql-generator/generators/drop-tables.d.ts +2 -0
  126. package/dist/sql-generator/generators/drop-tables.js +8 -0
  127. package/dist/sql-generator/generators/enums.d.ts +4 -0
  128. package/dist/sql-generator/generators/enums.js +16 -0
  129. package/dist/sql-generator/generators/extensions.d.ts +4 -0
  130. package/dist/sql-generator/generators/extensions.js +11 -0
  131. package/dist/sql-generator/generators/foreign-keys.d.ts +4 -0
  132. package/dist/sql-generator/generators/foreign-keys.js +23 -0
  133. package/dist/sql-generator/generators/indexes.d.ts +13 -0
  134. package/dist/sql-generator/generators/indexes.js +39 -0
  135. package/dist/sql-generator/generators/tables.d.ts +4 -0
  136. package/dist/sql-generator/generators/tables.js +65 -0
  137. package/dist/sql-generator/generators/triggers.d.ts +6 -0
  138. package/dist/sql-generator/generators/triggers.js +47 -0
  139. package/dist/sql-generator/index.d.ts +5 -0
  140. package/dist/sql-generator/index.js +3 -0
  141. package/dist/sql-generator/migration-planner.d.ts +15 -0
  142. package/dist/sql-generator/migration-planner.js +207 -0
  143. package/dist/sql-generator/migration-sql-generator.d.ts +9 -0
  144. package/dist/sql-generator/migration-sql-generator.js +181 -0
  145. package/dist/sql-generator/migration-types.d.ts +86 -0
  146. package/dist/sql-generator/migration-types.js +1 -0
  147. package/dist/sql-generator/sql-generator.d.ts +6 -0
  148. package/dist/sql-generator/sql-generator.js +26 -0
  149. package/dist/sql-generator/utils/ast-helpers.d.ts +58 -0
  150. package/dist/sql-generator/utils/ast-helpers.js +252 -0
  151. package/dist/sql-generator/utils/format.d.ts +2 -0
  152. package/dist/sql-generator/utils/format.js +21 -0
  153. package/dist/sql-generator/utils/snake-case.d.ts +3 -0
  154. package/dist/sql-generator/utils/snake-case.js +96 -0
  155. package/dist/sql-generator/utils/type-mapper.d.ts +2 -0
  156. package/dist/sql-generator/utils/type-mapper.js +39 -0
  157. package/dist/sql-generator/utils/value-formatter.d.ts +4 -0
  158. package/dist/sql-generator/utils/value-formatter.js +41 -0
  159. package/dist/types/generated-db.stub.d.ts +2 -0
  160. package/dist/types/generated-db.stub.js +3 -0
  161. package/dist/types/generated-policies.stub.d.ts +7 -0
  162. package/dist/types/generated-policies.stub.js +1 -0
  163. package/package.json +86 -0
@@ -0,0 +1,372 @@
1
+ import { TokenType } from './tokens.js';
2
+ export class ParseError extends Error {
3
+ line;
4
+ col;
5
+ expected;
6
+ found;
7
+ constructor(expected, found) {
8
+ super(`Parse error at line ${found.line}, col ${found.col}: expected ${expected}, found ${found.type} (${found.value || 'EOF'})`);
9
+ this.name = 'ParseError';
10
+ this.line = found.line;
11
+ this.col = found.col;
12
+ this.expected = expected;
13
+ this.found = found;
14
+ }
15
+ }
16
+ export class Parser {
17
+ tokens;
18
+ index = 0;
19
+ constructor(tokens) {
20
+ this.tokens = tokens;
21
+ }
22
+ parseSchema() {
23
+ const start = this.current();
24
+ const extensions = this.parseExtensionsSection();
25
+ const enums = this.parseEnumsSection();
26
+ const models = this.parseModelsSection();
27
+ this.expect(TokenType.EOF, 'end of schema');
28
+ return {
29
+ kind: 'Schema',
30
+ extensions,
31
+ enums,
32
+ models,
33
+ loc: this.loc(start),
34
+ };
35
+ }
36
+ parseModel() {
37
+ this.expect(TokenType.MODEL, "'model'");
38
+ const nameToken = this.expect(TokenType.IDENT, 'model name');
39
+ this.expect(TokenType.LBRACE, "'{'");
40
+ const model = this.parseModelBody(nameToken.value, nameToken);
41
+ this.expect(TokenType.RBRACE, "'}'");
42
+ return model;
43
+ }
44
+ parseField() {
45
+ const start = this.expect(TokenType.IDENT, 'field name');
46
+ this.expect(TokenType.COLON, "':'");
47
+ const type = this.parseTypeExpr();
48
+ const typeLine = type.loc.endLine ?? type.loc.line;
49
+ const attributes = this.parseFieldAttributes(typeLine);
50
+ return {
51
+ kind: 'Field',
52
+ name: start.value,
53
+ type,
54
+ attributes,
55
+ loc: this.loc(start),
56
+ };
57
+ }
58
+ parseAttribute() {
59
+ return this.parseAttributeInternal();
60
+ }
61
+ parseExtensionsSection() {
62
+ this.expect(TokenType.EXTENSIONS, "'extensions'");
63
+ this.expect(TokenType.LBRACE, "'{'");
64
+ const extensions = [];
65
+ while (!this.check(TokenType.RBRACE)) {
66
+ extensions.push(this.parseExtension());
67
+ }
68
+ this.expect(TokenType.RBRACE, "'}'");
69
+ return extensions;
70
+ }
71
+ parseExtension() {
72
+ const start = this.expect(TokenType.IDENT, 'extension name');
73
+ let options;
74
+ if (this.check(TokenType.LBRACE)) {
75
+ options = this.parseBlockLiteral();
76
+ }
77
+ return {
78
+ kind: 'Extension',
79
+ name: start.value,
80
+ options,
81
+ loc: this.loc(start),
82
+ };
83
+ }
84
+ parseEnumsSection() {
85
+ this.expect(TokenType.ENUMS, "'enums'");
86
+ this.expect(TokenType.LBRACE, "'{'");
87
+ const enums = [];
88
+ while (!this.check(TokenType.RBRACE)) {
89
+ enums.push(this.parseEnum());
90
+ }
91
+ this.expect(TokenType.RBRACE, "'}'");
92
+ return enums;
93
+ }
94
+ parseEnum() {
95
+ const start = this.expect(TokenType.IDENT, 'enum name');
96
+ this.expect(TokenType.LBRACE, "'{'");
97
+ const values = this.parseIdentList();
98
+ this.expect(TokenType.RBRACE, "'}'");
99
+ return {
100
+ kind: 'Enum',
101
+ name: start.value,
102
+ values,
103
+ loc: this.loc(start),
104
+ };
105
+ }
106
+ parseModelsSection() {
107
+ this.expect(TokenType.MODELS, "'models'");
108
+ this.expect(TokenType.LBRACE, "'{'");
109
+ const models = [];
110
+ while (!this.check(TokenType.RBRACE)) {
111
+ models.push(this.parseModel());
112
+ }
113
+ this.expect(TokenType.RBRACE, "'}'");
114
+ return models;
115
+ }
116
+ parseModelBody(name, start) {
117
+ const fields = [];
118
+ const attributes = [];
119
+ const directives = [];
120
+ while (!this.check(TokenType.RBRACE)) {
121
+ if (this.check(TokenType.ATAT)) {
122
+ directives.push(this.parseDirective());
123
+ continue;
124
+ }
125
+ if (this.check(TokenType.AT)) {
126
+ attributes.push(this.parseAttributeInternal());
127
+ continue;
128
+ }
129
+ fields.push(this.parseField());
130
+ }
131
+ return {
132
+ kind: 'Model',
133
+ name,
134
+ fields,
135
+ attributes,
136
+ directives,
137
+ loc: this.loc(start),
138
+ };
139
+ }
140
+ parseTypeExpr() {
141
+ const start = this.expect(TokenType.IDENT, 'type name');
142
+ let args;
143
+ if (this.check(TokenType.LPAREN)) {
144
+ this.advance();
145
+ args = this.parseValueList();
146
+ this.expect(TokenType.RPAREN, "')'");
147
+ }
148
+ let optional = false;
149
+ let array = false;
150
+ if (this.check(TokenType.QUESTION)) {
151
+ this.advance();
152
+ optional = true;
153
+ }
154
+ else if (this.check(TokenType.LBRACKET) && this.peekType(1) === TokenType.RBRACKET) {
155
+ this.advance();
156
+ this.advance();
157
+ array = true;
158
+ }
159
+ return {
160
+ kind: 'TypeExpr',
161
+ name: start.value,
162
+ args,
163
+ optional: optional || undefined,
164
+ array: array || undefined,
165
+ loc: this.loc(start),
166
+ };
167
+ }
168
+ parseFieldAttributes(typeLine) {
169
+ const attributes = [];
170
+ while (this.check(TokenType.AT) &&
171
+ this.peekType(1) !== TokenType.AT &&
172
+ this.current().line === typeLine) {
173
+ attributes.push(this.parseAttributeInternal());
174
+ }
175
+ return attributes;
176
+ }
177
+ parseAttributeInternal() {
178
+ const start = this.expect(TokenType.AT, "'@'");
179
+ const nameToken = this.expect(TokenType.IDENT, 'attribute name');
180
+ const args = this.check(TokenType.LPAREN) ? this.parseAttributeArgs() : undefined;
181
+ return {
182
+ kind: 'Attribute',
183
+ name: nameToken.value,
184
+ args,
185
+ loc: this.loc(start),
186
+ };
187
+ }
188
+ parseDirective() {
189
+ const start = this.expect(TokenType.ATAT, "'@@'");
190
+ const nameToken = this.expect(TokenType.IDENT, 'directive name');
191
+ let args;
192
+ if (this.check(TokenType.LBRACE)) {
193
+ args = {
194
+ kind: 'KeyValueArgs',
195
+ pairs: this.parseBlockLiteral().pairs,
196
+ };
197
+ }
198
+ else if (this.check(TokenType.LPAREN)) {
199
+ args = this.parseAttributeArgs();
200
+ }
201
+ return {
202
+ kind: 'Directive',
203
+ name: nameToken.value,
204
+ args,
205
+ loc: this.loc(start),
206
+ };
207
+ }
208
+ parseAttributeArgs() {
209
+ this.expect(TokenType.LPAREN, "'('");
210
+ if (this.check(TokenType.RPAREN)) {
211
+ this.advance();
212
+ return { kind: 'ExpressionArgs', expressions: [] };
213
+ }
214
+ const args = this.isKeyValueListStart()
215
+ ? { kind: 'KeyValueArgs', pairs: this.parseKeyValueList() }
216
+ : { kind: 'ExpressionArgs', expressions: this.parseValueList() };
217
+ this.expect(TokenType.RPAREN, "')'");
218
+ return args;
219
+ }
220
+ parseValue() {
221
+ const token = this.current();
222
+ switch (token.type) {
223
+ case TokenType.STRING:
224
+ this.advance();
225
+ return { kind: 'StringLiteral', value: token.value };
226
+ case TokenType.TRIPLE_STRING:
227
+ this.advance();
228
+ return { kind: 'TripleStringLiteral', value: token.value };
229
+ case TokenType.NUMBER:
230
+ this.advance();
231
+ return { kind: 'NumberLiteral', value: Number(token.value) };
232
+ case TokenType.BOOLEAN:
233
+ this.advance();
234
+ return { kind: 'BooleanLiteral', value: token.value === 'true' };
235
+ case TokenType.LBRACKET:
236
+ return this.parseArrayLiteral();
237
+ case TokenType.LBRACE:
238
+ return this.parseBlockLiteral();
239
+ case TokenType.IDENT:
240
+ return this.parseIdentOrCall();
241
+ default:
242
+ throw new ParseError('value', token);
243
+ }
244
+ }
245
+ parseIdentOrCall() {
246
+ const ident = this.expect(TokenType.IDENT, 'identifier');
247
+ if (!this.check(TokenType.LPAREN)) {
248
+ return { kind: 'Identifier', name: ident.value };
249
+ }
250
+ this.advance();
251
+ const args = this.check(TokenType.RPAREN) ? [] : this.parseValueList();
252
+ this.expect(TokenType.RPAREN, "')'");
253
+ return {
254
+ kind: 'CallExpression',
255
+ callee: ident.value,
256
+ args,
257
+ };
258
+ }
259
+ parseArrayLiteral() {
260
+ this.expect(TokenType.LBRACKET, "'['");
261
+ const elements = this.check(TokenType.RBRACKET) ? [] : this.parseValueList();
262
+ this.expect(TokenType.RBRACKET, "']'");
263
+ return { kind: 'ArrayLiteral', elements };
264
+ }
265
+ parseBlockLiteral() {
266
+ this.expect(TokenType.LBRACE, "'{'");
267
+ const pairs = this.check(TokenType.RBRACE) ? [] : this.parseKeyValueList();
268
+ this.expect(TokenType.RBRACE, "'}'");
269
+ return { kind: 'BlockLiteral', pairs };
270
+ }
271
+ parseKeyValueList() {
272
+ const pairs = [];
273
+ do {
274
+ const keyToken = this.expect(TokenType.IDENT, 'key');
275
+ this.expect(TokenType.COLON, "':'");
276
+ const value = this.parseValue();
277
+ pairs.push({
278
+ key: keyToken.value,
279
+ value,
280
+ loc: this.loc(keyToken),
281
+ });
282
+ } while (this.match(TokenType.COMMA) && !this.isListEnd());
283
+ this.consumeTrailingComma();
284
+ return pairs;
285
+ }
286
+ parseValueList() {
287
+ const values = [];
288
+ do {
289
+ values.push(this.parseValue());
290
+ } while (this.match(TokenType.COMMA) && !this.isListEnd());
291
+ this.consumeTrailingComma();
292
+ return values;
293
+ }
294
+ parseIdentList() {
295
+ const values = [];
296
+ do {
297
+ values.push(this.expect(TokenType.IDENT, 'identifier').value);
298
+ } while (this.match(TokenType.COMMA) && !this.check(TokenType.RBRACE));
299
+ this.consumeTrailingComma();
300
+ return values;
301
+ }
302
+ isKeyValueListStart() {
303
+ if (!this.check(TokenType.IDENT)) {
304
+ return false;
305
+ }
306
+ return this.peekType(1) === TokenType.COLON;
307
+ }
308
+ isListEnd() {
309
+ return (this.check(TokenType.RPAREN) ||
310
+ this.check(TokenType.RBRACE) ||
311
+ this.check(TokenType.RBRACKET));
312
+ }
313
+ consumeTrailingComma() {
314
+ if (this.isListEnd() && this.previous().type === TokenType.COMMA) {
315
+ // Trailing comma already consumed by match(); nothing else to do.
316
+ }
317
+ }
318
+ loc(start) {
319
+ const prev = this.previous();
320
+ return {
321
+ line: start.line,
322
+ col: start.col,
323
+ endLine: prev.line,
324
+ endCol: prev.col + prev.value.length,
325
+ };
326
+ }
327
+ current() {
328
+ return this.tokens[this.index] ?? this.eofToken();
329
+ }
330
+ previous() {
331
+ return this.tokens[this.index - 1] ?? this.eofToken();
332
+ }
333
+ advance() {
334
+ if (!this.isAtEnd()) {
335
+ this.index += 1;
336
+ }
337
+ return this.previous();
338
+ }
339
+ match(type) {
340
+ if (!this.check(type)) {
341
+ return false;
342
+ }
343
+ this.advance();
344
+ return true;
345
+ }
346
+ check(type) {
347
+ return this.current().type === type;
348
+ }
349
+ peekType(offset) {
350
+ return this.tokens[this.index + offset]?.type ?? TokenType.EOF;
351
+ }
352
+ expect(type, description) {
353
+ const token = this.current();
354
+ if (token.type !== type) {
355
+ throw new ParseError(description, token);
356
+ }
357
+ this.advance();
358
+ return token;
359
+ }
360
+ isAtEnd() {
361
+ return this.current().type === TokenType.EOF;
362
+ }
363
+ eofToken() {
364
+ const last = this.tokens[this.tokens.length - 1];
365
+ return {
366
+ type: TokenType.EOF,
367
+ value: '',
368
+ line: last?.line ?? 1,
369
+ col: last?.col ?? 1,
370
+ };
371
+ }
372
+ }
@@ -0,0 +1,30 @@
1
+ export declare enum TokenType {
2
+ EXTENSIONS = "EXTENSIONS",
3
+ ENUMS = "ENUMS",
4
+ MODELS = "MODELS",
5
+ MODEL = "MODEL",
6
+ STRING = "STRING",
7
+ TRIPLE_STRING = "TRIPLE_STRING",
8
+ NUMBER = "NUMBER",
9
+ BOOLEAN = "BOOLEAN",
10
+ IDENT = "IDENT",
11
+ LBRACE = "LBRACE",
12
+ RBRACE = "RBRACE",
13
+ LBRACKET = "LBRACKET",
14
+ RBRACKET = "RBRACKET",
15
+ LPAREN = "LPAREN",
16
+ RPAREN = "RPAREN",
17
+ COLON = "COLON",
18
+ COMMA = "COMMA",
19
+ QUESTION = "QUESTION",
20
+ AT = "AT",
21
+ ATAT = "ATAT",
22
+ EOF = "EOF"
23
+ }
24
+ export interface Token {
25
+ type: TokenType;
26
+ value: string;
27
+ line: number;
28
+ col: number;
29
+ }
30
+ export declare function keywordTokenType(value: string): TokenType;
@@ -0,0 +1,35 @@
1
+ export var TokenType;
2
+ (function (TokenType) {
3
+ TokenType["EXTENSIONS"] = "EXTENSIONS";
4
+ TokenType["ENUMS"] = "ENUMS";
5
+ TokenType["MODELS"] = "MODELS";
6
+ TokenType["MODEL"] = "MODEL";
7
+ TokenType["STRING"] = "STRING";
8
+ TokenType["TRIPLE_STRING"] = "TRIPLE_STRING";
9
+ TokenType["NUMBER"] = "NUMBER";
10
+ TokenType["BOOLEAN"] = "BOOLEAN";
11
+ TokenType["IDENT"] = "IDENT";
12
+ TokenType["LBRACE"] = "LBRACE";
13
+ TokenType["RBRACE"] = "RBRACE";
14
+ TokenType["LBRACKET"] = "LBRACKET";
15
+ TokenType["RBRACKET"] = "RBRACKET";
16
+ TokenType["LPAREN"] = "LPAREN";
17
+ TokenType["RPAREN"] = "RPAREN";
18
+ TokenType["COLON"] = "COLON";
19
+ TokenType["COMMA"] = "COMMA";
20
+ TokenType["QUESTION"] = "QUESTION";
21
+ TokenType["AT"] = "AT";
22
+ TokenType["ATAT"] = "ATAT";
23
+ TokenType["EOF"] = "EOF";
24
+ })(TokenType || (TokenType = {}));
25
+ const KEYWORDS = {
26
+ extensions: TokenType.EXTENSIONS,
27
+ enums: TokenType.ENUMS,
28
+ models: TokenType.MODELS,
29
+ model: TokenType.MODEL,
30
+ true: TokenType.BOOLEAN,
31
+ false: TokenType.BOOLEAN,
32
+ };
33
+ export function keywordTokenType(value) {
34
+ return KEYWORDS[value] ?? TokenType.IDENT;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { SqlGenerator } from './sql-generator.js';
4
+ const schemaPath = process.argv[2] ?? join(process.cwd(), 'app.schema');
5
+ const source = readFileSync(schemaPath, 'utf8');
6
+ const sql = new SqlGenerator().generateFromSource(source);
7
+ process.stdout.write(sql);
@@ -0,0 +1,2 @@
1
+ import type { Schema } from '../../schema-dsl/ast.js';
2
+ export declare function generateDropTables(schema: Schema): string;
@@ -0,0 +1,8 @@
1
+ import { joinSection } from '../utils/format.js';
2
+ import { quoteIdentifier, toTableName } from '../utils/snake-case.js';
3
+ export function generateDropTables(schema) {
4
+ const statements = [...schema.models]
5
+ .reverse()
6
+ .map((model) => `DROP TABLE IF EXISTS ${quoteIdentifier(toTableName(model.name))} CASCADE;`);
7
+ return joinSection('Drop tables', statements);
8
+ }
@@ -0,0 +1,4 @@
1
+ import type { Enum, Schema } from '../../schema-dsl/ast.js';
2
+ export declare function generateEnum(enumDef: Enum): string;
3
+ export declare function generateAddEnumValue(enumName: string, value: string): string;
4
+ export declare function generateEnums(schema: Schema): string;
@@ -0,0 +1,16 @@
1
+ import { joinSection } from '../utils/format.js';
2
+ import { escapeSqlString } from '../utils/value-formatter.js';
3
+ import { toSnakeCase } from '../utils/snake-case.js';
4
+ export function generateEnum(enumDef) {
5
+ const typeName = toSnakeCase(enumDef.name);
6
+ const values = enumDef.values.map((value) => `'${escapeSqlString(value)}'`).join(', ');
7
+ return `CREATE TYPE ${typeName} AS ENUM (${values});`;
8
+ }
9
+ export function generateAddEnumValue(enumName, value) {
10
+ const typeName = toSnakeCase(enumName);
11
+ return `ALTER TYPE ${typeName} ADD VALUE '${escapeSqlString(value)}';`;
12
+ }
13
+ export function generateEnums(schema) {
14
+ const statements = schema.enums.map((enumDef) => generateEnum(enumDef));
15
+ return joinSection('Enums', statements);
16
+ }
@@ -0,0 +1,4 @@
1
+ import type { Schema } from '../../schema-dsl/ast.js';
2
+ export declare function generateCreateExtension(name: string): string;
3
+ export declare function generateDropExtension(name: string): string;
4
+ export declare function generateExtensions(schema: Schema): string;
@@ -0,0 +1,11 @@
1
+ import { joinSection } from '../utils/format.js';
2
+ export function generateCreateExtension(name) {
3
+ return `CREATE EXTENSION IF NOT EXISTS "${name}" WITH SCHEMA public;`;
4
+ }
5
+ export function generateDropExtension(name) {
6
+ return `DROP EXTENSION IF EXISTS "${name}";`;
7
+ }
8
+ export function generateExtensions(schema) {
9
+ const statements = schema.extensions.map((extension) => generateCreateExtension(extension.name));
10
+ return joinSection('Extensions', statements);
11
+ }
@@ -0,0 +1,4 @@
1
+ import type { Schema } from '../../schema-dsl/ast.js';
2
+ import { type ForeignKeyInfo } from '../utils/ast-helpers.js';
3
+ export declare function generateForeignKey(foreignKey: ForeignKeyInfo): string;
4
+ export declare function generateForeignKeys(schema: Schema): string;
@@ -0,0 +1,23 @@
1
+ import { collectForeignKeys } from '../utils/ast-helpers.js';
2
+ import { joinSection } from '../utils/format.js';
3
+ import { quoteIdentifier } from '../utils/snake-case.js';
4
+ export function generateForeignKey(foreignKey) {
5
+ const constraintName = `${foreignKey.sourceTable}_${foreignKey.sourceColumns.join('_')}_fkey`;
6
+ const sourceColumns = foreignKey.sourceColumns.join(', ');
7
+ const targetColumns = foreignKey.targetColumns.join(', ');
8
+ const lines = [
9
+ `ALTER TABLE ${quoteIdentifier(foreignKey.sourceTable)} ADD CONSTRAINT ${constraintName}`,
10
+ ` FOREIGN KEY (${sourceColumns}) REFERENCES ${quoteIdentifier(foreignKey.targetTable)} (${targetColumns})`,
11
+ ];
12
+ if (foreignKey.onDelete) {
13
+ lines.push(` ON DELETE ${foreignKey.onDelete}`);
14
+ }
15
+ if (foreignKey.onUpdate) {
16
+ lines.push(` ON UPDATE ${foreignKey.onUpdate}`);
17
+ }
18
+ return lines.join('\n') + ';';
19
+ }
20
+ export function generateForeignKeys(schema) {
21
+ const statements = collectForeignKeys(schema).map((foreignKey) => generateForeignKey(foreignKey));
22
+ return joinSection('Alter tables (foreign keys)', statements);
23
+ }
@@ -0,0 +1,13 @@
1
+ import type { Model, Schema } from '../../schema-dsl/ast.js';
2
+ export interface NormalizedIndex {
3
+ fields: string[];
4
+ where?: string;
5
+ unique?: boolean;
6
+ name?: string;
7
+ type?: string;
8
+ }
9
+ export declare function buildIndexName(tableName: string, fields: string[]): string;
10
+ export declare function resolveIndexName(tableName: string, normalized: NormalizedIndex): string;
11
+ export declare function generateCreateIndex(model: Model, normalized: NormalizedIndex): string;
12
+ export declare function generateDropIndex(model: Model, normalized: NormalizedIndex): string;
13
+ export declare function generateIndexes(schema: Schema): string;
@@ -0,0 +1,39 @@
1
+ import { getDirectives, getModelNames, normalizeIndexDirective, } from '../utils/ast-helpers.js';
2
+ import { joinSection } from '../utils/format.js';
3
+ import { quoteIdentifier, toSnakeCase, toTableName } from '../utils/snake-case.js';
4
+ export function buildIndexName(tableName, fields) {
5
+ const fieldPart = fields.map(toSnakeCase).join('_');
6
+ return `${tableName}_${fieldPart}_idx`;
7
+ }
8
+ export function resolveIndexName(tableName, normalized) {
9
+ return normalized.name ?? buildIndexName(tableName, normalized.fields);
10
+ }
11
+ export function generateCreateIndex(model, normalized) {
12
+ const tableName = quoteIdentifier(toTableName(model.name));
13
+ const columns = normalized.fields.map(toSnakeCase).join(', ');
14
+ const indexName = resolveIndexName(toTableName(model.name), normalized);
15
+ const uniqueKeyword = normalized.unique ? 'UNIQUE ' : '';
16
+ const usingClause = normalized.type && normalized.type.toUpperCase() !== 'BTREE'
17
+ ? ` USING ${normalized.type.toLowerCase()}`
18
+ : normalized.type?.toUpperCase() === 'BTREE'
19
+ ? ' USING btree'
20
+ : '';
21
+ const whereClause = normalized.where ? ` WHERE ${normalized.where}` : '';
22
+ return `CREATE ${uniqueKeyword}INDEX ${indexName} ON ${tableName}${usingClause} (${columns})${whereClause};`;
23
+ }
24
+ export function generateDropIndex(model, normalized) {
25
+ const indexName = resolveIndexName(toTableName(model.name), normalized);
26
+ return `DROP INDEX IF EXISTS ${indexName};`;
27
+ }
28
+ export function generateIndexes(schema) {
29
+ const modelNames = getModelNames(schema);
30
+ const statements = [];
31
+ for (const model of schema.models) {
32
+ const indexDirectives = getDirectives(model, 'index');
33
+ for (const directive of indexDirectives) {
34
+ const normalized = normalizeIndexDirective(directive, model, modelNames);
35
+ statements.push(generateCreateIndex(model, normalized));
36
+ }
37
+ }
38
+ return joinSection('Create indexes', statements);
39
+ }
@@ -0,0 +1,4 @@
1
+ import type { Field, Model, Schema } from '../../schema-dsl/ast.js';
2
+ export declare function generateTables(schema: Schema): string;
3
+ export declare function generateTable(model: Model, enumNames: Set<string>, modelNames: Set<string>): string;
4
+ export declare function generateColumnDefinition(field: Field, model: Model, enumNames: Set<string>, modelNames: Set<string>): string;
@@ -0,0 +1,65 @@
1
+ import { collectValidationComments, getDefaultExpression, getEnumNames, getModelNames, getPrimaryKey, getStoredFields, fieldHasAttribute, } from '../utils/ast-helpers.js';
2
+ import { formatCreateTable, joinSection } from '../utils/format.js';
3
+ import { mapColumnType } from '../utils/type-mapper.js';
4
+ import { quoteIdentifier, toSnakeCase, toTableName } from '../utils/snake-case.js';
5
+ export function generateTables(schema) {
6
+ const enumNames = getEnumNames(schema);
7
+ const modelNames = getModelNames(schema);
8
+ const statements = schema.models.map((model) => generateTable(model, enumNames, modelNames));
9
+ return joinSection('Create tables', statements);
10
+ }
11
+ export function generateTable(model, enumNames, modelNames) {
12
+ const primaryKey = getPrimaryKey(model);
13
+ const blocks = [];
14
+ for (const field of getStoredFields(model, modelNames)) {
15
+ const columnParts = [];
16
+ const columnName = toSnakeCase(field.name);
17
+ columnParts.push(columnName);
18
+ columnParts.push(mapColumnType(field.type, enumNames));
19
+ const isSingleFieldPrimaryKey = primaryKey && !primaryKey.composite && primaryKey.fields.length === 1 && primaryKey.fields[0] === field.name;
20
+ if (isSingleFieldPrimaryKey) {
21
+ columnParts.push('PRIMARY KEY');
22
+ }
23
+ if (fieldHasAttribute(field, 'unique')) {
24
+ columnParts.push('UNIQUE');
25
+ }
26
+ const defaultExpression = getDefaultExpression(field, enumNames);
27
+ if (defaultExpression) {
28
+ columnParts.push(`DEFAULT ${defaultExpression}`);
29
+ }
30
+ if (!field.type.optional) {
31
+ columnParts.push('NOT NULL');
32
+ }
33
+ const block = [columnParts.join(' ')];
34
+ block.push(...collectValidationComments(field));
35
+ blocks.push(block);
36
+ }
37
+ if (primaryKey?.composite) {
38
+ const pkColumns = primaryKey.fields.map(toSnakeCase).join(', ');
39
+ blocks.push([`PRIMARY KEY (${pkColumns})`]);
40
+ }
41
+ const tableName = quoteIdentifier(toTableName(model.name));
42
+ return formatCreateTable(tableName, blocks);
43
+ }
44
+ export function generateColumnDefinition(field, model, enumNames, modelNames) {
45
+ const primaryKey = getPrimaryKey(model);
46
+ const columnParts = [];
47
+ const columnName = toSnakeCase(field.name);
48
+ columnParts.push(columnName);
49
+ columnParts.push(mapColumnType(field.type, enumNames));
50
+ const isSingleFieldPrimaryKey = primaryKey && !primaryKey.composite && primaryKey.fields.length === 1 && primaryKey.fields[0] === field.name;
51
+ if (isSingleFieldPrimaryKey) {
52
+ columnParts.push('PRIMARY KEY');
53
+ }
54
+ if (fieldHasAttribute(field, 'unique')) {
55
+ columnParts.push('UNIQUE');
56
+ }
57
+ const defaultExpression = getDefaultExpression(field, enumNames);
58
+ if (defaultExpression) {
59
+ columnParts.push(`DEFAULT ${defaultExpression}`);
60
+ }
61
+ if (!field.type.optional) {
62
+ columnParts.push('NOT NULL');
63
+ }
64
+ return columnParts.join(' ');
65
+ }
@@ -0,0 +1,6 @@
1
+ import type { Model, Schema } from '../../schema-dsl/ast.js';
2
+ import { type NormalizedTrigger } from '../utils/ast-helpers.js';
3
+ export type { NormalizedTrigger };
4
+ export declare function generateCreateTrigger(model: Model, normalized: NormalizedTrigger): string;
5
+ export declare function generateDropTrigger(model: Model, normalized: NormalizedTrigger): string;
6
+ export declare function generateTriggers(schema: Schema): string;