@sqldoc/templates 0.0.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 (108) hide show
  1. package/package.json +161 -0
  2. package/src/__tests__/dedent.test.ts +45 -0
  3. package/src/__tests__/docker-templates.test.ts +134 -0
  4. package/src/__tests__/go-structs.test.ts +184 -0
  5. package/src/__tests__/naming.test.ts +48 -0
  6. package/src/__tests__/python-dataclasses.test.ts +185 -0
  7. package/src/__tests__/rust-structs.test.ts +176 -0
  8. package/src/__tests__/tags-helpers.test.ts +72 -0
  9. package/src/__tests__/type-mapping.test.ts +332 -0
  10. package/src/__tests__/typescript.test.ts +202 -0
  11. package/src/cobol-copybook/index.ts +220 -0
  12. package/src/cobol-copybook/test/.gitignore +6 -0
  13. package/src/cobol-copybook/test/Dockerfile +7 -0
  14. package/src/csharp-records/index.ts +131 -0
  15. package/src/csharp-records/test/.gitignore +6 -0
  16. package/src/csharp-records/test/Dockerfile +6 -0
  17. package/src/diesel/index.ts +247 -0
  18. package/src/diesel/test/.gitignore +6 -0
  19. package/src/diesel/test/Dockerfile +16 -0
  20. package/src/drizzle/index.ts +255 -0
  21. package/src/drizzle/test/.gitignore +6 -0
  22. package/src/drizzle/test/Dockerfile +8 -0
  23. package/src/drizzle/test/test.ts +71 -0
  24. package/src/efcore/index.ts +190 -0
  25. package/src/efcore/test/.gitignore +6 -0
  26. package/src/efcore/test/Dockerfile +7 -0
  27. package/src/go-structs/index.ts +119 -0
  28. package/src/go-structs/test/.gitignore +6 -0
  29. package/src/go-structs/test/Dockerfile +13 -0
  30. package/src/go-structs/test/test.go +71 -0
  31. package/src/gorm/index.ts +134 -0
  32. package/src/gorm/test/.gitignore +6 -0
  33. package/src/gorm/test/Dockerfile +13 -0
  34. package/src/gorm/test/test.go +65 -0
  35. package/src/helpers/atlas.ts +43 -0
  36. package/src/helpers/enrich.ts +396 -0
  37. package/src/helpers/naming.ts +19 -0
  38. package/src/helpers/tags.ts +63 -0
  39. package/src/index.ts +24 -0
  40. package/src/java-records/index.ts +179 -0
  41. package/src/java-records/test/.gitignore +6 -0
  42. package/src/java-records/test/Dockerfile +11 -0
  43. package/src/java-records/test/Test.java +93 -0
  44. package/src/jpa/index.ts +279 -0
  45. package/src/jpa/test/.gitignore +6 -0
  46. package/src/jpa/test/Dockerfile +14 -0
  47. package/src/jpa/test/Test.java +111 -0
  48. package/src/json-schema/index.ts +351 -0
  49. package/src/json-schema/test/.gitignore +6 -0
  50. package/src/json-schema/test/Dockerfile +18 -0
  51. package/src/knex/index.ts +168 -0
  52. package/src/knex/test/.gitignore +6 -0
  53. package/src/knex/test/Dockerfile +7 -0
  54. package/src/knex/test/test.ts +75 -0
  55. package/src/kotlin-data/index.ts +147 -0
  56. package/src/kotlin-data/test/.gitignore +6 -0
  57. package/src/kotlin-data/test/Dockerfile +14 -0
  58. package/src/kotlin-data/test/Test.kt +82 -0
  59. package/src/kysely/index.ts +165 -0
  60. package/src/kysely/test/.gitignore +6 -0
  61. package/src/kysely/test/Dockerfile +8 -0
  62. package/src/kysely/test/test.ts +82 -0
  63. package/src/prisma/index.ts +387 -0
  64. package/src/prisma/test/.gitignore +6 -0
  65. package/src/prisma/test/Dockerfile +7 -0
  66. package/src/protobuf/index.ts +219 -0
  67. package/src/protobuf/test/.gitignore +6 -0
  68. package/src/protobuf/test/Dockerfile +6 -0
  69. package/src/pydantic/index.ts +272 -0
  70. package/src/pydantic/test/.gitignore +6 -0
  71. package/src/pydantic/test/Dockerfile +8 -0
  72. package/src/pydantic/test/test.py +63 -0
  73. package/src/python-dataclasses/index.ts +217 -0
  74. package/src/python-dataclasses/test/.gitignore +6 -0
  75. package/src/python-dataclasses/test/Dockerfile +8 -0
  76. package/src/python-dataclasses/test/test.py +63 -0
  77. package/src/rust-structs/index.ts +152 -0
  78. package/src/rust-structs/test/.gitignore +6 -0
  79. package/src/rust-structs/test/Dockerfile +22 -0
  80. package/src/rust-structs/test/test.rs +82 -0
  81. package/src/sqlalchemy/index.ts +258 -0
  82. package/src/sqlalchemy/test/.gitignore +6 -0
  83. package/src/sqlalchemy/test/Dockerfile +8 -0
  84. package/src/sqlalchemy/test/test.py +61 -0
  85. package/src/sqlc/index.ts +148 -0
  86. package/src/sqlc/test/.gitignore +6 -0
  87. package/src/sqlc/test/Dockerfile +13 -0
  88. package/src/sqlc/test/test.go +91 -0
  89. package/src/tags/dedent.ts +28 -0
  90. package/src/tags/index.ts +14 -0
  91. package/src/types/index.ts +8 -0
  92. package/src/types/pg-to-csharp.ts +136 -0
  93. package/src/types/pg-to-go.ts +120 -0
  94. package/src/types/pg-to-java.ts +141 -0
  95. package/src/types/pg-to-kotlin.ts +119 -0
  96. package/src/types/pg-to-python.ts +120 -0
  97. package/src/types/pg-to-rust.ts +121 -0
  98. package/src/types/pg-to-ts.ts +173 -0
  99. package/src/typescript/index.ts +168 -0
  100. package/src/typescript/test/.gitignore +6 -0
  101. package/src/typescript/test/Dockerfile +8 -0
  102. package/src/typescript/test/test.ts +89 -0
  103. package/src/xsd/index.ts +191 -0
  104. package/src/xsd/test/.gitignore +6 -0
  105. package/src/xsd/test/Dockerfile +6 -0
  106. package/src/zod/index.ts +289 -0
  107. package/src/zod/test/.gitignore +6 -0
  108. package/src/zod/test/Dockerfile +6 -0
