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.
Files changed (46) hide show
  1. package/lib/extractor/adapter.d.ts +9 -1
  2. package/lib/extractor/adapter.js +30 -1
  3. package/lib/extractor/canonicalise/composite.d.ts +7 -0
  4. package/lib/extractor/canonicalise/composite.js +53 -0
  5. package/lib/extractor/canonicalise/domain.d.ts +12 -0
  6. package/lib/extractor/canonicalise/domain.js +77 -0
  7. package/lib/extractor/canonicalise/enum.d.ts +8 -0
  8. package/lib/extractor/canonicalise/enum.js +22 -0
  9. package/lib/extractor/canonicalise/index.d.ts +11 -0
  10. package/lib/extractor/canonicalise/index.js +148 -0
  11. package/lib/extractor/canonicalise/parse.d.ts +43 -0
  12. package/lib/extractor/canonicalise/parse.js +50 -0
  13. package/lib/extractor/canonicalise/range.d.ts +11 -0
  14. package/lib/extractor/canonicalise/range.js +36 -0
  15. package/lib/extractor/canonicalise/resolve.d.ts +19 -0
  16. package/lib/extractor/canonicalise/resolve.js +59 -0
  17. package/lib/extractor/{canonicalise.d.ts → canonicalise/types.d.ts} +17 -11
  18. package/lib/extractor/canonicalise/types.js +13 -0
  19. package/lib/extractor/index.d.ts +7 -4
  20. package/lib/extractor/index.js +5 -10
  21. package/lib/extractor/kinds/composite.d.ts +1 -1
  22. package/lib/extractor/kinds/composite.js +1 -2
  23. package/lib/extractor/kinds/domain.d.ts +1 -1
  24. package/lib/extractor/kinds/domain.js +1 -2
  25. package/lib/extractor/kinds/function.d.ts +1 -1
  26. package/lib/extractor/kinds/function.js +5 -8
  27. package/lib/extractor/kinds/materialized-view.d.ts +1 -1
  28. package/lib/extractor/kinds/materialized-view.js +4 -8
  29. package/lib/extractor/kinds/range.d.ts +1 -1
  30. package/lib/extractor/kinds/range.js +1 -3
  31. package/lib/extractor/kinds/table.d.ts +1 -1
  32. package/lib/extractor/kinds/table.js +2 -8
  33. package/lib/extractor/kinds/view.d.ts +1 -1
  34. package/lib/extractor/kinds/view.js +4 -8
  35. package/lib/extractor/pgtype.d.ts +1 -1
  36. package/lib/imports.js +6 -3
  37. package/lib/imports.test.d.ts +1 -0
  38. package/lib/imports.test.js +180 -0
  39. package/lib/index.js +19 -3
  40. package/lib/kysely/index.js +2 -0
  41. package/lib/types.d.ts +1 -1
  42. package/lib/util.d.ts +4 -0
  43. package/lib/util.js +18 -0
  44. package/lib/zod/index.js +2 -10
  45. package/package.json +1 -1
  46. package/lib/extractor/canonicalise.js +0 -245
@@ -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" | "enum" | "domain" | "range" | "table" | "view" | "materializedView" | "composite")[];
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 += ".nullable()";
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,6 +1,6 @@
1
1
  {
2
2
  "name": "true-pg",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "module": "lib/index.js",
6
6
  "main": "lib/index.js",
@@ -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
- };