true-pg 0.6.0 → 0.8.0
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/lib/extractor/adapter.d.ts +9 -1
- package/lib/extractor/adapter.js +30 -1
- package/lib/extractor/canonicalise/composite.d.ts +7 -0
- package/lib/extractor/canonicalise/composite.js +53 -0
- package/lib/extractor/canonicalise/domain.d.ts +12 -0
- package/lib/extractor/canonicalise/domain.js +77 -0
- package/lib/extractor/canonicalise/enum.d.ts +8 -0
- package/lib/extractor/canonicalise/enum.js +22 -0
- package/lib/extractor/canonicalise/index.d.ts +11 -0
- package/lib/extractor/canonicalise/index.js +148 -0
- package/lib/extractor/canonicalise/parse.d.ts +43 -0
- package/lib/extractor/canonicalise/parse.js +50 -0
- package/lib/extractor/canonicalise/range.d.ts +11 -0
- package/lib/extractor/canonicalise/range.js +36 -0
- package/lib/extractor/canonicalise/resolve.d.ts +19 -0
- package/lib/extractor/canonicalise/resolve.js +59 -0
- package/lib/extractor/{canonicalise.d.ts → canonicalise/types.d.ts} +17 -11
- package/lib/extractor/canonicalise/types.js +13 -0
- package/lib/extractor/index.d.ts +7 -4
- package/lib/extractor/index.js +5 -10
- package/lib/extractor/kinds/composite.d.ts +1 -1
- package/lib/extractor/kinds/composite.js +1 -2
- package/lib/extractor/kinds/domain.d.ts +1 -1
- package/lib/extractor/kinds/domain.js +1 -2
- package/lib/extractor/kinds/function.d.ts +1 -1
- package/lib/extractor/kinds/function.js +5 -8
- package/lib/extractor/kinds/materialized-view.d.ts +1 -1
- package/lib/extractor/kinds/materialized-view.js +4 -8
- package/lib/extractor/kinds/range.d.ts +1 -1
- package/lib/extractor/kinds/range.js +1 -3
- package/lib/extractor/kinds/table.d.ts +1 -1
- package/lib/extractor/kinds/table.js +2 -8
- package/lib/extractor/kinds/view.d.ts +1 -1
- package/lib/extractor/kinds/view.js +4 -8
- package/lib/extractor/pgtype.d.ts +1 -1
- package/lib/imports.js +6 -3
- package/lib/imports.test.d.ts +1 -0
- package/lib/imports.test.js +180 -0
- package/lib/index.js +19 -3
- package/lib/kysely/index.js +2 -0
- package/lib/types.d.ts +1 -1
- package/lib/util.d.ts +4 -0
- package/lib/util.js +18 -0
- package/lib/zod/index.js +2 -10
- package/package.json +1 -1
- package/lib/extractor/canonicalise.js +0 -245
package/lib/kysely/index.js
CHANGED
@@ -31,6 +31,8 @@ export const Kysely = createGenerator(opts => {
|
|
31
31
|
qualified = `Generated<${qualified}>`;
|
32
32
|
ky(ctx, "Generated");
|
33
33
|
}
|
34
|
+
// TODO: Use ColumnType for appropriate cases:
|
35
|
+
// composite, jsonb, json, date types, etc
|
34
36
|
let out = col.comment ? `/** ${col.comment} */\n\t` : "";
|
35
37
|
out += quoteI(col.name);
|
36
38
|
out += `: ${qualified}`;
|
package/lib/types.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Canonical, type TableDetails, type ViewDetails, type MaterializedViewDetails, type EnumDetails, type CompositeTypeDetails, type DomainDetails, type RangeDetails, type FunctionDetails, type SchemaType, type Schema, type FunctionReturnType } from "./extractor/index.ts";
|
2
2
|
import type { ImportList } from "./imports.ts";
|
3
|
-
export declare const allowed_kind_names: ("function" | "
|
3
|
+
export declare const allowed_kind_names: ("function" | "composite" | "domain" | "enum" | "range" | "table" | "view" | "materializedView")[];
|
4
4
|
export type allowed_kind_names = (typeof allowed_kind_names)[number];
|
5
5
|
export interface FolderStructure {
|
6
6
|
name: string;
|
package/lib/util.d.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
export declare const unreachable: (value: never) => never;
|
1
2
|
export declare const eq: <T>(a: T, b: T) => boolean;
|
2
3
|
export declare const toPascalCase: (str: string) => string;
|
3
4
|
export declare const to_snake_case: (str: string) => string;
|
@@ -13,4 +14,7 @@ export type Simplify<T> = {
|
|
13
14
|
export declare const parens: (str: string, type?: string) => string;
|
14
15
|
export declare const quote: (str: string, using?: string) => string;
|
15
16
|
export declare const quoteI: (str: string, using?: string) => string;
|
17
|
+
export declare const removeNulls: <T>(o: T) => T;
|
18
|
+
export declare const pos: (num: number) => number | undefined;
|
19
|
+
export declare const minifyQuery: (query: string) => string;
|
16
20
|
export {};
|
package/lib/util.js
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
export const unreachable = (value) => {
|
2
|
+
throw new Error(`Fatal: Reached unreachable code: ${value}`);
|
3
|
+
};
|
1
4
|
export const eq = (a, b) => {
|
2
5
|
if (a === b)
|
3
6
|
return true;
|
@@ -128,3 +131,18 @@ const isIdentifierInvalid = (str) => {
|
|
128
131
|
export const parens = (str, type = "()") => `${type[0]}${str}${type[1]}`;
|
129
132
|
export const quote = (str, using = '"') => `${using}${str.replaceAll(using, "\\" + using)}${using}`;
|
130
133
|
export const quoteI = (str, using = '"') => (isIdentifierInvalid(str) ? quote(str, using) : str);
|
134
|
+
export const removeNulls = (o) => {
|
135
|
+
for (const key in o)
|
136
|
+
if (o[key] == null)
|
137
|
+
delete o[key];
|
138
|
+
return o;
|
139
|
+
};
|
140
|
+
export const pos = (num) => (num < 0 ? undefined : num);
|
141
|
+
export const minifyQuery = (query) => {
|
142
|
+
return query
|
143
|
+
.split("\n")
|
144
|
+
.map(line => line.slice(0, pos(line.indexOf("--"))))
|
145
|
+
.join("\n")
|
146
|
+
.replaceAll(/\s+/g, " ")
|
147
|
+
.trim();
|
148
|
+
};
|
package/lib/zod/index.js
CHANGED
@@ -22,16 +22,12 @@ export const Zod = createGenerator(opts => {
|
|
22
22
|
out += quoteI(col.name);
|
23
23
|
const nullable = col.isNullable || col.generated === "BY DEFAULT" || col.defaultValue;
|
24
24
|
let type = generator.formatType(ctx, col.type, { nullable });
|
25
|
-
if (nullable)
|
26
|
-
type += `.optional()`;
|
27
25
|
out += `: ${type}`;
|
28
26
|
return `\t${out},\n`;
|
29
27
|
};
|
30
28
|
const composite_attribute = (ctx, attr) => {
|
31
29
|
let out = quoteI(attr.name);
|
32
30
|
out += `: ${generator.formatType(ctx, attr.type, { nullable: attr.isNullable })}`;
|
33
|
-
if (attr.isNullable)
|
34
|
-
out += ".optional()";
|
35
31
|
return out;
|
36
32
|
};
|
37
33
|
const generator = {
|
@@ -80,7 +76,7 @@ export const Zod = createGenerator(opts => {
|
|
80
76
|
if ("dimensions" in type)
|
81
77
|
base += ".array()".repeat(type.dimensions);
|
82
78
|
if (attr?.nullable)
|
83
|
-
base += ".
|
79
|
+
base += ".optional()";
|
84
80
|
return base;
|
85
81
|
},
|
86
82
|
table(ctx, table) {
|
@@ -163,8 +159,6 @@ export const Zod = createGenerator(opts => {
|
|
163
159
|
for (const param of inputParams) {
|
164
160
|
// TODO: update imports for non-primitive types based on typeInfo.kind
|
165
161
|
out += "\t\t" + this.formatType(ctx, param.type, { nullable: param.hasDefault });
|
166
|
-
if (param.hasDefault)
|
167
|
-
out += ".optional()";
|
168
162
|
out += `, // ${param.name}\n`;
|
169
163
|
}
|
170
164
|
out += "\t])";
|
@@ -249,9 +243,7 @@ export const Zod = createGenerator(opts => {
|
|
249
243
|
fullIndex(ctx, schemas, main_generator) {
|
250
244
|
const generator = main_generator ?? this;
|
251
245
|
const parts = [];
|
252
|
-
parts.push(schemas
|
253
|
-
.map(s => `import { ${generator.formatSchemaName(s.name)} } from "./${s.name}/index.ts";`)
|
254
|
-
.join("\n"));
|
246
|
+
parts.push(schemas.map(s => `import { ${generator.formatSchemaName(s.name)} } from "./${s.name}/index.ts";`).join("\n"));
|
255
247
|
{
|
256
248
|
let validator = `export const Validators = {\n`;
|
257
249
|
validator += join(schemas.map(schema => {
|
package/package.json
CHANGED
@@ -1,245 +0,0 @@
|
|
1
|
-
import { DbAdapter } from "./adapter.js";
|
2
|
-
const removeNulls = (o) => {
|
3
|
-
for (const key in o)
|
4
|
-
if (o[key] == null)
|
5
|
-
delete o[key];
|
6
|
-
return o;
|
7
|
-
};
|
8
|
-
export var Canonical;
|
9
|
-
(function (Canonical) {
|
10
|
-
let Kind;
|
11
|
-
(function (Kind) {
|
12
|
-
Kind["Base"] = "base";
|
13
|
-
Kind["Composite"] = "composite";
|
14
|
-
Kind["Domain"] = "domain";
|
15
|
-
Kind["Enum"] = "enum";
|
16
|
-
Kind["Range"] = "range";
|
17
|
-
Kind["Pseudo"] = "pseudo";
|
18
|
-
Kind["Unknown"] = "unknown";
|
19
|
-
})(Kind = Canonical.Kind || (Canonical.Kind = {}));
|
20
|
-
})(Canonical || (Canonical = {}));
|
21
|
-
export const canonicalise = async (db, types) => {
|
22
|
-
if (types.length === 0)
|
23
|
-
return [];
|
24
|
-
const placeholders = types.map((_, i) => `($${i * 2 + 1}, $${i * 2 + 2})`).join(", ");
|
25
|
-
const query = `
|
26
|
-
WITH RECURSIVE
|
27
|
-
-- Parameters with sequence numbers to preserve order
|
28
|
-
input(type_name, seq) AS (
|
29
|
-
VALUES ${placeholders}
|
30
|
-
),
|
31
|
-
-- Parse array dimensions and base type
|
32
|
-
type_parts AS (
|
33
|
-
SELECT
|
34
|
-
type_name,
|
35
|
-
seq,
|
36
|
-
CASE
|
37
|
-
WHEN type_name ~ '\\(.*\\)' THEN regexp_replace(type_name, '\\(.*\\)', '')
|
38
|
-
ELSE type_name
|
39
|
-
END AS clean_type,
|
40
|
-
CASE
|
41
|
-
WHEN type_name ~ '\\(.*\\)' THEN substring(type_name from '\\((.*\\?)\\)')
|
42
|
-
ELSE NULL
|
43
|
-
END AS modifiers
|
44
|
-
FROM input
|
45
|
-
),
|
46
|
-
array_dimensions AS (
|
47
|
-
SELECT
|
48
|
-
type_name,
|
49
|
-
seq,
|
50
|
-
modifiers,
|
51
|
-
CASE
|
52
|
-
WHEN clean_type ~ '.*\\[\\].*' THEN
|
53
|
-
(length(clean_type) - length(regexp_replace(clean_type, '\\[\\]', '', 'g'))) / 2
|
54
|
-
ELSE 0
|
55
|
-
END AS dimensions,
|
56
|
-
regexp_replace(clean_type, '\\[\\]', '', 'g') AS base_type_name
|
57
|
-
FROM type_parts
|
58
|
-
),
|
59
|
-
-- Get base type information
|
60
|
-
base_type_info AS (
|
61
|
-
SELECT
|
62
|
-
a.type_name,
|
63
|
-
a.seq,
|
64
|
-
a.modifiers,
|
65
|
-
a.dimensions,
|
66
|
-
t.oid AS type_oid,
|
67
|
-
t.typname AS internal_name,
|
68
|
-
n.nspname AS schema_name,
|
69
|
-
t.typtype AS type_kind_code,
|
70
|
-
t.typbasetype,
|
71
|
-
CASE t.typtype
|
72
|
-
WHEN 'b' THEN 'base'
|
73
|
-
WHEN 'c' THEN 'composite'
|
74
|
-
WHEN 'd' THEN 'domain'
|
75
|
-
WHEN 'e' THEN 'enum'
|
76
|
-
WHEN 'p' THEN 'pseudo'
|
77
|
-
WHEN 'r' THEN 'range'
|
78
|
-
ELSE 'unknown'
|
79
|
-
END AS type_kind
|
80
|
-
FROM array_dimensions a
|
81
|
-
JOIN pg_type t ON t.oid = a.base_type_name::regtype
|
82
|
-
JOIN pg_namespace n ON t.typnamespace = n.oid
|
83
|
-
),
|
84
|
-
-- Handle enum values for enum types
|
85
|
-
enum_values AS (
|
86
|
-
SELECT
|
87
|
-
b.type_name,
|
88
|
-
jsonb_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
|
89
|
-
FROM base_type_info b
|
90
|
-
JOIN pg_enum e ON b.type_oid = e.enumtypid
|
91
|
-
WHERE b.type_kind_code = 'e'
|
92
|
-
GROUP BY b.type_name
|
93
|
-
),
|
94
|
-
-- Enhanced composite attributes with additional metadata
|
95
|
-
composite_attributes AS (
|
96
|
-
SELECT
|
97
|
-
b.type_name,
|
98
|
-
jsonb_agg(
|
99
|
-
jsonb_build_object(
|
100
|
-
'name', a.attname,
|
101
|
-
'index', a.attnum,
|
102
|
-
'type_oid', a.atttypid,
|
103
|
-
'type_name', format_type(a.atttypid, null),
|
104
|
-
'comment', col_description(c.oid, a.attnum::int),
|
105
|
-
'defaultValue', pg_get_expr(d.adbin, d.adrelid),
|
106
|
-
'isNullable', NOT a.attnotnull,
|
107
|
-
'isIdentity', a.attidentity IS NOT NULL AND a.attidentity != '',
|
108
|
-
'generated', CASE
|
109
|
-
WHEN a.attidentity = 'a' THEN 'ALWAYS'
|
110
|
-
WHEN a.attidentity = 'd' THEN 'BY DEFAULT'
|
111
|
-
WHEN a.attgenerated = 's' THEN 'ALWAYS'
|
112
|
-
ELSE 'NEVER'
|
113
|
-
END
|
114
|
-
)
|
115
|
-
ORDER BY a.attnum
|
116
|
-
) AS attributes
|
117
|
-
FROM base_type_info b
|
118
|
-
JOIN pg_type t ON t.oid = b.type_oid
|
119
|
-
JOIN pg_class c ON c.oid = t.typrelid
|
120
|
-
JOIN pg_attribute a ON a.attrelid = c.oid
|
121
|
-
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = a.attnum
|
122
|
-
WHERE b.type_kind_code = 'c' AND a.attnum > 0 AND NOT a.attisdropped
|
123
|
-
GROUP BY b.type_name
|
124
|
-
),
|
125
|
-
-- Recursive CTE to resolve domain base types
|
126
|
-
domain_types AS (
|
127
|
-
-- Base case: start with initial domain type
|
128
|
-
SELECT
|
129
|
-
b.type_name AS original_type,
|
130
|
-
b.type_oid AS domain_oid,
|
131
|
-
b.typbasetype AS base_type_oid,
|
132
|
-
1 AS level
|
133
|
-
FROM base_type_info b
|
134
|
-
WHERE b.type_kind_code = 'd'
|
135
|
-
|
136
|
-
UNION ALL
|
137
|
-
|
138
|
-
-- Recursive case: follow chain of domains
|
139
|
-
SELECT
|
140
|
-
d.original_type,
|
141
|
-
t.oid AS domain_oid,
|
142
|
-
t.typbasetype AS base_type_oid,
|
143
|
-
d.level + 1 AS level
|
144
|
-
FROM domain_types d
|
145
|
-
JOIN pg_type t ON d.base_type_oid = t.oid
|
146
|
-
WHERE t.typtype = 'd'-- Only continue if the base is also a domain
|
147
|
-
),
|
148
|
-
-- Get ultimate base type for domains
|
149
|
-
domain_base_types AS (
|
150
|
-
SELECT DISTINCT ON (original_type)
|
151
|
-
d.original_type,
|
152
|
-
format('%s.%s', n.nspname, t.typname) AS base_canonical_name
|
153
|
-
FROM (
|
154
|
-
-- Get the max level for each original type
|
155
|
-
SELECT original_type, MAX(level) AS max_level
|
156
|
-
FROM domain_types
|
157
|
-
GROUP BY original_type
|
158
|
-
) m
|
159
|
-
JOIN domain_types d ON d.original_type = m.original_type AND d.level = m.max_level
|
160
|
-
JOIN pg_type t ON d.base_type_oid = t.oid
|
161
|
-
JOIN pg_namespace n ON t.typnamespace = n.oid
|
162
|
-
),
|
163
|
-
-- Range type subtype information
|
164
|
-
range_subtypes AS (
|
165
|
-
SELECT
|
166
|
-
b.type_name,
|
167
|
-
format('%s.%s', n.nspname, t.typname) AS subtype_canonical_name
|
168
|
-
FROM base_type_info b
|
169
|
-
JOIN pg_range r ON b.type_oid = r.rngtypid
|
170
|
-
JOIN pg_type t ON t.oid = r.rngsubtype -- Join to get subtype details
|
171
|
-
JOIN pg_namespace n ON n.oid = t.typnamespace -- Join to get subtype schema
|
172
|
-
WHERE b.type_kind_code = 'r'
|
173
|
-
)
|
174
|
-
-- Final result as JSON
|
175
|
-
SELECT jsonb_build_object(
|
176
|
-
'canonical_name', b.schema_name || '.' || b.internal_name,
|
177
|
-
'schema', b.schema_name,
|
178
|
-
'name', b.internal_name,
|
179
|
-
'kind', b.type_kind,
|
180
|
-
'dimensions', b.dimensions,
|
181
|
-
'original_type', b.type_name,
|
182
|
-
'modifiers', b.modifiers,
|
183
|
-
'enum_values', e.values,
|
184
|
-
'attributes', c.attributes,
|
185
|
-
'domain_base_type', CASE
|
186
|
-
WHEN b.type_kind_code = 'd' THEN d.base_canonical_name
|
187
|
-
ELSE NULL
|
188
|
-
END,
|
189
|
-
'range_subtype', CASE
|
190
|
-
WHEN b.type_kind_code = 'r' THEN r.subtype_canonical_name
|
191
|
-
ELSE NULL
|
192
|
-
END
|
193
|
-
) AS type_info,
|
194
|
-
b.seq
|
195
|
-
FROM base_type_info b
|
196
|
-
LEFT JOIN enum_values e ON b.type_name = e.type_name
|
197
|
-
LEFT JOIN composite_attributes c ON b.type_name = c.type_name
|
198
|
-
LEFT JOIN domain_base_types d ON b.type_name = d.original_type
|
199
|
-
LEFT JOIN range_subtypes r ON b.type_name = r.type_name
|
200
|
-
ORDER BY b.seq::integer;
|
201
|
-
`;
|
202
|
-
const resolved = await db.query(query, types.flatMap((type, index) => [type, index]));
|
203
|
-
return Promise.all(resolved
|
204
|
-
.map(each => each.type_info)
|
205
|
-
.map(async (each) => {
|
206
|
-
if (each.kind === Canonical.Kind.Composite) {
|
207
|
-
const types = each.attributes.map(each => each.type_name);
|
208
|
-
const canonical = await canonicalise(db, types);
|
209
|
-
const attributes = await Promise.all(each.attributes.map(async (each, index) => {
|
210
|
-
return {
|
211
|
-
name: each.name,
|
212
|
-
index: each.index,
|
213
|
-
type: canonical[index],
|
214
|
-
comment: each.comment,
|
215
|
-
defaultValue: each.defaultValue,
|
216
|
-
isNullable: each.isNullable,
|
217
|
-
isIdentity: each.isIdentity,
|
218
|
-
generated: each.generated,
|
219
|
-
};
|
220
|
-
}));
|
221
|
-
return removeNulls({
|
222
|
-
...each,
|
223
|
-
kind: Canonical.Kind.Composite,
|
224
|
-
attributes,
|
225
|
-
});
|
226
|
-
}
|
227
|
-
if (each.kind === Canonical.Kind.Domain) {
|
228
|
-
const canonical = await canonicalise(db, [each.domain_base_type]);
|
229
|
-
return removeNulls({
|
230
|
-
...each,
|
231
|
-
kind: Canonical.Kind.Domain,
|
232
|
-
domain_base_type: canonical[0],
|
233
|
-
});
|
234
|
-
}
|
235
|
-
if (each.kind === Canonical.Kind.Range) {
|
236
|
-
const canonical = await canonicalise(db, [each.range_subtype]);
|
237
|
-
return removeNulls({
|
238
|
-
...each,
|
239
|
-
kind: Canonical.Kind.Range,
|
240
|
-
range_subtype: canonical[0],
|
241
|
-
});
|
242
|
-
}
|
243
|
-
return removeNulls(each);
|
244
|
-
}));
|
245
|
-
};
|