@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,185 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import pythonDataclasses from '../python-dataclasses/index.ts'
3
+
4
+ const generate = pythonDataclasses.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: 'python-dataclasses',
75
+ ...overrides,
76
+ }
77
+ }
78
+
79
+ describe('python-dataclasses template', () => {
80
+ it('generates dataclass definitions', () => {
81
+ const result = generate(makeCtx())
82
+ expect(result.files).toHaveLength(1)
83
+ expect(result.files[0].path).toBe('models.py')
84
+
85
+ const content = result.files[0].content
86
+ expect(content).toContain('@dataclass')
87
+ expect(content).toContain('class Users:')
88
+ expect(content).toContain('class Posts:')
89
+ expect(content).toContain('class Comments:')
90
+ })
91
+
92
+ it('maps bigserial to int', () => {
93
+ const result = generate(makeCtx())
94
+ const content = result.files[0].content
95
+ expect(content).toContain('id: int')
96
+ })
97
+
98
+ it('maps nullable text to Optional[str]', () => {
99
+ const result = generate(makeCtx())
100
+ const content = result.files[0].content
101
+ expect(content).toContain('name: Optional[str]')
102
+ })
103
+
104
+ it('maps nullable text[] to Optional[list[str]]', () => {
105
+ const result = generate(makeCtx())
106
+ const content = result.files[0].content
107
+ expect(content).toContain('tags: Optional[list[str]]')
108
+ })
109
+
110
+ it('includes proper Python imports', () => {
111
+ const result = generate(makeCtx())
112
+ const content = result.files[0].content
113
+ expect(content).toContain('from dataclasses import dataclass')
114
+ expect(content).toContain('from typing import Optional')
115
+ expect(content).toContain('from datetime import datetime')
116
+ })
117
+
118
+ it('nullable fields have = None default', () => {
119
+ const result = generate(makeCtx())
120
+ const content = result.files[0].content
121
+ expect(content).toContain('name: Optional[str] = None')
122
+ })
123
+
124
+ it('respects @codegen.skip tag', () => {
125
+ const ctx = makeCtx({
126
+ allFileTags: [
127
+ {
128
+ sourceFile: 'test.sql',
129
+ objects: [
130
+ {
131
+ objectName: 'comments',
132
+ target: 'table',
133
+ tags: [{ namespace: 'codegen', tag: 'skip', args: [] }],
134
+ },
135
+ ],
136
+ },
137
+ ],
138
+ })
139
+ const result = generate(ctx)
140
+ const content = result.files[0].content
141
+ expect(content).not.toContain('class Comments:')
142
+ expect(content).toContain('class Users:')
143
+ })
144
+
145
+ it('respects @codegen.rename tag', () => {
146
+ const ctx = makeCtx({
147
+ allFileTags: [
148
+ {
149
+ sourceFile: 'test.sql',
150
+ objects: [
151
+ {
152
+ objectName: 'users',
153
+ target: 'table',
154
+ tags: [{ namespace: 'codegen', tag: 'rename', args: ['Account'] }],
155
+ },
156
+ ],
157
+ },
158
+ ],
159
+ })
160
+ const result = generate(ctx)
161
+ const content = result.files[0].content
162
+ expect(content).toContain('class Account:')
163
+ expect(content).not.toContain('class Users:')
164
+ })
165
+
166
+ it('respects @codegen.type override on column', () => {
167
+ const ctx = makeCtx({
168
+ allFileTags: [
169
+ {
170
+ sourceFile: 'test.sql',
171
+ objects: [
172
+ {
173
+ objectName: 'users.metadata',
174
+ target: 'column',
175
+ tags: [{ namespace: 'codegen', tag: 'type', args: ['dict[str, Any]'] }],
176
+ },
177
+ ],
178
+ },
179
+ ],
180
+ })
181
+ const result = generate(ctx)
182
+ const content = result.files[0].content
183
+ expect(content).toContain('metadata: dict[str, Any]')
184
+ })
185
+ })
@@ -0,0 +1,176 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import rustStructs from '../rust-structs/index'
3
+
4
+ const generate = rustStructs.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: '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: 'timestamptz', 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: { name: 'users_pkey', 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: 'timestamptz', 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: { name: 'posts_pkey', 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
+ },
49
+ ],
50
+ }
51
+
52
+ function makeCtx(overrides: Partial<TemplateContext> = {}): TemplateContext {
53
+ return {
54
+ realm: testRealm,
55
+ allFileTags: [],
56
+ docsMeta: [],
57
+ config: {},
58
+ output: './generated',
59
+ templateName: 'rust-structs',
60
+ ...overrides,
61
+ }
62
+ }
63
+
64
+ describe('rust-structs template', () => {
65
+ it('generates valid Rust structs', () => {
66
+ const result = generate(makeCtx())
67
+ expect(result.files).toHaveLength(1)
68
+ expect(result.files[0].path).toBe('models.rs')
69
+ })
70
+
71
+ it('contains pub struct Users', () => {
72
+ const result = generate(makeCtx())
73
+ expect(result.files[0].content).toContain('pub struct Users {')
74
+ })
75
+
76
+ it('maps bigserial to i64', () => {
77
+ const result = generate(makeCtx())
78
+ expect(result.files[0].content).toContain('pub id: i64')
79
+ })
80
+
81
+ it('maps nullable text to Option<String>', () => {
82
+ const result = generate(makeCtx())
83
+ expect(result.files[0].content).toContain('pub name: Option<String>')
84
+ })
85
+
86
+ it('maps nullable text[] to Option<Vec<String>>', () => {
87
+ const result = generate(makeCtx())
88
+ expect(result.files[0].content).toContain('pub tags: Option<Vec<String>>')
89
+ })
90
+
91
+ it('includes chrono use statement for timestamptz', () => {
92
+ const result = generate(makeCtx())
93
+ expect(result.files[0].content).toContain('use chrono::')
94
+ })
95
+
96
+ it('includes serde derives', () => {
97
+ const result = generate(makeCtx())
98
+ expect(result.files[0].content).toContain('#[derive(Debug, Clone, Serialize, Deserialize)]')
99
+ })
100
+
101
+ it('includes use serde statement', () => {
102
+ const result = generate(makeCtx())
103
+ expect(result.files[0].content).toContain('use serde::{Serialize, Deserialize};')
104
+ })
105
+
106
+ it('generates Posts struct with correct types', () => {
107
+ const result = generate(makeCtx())
108
+ const content = result.files[0].content
109
+ expect(content).toContain('pub struct Posts {')
110
+ expect(content).toContain('pub user_id: i64')
111
+ expect(content).toContain('pub rating: Option<f64>')
112
+ })
113
+
114
+ it('skips tables with @codegen.skip', () => {
115
+ const result = generate(
116
+ makeCtx({
117
+ allFileTags: [
118
+ {
119
+ sourceFile: 'test.sql',
120
+ objects: [
121
+ {
122
+ objectName: 'users',
123
+ target: 'table',
124
+ tags: [{ namespace: 'codegen', tag: 'skip', args: [] }],
125
+ },
126
+ ],
127
+ },
128
+ ],
129
+ }),
130
+ )
131
+ const content = result.files[0].content
132
+ expect(content).not.toContain('pub struct Users')
133
+ expect(content).toContain('pub struct Posts')
134
+ })
135
+
136
+ it('applies @codegen.rename to struct name', () => {
137
+ const result = generate(
138
+ makeCtx({
139
+ allFileTags: [
140
+ {
141
+ sourceFile: 'test.sql',
142
+ objects: [
143
+ {
144
+ objectName: 'users',
145
+ target: 'table',
146
+ tags: [{ namespace: 'codegen', tag: 'rename', args: ['Account'] }],
147
+ },
148
+ ],
149
+ },
150
+ ],
151
+ }),
152
+ )
153
+ expect(result.files[0].content).toContain('pub struct Account {')
154
+ expect(result.files[0].content).not.toContain('pub struct Users')
155
+ })
156
+
157
+ it('applies @codegen.type override on a column', () => {
158
+ const result = generate(
159
+ makeCtx({
160
+ allFileTags: [
161
+ {
162
+ sourceFile: 'test.sql',
163
+ objects: [
164
+ {
165
+ objectName: 'users.metadata',
166
+ target: 'column',
167
+ tags: [{ namespace: 'codegen', tag: 'type', args: ['MyCustomType'] }],
168
+ },
169
+ ],
170
+ },
171
+ ],
172
+ }),
173
+ )
174
+ expect(result.files[0].content).toContain('pub metadata: Option<MyCustomType>')
175
+ })
176
+ })
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { findRename, findTypeOverride, isSkipped } from '../helpers/tags'
3
+
4
+ describe('findRename', () => {
5
+ it('returns undefined when no rename tag exists', () => {
6
+ const tags = [{ namespace: 'codegen', tag: 'skip', args: [] }]
7
+ expect(findRename(tags, 'typescript')).toBeUndefined()
8
+ })
9
+
10
+ it('returns global rename when no template-specific exists', () => {
11
+ const tags = [{ namespace: 'codegen', tag: 'rename', args: ['UserAccount'] }]
12
+ expect(findRename(tags, 'typescript')).toBe('UserAccount')
13
+ })
14
+
15
+ it('returns template-specific rename over global', () => {
16
+ const tags = [
17
+ { namespace: 'codegen', tag: 'rename', args: ['UserAccount'] },
18
+ { namespace: 'codegen', tag: 'rename', args: ['UserModel', 'typescript'] },
19
+ ]
20
+ expect(findRename(tags, 'typescript')).toBe('UserModel')
21
+ })
22
+
23
+ it('returns global rename for different template', () => {
24
+ const tags = [
25
+ { namespace: 'codegen', tag: 'rename', args: ['UserAccount'] },
26
+ { namespace: 'codegen', tag: 'rename', args: ['UserModel', 'typescript'] },
27
+ ]
28
+ expect(findRename(tags, 'go-structs')).toBe('UserAccount')
29
+ })
30
+ })
31
+
32
+ describe('isSkipped', () => {
33
+ it('returns false when no skip tag exists', () => {
34
+ const tags = [{ namespace: 'codegen', tag: 'rename', args: ['Foo'] }]
35
+ expect(isSkipped(tags, 'typescript')).toBe(false)
36
+ })
37
+
38
+ it('returns true for global skip (no args)', () => {
39
+ const tags = [{ namespace: 'codegen', tag: 'skip', args: [] }]
40
+ expect(isSkipped(tags, 'typescript')).toBe(true)
41
+ })
42
+
43
+ it('returns true for template-specific skip', () => {
44
+ const tags = [{ namespace: 'codegen', tag: 'skip', args: ['typescript'] }]
45
+ expect(isSkipped(tags, 'typescript')).toBe(true)
46
+ })
47
+
48
+ it('returns false for skip targeting different template', () => {
49
+ const tags = [{ namespace: 'codegen', tag: 'skip', args: ['go-structs'] }]
50
+ expect(isSkipped(tags, 'typescript')).toBe(false)
51
+ })
52
+ })
53
+
54
+ describe('findTypeOverride', () => {
55
+ it('returns undefined when no type tag exists', () => {
56
+ const tags = [{ namespace: 'codegen', tag: 'rename', args: ['Foo'] }]
57
+ expect(findTypeOverride(tags, 'typescript')).toBeUndefined()
58
+ })
59
+
60
+ it('returns global type override', () => {
61
+ const tags = [{ namespace: 'codegen', tag: 'type', args: ['CustomType'] }]
62
+ expect(findTypeOverride(tags, 'typescript')).toBe('CustomType')
63
+ })
64
+
65
+ it('returns template-specific type over global', () => {
66
+ const tags = [
67
+ { namespace: 'codegen', tag: 'type', args: ['GlobalType'] },
68
+ { namespace: 'codegen', tag: 'type', args: ['TsType', 'typescript'] },
69
+ ]
70
+ expect(findTypeOverride(tags, 'typescript')).toBe('TsType')
71
+ })
72
+ })