@@ -0,0 +1,289 @@
1
+ import { defineTemplate } from '@sqldoc/ns-codegen'
2
+ import { activeTables, enrichRealm, type TagEntry } from '../helpers/enrich.ts'
3
+ import { toCamelCase, toPascalCase } from '../helpers/naming.ts'
4
+
5
+ export const configSchema = {
6
+ dateType: {
7
+ type: 'enum',
8
+ values: ['Date', 'dayjs', 'luxon', 'string'],
9
+ description: 'How to represent date/time types',
10
+ },
11
+ nullableStyle: {
12
+ type: 'enum',
13
+ values: ['optional', 'null-union'],
14
+ description: 'Use .optional() or .nullable() for nullable columns',
15
+ },
16
+ bigintType: {
17
+ type: 'enum',
18
+ values: ['number', 'bigint', 'string'],
19
+ description: 'How to represent bigint/bigserial columns',
20
+ },
21
+ strict: {
22
+ type: 'boolean',
23
+ description: 'Enable strict mode for Zod schemas',
24
+ },
25
+ } as const
26
+
27
+ /** Map a PostgreSQL type to a Zod schema call */
28
+ function pgToZod(pgType: string): string {
29
+ const normalized = pgType.toLowerCase().trim()
30
+
31
+ // Handle arrays
32
+ if (normalized.endsWith('[]')) {
33
+ return `z.array(${pgToZod(normalized.slice(0, -2))})`
34
+ }
35
+ if (normalized.startsWith('_')) {
36
+ return `z.array(${pgToZod(normalized.slice(1))})`
37
+ }
38
+
39
+ // Strip length specifiers
40
+ const baseType = normalized.replace(/\(\d+(?:,\s*\d+)?\)/, '').trim()
41
+
42
+ const map: Record<string, string> = {
43
+ // Numeric
44
+ smallint: 'z.number()',
45
+ int2: 'z.number()',
46
+ integer: 'z.number()',
47
+ int: 'z.number()',
48
+ int4: 'z.number()',
49
+ bigint: 'z.number()',
50
+ int8: 'z.number()',
51
+ serial: 'z.number()',
52
+ serial4: 'z.number()',
53
+ bigserial: 'z.number()',
54
+ serial8: 'z.number()',
55
+ smallserial: 'z.number()',
56
+ serial2: 'z.number()',
57
+ real: 'z.number()',
58
+ float4: 'z.number()',
59
+ 'double precision': 'z.number()',
60
+ float8: 'z.number()',
61
+ numeric: 'z.string()',
62
+ decimal: 'z.string()',
63
+ money: 'z.string()',
64
+
65
+ // String
66
+ text: 'z.string()',
67
+ varchar: 'z.string()',
68
+ 'character varying': 'z.string()',
69
+ char: 'z.string()',
70
+ character: 'z.string()',
71
+ name: 'z.string()',
72
+ citext: 'z.string()',
73
+
74
+ // Boolean
75
+ boolean: 'z.boolean()',
76
+ bool: 'z.boolean()',
77
+
78
+ // Date/Time
79
+ timestamp: 'z.date()',
80
+ 'timestamp without time zone': 'z.date()',
81
+ timestamptz: 'z.date()',
82
+ 'timestamp with time zone': 'z.date()',
83
+ date: 'z.string()',
84
+ time: 'z.string()',
85
+ 'time without time zone': 'z.string()',
86
+ timetz: 'z.string()',
87
+ 'time with time zone': 'z.string()',
88
+ interval: 'z.string()',
89
+
90
+ // Binary
91
+ bytea: 'z.instanceof(Buffer)',
92
+
93
+ // JSON
94
+ json: 'z.unknown()',
95
+ jsonb: 'z.unknown()',
96
+
97
+ // UUID
98
+ uuid: 'z.string().uuid()',
99
+
100
+ // Network
101
+ inet: 'z.string()',
102
+ cidr: 'z.string()',
103
+ macaddr: 'z.string()',
104
+ macaddr8: 'z.string()',
105
+
106
+ // Other
107
+ xml: 'z.string()',
108
+ tsvector: 'z.string()',
109
+ tsquery: 'z.string()',
110
+ oid: 'z.number()',
111
+ point: 'z.string()',
112
+ line: 'z.string()',
113
+ box: 'z.string()',
114
+ circle: 'z.string()',
115
+ polygon: 'z.string()',
116
+ path: 'z.string()',
117
+ }
118
+
119
+ return map[baseType] ?? 'z.unknown()'
120
+ }
121
+
122
+ /** Extract @validate modifiers and append Zod chain calls */
123
+ function applyValidation(baseSchema: string, tags: TagEntry[]): string {
124
+ let schema = baseSchema
125
+
126
+ for (const t of tags) {
127
+ if (t.namespace !== 'validate') continue
128
+
129
+ if (t.tag === 'notEmpty') {
130
+ schema += '.min(1)'
131
+ } else if (t.tag === 'length') {
132
+ const args = t.args as Record<string, unknown>
133
+ if (args.min !== undefined) schema += `.min(${args.min})`
134
+ if (args.max !== undefined) schema += `.max(${args.max})`
135
+ } else if (t.tag === 'range') {
136
+ const args = t.args as Record<string, unknown>
137
+ if (args.min !== undefined) schema += `.min(${args.min})`
138
+ if (args.max !== undefined) schema += `.max(${args.max})`
139
+ } else if (t.tag === 'pattern') {
140
+ const pattern = Array.isArray(t.args) ? t.args[0] : undefined
141
+ if (pattern) schema += `.regex(/${pattern}/)`
142
+ }
143
+ }
144
+
145
+ return schema
146
+ }
147
+
148
+ export default defineTemplate({
149
+ name: 'Zod Schemas',
150
+ description: 'Generate Zod validation schemas from SQL schema',
151
+ language: 'typescript',
152
+ configSchema,
153
+
154
+ generate(ctx) {
155
+ const config = ctx.config ?? {}
156
+ const schema = enrichRealm(ctx)
157
+
158
+ const lines: string[] = ['// Generated by @sqldoc/templates/zod -- DO NOT EDIT', '', "import { z } from 'zod'", '']
159
+
160
+ // Enums
161
+ for (const e of schema.enums) {
162
+ const values = e.values.map((v) => `'${v}'`).join(', ')
163
+ lines.push(`export const ${toCamelCase(e.name)}Schema = z.enum([${values}])`)
164
+ lines.push(`export type ${e.pascalName} = z.infer<typeof ${toCamelCase(e.name)}Schema>`)
165
+ lines.push('')
166
+ }
167
+
168
+ // Composite types (collected from columns)
169
+ const composites = new Map<string, Array<{ name: string; type: string }>>()
170
+ for (const table of schema.tables) {
171
+ for (const col of table.columns) {
172
+ if (col.category === 'composite' && col.compositeFields?.length && !composites.has(col.pgType)) {
173
+ composites.set(col.pgType, col.compositeFields)
174
+ }
175
+ }
176
+ }
177
+ for (const [name, fields] of composites) {
178
+ const camelName = toCamelCase(name)
179
+ const typeName = toPascalCase(name)
180
+ lines.push(`export const ${camelName}Schema = z.object({`)
181
+ for (const f of fields) {
182
+ lines.push(` ${toCamelCase(f.name)}: ${pgToZod(f.type)},`)
183
+ }
184
+ lines.push('})')
185
+ lines.push(`export type ${typeName} = z.infer<typeof ${camelName}Schema>`)
186
+ lines.push('')
187
+ }
188
+
189
+ for (const table of activeTables(schema)) {
190
+ const camelName = toCamelCase(table.pascalName)
191
+
192
+ lines.push(`export const ${camelName}Schema = z.object({`)
193
+
194
+ for (const col of table.columns) {
195
+ const propName = toCamelCase(col.name)
196
+ const zodSchema = resolveZodType(col, config)
197
+ lines.push(` ${propName}: ${zodSchema},`)
198
+ }
199
+
200
+ lines.push('})')
201
+ lines.push('')
202
+ lines.push(`export type ${table.pascalName} = z.infer<typeof ${camelName}Schema>`)
203
+ lines.push('')
204
+ }
205
+
206
+ // Views (read-only)
207
+ for (const view of schema.views.filter((v) => !v.skipped)) {
208
+ const camelName = toCamelCase(view.pascalName)
209
+
210
+ lines.push(`/** Read-only schema (from view) */`)
211
+ lines.push(`export const ${camelName}Schema = z.object({`)
212
+
213
+ for (const col of view.columns) {
214
+ const propName = toCamelCase(col.name)
215
+ const zodSchema = resolveZodType(col, config)
216
+ lines.push(` ${propName}: ${zodSchema},`)
217
+ }
218
+
219
+ lines.push('})')
220
+ lines.push('')
221
+ lines.push(`export type ${view.pascalName} = z.infer<typeof ${camelName}Schema>`)
222
+ lines.push('')
223
+ }
224
+
225
+ // Functions (skip trigger functions)
226
+ for (const fn of schema.functions) {
227
+ const retRaw = fn.returnType?.type?.toLowerCase() ?? ''
228
+ if (retRaw === 'trigger') continue
229
+
230
+ const camelName = toCamelCase(fn.pascalName)
231
+
232
+ // Build input schema
233
+ const inputArgs = fn.args.filter((a) => !a.name?.startsWith('_') && (a as any).mode !== 'OUT')
234
+ lines.push(`export const ${camelName}InputSchema = z.object({`)
235
+ for (const a of inputArgs) {
236
+ lines.push(` ${toCamelCase(a.name || 'arg')}: ${pgToZod(a.type)},`)
237
+ }
238
+ lines.push('})')
239
+ lines.push('')
240
+
241
+ // Build output schema
242
+ let outputSchema: string
243
+ if (retRaw.startsWith('setof ')) {
244
+ const tableName = retRaw.replace('setof ', '')
245
+ const table = schema.tables.find((t) => t.name === tableName)
246
+ if (table) {
247
+ outputSchema = `z.array(${toCamelCase(table.pascalName)}Schema)`
248
+ } else {
249
+ outputSchema = `z.array(${pgToZod(tableName)})`
250
+ }
251
+ } else if (fn.returnType) {
252
+ outputSchema = pgToZod(fn.returnType.type)
253
+ } else {
254
+ outputSchema = 'z.void()'
255
+ }
256
+ lines.push(`export const ${camelName}OutputSchema = ${outputSchema}`)
257
+ lines.push('')
258
+ }
259
+
260
+ return {
261
+ files: [
262
+ {
263
+ path: 'schemas.ts',
264
+ content: lines.join('\n'),
265
+ },
266
+ ],
267
+ }
268
+
269
+ function resolveZodType(col: any, config: any): string {
270
+ let zodSchema: string
271
+ if (col.typeOverride) {
272
+ zodSchema = col.typeOverride
273
+ } else if (col.category === 'enum' && col.enumValues?.length) {
274
+ zodSchema = `${toCamelCase(col.pgType)}Schema`
275
+ } else if (col.category === 'composite' && col.compositeFields?.length) {
276
+ zodSchema = `${toCamelCase(col.pgType)}Schema`
277
+ } else {
278
+ zodSchema = pgToZod(col.pgType)
279
+ zodSchema = applyValidation(zodSchema, col.tags)
280
+ }
281
+
282
+ if (col.nullable) {
283
+ zodSchema += config.nullableStyle === 'null-union' ? '.nullable()' : '.optional()'
284
+ }
285
+
286
+ return zodSchema
287
+ }
288
+ },
289
+ })
@@ -0,0 +1,6 @@
1
+ # Generated by codegen — only Dockerfile and test scripts are tracked
2
+ *
3
+ !.gitignore
4
+ !Dockerfile
5
+ !test.*
6
+ !Test.*
@@ -0,0 +1,6 @@
1
+ FROM node:23-slim
2
+ WORKDIR /app
3
+ COPY . .
4
+ RUN npm init -y && npm install typescript@5 zod@3 @types/node --save-dev
5
+ RUN npx tsc --noEmit --strict --esModuleInterop --module nodenext --moduleResolution nodenext *.ts
6
+ CMD ["echo", "ok"]