@rip-lang/schema 0.2.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.
- package/README.md +1042 -0
- package/SCHEMA.md +1113 -0
- package/emit-sql.js +460 -0
- package/emit-types.js +366 -0
- package/generate.js +144 -0
- package/grammar.rip +504 -0
- package/index.js +39 -0
- package/lexer.js +438 -0
- package/orm.js +916 -0
- package/package.json +62 -0
- package/parser.js +246 -0
- package/runtime.js +494 -0
package/emit-types.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// ==============================================================================
|
|
2
|
+
// emit-types.js - Generate TypeScript declarations from Schema AST
|
|
3
|
+
//
|
|
4
|
+
// Walks the S-expression AST produced by the schema parser and emits clean
|
|
5
|
+
// TypeScript interfaces, enums, and type declarations.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// import { generateTypes } from '@rip-lang/schema'
|
|
9
|
+
// const ts = generateTypes(ast)
|
|
10
|
+
//
|
|
11
|
+
// Author: Steve Shreeve <steve.shreeve@gmail.com>
|
|
12
|
+
// Date: February 2026
|
|
13
|
+
// ==============================================================================
|
|
14
|
+
|
|
15
|
+
// Schema type → TypeScript type
|
|
16
|
+
const typeMap = {
|
|
17
|
+
string: 'string',
|
|
18
|
+
text: 'string',
|
|
19
|
+
integer: 'number',
|
|
20
|
+
number: 'number',
|
|
21
|
+
boolean: 'boolean',
|
|
22
|
+
date: 'Date',
|
|
23
|
+
datetime: 'Date',
|
|
24
|
+
email: 'string',
|
|
25
|
+
url: 'string',
|
|
26
|
+
uuid: 'string',
|
|
27
|
+
phone: 'string',
|
|
28
|
+
json: 'unknown',
|
|
29
|
+
any: 'any',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const numericTypes = new Set(['integer', 'number'])
|
|
33
|
+
|
|
34
|
+
// Convert an S-expression value back to a readable form for JSDoc
|
|
35
|
+
function formatDefault(val) {
|
|
36
|
+
if (Array.isArray(val)) {
|
|
37
|
+
if (val[0] === 'object') return '{}'
|
|
38
|
+
if (val[0] === 'array') return '[]'
|
|
39
|
+
}
|
|
40
|
+
return JSON.stringify(val)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Convert camelCase to snake_case for foreign key naming
|
|
44
|
+
function toForeignKey(name) {
|
|
45
|
+
return name[0].toLowerCase() + name.slice(1) + 'Id'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// JSDoc generation for constraints and metadata
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
function buildJSDoc(field) {
|
|
53
|
+
const tags = []
|
|
54
|
+
const [, , modifiers, type, constraints] = field
|
|
55
|
+
const baseType = Array.isArray(type) ? type[1] : type
|
|
56
|
+
const isNumeric = numericTypes.has(baseType)
|
|
57
|
+
|
|
58
|
+
if (modifiers?.includes('#')) {
|
|
59
|
+
tags.push('@unique')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (constraints && Array.isArray(constraints)) {
|
|
63
|
+
if (constraints.length === 1) {
|
|
64
|
+
// Single value = default
|
|
65
|
+
tags.push(`@default ${formatDefault(constraints[0])}`)
|
|
66
|
+
} else if (constraints.length >= 2) {
|
|
67
|
+
// Two+ values = min, max [, default]
|
|
68
|
+
if (isNumeric) {
|
|
69
|
+
tags.push(`@minimum ${constraints[0]}`)
|
|
70
|
+
tags.push(`@maximum ${constraints[1]}`)
|
|
71
|
+
} else {
|
|
72
|
+
tags.push(`@minLength ${constraints[0]}`)
|
|
73
|
+
tags.push(`@maxLength ${constraints[1]}`)
|
|
74
|
+
}
|
|
75
|
+
if (constraints.length >= 3) {
|
|
76
|
+
tags.push(`@default ${formatDefault(constraints[2])}`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (tags.length === 0) return null
|
|
82
|
+
return `/** ${tags.join(' ')} */`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// =============================================================================
|
|
86
|
+
// Field emission
|
|
87
|
+
// =============================================================================
|
|
88
|
+
|
|
89
|
+
function emitField(field, indent, enums) {
|
|
90
|
+
const [, name, modifiers, type, constraints] = field
|
|
91
|
+
const optional = modifiers?.includes('?')
|
|
92
|
+
const tsType = resolveType(type, enums)
|
|
93
|
+
const jsdoc = buildJSDoc(field)
|
|
94
|
+
const optMark = optional ? '?' : ''
|
|
95
|
+
const line = `${indent}${name}${optMark}: ${tsType};`
|
|
96
|
+
return jsdoc ? `${indent}${jsdoc}\n${line}` : line
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resolveType(type, enums) {
|
|
100
|
+
if (Array.isArray(type) && type[0] === 'array') {
|
|
101
|
+
return `${resolveType(type[1], enums)}[]`
|
|
102
|
+
}
|
|
103
|
+
if (typeMap[type]) return typeMap[type]
|
|
104
|
+
// References to enums and other types pass through as-is
|
|
105
|
+
return type
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// Enum emission
|
|
110
|
+
// =============================================================================
|
|
111
|
+
|
|
112
|
+
function emitEnum(def) {
|
|
113
|
+
const [, name, values] = def
|
|
114
|
+
const lines = [`export enum ${name} {`]
|
|
115
|
+
|
|
116
|
+
if (Array.isArray(values[0])) {
|
|
117
|
+
// Valued enum: [["pending", 0], ["active", 1]]
|
|
118
|
+
for (const [member, value] of values) {
|
|
119
|
+
lines.push(` ${member} = ${JSON.stringify(value)},`)
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Simple enum: ["admin", "user", "guest"]
|
|
123
|
+
for (const member of values) {
|
|
124
|
+
lines.push(` ${member} = ${JSON.stringify(member)},`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
lines.push('}')
|
|
129
|
+
return lines.join('\n')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// =============================================================================
|
|
133
|
+
// Interface emission (for @type and @model)
|
|
134
|
+
// =============================================================================
|
|
135
|
+
|
|
136
|
+
function emitInterface(def, enums, isModel) {
|
|
137
|
+
const [, name, parent, body] = def
|
|
138
|
+
const ext = parent ? ` extends ${parent}` : ''
|
|
139
|
+
const lines = [`export interface ${name}${ext} {`]
|
|
140
|
+
const indent = ' '
|
|
141
|
+
|
|
142
|
+
// Models get an auto-generated id field
|
|
143
|
+
if (isModel) {
|
|
144
|
+
lines.push(`${indent}id: string;`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (Array.isArray(body)) {
|
|
148
|
+
for (const member of body) {
|
|
149
|
+
if (!Array.isArray(member)) continue
|
|
150
|
+
const kind = member[0]
|
|
151
|
+
|
|
152
|
+
if (kind === 'field') {
|
|
153
|
+
lines.push(emitField(member, indent, enums))
|
|
154
|
+
|
|
155
|
+
} else if (kind === 'timestamps') {
|
|
156
|
+
lines.push(`${indent}createdAt: Date;`)
|
|
157
|
+
lines.push(`${indent}updatedAt: Date;`)
|
|
158
|
+
|
|
159
|
+
} else if (kind === 'softDelete') {
|
|
160
|
+
lines.push(`${indent}deletedAt?: Date;`)
|
|
161
|
+
|
|
162
|
+
} else if (kind === 'belongs_to') {
|
|
163
|
+
const [, target, opts] = member
|
|
164
|
+
const fk = toForeignKey(target)
|
|
165
|
+
const isOptional = isRelationOptional(opts)
|
|
166
|
+
lines.push(`${indent}${fk}${isOptional ? '?' : ''}: string;`)
|
|
167
|
+
lines.push(`${indent}${target.toLowerCase()}?: ${target};`)
|
|
168
|
+
|
|
169
|
+
} else if (kind === 'has_many') {
|
|
170
|
+
const [, target] = member
|
|
171
|
+
const plural = target.toLowerCase() + 's'
|
|
172
|
+
lines.push(`${indent}${plural}?: ${target}[];`)
|
|
173
|
+
|
|
174
|
+
} else if (kind === 'has_one') {
|
|
175
|
+
const [, target] = member
|
|
176
|
+
lines.push(`${indent}${target.toLowerCase()}?: ${target};`)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
lines.push('}')
|
|
182
|
+
return lines.join('\n')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// =============================================================================
|
|
186
|
+
// Derived type emission — Create and Update variants for @model
|
|
187
|
+
// =============================================================================
|
|
188
|
+
|
|
189
|
+
function hasDefault(field) {
|
|
190
|
+
const constraints = field[4]
|
|
191
|
+
if (!constraints || !Array.isArray(constraints)) return false
|
|
192
|
+
// Single value = default; three values = min, max, default
|
|
193
|
+
return constraints.length === 1 || constraints.length >= 3
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function emitCreateInterface(def, enums) {
|
|
197
|
+
const [, name, , body] = def
|
|
198
|
+
const lines = [`export interface ${name}Create {`]
|
|
199
|
+
const indent = ' '
|
|
200
|
+
|
|
201
|
+
if (!Array.isArray(body)) { lines.push('}'); return lines.join('\n') }
|
|
202
|
+
|
|
203
|
+
for (const member of body) {
|
|
204
|
+
if (!Array.isArray(member)) continue
|
|
205
|
+
const kind = member[0]
|
|
206
|
+
|
|
207
|
+
if (kind === 'field') {
|
|
208
|
+
const [, fieldName, modifiers, type, constraints] = member
|
|
209
|
+
const tsType = resolveType(type, enums)
|
|
210
|
+
const required = modifiers?.includes('!')
|
|
211
|
+
const optional = !required || hasDefault(member)
|
|
212
|
+
const jsdoc = buildJSDoc(member)
|
|
213
|
+
const optMark = optional ? '?' : ''
|
|
214
|
+
const line = `${indent}${fieldName}${optMark}: ${tsType};`
|
|
215
|
+
lines.push(jsdoc ? `${indent}${jsdoc}\n${line}` : line)
|
|
216
|
+
|
|
217
|
+
} else if (kind === 'belongs_to') {
|
|
218
|
+
const [, target, opts] = member
|
|
219
|
+
const fk = toForeignKey(target)
|
|
220
|
+
const isOptional = isRelationOptional(opts)
|
|
221
|
+
lines.push(`${indent}${fk}${isOptional ? '?' : ''}: string;`)
|
|
222
|
+
}
|
|
223
|
+
// Skip: id, timestamps, softDelete, has_many, has_one
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
lines.push('}')
|
|
227
|
+
return lines.join('\n')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function emitUpdateInterface(def, enums) {
|
|
231
|
+
const [, name, , body] = def
|
|
232
|
+
const lines = [`export interface ${name}Update {`]
|
|
233
|
+
const indent = ' '
|
|
234
|
+
|
|
235
|
+
if (!Array.isArray(body)) { lines.push('}'); return lines.join('\n') }
|
|
236
|
+
|
|
237
|
+
for (const member of body) {
|
|
238
|
+
if (!Array.isArray(member)) continue
|
|
239
|
+
const kind = member[0]
|
|
240
|
+
|
|
241
|
+
if (kind === 'field') {
|
|
242
|
+
const [, fieldName, , type] = member
|
|
243
|
+
const tsType = resolveType(type, enums)
|
|
244
|
+
lines.push(`${indent}${fieldName}?: ${tsType};`)
|
|
245
|
+
|
|
246
|
+
} else if (kind === 'belongs_to') {
|
|
247
|
+
const [, target] = member
|
|
248
|
+
const fk = toForeignKey(target)
|
|
249
|
+
lines.push(`${indent}${fk}?: string;`)
|
|
250
|
+
}
|
|
251
|
+
// Skip: id, timestamps, softDelete, has_many, has_one
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
lines.push('}')
|
|
255
|
+
return lines.join('\n')
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function isRelationOptional(opts) {
|
|
259
|
+
if (!Array.isArray(opts) || opts[0] !== 'object') return false
|
|
260
|
+
for (let i = 1; i < opts.length; i++) {
|
|
261
|
+
if (Array.isArray(opts[i]) && opts[i][0] === 'optional' && opts[i][1] === true) {
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// =============================================================================
|
|
269
|
+
// Main entry point
|
|
270
|
+
// =============================================================================
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Generate TypeScript declarations from a schema AST.
|
|
274
|
+
*
|
|
275
|
+
* @param {Array} ast - The S-expression AST from parse()
|
|
276
|
+
* @param {Object} [options] - Generation options
|
|
277
|
+
* @param {boolean} [options.models=true] - Emit interfaces for @model definitions
|
|
278
|
+
* @param {boolean} [options.types=true] - Emit interfaces for @type definitions
|
|
279
|
+
* @param {boolean} [options.enums=true] - Emit TypeScript enums
|
|
280
|
+
* @param {boolean} [options.derived=true] - Emit Create/Update variants for models
|
|
281
|
+
* @param {string} [options.header] - Custom header comment
|
|
282
|
+
* @returns {string} TypeScript declaration source
|
|
283
|
+
*/
|
|
284
|
+
export function generateTypes(ast, options = {}) {
|
|
285
|
+
if (!Array.isArray(ast) || ast[0] !== 'schema') {
|
|
286
|
+
throw new Error('Invalid schema AST: expected ["schema", ...]')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const {
|
|
290
|
+
models: emitModels = true,
|
|
291
|
+
types: emitTypeDefs = true,
|
|
292
|
+
enums: emitEnums = true,
|
|
293
|
+
derived: emitDerived = true,
|
|
294
|
+
header = '// Generated by @rip-lang/schema — do not edit\n',
|
|
295
|
+
} = options
|
|
296
|
+
|
|
297
|
+
// Collect enum names for type resolution
|
|
298
|
+
const enumNames = new Set()
|
|
299
|
+
for (let i = 1; i < ast.length; i++) {
|
|
300
|
+
if (Array.isArray(ast[i]) && ast[i][0] === 'enum') {
|
|
301
|
+
enumNames.add(ast[i][1])
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const blocks = []
|
|
306
|
+
if (header) blocks.push(header)
|
|
307
|
+
|
|
308
|
+
for (let i = 1; i < ast.length; i++) {
|
|
309
|
+
const def = ast[i]
|
|
310
|
+
if (!Array.isArray(def)) continue
|
|
311
|
+
|
|
312
|
+
switch (def[0]) {
|
|
313
|
+
case 'enum':
|
|
314
|
+
if (emitEnums) blocks.push(emitEnum(def))
|
|
315
|
+
break
|
|
316
|
+
case 'type':
|
|
317
|
+
if (emitTypeDefs) blocks.push(emitInterface(def, enumNames, false))
|
|
318
|
+
break
|
|
319
|
+
case 'model':
|
|
320
|
+
if (emitModels) blocks.push(emitInterface(def, enumNames, true))
|
|
321
|
+
if (emitModels && emitDerived) {
|
|
322
|
+
blocks.push(emitCreateInterface(def, enumNames))
|
|
323
|
+
blocks.push(emitUpdateInterface(def, enumNames))
|
|
324
|
+
}
|
|
325
|
+
break
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Auto-generate Link interface if any model uses @link
|
|
330
|
+
if (hasLinks(ast)) {
|
|
331
|
+
blocks.push(emitLinkInterface())
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return blocks.join('\n\n') + '\n'
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function hasLinks(ast) {
|
|
338
|
+
for (let i = 1; i < ast.length; i++) {
|
|
339
|
+
const def = ast[i]
|
|
340
|
+
if (!Array.isArray(def) || def[0] !== 'model') continue
|
|
341
|
+
const body = def[3]
|
|
342
|
+
if (!Array.isArray(body)) continue
|
|
343
|
+
for (const member of body) {
|
|
344
|
+
if (Array.isArray(member) && member[0] === 'link') return true
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return false
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function emitLinkInterface() {
|
|
351
|
+
return [
|
|
352
|
+
'export interface Link {',
|
|
353
|
+
' id: string;',
|
|
354
|
+
' sourceType: string;',
|
|
355
|
+
' sourceId: string;',
|
|
356
|
+
' targetType: string;',
|
|
357
|
+
' targetId: string;',
|
|
358
|
+
' role: string;',
|
|
359
|
+
' whenFrom?: Date;',
|
|
360
|
+
' whenTill?: Date;',
|
|
361
|
+
' createdAt: Date;',
|
|
362
|
+
'}',
|
|
363
|
+
].join('\n')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export default generateTypes
|
package/generate.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ==============================================================================
|
|
3
|
+
// generate.js - CLI for Rip Schema code generation
|
|
4
|
+
//
|
|
5
|
+
// Reads .schema files and generates TypeScript types, SQL DDL, and Zod schemas.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// bun packages/schema/generate.js app.schema
|
|
9
|
+
// bun packages/schema/generate.js app.schema --types
|
|
10
|
+
// bun packages/schema/generate.js app.schema --sql
|
|
11
|
+
// bun packages/schema/generate.js app.schema --zod
|
|
12
|
+
// bun packages/schema/generate.js app.schema --outdir ./generated
|
|
13
|
+
// bun packages/schema/generate.js app.schema --drop (prepend DROP TABLE)
|
|
14
|
+
// bun packages/schema/generate.js app.schema --stdout (print to stdout)
|
|
15
|
+
//
|
|
16
|
+
// Author: Steve Shreeve <steve.shreeve@gmail.com>
|
|
17
|
+
// Date: February 2026
|
|
18
|
+
// ==============================================================================
|
|
19
|
+
|
|
20
|
+
import * as fs from 'fs'
|
|
21
|
+
import * as path from 'path'
|
|
22
|
+
import { parse } from './index.js'
|
|
23
|
+
import { generateTypes } from './emit-types.js'
|
|
24
|
+
import { generateSQL } from './emit-sql.js'
|
|
25
|
+
import { generateZod } from './emit-zod.js'
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Parse CLI arguments
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
const rawArgs = process.argv.slice(2)
|
|
32
|
+
const flags = new Set()
|
|
33
|
+
const files = []
|
|
34
|
+
let outdir = null
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
37
|
+
const a = rawArgs[i]
|
|
38
|
+
if (a === '--outdir' && i + 1 < rawArgs.length) {
|
|
39
|
+
outdir = rawArgs[++i]
|
|
40
|
+
} else if (a.startsWith('--')) {
|
|
41
|
+
flags.add(a.slice(2))
|
|
42
|
+
} else {
|
|
43
|
+
files.push(a)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (files.length === 0 || flags.has('help')) {
|
|
48
|
+
console.log(`
|
|
49
|
+
Rip Schema Generator
|
|
50
|
+
|
|
51
|
+
Usage: rip-schema <schema-file> [options]
|
|
52
|
+
|
|
53
|
+
Options:
|
|
54
|
+
--types Generate TypeScript declarations only
|
|
55
|
+
--sql Generate SQL DDL only
|
|
56
|
+
--zod Generate Zod schemas only
|
|
57
|
+
--stdout Print to stdout instead of writing files
|
|
58
|
+
--outdir Output directory (default: same as input file)
|
|
59
|
+
--drop Prepend DROP TABLE IF EXISTS (SQL only)
|
|
60
|
+
--help Show this help message
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
rip-schema app.schema Generate .d.ts and .sql files
|
|
64
|
+
rip-schema app.schema --types Generate .d.ts only
|
|
65
|
+
rip-schema app.schema --sql --drop Generate .sql with DROP statements
|
|
66
|
+
rip-schema app.schema --zod Generate .zod.ts Zod schemas
|
|
67
|
+
rip-schema app.schema --stdout Print all output to stdout
|
|
68
|
+
`.trim())
|
|
69
|
+
process.exit(flags.has('help') ? 0 : 1)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// Determine what to generate
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
76
|
+
const explicit = flags.has('types') || flags.has('sql') || flags.has('zod')
|
|
77
|
+
const wantTypes = flags.has('types') || !explicit
|
|
78
|
+
const wantSQL = flags.has('sql') || !explicit
|
|
79
|
+
const wantZod = flags.has('zod')
|
|
80
|
+
const toStdout = flags.has('stdout')
|
|
81
|
+
const dropFirst = flags.has('drop')
|
|
82
|
+
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// Process each schema file
|
|
85
|
+
// =============================================================================
|
|
86
|
+
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
const schemaPath = path.resolve(file)
|
|
89
|
+
if (!fs.existsSync(schemaPath)) {
|
|
90
|
+
console.error(`Error: File not found: ${file}`)
|
|
91
|
+
process.exit(1)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const source = fs.readFileSync(schemaPath, 'utf-8')
|
|
95
|
+
const basename = path.basename(file)
|
|
96
|
+
const dir = outdir ? path.resolve(outdir) : path.dirname(schemaPath)
|
|
97
|
+
|
|
98
|
+
let ast
|
|
99
|
+
try {
|
|
100
|
+
ast = parse(source, file)
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(err.message)
|
|
103
|
+
process.exit(1)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Generate TypeScript
|
|
107
|
+
if (wantTypes) {
|
|
108
|
+
const ts = generateTypes(ast)
|
|
109
|
+
if (toStdout) {
|
|
110
|
+
console.log(ts)
|
|
111
|
+
} else {
|
|
112
|
+
const outPath = path.join(dir, basename.replace(/\.schema$/, '.d.ts'))
|
|
113
|
+
if (outdir) fs.mkdirSync(dir, { recursive: true })
|
|
114
|
+
fs.writeFileSync(outPath, ts)
|
|
115
|
+
console.log(` types → ${path.relative(process.cwd(), outPath)}`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Generate SQL
|
|
120
|
+
if (wantSQL) {
|
|
121
|
+
const sql = generateSQL(ast, { dropFirst })
|
|
122
|
+
if (toStdout) {
|
|
123
|
+
console.log(sql)
|
|
124
|
+
} else {
|
|
125
|
+
const outPath = path.join(dir, basename.replace(/\.schema$/, '.sql'))
|
|
126
|
+
if (outdir) fs.mkdirSync(dir, { recursive: true })
|
|
127
|
+
fs.writeFileSync(outPath, sql)
|
|
128
|
+
console.log(` sql → ${path.relative(process.cwd(), outPath)}`)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Generate Zod
|
|
133
|
+
if (wantZod) {
|
|
134
|
+
const zod = generateZod(ast)
|
|
135
|
+
if (toStdout) {
|
|
136
|
+
console.log(zod)
|
|
137
|
+
} else {
|
|
138
|
+
const outPath = path.join(dir, basename.replace(/\.schema$/, '.zod.ts'))
|
|
139
|
+
if (outdir) fs.mkdirSync(dir, { recursive: true })
|
|
140
|
+
fs.writeFileSync(outPath, zod)
|
|
141
|
+
console.log(` zod → ${path.relative(process.cwd(), outPath)}`)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|