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
@@ -1,6 +1,5 @@
|
|
1
|
-
import { DbAdapter } from "./adapter.ts";
|
2
1
|
export declare namespace Canonical {
|
3
|
-
|
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
|
-
|
20
|
+
interface Base extends Abstract {
|
22
21
|
kind: Kind.Base;
|
23
22
|
}
|
24
|
-
|
23
|
+
interface Enum extends Abstract {
|
25
24
|
kind: Kind.Enum;
|
26
25
|
enum_values: string[];
|
27
26
|
}
|
28
|
-
|
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
|
-
|
45
|
+
interface Composite extends Abstract {
|
47
46
|
kind: Kind.Composite;
|
48
47
|
attributes: CompositeAttribute[];
|
49
48
|
}
|
50
|
-
|
49
|
+
interface Domain extends Abstract {
|
51
50
|
kind: Kind.Domain;
|
52
51
|
domain_base_type: Canonical;
|
53
52
|
}
|
54
|
-
|
53
|
+
interface Range extends Abstract {
|
55
54
|
kind: Kind.Range;
|
56
55
|
range_subtype: Canonical;
|
57
56
|
}
|
58
|
-
|
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
|
-
|
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 = {}));
|
package/lib/extractor/index.d.ts
CHANGED
@@ -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
|
-
|
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<
|
95
|
+
extractSchemas(options?: ExtractSchemaOptions): Promise<{
|
96
|
+
schemas: Record<string, Schema>;
|
97
|
+
queryCount: number;
|
98
|
+
}>;
|
96
99
|
}
|
package/lib/extractor/index.js
CHANGED
@@ -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 {
|
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
|
-
|
140
|
-
|
141
|
-
|
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
|
141
|
+
return { schemas, queryCount };
|
147
142
|
}
|
148
143
|
}
|
@@ -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
|
5
|
+
const canonical = db.enqueue(fullTypeName);
|
7
6
|
// Return the composite type with its canonical representation
|
8
7
|
return {
|
9
8
|
...composite,
|
@@ -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
|
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 ?
|
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 =
|
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 = (
|
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:
|
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:
|
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
|
-
//
|
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
|
-
|
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:
|
28
|
+
type: db.enqueue(row.definedType),
|
33
29
|
isNullable: row.isNullable,
|
34
30
|
ordinalPosition: row.ordinalPosition,
|
35
31
|
comment: row.comment,
|
36
32
|
}));
|
37
|
-
//
|
33
|
+
// Query for materialized view definition, comment, and properties
|
38
34
|
const mviewInfoQuery = await db.query(`
|
39
35
|
SELECT
|
40
36
|
m.definition,
|
@@ -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
|
-
|
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(
|
109
|
+
const columns = columnsQuery.map(row => ({
|
115
110
|
name: row.name,
|
116
|
-
type:
|
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
|
-
//
|
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
|
-
|
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:
|
27
|
+
type: db.enqueue(row.definedType),
|
32
28
|
isNullable: row.isNullable,
|
33
29
|
isUpdatable: row.isUpdatable,
|
34
30
|
ordinalPosition: row.ordinalPosition,
|
35
31
|
}));
|
36
|
-
//
|
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" | "
|
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
|
22
|
-
const type = kind
|
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
|
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);
|