@danielfgray/pg-sourcerer 0.2.1 → 0.2.2
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/dist/cli.js +3 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +14 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -1
- package/dist/generate.d.ts +5 -9
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +27 -29
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +19 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -13
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +39 -9
- package/dist/init.js.map +1 -1
- package/dist/ir/extensions/queries.d.ts +264 -0
- package/dist/ir/extensions/queries.d.ts.map +1 -0
- package/dist/ir/extensions/queries.js +153 -0
- package/dist/ir/extensions/queries.js.map +1 -0
- package/dist/ir/extensions/schema-builder.d.ts +61 -0
- package/dist/ir/extensions/schema-builder.d.ts.map +1 -0
- package/dist/ir/extensions/schema-builder.js +5 -0
- package/dist/ir/extensions/schema-builder.js.map +1 -0
- package/dist/lib/conjure.d.ts +66 -0
- package/dist/lib/conjure.d.ts.map +1 -1
- package/dist/lib/conjure.js +127 -29
- package/dist/lib/conjure.js.map +1 -1
- package/dist/lib/hex.d.ts +10 -3
- package/dist/lib/hex.d.ts.map +1 -1
- package/dist/lib/hex.js +18 -8
- package/dist/lib/hex.js.map +1 -1
- package/dist/plugins/arktype.d.ts +27 -14
- package/dist/plugins/arktype.d.ts.map +1 -1
- package/dist/plugins/arktype.js +166 -130
- package/dist/plugins/arktype.js.map +1 -1
- package/dist/plugins/effect.d.ts +53 -0
- package/dist/plugins/effect.d.ts.map +1 -0
- package/dist/plugins/effect.js +1074 -0
- package/dist/plugins/effect.js.map +1 -0
- package/dist/plugins/http-elysia.d.ts +32 -0
- package/dist/plugins/http-elysia.d.ts.map +1 -0
- package/dist/plugins/http-elysia.js +613 -0
- package/dist/plugins/http-elysia.js.map +1 -0
- package/dist/plugins/http-express.d.ts +36 -0
- package/dist/plugins/http-express.d.ts.map +1 -0
- package/dist/plugins/http-express.js +388 -0
- package/dist/plugins/http-express.js.map +1 -0
- package/dist/plugins/http-hono.d.ts +36 -0
- package/dist/plugins/http-hono.d.ts.map +1 -0
- package/dist/plugins/http-hono.js +453 -0
- package/dist/plugins/http-hono.js.map +1 -0
- package/dist/plugins/http-orpc.d.ts +55 -0
- package/dist/plugins/http-orpc.d.ts.map +1 -0
- package/dist/plugins/http-orpc.js +370 -0
- package/dist/plugins/http-orpc.js.map +1 -0
- package/dist/plugins/http-trpc.d.ts +59 -0
- package/dist/plugins/http-trpc.d.ts.map +1 -0
- package/dist/plugins/http-trpc.js +392 -0
- package/dist/plugins/http-trpc.js.map +1 -0
- package/dist/plugins/kysely/queries.d.ts +92 -0
- package/dist/plugins/kysely/queries.d.ts.map +1 -0
- package/dist/plugins/kysely/queries.js +1169 -0
- package/dist/plugins/kysely/queries.js.map +1 -0
- package/dist/plugins/kysely/shared.d.ts +59 -0
- package/dist/plugins/kysely/shared.d.ts.map +1 -0
- package/dist/plugins/kysely/shared.js +247 -0
- package/dist/plugins/kysely/shared.js.map +1 -0
- package/dist/plugins/kysely/types.d.ts +22 -0
- package/dist/plugins/kysely/types.d.ts.map +1 -0
- package/dist/plugins/kysely/types.js +428 -0
- package/dist/plugins/kysely/types.js.map +1 -0
- package/dist/plugins/kysely.d.ts +72 -0
- package/dist/plugins/kysely.d.ts.map +1 -0
- package/dist/plugins/kysely.js +906 -0
- package/dist/plugins/kysely.js.map +1 -0
- package/dist/plugins/sql-queries.d.ts +55 -11
- package/dist/plugins/sql-queries.d.ts.map +1 -1
- package/dist/plugins/sql-queries.js +467 -218
- package/dist/plugins/sql-queries.js.map +1 -1
- package/dist/plugins/types.d.ts +20 -14
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/plugins/types.js +90 -112
- package/dist/plugins/types.js.map +1 -1
- package/dist/plugins/valibot.d.ts +45 -0
- package/dist/plugins/valibot.d.ts.map +1 -0
- package/dist/plugins/valibot.js +422 -0
- package/dist/plugins/valibot.js.map +1 -0
- package/dist/plugins/zod.d.ts +27 -14
- package/dist/plugins/zod.d.ts.map +1 -1
- package/dist/plugins/zod.js +231 -166
- package/dist/plugins/zod.js.map +1 -1
- package/dist/services/artifact-store.d.ts +11 -1
- package/dist/services/artifact-store.d.ts.map +1 -1
- package/dist/services/artifact-store.js +9 -0
- package/dist/services/artifact-store.js.map +1 -1
- package/dist/services/core-providers.d.ts +15 -0
- package/dist/services/core-providers.d.ts.map +1 -0
- package/dist/services/core-providers.js +23 -0
- package/dist/services/core-providers.js.map +1 -0
- package/dist/services/emissions.d.ts +14 -0
- package/dist/services/emissions.d.ts.map +1 -1
- package/dist/services/emissions.js +86 -47
- package/dist/services/emissions.js.map +1 -1
- package/dist/services/execution.d.ts +35 -0
- package/dist/services/execution.d.ts.map +1 -0
- package/dist/services/execution.js +86 -0
- package/dist/services/execution.js.map +1 -0
- package/dist/services/file-builder.d.ts +4 -0
- package/dist/services/file-builder.d.ts.map +1 -1
- package/dist/services/file-builder.js.map +1 -1
- package/dist/services/inflection.d.ts +2 -2
- package/dist/services/inflection.d.ts.map +1 -1
- package/dist/services/inflection.js +4 -4
- package/dist/services/inflection.js.map +1 -1
- package/dist/services/ir-builder.d.ts.map +1 -1
- package/dist/services/ir-builder.js +10 -3
- package/dist/services/ir-builder.js.map +1 -1
- package/dist/services/pg-types.d.ts +31 -0
- package/dist/services/pg-types.d.ts.map +1 -1
- package/dist/services/pg-types.js +24 -0
- package/dist/services/pg-types.js.map +1 -1
- package/dist/services/plugin-runner.d.ts +27 -37
- package/dist/services/plugin-runner.d.ts.map +1 -1
- package/dist/services/plugin-runner.js +73 -171
- package/dist/services/plugin-runner.js.map +1 -1
- package/dist/services/plugin.d.ts +349 -217
- package/dist/services/plugin.d.ts.map +1 -1
- package/dist/services/plugin.js +182 -130
- package/dist/services/plugin.js.map +1 -1
- package/dist/services/resolution.d.ts +38 -0
- package/dist/services/resolution.d.ts.map +1 -0
- package/dist/services/resolution.js +242 -0
- package/dist/services/resolution.js.map +1 -0
- package/dist/services/service-registry.d.ts +74 -0
- package/dist/services/service-registry.d.ts.map +1 -0
- package/dist/services/service-registry.js +61 -0
- package/dist/services/service-registry.js.map +1 -0
- package/dist/services/symbols.d.ts +59 -0
- package/dist/services/symbols.d.ts.map +1 -1
- package/dist/services/symbols.js +16 -0
- package/dist/services/symbols.js.map +1 -1
- package/dist/testing.d.ts +4 -25
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -23
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/plugins/effect-model.d.ts +0 -17
- package/dist/plugins/effect-model.d.ts.map +0 -1
- package/dist/plugins/effect-model.js +0 -409
- package/dist/plugins/effect-model.js.map +0 -1
- package/dist/plugins/kysely-queries.d.ts +0 -66
- package/dist/plugins/kysely-queries.d.ts.map +0 -1
- package/dist/plugins/kysely-queries.js +0 -951
- package/dist/plugins/kysely-queries.js.map +0 -1
- package/dist/plugins/kysely-types.d.ts +0 -35
- package/dist/plugins/kysely-types.d.ts.map +0 -1
- package/dist/plugins/kysely-types.js +0 -601
- package/dist/plugins/kysely-types.js.map +0 -1
|
@@ -0,0 +1,906 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kysely Plugin - Generate Kysely-compatible types and query functions
|
|
3
|
+
*
|
|
4
|
+
* Consolidates types and queries generation into a single plugin with unified config.
|
|
5
|
+
* Generates:
|
|
6
|
+
* - Types: DB interface, table interfaces with Generated<T> wrappers, enum types
|
|
7
|
+
* - Queries: CRUD functions, index lookups, stored function wrappers
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { kysely } from "pg-sourcerer"
|
|
12
|
+
*
|
|
13
|
+
* export default defineConfig({
|
|
14
|
+
* plugins: [
|
|
15
|
+
* kysely({
|
|
16
|
+
* outputDir: "db",
|
|
17
|
+
* generateQueries: true,
|
|
18
|
+
* }),
|
|
19
|
+
* ],
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { Option, pipe, Schema as S } from "effect";
|
|
24
|
+
import { definePlugin } from "../services/plugin.js";
|
|
25
|
+
import { findEnumByPgName, findCompositeByPgName, PgTypeOid } from "../services/pg-types.js";
|
|
26
|
+
import { getEnumEntities, getTableEntities, getCompositeEntities, } from "../ir/semantic-ir.js";
|
|
27
|
+
import { conjure, cast } from "../lib/conjure.js";
|
|
28
|
+
import { resolveFieldType, tsTypeToAst } from "../lib/field-utils.js";
|
|
29
|
+
import { inflect } from "../services/inflection.js";
|
|
30
|
+
import { isEnumType, getPgTypeName } from "../lib/field-utils.js";
|
|
31
|
+
import { getExtensionTypeMapping } from "../services/pg-types.js";
|
|
32
|
+
const { ts, exp, b, param, str } = conjure;
|
|
33
|
+
const { toExpr } = cast;
|
|
34
|
+
const KyselyConfigSchema = S.Struct({
|
|
35
|
+
outputDir: S.optionalWith(S.String, { default: () => "db" }),
|
|
36
|
+
dbTypeName: S.optionalWith(S.String, { default: () => "DB" }),
|
|
37
|
+
camelCase: S.optionalWith(S.Boolean, { default: () => true }),
|
|
38
|
+
runtimeEnums: S.optionalWith(S.Boolean, { default: () => false }),
|
|
39
|
+
typeOnlyImports: S.optionalWith(S.Boolean, { default: () => true }),
|
|
40
|
+
generateQueries: S.optionalWith(S.Boolean, { default: () => true }),
|
|
41
|
+
dbTypesPath: S.optionalWith(S.String, { default: () => "./db.js" }),
|
|
42
|
+
executeQueries: S.optionalWith(S.Boolean, { default: () => true }),
|
|
43
|
+
generateListMany: S.optionalWith(S.Boolean, { default: () => false }),
|
|
44
|
+
generateFunctions: S.optionalWith(S.Boolean, { default: () => true }),
|
|
45
|
+
functionsFile: S.optionalWith(S.String, { default: () => "functions.ts" }),
|
|
46
|
+
exportName: S.optional(S.Any),
|
|
47
|
+
exportStyle: S.optionalWith(S.Literal("flat", "namespace"), { default: () => "flat" }),
|
|
48
|
+
explicitColumns: S.optionalWith(S.Boolean, { default: () => true }),
|
|
49
|
+
dbAsParameter: S.optionalWith(S.Boolean, { default: () => true }),
|
|
50
|
+
header: S.optional(S.String),
|
|
51
|
+
defaultLimit: S.optionalWith(S.Number, { default: () => 50 }),
|
|
52
|
+
});
|
|
53
|
+
const GENERATED_TYPE_DEF = `T extends ColumnType<infer S, infer I, infer U>
|
|
54
|
+
? ColumnType<S, I | undefined, U>
|
|
55
|
+
: ColumnType<T, T | undefined, T>`;
|
|
56
|
+
const ARRAY_TYPE_DEF = `ArrayTypeImpl<T> extends (infer U)[]
|
|
57
|
+
? U[]
|
|
58
|
+
: ArrayTypeImpl<T>`;
|
|
59
|
+
const ARRAY_TYPE_IMPL_DEF = `T extends ColumnType<infer S, infer I, infer U>
|
|
60
|
+
? ColumnType<S[], I[], U[]>
|
|
61
|
+
: T[]`;
|
|
62
|
+
const SCALAR_TYPES = {
|
|
63
|
+
bool: ts.boolean,
|
|
64
|
+
int2: ts.number,
|
|
65
|
+
int4: ts.number,
|
|
66
|
+
float4: ts.number,
|
|
67
|
+
float8: ts.number,
|
|
68
|
+
oid: ts.number,
|
|
69
|
+
text: ts.string,
|
|
70
|
+
varchar: ts.string,
|
|
71
|
+
bpchar: ts.string,
|
|
72
|
+
char: ts.string,
|
|
73
|
+
name: ts.string,
|
|
74
|
+
bit: ts.string,
|
|
75
|
+
varbit: ts.string,
|
|
76
|
+
xml: ts.string,
|
|
77
|
+
uuid: ts.string,
|
|
78
|
+
inet: ts.string,
|
|
79
|
+
cidr: ts.string,
|
|
80
|
+
macaddr: ts.string,
|
|
81
|
+
macaddr8: ts.string,
|
|
82
|
+
line: ts.string,
|
|
83
|
+
lseg: ts.string,
|
|
84
|
+
box: ts.string,
|
|
85
|
+
path: ts.string,
|
|
86
|
+
polygon: ts.string,
|
|
87
|
+
time: ts.string,
|
|
88
|
+
timetz: ts.string,
|
|
89
|
+
tsvector: ts.string,
|
|
90
|
+
tsquery: ts.string,
|
|
91
|
+
txid_snapshot: ts.string,
|
|
92
|
+
money: ts.string,
|
|
93
|
+
bytea: () => ts.ref("Buffer"),
|
|
94
|
+
};
|
|
95
|
+
const COMPLEX_TYPES = {
|
|
96
|
+
int8: {
|
|
97
|
+
select: ts.string,
|
|
98
|
+
insert: () => ts.union(ts.string(), ts.number(), ts.bigint()),
|
|
99
|
+
update: () => ts.union(ts.string(), ts.number(), ts.bigint()),
|
|
100
|
+
},
|
|
101
|
+
numeric: {
|
|
102
|
+
select: ts.string,
|
|
103
|
+
insert: () => ts.union(ts.number(), ts.string()),
|
|
104
|
+
update: () => ts.union(ts.number(), ts.string()),
|
|
105
|
+
},
|
|
106
|
+
date: {
|
|
107
|
+
select: () => ts.ref("Date"),
|
|
108
|
+
insert: () => ts.union(ts.ref("Date"), ts.string()),
|
|
109
|
+
update: () => ts.union(ts.ref("Date"), ts.string()),
|
|
110
|
+
},
|
|
111
|
+
timestamp: {
|
|
112
|
+
select: () => ts.ref("Date"),
|
|
113
|
+
insert: () => ts.union(ts.ref("Date"), ts.string()),
|
|
114
|
+
update: () => ts.union(ts.ref("Date"), ts.string()),
|
|
115
|
+
},
|
|
116
|
+
timestamptz: {
|
|
117
|
+
select: () => ts.ref("Date"),
|
|
118
|
+
insert: () => ts.union(ts.ref("Date"), ts.string()),
|
|
119
|
+
update: () => ts.union(ts.ref("Date"), ts.string()),
|
|
120
|
+
},
|
|
121
|
+
interval: {
|
|
122
|
+
select: ts.string,
|
|
123
|
+
insert: () => ts.union(ts.string(), ts.number()),
|
|
124
|
+
update: () => ts.union(ts.string(), ts.number()),
|
|
125
|
+
},
|
|
126
|
+
json: {
|
|
127
|
+
select: () => ts.ref("JsonValue"),
|
|
128
|
+
insert: () => ts.ref("JsonValue"),
|
|
129
|
+
update: () => ts.ref("JsonValue"),
|
|
130
|
+
},
|
|
131
|
+
jsonb: {
|
|
132
|
+
select: () => ts.ref("JsonValue"),
|
|
133
|
+
insert: () => ts.ref("JsonValue"),
|
|
134
|
+
update: () => ts.ref("JsonValue"),
|
|
135
|
+
},
|
|
136
|
+
point: {
|
|
137
|
+
select: () => ts.objectType([
|
|
138
|
+
{ name: "x", type: ts.number() },
|
|
139
|
+
{ name: "y", type: ts.number() },
|
|
140
|
+
]),
|
|
141
|
+
insert: () => ts.objectType([
|
|
142
|
+
{ name: "x", type: ts.number() },
|
|
143
|
+
{ name: "y", type: ts.number() },
|
|
144
|
+
]),
|
|
145
|
+
update: () => ts.objectType([
|
|
146
|
+
{ name: "x", type: ts.number() },
|
|
147
|
+
{ name: "y", type: ts.number() },
|
|
148
|
+
]),
|
|
149
|
+
},
|
|
150
|
+
circle: {
|
|
151
|
+
select: () => ts.objectType([
|
|
152
|
+
{ name: "x", type: ts.number() },
|
|
153
|
+
{ name: "y", type: ts.number() },
|
|
154
|
+
{ name: "radius", type: ts.number() },
|
|
155
|
+
]),
|
|
156
|
+
insert: () => ts.objectType([
|
|
157
|
+
{ name: "x", type: ts.number() },
|
|
158
|
+
{ name: "y", type: ts.number() },
|
|
159
|
+
{ name: "radius", type: ts.number() },
|
|
160
|
+
]),
|
|
161
|
+
update: () => ts.objectType([
|
|
162
|
+
{ name: "x", type: ts.number() },
|
|
163
|
+
{ name: "y", type: ts.number() },
|
|
164
|
+
{ name: "radius", type: ts.number() },
|
|
165
|
+
]),
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Helper Functions (Types)
|
|
170
|
+
// ============================================================================
|
|
171
|
+
const buildFieldMatch = (field, ctx) => ({
|
|
172
|
+
schema: ctx.schemaName,
|
|
173
|
+
table: ctx.tableName,
|
|
174
|
+
column: field.columnName,
|
|
175
|
+
pgType: field.isArray && field.elementTypeName
|
|
176
|
+
? field.elementTypeName
|
|
177
|
+
: field.pgAttribute.getType()?.typname ?? "",
|
|
178
|
+
});
|
|
179
|
+
function resolveFieldTypeForKysely(field, ctx) {
|
|
180
|
+
const pgType = field.pgAttribute.getType();
|
|
181
|
+
const typeName = pgType?.typname ?? "";
|
|
182
|
+
const fieldMatch = buildFieldMatch(field, ctx);
|
|
183
|
+
const tsTypeHint = ctx.typeHints.getHint(fieldMatch, "tsType");
|
|
184
|
+
if (Option.isSome(tsTypeHint)) {
|
|
185
|
+
const typeName = tsTypeHint.value;
|
|
186
|
+
const importPath = ctx.typeHints.getHint(fieldMatch, "import");
|
|
187
|
+
return {
|
|
188
|
+
selectType: ts.ref(typeName),
|
|
189
|
+
needsColumnType: false,
|
|
190
|
+
externalImport: pipe(importPath, Option.map(path => ({ name: typeName, from: path })), Option.getOrUndefined),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (isEnumType(field)) {
|
|
194
|
+
const enumName = getPgTypeName(field);
|
|
195
|
+
if (enumName) {
|
|
196
|
+
const enumDef = findEnumByPgName(ctx.enums, enumName);
|
|
197
|
+
if (Option.isSome(enumDef)) {
|
|
198
|
+
return {
|
|
199
|
+
selectType: ts.ref(enumDef.value.name),
|
|
200
|
+
needsColumnType: false,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (pgType?.typtype === "c") {
|
|
206
|
+
const compositeName = getPgTypeName(field);
|
|
207
|
+
if (compositeName) {
|
|
208
|
+
const compositeDef = findCompositeByPgName(ctx.composites, compositeName);
|
|
209
|
+
if (Option.isSome(compositeDef)) {
|
|
210
|
+
return {
|
|
211
|
+
selectType: ts.ref(compositeDef.value.name),
|
|
212
|
+
needsColumnType: false,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const complexType = COMPLEX_TYPES[typeName];
|
|
218
|
+
if (complexType) {
|
|
219
|
+
return {
|
|
220
|
+
selectType: complexType.select(),
|
|
221
|
+
insertType: complexType.insert(),
|
|
222
|
+
updateType: complexType.update(),
|
|
223
|
+
needsColumnType: true,
|
|
224
|
+
externalImport: complexType.import,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const scalarBuilder = SCALAR_TYPES[typeName];
|
|
228
|
+
if (scalarBuilder) {
|
|
229
|
+
return {
|
|
230
|
+
selectType: scalarBuilder(),
|
|
231
|
+
needsColumnType: false,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (pgType) {
|
|
235
|
+
const extType = getExtensionTypeMapping(typeName, String(pgType.typnamespace), ctx.extensions);
|
|
236
|
+
if (Option.isSome(extType)) {
|
|
237
|
+
return {
|
|
238
|
+
selectType: ts.fromString(extType.value),
|
|
239
|
+
needsColumnType: false,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
selectType: ts.string(),
|
|
245
|
+
needsColumnType: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function buildFieldType(field, kyselyType, needsGenerated) {
|
|
249
|
+
let baseType;
|
|
250
|
+
if (kyselyType.needsColumnType && kyselyType.insertType && kyselyType.updateType) {
|
|
251
|
+
baseType = ts.ref("ColumnType", [
|
|
252
|
+
kyselyType.selectType,
|
|
253
|
+
kyselyType.insertType,
|
|
254
|
+
kyselyType.updateType,
|
|
255
|
+
]);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
baseType = kyselyType.selectType;
|
|
259
|
+
}
|
|
260
|
+
if (field.isArray) {
|
|
261
|
+
if (kyselyType.needsColumnType) {
|
|
262
|
+
baseType = ts.ref("ArrayType", [baseType]);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
baseType = ts.array(baseType);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (field.nullable) {
|
|
269
|
+
baseType = ts.nullable(baseType);
|
|
270
|
+
}
|
|
271
|
+
if (needsGenerated) {
|
|
272
|
+
baseType = ts.ref("Generated", [baseType]);
|
|
273
|
+
}
|
|
274
|
+
return baseType;
|
|
275
|
+
}
|
|
276
|
+
function isGeneratedField(field) {
|
|
277
|
+
// Identity and generated columns are always "generated" regardless of hasDefault
|
|
278
|
+
if (field.isIdentity)
|
|
279
|
+
return true;
|
|
280
|
+
if (field.isGenerated)
|
|
281
|
+
return true;
|
|
282
|
+
if (!field.hasDefault)
|
|
283
|
+
return false;
|
|
284
|
+
if (!field.permissions.canInsert)
|
|
285
|
+
return true;
|
|
286
|
+
const pgType = field.pgAttribute.getType();
|
|
287
|
+
if (!pgType)
|
|
288
|
+
return false;
|
|
289
|
+
const typeOid = Number(pgType._id);
|
|
290
|
+
const isIntegerType = typeOid === PgTypeOid.Int2
|
|
291
|
+
|| typeOid === PgTypeOid.Int4
|
|
292
|
+
|| typeOid === PgTypeOid.Int8;
|
|
293
|
+
if (isIntegerType && field.hasDefault) {
|
|
294
|
+
const constraints = field.pgAttribute.getClass()?.getConstraints() ?? [];
|
|
295
|
+
const isPrimaryKey = constraints.some(c => c.contype === "p" && c.conkey?.includes(field.pgAttribute.attnum));
|
|
296
|
+
if (isPrimaryKey)
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// Helper Functions (Queries)
|
|
303
|
+
// ============================================================================
|
|
304
|
+
const getTableTypeName = (entity) => entity.name;
|
|
305
|
+
const getTableRef = (entity, defaultSchemas) => defaultSchemas.includes(entity.schemaName)
|
|
306
|
+
? entity.pgName
|
|
307
|
+
: `${entity.schemaName}.${entity.pgName}`;
|
|
308
|
+
const findRowField = (entity, columnName) => entity.shapes.row.fields.find(f => f.columnName === columnName);
|
|
309
|
+
const getFieldTypeAst = (field, ctx) => {
|
|
310
|
+
if (!field)
|
|
311
|
+
return ts.string();
|
|
312
|
+
const resolved = resolveFieldType(field, ctx.enums, ctx.ir.extensions);
|
|
313
|
+
return resolved.enumDef ? ts.ref(resolved.enumDef.name) : tsTypeToAst(resolved.tsType);
|
|
314
|
+
};
|
|
315
|
+
const getFieldTypeString = (field, ctx) => {
|
|
316
|
+
if (!field)
|
|
317
|
+
return "string";
|
|
318
|
+
const resolved = resolveFieldType(field, ctx.enums, ctx.ir.extensions);
|
|
319
|
+
return resolved.enumDef ? resolved.enumDef.name : resolved.tsType;
|
|
320
|
+
};
|
|
321
|
+
const toPascalCase = (s) => inflect.pascalCase(s);
|
|
322
|
+
const id = (name) => b.identifier(name);
|
|
323
|
+
const call = (obj, method, args = []) => b.callExpression(b.memberExpression(toExpr(obj), id(method)), args.map(toExpr));
|
|
324
|
+
const selectFrom = (tableRef) => call(id("db"), "selectFrom", [str(tableRef)]);
|
|
325
|
+
const selectColumns = (base, entity, explicitColumns) => explicitColumns
|
|
326
|
+
? call(base, "select", [b.arrayExpression(entity.shapes.row.fields.map(f => str(f.columnName)))])
|
|
327
|
+
: call(base, "selectAll");
|
|
328
|
+
const insertInto = (tableRef) => call(id("db"), "insertInto", [str(tableRef)]);
|
|
329
|
+
const updateTable = (tableRef) => call(id("db"), "updateTable", [str(tableRef)]);
|
|
330
|
+
const deleteFrom = (tableRef) => call(id("db"), "deleteFrom", [str(tableRef)]);
|
|
331
|
+
const chain = (expr, method, args = []) => call(expr, method, args);
|
|
332
|
+
const arrowFn = (params, body) => {
|
|
333
|
+
const fn = b.arrowFunctionExpression(params.map(p => p), toExpr(body));
|
|
334
|
+
return fn;
|
|
335
|
+
};
|
|
336
|
+
const pgTypeNameToTs = (typeName) => {
|
|
337
|
+
const baseName = typeName.includes(".") ? typeName.split(".").pop() : typeName;
|
|
338
|
+
switch (baseName) {
|
|
339
|
+
case "bool":
|
|
340
|
+
case "boolean":
|
|
341
|
+
return "boolean";
|
|
342
|
+
case "int2":
|
|
343
|
+
case "smallint":
|
|
344
|
+
case "int4":
|
|
345
|
+
case "integer":
|
|
346
|
+
case "int":
|
|
347
|
+
case "oid":
|
|
348
|
+
case "float4":
|
|
349
|
+
case "real":
|
|
350
|
+
case "float8":
|
|
351
|
+
case "double precision":
|
|
352
|
+
return "number";
|
|
353
|
+
case "int8":
|
|
354
|
+
case "bigint":
|
|
355
|
+
case "numeric":
|
|
356
|
+
case "decimal":
|
|
357
|
+
case "money":
|
|
358
|
+
return "string";
|
|
359
|
+
case "text":
|
|
360
|
+
case "varchar":
|
|
361
|
+
case "character varying":
|
|
362
|
+
case "char":
|
|
363
|
+
case "character":
|
|
364
|
+
case "bpchar":
|
|
365
|
+
case "name":
|
|
366
|
+
case "xml":
|
|
367
|
+
case "bit":
|
|
368
|
+
case "varbit":
|
|
369
|
+
case "bit varying":
|
|
370
|
+
case "uuid":
|
|
371
|
+
case "inet":
|
|
372
|
+
case "cidr":
|
|
373
|
+
case "macaddr":
|
|
374
|
+
case "macaddr8":
|
|
375
|
+
case "time":
|
|
376
|
+
case "timetz":
|
|
377
|
+
case "time with time zone":
|
|
378
|
+
case "time without time zone":
|
|
379
|
+
case "interval":
|
|
380
|
+
return "string";
|
|
381
|
+
case "date":
|
|
382
|
+
case "timestamp":
|
|
383
|
+
case "timestamptz":
|
|
384
|
+
case "timestamp with time zone":
|
|
385
|
+
case "timestamp without time zone":
|
|
386
|
+
return "Date";
|
|
387
|
+
case "json":
|
|
388
|
+
case "jsonb":
|
|
389
|
+
case "jsonpath":
|
|
390
|
+
return "unknown";
|
|
391
|
+
case "bytea":
|
|
392
|
+
return "Buffer";
|
|
393
|
+
case "void":
|
|
394
|
+
return "void";
|
|
395
|
+
default:
|
|
396
|
+
return "unknown";
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Query Method Generators
|
|
401
|
+
// ============================================================================
|
|
402
|
+
const generateFindById = (ctx) => {
|
|
403
|
+
const { entity, executeQueries, defaultSchemas, entityName, exportName, explicitColumns, dbAsParameter } = ctx;
|
|
404
|
+
if (!entity.primaryKey || !entity.permissions.canSelect)
|
|
405
|
+
return undefined;
|
|
406
|
+
const pkColName = entity.primaryKey.columns[0];
|
|
407
|
+
const pkField = findRowField(entity, pkColName);
|
|
408
|
+
if (!pkField)
|
|
409
|
+
return undefined;
|
|
410
|
+
const tableRef = getTableRef(entity, defaultSchemas);
|
|
411
|
+
const fieldName = pkField.name;
|
|
412
|
+
const fieldType = getFieldTypeAst(pkField, ctx);
|
|
413
|
+
let query = chain(selectColumns(selectFrom(tableRef), entity, explicitColumns), "where", [str(pkColName), str("="), id(fieldName)]);
|
|
414
|
+
if (executeQueries) {
|
|
415
|
+
query = chain(query, "executeTakeFirst");
|
|
416
|
+
}
|
|
417
|
+
const optionsParam = param.destructured([{ name: fieldName, type: fieldType }]);
|
|
418
|
+
const dbParam = param.typed("db", ts.ref("Kysely", [ts.ref("DB")]));
|
|
419
|
+
const params = dbAsParameter ? [optionsParam, dbParam] : [optionsParam];
|
|
420
|
+
const fn = arrowFn(params, query);
|
|
421
|
+
const name = exportName(entityName, "FindById");
|
|
422
|
+
const rowType = entity.shapes.row.name;
|
|
423
|
+
const meta = {
|
|
424
|
+
name,
|
|
425
|
+
kind: "read",
|
|
426
|
+
params: [{
|
|
427
|
+
name: fieldName,
|
|
428
|
+
type: getFieldTypeString(pkField, ctx),
|
|
429
|
+
required: true,
|
|
430
|
+
columnName: pkColName,
|
|
431
|
+
source: "pk",
|
|
432
|
+
}],
|
|
433
|
+
returns: { type: rowType, nullable: true, isArray: false },
|
|
434
|
+
callSignature: { style: "named" },
|
|
435
|
+
};
|
|
436
|
+
return { name, fn, meta };
|
|
437
|
+
};
|
|
438
|
+
const generateListMany = (ctx) => {
|
|
439
|
+
const { entity, executeQueries, defaultSchemas, entityName, exportName, explicitColumns, dbAsParameter, defaultLimit } = ctx;
|
|
440
|
+
if (!entity.permissions.canSelect)
|
|
441
|
+
return undefined;
|
|
442
|
+
const tableRef = getTableRef(entity, defaultSchemas);
|
|
443
|
+
let query = chain(chain(selectColumns(selectFrom(tableRef), entity, explicitColumns), "limit", [id("limit")]), "offset", [id("offset")]);
|
|
444
|
+
if (executeQueries) {
|
|
445
|
+
query = chain(query, "execute");
|
|
446
|
+
}
|
|
447
|
+
const optionsParam = param.destructured([
|
|
448
|
+
{ name: "limit", type: ts.number(), optional: true, defaultValue: b.numericLiteral(defaultLimit) },
|
|
449
|
+
{ name: "offset", type: ts.number(), optional: true, defaultValue: b.numericLiteral(0) },
|
|
450
|
+
]);
|
|
451
|
+
const dbParam = param.typed("db", ts.ref("Kysely", [ts.ref("DB")]));
|
|
452
|
+
const params = dbAsParameter ? [optionsParam, dbParam] : [optionsParam];
|
|
453
|
+
const fn = arrowFn(params, query);
|
|
454
|
+
const name = exportName(entityName, "ListMany");
|
|
455
|
+
const rowType = entity.shapes.row.name;
|
|
456
|
+
const meta = {
|
|
457
|
+
name,
|
|
458
|
+
kind: "list",
|
|
459
|
+
params: [
|
|
460
|
+
{ name: "limit", type: "number", required: false, source: "pagination" },
|
|
461
|
+
{ name: "offset", type: "number", required: false, source: "pagination" },
|
|
462
|
+
],
|
|
463
|
+
returns: { type: rowType, nullable: false, isArray: true },
|
|
464
|
+
callSignature: { style: "named" },
|
|
465
|
+
};
|
|
466
|
+
return { name, fn, meta };
|
|
467
|
+
};
|
|
468
|
+
const generateCreate = (ctx) => {
|
|
469
|
+
const { entity, executeQueries, defaultSchemas, entityName, exportName, dbAsParameter } = ctx;
|
|
470
|
+
if (!entity.permissions.canInsert)
|
|
471
|
+
return undefined;
|
|
472
|
+
const tableRef = getTableRef(entity, defaultSchemas);
|
|
473
|
+
const tableTypeName = getTableTypeName(entity);
|
|
474
|
+
let query = chain(chain(insertInto(tableRef), "values", [id("data")]), "returningAll");
|
|
475
|
+
if (executeQueries) {
|
|
476
|
+
query = chain(query, "executeTakeFirstOrThrow");
|
|
477
|
+
}
|
|
478
|
+
const optionsParam = param.destructured([
|
|
479
|
+
{ name: "data", type: ts.ref("Insertable", [ts.ref(tableTypeName)]) },
|
|
480
|
+
]);
|
|
481
|
+
const dbParam = param.typed("db", ts.ref("Kysely", [ts.ref("DB")]));
|
|
482
|
+
const params = dbAsParameter ? [optionsParam, dbParam] : [optionsParam];
|
|
483
|
+
const fn = arrowFn(params, query);
|
|
484
|
+
const name = exportName(entityName, "Create");
|
|
485
|
+
const rowType = entity.shapes.row.name;
|
|
486
|
+
const insertType = `Insertable<${tableTypeName}>`;
|
|
487
|
+
const meta = {
|
|
488
|
+
name,
|
|
489
|
+
kind: "create",
|
|
490
|
+
params: [{
|
|
491
|
+
name: "data",
|
|
492
|
+
type: insertType,
|
|
493
|
+
required: true,
|
|
494
|
+
source: "body",
|
|
495
|
+
}],
|
|
496
|
+
returns: { type: rowType, nullable: false, isArray: false },
|
|
497
|
+
callSignature: { style: "named", bodyStyle: "property" },
|
|
498
|
+
};
|
|
499
|
+
return { name, fn, meta };
|
|
500
|
+
};
|
|
501
|
+
const generateUpdate = (ctx) => {
|
|
502
|
+
const { entity, executeQueries, defaultSchemas, entityName, exportName, dbAsParameter } = ctx;
|
|
503
|
+
if (!entity.primaryKey || !entity.permissions.canUpdate)
|
|
504
|
+
return undefined;
|
|
505
|
+
const pkColName = entity.primaryKey.columns[0];
|
|
506
|
+
const pkField = findRowField(entity, pkColName);
|
|
507
|
+
if (!pkField)
|
|
508
|
+
return undefined;
|
|
509
|
+
const tableRef = getTableRef(entity, defaultSchemas);
|
|
510
|
+
const fieldName = pkField.name;
|
|
511
|
+
const fieldType = getFieldTypeAst(pkField, ctx);
|
|
512
|
+
const tableTypeName = getTableTypeName(entity);
|
|
513
|
+
let query = chain(chain(chain(updateTable(tableRef), "set", [id("data")]), "where", [
|
|
514
|
+
str(pkColName),
|
|
515
|
+
str("="),
|
|
516
|
+
id(fieldName),
|
|
517
|
+
]), "returningAll");
|
|
518
|
+
if (executeQueries) {
|
|
519
|
+
query = chain(query, "executeTakeFirstOrThrow");
|
|
520
|
+
}
|
|
521
|
+
const optionsParam = param.destructured([
|
|
522
|
+
{ name: fieldName, type: fieldType },
|
|
523
|
+
{ name: "data", type: ts.ref("Updateable", [ts.ref(tableTypeName)]) },
|
|
524
|
+
]);
|
|
525
|
+
const dbParam = param.typed("db", ts.ref("Kysely", [ts.ref("DB")]));
|
|
526
|
+
const params = dbAsParameter ? [optionsParam, dbParam] : [optionsParam];
|
|
527
|
+
const fn = arrowFn(params, query);
|
|
528
|
+
const name = exportName(entityName, "Update");
|
|
529
|
+
const rowType = entity.shapes.row.name;
|
|
530
|
+
const updateType = `Updateable<${tableTypeName}>`;
|
|
531
|
+
const meta = {
|
|
532
|
+
name,
|
|
533
|
+
kind: "update",
|
|
534
|
+
params: [
|
|
535
|
+
{
|
|
536
|
+
name: fieldName,
|
|
537
|
+
type: getFieldTypeString(pkField, ctx),
|
|
538
|
+
required: true,
|
|
539
|
+
columnName: pkColName,
|
|
540
|
+
source: "pk",
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: "data",
|
|
544
|
+
type: updateType,
|
|
545
|
+
required: true,
|
|
546
|
+
source: "body",
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
returns: { type: rowType, nullable: false, isArray: false },
|
|
550
|
+
callSignature: { style: "named", bodyStyle: "property" },
|
|
551
|
+
};
|
|
552
|
+
return { name, fn, meta };
|
|
553
|
+
};
|
|
554
|
+
const generateDelete = (ctx) => {
|
|
555
|
+
const { entity, executeQueries, defaultSchemas, entityName, exportName, dbAsParameter } = ctx;
|
|
556
|
+
if (!entity.primaryKey || !entity.permissions.canDelete)
|
|
557
|
+
return undefined;
|
|
558
|
+
const pkColName = entity.primaryKey.columns[0];
|
|
559
|
+
const pkField = findRowField(entity, pkColName);
|
|
560
|
+
if (!pkField)
|
|
561
|
+
return undefined;
|
|
562
|
+
const tableRef = getTableRef(entity, defaultSchemas);
|
|
563
|
+
const fieldName = pkField.name;
|
|
564
|
+
const fieldType = getFieldTypeAst(pkField, ctx);
|
|
565
|
+
let query = chain(deleteFrom(tableRef), "where", [
|
|
566
|
+
str(pkColName),
|
|
567
|
+
str("="),
|
|
568
|
+
id(fieldName),
|
|
569
|
+
]);
|
|
570
|
+
if (executeQueries) {
|
|
571
|
+
query = chain(query, "execute");
|
|
572
|
+
}
|
|
573
|
+
const optionsParam = param.destructured([{ name: fieldName, type: fieldType }]);
|
|
574
|
+
const dbParam = param.typed("db", ts.ref("Kysely", [ts.ref("DB")]));
|
|
575
|
+
const params = dbAsParameter ? [optionsParam, dbParam] : [optionsParam];
|
|
576
|
+
const fn = arrowFn(params, query);
|
|
577
|
+
const name = exportName(entityName, "Remove");
|
|
578
|
+
const meta = {
|
|
579
|
+
name,
|
|
580
|
+
kind: "delete",
|
|
581
|
+
params: [{
|
|
582
|
+
name: fieldName,
|
|
583
|
+
type: getFieldTypeString(pkField, ctx),
|
|
584
|
+
required: true,
|
|
585
|
+
columnName: pkColName,
|
|
586
|
+
source: "pk",
|
|
587
|
+
}],
|
|
588
|
+
returns: { type: "void", nullable: false, isArray: false },
|
|
589
|
+
callSignature: { style: "named" },
|
|
590
|
+
};
|
|
591
|
+
return { name, fn, meta };
|
|
592
|
+
};
|
|
593
|
+
const generateCrudMethods = (ctx) => [
|
|
594
|
+
generateFindById(ctx),
|
|
595
|
+
ctx.generateListMany ? generateListMany(ctx) : undefined,
|
|
596
|
+
generateCreate(ctx),
|
|
597
|
+
generateUpdate(ctx),
|
|
598
|
+
generateDelete(ctx),
|
|
599
|
+
].filter((p) => p != null);
|
|
600
|
+
const collectImports = (entities, composites, ctx) => {
|
|
601
|
+
const kyselyImports = new Set();
|
|
602
|
+
const externalImports = new Map();
|
|
603
|
+
let needsJsonTypes = false;
|
|
604
|
+
let needsArrayType = false;
|
|
605
|
+
let needsGenerated = false;
|
|
606
|
+
const processField = (field, schemaName, tableName, checkGenerated) => {
|
|
607
|
+
const kyselyType = resolveFieldTypeForKysely(field, {
|
|
608
|
+
...ctx,
|
|
609
|
+
schemaName,
|
|
610
|
+
tableName,
|
|
611
|
+
});
|
|
612
|
+
if (kyselyType.needsColumnType) {
|
|
613
|
+
kyselyImports.add("ColumnType");
|
|
614
|
+
}
|
|
615
|
+
if (kyselyType.externalImport) {
|
|
616
|
+
const { name, from } = kyselyType.externalImport;
|
|
617
|
+
if (!externalImports.has(from)) {
|
|
618
|
+
externalImports.set(from, new Set());
|
|
619
|
+
}
|
|
620
|
+
externalImports.get(from).add(name);
|
|
621
|
+
}
|
|
622
|
+
// Note: Enum types are generated in the same file, no import needed
|
|
623
|
+
const pgType = field.pgAttribute.getType();
|
|
624
|
+
if (pgType?.typname === "json" || pgType?.typname === "jsonb") {
|
|
625
|
+
needsJsonTypes = true;
|
|
626
|
+
}
|
|
627
|
+
if (field.isArray && kyselyType.needsColumnType) {
|
|
628
|
+
needsArrayType = true;
|
|
629
|
+
}
|
|
630
|
+
if (checkGenerated && isGeneratedField(field)) {
|
|
631
|
+
needsGenerated = true;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
for (const entity of entities) {
|
|
635
|
+
if (!entity.permissions.canSelect)
|
|
636
|
+
continue;
|
|
637
|
+
for (const field of entity.shapes.row.fields) {
|
|
638
|
+
if (!field.permissions.canSelect)
|
|
639
|
+
continue;
|
|
640
|
+
processField(field, entity.schemaName, entity.pgName, true);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
for (const composite of composites) {
|
|
644
|
+
for (const field of composite.fields) {
|
|
645
|
+
processField(field, composite.schemaName, composite.pgName, false);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return {
|
|
649
|
+
kyselyImports,
|
|
650
|
+
externalImports,
|
|
651
|
+
needsJsonTypes,
|
|
652
|
+
needsArrayType,
|
|
653
|
+
needsGenerated,
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
// ============================================================================
|
|
657
|
+
// Statement Generators (Types)
|
|
658
|
+
// ============================================================================
|
|
659
|
+
const generateEnumStatement = (enumEntity) => {
|
|
660
|
+
return exp.typeAlias(enumEntity.name, { capability: "types", entity: enumEntity.name }, ts.union(...enumEntity.values.map(v => ts.literal(v))));
|
|
661
|
+
};
|
|
662
|
+
const generateTableInterface = (entity, ctx) => {
|
|
663
|
+
const properties = [];
|
|
664
|
+
for (const field of entity.shapes.row.fields) {
|
|
665
|
+
if (!field.permissions.canSelect)
|
|
666
|
+
continue;
|
|
667
|
+
const kyselyType = resolveFieldTypeForKysely(field, ctx);
|
|
668
|
+
const needsGenerated = isGeneratedField(field);
|
|
669
|
+
const fieldType = buildFieldType(field, kyselyType, needsGenerated);
|
|
670
|
+
properties.push({
|
|
671
|
+
name: field.name,
|
|
672
|
+
type: fieldType,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
return exp.interface(entity.name, { capability: "types", entity: entity.name }, properties);
|
|
676
|
+
};
|
|
677
|
+
const generateCompositeInterface = (composite, ctx) => {
|
|
678
|
+
const properties = [];
|
|
679
|
+
for (const field of composite.fields) {
|
|
680
|
+
const kyselyType = resolveFieldTypeForKysely(field, ctx);
|
|
681
|
+
const fieldType = buildFieldType(field, kyselyType, false);
|
|
682
|
+
properties.push({
|
|
683
|
+
name: field.name,
|
|
684
|
+
type: fieldType,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
return exp.interface(composite.name, { capability: "types", entity: composite.name }, properties);
|
|
688
|
+
};
|
|
689
|
+
const generateDBInterface = (entities, defaultSchemas, dbTypeName) => {
|
|
690
|
+
const properties = [];
|
|
691
|
+
for (const entity of entities) {
|
|
692
|
+
if (!entity.permissions.canSelect)
|
|
693
|
+
continue;
|
|
694
|
+
const key = defaultSchemas.includes(entity.schemaName)
|
|
695
|
+
? entity.pgName
|
|
696
|
+
: `${entity.schemaName}.${entity.pgName}`;
|
|
697
|
+
properties.push({
|
|
698
|
+
name: key,
|
|
699
|
+
type: ts.ref(entity.name),
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
properties.sort((a, b) => a.name.localeCompare(b.name));
|
|
703
|
+
return exp.interface(dbTypeName, { capability: "types", entity: dbTypeName }, properties);
|
|
704
|
+
};
|
|
705
|
+
// ============================================================================
|
|
706
|
+
// Export Style Helpers (Queries)
|
|
707
|
+
// ============================================================================
|
|
708
|
+
const toFlatExports = (methods) => methods.map(m => conjure.export.const(m.name, m.fn));
|
|
709
|
+
const toNamespaceExport = (entityName, methods) => {
|
|
710
|
+
const properties = methods.map(m => b.objectProperty(id(m.name), m.fn));
|
|
711
|
+
const obj = b.objectExpression(properties);
|
|
712
|
+
return conjure.export.const(entityName, obj);
|
|
713
|
+
};
|
|
714
|
+
const toStatements = (methods, exportStyle, entityName) => {
|
|
715
|
+
if (methods.length === 0)
|
|
716
|
+
return [];
|
|
717
|
+
return exportStyle === "namespace"
|
|
718
|
+
? [toNamespaceExport(entityName, methods)]
|
|
719
|
+
: toFlatExports(methods);
|
|
720
|
+
};
|
|
721
|
+
// ============================================================================
|
|
722
|
+
// Main Generation Function
|
|
723
|
+
// ============================================================================
|
|
724
|
+
export function kysely(config) {
|
|
725
|
+
const parsed = S.decodeUnknownSync(KyselyConfigSchema)(config);
|
|
726
|
+
const defaultExportName = (_entityName, methodName) => methodName.charAt(0).toLowerCase() + methodName.slice(1);
|
|
727
|
+
// Compute dbTypesPath from dbTypeName if not explicitly provided
|
|
728
|
+
// Both DB types and query files are in the same outputDir, so use "./" not "../"
|
|
729
|
+
const dbTypesPath = config.dbTypesPath ?? `./${parsed.dbTypeName}.js`;
|
|
730
|
+
const resolvedConfig = {
|
|
731
|
+
...parsed,
|
|
732
|
+
dbTypesPath,
|
|
733
|
+
exportName: parsed.exportName ?? defaultExportName,
|
|
734
|
+
};
|
|
735
|
+
return definePlugin({
|
|
736
|
+
name: "kysely",
|
|
737
|
+
kind: "queries",
|
|
738
|
+
singleton: true,
|
|
739
|
+
canProvide: () => true,
|
|
740
|
+
provide: (_params, _deps, ctx) => {
|
|
741
|
+
const { ir, typeHints } = ctx;
|
|
742
|
+
const enumEntities = getEnumEntities(ir);
|
|
743
|
+
const compositeEntities = getCompositeEntities(ir).filter(e => e.tags.omit !== true);
|
|
744
|
+
const tableEntities = getTableEntities(ir).filter(e => e.tags.omit !== true);
|
|
745
|
+
const defaultSchemas = ir.schemas;
|
|
746
|
+
const dbTypeName = parsed.dbTypeName;
|
|
747
|
+
// ================================================================
|
|
748
|
+
// Generate Types (DB.ts)
|
|
749
|
+
// ================================================================
|
|
750
|
+
const fieldCtx = {
|
|
751
|
+
schemaName: "",
|
|
752
|
+
tableName: "",
|
|
753
|
+
enums: enumEntities,
|
|
754
|
+
composites: compositeEntities,
|
|
755
|
+
extensions: ir.extensions,
|
|
756
|
+
typeHints,
|
|
757
|
+
defaultSchemas,
|
|
758
|
+
};
|
|
759
|
+
const imports = collectImports(tableEntities, compositeEntities, fieldCtx);
|
|
760
|
+
const statements = [];
|
|
761
|
+
for (const enumEntity of enumEntities) {
|
|
762
|
+
if (enumEntity.tags.omit === true)
|
|
763
|
+
continue;
|
|
764
|
+
statements.push(generateEnumStatement(enumEntity));
|
|
765
|
+
}
|
|
766
|
+
for (const composite of compositeEntities) {
|
|
767
|
+
statements.push(generateCompositeInterface(composite, {
|
|
768
|
+
...fieldCtx,
|
|
769
|
+
schemaName: composite.schemaName,
|
|
770
|
+
tableName: composite.pgName,
|
|
771
|
+
}));
|
|
772
|
+
}
|
|
773
|
+
for (const entity of tableEntities) {
|
|
774
|
+
statements.push(generateTableInterface(entity, {
|
|
775
|
+
...fieldCtx,
|
|
776
|
+
schemaName: entity.schemaName,
|
|
777
|
+
tableName: entity.pgName,
|
|
778
|
+
}));
|
|
779
|
+
}
|
|
780
|
+
statements.push(generateDBInterface(tableEntities, defaultSchemas, dbTypeName));
|
|
781
|
+
const dbFilePath = `${parsed.outputDir}/${dbTypeName}.ts`;
|
|
782
|
+
const dbFile = ctx.file(dbFilePath);
|
|
783
|
+
if (imports.kyselyImports.size > 0) {
|
|
784
|
+
dbFile.import({
|
|
785
|
+
kind: "package",
|
|
786
|
+
types: [...imports.kyselyImports],
|
|
787
|
+
from: "kysely",
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
for (const [from, names] of imports.externalImports) {
|
|
791
|
+
dbFile.import({
|
|
792
|
+
kind: "relative",
|
|
793
|
+
types: [...names],
|
|
794
|
+
from,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
const helperTypes = [];
|
|
798
|
+
if (imports.needsGenerated) {
|
|
799
|
+
helperTypes.push(`export type Generated<T> = ${GENERATED_TYPE_DEF};`);
|
|
800
|
+
}
|
|
801
|
+
if (imports.needsArrayType) {
|
|
802
|
+
helperTypes.push(`export type ArrayType<T> = ${ARRAY_TYPE_DEF};`);
|
|
803
|
+
helperTypes.push(`export type ArrayTypeImpl<T> = ${ARRAY_TYPE_IMPL_DEF};`);
|
|
804
|
+
}
|
|
805
|
+
if (imports.needsJsonTypes) {
|
|
806
|
+
helperTypes.push(`export type JsonPrimitive = boolean | number | string | null;`);
|
|
807
|
+
helperTypes.push(`export type JsonObject = { [x: string]: JsonValue | undefined };`);
|
|
808
|
+
helperTypes.push(`export type JsonArray = JsonValue[];`);
|
|
809
|
+
helperTypes.push(`export type JsonValue = JsonArray | JsonObject | JsonPrimitive;`);
|
|
810
|
+
}
|
|
811
|
+
if (helperTypes.length > 0) {
|
|
812
|
+
dbFile.header(helperTypes.join("\n\n"));
|
|
813
|
+
}
|
|
814
|
+
dbFile.ast(conjure.symbolProgram(...statements)).emit();
|
|
815
|
+
// ================================================================
|
|
816
|
+
// Generate Queries
|
|
817
|
+
// ================================================================
|
|
818
|
+
if (!parsed.generateQueries) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const exportName = resolvedConfig.exportName;
|
|
822
|
+
const exportStyle = resolvedConfig.exportStyle;
|
|
823
|
+
for (const entity of tableEntities) {
|
|
824
|
+
const entityName = entity.name;
|
|
825
|
+
const filePath = `${parsed.outputDir}/${entityName}.ts`;
|
|
826
|
+
const file = ctx.file(filePath);
|
|
827
|
+
const genCtx = {
|
|
828
|
+
entity,
|
|
829
|
+
enums: enumEntities,
|
|
830
|
+
ir,
|
|
831
|
+
defaultSchemas,
|
|
832
|
+
dbTypesPath: resolvedConfig.dbTypesPath,
|
|
833
|
+
executeQueries: parsed.executeQueries,
|
|
834
|
+
generateListMany: parsed.generateListMany,
|
|
835
|
+
entityName,
|
|
836
|
+
exportName,
|
|
837
|
+
explicitColumns: parsed.explicitColumns,
|
|
838
|
+
dbAsParameter: parsed.dbAsParameter,
|
|
839
|
+
defaultLimit: parsed.defaultLimit,
|
|
840
|
+
};
|
|
841
|
+
const methods = generateCrudMethods(genCtx);
|
|
842
|
+
if (methods.length === 0) {
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (parsed.header) {
|
|
846
|
+
file.header(parsed.header);
|
|
847
|
+
}
|
|
848
|
+
// Build kysely imports based on what's actually used
|
|
849
|
+
const kyselyTypes = [];
|
|
850
|
+
if (parsed.dbAsParameter) {
|
|
851
|
+
kyselyTypes.push("Kysely");
|
|
852
|
+
}
|
|
853
|
+
if (methods.some((m) => m.meta.kind === "create")) {
|
|
854
|
+
kyselyTypes.push("Insertable");
|
|
855
|
+
}
|
|
856
|
+
if (methods.some((m) => m.meta.kind === "update")) {
|
|
857
|
+
kyselyTypes.push("Updateable");
|
|
858
|
+
}
|
|
859
|
+
// Selectable would be used for explicit return types, but we use returningAll()
|
|
860
|
+
if (kyselyTypes.length > 0) {
|
|
861
|
+
file.import({
|
|
862
|
+
kind: "package",
|
|
863
|
+
types: kyselyTypes,
|
|
864
|
+
from: "kysely",
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
// Only import DB if dbAsParameter is true
|
|
868
|
+
const dbImportTypes = parsed.dbAsParameter ? [dbTypeName, entityName] : [entityName];
|
|
869
|
+
file.import({
|
|
870
|
+
kind: "relative",
|
|
871
|
+
types: dbImportTypes,
|
|
872
|
+
from: resolvedConfig.dbTypesPath,
|
|
873
|
+
});
|
|
874
|
+
const stmts = toStatements(methods, exportStyle, entityName);
|
|
875
|
+
file.ast(conjure.program(...stmts)).emit();
|
|
876
|
+
const pkType = entity.primaryKey
|
|
877
|
+
? getFieldTypeString(findRowField(entity, entity.primaryKey.columns[0]), genCtx)
|
|
878
|
+
: "unknown";
|
|
879
|
+
ctx.symbols.registerEntityMethods({
|
|
880
|
+
entity: entityName,
|
|
881
|
+
importPath: filePath,
|
|
882
|
+
pkType,
|
|
883
|
+
hasCompositePk: (entity.primaryKey?.columns.length ?? 0) > 1,
|
|
884
|
+
methods: methods.map(m => ({
|
|
885
|
+
name: m.meta.name,
|
|
886
|
+
file: filePath,
|
|
887
|
+
entity: entityName,
|
|
888
|
+
kind: m.meta.kind,
|
|
889
|
+
params: m.meta.params.map(p => ({
|
|
890
|
+
name: p.name,
|
|
891
|
+
type: p.type,
|
|
892
|
+
required: p.required,
|
|
893
|
+
columnName: p.columnName,
|
|
894
|
+
source: p.source,
|
|
895
|
+
})),
|
|
896
|
+
returns: m.meta.returns,
|
|
897
|
+
lookupField: m.meta.lookupField,
|
|
898
|
+
isUniqueLookup: m.meta.isUniqueLookup,
|
|
899
|
+
callSignature: m.meta.callSignature,
|
|
900
|
+
})),
|
|
901
|
+
}, "kysely");
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
//# sourceMappingURL=kysely.js.map
|