true-pg 0.5.0 → 0.6.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/README.md CHANGED
@@ -15,7 +15,7 @@ bun add true-pg
15
15
  ## Quickstart
16
16
 
17
17
  ```bash
18
- npx true-pg --all-adapters --uri postgres://user:password@localhost:5432/database --out ./models
18
+ npx true-pg --all-generators --uri postgres://user:password@localhost:5432/database --out ./models
19
19
  ```
20
20
 
21
21
  This will generate a `models` directory with the following structure:
@@ -68,8 +68,8 @@ Options:
68
68
  - `-c, --config [path]` - Path to config file (JSON)
69
69
  - `-u, --uri [uri]` - Database URI (Postgres only!)
70
70
  - `-o, --out [path]` - Path to output directory (defaults to "models")
71
- - `-a, --adapter [adapter]` - Adapter to use (e.g. `kysely`, `zod`). Can be specified multiple times.
72
- - `-A, --all-adapters` - Enable all built-in adapters
71
+ - `-g, --generator [generator]` - Generator to use (e.g. `kysely`, `zod`). Can be specified multiple times.
72
+ - `-A, --all-generators` - Enable all built-in generators
73
73
 
74
74
  You can configure true-pg either through command-line arguments or a config file.
75
75
 
@@ -89,7 +89,7 @@ import { config } from "true-pg";
89
89
  export default config({
90
90
  uri: "postgres://user:password@localhost:5432/database",
91
91
  out: "src/models",
92
- adapters: ["kysely", "zod"],
92
+ generators: ["kysely", "zod"],
93
93
  defaultSchema: "public",
94
94
  });
95
95
  ```
@@ -101,7 +101,7 @@ export default config({
101
101
  | `uri` | PostgreSQL connection URI | Required, or config |
102
102
  | `config` | Knex connection config object | Required, or uri |
103
103
  | `out` | Output directory for generated files | `"models"` |
104
- | `adapters` | Adapters to use (e.g. `kysely`, `zod`) | `"kysely"` |
104
+ | `generators` | Generators to use (e.g. `kysely`, `zod`) | `"kysely"` |
105
105
  | `defaultSchema` | Default schema to use (Kysely schema will be unprefixed) | `"public"` |
106
106
 
107
107
  ## Customising Code Generation
@@ -118,28 +118,28 @@ You can create a custom generator to control how code is generated:
118
118
  import { createGenerator, generate } from "true-pg";
119
119
 
120
120
  const generator = createGenerator(opts => ({
121
- formatSchema: name => `${name}Schema`,
122
- formatSchemaType: type => `${type}Type`,
123
- formatType: type => `${type}Interface`,
124
- table: (imports, table) => {
121
+ formatSchemaName: name => `${name}Schema`,
122
+ formatSchemaMemberName: type => `${type.name}Type`,
123
+ formatType: (ctx, type) => `${type}Interface`,
124
+ table: (ctx, table) => {
125
125
  // Custom table type generation
126
126
  },
127
- enum: (imports, en) => {
127
+ enum: (ctx, en) => {
128
128
  // Custom enum type generation
129
129
  },
130
- composite: (imports, composite) => {
130
+ composite: (ctx, composite) => {
131
131
  // Custom composite type generation
132
132
  },
133
- function: (imports, func) => {
133
+ function: (ctx, func) => {
134
134
  // Custom function type generation
135
135
  },
136
- schemaKindIndex: (schema, kind) => {
136
+ schemaKindIndex: (ctx, schema, kind) => {
137
137
  // Custom schema kind index generation
138
138
  },
139
- schemaIndex: schema => {
139
+ schemaIndex: (ctx, schema) => {
140
140
  // Custom schema index generation
141
141
  },
142
- fullIndex: schemas => {
142
+ fullIndex: (ctx, schemas) => {
143
143
  // Custom full index generation
144
144
  },
145
145
  }));
@@ -147,14 +147,14 @@ const generator = createGenerator(opts => ({
147
147
  await generate(
148
148
  {
149
149
  uri: "postgres://user:password@localhost:5432/database",
150
- adapters: [], // empty array to disable adapters
150
+ generators: [], // empty array to disable generators
151
151
  out: "src/models",
152
152
  },
153
153
  [generator],
154
154
  );
155
155
  ```
