@danielfgray/pg-sourcerer 0.2.1 → 0.3.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 (164) hide show
  1. package/dist/cli.js +3 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +1 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/errors.d.ts +14 -1
  8. package/dist/errors.d.ts.map +1 -1
  9. package/dist/errors.js +2 -0
  10. package/dist/errors.js.map +1 -1
  11. package/dist/generate.d.ts +5 -9
  12. package/dist/generate.d.ts.map +1 -1
  13. package/dist/generate.js +27 -29
  14. package/dist/generate.js.map +1 -1
  15. package/dist/index.d.ts +19 -12
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +25 -13
  18. package/dist/index.js.map +1 -1
  19. package/dist/init.d.ts.map +1 -1
  20. package/dist/init.js +39 -9
  21. package/dist/init.js.map +1 -1
  22. package/dist/ir/extensions/queries.d.ts +264 -0
  23. package/dist/ir/extensions/queries.d.ts.map +1 -0
  24. package/dist/ir/extensions/queries.js +153 -0
  25. package/dist/ir/extensions/queries.js.map +1 -0
  26. package/dist/ir/extensions/schema-builder.d.ts +61 -0
  27. package/dist/ir/extensions/schema-builder.d.ts.map +1 -0
  28. package/dist/ir/extensions/schema-builder.js +5 -0
  29. package/dist/ir/extensions/schema-builder.js.map +1 -0
  30. package/dist/lib/conjure.d.ts +66 -0
  31. package/dist/lib/conjure.d.ts.map +1 -1
  32. package/dist/lib/conjure.js +127 -29
  33. package/dist/lib/conjure.js.map +1 -1
  34. package/dist/lib/hex.d.ts +10 -3
  35. package/dist/lib/hex.d.ts.map +1 -1
  36. package/dist/lib/hex.js +18 -8
  37. package/dist/lib/hex.js.map +1 -1
  38. package/dist/plugins/arktype.d.ts +27 -14
  39. package/dist/plugins/arktype.d.ts.map +1 -1
  40. package/dist/plugins/arktype.js +166 -130
  41. package/dist/plugins/arktype.js.map +1 -1
  42. package/dist/plugins/effect.d.ts +53 -0
  43. package/dist/plugins/effect.d.ts.map +1 -0
  44. package/dist/plugins/effect.js +1074 -0
  45. package/dist/plugins/effect.js.map +1 -0
  46. package/dist/plugins/http-elysia.d.ts +32 -0
  47. package/dist/plugins/http-elysia.d.ts.map +1 -0
  48. package/dist/plugins/http-elysia.js +613 -0
  49. package/dist/plugins/http-elysia.js.map +1 -0
  50. package/dist/plugins/http-express.d.ts +36 -0
  51. package/dist/plugins/http-express.d.ts.map +1 -0
  52. package/dist/plugins/http-express.js +388 -0
  53. package/dist/plugins/http-express.js.map +1 -0
  54. package/dist/plugins/http-hono.d.ts +36 -0
  55. package/dist/plugins/http-hono.d.ts.map +1 -0
  56. package/dist/plugins/http-hono.js +453 -0
  57. package/dist/plugins/http-hono.js.map +1 -0
  58. package/dist/plugins/http-orpc.d.ts +55 -0
  59. package/dist/plugins/http-orpc.d.ts.map +1 -0
  60. package/dist/plugins/http-orpc.js +370 -0
  61. package/dist/plugins/http-orpc.js.map +1 -0
  62. package/dist/plugins/http-trpc.d.ts +59 -0
  63. package/dist/plugins/http-trpc.d.ts.map +1 -0
  64. package/dist/plugins/http-trpc.js +392 -0
  65. package/dist/plugins/http-trpc.js.map +1 -0
  66. package/dist/plugins/kysely/queries.d.ts +92 -0
  67. package/dist/plugins/kysely/queries.d.ts.map +1 -0
  68. package/dist/plugins/kysely/queries.js +1169 -0
  69. package/dist/plugins/kysely/queries.js.map +1 -0
  70. package/dist/plugins/kysely/shared.d.ts +59 -0
  71. package/dist/plugins/kysely/shared.d.ts.map +1 -0
  72. package/dist/plugins/kysely/shared.js +247 -0
  73. package/dist/plugins/kysely/shared.js.map +1 -0
  74. package/dist/plugins/kysely/types.d.ts +22 -0
  75. package/dist/plugins/kysely/types.d.ts.map +1 -0
  76. package/dist/plugins/kysely/types.js +428 -0
  77. package/dist/plugins/kysely/types.js.map +1 -0
  78. package/dist/plugins/kysely.d.ts +72 -0
  79. package/dist/plugins/kysely.d.ts.map +1 -0
  80. package/dist/plugins/kysely.js +906 -0
  81. package/dist/plugins/kysely.js.map +1 -0
  82. package/dist/plugins/sql-queries.d.ts +55 -11
  83. package/dist/plugins/sql-queries.d.ts.map +1 -1
  84. package/dist/plugins/sql-queries.js +467 -218
  85. package/dist/plugins/sql-queries.js.map +1 -1
  86. package/dist/plugins/types.d.ts +20 -14
  87. package/dist/plugins/types.d.ts.map +1 -1
  88. package/dist/plugins/types.js +90 -112
  89. package/dist/plugins/types.js.map +1 -1
  90. package/dist/plugins/valibot.d.ts +45 -0
  91. package/dist/plugins/valibot.d.ts.map +1 -0
  92. package/dist/plugins/valibot.js +422 -0
  93. package/dist/plugins/valibot.js.map +1 -0
  94. package/dist/plugins/zod.d.ts +27 -14
  95. package/dist/plugins/zod.d.ts.map +1 -1
  96. package/dist/plugins/zod.js +231 -166
  97. package/dist/plugins/zod.js.map +1 -1
  98. package/dist/services/artifact-store.d.ts +11 -1
  99. package/dist/services/artifact-store.d.ts.map +1 -1
  100. package/dist/services/artifact-store.js +9 -0
  101. package/dist/services/artifact-store.js.map +1 -1
  102. package/dist/services/core-providers.d.ts +15 -0
  103. package/dist/services/core-providers.d.ts.map +1 -0
  104. package/dist/services/core-providers.js +23 -0
  105. package/dist/services/core-providers.js.map +1 -0
  106. package/dist/services/emissions.d.ts +14 -0
  107. package/dist/services/emissions.d.ts.map +1 -1
  108. package/dist/services/emissions.js +86 -47
  109. package/dist/services/emissions.js.map +1 -1
  110. package/dist/services/execution.d.ts +35 -0
  111. package/dist/services/execution.d.ts.map +1 -0
  112. package/dist/services/execution.js +86 -0
  113. package/dist/services/execution.js.map +1 -0
  114. package/dist/services/file-builder.d.ts +4 -0
  115. package/dist/services/file-builder.d.ts.map +1 -1
  116. package/dist/services/file-builder.js.map +1 -1
  117. package/dist/services/inflection.d.ts +2 -2
  118. package/dist/services/inflection.d.ts.map +1 -1
  119. package/dist/services/inflection.js +4 -4
  120. package/dist/services/inflection.js.map +1 -1
  121. package/dist/services/ir-builder.d.ts.map +1 -1
  122. package/dist/services/ir-builder.js +10 -3
  123. package/dist/services/ir-builder.js.map +1 -1
  124. package/dist/services/pg-types.d.ts +31 -0
  125. package/dist/services/pg-types.d.ts.map +1 -1
  126. package/dist/services/pg-types.js +24 -0
  127. package/dist/services/pg-types.js.map +1 -1
  128. package/dist/services/plugin-runner.d.ts +27 -37
  129. package/dist/services/plugin-runner.d.ts.map +1 -1
  130. package/dist/services/plugin-runner.js +73 -171
  131. package/dist/services/plugin-runner.js.map +1 -1
  132. package/dist/services/plugin.d.ts +349 -217
  133. package/dist/services/plugin.d.ts.map +1 -1
  134. package/dist/services/plugin.js +182 -130
  135. package/dist/services/plugin.js.map +1 -1
  136. package/dist/services/resolution.d.ts +38 -0
  137. package/dist/services/resolution.d.ts.map +1 -0
  138. package/dist/services/resolution.js +242 -0
  139. package/dist/services/resolution.js.map +1 -0
  140. package/dist/services/service-registry.d.ts +74 -0
  141. package/dist/services/service-registry.d.ts.map +1 -0
  142. package/dist/services/service-registry.js +61 -0
  143. package/dist/services/service-registry.js.map +1 -0
  144. package/dist/services/symbols.d.ts +59 -0
  145. package/dist/services/symbols.d.ts.map +1 -1
  146. package/dist/services/symbols.js +16 -0
  147. package/dist/services/symbols.js.map +1 -1
  148. package/dist/testing.d.ts +4 -25
  149. package/dist/testing.d.ts.map +1 -1
  150. package/dist/testing.js +2 -23
  151. package/dist/testing.js.map +1 -1
  152. package/package.json +1 -1
  153. package/dist/plugins/effect-model.d.ts +0 -17
  154. package/dist/plugins/effect-model.d.ts.map +0 -1
  155. package/dist/plugins/effect-model.js +0 -409
  156. package/dist/plugins/effect-model.js.map +0 -1
  157. package/dist/plugins/kysely-queries.d.ts +0 -66
  158. package/dist/plugins/kysely-queries.d.ts.map +0 -1
  159. package/dist/plugins/kysely-queries.js +0 -951
  160. package/dist/plugins/kysely-queries.js.map +0 -1
  161. package/dist/plugins/kysely-types.d.ts +0 -35
  162. package/dist/plugins/kysely-types.d.ts.map +0 -1
  163. package/dist/plugins/kysely-types.js +0 -601
  164. 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