@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
package/package.json ADDED
@@ -0,0 +1,161 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@sqldoc/templates",
4
+ "version": "0.0.1",
5
+ "description": "Code generation templates for sqldoc -- 18 templates across 6 languages plus tagged template syntax helpers",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./src/index.ts",
9
+ "import": "./src/index.ts",
10
+ "default": "./src/index.ts"
11
+ },
12
+ "./tags": {
13
+ "types": "./src/tags/index.ts",
14
+ "import": "./src/tags/index.ts",
15
+ "default": "./src/tags/index.ts"
16
+ },
17
+ "./typescript": {
18
+ "types": "./src/typescript/index.ts",
19
+ "import": "./src/typescript/index.ts",
20
+ "default": "./src/typescript/index.ts"
21
+ },
22
+ "./zod": {
23
+ "types": "./src/zod/index.ts",
24
+ "import": "./src/zod/index.ts",
25
+ "default": "./src/zod/index.ts"
26
+ },
27
+ "./drizzle": {
28
+ "types": "./src/drizzle/index.ts",
29
+ "import": "./src/drizzle/index.ts",
30
+ "default": "./src/drizzle/index.ts"
31
+ },
32
+ "./prisma": {
33
+ "types": "./src/prisma/index.ts",
34
+ "import": "./src/prisma/index.ts",
35
+ "default": "./src/prisma/index.ts"
36
+ },
37
+ "./kysely": {
38
+ "types": "./src/kysely/index.ts",
39
+ "import": "./src/kysely/index.ts",
40
+ "default": "./src/kysely/index.ts"
41
+ },
42
+ "./knex": {
43
+ "types": "./src/knex/index.ts",
44
+ "import": "./src/knex/index.ts",
45
+ "default": "./src/knex/index.ts"
46
+ },
47
+ "./go-structs": {
48
+ "types": "./src/go-structs/index.ts",
49
+ "import": "./src/go-structs/index.ts",
50
+ "default": "./src/go-structs/index.ts"
51
+ },
52
+ "./gorm": {
53
+ "types": "./src/gorm/index.ts",
54
+ "import": "./src/gorm/index.ts",
55
+ "default": "./src/gorm/index.ts"
56
+ },
57
+ "./sqlc": {
58
+ "types": "./src/sqlc/index.ts",
59
+ "import": "./src/sqlc/index.ts",
60
+ "default": "./src/sqlc/index.ts"
61
+ },
62
+ "./python-dataclasses": {
63
+ "types": "./src/python-dataclasses/index.ts",
64
+ "import": "./src/python-dataclasses/index.ts",
65
+ "default": "./src/python-dataclasses/index.ts"
66
+ },
67
+ "./sqlalchemy": {
68
+ "types": "./src/sqlalchemy/index.ts",
69
+ "import": "./src/sqlalchemy/index.ts",
70
+ "default": "./src/sqlalchemy/index.ts"
71
+ },
72
+ "./pydantic": {
73
+ "types": "./src/pydantic/index.ts",
74
+ "import": "./src/pydantic/index.ts",
75
+ "default": "./src/pydantic/index.ts"
76
+ },
77
+ "./java-records": {
78
+ "types": "./src/java-records/index.ts",
79
+ "import": "./src/java-records/index.ts",
80
+ "default": "./src/java-records/index.ts"
81
+ },
82
+ "./jpa": {
83
+ "types": "./src/jpa/index.ts",
84
+ "import": "./src/jpa/index.ts",
85
+ "default": "./src/jpa/index.ts"
86
+ },
87
+ "./kotlin-data": {
88
+ "types": "./src/kotlin-data/index.ts",
89
+ "import": "./src/kotlin-data/index.ts",
90
+ "default": "./src/kotlin-data/index.ts"
91
+ },
92
+ "./rust-structs": {
93
+ "types": "./src/rust-structs/index.ts",
94
+ "import": "./src/rust-structs/index.ts",
95
+ "default": "./src/rust-structs/index.ts"
96
+ },
97
+ "./diesel": {
98
+ "types": "./src/diesel/index.ts",
99
+ "import": "./src/diesel/index.ts",
100
+ "default": "./src/diesel/index.ts"
101
+ },
102
+ "./csharp-records": {
103
+ "types": "./src/csharp-records/index.ts",
104
+ "import": "./src/csharp-records/index.ts",
105
+ "default": "./src/csharp-records/index.ts"
106
+ },
107
+ "./efcore": {
108
+ "types": "./src/efcore/index.ts",
109
+ "import": "./src/efcore/index.ts",
110
+ "default": "./src/efcore/index.ts"
111
+ },
112
+ "./json-schema": {
113
+ "types": "./src/json-schema/index.ts",
114
+ "import": "./src/json-schema/index.ts",
115
+ "default": "./src/json-schema/index.ts"
116
+ },
117
+ "./xsd": {
118
+ "types": "./src/xsd/index.ts",
119
+ "import": "./src/xsd/index.ts",
120
+ "default": "./src/xsd/index.ts"
121
+ },
122
+ "./protobuf": {
123
+ "types": "./src/protobuf/index.ts",
124
+ "import": "./src/protobuf/index.ts",
125
+ "default": "./src/protobuf/index.ts"
126
+ },
127
+ "./cobol-copybook": {
128
+ "types": "./src/cobol-copybook/index.ts",
129
+ "import": "./src/cobol-copybook/index.ts",
130
+ "default": "./src/cobol-copybook/index.ts"
131
+ }
132
+ },
133
+ "main": "./src/index.ts",
134
+ "types": "./src/index.ts",
135
+ "files": [
136
+ "src",
137
+ "package.json"
138
+ ],
139
+ "peerDependencies": {
140
+ "@sqldoc/atlas": "0.0.1",
141
+ "@sqldoc/core": "0.0.1",
142
+ "@sqldoc/ns-codegen": "0.0.1"
143
+ },
144
+ "devDependencies": {
145
+ "@electric-sql/pglite": "^0.4.1",
146
+ "@types/pg": "^8.11.0",
147
+ "@electric-sql/pglite-socket": "^0.1.1",
148
+ "pg": "^8.13.0",
149
+ "tsx": "^4.21.0",
150
+ "typescript": "^5.9.3",
151
+ "vitest": "^4.1.0",
152
+ "@sqldoc/atlas": "0.0.1",
153
+ "@sqldoc/core": "0.0.1",
154
+ "@sqldoc/ns-codegen": "0.0.1",
155
+ "@sqldoc/ns-validate": "0.0.1"
156
+ },
157
+ "scripts": {
158
+ "test": "vitest run",
159
+ "test:docker": "bun test src/__tests__/docker-templates.test.ts"
160
+ }
161
+ }
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { dedent } from '../tags/dedent'
3
+
4
+ describe('dedent', () => {
5
+ it('strips common leading whitespace', () => {
6
+ const result = dedent`
7
+ hello
8
+ world
9
+ `
10
+ expect(result).toBe('hello\nworld')
11
+ })
12
+
13
+ it('preserves relative indentation', () => {
14
+ const result = dedent`
15
+ if (true) {
16
+ return 1
17
+ }
18
+ `
19
+ expect(result).toBe('if (true) {\n return 1\n}')
20
+ })
21
+
22
+ it('handles string interpolation', () => {
23
+ const name = 'World'
24
+ const result = dedent`
25
+ hello ${name}
26
+ goodbye
27
+ `
28
+ expect(result).toBe('hello World\ngoodbye')
29
+ })
30
+
31
+ it('handles zero-indent lines', () => {
32
+ const result = dedent`
33
+ hello
34
+ world
35
+ `
36
+ expect(result).toBe('hello\nworld')
37
+ })
38
+
39
+ it('removes empty first and last lines', () => {
40
+ const result = dedent`
41
+ content
42
+ `
43
+ expect(result).toBe('content')
44
+ })
45
+ })
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Docker-based template tests.
3
+ *
4
+ * Auto-discovers templates by finding src/<template>/test/Dockerfile.
5
+ * Starts a Postgres container on a shared Docker network, seeds it,
6
+ * runs codegen (output goes directly into each test dir), then for each template:
7
+ * 1. docker build (typecheck / compile)
8
+ * 2. docker run (integration test or no-op CMD)
9
+ *
10
+ * Run with: pnpm test:docker
11
+ */
12
+
13
+ import { execSync } from 'node:child_process'
14
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
15
+ import { dirname, join, resolve } from 'node:path'
16
+ import { fileURLToPath } from 'node:url'
17
+ import pg from 'pg'
18
+ import { afterAll, beforeAll, describe, test } from 'vitest'
19
+
20
+ const thisDir = dirname(fileURLToPath(import.meta.url))
21
+ const TEMPLATES_DIR = resolve(thisDir, '../..')
22
+ const SRC_DIR = join(TEMPLATES_DIR, 'src')
23
+ const TEST_DIR = join(TEMPLATES_DIR, 'test')
24
+ const REPO_ROOT = resolve(TEMPLATES_DIR, '../..')
25
+
26
+ const NETWORK = 'sqldoc-test-net'
27
+ const PG_CONTAINER = 'sqldoc-test-pg'
28
+ const PG_PORT = 54321
29
+ const DB_URL = `postgresql://postgres:postgres@${PG_CONTAINER}:5432/postgres`
30
+
31
+ function discoverTemplates(): string[] {
32
+ return readdirSync(SRC_DIR, { withFileTypes: true })
33
+ .filter((d) => d.isDirectory() && existsSync(join(SRC_DIR, d.name, 'test', 'Dockerfile')))
34
+ .map((d) => d.name)
35
+ .sort()
36
+ }
37
+
38
+ const templates = discoverTemplates()
39
+
40
+ function dockerExec(cmd: string, timeout = 30_000) {
41
+ return execSync(cmd, { stdio: 'pipe', timeout }).toString()
42
+ }
43
+
44
+ beforeAll(async () => {
45
+ // Clean up any leftovers from previous runs, then create fresh
46
+ try {
47
+ dockerExec(`docker rm -f ${PG_CONTAINER}`)
48
+ } catch {}
49
+ try {
50
+ dockerExec(`docker network rm ${NETWORK}`)
51
+ } catch {}
52
+ dockerExec(`docker network create ${NETWORK}`)
53
+ dockerExec(
54
+ `docker run -d --name ${PG_CONTAINER} --network ${NETWORK} -p ${PG_PORT}:5432 -e POSTGRES_PASSWORD=postgres postgres:17-alpine`,
55
+ )
56
+
57
+ // Wait for postgres to be ready
58
+ for (let i = 0; i < 30; i++) {
59
+ try {
60
+ dockerExec(`docker exec ${PG_CONTAINER} pg_isready -U postgres`)
61
+ break
62
+ } catch {
63
+ await new Promise((r) => setTimeout(r, 1000))
64
+ }
65
+ }
66
+
67
+ // Seed the database
68
+ const client = new pg.Client({
69
+ host: '127.0.0.1',
70
+ port: PG_PORT,
71
+ database: 'postgres',
72
+ user: 'postgres',
73
+ password: 'postgres',
74
+ connectionTimeoutMillis: 10_000,
75
+ })
76
+ await client.connect()
77
+ const schemaSql = readFileSync(join(TEST_DIR, 'fixture.sql'), 'utf-8')
78
+ .split('\n')
79
+ .filter((line) => !line.trim().startsWith('-- @import'))
80
+ .join('\n')
81
+ await client.query(schemaSql)
82
+ await client.query(readFileSync(join(TEST_DIR, 'fixture-seed.sql'), 'utf-8'))
83
+ await client.end()
84
+ console.log('Database seeded.')
85
+
86
+ // Run codegen — outputs directly into each src/<template>/test/ directory
87
+ console.log('Running codegen...')
88
+ execSync(
89
+ `node ${join(REPO_ROOT, 'packages/cli/src/index.ts')} codegen -c ${join(TEST_DIR, 'sqldoc.config.ts')} ${join(TEST_DIR, 'fixture.sql')}`,
90
+ {
91
+ cwd: TEMPLATES_DIR,
92
+ stdio: 'pipe',
93
+ timeout: 120_000,
94
+ env: { ...process.env, SQLDOC_RESOLVE_FROM_LOCAL_PACKAGE: 'true' },
95
+ },
96
+ )
97
+ console.log(`Codegen complete. Testing ${templates.length} templates.`)
98
+ }, 180_000)
99
+
100
+ afterAll(() => {
101
+ try {
102
+ dockerExec(`docker rm -f ${PG_CONTAINER}`)
103
+ } catch {}
104
+ try {
105
+ dockerExec(`docker network rm ${NETWORK}`)
106
+ } catch {}
107
+ })
108
+
109
+ describe('docker templates', () => {
110
+ for (const name of templates) {
111
+ test(name, () => {
112
+ const testDir = join(SRC_DIR, name, 'test')
113
+ const tag = `sqldoc-test-${name}`
114
+ try {
115
+ execSync(`docker build -t ${tag} ${testDir}`, {
116
+ stdio: 'pipe',
117
+ timeout: 120_000,
118
+ })
119
+ execSync(`docker run --rm --network ${NETWORK} -e DATABASE_URL="${DB_URL}" ${tag}`, {
120
+ stdio: 'pipe',
121
+ timeout: 30_000,
122
+ })
123
+ } catch (err: any) {
124
+ const stderr = err.stderr?.toString() ?? ''
125
+ const stdout = err.stdout?.toString() ?? ''
126
+ throw new Error(`Docker test failed for ${name}:\n${stderr}\n${stdout}`)
127
+ } finally {
128
+ try {
129
+ execSync(`docker rmi ${tag}`, { stdio: 'ignore' })
130
+ } catch {}
131
+ }
132
+ }, 180_000)
133
+ }
134
+ })
@@ -0,0 +1,184 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import goStructs from '../go-structs/index.ts'
3
+
4
+ const generate = goStructs.generate
5
+
6
+ import type { AtlasRealm } from '@sqldoc/atlas'
7
+ import type { TemplateContext } from '@sqldoc/ns-codegen'
8
+
9
+ const testRealm: AtlasRealm = {
10
+ schemas: [
11
+ {
12
+ name: 'public',
13
+ tables: [
14
+ {
15
+ name: 'users',
16
+ columns: [
17
+ { name: 'id', type: { T: 'bigserial', null: false, category: 'integer' } },
18
+ { name: 'email', type: { T: 'character varying', raw: 'varchar(255)', null: false, category: 'string' } },
19
+ { name: 'name', type: { T: 'text', null: true, category: 'string' } },
20
+ { name: 'age', type: { T: 'integer', null: true, category: 'integer' } },
21
+ { name: 'is_active', type: { T: 'boolean', null: false, category: 'boolean' } },
22
+ { name: 'metadata', type: { T: 'jsonb', null: true, category: 'json' } },
23
+ { name: 'created_at', type: { T: 'timestamp with time zone', null: false, category: 'time' } },
24
+ { name: 'tags', type: { T: 'text[]', null: true, category: 'array' } },
25
+ { name: 'avatar', type: { T: 'bytea', null: true, category: 'binary' } },
26
+ { name: 'balance', type: { T: 'numeric(10,2)', null: true, category: 'decimal' } },
27
+ { name: 'external_id', type: { T: 'uuid', null: true, category: 'uuid' } },
28
+ ],
29
+ primary_key: { parts: [{ column: 'id' }] },
30
+ },
31
+ {
32
+ name: 'posts',
33
+ columns: [
34
+ { name: 'id', type: { T: 'bigserial', null: false, category: 'integer' } },
35
+ { name: 'user_id', type: { T: 'bigint', null: false, category: 'integer' } },
36
+ { name: 'title', type: { T: 'text', null: false, category: 'string' } },
37
+ { name: 'body', type: { T: 'text', null: false, category: 'string' } },
38
+ { name: 'published_at', type: { T: 'timestamp with time zone', null: true, category: 'time' } },
39
+ { name: 'view_count', type: { T: 'integer', null: false, category: 'integer' } },
40
+ { name: 'rating', type: { T: 'double precision', null: true, category: 'float' } },
41
+ ],
42
+ primary_key: { parts: [{ column: 'id' }] },
43
+ foreign_keys: [
44
+ { symbol: 'posts_user_id_fkey', columns: ['user_id'], ref_table: 'users', ref_columns: ['id'] },
45
+ ],
46
+ },
47
+ {
48
+ name: 'comments',
49
+ columns: [
50
+ { name: 'id', type: { T: 'bigserial', null: false, category: 'integer' } },
51
+ { name: 'post_id', type: { T: 'bigint', null: false, category: 'integer' } },
52
+ { name: 'user_id', type: { T: 'bigint', null: false, category: 'integer' } },
53
+ { name: 'content', type: { T: 'text', null: false, category: 'string' } },
54
+ { name: 'created_at', type: { T: 'timestamp with time zone', null: false, category: 'time' } },
55
+ ],
56
+ primary_key: { parts: [{ column: 'id' }] },
57
+ foreign_keys: [
58
+ { symbol: 'comments_post_id_fkey', columns: ['post_id'], ref_table: 'posts', ref_columns: ['id'] },
59
+ { symbol: 'comments_user_id_fkey', columns: ['user_id'], ref_table: 'users', ref_columns: ['id'] },
60
+ ],
61
+ },
62
+ ],
63
+ },
64
+ ],
65
+ }
66
+
67
+ function makeCtx(overrides?: Partial<TemplateContext>): TemplateContext {
68
+ return {
69
+ realm: testRealm,
70
+ allFileTags: [],
71
+ docsMeta: [],
72
+ config: {},
73
+ output: './out',
74
+ templateName: 'go-structs',
75
+ ...overrides,
76
+ }
77
+ }
78
+
79
+ describe('go-structs template', () => {
80
+ it('generates Go structs with json tags', () => {
81
+ const result = generate(makeCtx())
82
+ expect(result.files).toHaveLength(1)
83
+ expect(result.files[0].path).toBe('models.go')
84
+
85
+ const content = result.files[0].content
86
+ expect(content).toContain('type Users struct {')
87
+ expect(content).toContain('type Posts struct {')
88
+ expect(content).toContain('type Comments struct {')
89
+ })
90
+
91
+ it('maps bigserial to int64', () => {
92
+ const result = generate(makeCtx())
93
+ const content = result.files[0].content
94
+ expect(content).toContain('Id int64')
95
+ })
96
+
97
+ it('maps nullable text to *string', () => {
98
+ const result = generate(makeCtx())
99
+ const content = result.files[0].content
100
+ expect(content).toContain('Name *string')
101
+ })
102
+
103
+ it('maps text[] to []string', () => {
104
+ const result = generate(makeCtx())
105
+ const content = result.files[0].content
106
+ expect(content).toContain('Tags []string')
107
+ })
108
+
109
+ it('includes time import for timestamptz', () => {
110
+ const result = generate(makeCtx())
111
+ const content = result.files[0].content
112
+ expect(content).toContain('import (')
113
+ expect(content).toContain('"time"')
114
+ })
115
+
116
+ it('uses omitempty for nullable json tags', () => {
117
+ const result = generate(makeCtx())
118
+ const content = result.files[0].content
119
+ expect(content).toContain('`json:"name,omitempty"`')
120
+ expect(content).toContain('`json:"email"`')
121
+ })
122
+
123
+ it('respects @codegen.skip tag', () => {
124
+ const ctx = makeCtx({
125
+ allFileTags: [
126
+ {
127
+ sourceFile: 'test.sql',
128
+ objects: [
129
+ {
130
+ objectName: 'comments',
131
+ target: 'table',
132
+ tags: [{ namespace: 'codegen', tag: 'skip', args: [] }],
133
+ },
134
+ ],
135
+ },
136
+ ],
137
+ })
138
+ const result = generate(ctx)
139
+ const content = result.files[0].content
140
+ expect(content).not.toContain('type Comments struct')
141
+ expect(content).toContain('type Users struct')
142
+ })
143
+
144
+ it('respects @codegen.rename tag', () => {
145
+ const ctx = makeCtx({
146
+ allFileTags: [
147
+ {
148
+ sourceFile: 'test.sql',
149
+ objects: [
150
+ {
151
+ objectName: 'users',
152
+ target: 'table',
153
+ tags: [{ namespace: 'codegen', tag: 'rename', args: ['Account'] }],
154
+ },
155
+ ],
156
+ },
157
+ ],
158
+ })
159
+ const result = generate(ctx)
160
+ const content = result.files[0].content
161
+ expect(content).toContain('type Account struct')
162
+ expect(content).not.toContain('type Users struct')
163
+ })
164
+
165
+ it('respects @codegen.type override on column', () => {
166
+ const ctx = makeCtx({
167
+ allFileTags: [
168
+ {
169
+ sourceFile: 'test.sql',
170
+ objects: [
171
+ {
172
+ objectName: 'users.metadata',
173
+ target: 'column',
174
+ tags: [{ namespace: 'codegen', tag: 'type', args: ['map[string]interface{}'] }],
175
+ },
176
+ ],
177
+ },
178
+ ],
179
+ })
180
+ const result = generate(ctx)
181
+ const content = result.files[0].content
182
+ expect(content).toContain('Metadata map[string]interface{}')
183
+ })
184
+ })
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { toCamelCase, toPascalCase, toScreamingSnake } from '../helpers/naming'
3
+
4
+ describe('toPascalCase', () => {
5
+ it('converts snake_case to PascalCase', () => {
6
+ expect(toPascalCase('user_accounts')).toBe('UserAccounts')
7
+ })
8
+
9
+ it('handles single word', () => {
10
+ expect(toPascalCase('id')).toBe('Id')
11
+ })
12
+
13
+ it('handles leading underscore', () => {
14
+ expect(toPascalCase('_private_field')).toBe('PrivateField')
15
+ })
16
+
17
+ it('handles multiple underscores', () => {
18
+ expect(toPascalCase('user__name')).toBe('UserName')
19
+ })
20
+
21
+ it('handles already capitalized segments', () => {
22
+ expect(toPascalCase('USER_STATUS')).toBe('UserStatus')
23
+ })
24
+ })
25
+
26
+ describe('toCamelCase', () => {
27
+ it('converts snake_case to camelCase', () => {
28
+ expect(toCamelCase('user_accounts')).toBe('userAccounts')
29
+ })
30
+
31
+ it('handles single word', () => {
32
+ expect(toCamelCase('name')).toBe('name')
33
+ })
34
+
35
+ it('produces lowercase first char', () => {
36
+ expect(toCamelCase('ID')).toBe('id')
37
+ })
38
+ })
39
+
40
+ describe('toScreamingSnake', () => {
41
+ it('converts to SCREAMING_SNAKE', () => {
42
+ expect(toScreamingSnake('user_status')).toBe('USER_STATUS')
43
+ })
44
+
45
+ it('handles already lowercase', () => {
46
+ expect(toScreamingSnake('created_at')).toBe('CREATED_AT')
47
+ })
48
+ })