156
156
 
157
- Filenames will be created using the `format*` methods of the FIRST generator passed to `generate` or via the `--adapter` CLI option.
157
+ Filenames will be created using the `format*` methods of the FIRST generator passed to `generate` or via the `--generator` CLI option.
158
158
 
159
159
  ## Schema Generator Interface
160
160
 
package/lib/bin.js CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import mri from "mri";
3
3
  import { generate } from "./index.js";
4
- import { adapters } from "./config.js";
4
+ import { generators } from "./config.js";
5
5
  const args = process.argv.slice(2);
6
6
  const opts = mri(args, {
7
- boolean: ["help", "all-adapters"],
8
- string: ["config", "uri", "out", "adapter"],
7
+ boolean: ["help", "all-generators"],
8
+ string: ["config", "uri", "out", "generator"],
9
9
  alias: {
10
10
  h: "help",
11
11
  c: "config",
12
12
  u: "uri",
13
13
  o: "out",
14
- a: "adapter",
15
- A: "all-adapters",
14
+ a: "generator",
15
+ A: "all-generators",
16
16
  },
17
17
  });
18
18
  import { cosmiconfig } from "cosmiconfig";
@@ -31,27 +31,27 @@ if (help) {
31
31
  log(" -h, --help Show help");
32
32
  log(" -u, --uri [uri] Database URI (Postgres only!)");
33
33
  log(" -o, --out [path] Path to output directory");
34
- log(" -a, --adapter [adapter] Output adapter to use (default: 'kysely')");
35
- log(" -A, --all-adapters Output all adapters");
34
+ log(" -g, --generator [generator] Output generator to use (default: 'kysely')");
35
+ log(" -A, --all-generators Output all generators");
36
36
  log(" -c, --config [path] Path to config file");
37
37
  log(" Defaults to '.truepgrc.json' or '.config/.truepgrc.json'");
38
38
  log("Example:");
39
- log(" true-pg -u postgres://user:pass@localhost:5432/my-database -o models -a kysely -a zod");
39
+ log(" true-pg -u postgres://user:pass@localhost:5432/my-database -o models -g kysely -g zod");
40
40
  log();
41
41
  if (opts.help)
42
42
  process.exit(0);
43
43
  else
44
44
  process.exit(1);
45
45
  }
46
- if (opts["all-adapters"])
47
- opts.adapter = Object.keys(adapters);
48
- if (!(opts.adapter || config.adapters))
49
- console.warn('No adapters specified, using default: ["kysely"]');
50
- // allow single adapter or comma-separated list of adapters
51
- if (typeof opts.adapter === "string")
52
- opts.adapter = opts.adapter.split(",");
46
+ if (opts["all-generators"])
47
+ opts.generator = Object.keys(generators);
48
+ if (!(opts.generator || config.generators))
49
+ console.warn('No generators specified, using default: ["kysely"]');
50
+ // allow single generator or comma-separated list of generators
51
+ if (typeof opts.generator === "string")
52
+ opts.generator = opts.generator.split(",");
53
53
  // CLI args take precedence over config file
54
54
  config.uri = opts.uri ?? config.uri;
55
55
  config.out = opts.out ?? config.out;
56
- config.adapters = opts.adapter ?? config.adapters ?? ["kysely"];
56
+ config.generators = opts.generator ?? config.generators ?? ["kysely"];
57
57
  await generate(config);
