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,141 @@
1
+ export class WhereTranslator {
2
+ model;
3
+ scalarFields;
4
+ paramIndex;
5
+ startParamIndex;
6
+ constructor(model, startParamIndex = 1) {
7
+ this.model = model;
8
+ this.scalarFields = new Set(model.fields.map((field) => field.name));
9
+ this.startParamIndex = startParamIndex;
10
+ this.paramIndex = startParamIndex;
11
+ }
12
+ translate(where) {
13
+ this.paramIndex = this.startParamIndex;
14
+ if (!where || Object.keys(where).length === 0) {
15
+ return { sql: '', params: [] };
16
+ }
17
+ const params = [];
18
+ const clauses = [];
19
+ for (const [key, value] of Object.entries(where)) {
20
+ if (key === 'AND') {
21
+ clauses.push(this.translateLogical(value, 'AND', params));
22
+ continue;
23
+ }
24
+ if (key === 'OR') {
25
+ clauses.push(this.translateLogical(value, 'OR', params));
26
+ continue;
27
+ }
28
+ if (key === 'NOT') {
29
+ const nested = this.translateNested(value, params);
30
+ clauses.push(`NOT (${nested})`);
31
+ continue;
32
+ }
33
+ if (!this.scalarFields.has(key)) {
34
+ throw new Error(`Unknown where field "${key}" on model ${this.model.name}`);
35
+ }
36
+ clauses.push(this.translateField(key, value, params));
37
+ }
38
+ return {
39
+ sql: clauses.join(' AND '),
40
+ params,
41
+ };
42
+ }
43
+ getNextParamIndex() {
44
+ return this.paramIndex;
45
+ }
46
+ translateLogical(value, operator, params) {
47
+ if (!Array.isArray(value)) {
48
+ throw new Error(`${operator} expects an array of where clauses`);
49
+ }
50
+ const parts = value.map((entry) => this.translateNested(entry, params));
51
+ return `(${parts.join(` ${operator} `)})`;
52
+ }
53
+ translateNested(where, params) {
54
+ const nestedClauses = [];
55
+ for (const [key, value] of Object.entries(where)) {
56
+ if (key === 'AND') {
57
+ nestedClauses.push(this.translateLogical(value, 'AND', params));
58
+ continue;
59
+ }
60
+ if (key === 'OR') {
61
+ nestedClauses.push(this.translateLogical(value, 'OR', params));
62
+ continue;
63
+ }
64
+ if (key === 'NOT') {
65
+ const nested = this.translateNested(value, params);
66
+ nestedClauses.push(`NOT (${nested})`);
67
+ continue;
68
+ }
69
+ nestedClauses.push(this.translateField(key, value, params));
70
+ }
71
+ return nestedClauses.join(' AND ');
72
+ }
73
+ translateField(fieldName, value, params) {
74
+ const field = this.model.fieldByName.get(fieldName);
75
+ if (!field) {
76
+ throw new Error(`Unknown field "${fieldName}"`);
77
+ }
78
+ const column = field.columnName;
79
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
80
+ return this.translateOperator(field, column, value, params);
81
+ }
82
+ params.push(value);
83
+ return `${column} = ${this.nextPlaceholder()}`;
84
+ }
85
+ translateOperator(field, column, operators, params) {
86
+ const entries = Object.entries(operators);
87
+ if (entries.length !== 1) {
88
+ throw new Error(`Field "${field.name}" expects a single filter operator`);
89
+ }
90
+ const [operator, rawValue] = entries[0];
91
+ if (operator === 'equals') {
92
+ params.push(rawValue);
93
+ return `${column} = ${this.nextPlaceholder()}`;
94
+ }
95
+ if (operator === 'in') {
96
+ if (!Array.isArray(rawValue) || rawValue.length === 0) {
97
+ throw new Error(`Field "${field.name}" "in" expects a non-empty array`);
98
+ }
99
+ const placeholders = rawValue.map((item) => {
100
+ params.push(item);
101
+ return this.nextPlaceholder();
102
+ });
103
+ return `${column} IN (${placeholders.join(', ')})`;
104
+ }
105
+ if (operator === 'contains' || operator === 'startsWith' || operator === 'endsWith') {
106
+ if (!field.isString) {
107
+ throw new Error(`Operator "${operator}" is only supported on string fields`);
108
+ }
109
+ params.push(wrapLikePattern(String(rawValue), operator));
110
+ return `${column} LIKE ${this.nextPlaceholder()}`;
111
+ }
112
+ if (operator === 'gt' || operator === 'gte' || operator === 'lt' || operator === 'lte') {
113
+ if (!field.isNumeric && !field.isEnum) {
114
+ throw new Error(`Operator "${operator}" is only supported on numeric or enum fields`);
115
+ }
116
+ const sqlOperator = {
117
+ gt: '>',
118
+ gte: '>=',
119
+ lt: '<',
120
+ lte: '<=',
121
+ }[operator];
122
+ params.push(rawValue);
123
+ return `${column} ${sqlOperator} ${this.nextPlaceholder()}`;
124
+ }
125
+ throw new Error(`Unsupported operator "${operator}" for field "${field.name}"`);
126
+ }
127
+ nextPlaceholder() {
128
+ const placeholder = `$${this.paramIndex}`;
129
+ this.paramIndex += 1;
130
+ return placeholder;
131
+ }
132
+ }
133
+ function wrapLikePattern(value, operator) {
134
+ if (operator === 'contains') {
135
+ return `%${value}%`;
136
+ }
137
+ if (operator === 'startsWith') {
138
+ return `${value}%`;
139
+ }
140
+ return `%${value}`;
141
+ }
@@ -0,0 +1 @@
1
+ export * from './schema-dsl/index.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './schema-dsl/index.js';
@@ -0,0 +1,4 @@
1
+ import { Hono } from 'hono';
2
+ import type { AppEnv } from 'schematic-pg/api/types';
3
+ declare const router: Hono<AppEnv, import("hono/types").BlankSchema, "/">;
4
+ export default router;
@@ -0,0 +1,4 @@
1
+ import { Hono } from 'hono';
2
+ const router = new Hono();
3
+ router.get('/', (c) => c.json({ ok: true }));
4
+ export default router;
@@ -0,0 +1,108 @@
1
+ export interface SourceLocation {
2
+ line: number;
3
+ col: number;
4
+ endLine?: number;
5
+ endCol?: number;
6
+ }
7
+ export interface Schema {
8
+ kind: 'Schema';
9
+ extensions: Extension[];
10
+ enums: Enum[];
11
+ models: Model[];
12
+ loc: SourceLocation;
13
+ }
14
+ export interface Extension {
15
+ kind: 'Extension';
16
+ name: string;
17
+ options?: BlockLiteral;
18
+ loc: SourceLocation;
19
+ }
20
+ export interface Enum {
21
+ kind: 'Enum';
22
+ name: string;
23
+ values: string[];
24
+ loc: SourceLocation;
25
+ }
26
+ export interface Model {
27
+ kind: 'Model';
28
+ name: string;
29
+ fields: Field[];
30
+ attributes: Attribute[];
31
+ directives: Directive[];
32
+ loc: SourceLocation;
33
+ }
34
+ export interface Field {
35
+ kind: 'Field';
36
+ name: string;
37
+ type: TypeExpr;
38
+ attributes: Attribute[];
39
+ loc: SourceLocation;
40
+ }
41
+ export interface TypeExpr {
42
+ kind: 'TypeExpr';
43
+ name: string;
44
+ args?: Value[];
45
+ optional?: boolean;
46
+ array?: boolean;
47
+ loc: SourceLocation;
48
+ }
49
+ export interface Attribute {
50
+ kind: 'Attribute';
51
+ name: string;
52
+ args?: AttributeArgs;
53
+ loc: SourceLocation;
54
+ }
55
+ export type AttributeArgs = KeyValueArgs | ExpressionArgs;
56
+ export interface KeyValueArgs {
57
+ kind: 'KeyValueArgs';
58
+ pairs: KeyValuePair[];
59
+ }
60
+ export interface ExpressionArgs {
61
+ kind: 'ExpressionArgs';
62
+ expressions: Value[];
63
+ }
64
+ export interface Directive {
65
+ kind: 'Directive';
66
+ name: string;
67
+ args?: AttributeArgs;
68
+ loc: SourceLocation;
69
+ }
70
+ export interface KeyValuePair {
71
+ key: string;
72
+ value: Value;
73
+ loc?: SourceLocation;
74
+ }
75
+ export type Value = StringLiteral | TripleStringLiteral | NumberLiteral | BooleanLiteral | Identifier | ArrayLiteral | BlockLiteral | CallExpression;
76
+ export interface StringLiteral {
77
+ kind: 'StringLiteral';
78
+ value: string;
79
+ }
80
+ export interface TripleStringLiteral {
81
+ kind: 'TripleStringLiteral';
82
+ value: string;
83
+ }
84
+ export interface NumberLiteral {
85
+ kind: 'NumberLiteral';
86
+ value: number;
87
+ }
88
+ export interface BooleanLiteral {
89
+ kind: 'BooleanLiteral';
90
+ value: boolean;
91
+ }
92
+ export interface Identifier {
93
+ kind: 'Identifier';
94
+ name: string;
95
+ }
96
+ export interface ArrayLiteral {
97
+ kind: 'ArrayLiteral';
98
+ elements: Value[];
99
+ }
100
+ export interface BlockLiteral {
101
+ kind: 'BlockLiteral';
102
+ pairs: KeyValuePair[];
103
+ }
104
+ export interface CallExpression {
105
+ kind: 'CallExpression';
106
+ callee: string;
107
+ args: Value[];
108
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { inspect, parse, tokenize } from './index.js';
3
+ const args = process.argv.slice(2);
4
+ const showTokens = args.includes('--tokens');
5
+ const filePath = args.find((arg) => !arg.startsWith('--'));
6
+ if (!filePath) {
7
+ console.error('Usage: tsx src/schema-dsl/cli.ts [--tokens] <file.schema>');
8
+ process.exit(1);
9
+ }
10
+ const source = readFileSync(filePath, 'utf8');
11
+ try {
12
+ if (showTokens) {
13
+ const tokens = tokenize(source);
14
+ for (const token of tokens) {
15
+ console.log(`${token.line}:${token.col}\t${token.type}\t${JSON.stringify(token.value)}`);
16
+ }
17
+ process.exit(0);
18
+ }
19
+ const schema = parse(source);
20
+ console.log(inspect(schema));
21
+ }
22
+ catch (error) {
23
+ console.error(error instanceof Error ? error.message : error);
24
+ process.exit(1);
25
+ }
@@ -0,0 +1,8 @@
1
+ import type { Schema } from './ast.js';
2
+ export { Lexer, LexError } from './lexer.js';
3
+ export { Parser, ParseError } from './parser.js';
4
+ export { inspect } from './inspect.js';
5
+ export * from './ast.js';
6
+ export * from './tokens.js';
7
+ export declare function parse(source: string): Schema;
8
+ export declare function tokenize(source: string): import("./tokens.js").Token[];
@@ -0,0 +1,16 @@
1
+ import { Lexer } from './lexer.js';
2
+ import { Parser } from './parser.js';
3
+ export { Lexer, LexError } from './lexer.js';
4
+ export { Parser, ParseError } from './parser.js';
5
+ export { inspect } from './inspect.js';
6
+ export * from './ast.js';
7
+ export * from './tokens.js';
8
+ export function parse(source) {
9
+ const lexer = new Lexer(source);
10
+ const tokens = lexer.tokenizeAll();
11
+ const parser = new Parser(tokens);
12
+ return parser.parseSchema();
13
+ }
14
+ export function tokenize(source) {
15
+ return new Lexer(source).tokenizeAll();
16
+ }
@@ -0,0 +1 @@
1
+ export declare function inspect(node: unknown): string;
@@ -0,0 +1,9 @@
1
+ export function inspect(node) {
2
+ return JSON.stringify(node, replacer, 2);
3
+ }
4
+ function replacer(_key, value) {
5
+ if (value === undefined) {
6
+ return undefined;
7
+ }
8
+ return value;
9
+ }
@@ -0,0 +1,31 @@
1
+ import { Token } from './tokens.js';
2
+ export declare class LexError extends Error {
3
+ readonly line: number;
4
+ readonly col: number;
5
+ constructor(message: string, line: number, col: number);
6
+ }
7
+ export declare class Lexer {
8
+ private readonly source;
9
+ private pos;
10
+ private line;
11
+ private col;
12
+ private cached;
13
+ constructor(source: string);
14
+ peek(): Token;
15
+ nextToken(): Token;
16
+ tokenizeAll(): Token[];
17
+ private scanToken;
18
+ private scanString;
19
+ private scanTripleString;
20
+ private scanNumber;
21
+ private scanIdentifier;
22
+ private skipWhitespaceAndComments;
23
+ private makeToken;
24
+ private isAtEnd;
25
+ private peekChar;
26
+ private advance;
27
+ private match;
28
+ private isDigit;
29
+ private isIdentStart;
30
+ private isIdentPart;
31
+ }
@@ -0,0 +1,216 @@
1
+ import { keywordTokenType, TokenType } from './tokens.js';
2
+ export class LexError extends Error {
3
+ line;
4
+ col;
5
+ constructor(message, line, col) {
6
+ super(`${message} at line ${line}, col ${col}`);
7
+ this.name = 'LexError';
8
+ this.line = line;
9
+ this.col = col;
10
+ }
11
+ }
12
+ export class Lexer {
13
+ source;
14
+ pos = 0;
15
+ line = 1;
16
+ col = 1;
17
+ cached = null;
18
+ constructor(source) {
19
+ this.source = source;
20
+ }
21
+ peek() {
22
+ if (!this.cached) {
23
+ this.cached = this.scanToken();
24
+ }
25
+ return this.cached;
26
+ }
27
+ nextToken() {
28
+ const token = this.peek();
29
+ this.cached = null;
30
+ return token;
31
+ }
32
+ tokenizeAll() {
33
+ const tokens = [];
34
+ let token = this.nextToken();
35
+ while (token.type !== TokenType.EOF) {
36
+ tokens.push(token);
37
+ token = this.nextToken();
38
+ }
39
+ tokens.push(token);
40
+ return tokens;
41
+ }
42
+ scanToken() {
43
+ this.skipWhitespaceAndComments();
44
+ const startLine = this.line;
45
+ const startCol = this.col;
46
+ if (this.isAtEnd()) {
47
+ return this.makeToken(TokenType.EOF, '', startLine, startCol);
48
+ }
49
+ const char = this.advance();
50
+ switch (char) {
51
+ case '{':
52
+ return this.makeToken(TokenType.LBRACE, char, startLine, startCol);
53
+ case '}':
54
+ return this.makeToken(TokenType.RBRACE, char, startLine, startCol);
55
+ case '[':
56
+ return this.makeToken(TokenType.LBRACKET, char, startLine, startCol);
57
+ case ']':
58
+ return this.makeToken(TokenType.RBRACKET, char, startLine, startCol);
59
+ case '(':
60
+ return this.makeToken(TokenType.LPAREN, char, startLine, startCol);
61
+ case ')':
62
+ return this.makeToken(TokenType.RPAREN, char, startLine, startCol);
63
+ case ':':
64
+ return this.makeToken(TokenType.COLON, char, startLine, startCol);
65
+ case ',':
66
+ return this.makeToken(TokenType.COMMA, char, startLine, startCol);
67
+ case '?':
68
+ return this.makeToken(TokenType.QUESTION, char, startLine, startCol);
69
+ case '@':
70
+ if (this.match('@')) {
71
+ return this.makeToken(TokenType.ATAT, '@@', startLine, startCol);
72
+ }
73
+ return this.makeToken(TokenType.AT, char, startLine, startCol);
74
+ case '"':
75
+ if (this.match('"') && this.match('"')) {
76
+ return this.scanTripleString(startLine, startCol);
77
+ }
78
+ return this.scanString(startLine, startCol);
79
+ default:
80
+ if (this.isDigit(char)) {
81
+ return this.scanNumber(startLine, startCol);
82
+ }
83
+ if (this.isIdentStart(char)) {
84
+ return this.scanIdentifier(startLine, startCol);
85
+ }
86
+ throw new LexError(`Unexpected character '${char}'`, startLine, startCol);
87
+ }
88
+ }
89
+ scanString(startLine, startCol) {
90
+ let value = '';
91
+ while (!this.isAtEnd()) {
92
+ const char = this.advance();
93
+ if (char === '"') {
94
+ return this.makeToken(TokenType.STRING, value, startLine, startCol);
95
+ }
96
+ if (char === '\\') {
97
+ if (this.isAtEnd()) {
98
+ throw new LexError('Unterminated string escape', startLine, startCol);
99
+ }
100
+ const escaped = this.advance();
101
+ switch (escaped) {
102
+ case 'n':
103
+ value += '\n';
104
+ break;
105
+ case 't':
106
+ value += '\t';
107
+ break;
108
+ case 'r':
109
+ value += '\r';
110
+ break;
111
+ case '"':
112
+ value += '"';
113
+ break;
114
+ case '\\':
115
+ value += '\\';
116
+ break;
117
+ default:
118
+ value += escaped;
119
+ break;
120
+ }
121
+ continue;
122
+ }
123
+ if (char === '\n') {
124
+ throw new LexError('Unterminated string', startLine, startCol);
125
+ }
126
+ value += char;
127
+ }
128
+ throw new LexError('Unterminated string', startLine, startCol);
129
+ }
130
+ scanTripleString(startLine, startCol) {
131
+ let value = '';
132
+ while (!this.isAtEnd()) {
133
+ if (this.match('"') && this.match('"') && this.match('"')) {
134
+ return this.makeToken(TokenType.TRIPLE_STRING, value, startLine, startCol);
135
+ }
136
+ value += this.advance();
137
+ }
138
+ throw new LexError('Unterminated triple-quoted string', startLine, startCol);
139
+ }
140
+ scanNumber(startLine, startCol) {
141
+ let value = this.source[this.pos - 1];
142
+ while (!this.isAtEnd() && this.isDigit(this.peekChar())) {
143
+ value += this.advance();
144
+ }
145
+ if (this.peekChar() === '.' && this.isDigit(this.peekChar(1))) {
146
+ value += this.advance();
147
+ while (!this.isAtEnd() && this.isDigit(this.peekChar())) {
148
+ value += this.advance();
149
+ }
150
+ }
151
+ return this.makeToken(TokenType.NUMBER, value, startLine, startCol);
152
+ }
153
+ scanIdentifier(startLine, startCol) {
154
+ let value = this.source[this.pos - 1];
155
+ while (!this.isAtEnd() && this.isIdentPart(this.peekChar())) {
156
+ value += this.advance();
157
+ }
158
+ const type = keywordTokenType(value);
159
+ return this.makeToken(type, value, startLine, startCol);
160
+ }
161
+ skipWhitespaceAndComments() {
162
+ while (!this.isAtEnd()) {
163
+ const char = this.peekChar();
164
+ if (char === ' ' || char === '\t' || char === '\r') {
165
+ this.advance();
166
+ continue;
167
+ }
168
+ if (char === '\n') {
169
+ this.advance();
170
+ this.line += 1;
171
+ this.col = 1;
172
+ continue;
173
+ }
174
+ if (char === '/' && this.peekChar(1) === '/') {
175
+ while (!this.isAtEnd() && this.peekChar() !== '\n') {
176
+ this.advance();
177
+ }
178
+ continue;
179
+ }
180
+ break;
181
+ }
182
+ }
183
+ makeToken(type, value, line, col) {
184
+ return { type, value, line, col };
185
+ }
186
+ isAtEnd() {
187
+ return this.pos >= this.source.length;
188
+ }
189
+ peekChar(offset = 0) {
190
+ return this.source[this.pos + offset] ?? '\0';
191
+ }
192
+ advance() {
193
+ const char = this.source[this.pos];
194
+ this.pos += 1;
195
+ if (char !== '\n') {
196
+ this.col += 1;
197
+ }
198
+ return char;
199
+ }
200
+ match(expected) {
201
+ if (this.isAtEnd() || this.source[this.pos] !== expected) {
202
+ return false;
203
+ }
204
+ this.advance();
205
+ return true;
206
+ }
207
+ isDigit(char) {
208
+ return char >= '0' && char <= '9';
209
+ }
210
+ isIdentStart(char) {
211
+ return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char === '_';
212
+ }
213
+ isIdentPart(char) {
214
+ return this.isIdentStart(char) || this.isDigit(char) || char === '-';
215
+ }
216
+ }
@@ -0,0 +1,49 @@
1
+ import type { Attribute, Field, Model, Schema } from './ast.js';
2
+ import { Token } from './tokens.js';
3
+ export declare class ParseError extends Error {
4
+ readonly line: number;
5
+ readonly col: number;
6
+ readonly expected: string;
7
+ readonly found: Token;
8
+ constructor(expected: string, found: Token);
9
+ }
10
+ export declare class Parser {
11
+ private readonly tokens;
12
+ private index;
13
+ constructor(tokens: Token[]);
14
+ parseSchema(): Schema;
15
+ parseModel(): Model;
16
+ parseField(): Field;
17
+ parseAttribute(): Attribute;
18
+ private parseExtensionsSection;
19
+ private parseExtension;
20
+ private parseEnumsSection;
21
+ private parseEnum;
22
+ private parseModelsSection;
23
+ private parseModelBody;
24
+ private parseTypeExpr;
25
+ private parseFieldAttributes;
26
+ private parseAttributeInternal;
27
+ private parseDirective;
28
+ private parseAttributeArgs;
29
+ private parseValue;
30
+ private parseIdentOrCall;
31
+ private parseArrayLiteral;
32
+ private parseBlockLiteral;
33
+ private parseKeyValueList;
34
+ private parseValueList;
35
+ private parseIdentList;
36
+ private isKeyValueListStart;
37
+ private isListEnd;
38
+ private consumeTrailingComma;
39
+ private loc;
40
+ private current;
41
+ private previous;
42
+ private advance;
43
+ private match;
44
+ private check;
45
+ private peekType;
46
+ private expect;
47
+ private isAtEnd;
48
+ private eofToken;
49
+ }