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
@@ -1,6 +1,5 @@
1
- import { DbAdapter } from "./adapter.ts";
2
1
  export declare namespace Canonical {
3
- export enum Kind {
2
+ enum Kind {
4
3
  Base = "base",
5
4
  Composite = "composite",
6
5
  Domain = "domain",
@@ -18,14 +17,14 @@ export declare namespace Canonical {
18
17
  dimensions: number;
19
18
  modifiers?: string | null;
20
19
  }
21
- export interface Base extends Abstract {
20
+ interface Base extends Abstract {
22
21
  kind: Kind.Base;
23
22
  }
24
- export interface Enum extends Abstract {
23
+ interface Enum extends Abstract {
25
24
  kind: Kind.Enum;
26
25
  enum_values: string[];
27
26
  }
28
- export interface CompositeAttribute {
27
+ interface CompositeAttribute {
29
28
  name: string;
30
29
  index: number;
31
30
  type: Canonical;
@@ -43,22 +42,29 @@ export declare namespace Canonical {
43
42
  */
44
43
  generated: "ALWAYS" | "NEVER" | "BY DEFAULT";
45
44
  }
46
- export interface Composite extends Abstract {
45
+ interface Composite extends Abstract {
47
46
  kind: Kind.Composite;
48
47
  attributes: CompositeAttribute[];
49
48
  }
50
- export interface Domain extends Abstract {
49
+ interface Domain extends Abstract {
51
50
  kind: Kind.Domain;
52
51
  domain_base_type: Canonical;
53
52
  }
54
- export interface Range extends Abstract {
53
+ interface Range extends Abstract {
55
54
  kind: Kind.Range;
56
55
  range_subtype: Canonical;
57
56
  }
58
- export interface Pseudo extends Abstract {
57
+ interface Pseudo extends Abstract {
59
58
  kind: Kind.Pseudo;
60
59
  }
61
- export {};
62
60
  }
63
61
  export type Canonical = Canonical.Base | Canonical.Enum | Canonical.Composite | Canonical.Domain | Canonical.Range | Canonical.Pseudo;
64
- export declare const canonicalise: (db: DbAdapter, types: string[]) => Promise<Canonical[]>;
62
+ type Exclusive<T> = Omit<T, Exclude<keyof Canonical.Abstract, "kind" | "canonical_name">>;
63
+ export type ExclusiveBase = Exclusive<Canonical.Base>;
64
+ export type ExclusiveEnum = Exclusive<Canonical.Enum>;
65
+ export type ExclusiveComposite = Exclusive<Canonical.Composite>;
66
+ export type ExclusiveDomain = Exclusive<Canonical.Domain>;
67
+ export type ExclusiveRange = Exclusive<Canonical.Range>;
68
+ export type ExclusivePseudo = Exclusive<Canonical.Pseudo>;
69
+ export type ExclusiveCanonProps = ExclusiveBase | ExclusiveEnum | ExclusiveComposite | ExclusiveDomain | ExclusiveRange | ExclusivePseudo;
70
+ export {};
@@ -0,0 +1,13 @@
1
+ export var Canonical;
2
+ (function (Canonical) {
3
+ let Kind;
4
+ (function (Kind) {
5
+ Kind["Base"] = "base";
6
+ Kind["Composite"] = "composite";
7
+ Kind["Domain"] = "domain";
8
+ Kind["Enum"] = "enum";
9
+ Kind["Range"] = "range";
10
+ Kind["Pseudo"] = "pseudo";
11
+ Kind["Unknown"] = "unknown";
12
+ })(Kind = Canonical.Kind || (Canonical.Kind = {}));
13
+ })(Canonical || (Canonical = {}));
@@ -1,5 +1,6 @@
1
1
  import Pg from "pg";
2
2
  import { PGlite as Pglite } from "@electric-sql/pglite";
3
+ import { DbAdapter } from "./adapter.ts";
3
4
  import { type TableDetails } from "./kinds/table.ts";
4
5
  import { type ViewDetails } from "./kinds/view.ts";
5
6
  import { type MaterializedViewDetails } from "./kinds/materialized-view.ts";
@@ -10,7 +11,7 @@ import { type DomainDetails } from "./kinds/domain.ts";
10
11
  import { type RangeDetails } from "./kinds/range.ts";
11
12
  import type { PgType } from "./pgtype.ts";
12
13
  export { pgTypeKinds, type PgType, type Kind } from "./pgtype.ts";
13
- import { Canonical } from "./canonicalise.ts";
14
+ import { Canonical } from "./canonicalise/index.ts";
14
15
  export { Canonical };
15
16
  export type { TableDetails, ViewDetails, MaterializedViewDetails, EnumDetails, CompositeTypeDetails, FunctionDetails, DomainDetails, RangeDetails, };
16
17
  export type { TableColumn } from "./kinds/table.ts";
@@ -72,7 +73,7 @@ export interface ExtractSchemaOptions {
72
73
  onProgressEnd?: () => void;
73
74
  }
74
75
  export declare class Extractor {
75
- private db;
76
+ db: DbAdapter;
76
77
  /**
77
78
  * @param connectionConfig - Connection string or configuration object for Postgres connection
78
79
  */
@@ -81,7 +82,6 @@ export declare class Extractor {
81
82
  uri?: string;
82
83
  config?: Pg.ConnectionConfig;
83
84
  });
84
- canonicalise(types: string[]): Promise<Canonical[]>;
85
85
  getBuiltinTypes(): Promise<{
86
86
  name: string;
87
87
  format: string;
@@ -92,5 +92,8 @@ export declare class Extractor {
92
92
  * @param options - Optional options
93
93
  * @returns A record of all the schemas extracted, indexed by schema name.
94
94
  */
95
- extractSchemas(options?: ExtractSchemaOptions): Promise<Record<string, Schema>>;
95
+ extractSchemas(options?: ExtractSchemaOptions): Promise<{
96
+ schemas: Record<string, Schema>;
97
+ queryCount: number;
98
+ }>;
96
99
  }
@@ -11,7 +11,7 @@ import extractDomain, {} from "./kinds/domain.js";
11
11
  import extractRange, {} from "./kinds/range.js";
12
12
  import fetchTypes from "./fetchTypes.js";
13
13
  export { pgTypeKinds } from "./pgtype.js";
14
- import { canonicalise, Canonical } from "./canonicalise.js";
14
+ import { Canonical } from "./canonicalise/index.js";
15
15
  export { Canonical };
16
16
  export { FunctionReturnTypeKind } from "./kinds/function.js";
17
17
  const emptySchema = {
@@ -63,9 +63,6 @@ export class Extractor {
63
63
  }
64
64
  this.db = new DbAdapter(pg, opts.pg ? true : false);
65
65
  }
66
- async canonicalise(types) {
67
- return canonicalise(this.db, types);
68
- }
69
66
  async getBuiltinTypes() {
70
67
  await this.db.connect();
71
68
  const db = this.db;
@@ -136,13 +133,11 @@ export class Extractor {
136
133
  }
137
134
  schemas[p.schemaName][p.kind] = [...schemas[p.schemaName][p.kind], p];
138
135
  }
139
- const result =
140
- // options?.resolveViews
141
- // ? resolveViewColumns(schemas)
142
- // :
143
- schemas;
136
+ // resolve all canonical types and patch the results into their placeholders
137
+ await db.resolve();
138
+ const queryCount = db.queryCount;
144
139
  options?.onProgressEnd?.();
145
140
  await db.close();
146
- return result;
141
+ return { schemas, queryCount };
147
142
  }
148
143
  }
@@ -1,6 +1,6 @@
1
1
  import type { DbAdapter } from "../adapter.ts";
2
2
  import type { PgType } from "../pgtype.ts";
3
- import { Canonical } from "../canonicalise.ts";
3
+ import type { Canonical } from "../canonicalise/index.ts";
4
4
  /**
5
5
  * Composite type in a schema with details.
6
6
  */
@@ -1,9 +1,8 @@
1
- import { Canonical, canonicalise } from "../canonicalise.js";
2
1
  const extractComposite = async (db, composite) => {
3
2
  // Form the fully qualified type name
4
3
  const fullTypeName = `"${composite.schemaName}"."${composite.name}"`;
5
4
  // Get canonical type information with all the metadata
6
- const [canonical] = await canonicalise(db, [fullTypeName]);
5
+ const canonical = db.enqueue(fullTypeName);
7
6
  // Return the composite type with its canonical representation
8
7
  return {
9
8
  ...composite,
@@ -1,6 +1,6 @@
1
1
  import type { DbAdapter } from "../adapter.ts";
2
2
  import type { PgType } from "../pgtype.ts";
3
- import { Canonical } from "../canonicalise.ts";
3
+ import type { Canonical } from "../canonicalise/index.ts";
4
4
  /**
5
5
  * Domain type in a schema with details.
6
6
  */
@@ -1,9 +1,8 @@
1
- import { Canonical, canonicalise } from "../canonicalise.js";
2
1
  const extractDomain = async (db, domain) => {
3
2
  // Form the fully qualified type name
4
3
  const fullTypeName = `"${domain.schemaName}"."${domain.name}"`;
5
4
  // Get canonical type information with all the metadata
6
- const [canonical] = await canonicalise(db, [fullTypeName]);
5
+ const canonical = db.enqueue(fullTypeName);
7
6
  // Return the composite type with its canonical representation
8
7
  return {
9
8
  ...domain,
@@ -1,6 +1,6 @@
1
1
  import type { DbAdapter } from "../adapter.ts";
2
2
  import type { PgType } from "../pgtype.ts";
3
- import { Canonical } from "../canonicalise.ts";
3
+ import type { Canonical } from "../canonicalise/index.ts";
4
4
  declare const parameterModeMap: {
5
5
  readonly i: "IN";
6
6
  readonly o: "OUT";
@@ -1,4 +1,3 @@
1
- import { canonicalise, Canonical } from "../canonicalise.js";
2
1
  import { parsePostgresTableDefinition } from "./util/parseInlineTable.js";
3
2
  const parameterModeMap = {
4
3
  i: "IN",
@@ -75,13 +74,13 @@ async function extractFunction(db, pgType) {
75
74
  if (row.arg_names && !row.arg_modes)
76
75
  row.arg_modes = row.arg_names.map(() => "i");
77
76
  const argModes = row.arg_modes?.map(mode => parameterModeMap[mode]) ?? [];
78
- const canonical_arg_types = row.arg_types ? await canonicalise(db, row.arg_types) : [];
77
+ const canonical_arg_types = row.arg_types ? row.arg_types.map(type => db.enqueue(type)) : [];
79
78
  let returnType;
80
79
  const tableMatch = row.declared_return_type.match(/^TABLE\((.*)\)$/i);
81
80
  if (tableMatch) {
82
81
  const columnDefs = parsePostgresTableDefinition(row.declared_return_type);
83
82
  const columnTypes = columnDefs.map(col => col.type);
84
- const canonicalColumnTypes = await canonicalise(db, columnTypes);
83
+ const canonicalColumnTypes = columnTypes.map(type => db.enqueue(type));
85
84
  returnType = {
86
85
  kind: FunctionReturnTypeKind.InlineTable,
87
86
  columns: columnDefs.map((col, i) => ({
@@ -111,7 +110,7 @@ async function extractFunction(db, pgType) {
111
110
  else if (
112
111
  // "c" = composite type
113
112
  row.return_type_relation_kind === "c") {
114
- const canonicalReturnType = (await canonicalise(db, [row.return_type_string]))[0];
113
+ const canonicalReturnType = db.enqueue(row.return_type_string);
115
114
  returnType = {
116
115
  kind: FunctionReturnTypeKind.Regular,
117
116
  type: canonicalReturnType,
@@ -120,19 +119,17 @@ async function extractFunction(db, pgType) {
120
119
  }
121
120
  else {
122
121
  console.warn(`Composite return type '${row.return_type_string}' has unexpected relkind '${row.return_type_relation_kind}' for function ${pgType.schemaName}.${row.name}`);
123
- const canonicalReturnType = (await canonicalise(db, [row.return_type_string]))[0];
124
122
  returnType = {
125
123
  kind: FunctionReturnTypeKind.Regular,
126
- type: canonicalReturnType,
124
+ type: db.enqueue(row.return_type_string),
127
125
  isSet: row.returns_set,
128
126
  };
129
127
  }
130
128
  }
131
129
  else {
132
- const canonicalReturnType = (await canonicalise(db, [row.return_type_string]))[0];
133
130
  returnType = {
134
131
  kind: FunctionReturnTypeKind.Regular,
135
- type: canonicalReturnType,
132
+ type: db.enqueue(row.return_type_string),
136
133
  isSet: row.returns_set,
137
134
  };
138
135
  }
@@ -1,6 +1,6 @@
1
1
  import type { DbAdapter } from "../adapter.ts";
2
2
  import type { PgType } from "../pgtype.ts";
3
- import { Canonical } from "../canonicalise.ts";
3
+ import type { Canonical } from "../canonicalise/index.ts";
4
4
  export interface MaterializedViewColumn {
5
5
  name: string;
6
6
  type: Canonical;
@@ -1,6 +1,5 @@
1
- import { Canonical, canonicalise } from "../canonicalise.js";
2
1
  const extractMaterializedView = async (db, mview) => {
3
- // 1. Query for columns (using pg_attribute for potentially more accurate nullability)
2
+ // Query for columns (using pg_attribute for potentially more accurate nullability)
4
3
  const columnQuery = await db.query(`
5
4
  SELECT
6
5
  attr.attname AS "name",
@@ -24,17 +23,14 @@ const extractMaterializedView = async (db, mview) => {
24
23
  AND NOT attr.attisdropped
25
24
  ORDER BY attr.attnum;
26
25
  `, [mview.name, mview.schemaName]);
27
- // 2. Get canonical types
28
- const definedTypes = columnQuery.map(row => row.definedType);
29
- const canonicalTypes = await canonicalise(db, definedTypes);
30
- const columns = columnQuery.map((row, index) => ({
26
+ const columns = columnQuery.map(row => ({
31
27
  name: row.name,
32
- type: canonicalTypes[index],
28
+ type: db.enqueue(row.definedType),
33
29
  isNullable: row.isNullable,
34
30
  ordinalPosition: row.ordinalPosition,
35
31
  comment: row.comment,
36
32
  }));
37
- // 3. Query for materialized view definition, comment, and properties
33
+ // Query for materialized view definition, comment, and properties
38
34
  const mviewInfoQuery = await db.query(`
39
35
  SELECT
40
36
  m.definition,
@@ -1,6 +1,6 @@
1
1
  import type { DbAdapter } from "../adapter.ts";
2
2
  import type { PgType } from "../pgtype.ts";
3
- import { Canonical } from "../canonicalise.ts";
3
+ import type { Canonical } from "../canonicalise/index.ts";
4
4
  /**
5
5
  * Range type in a schema with details.
6
6
  */
@@ -1,9 +1,7 @@
1
- import { Canonical, canonicalise } from "../canonicalise.js";
2
1
  const extractRange = async (db, range) => {
3
2
  // Form the fully qualified type name
4
3
  const fullTypeName = `"${range.schemaName}"."${range.name}"`;
5
- // Get canonical type information with all the metadata
6
- const [canonical] = await canonicalise(db, [fullTypeName]);
4
+ const canonical = db.enqueue(fullTypeName);
7
5
  // Return the composite type with its canonical representation
8
6
  return {
9
7
  ...range,
@@ -1,6 +1,6 @@
1
1
  import { DbAdapter } from "../adapter.ts";
2
2
  import type { PgType } from "../pgtype.ts";
3
- import { Canonical } from "../canonicalise.ts";
3
+ import type { Canonical } from "../canonicalise/index.ts";
4
4
  export declare const updateActionMap: {
5
5
  readonly a: "NO ACTION";
6
6
  readonly r: "RESTRICT";
@@ -1,7 +1,6 @@
1
1
  import { DbAdapter } from "../adapter.js";
2
2
  import commentMapQueryPart from "./parts/commentMapQueryPart.js";
3
3
  import indexMapQueryPart from "./parts/indexMapQueryPart.js";
4
- import { Canonical, canonicalise } from "../canonicalise.js";
5
4
  export const updateActionMap = {
6
5
  a: "NO ACTION",
7
6
  r: "RESTRICT",
@@ -106,14 +105,10 @@ const extractTable = async (db, table) => {
106
105
  AND NOT attr.attisdropped
107
106
  ORDER BY col.ordinal_position;
108
107
  `, [table.name, table.schemaName]);
109
- // Get the expanded type names from the query result
110
- const definedTypes = columnsQuery.map(row => row.definedType);
111
- // Use canonicaliseTypes to get detailed type information
112
- const canonicalTypes = await canonicalise(db, definedTypes);
113
108
  // Combine the column information with the canonical type information
114
- const columns = columnsQuery.map((row, index) => ({
109
+ const columns = columnsQuery.map(row => ({
115
110
  name: row.name,
116
- type: canonicalTypes[index],
111
+ type: db.enqueue(row.definedType),
117
112
  comment: row.comment,
118
113
  defaultValue: row.defaultValue,
119
114
  isPrimaryKey: row.isPrimaryKey,
@@ -123,7 +118,6 @@ const extractTable = async (db, table) => {
123
118
  isIdentity: row.isIdentity,
124
119
  isUpdatable: row.isUpdatable,
125
120
  generated: row.generated,
126
- informationSchemaValue: row.informationSchemaValue,
127
121
  }));
128
122
  const indicesQuery = await db.query(`
129
123
  WITH index_columns AS (
@@ -1,6 +1,6 @@
1
1
  import type { DbAdapter } from "../adapter.ts";
2
2
  import type { PgType } from "../pgtype.ts";
3
- import { Canonical } from "../canonicalise.ts";
3
+ import type { Canonical } from "../canonicalise/index.ts";
4
4
  export interface ViewColumn {
5
5
  name: string;
6
6
  type: Canonical;
@@ -1,6 +1,5 @@
1
- import { Canonical, canonicalise } from "../canonicalise.js";
2
1
  const extractView = async (db, view) => {
3
- // 1. Query for columns (information_schema.columns + pg_attribute)
2
+ // Query for columns (information_schema.columns + pg_attribute)
4
3
  const columnQuery = await db.query(`
5
4
  SELECT
6
5
  col.column_name AS "name",
@@ -23,17 +22,14 @@ const extractView = async (db, view) => {
23
22
  AND NOT attr.attisdropped -- Exclude dropped columns
24
23
  ORDER BY col.ordinal_position;
25
24
  `, [view.name, view.schemaName]);
26
- // 2. Get canonical types
27
- const definedTypes = columnQuery.map(row => row.definedType);
28
- const canonicalTypes = await canonicalise(db, definedTypes);
29
- const columns = columnQuery.map((row, index) => ({
25
+ const columns = columnQuery.map(row => ({
30
26
  name: row.name,
31
- type: canonicalTypes[index],
27
+ type: db.enqueue(row.definedType),
32
28
  isNullable: row.isNullable,
33
29
  isUpdatable: row.isUpdatable,
34
30
  ordinalPosition: row.ordinalPosition,
35
31
  }));
36
- // 3. Query for view definition, comment, and other properties
32
+ // Query for view definition, comment, and other properties
37
33
  const viewInfoQuery = await db.query(`
38
34
  SELECT
39
35
  d.description AS "comment",
@@ -16,7 +16,7 @@ export declare const routineKindMap: {
16
16
  readonly f: "function";
17
17
  };
18
18
  export type RoutineKind = (typeof routineKindMap)[keyof typeof routineKindMap];
19
- export declare const pgTypeKinds: ("function" | "enum" | "domain" | "range" | "table" | "view" | "materializedView" | "composite")[];
19
+ export declare const pgTypeKinds: ("function" | "composite" | "domain" | "enum" | "range" | "table" | "view" | "materializedView")[];
20
20
  export type Kind = (typeof pgTypeKinds)[number];
21
21
  /**
22
22
  * Base type for Postgres objects.
package/lib/imports.js CHANGED
@@ -1,4 +1,4 @@
1
- import { dirname, relative } from "node:path";
1
+ import { dirname, relative } from "node:path/posix";
2
2
  import { eq } from "./util.js";
3
3
  export class Import {
4
4
  from;
@@ -18,8 +18,11 @@ export class Import {
18
18
  return new Import({
19
19
  from: files => {
20
20
  const schema = files.children[t.schema];
21
- const kind = schema.children[t.kind];
22
- const type = kind.children[t.name];
21
+ const kind = schema?.children[t.kind];
22
+ const type = kind?.children[t.name];
23
+ if (!schema || !kind || !type) {
24
+ throw new Error(`Type ${t.kind}/${t.name} not found in schema "${t.schema}"`);
25
+ }
23
26
  const path = `${files.name}/${schema.name}/${kind.kind}s/${type.name}.ts`;
24
27
  return relative(dirname(opts.source), path);
25
28
  },
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,180 @@
1
+ import { Canonical } from "./extractor/canonicalise/index.js";
2
+ import { Import, ImportList } from "./imports.js";
3
+ import { describe, it, expect } from "bun:test";
4
+ const files = {
5
+ name: "root",
6
+ type: "root",
7
+ children: {
8
+ public: {
9
+ name: "public",
10
+ type: "schema",
11
+ children: {
12
+ function: {
13
+ kind: "function",
14
+ type: "kind",
15
+ children: {},
16
+ },
17
+ view: {
18
+ kind: "view",
19
+ type: "kind",
20
+ children: {},
21
+ },
22
+ materializedView: {
23
+ kind: "materializedView",
24
+ type: "kind",
25
+ children: {},
26
+ },
27
+ composite: {
28
+ kind: "composite",
29
+ type: "kind",
30
+ children: {},
31
+ },
32
+ table: {
33
+ kind: "table",
34
+ type: "kind",
35
+ children: {
36
+ testTable: {
37
+ name: "testTable",
38
+ type: "type",
39
+ },
40
+ },
41
+ },
42
+ range: {
43
+ kind: "range",
44
+ type: "kind",
45
+ children: {},
46
+ },
47
+ enum: {
48
+ kind: "enum",
49
+ type: "kind",
50
+ children: {
51
+ testEnum: {
52
+ name: "testEnum",
53
+ type: "type",
54
+ },
55
+ },
56
+ },
57
+ domain: {
58
+ kind: "domain",
59
+ type: "kind",
60
+ children: {},
61
+ },
62
+ },
63
+ },
64
+ },
65
+ };
66
+ describe("Import Class", () => {
67
+ it("should initialize correctly with constructor arguments", () => {
68
+ const importInstance = new Import({
69
+ from: "module/path",
70
+ namedImports: ["component"],
71
+ star: "starImport",
72
+ default: "defaultImport",
73
+ typeOnly: true,
74
+ });
75
+ expect(importInstance.from).toBe("module/path");
76
+ expect(importInstance.namedImports).toEqual(["component"]);
77
+ expect(importInstance.star).toBe("starImport");
78
+ expect(importInstance.default).toBe("defaultImport");
79
+ expect(importInstance.typeOnly).toBe(true);
80
+ });
81
+ it("should create import fromInternal method correctly", () => {
82
+ const importInstance = Import.fromInternal({
83
+ source: "root/public/testEnum/testEnum.ts",
84
+ type: {
85
+ schema: "public",
86
+ kind: Canonical.Kind.Enum,
87
+ name: "testEnum",
88
+ canonical_name: "test",
89
+ dimensions: 1,
90
+ enum_values: ["test1", "test2", "test3"],
91
+ original_type: "string",
92
+ },
93
+ });
94
+ expect(importInstance).toBeInstanceOf(Import);
95
+ expect(typeof importInstance.from === "function" || typeof importInstance.from === "string").toBe(true);
96
+ if (typeof importInstance.from === "function") {
97
+ const generatedPath = importInstance.from(files);
98
+ expect(generatedPath).toBe("../enums/testEnum.ts");
99
+ }
100
+ });
101
+ it("should handle missing properties in fromInternal method", () => {
102
+ const importInstance = Import.fromInternal({
103
+ source: "root/public/testEnum/testEnum.ts",
104
+ type: {
105
+ schema: "public",
106
+ kind: Canonical.Kind.Enum,
107
+ name: "testEnum",
108
+ canonical_name: "test",
109
+ dimensions: 1,
110
+ enum_values: ["test1", "test2", "test3"],
111
+ original_type: "string",
112
+ },
113
+ });
114
+ expect(importInstance).toBeInstanceOf(Import);
115
+ expect(typeof importInstance.from === "function" || typeof importInstance.from === "string").toBe(true);
116
+ if (typeof importInstance.from === "function") {
117
+ const generatedPath = importInstance.from(files);
118
+ expect(generatedPath).toBe("../enums/testEnum.ts");
119
+ }
120
+ });
121
+ it("should handle missing `namedImports` or other constructor properties", () => {
122
+ const importInstance = new Import({ from: "module/path" });
123
+ expect(importInstance.from).toBe("module/path");
124
+ expect(importInstance.namedImports).toBeUndefined();
125
+ expect(importInstance.star).toBeUndefined();
126
+ expect(importInstance.default).toBeUndefined();
127
+ expect(importInstance.typeOnly).toBe(false);
128
+ });
129
+ });
130
+ describe("ImportList Class", () => {
131
+ it("should add imports correctly", () => {
132
+ const importList = new ImportList();
133
+ const newImport = new Import({
134
+ from: "module/path",
135
+ namedImports: ["MyComponent"],
136
+ });
137
+ importList.add(newImport);
138
+ expect(importList.imports).toHaveLength(1);
139
+ expect(importList.imports[0]).toBe(newImport);
140
+ });
141
+ it("should merge import lists correctly", () => {
142
+ const list1 = new ImportList([new Import({ from: "module1", namedImports: ["a"] })]);
143
+ const list2 = new ImportList([new Import({ from: "module2", namedImports: ["b"] })]);
144
+ const mergedList = ImportList.merge([list1, list2]);
145
+ expect(mergedList.imports).toHaveLength(2);
146
+ });
147
+ it("should merge import lists correctly with empty list", () => {
148
+ const list1 = new ImportList([new Import({ from: "module1", namedImports: ["a"] })]);
149
+ const list2 = new ImportList([]);
150
+ const mergedList = ImportList.merge([list1, list2]);
151
+ expect(mergedList.imports).toHaveLength(1);
152
+ });
153
+ it("should stringify imports correctly", () => {
154
+ const importList = new ImportList([
155
+ new Import({ from: "module1", namedImports: ["a"] }),
156
+ new Import({ from: "module1", namedImports: ["b"] }),
157
+ ]);
158
+ const result = importList.stringify(files);
159
+ expect(result).toContain('import { a, b } from "module1";');
160
+ });
161
+ it("should handle empty ImportList gracefully", () => {
162
+ const importList = new ImportList();
163
+ const files = {
164
+ name: "root",
165
+ type: "root",
166
+ children: {},
167
+ };
168
+ const result = importList.stringify(files);
169
+ expect(result).toBe("");
170
+ });
171
+ it("should handle duplicate imports and avoid repetition", () => {
172
+ const importList = new ImportList([
173
+ new Import({ from: "module1", namedImports: ["a"] }),
174
+ new Import({ from: "module1", namedImports: ["a"] }),
175
+ ]);
176
+ const result = importList.stringify(files);
177
+ expect(result).toBe('import { a } from "module1";');
178
+ expect(result.split("\n")).toBeArrayOfSize(1);
179
+ });
180
+ });
package/lib/index.js CHANGED
@@ -14,11 +14,27 @@ const yellow = (str) => (NO_COLOR ? str : `\x1b[33m${str}\x1b[0m`);
14
14
  const blue = (str) => (NO_COLOR ? str : `\x1b[34m${str}\x1b[0m`);
15
15
  const bold = (str) => (NO_COLOR ? str : `\x1b[1m${str}\x1b[0m`);
16
16
  const underline = (str) => (NO_COLOR ? str : `\x1b[4m${str}\x1b[0m`);
17
+ const formatTime = (time) => {
18
+ const mins = Math.floor(time / 60000);
19
+ const secs = Math.floor((time % 60000) / 1000);
20
+ const ms = Math.floor(time % 1000);
21
+ const us = Math.floor((time * 1000) % 1000)
22
+ .toString()
23
+ .padStart(3, "0");
24
+ const parts = [];
25
+ if (mins)
26
+ parts.push(mins + "m");
27
+ if (secs)
28
+ parts.push(secs + "s");
29
+ if (!mins)
30
+ parts.push(ms + (!secs && us ? "." + us : "") + "ms");
31
+ return parts.join("");
32
+ };
17
33
  const THRESHOLD1 = 800;
18
34
  const THRESHOLD2 = 1500;
19
35
  const time = (start, addParens = true) => {
20
36
  const diff = performance.now() - start;
21
- const diffstr = diff.toFixed(2) + "ms";
37
+ const diffstr = formatTime(diff);
22
38
  const str = addParens ? parens(diffstr) : diffstr;
23
39
  if (diff < THRESHOLD1)
24
40
  return green(str);
@@ -201,8 +217,8 @@ export async function generate(opts, generators) {
201
217
  const out = validated.out;
202
218
  const extractor = new Extractor(opts);
203
219
  const start = performance.now();
204
- const schemas = await extractor.extractSchemas();
205
- console.log("Extracted schemas %s\n", time(start));
220
+ const { schemas, queryCount } = await extractor.extractSchemas();
221
+ console.log("Extracted schemas %s (made %s queries)\n", time(start), queryCount);
206
222
  console.info("Generators enabled: %s\n", validated.generators.join(", "));
207
223
  generators = validated.generators.map(generator => builtin_generators[generator]).concat(generators ?? []);
208
224
  console.log("Clearing directory and generating schemas at '%s'\n", out);