package/lib/config.d.ts CHANGED
@@ -4,8 +4,8 @@ export type ExtractorConfig = Exclude<ConstructorParameters<typeof Extractor>[0]
4
4
  export interface BaseConfig {
5
5
  /** The output directory for the generated models. Default: "models" */
6
6
  out?: string;
7
- /** Adapters to enable. Currently supported adapters are "kysely" and "zod". Default: ["kysely"] */
8
- adapters?: ("kysely" | "zod")[];
7
+ /** Generators to enable. Currently supported generators are "kysely" and "zod". Default: ["kysely"] */
8
+ generators?: ("kysely" | "zod")[];
9
9
  /** The default schema to use for the generated models. These will be unprefixed in the final `Database` interface. Default: "public" */
10
10
  defaultSchema?: string;
11
11
  }
@@ -22,27 +22,27 @@ export interface ConfigConfig extends BaseConfig {
22
22
  config: ExtractorConfig["config"];
23
23
  }
24
24
  export type TruePGConfig = Deunionise<PgConfig | UriConfig | ConfigConfig>;
25
- export declare const adapters: {
25
+ export declare const generators: {
26
26
  kysely: import("./types.ts").createGenerator;
27
27
  zod: import("./types.ts").createGenerator;
28
28
  };
29
29
  export declare function config(opts: TruePGConfig): {
30
30
  out: string;
31
- adapters: ("kysely" | "zod")[];
31
+ generators: ("kysely" | "zod")[];
32
32
  defaultSchema: string;
33
33
  pg: ExtractorConfig["pg"];
34
34
  uri?: undefined;
35
35
  config?: undefined;
36
36
  } | {
37
37
  out: string;
38
- adapters: ("kysely" | "zod")[];
38
+ generators: ("kysely" | "zod")[];
39
39
  defaultSchema: string;
40
40
  uri: ExtractorConfig["uri"];
41
41
  pg?: undefined;
42
42
  config?: undefined;
43
43
  } | {
44
44
  out: string;
45
- adapters: ("kysely" | "zod")[];
45
+ generators: ("kysely" | "zod")[];
46
46
  defaultSchema: string;
47
47
  config: ExtractorConfig["config"];
48
48
  pg?: undefined;
package/lib/config.js CHANGED
@@ -2,19 +2,19 @@ import { normalize as normalise } from "node:path";
2
2
  import {} from "./util.js";
3
3
  import { Kysely } from "./kysely/index.js";
4
4
  import { Zod } from "./zod/index.js";
5
- export const adapters = {
5
+ export const generators = {
6
6
  kysely: Kysely,
7
7
  zod: Zod,
8
8
  };
9
- const availableAdapters = Object.keys(adapters);
9
+ const availableGenerators = Object.keys(generators);
10
10
  export function config(opts) {
11
11
  const out = normalise(opts.out || "./models");
12
- const adapters = opts.adapters || ["kysely"];
12
+ const generators = opts.generators || ["kysely"];
13
13
  const defaultSchema = opts.defaultSchema || "public";
14
- for (const adapter of opts.adapters ?? []) {
15
- if (!availableAdapters.includes(adapter)) {
16
- console.error('Requested adapter "%s" not found.', adapter);
17
- console.error("Available adapters: %s", availableAdapters.join(", "));
14
+ for (const generator of opts.generators ?? []) {
15
+ if (!availableGenerators.includes(generator)) {
16
+ console.error('Requested generator "%s" not found.', generator);
17
+ console.error("Available generators: %s", availableGenerators.join(", "));
18
18
  console.error("See documentation for more information.");
19
19
  process.exit(1);
20
20
  }
@@ -26,7 +26,7 @@ export function config(opts) {
26
26
  return {
27
27
  ...opts,
28
28
  out,
29
- adapters,
29
+ generators,
30
30
  defaultSchema,
31
31
  };
32
32
  }
@@ -9,6 +9,7 @@ import { type FunctionDetails } from "./kinds/function.ts";
9
9
  import { type DomainDetails } from "./kinds/domain.ts";
10
10
  import { type RangeDetails } from "./kinds/range.ts";
11
11
  import type { PgType } from "./pgtype.ts";
12
+ export { pgTypeKinds, type PgType, type Kind } from "./pgtype.ts";
12
13
  import { Canonical } from "./canonicalise.ts";
13
14
  export { Canonical };
14
15
  export type { TableDetails, ViewDetails, MaterializedViewDetails, EnumDetails, CompositeTypeDetails, FunctionDetails, DomainDetails, RangeDetails, };
@@ -23,14 +24,14 @@ export { FunctionReturnTypeKind } from "./kinds/function.ts";
23
24
  */
24
25
  export type Schema = {
25
26
  name: string;
26
- tables: TableDetails[];
27
- views: ViewDetails[];
28
- materializedViews: MaterializedViewDetails[];
29
- enums: EnumDetails[];
30
- composites: CompositeTypeDetails[];
31
- functions: FunctionDetails[];
32
- domains: DomainDetails[];
33
- ranges: RangeDetails[];
27
+ table: TableDetails[];
28
+ view: ViewDetails[];
29
+ materializedView: MaterializedViewDetails[];
30
+ enum: EnumDetails[];
31
+ composite: CompositeTypeDetails[];
32
+ function: FunctionDetails[];
33
+ domain: DomainDetails[];
34
+ range: RangeDetails[];
34
35
  };
35
36
  export type SchemaType = TableDetails | ViewDetails | MaterializedViewDetails | EnumDetails | CompositeTypeDetails | FunctionDetails | DomainDetails | RangeDetails;
36
37
  /**
@@ -10,18 +10,19 @@ import extractFunction, {} from "./kinds/function.js";
10
10
  import extractDomain, {} from "./kinds/domain.js";
11
11
  import extractRange, {} from "./kinds/range.js";
12
12
  import fetchTypes from "./fetchTypes.js";
13
+ export { pgTypeKinds } from "./pgtype.js";
13
14
  import { canonicalise, Canonical } from "./canonicalise.js";
14
15
  export { Canonical };
15
16
  export { FunctionReturnTypeKind } from "./kinds/function.js";
16
17
  const emptySchema = {
17
- tables: [],
18
- views: [],
19
- materializedViews: [],
20
- enums: [],
21
- composites: [],
22
- functions: [],
23
- domains: [],
24
- ranges: [],
18
+ table: [],
19
+ view: [],
20
+ materializedView: [],
21
+ enum: [],
22
+ composite: [],
23
+ function: [],
24
+ domain: [],
25
+ range: [],
25
26
  };
26
27
  const populatorMap = {
27
28
  table: extractTable,
@@ -133,10 +134,7 @@ export class Extractor {
133
134
  ...emptySchema,
134
135
  };
135
136
  }
136
- schemas[p.schemaName][`${p.kind}s`] = [
137
- ...schemas[p.schemaName][`${p.kind}s`],
138
- p,
139
- ];
137
+ schemas[p.schemaName][p.kind] = [...schemas[p.schemaName][p.kind], p];
140
138
  }
141
139
  const result =
142
140
  // options?.resolveViews
@@ -1,9 +1,9 @@
1
1
  export declare const typeKindMap: {
2
- readonly d: "domain";
3
2
  readonly e: "enum";
3
+ readonly d: "domain";
4
4
  readonly r: "range";
5
5
  };
6
- type TypeKind = (typeof typeKindMap)[keyof typeof typeKindMap];
6
+ export type TypeKind = (typeof typeKindMap)[keyof typeof typeKindMap];
7
7
  export declare const classKindMap: {
8
8
  readonly r: "table";
9
9
  readonly p: "table";
@@ -11,12 +11,13 @@ export declare const classKindMap: {
11
11
  readonly m: "materializedView";
12
12
  readonly c: "composite";
13
13
  };
14
- type ClassKind = (typeof classKindMap)[keyof typeof classKindMap];
14
+ export type ClassKind = (typeof classKindMap)[keyof typeof classKindMap];
15
15
  export declare const routineKindMap: {
16
16
  readonly f: "function";
17
17
  };
18
- type RoutineKind = (typeof routineKindMap)[keyof typeof routineKindMap];
19
- export type Kind = TypeKind | ClassKind | RoutineKind;
18
+ export type RoutineKind = (typeof routineKindMap)[keyof typeof routineKindMap];
19
+ export declare const pgTypeKinds: ("function" | "enum" | "domain" | "range" | "table" | "view" | "materializedView" | "composite")[];
20
+ export type Kind = (typeof pgTypeKinds)[number];
20
21
  /**
21
22
  * Base type for Postgres objects.
22
23
  */
@@ -38,4 +39,3 @@ export type PgType<K extends Kind = Kind> = {
38
39
  */
39
40
  comment: string | null;
40
41
  };
41
- export {};
@@ -1,6 +1,6 @@
1
1
  export const typeKindMap = {
2
- d: "domain",
3
2
  e: "enum",
3
+ d: "domain",
4
4
  r: "range",
5
5
  // Not supported (yet):
6
6
  // m: 'multiRange',
@@ -28,3 +28,9 @@ export const routineKindMap = {
28
28
  // a: 'aggregate',
29
29
  // w: 'windowFunction',
30
30
  };
31
+ const unique = (arr) => [...new Set(arr)];
32
+ export const pgTypeKinds = unique([
33
+ ...Object.values(classKindMap),
34
+ ...Object.values(typeKindMap),
35
+ ...Object.values(routineKindMap),
36
+ ]);
@@ -0,0 +1,41 @@
1
+ import type { Canonical } from "./extractor/index.ts";
2
+ import type { FunctionReturnType } from "./extractor/index.ts";
3
+ import type { allowed_kind_names, FolderStructure } from "./types.ts";
4
+ export interface ImportIdentifier {
5
+ name: string;
6
+ alias?: string;
7
+ typeOnly?: boolean;
8
+ }
9
+ type Supported<T> = {
10
+ [key in allowed_kind_names]: T extends {
11
+ kind: key;
12
+ } ? T : never;
13
+ }[allowed_kind_names];
14
+ export declare class Import {
15
+ from: string | ((files: FolderStructure) => string);
16
+ namedImports?: (string | ImportIdentifier)[];
17
+ star?: string;
18
+ default?: string;
19
+ typeOnly?: boolean;
20
+ constructor(args: {
21
+ from: string | ((files: FolderStructure) => string);
22
+ namedImports?: (string | ImportIdentifier)[];
23
+ star?: string;
24
+ default?: string;
25
+ typeOnly?: boolean;
26
+ });
27
+ static fromInternal(opts: {
28
+ source: string;
29
+ type: Supported<Canonical | FunctionReturnType.ExistingTable>;
30
+ withName?: string;
31
+ typeOnly?: boolean;
32
+ }): Import;
33
+ }
34
+ export declare class ImportList {
35
+ imports: Import[];
36
+ constructor(imports?: Import[]);
37
+ static merge(lists: ImportList[]): ImportList;
38
+ add(item: Import): void;
39
+ stringify(files: FolderStructure): string;
40
+ }
41
+ export {};
package/lib/imports.js ADDED
@@ -0,0 +1,102 @@
1
+ import { dirname, relative } from "node:path";
2
+ import { eq } from "./util.js";
3
+ export class Import {
4
+ from;
5
+ namedImports;
6
+ star;
7
+ default;
8
+ typeOnly;
9
+ constructor(args) {
10
+ this.from = args.from;
11
+ this.namedImports = args.namedImports;
12
+ this.star = args.star;
13
+ this.default = args.default;
14
+ this.typeOnly = args.typeOnly ?? false;
15
+ }
16
+ static fromInternal(opts) {
17
+ const t = opts.type;
18
+ return new Import({
19
+ from: files => {
20
+ const schema = files.children[t.schema];
21
+ const kind = schema.children[t.kind];
22
+ const type = kind.children[t.name];
23
+ const path = `${files.name}/${schema.name}/${kind.kind}s/${type.name}.ts`;
24
+ return relative(dirname(opts.source), path);
25
+ },
26
+ namedImports: [opts.withName ?? t.name],
27
+ typeOnly: opts.typeOnly,
28
+ });
29
+ }
30
+ }
31
+ export class ImportList {
32
+ imports;
33
+ constructor(imports = []) {
34
+ this.imports = imports;
35
+ }
36
+ static merge(lists) {
37
+ return new ImportList(lists.flatMap(l => l.imports));
38
+ }
39
+ add(item) {
40
+ this.imports.push(item);
41
+ }
42
+ stringify(files) {
43
+ const modulegroups = {};
44
+ for (const item of this.imports) {
45
+ const from = typeof item.from === "function" ? item.from(files) : item.from;
46
+ const group = modulegroups[from];
47
+ if (group)
48
+ group.push(item);
49
+ else
50
+ modulegroups[from] = [item];
51
+ }
52
+ const imports = [];
53
+ const modules = Object.keys(modulegroups).sort((a, b) => {
54
+ const dotA = a.startsWith(".");
55
+ const dotB = b.startsWith(".");
56
+ // we could do localeCompare instead of 0, but 0 maintains order of fields for imports
57
+ return dotA === dotB ? 0 : dotA ? 1 : -1;
58
+ });
59
+ let broke = false;
60
+ for (const from of modules) {
61
+ if (!broke && from.startsWith(".")) {
62
+ imports.push("");
63
+ broke = true;
64
+ }
65
+ const items = modulegroups[from];
66
+ // unique named imports from this module
67
+ const namedImports = items
68
+ .flatMap(s => s.namedImports?.map(i => (typeof i === "string" ? { name: i, typeOnly: s.typeOnly } : i)) ?? [])
69
+ .filter((imp, index, arr) => {
70
+ if (arr.findIndex(i => eq(i, imp)) !== index)
71
+ return false;
72
+ return true;
73
+ });
74
+ const allTypeOnly = namedImports.every(i => i.typeOnly);
75
+ const namedImportPart = namedImports
76
+ .map(i => (!allTypeOnly && i.typeOnly ? "type " : "") + i.name)
77
+ .join(", ");
78
+ const namedImportLine = namedImportPart
79
+ ? `import ${allTypeOnly ? "type " : ""}{ ${namedImportPart} } from "${from}";`
80
+ : undefined;
81
+ // all star imports from this module
82
+ const stars = items.filter(i => i.star).filter((i, index, arr) => arr.findIndex(j => eq(j, i)) === index);
83
+ const starImportLines = stars.map(i => `import ${i.typeOnly ? "type " : ""}* as ${i.star} from "${from}";`);
84
+ // all default imports from this module
85
+ const defaults = items
86
+ .filter(i => i.default)
87
+ .filter((i, index, arr) => arr.findIndex(j => eq(j, i)) === index);
88
+ const defaultImportLines = defaults.map(i => `import ${i.typeOnly ? "type " : ""}${i.default} from "${from}";`);
89
+ const sideEffectImports = items.find(i => !i.default && !i.star && !i.namedImports?.length);
90
+ const sideEffectImportLine = sideEffectImports ? `import "${sideEffectImports.from}";` : undefined;
91
+ if (sideEffectImportLine)
92
+ imports.push(sideEffectImportLine);
93
+ if (namedImportLine)
94
+ imports.push(namedImportLine);
95
+ if (starImportLines.length)
96
+ imports.push(...starImportLines);
97
+ if (defaultImportLines.length)
98
+ imports.push(...defaultImportLines);
99
+ }
100
+ return imports.join("\n");
101
+ }
102
+ }