@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.
- package/package.json +161 -0
- package/src/__tests__/dedent.test.ts +45 -0
- package/src/__tests__/docker-templates.test.ts +134 -0
- package/src/__tests__/go-structs.test.ts +184 -0
- package/src/__tests__/naming.test.ts +48 -0
- package/src/__tests__/python-dataclasses.test.ts +185 -0
- package/src/__tests__/rust-structs.test.ts +176 -0
- package/src/__tests__/tags-helpers.test.ts +72 -0
- package/src/__tests__/type-mapping.test.ts +332 -0
- package/src/__tests__/typescript.test.ts +202 -0
- package/src/cobol-copybook/index.ts +220 -0
- package/src/cobol-copybook/test/.gitignore +6 -0
- package/src/cobol-copybook/test/Dockerfile +7 -0
- package/src/csharp-records/index.ts +131 -0
- package/src/csharp-records/test/.gitignore +6 -0
- package/src/csharp-records/test/Dockerfile +6 -0
- package/src/diesel/index.ts +247 -0
- package/src/diesel/test/.gitignore +6 -0
- package/src/diesel/test/Dockerfile +16 -0
- package/src/drizzle/index.ts +255 -0
- package/src/drizzle/test/.gitignore +6 -0
- package/src/drizzle/test/Dockerfile +8 -0
- package/src/drizzle/test/test.ts +71 -0
- package/src/efcore/index.ts +190 -0
- package/src/efcore/test/.gitignore +6 -0
- package/src/efcore/test/Dockerfile +7 -0
- package/src/go-structs/index.ts +119 -0
- package/src/go-structs/test/.gitignore +6 -0
- package/src/go-structs/test/Dockerfile +13 -0
- package/src/go-structs/test/test.go +71 -0
- package/src/gorm/index.ts +134 -0
- package/src/gorm/test/.gitignore +6 -0
- package/src/gorm/test/Dockerfile +13 -0
- package/src/gorm/test/test.go +65 -0
- package/src/helpers/atlas.ts +43 -0
- package/src/helpers/enrich.ts +396 -0
- package/src/helpers/naming.ts +19 -0
- package/src/helpers/tags.ts +63 -0
- package/src/index.ts +24 -0
- package/src/java-records/index.ts +179 -0
- package/src/java-records/test/.gitignore +6 -0
- package/src/java-records/test/Dockerfile +11 -0
- package/src/java-records/test/Test.java +93 -0
- package/src/jpa/index.ts +279 -0
- package/src/jpa/test/.gitignore +6 -0
- package/src/jpa/test/Dockerfile +14 -0
- package/src/jpa/test/Test.java +111 -0
- package/src/json-schema/index.ts +351 -0
- package/src/json-schema/test/.gitignore +6 -0
- package/src/json-schema/test/Dockerfile +18 -0
- package/src/knex/index.ts +168 -0
- package/src/knex/test/.gitignore +6 -0
- package/src/knex/test/Dockerfile +7 -0
- package/src/knex/test/test.ts +75 -0
- package/src/kotlin-data/index.ts +147 -0
- package/src/kotlin-data/test/.gitignore +6 -0
- package/src/kotlin-data/test/Dockerfile +14 -0
- package/src/kotlin-data/test/Test.kt +82 -0
- package/src/kysely/index.ts +165 -0
- package/src/kysely/test/.gitignore +6 -0
- package/src/kysely/test/Dockerfile +8 -0
- package/src/kysely/test/test.ts +82 -0
- package/src/prisma/index.ts +387 -0
- package/src/prisma/test/.gitignore +6 -0
- package/src/prisma/test/Dockerfile +7 -0
- package/src/protobuf/index.ts +219 -0
- package/src/protobuf/test/.gitignore +6 -0
- package/src/protobuf/test/Dockerfile +6 -0
- package/src/pydantic/index.ts +272 -0
- package/src/pydantic/test/.gitignore +6 -0
- package/src/pydantic/test/Dockerfile +8 -0
- package/src/pydantic/test/test.py +63 -0
- package/src/python-dataclasses/index.ts +217 -0
- package/src/python-dataclasses/test/.gitignore +6 -0
- package/src/python-dataclasses/test/Dockerfile +8 -0
- package/src/python-dataclasses/test/test.py +63 -0
- package/src/rust-structs/index.ts +152 -0
- package/src/rust-structs/test/.gitignore +6 -0
- package/src/rust-structs/test/Dockerfile +22 -0
- package/src/rust-structs/test/test.rs +82 -0
- package/src/sqlalchemy/index.ts +258 -0
- package/src/sqlalchemy/test/.gitignore +6 -0
- package/src/sqlalchemy/test/Dockerfile +8 -0
- package/src/sqlalchemy/test/test.py +61 -0
- package/src/sqlc/index.ts +148 -0
- package/src/sqlc/test/.gitignore +6 -0
- package/src/sqlc/test/Dockerfile +13 -0
- package/src/sqlc/test/test.go +91 -0
- package/src/tags/dedent.ts +28 -0
- package/src/tags/index.ts +14 -0
- package/src/types/index.ts +8 -0
- package/src/types/pg-to-csharp.ts +136 -0
- package/src/types/pg-to-go.ts +120 -0
- package/src/types/pg-to-java.ts +141 -0
- package/src/types/pg-to-kotlin.ts +119 -0
- package/src/types/pg-to-python.ts +120 -0
- package/src/types/pg-to-rust.ts +121 -0
- package/src/types/pg-to-ts.ts +173 -0
- package/src/typescript/index.ts +168 -0
- package/src/typescript/test/.gitignore +6 -0
- package/src/typescript/test/Dockerfile +8 -0
- package/src/typescript/test/test.ts +89 -0
- package/src/xsd/index.ts +191 -0
- package/src/xsd/test/.gitignore +6 -0
- package/src/xsd/test/Dockerfile +6 -0
- package/src/zod/index.ts +289 -0
- package/src/zod/test/.gitignore +6 -0
- package/src/zod/test/Dockerfile +6 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/** Category-based mapping — used when Atlas provides a type category */
|
|
2
|
+
const CATEGORY_TO_JAVA: Record<string, [string, string | null]> = {
|
|
3
|
+
string: ['String', null],
|
|
4
|
+
integer: ['long', null],
|
|
5
|
+
float: ['double', null],
|
|
6
|
+
decimal: ['BigDecimal', 'java.math.BigDecimal'],
|
|
7
|
+
boolean: ['boolean', null],
|
|
8
|
+
time: ['LocalDateTime', 'java.time.LocalDateTime'],
|
|
9
|
+
binary: ['byte[]', null],
|
|
10
|
+
json: ['String', null],
|
|
11
|
+
uuid: ['UUID', 'java.util.UUID'],
|
|
12
|
+
spatial: ['String', null],
|
|
13
|
+
enum: ['String', null], // overridden per-column with actual enum type
|
|
14
|
+
unknown: ['Object', null],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Java uses wrapper types for nullable primitives
|
|
18
|
+
const PG_TO_JAVA: Record<string, [string, string | null]> = {
|
|
19
|
+
// [javaType, importPath] -- primitive types have wrapper equivalents for nullable
|
|
20
|
+
// Numeric
|
|
21
|
+
smallint: ['short', null],
|
|
22
|
+
int2: ['short', null],
|
|
23
|
+
integer: ['int', null],
|
|
24
|
+
int: ['int', null],
|
|
25
|
+
int4: ['int', null],
|
|
26
|
+
bigint: ['long', null],
|
|
27
|
+
int8: ['long', null],
|
|
28
|
+
serial: ['int', null],
|
|
29
|
+
serial4: ['int', null],
|
|
30
|
+
bigserial: ['long', null],
|
|
31
|
+
serial8: ['long', null],
|
|
32
|
+
smallserial: ['short', null],
|
|
33
|
+
serial2: ['short', null],
|
|
34
|
+
real: ['float', null],
|
|
35
|
+
float4: ['float', null],
|
|
36
|
+
'double precision': ['double', null],
|
|
37
|
+
float8: ['double', null],
|
|
38
|
+
numeric: ['BigDecimal', 'java.math.BigDecimal'],
|
|
39
|
+
decimal: ['BigDecimal', 'java.math.BigDecimal'],
|
|
40
|
+
money: ['BigDecimal', 'java.math.BigDecimal'],
|
|
41
|
+
|
|
42
|
+
// String
|
|
43
|
+
text: ['String', null],
|
|
44
|
+
varchar: ['String', null],
|
|
45
|
+
'character varying': ['String', null],
|
|
46
|
+
char: ['String', null],
|
|
47
|
+
character: ['String', null],
|
|
48
|
+
name: ['String', null],
|
|
49
|
+
citext: ['String', null],
|
|
50
|
+
|
|
51
|
+
// Boolean
|
|
52
|
+
boolean: ['boolean', null],
|
|
53
|
+
bool: ['boolean', null],
|
|
54
|
+
|
|
55
|
+
// Date/Time
|
|
56
|
+
timestamp: ['LocalDateTime', 'java.time.LocalDateTime'],
|
|
57
|
+
'timestamp without time zone': ['LocalDateTime', 'java.time.LocalDateTime'],
|
|
58
|
+
timestamptz: ['OffsetDateTime', 'java.time.OffsetDateTime'],
|
|
59
|
+
'timestamp with time zone': ['OffsetDateTime', 'java.time.OffsetDateTime'],
|
|
60
|
+
date: ['LocalDate', 'java.time.LocalDate'],
|
|
61
|
+
time: ['LocalTime', 'java.time.LocalTime'],
|
|
62
|
+
'time without time zone': ['LocalTime', 'java.time.LocalTime'],
|
|
63
|
+
timetz: ['LocalTime', 'java.time.LocalTime'],
|
|
64
|
+
'time with time zone': ['LocalTime', 'java.time.LocalTime'],
|
|
65
|
+
interval: ['Duration', 'java.time.Duration'],
|
|
66
|
+
|
|
67
|
+
// Binary
|
|
68
|
+
bytea: ['byte[]', null],
|
|
69
|
+
|
|
70
|
+
// JSON
|
|
71
|
+
json: ['String', null],
|
|
72
|
+
jsonb: ['String', null],
|
|
73
|
+
|
|
74
|
+
// UUID
|
|
75
|
+
uuid: ['UUID', 'java.util.UUID'],
|
|
76
|
+
|
|
77
|
+
// Network
|
|
78
|
+
inet: ['String', null],
|
|
79
|
+
cidr: ['String', null],
|
|
80
|
+
macaddr: ['String', null],
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mapping from primitive types to their wrapper types for nullable
|
|
84
|
+
const PRIMITIVE_TO_WRAPPER: Record<string, string> = {
|
|
85
|
+
short: 'Short',
|
|
86
|
+
int: 'Integer',
|
|
87
|
+
long: 'Long',
|
|
88
|
+
float: 'Float',
|
|
89
|
+
double: 'Double',
|
|
90
|
+
boolean: 'Boolean',
|
|
91
|
+
'byte[]': 'byte[]',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Map a PostgreSQL type to a Java type and required imports.
|
|
96
|
+
* Nullable primitive types use their wrapper equivalents (int -> Integer, long -> Long).
|
|
97
|
+
*/
|
|
98
|
+
export function pgToJava(pgType: string, nullable: boolean, category?: string): { type: string; imports: string[] } {
|
|
99
|
+
const normalized = pgType.toLowerCase().trim()
|
|
100
|
+
|
|
101
|
+
// Handle arrays
|
|
102
|
+
if (normalized.endsWith('[]')) {
|
|
103
|
+
const base = pgToJava(normalized.slice(0, -2), false)
|
|
104
|
+
const wrappedType = PRIMITIVE_TO_WRAPPER[base.type] ?? base.type
|
|
105
|
+
const imports = [...base.imports]
|
|
106
|
+
if (!imports.includes('java.util.List')) imports.push('java.util.List')
|
|
107
|
+
return { type: `List<${wrappedType}>`, imports }
|
|
108
|
+
}
|
|
109
|
+
if (normalized.startsWith('_')) {
|
|
110
|
+
const base = pgToJava(normalized.slice(1), false)
|
|
111
|
+
const wrappedType = PRIMITIVE_TO_WRAPPER[base.type] ?? base.type
|
|
112
|
+
const imports = [...base.imports]
|
|
113
|
+
if (!imports.includes('java.util.List')) imports.push('java.util.List')
|
|
114
|
+
return { type: `List<${wrappedType}>`, imports }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Strip length specifiers
|
|
118
|
+
const baseType = normalized.replace(/\(\d+(?:,\s*\d+)?\)/, '').trim()
|
|
119
|
+
|
|
120
|
+
// Try raw type lookup first for precise mapping
|
|
121
|
+
let mapping = PG_TO_JAVA[baseType]
|
|
122
|
+
|
|
123
|
+
// Fall back to category-based mapping for unknown raw types
|
|
124
|
+
if (!mapping && category) {
|
|
125
|
+
mapping = CATEGORY_TO_JAVA[category]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!mapping) return { type: 'Object', imports: [] }
|
|
129
|
+
|
|
130
|
+
const [javaType, importPath] = mapping
|
|
131
|
+
const imports = importPath ? [importPath] : []
|
|
132
|
+
|
|
133
|
+
if (nullable) {
|
|
134
|
+
const wrapper = PRIMITIVE_TO_WRAPPER[javaType]
|
|
135
|
+
if (wrapper) {
|
|
136
|
+
return { type: wrapper, imports }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { type: javaType, imports }
|
|
141
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** Category-based mapping — used when Atlas provides a type category */
|
|
2
|
+
const CATEGORY_TO_KOTLIN: Record<string, string> = {
|
|
3
|
+
string: 'String',
|
|
4
|
+
integer: 'Long',
|
|
5
|
+
float: 'Double',
|
|
6
|
+
decimal: 'BigDecimal',
|
|
7
|
+
boolean: 'Boolean',
|
|
8
|
+
time: 'LocalDateTime',
|
|
9
|
+
binary: 'ByteArray',
|
|
10
|
+
json: 'String',
|
|
11
|
+
uuid: 'UUID',
|
|
12
|
+
spatial: 'String',
|
|
13
|
+
enum: 'String', // overridden per-column with actual enum type
|
|
14
|
+
unknown: 'String',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PG_TO_KOTLIN: Record<string, string> = {
|
|
18
|
+
// Numeric
|
|
19
|
+
smallint: 'Short',
|
|
20
|
+
int2: 'Short',
|
|
21
|
+
integer: 'Int',
|
|
22
|
+
int: 'Int',
|
|
23
|
+
int4: 'Int',
|
|
24
|
+
bigint: 'Long',
|
|
25
|
+
int8: 'Long',
|
|
26
|
+
serial: 'Int',
|
|
27
|
+
serial4: 'Int',
|
|
28
|
+
bigserial: 'Long',
|
|
29
|
+
serial8: 'Long',
|
|
30
|
+
smallserial: 'Short',
|
|
31
|
+
serial2: 'Short',
|
|
32
|
+
real: 'Float',
|
|
33
|
+
float4: 'Float',
|
|
34
|
+
'double precision': 'Double',
|
|
35
|
+
float8: 'Double',
|
|
36
|
+
numeric: 'BigDecimal',
|
|
37
|
+
decimal: 'BigDecimal',
|
|
38
|
+
money: 'BigDecimal',
|
|
39
|
+
|
|
40
|
+
// String
|
|
41
|
+
text: 'String',
|
|
42
|
+
varchar: 'String',
|
|
43
|
+
'character varying': 'String',
|
|
44
|
+
char: 'String',
|
|
45
|
+
character: 'String',
|
|
46
|
+
name: 'String',
|
|
47
|
+
citext: 'String',
|
|
48
|
+
|
|
49
|
+
// Boolean
|
|
50
|
+
boolean: 'Boolean',
|
|
51
|
+
bool: 'Boolean',
|
|
52
|
+
|
|
53
|
+
// Date/Time
|
|
54
|
+
timestamp: 'LocalDateTime',
|
|
55
|
+
'timestamp without time zone': 'LocalDateTime',
|
|
56
|
+
timestamptz: 'OffsetDateTime',
|
|
57
|
+
'timestamp with time zone': 'OffsetDateTime',
|
|
58
|
+
date: 'LocalDate',
|
|
59
|
+
time: 'LocalTime',
|
|
60
|
+
'time without time zone': 'LocalTime',
|
|
61
|
+
timetz: 'LocalTime',
|
|
62
|
+
'time with time zone': 'LocalTime',
|
|
63
|
+
interval: 'Duration',
|
|
64
|
+
|
|
65
|
+
// Binary
|
|
66
|
+
bytea: 'ByteArray',
|
|
67
|
+
|
|
68
|
+
// JSON
|
|
69
|
+
json: 'String',
|
|
70
|
+
jsonb: 'String',
|
|
71
|
+
|
|
72
|
+
// UUID
|
|
73
|
+
uuid: 'UUID',
|
|
74
|
+
|
|
75
|
+
// Network
|
|
76
|
+
inet: 'String',
|
|
77
|
+
cidr: 'String',
|
|
78
|
+
macaddr: 'String',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Map a PostgreSQL type to a Kotlin type string.
|
|
83
|
+
* Nullable types use Kotlin's ? suffix: String -> String?
|
|
84
|
+
*/
|
|
85
|
+
export function pgToKotlin(pgType: string, nullable: boolean, category?: string): string {
|
|
86
|
+
const normalized = pgType.toLowerCase().trim()
|
|
87
|
+
|
|
88
|
+
// Handle arrays
|
|
89
|
+
if (normalized.endsWith('[]')) {
|
|
90
|
+
const base = pgToKotlin(normalized.slice(0, -2), false)
|
|
91
|
+
return wrapNullable(`List<${base}>`, nullable)
|
|
92
|
+
}
|
|
93
|
+
if (normalized.startsWith('_')) {
|
|
94
|
+
const base = pgToKotlin(normalized.slice(1), false)
|
|
95
|
+
return wrapNullable(`List<${base}>`, nullable)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Strip length specifiers
|
|
99
|
+
const baseType = normalized.replace(/\(\d+(?:,\s*\d+)?\)/, '').trim()
|
|
100
|
+
|
|
101
|
+
// Try raw type lookup first for precise mapping
|
|
102
|
+
let ktType = PG_TO_KOTLIN[baseType]
|
|
103
|
+
|
|
104
|
+
// Fall back to category-based mapping for unknown raw types
|
|
105
|
+
if (ktType === undefined && category) {
|
|
106
|
+
ktType = CATEGORY_TO_KOTLIN[category]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (ktType === undefined) {
|
|
110
|
+
ktType = 'String'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return wrapNullable(ktType, nullable)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function wrapNullable(type: string, nullable: boolean): string {
|
|
117
|
+
if (!nullable) return type
|
|
118
|
+
return `${type}?`
|
|
119
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/** Category-based mapping — used when Atlas provides a type category */
|
|
2
|
+
const CATEGORY_TO_PYTHON: Record<string, string> = {
|
|
3
|
+
string: 'str',
|
|
4
|
+
integer: 'int',
|
|
5
|
+
float: 'float',
|
|
6
|
+
decimal: 'Decimal',
|
|
7
|
+
boolean: 'bool',
|
|
8
|
+
time: 'datetime',
|
|
9
|
+
binary: 'bytes',
|
|
10
|
+
json: 'dict',
|
|
11
|
+
uuid: 'UUID',
|
|
12
|
+
spatial: 'str',
|
|
13
|
+
enum: 'str', // overridden per-column with actual enum type
|
|
14
|
+
unknown: 'Any',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PG_TO_PYTHON: Record<string, string> = {
|
|
18
|
+
// Numeric
|
|
19
|
+
smallint: 'int',
|
|
20
|
+
int2: 'int',
|
|
21
|
+
integer: 'int',
|
|
22
|
+
int: 'int',
|
|
23
|
+
int4: 'int',
|
|
24
|
+
bigint: 'int',
|
|
25
|
+
int8: 'int',
|
|
26
|
+
serial: 'int',
|
|
27
|
+
serial4: 'int',
|
|
28
|
+
bigserial: 'int',
|
|
29
|
+
serial8: 'int',
|
|
30
|
+
smallserial: 'int',
|
|
31
|
+
serial2: 'int',
|
|
32
|
+
real: 'float',
|
|
33
|
+
float4: 'float',
|
|
34
|
+
'double precision': 'float',
|
|
35
|
+
float8: 'float',
|
|
36
|
+
numeric: 'Decimal',
|
|
37
|
+
decimal: 'Decimal',
|
|
38
|
+
money: 'Decimal',
|
|
39
|
+
|
|
40
|
+
// String
|
|
41
|
+
text: 'str',
|
|
42
|
+
varchar: 'str',
|
|
43
|
+
'character varying': 'str',
|
|
44
|
+
char: 'str',
|
|
45
|
+
character: 'str',
|
|
46
|
+
name: 'str',
|
|
47
|
+
citext: 'str',
|
|
48
|
+
|
|
49
|
+
// Boolean
|
|
50
|
+
boolean: 'bool',
|
|
51
|
+
bool: 'bool',
|
|
52
|
+
|
|
53
|
+
// Date/Time
|
|
54
|
+
timestamp: 'datetime',
|
|
55
|
+
'timestamp without time zone': 'datetime',
|
|
56
|
+
timestamptz: 'datetime',
|
|
57
|
+
'timestamp with time zone': 'datetime',
|
|
58
|
+
date: 'date',
|
|
59
|
+
time: 'time',
|
|
60
|
+
'time without time zone': 'time',
|
|
61
|
+
timetz: 'time',
|
|
62
|
+
'time with time zone': 'time',
|
|
63
|
+
interval: 'timedelta',
|
|
64
|
+
|
|
65
|
+
// Binary
|
|
66
|
+
bytea: 'bytes',
|
|
67
|
+
|
|
68
|
+
// JSON
|
|
69
|
+
json: 'dict',
|
|
70
|
+
jsonb: 'dict',
|
|
71
|
+
|
|
72
|
+
// UUID
|
|
73
|
+
uuid: 'UUID',
|
|
74
|
+
|
|
75
|
+
// Network
|
|
76
|
+
inet: 'str',
|
|
77
|
+
cidr: 'str',
|
|
78
|
+
macaddr: 'str',
|
|
79
|
+
macaddr8: 'str',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Map a PostgreSQL type to a Python type annotation.
|
|
84
|
+
* Nullable types use Optional[T].
|
|
85
|
+
*/
|
|
86
|
+
export function pgToPython(pgType: string, nullable: boolean, category?: string): string {
|
|
87
|
+
const normalized = pgType.toLowerCase().trim()
|
|
88
|
+
|
|
89
|
+
// Handle arrays
|
|
90
|
+
if (normalized.endsWith('[]')) {
|
|
91
|
+
const base = pgToPython(normalized.slice(0, -2), false)
|
|
92
|
+
return wrapNullable(`list[${base}]`, nullable)
|
|
93
|
+
}
|
|
94
|
+
if (normalized.startsWith('_')) {
|
|
95
|
+
const base = pgToPython(normalized.slice(1), false)
|
|
96
|
+
return wrapNullable(`list[${base}]`, nullable)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Strip length specifiers
|
|
100
|
+
const baseType = normalized.replace(/\(\d+(?:,\s*\d+)?\)/, '').trim()
|
|
101
|
+
|
|
102
|
+
// Try raw type lookup first for precise mapping
|
|
103
|
+
let pyType = PG_TO_PYTHON[baseType]
|
|
104
|
+
|
|
105
|
+
// Fall back to category-based mapping for unknown raw types
|
|
106
|
+
if (pyType === undefined && category) {
|
|
107
|
+
pyType = CATEGORY_TO_PYTHON[category]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (pyType === undefined) {
|
|
111
|
+
pyType = 'Any'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return wrapNullable(pyType, nullable)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function wrapNullable(type: string, nullable: boolean): string {
|
|
118
|
+
if (!nullable) return type
|
|
119
|
+
return `Optional[${type}]`
|
|
120
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** Category-based mapping — used when Atlas provides a type category */
|
|
2
|
+
const CATEGORY_TO_RUST: Record<string, [string, string | null]> = {
|
|
3
|
+
string: ['String', null],
|
|
4
|
+
integer: ['i64', null],
|
|
5
|
+
float: ['f64', null],
|
|
6
|
+
decimal: ['bigdecimal::BigDecimal', 'bigdecimal::BigDecimal'],
|
|
7
|
+
boolean: ['bool', null],
|
|
8
|
+
time: ['NaiveDateTime', 'chrono::NaiveDateTime'],
|
|
9
|
+
binary: ['Vec<u8>', null],
|
|
10
|
+
json: ['serde_json::Value', 'serde_json::Value'],
|
|
11
|
+
uuid: ['Uuid', 'uuid::Uuid'],
|
|
12
|
+
spatial: ['String', null],
|
|
13
|
+
enum: ['String', null], // overridden per-column with actual enum type
|
|
14
|
+
unknown: ['String', null],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PG_TO_RUST: Record<string, [string, string | null]> = {
|
|
18
|
+
// [rustType, importPath]
|
|
19
|
+
// Numeric
|
|
20
|
+
smallint: ['i16', null],
|
|
21
|
+
int2: ['i16', null],
|
|
22
|
+
integer: ['i32', null],
|
|
23
|
+
int: ['i32', null],
|
|
24
|
+
int4: ['i32', null],
|
|
25
|
+
bigint: ['i64', null],
|
|
26
|
+
int8: ['i64', null],
|
|
27
|
+
serial: ['i32', null],
|
|
28
|
+
serial4: ['i32', null],
|
|
29
|
+
bigserial: ['i64', null],
|
|
30
|
+
serial8: ['i64', null],
|
|
31
|
+
smallserial: ['i16', null],
|
|
32
|
+
serial2: ['i16', null],
|
|
33
|
+
real: ['f32', null],
|
|
34
|
+
float4: ['f32', null],
|
|
35
|
+
'double precision': ['f64', null],
|
|
36
|
+
float8: ['f64', null],
|
|
37
|
+
numeric: ['bigdecimal::BigDecimal', 'bigdecimal::BigDecimal'],
|
|
38
|
+
decimal: ['bigdecimal::BigDecimal', 'bigdecimal::BigDecimal'],
|
|
39
|
+
money: ['String', null],
|
|
40
|
+
|
|
41
|
+
// String
|
|
42
|
+
text: ['String', null],
|
|
43
|
+
varchar: ['String', null],
|
|
44
|
+
'character varying': ['String', null],
|
|
45
|
+
char: ['String', null],
|
|
46
|
+
character: ['String', null],
|
|
47
|
+
name: ['String', null],
|
|
48
|
+
citext: ['String', null],
|
|
49
|
+
|
|
50
|
+
// Boolean
|
|
51
|
+
boolean: ['bool', null],
|
|
52
|
+
bool: ['bool', null],
|
|
53
|
+
|
|
54
|
+
// Date/Time
|
|
55
|
+
timestamp: ['NaiveDateTime', 'chrono::NaiveDateTime'],
|
|
56
|
+
'timestamp without time zone': ['NaiveDateTime', 'chrono::NaiveDateTime'],
|
|
57
|
+
timestamptz: ['DateTime<Utc>', 'chrono::{DateTime, Utc}'],
|
|
58
|
+
'timestamp with time zone': ['DateTime<Utc>', 'chrono::{DateTime, Utc}'],
|
|
59
|
+
date: ['NaiveDate', 'chrono::NaiveDate'],
|
|
60
|
+
time: ['NaiveTime', 'chrono::NaiveTime'],
|
|
61
|
+
'time without time zone': ['NaiveTime', 'chrono::NaiveTime'],
|
|
62
|
+
timetz: ['NaiveTime', 'chrono::NaiveTime'],
|
|
63
|
+
'time with time zone': ['NaiveTime', 'chrono::NaiveTime'],
|
|
64
|
+
interval: ['PgInterval', null],
|
|
65
|
+
|
|
66
|
+
// Binary
|
|
67
|
+
bytea: ['Vec<u8>', null],
|
|
68
|
+
|
|
69
|
+
// JSON
|
|
70
|
+
json: ['serde_json::Value', 'serde_json::Value'],
|
|
71
|
+
jsonb: ['serde_json::Value', 'serde_json::Value'],
|
|
72
|
+
|
|
73
|
+
// UUID
|
|
74
|
+
uuid: ['Uuid', 'uuid::Uuid'],
|
|
75
|
+
|
|
76
|
+
// Network
|
|
77
|
+
inet: ['String', null],
|
|
78
|
+
cidr: ['String', null],
|
|
79
|
+
macaddr: ['String', null],
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Map a PostgreSQL type to a Rust type and required imports.
|
|
84
|
+
* Nullable types use Option<T>.
|
|
85
|
+
*/
|
|
86
|
+
export function pgToRust(pgType: string, nullable: boolean, category?: string): { type: string; imports: string[] } {
|
|
87
|
+
const normalized = pgType.toLowerCase().trim()
|
|
88
|
+
|
|
89
|
+
// Handle arrays
|
|
90
|
+
if (normalized.endsWith('[]')) {
|
|
91
|
+
const base = pgToRust(normalized.slice(0, -2), false)
|
|
92
|
+
const vecType = `Vec<${base.type}>`
|
|
93
|
+
return nullable ? { type: `Option<${vecType}>`, imports: base.imports } : { type: vecType, imports: base.imports }
|
|
94
|
+
}
|
|
95
|
+
if (normalized.startsWith('_')) {
|
|
96
|
+
const base = pgToRust(normalized.slice(1), false)
|
|
97
|
+
const vecType = `Vec<${base.type}>`
|
|
98
|
+
return nullable ? { type: `Option<${vecType}>`, imports: base.imports } : { type: vecType, imports: base.imports }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Strip length specifiers
|
|
102
|
+
const baseType = normalized.replace(/\(\d+(?:,\s*\d+)?\)/, '').trim()
|
|
103
|
+
|
|
104
|
+
// Try raw type lookup first for precise mapping
|
|
105
|
+
let mapping = PG_TO_RUST[baseType]
|
|
106
|
+
|
|
107
|
+
// Fall back to category-based mapping for unknown raw types
|
|
108
|
+
if (!mapping && category) {
|
|
109
|
+
mapping = CATEGORY_TO_RUST[category]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!mapping) return { type: 'String', imports: [] }
|
|
113
|
+
|
|
114
|
+
const [rustType, importPath] = mapping
|
|
115
|
+
const imports = importPath ? [importPath] : []
|
|
116
|
+
|
|
117
|
+
if (nullable) {
|
|
118
|
+
return { type: `Option<${rustType}>`, imports }
|
|
119
|
+
}
|
|
120
|
+
return { type: rustType, imports }
|
|
121
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export interface TsTypeOptions {
|
|
2
|
+
dateType?: 'Date' | 'temporal' | 'dayjs' | 'luxon' | 'string'
|
|
3
|
+
nullableStyle?: 'optional' | 'null-union'
|
|
4
|
+
bigintType?: 'number' | 'bigint' | 'string'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Category-based mapping — used when Atlas provides a type category */
|
|
8
|
+
const CATEGORY_TO_TS: Record<string, string> = {
|
|
9
|
+
string: 'string',
|
|
10
|
+
integer: 'number',
|
|
11
|
+
float: 'number',
|
|
12
|
+
decimal: 'string',
|
|
13
|
+
boolean: 'boolean',
|
|
14
|
+
time: 'Date',
|
|
15
|
+
binary: 'Buffer',
|
|
16
|
+
json: 'unknown',
|
|
17
|
+
uuid: 'string',
|
|
18
|
+
spatial: 'string',
|
|
19
|
+
enum: 'string', // overridden per-column with actual enum type
|
|
20
|
+
unknown: 'unknown',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PG_TO_TS: Record<string, string> = {
|
|
24
|
+
// Numeric
|
|
25
|
+
smallint: 'number',
|
|
26
|
+
int2: 'number',
|
|
27
|
+
integer: 'number',
|
|
28
|
+
int: 'number',
|
|
29
|
+
int4: 'number',
|
|
30
|
+
bigint: 'number',
|
|
31
|
+
int8: 'number',
|
|
32
|
+
serial: 'number',
|
|
33
|
+
serial4: 'number',
|
|
34
|
+
bigserial: 'number',
|
|
35
|
+
serial8: 'number',
|
|
36
|
+
smallserial: 'number',
|
|
37
|
+
serial2: 'number',
|
|
38
|
+
real: 'number',
|
|
39
|
+
float4: 'number',
|
|
40
|
+
'double precision': 'number',
|
|
41
|
+
float8: 'number',
|
|
42
|
+
numeric: 'string',
|
|
43
|
+
decimal: 'string',
|
|
44
|
+
money: 'string',
|
|
45
|
+
|
|
46
|
+
// String
|
|
47
|
+
text: 'string',
|
|
48
|
+
varchar: 'string',
|
|
49
|
+
'character varying': 'string',
|
|
50
|
+
char: 'string',
|
|
51
|
+
character: 'string',
|
|
52
|
+
name: 'string',
|
|
53
|
+
citext: 'string',
|
|
54
|
+
|
|
55
|
+
// Boolean
|
|
56
|
+
boolean: 'boolean',
|
|
57
|
+
bool: 'boolean',
|
|
58
|
+
|
|
59
|
+
// Date/Time
|
|
60
|
+
timestamp: 'Date',
|
|
61
|
+
'timestamp without time zone': 'Date',
|
|
62
|
+
timestamptz: 'Date',
|
|
63
|
+
'timestamp with time zone': 'Date',
|
|
64
|
+
date: 'string',
|
|
65
|
+
time: 'string',
|
|
66
|
+
'time without time zone': 'string',
|
|
67
|
+
timetz: 'string',
|
|
68
|
+
'time with time zone': 'string',
|
|
69
|
+
interval: 'string',
|
|
70
|
+
|
|
71
|
+
// Binary
|
|
72
|
+
bytea: 'Buffer',
|
|
73
|
+
|
|
74
|
+
// JSON
|
|
75
|
+
json: 'unknown',
|
|
76
|
+
jsonb: 'unknown',
|
|
77
|
+
|
|
78
|
+
// UUID
|
|
79
|
+
uuid: 'string',
|
|
80
|
+
|
|
81
|
+
// Network
|
|
82
|
+
inet: 'string',
|
|
83
|
+
cidr: 'string',
|
|
84
|
+
macaddr: 'string',
|
|
85
|
+
macaddr8: 'string',
|
|
86
|
+
|
|
87
|
+
// Geometric
|
|
88
|
+
point: 'string',
|
|
89
|
+
line: 'string',
|
|
90
|
+
box: 'string',
|
|
91
|
+
circle: 'string',
|
|
92
|
+
polygon: 'string',
|
|
93
|
+
path: 'string',
|
|
94
|
+
|
|
95
|
+
// Other
|
|
96
|
+
xml: 'string',
|
|
97
|
+
tsvector: 'string',
|
|
98
|
+
tsquery: 'string',
|
|
99
|
+
oid: 'number',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Temporal API mappings — each SQL date/time type maps to the right Temporal type */
|
|
103
|
+
const TEMPORAL_MAP: Record<string, string> = {
|
|
104
|
+
date: 'Temporal.PlainDate',
|
|
105
|
+
timestamp: 'Temporal.PlainDateTime',
|
|
106
|
+
'timestamp without time zone': 'Temporal.PlainDateTime',
|
|
107
|
+
timestamptz: 'Temporal.ZonedDateTime',
|
|
108
|
+
'timestamp with time zone': 'Temporal.ZonedDateTime',
|
|
109
|
+
time: 'Temporal.PlainTime',
|
|
110
|
+
'time without time zone': 'Temporal.PlainTime',
|
|
111
|
+
timetz: 'string',
|
|
112
|
+
'time with time zone': 'string',
|
|
113
|
+
interval: 'Temporal.Duration',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Map a PostgreSQL type to a TypeScript type string.
|
|
118
|
+
* Handles arrays (text[], _text), length specifiers (varchar(255)), and nullability.
|
|
119
|
+
*/
|
|
120
|
+
export function pgToTs(pgType: string, nullable: boolean, options: TsTypeOptions = {}, category?: string): string {
|
|
121
|
+
const normalized = pgType.toLowerCase().trim()
|
|
122
|
+
|
|
123
|
+
// Handle arrays: text[] or _text
|
|
124
|
+
if (normalized.endsWith('[]')) {
|
|
125
|
+
const base = pgToTs(normalized.slice(0, -2), false, options)
|
|
126
|
+
return wrapNullable(`${base}[]`, nullable, options.nullableStyle)
|
|
127
|
+
}
|
|
128
|
+
if (normalized.startsWith('_')) {
|
|
129
|
+
const base = pgToTs(normalized.slice(1), false, options)
|
|
130
|
+
return wrapNullable(`${base}[]`, nullable, options.nullableStyle)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Strip length specifiers: varchar(255) -> varchar, numeric(10,2) -> numeric
|
|
134
|
+
const baseType = normalized.replace(/\(\d+(?:,\s*\d+)?\)/, '').trim()
|
|
135
|
+
|
|
136
|
+
// Try raw type lookup first for precise mapping
|
|
137
|
+
let tsType = PG_TO_TS[baseType]
|
|
138
|
+
|
|
139
|
+
// Fall back to category-based mapping for unknown raw types
|
|
140
|
+
if (tsType === undefined && category) {
|
|
141
|
+
tsType = CATEGORY_TO_TS[category]
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (tsType === undefined) {
|
|
145
|
+
tsType = 'unknown'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Apply bigintType option
|
|
149
|
+
if (
|
|
150
|
+
(baseType === 'bigint' || baseType === 'int8' || baseType === 'bigserial' || baseType === 'serial8') &&
|
|
151
|
+
options.bigintType
|
|
152
|
+
) {
|
|
153
|
+
tsType = options.bigintType
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Apply dateType option
|
|
157
|
+
if (options.dateType === 'temporal') {
|
|
158
|
+
const temporal = TEMPORAL_MAP[baseType]
|
|
159
|
+
if (temporal) tsType = temporal
|
|
160
|
+
} else if (options.dateType && options.dateType !== 'Date') {
|
|
161
|
+
if (tsType === 'Date') {
|
|
162
|
+
tsType = options.dateType === 'string' ? 'string' : `import('${options.dateType}').Dayjs`
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return wrapNullable(tsType, nullable, options.nullableStyle)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function wrapNullable(type: string, nullable: boolean, style?: 'optional' | 'null-union'): string {
|
|
170
|
+
if (!nullable) return type
|
|
171
|
+
if (style === 'null-union') return `${type} | null`
|
|
172
|
+
return type // 'optional' style is handled at property level with ?:
|
|
173
|
+
}
|