@danielfgray/pg-sourcerer 0.2.0 → 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.
Files changed (173) hide show
  1. package/dist/cli.js +25 -19
  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 +31 -21
  12. package/dist/generate.d.ts.map +1 -1
  13. package/dist/generate.js +53 -43
  14. package/dist/generate.js.map +1 -1
  15. package/dist/index.d.ts +20 -12
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +27 -13
  18. package/dist/index.js.map +1 -1
  19. package/dist/init.d.ts +0 -1
  20. package/dist/init.d.ts.map +1 -1
  21. package/dist/init.js +90 -35
  22. package/dist/init.js.map +1 -1
  23. package/dist/ir/extensions/queries.d.ts +264 -0
  24. package/dist/ir/extensions/queries.d.ts.map +1 -0
  25. package/dist/ir/extensions/queries.js +153 -0
  26. package/dist/ir/extensions/queries.js.map +1 -0
  27. package/dist/ir/extensions/schema-builder.d.ts +61 -0
  28. package/dist/ir/extensions/schema-builder.d.ts.map +1 -0
  29. package/dist/ir/extensions/schema-builder.js +5 -0
  30. package/dist/ir/extensions/schema-builder.js.map +1 -0
  31. package/dist/lib/conjure.d.ts +101 -0
  32. package/dist/lib/conjure.d.ts.map +1 -1
  33. package/dist/lib/conjure.js +204 -26
  34. package/dist/lib/conjure.js.map +1 -1
  35. package/dist/lib/hex.d.ts +10 -8
  36. package/dist/lib/hex.d.ts.map +1 -1
  37. package/dist/lib/hex.js +18 -15
  38. package/dist/lib/hex.js.map +1 -1
  39. package/dist/plugins/arktype.d.ts +27 -14
  40. package/dist/plugins/arktype.d.ts.map +1 -1
  41. package/dist/plugins/arktype.js +166 -130
  42. package/dist/plugins/arktype.js.map +1 -1
  43. package/dist/plugins/effect.d.ts +53 -0
  44. package/dist/plugins/effect.d.ts.map +1 -0
  45. package/dist/plugins/effect.js +1074 -0
  46. package/dist/plugins/effect.js.map +1 -0
  47. package/dist/plugins/http-elysia.d.ts +32 -0
  48. package/dist/plugins/http-elysia.d.ts.map +1 -0
  49. package/dist/plugins/http-elysia.js +613 -0
  50. package/dist/plugins/http-elysia.js.map +1 -0
  51. package/dist/plugins/http-express.d.ts +36 -0
  52. package/dist/plugins/http-express.d.ts.map +1 -0
  53. package/dist/plugins/http-express.js +388 -0
  54. package/dist/plugins/http-express.js.map +1 -0
  55. package/dist/plugins/http-hono.d.ts +36 -0
  56. package/dist/plugins/http-hono.d.ts.map +1 -0
  57. package/dist/plugins/http-hono.js +453 -0
  58. package/dist/plugins/http-hono.js.map +1 -0
  59. package/dist/plugins/http-orpc.d.ts +55 -0
  60. package/dist/plugins/http-orpc.d.ts.map +1 -0
  61. package/dist/plugins/http-orpc.js +370 -0
  62. package/dist/plugins/http-orpc.js.map +1 -0
  63. package/dist/plugins/http-trpc.d.ts +59 -0
  64. package/dist/plugins/http-trpc.d.ts.map +1 -0
  65. package/dist/plugins/http-trpc.js +392 -0
  66. package/dist/plugins/http-trpc.js.map +1 -0
  67. package/dist/plugins/kysely/queries.d.ts +92 -0
  68. package/dist/plugins/kysely/queries.d.ts.map +1 -0
  69. package/dist/plugins/kysely/queries.js +1169 -0
  70. package/dist/plugins/kysely/queries.js.map +1 -0
  71. package/dist/plugins/kysely/shared.d.ts +59 -0
  72. package/dist/plugins/kysely/shared.d.ts.map +1 -0
  73. package/dist/plugins/kysely/shared.js +247 -0
  74. package/dist/plugins/kysely/shared.js.map +1 -0
  75. package/dist/plugins/kysely/types.d.ts +22 -0
  76. package/dist/plugins/kysely/types.d.ts.map +1 -0
  77. package/dist/plugins/kysely/types.js +428 -0
  78. package/dist/plugins/kysely/types.js.map +1 -0
  79. package/dist/plugins/kysely.d.ts +72 -0
  80. package/dist/plugins/kysely.d.ts.map +1 -0
  81. package/dist/plugins/kysely.js +906 -0
  82. package/dist/plugins/kysely.js.map +1 -0
  83. package/dist/plugins/sql-queries.d.ts +55 -7
  84. package/dist/plugins/sql-queries.d.ts.map +1 -1
  85. package/dist/plugins/sql-queries.js +747 -121
  86. package/dist/plugins/sql-queries.js.map +1 -1
  87. package/dist/plugins/types.d.ts +20 -14
  88. package/dist/plugins/types.d.ts.map +1 -1
  89. package/dist/plugins/types.js +90 -112
  90. package/dist/plugins/types.js.map +1 -1
  91. package/dist/plugins/valibot.d.ts +45 -0
  92. package/dist/plugins/valibot.d.ts.map +1 -0
  93. package/dist/plugins/valibot.js +422 -0
  94. package/dist/plugins/valibot.js.map +1 -0
  95. package/dist/plugins/zod.d.ts +27 -14
  96. package/dist/plugins/zod.d.ts.map +1 -1
  97. package/dist/plugins/zod.js +231 -166
  98. package/dist/plugins/zod.js.map +1 -1
  99. package/dist/services/artifact-store.d.ts +11 -1
  100. package/dist/services/artifact-store.d.ts.map +1 -1
  101. package/dist/services/artifact-store.js +9 -0
  102. package/dist/services/artifact-store.js.map +1 -1
  103. package/dist/services/config-loader.d.ts +4 -0
  104. package/dist/services/config-loader.d.ts.map +1 -1
  105. package/dist/services/config-loader.js +1 -1
  106. package/dist/services/config-loader.js.map +1 -1
  107. package/dist/services/config.d.ts +57 -0
  108. package/dist/services/config.d.ts.map +1 -0
  109. package/dist/services/config.js +66 -0
  110. package/dist/services/config.js.map +1 -0
  111. package/dist/services/core-providers.d.ts +15 -0
  112. package/dist/services/core-providers.d.ts.map +1 -0
  113. package/dist/services/core-providers.js +23 -0
  114. package/dist/services/core-providers.js.map +1 -0
  115. package/dist/services/emissions.d.ts +14 -0
  116. package/dist/services/emissions.d.ts.map +1 -1
  117. package/dist/services/emissions.js +86 -47
  118. package/dist/services/emissions.js.map +1 -1
  119. package/dist/services/execution.d.ts +35 -0
  120. package/dist/services/execution.d.ts.map +1 -0
  121. package/dist/services/execution.js +86 -0
  122. package/dist/services/execution.js.map +1 -0
  123. package/dist/services/file-builder.d.ts +4 -0
  124. package/dist/services/file-builder.d.ts.map +1 -1
  125. package/dist/services/file-builder.js.map +1 -1
  126. package/dist/services/inflection.d.ts +2 -2
  127. package/dist/services/inflection.d.ts.map +1 -1
  128. package/dist/services/inflection.js +4 -4
  129. package/dist/services/inflection.js.map +1 -1
  130. package/dist/services/ir-builder.d.ts.map +1 -1
  131. package/dist/services/ir-builder.js +10 -3
  132. package/dist/services/ir-builder.js.map +1 -1
  133. package/dist/services/pg-types.d.ts +31 -0
  134. package/dist/services/pg-types.d.ts.map +1 -1
  135. package/dist/services/pg-types.js +24 -0
  136. package/dist/services/pg-types.js.map +1 -1
  137. package/dist/services/plugin-runner.d.ts +27 -37
  138. package/dist/services/plugin-runner.d.ts.map +1 -1
  139. package/dist/services/plugin-runner.js +73 -171
  140. package/dist/services/plugin-runner.js.map +1 -1
  141. package/dist/services/plugin.d.ts +350 -215
  142. package/dist/services/plugin.d.ts.map +1 -1
  143. package/dist/services/plugin.js +182 -130
  144. package/dist/services/plugin.js.map +1 -1
  145. package/dist/services/resolution.d.ts +38 -0
  146. package/dist/services/resolution.d.ts.map +1 -0
  147. package/dist/services/resolution.js +242 -0
  148. package/dist/services/resolution.js.map +1 -0
  149. package/dist/services/service-registry.d.ts +74 -0
  150. package/dist/services/service-registry.d.ts.map +1 -0
  151. package/dist/services/service-registry.js +61 -0
  152. package/dist/services/service-registry.js.map +1 -0
  153. package/dist/services/symbols.d.ts +59 -0
  154. package/dist/services/symbols.d.ts.map +1 -1
  155. package/dist/services/symbols.js +16 -0
  156. package/dist/services/symbols.js.map +1 -1
  157. package/dist/testing.d.ts +4 -25
  158. package/dist/testing.d.ts.map +1 -1
  159. package/dist/testing.js +2 -23
  160. package/dist/testing.js.map +1 -1
  161. package/package.json +1 -1
  162. package/dist/plugins/effect-model.d.ts +0 -17
  163. package/dist/plugins/effect-model.d.ts.map +0 -1
  164. package/dist/plugins/effect-model.js +0 -409
  165. package/dist/plugins/effect-model.js.map +0 -1
  166. package/dist/plugins/kysely-queries.d.ts +0 -66
  167. package/dist/plugins/kysely-queries.d.ts.map +0 -1
  168. package/dist/plugins/kysely-queries.js +0 -960
  169. package/dist/plugins/kysely-queries.js.map +0 -1
  170. package/dist/plugins/kysely-types.d.ts +0 -35
  171. package/dist/plugins/kysely-types.d.ts.map +0 -1
  172. package/dist/plugins/kysely-types.js +0 -601
  173. package/dist/plugins/kysely-types.js.map +0 -1
@@ -1,960 +0,0 @@
1
- /**
2
- * Kysely Queries Plugin - Generate type-safe Kysely query functions
3
- *
4
- * Generates permission-aware CRUD query functions using Kysely's query builder.
5
- * Uses object namespace style with explicit `db: Kysely<DB>` first parameter.
6
- */
7
- import { Schema as S } from "effect";
8
- import { definePlugin } from "../services/plugin.js";
9
- import { getTableEntities, getEnumEntities, getFunctionEntities, getCompositeEntities } from "../ir/semantic-ir.js";
10
- import { conjure, cast } from "../lib/conjure.js";
11
- import { resolveFieldType, tsTypeToAst } from "../lib/field-utils.js";
12
- import { inflect } from "../services/inflection.js";
13
- const { ts, b } = conjure;
14
- const { toExpr } = cast;
15
- /** Default export name: entityName + methodName (e.g., "UserFindById") */
16
- const defaultExportName = (entityName, methodName) => `${entityName}${methodName}`;
17
- /** Default function export name: camelCase of pg function name */
18
- const defaultFunctionExportName = (pgFunctionName) => inflect.camelCase(pgFunctionName);
19
- /**
20
- * Schema for serializable config options (JSON/YAML compatible).
21
- * Function options are typed separately in KyselyQueriesConfigInput.
22
- */
23
- const KyselyQueriesPluginConfigSchema = S.Struct({
24
- outputDir: S.optionalWith(S.String, { default: () => "kysely-queries" }),
25
- /**
26
- * Path to import DB type from (relative to outputDir).
27
- * Defaults to "../db.js" which works with kysely-types plugin output.
28
- * For node16/nodenext module resolution, use ".js" extension even for .ts files.
29
- */
30
- dbTypesPath: S.optionalWith(S.String, { default: () => "../db.js" }),
31
- /**
32
- * Whether to call .execute() / .executeTakeFirst() on queries.
33
- * When true (default), methods return Promise<Row> or Promise<Row[]>.
34
- * When false, methods return the query builder for further customization.
35
- */
36
- executeQueries: S.optionalWith(S.Boolean, { default: () => true }),
37
- /**
38
- * Whether to generate listMany() method for unfiltered table scans.
39
- * Disabled by default since unfiltered scans don't use indexes.
40
- * When enabled, generates: listMany(db, limit = 50, offset = 0)
41
- */
42
- generateListMany: S.optionalWith(S.Boolean, { default: () => false }),
43
- /**
44
- * Whether to generate function wrappers for stored functions.
45
- * When true (default), generates queries/mutations namespaces in functions.ts.
46
- */
47
- generateFunctions: S.optionalWith(S.Boolean, { default: () => true }),
48
- /**
49
- * Output file name for function wrappers (relative to outputDir).
50
- */
51
- functionsFile: S.optionalWith(S.String, { default: () => "functions.ts" }),
52
- /**
53
- * Export name function (validated as Any, properly typed in KyselyQueriesConfigInput)
54
- */
55
- exportName: S.optional(S.Any),
56
- /**
57
- * Function export name function (validated as Any, properly typed in KyselyQueriesConfigInput)
58
- */
59
- functionExportName: S.optional(S.Any),
60
- });
61
- /**
62
- * Get the Kysely table interface name from the entity.
63
- * Uses entity.name which is already PascalCase from inflection (e.g., Users).
64
- */
65
- const getTableTypeName = (entity) => entity.name;
66
- /**
67
- * Get the table reference for Kysely queries.
68
- * Uses schema-qualified name only if the schema is NOT in defaultSchemas.
69
- * This matches the keys in the DB interface from kysely-types plugin.
70
- */
71
- const getTableRef = (entity, defaultSchemas) => defaultSchemas.includes(entity.schemaName)
72
- ? entity.pgName
73
- : `${entity.schemaName}.${entity.pgName}`;
74
- /** Find a field in the row shape by column name */
75
- const findRowField = (entity, columnName) => entity.shapes.row.fields.find(f => f.columnName === columnName);
76
- /** Get the TypeScript type AST for a field */
77
- const getFieldTypeAst = (field, ctx) => {
78
- if (!field)
79
- return ts.string();
80
- const resolved = resolveFieldType(field, ctx.enums, ctx.ir.extensions);
81
- return resolved.enumDef ? ts.ref(resolved.enumDef.name) : tsTypeToAst(resolved.tsType);
82
- };
83
- // ============================================================================
84
- // FK Semantic Naming Helpers
85
- // ============================================================================
86
- /**
87
- * Find a belongsTo relation that uses the given column as its local FK column.
88
- * For single-column indexes only.
89
- */
90
- const findRelationForColumn = (entity, columnName) => entity.relations.find(r => r.kind === "belongsTo" && r.columns.length === 1 && r.columns[0]?.local === columnName);
91
- /**
92
- * Derive semantic name for an FK-based lookup.
93
- * Priority: @fieldName tag → column minus _id suffix → target entity name
94
- */
95
- const deriveSemanticName = (relation, columnName) => {
96
- // 1. Check for @fieldName smart tag
97
- if (relation.tags.fieldName && typeof relation.tags.fieldName === "string") {
98
- return relation.tags.fieldName;
99
- }
100
- // 2. Strip common FK suffixes from column name
101
- const suffixes = ["_id", "_fk", "Id", "Fk"];
102
- for (const suffix of suffixes) {
103
- if (columnName.endsWith(suffix)) {
104
- const stripped = columnName.slice(0, -suffix.length);
105
- if (stripped.length > 0)
106
- return stripped;
107
- }
108
- }
109
- // 3. Fall back to target entity name (lowercased first char)
110
- const target = relation.targetEntity;
111
- return target.charAt(0).toLowerCase() + target.slice(1);
112
- };
113
- /**
114
- * Convert to PascalCase for use in method names.
115
- * Handles snake_case (created_at → CreatedAt) and regular strings.
116
- */
117
- const toPascalCase = (s) => inflect.pascalCase(s);
118
- // ============================================================================
119
- // AST Building Helpers
120
- // ============================================================================
121
- /** Create identifier */
122
- const id = (name) => b.identifier(name);
123
- /** Create member expression: obj.prop */
124
- const member = (obj, prop) => b.memberExpression(toExpr(obj), id(prop));
125
- /** Create method call: obj.method(args) */
126
- const call = (obj, method, args = []) => b.callExpression(member(obj, method), args.map(toExpr));
127
- /** Create string literal */
128
- const str = (value) => b.stringLiteral(value);
129
- /** Create typed parameter: name: Type */
130
- const typedParam = (name, type) => {
131
- const param = id(name);
132
- param.typeAnnotation = b.tsTypeAnnotation(cast.toTSType(type));
133
- return param;
134
- };
135
- /** Create optional typed parameter: name?: Type */
136
- const optionalTypedParam = (name, type) => {
137
- const param = typedParam(name, type);
138
- param.optional = true;
139
- return param;
140
- };
141
- /**
142
- * Build Kysely query chain starting with db.selectFrom('table')
143
- */
144
- const selectFrom = (tableRef) => call(id("db"), "selectFrom", [str(tableRef)]);
145
- /**
146
- * Build Kysely query chain: db.insertInto('table')
147
- */
148
- const insertInto = (tableRef) => call(id("db"), "insertInto", [str(tableRef)]);
149
- /**
150
- * Build Kysely query chain: db.updateTable('table')
151
- */
152
- const updateTable = (tableRef) => call(id("db"), "updateTable", [str(tableRef)]);
153
- /**
154
- * Build Kysely query chain: db.deleteFrom('table')
155
- */
156
- const deleteFrom = (tableRef) => call(id("db"), "deleteFrom", [str(tableRef)]);
157
- /**
158
- * Chain method call onto existing expression
159
- */
160
- const chain = (expr, method, args = []) => call(expr, method, args);
161
- /**
162
- * Create an exported const declaration: export const name = value
163
- */
164
- const exportConst = (name, value) => {
165
- const constDecl = b.variableDeclaration("const", [
166
- b.variableDeclarator(id(name), toExpr(value))
167
- ]);
168
- return b.exportNamedDeclaration(constDecl, []);
169
- };
170
- /**
171
- * Build arrow function expression: (params) => body
172
- */
173
- const arrowFn = (params, body) => {
174
- const fn = b.arrowFunctionExpression(params.map(p => p), toExpr(body));
175
- return fn;
176
- };
177
- /**
178
- * Build object property: key: value
179
- */
180
- const objProp = (key, value) => {
181
- const prop = b.objectProperty(id(key), toExpr(value));
182
- return prop;
183
- };
184
- // ============================================================================
185
- // PostgreSQL Type Name to TypeScript Mapping
186
- // ============================================================================
187
- /**
188
- * Map PostgreSQL type name to TypeScript type string.
189
- * Used for function argument and return type resolution.
190
- */
191
- const pgTypeNameToTs = (typeName) => {
192
- // Normalize: strip schema prefix if present
193
- const baseName = typeName.includes(".") ? typeName.split(".").pop() : typeName;
194
- switch (baseName) {
195
- // Boolean
196
- case "bool":
197
- case "boolean":
198
- return "boolean";
199
- // Integer types → number
200
- case "int2":
201
- case "smallint":
202
- case "int4":
203
- case "integer":
204
- case "int":
205
- case "oid":
206
- case "float4":
207
- case "real":
208
- case "float8":
209
- case "double precision":
210
- return "number";
211
- // Big integers/numeric → string (to avoid precision loss)
212
- case "int8":
213
- case "bigint":
214
- case "numeric":
215
- case "decimal":
216
- case "money":
217
- return "string";
218
- // Text types → string
219
- case "text":
220
- case "varchar":
221
- case "character varying":
222
- case "char":
223
- case "character":
224
- case "bpchar":
225
- case "name":
226
- case "xml":
227
- case "bit":
228
- case "varbit":
229
- case "bit varying":
230
- case "uuid":
231
- case "inet":
232
- case "cidr":
233
- case "macaddr":
234
- case "macaddr8":
235
- case "time":
236
- case "timetz":
237
- case "time with time zone":
238
- case "time without time zone":
239
- case "interval":
240
- return "string";
241
- // Date/Time with date component → Date
242
- case "date":
243
- case "timestamp":
244
- case "timestamptz":
245
- case "timestamp with time zone":
246
- case "timestamp without time zone":
247
- return "Date";
248
- // JSON → unknown
249
- case "json":
250
- case "jsonb":
251
- case "jsonpath":
252
- return "unknown";
253
- // Binary → Buffer
254
- case "bytea":
255
- return "Buffer";
256
- // Void
257
- case "void":
258
- return "void";
259
- // Default to unknown
260
- default:
261
- return "unknown";
262
- }
263
- };
264
- /**
265
- * Check if a function argument type matches a table/view entity (row type argument).
266
- * Functions with row-type arguments are computed fields (e.g., posts_short_body(posts))
267
- * and should be excluded from function wrapper generation.
268
- */
269
- const hasRowTypeArg = (arg, ir) => {
270
- const tableEntities = getTableEntities(ir);
271
- // Check if arg.typeName matches a table entity's qualified name
272
- // Format: "schema.tablename" or just "tablename" for public schema
273
- return tableEntities.some(entity => {
274
- const qualifiedName = `${entity.schemaName}.${entity.pgName}`;
275
- return arg.typeName === qualifiedName || arg.typeName === entity.pgName;
276
- });
277
- };
278
- /**
279
- * Check if a function should be included in generated wrappers.
280
- *
281
- * Includes functions that:
282
- * - Have canExecute permission
283
- * - Are not trigger functions
284
- * - Are not from extensions
285
- * - Are not @omit tagged
286
- * - Don't have row-type arguments (computed fields)
287
- */
288
- const isGeneratableFunction = (fn, ir) => {
289
- if (!fn.canExecute)
290
- return false;
291
- if (fn.returnTypeName === "trigger")
292
- return false;
293
- if (fn.isFromExtension)
294
- return false;
295
- if (fn.tags.omit === true)
296
- return false;
297
- // Check for row-type args (computed field pattern)
298
- if (fn.args.some(arg => hasRowTypeArg(arg, ir)))
299
- return false;
300
- return true;
301
- };
302
- /**
303
- * Categorize functions by volatility.
304
- * Volatile functions go in mutations namespace, stable/immutable in queries.
305
- */
306
- const categorizeFunction = (fn) => fn.volatility === "volatile" ? "mutations" : "queries";
307
- /**
308
- * Get all generatable functions from the IR, categorized by volatility.
309
- */
310
- const getGeneratableFunctions = (ir) => {
311
- const all = getFunctionEntities(ir).filter(fn => isGeneratableFunction(fn, ir));
312
- return {
313
- queries: all.filter(fn => categorizeFunction(fn) === "queries"),
314
- mutations: all.filter(fn => categorizeFunction(fn) === "mutations"),
315
- };
316
- };
317
- /**
318
- * Resolve a function's return type to TypeScript type information.
319
- */
320
- const resolveReturnType = (fn, ir) => {
321
- const returnTypeName = fn.returnTypeName;
322
- const isArray = fn.returnsSet;
323
- // 1. Check if it's a table return type
324
- const tableEntities = getTableEntities(ir);
325
- const tableMatch = tableEntities.find(entity => {
326
- const qualifiedName = `${entity.schemaName}.${entity.pgName}`;
327
- return returnTypeName === qualifiedName || returnTypeName === entity.pgName;
328
- });
329
- if (tableMatch) {
330
- return {
331
- tsType: tableMatch.name,
332
- isArray,
333
- isScalar: false,
334
- needsImport: tableMatch.name,
335
- returnEntity: tableMatch,
336
- };
337
- }
338
- // 2. Check if it's a composite type return
339
- const compositeEntities = getCompositeEntities(ir);
340
- const compositeMatch = compositeEntities.find(entity => {
341
- const qualifiedName = `${entity.schemaName}.${entity.pgName}`;
342
- return returnTypeName === qualifiedName || returnTypeName === entity.pgName;
343
- });
344
- if (compositeMatch) {
345
- return {
346
- tsType: compositeMatch.name,
347
- isArray,
348
- isScalar: false,
349
- needsImport: compositeMatch.name,
350
- returnEntity: compositeMatch,
351
- };
352
- }
353
- // 3. It's a scalar type - map via type name
354
- // Handle "schema.typename" format by extracting just the type name
355
- const baseTypeName = returnTypeName.includes(".")
356
- ? returnTypeName.split(".").pop()
357
- : returnTypeName;
358
- const tsType = pgTypeNameToTs(baseTypeName);
359
- return {
360
- tsType,
361
- isArray,
362
- isScalar: true,
363
- };
364
- };
365
- /**
366
- * Resolve a function argument to TypeScript type information.
367
- */
368
- const resolveArg = (arg, ir) => {
369
- const typeName = arg.typeName;
370
- // Check if it's an array type (ends with [])
371
- const isArrayType = typeName.endsWith("[]");
372
- const baseTypeName = isArrayType ? typeName.slice(0, -2) : typeName;
373
- // Check enums
374
- const enums = getEnumEntities(ir);
375
- const enumMatch = enums.find(e => {
376
- const qualifiedName = `${e.schemaName}.${e.pgName}`;
377
- return baseTypeName === qualifiedName || baseTypeName === e.pgName;
378
- });
379
- if (enumMatch) {
380
- const tsType = isArrayType ? `${enumMatch.name}[]` : enumMatch.name;
381
- return {
382
- name: arg.name || "arg",
383
- tsType,
384
- isOptional: arg.hasDefault,
385
- needsImport: enumMatch.name,
386
- };
387
- }
388
- // Check composites
389
- const composites = getCompositeEntities(ir);
390
- const compositeMatch = composites.find(e => {
391
- const qualifiedName = `${e.schemaName}.${e.pgName}`;
392
- return baseTypeName === qualifiedName || baseTypeName === e.pgName;
393
- });
394
- if (compositeMatch) {
395
- const tsType = isArrayType ? `${compositeMatch.name}[]` : compositeMatch.name;
396
- return {
397
- name: arg.name || "arg",
398
- tsType,
399
- isOptional: arg.hasDefault,
400
- needsImport: compositeMatch.name,
401
- };
402
- }
403
- // Scalar type - map via type name
404
- // Handle "schema.typename" format
405
- const scalarBase = baseTypeName.includes(".")
406
- ? baseTypeName.split(".").pop()
407
- : baseTypeName;
408
- const scalarTs = pgTypeNameToTs(scalarBase);
409
- const tsType = isArrayType ? `${scalarTs}[]` : scalarTs;
410
- return {
411
- name: arg.name || "arg",
412
- tsType,
413
- isOptional: arg.hasDefault,
414
- };
415
- };
416
- /**
417
- * Resolve all arguments for a function.
418
- */
419
- const resolveArgs = (fn, ir) => fn.args.map(arg => resolveArg(arg, ir));
420
- // ============================================================================
421
- // Function Wrapper AST Generation
422
- // ============================================================================
423
- /**
424
- * Generate a typed parameter with explicit type annotation from type string.
425
- */
426
- const typedParamFromString = (name, typeStr) => {
427
- const param = id(name);
428
- // Map type string to AST
429
- let typeAst;
430
- switch (typeStr) {
431
- case "string":
432
- typeAst = ts.string();
433
- break;
434
- case "number":
435
- typeAst = ts.number();
436
- break;
437
- case "boolean":
438
- typeAst = ts.boolean();
439
- break;
440
- case "Date":
441
- typeAst = ts.ref("Date");
442
- break;
443
- case "Buffer":
444
- typeAst = ts.ref("Buffer");
445
- break;
446
- case "unknown":
447
- typeAst = ts.unknown();
448
- break;
449
- case "void":
450
- typeAst = ts.void();
451
- break;
452
- default:
453
- // Handle array types like "string[]"
454
- if (typeStr.endsWith("[]")) {
455
- const elemType = typeStr.slice(0, -2);
456
- const elemAst = elemType === "string" ? ts.string()
457
- : elemType === "number" ? ts.number()
458
- : elemType === "boolean" ? ts.boolean()
459
- : ts.ref(elemType);
460
- typeAst = ts.array(elemAst);
461
- }
462
- else {
463
- // Assume it's a type reference (composite, enum, etc.)
464
- typeAst = ts.ref(typeStr);
465
- }
466
- }
467
- param.typeAnnotation = b.tsTypeAnnotation(cast.toTSType(typeAst));
468
- return param;
469
- };
470
- /**
471
- * Generate an optional typed parameter with explicit type annotation.
472
- */
473
- const optionalTypedParamFromString = (name, typeStr) => {
474
- const param = typedParamFromString(name, typeStr);
475
- param.optional = true;
476
- return param;
477
- };
478
- /**
479
- * Get the fully qualified function name for use in eb.fn call.
480
- */
481
- const getFunctionQualifiedName = (fn) => `${fn.schemaName}.${fn.pgName}`;
482
- /**
483
- * Generate a function wrapper method as an object property.
484
- *
485
- * Patterns:
486
- * - SETOF/table return: db.selectFrom(eb => eb.fn<Type>(...).as('f')).selectAll().execute()
487
- * - Single row return: db.selectFrom(eb => eb.fn<Type>(...).as('f')).selectAll().executeTakeFirst()
488
- * - Scalar return: db.selectNoFrom(eb => eb.fn<Type>(...).as('result')).executeTakeFirst().then(r => r?.result)
489
- */
490
- const generateFunctionWrapper = (fn, ir, executeQueries, functionExportName) => {
491
- const resolvedReturn = resolveReturnType(fn, ir);
492
- const resolvedArgs = resolveArgs(fn, ir);
493
- const qualifiedName = getFunctionQualifiedName(fn);
494
- // Build eb.val(arg) for each argument
495
- const fnArgs = resolvedArgs.map(arg => call(id("eb"), "val", [id(arg.name)]));
496
- // Build eb.fn<Type>('schema.fn_name', [args]).as('alias')
497
- // The type parameter is the return type
498
- const returnTypeAst = resolvedReturn.isScalar
499
- ? typedParamFromString("_", resolvedReturn.tsType).typeAnnotation.typeAnnotation
500
- : ts.ref(resolvedReturn.tsType);
501
- // Create eb.fn with type parameter: eb.fn<Type>
502
- const fnMember = b.memberExpression(id("eb"), id("fn"));
503
- const fnWithType = b.tsInstantiationExpression(fnMember, b.tsTypeParameterInstantiation([cast.toTSType(returnTypeAst)]));
504
- // Call it: eb.fn<Type>(name, args)
505
- const fnCallBase = b.callExpression(fnWithType, [str(qualifiedName), b.arrayExpression(fnArgs.map(toExpr))]);
506
- // .as('f') or .as('result') for scalar
507
- const alias = resolvedReturn.isScalar ? "result" : "f";
508
- const fnCallWithAlias = call(fnCallBase, "as", [str(alias)]);
509
- // Arrow function for selectFrom callback: eb => eb.fn<...>(...).as('f')
510
- const selectCallback = arrowFn([id("eb")], fnCallWithAlias);
511
- // Build the query chain
512
- let query;
513
- if (resolvedReturn.isScalar) {
514
- // Scalar: db.selectNoFrom(eb => ...).executeTakeFirst()
515
- // Returns { result: T } | undefined - caller accesses .result
516
- query = call(id("db"), "selectNoFrom", [selectCallback]);
517
- if (executeQueries) {
518
- query = chain(query, "executeTakeFirst");
519
- }
520
- }
521
- else {
522
- // Table/composite: db.selectFrom(eb => ...).selectAll()
523
- query = chain(call(id("db"), "selectFrom", [selectCallback]), "selectAll");
524
- if (executeQueries) {
525
- // SETOF → .execute(), single row → .executeTakeFirst()
526
- query = chain(query, resolvedReturn.isArray ? "execute" : "executeTakeFirst");
527
- }
528
- }
529
- // Build the parameters: (db: Kysely<DB>, arg1: Type1, arg2?: Type2, ...)
530
- const params = [
531
- typedParam("db", ts.ref("Kysely", [ts.ref("DB")])),
532
- ...resolvedArgs.map(arg => arg.isOptional
533
- ? optionalTypedParamFromString(arg.name, arg.tsType)
534
- : typedParamFromString(arg.name, arg.tsType))
535
- ];
536
- const wrapperFn = arrowFn(params, query);
537
- const exportName = functionExportName(fn.pgName);
538
- const constDecl = b.variableDeclaration("const", [
539
- b.variableDeclarator(id(exportName), wrapperFn)
540
- ]);
541
- return b.exportNamedDeclaration(constDecl, []);
542
- };
543
- /**
544
- * Collect all type imports needed for function wrappers.
545
- */
546
- const collectFunctionTypeImports = (functions, ir) => {
547
- const imports = new Set();
548
- for (const fn of functions) {
549
- const resolvedReturn = resolveReturnType(fn, ir);
550
- if (resolvedReturn.needsImport) {
551
- imports.add(resolvedReturn.needsImport);
552
- }
553
- for (const arg of resolveArgs(fn, ir)) {
554
- if (arg.needsImport) {
555
- imports.add(arg.needsImport);
556
- }
557
- }
558
- }
559
- return imports;
560
- };
561
- // ============================================================================
562
- // CRUD Method Generators
563
- // ============================================================================
564
- /**
565
- * Generate findById method:
566
- * export const UserFindById = (db, id) => db.selectFrom('table').selectAll().where('id', '=', id).executeTakeFirst()
567
- */
568
- const generateFindById = (ctx) => {
569
- const { entity, executeQueries, defaultSchemas, entityName, exportName } = ctx;
570
- if (!entity.primaryKey || !entity.permissions.canSelect)
571
- return undefined;
572
- const pkColName = entity.primaryKey.columns[0];
573
- const pkField = findRowField(entity, pkColName);
574
- if (!pkField)
575
- return undefined;
576
- const tableRef = getTableRef(entity, defaultSchemas);
577
- const fieldName = pkField.name;
578
- const fieldType = getFieldTypeAst(pkField, ctx);
579
- // db.selectFrom('table').selectAll().where('col', '=', id)
580
- let query = chain(chain(selectFrom(tableRef), "selectAll"), "where", [str(pkColName), str("="), id(fieldName)]);
581
- if (executeQueries) {
582
- query = chain(query, "executeTakeFirst");
583
- }
584
- const fn = arrowFn([typedParam("db", ts.ref("Kysely", [ts.ref("DB")])), typedParam(fieldName, fieldType)], query);
585
- return exportConst(exportName(entityName, "FindById"), fn);
586
- };
587
- /** Default limit for findMany queries */
588
- const DEFAULT_LIMIT = 50;
589
- /** Default offset for findMany queries */
590
- const DEFAULT_OFFSET = 0;
591
- /**
592
- * Create a parameter with a default value: name = defaultValue
593
- * Type is inferred from the default value, no explicit annotation.
594
- */
595
- const paramWithDefault = (name, defaultValue) => b.assignmentPattern(id(name), toExpr(defaultValue));
596
- /**
597
- * Generate listMany method with pagination defaults:
598
- * export const UserListMany = (db, limit = 50, offset = 0) => db.selectFrom('table').selectAll()
599
- * .limit(limit).offset(offset).execute()
600
- */
601
- const generateListMany = (ctx) => {
602
- const { entity, executeQueries, defaultSchemas, entityName, exportName } = ctx;
603
- if (!entity.permissions.canSelect)
604
- return undefined;
605
- const tableRef = getTableRef(entity, defaultSchemas);
606
- // Build query: db.selectFrom('table').selectAll().limit(limit).offset(offset)
607
- let query = chain(chain(chain(selectFrom(tableRef), "selectAll"), "limit", [id("limit")]), "offset", [id("offset")]);
608
- // Add .execute() if executeQueries is true
609
- if (executeQueries) {
610
- query = chain(query, "execute");
611
- }
612
- const fn = arrowFn([
613
- typedParam("db", ts.ref("Kysely", [ts.ref("DB")])),
614
- paramWithDefault("limit", b.numericLiteral(DEFAULT_LIMIT)),
615
- paramWithDefault("offset", b.numericLiteral(DEFAULT_OFFSET)),
616
- ], query);
617
- return exportConst(exportName(entityName, "ListMany"), fn);
618
- };
619
- /**
620
- * Generate create method:
621
- * export const UserCreate = (db, data) => db.insertInto('table').values(data).returningAll().executeTakeFirstOrThrow()
622
- */
623
- const generateCreate = (ctx) => {
624
- const { entity, executeQueries, defaultSchemas, entityName, exportName } = ctx;
625
- if (!entity.permissions.canInsert)
626
- return undefined;
627
- const tableRef = getTableRef(entity, defaultSchemas);
628
- const tableTypeName = getTableTypeName(entity);
629
- // db.insertInto('table').values(data).returningAll()
630
- let query = chain(chain(insertInto(tableRef), "values", [id("data")]), "returningAll");
631
- if (executeQueries) {
632
- query = chain(query, "executeTakeFirstOrThrow");
633
- }
634
- // Use Insertable<TableTypeName> for the data parameter
635
- const fn = arrowFn([
636
- typedParam("db", ts.ref("Kysely", [ts.ref("DB")])),
637
- typedParam("data", ts.ref("Insertable", [ts.ref(tableTypeName)])),
638
- ], query);
639
- return exportConst(exportName(entityName, "Create"), fn);
640
- };
641
- /**
642
- * Generate update method:
643
- * export const UserUpdate = (db, id, data) => db.updateTable('table').set(data).where('id', '=', id).returningAll().executeTakeFirstOrThrow()
644
- */
645
- const generateUpdate = (ctx) => {
646
- const { entity, executeQueries, defaultSchemas, entityName, exportName } = ctx;
647
- if (!entity.primaryKey || !entity.permissions.canUpdate)
648
- return undefined;
649
- const pkColName = entity.primaryKey.columns[0];
650
- const pkField = findRowField(entity, pkColName);
651
- if (!pkField)
652
- return undefined;
653
- const tableRef = getTableRef(entity, defaultSchemas);
654
- const fieldName = pkField.name;
655
- const fieldType = getFieldTypeAst(pkField, ctx);
656
- const tableTypeName = getTableTypeName(entity);
657
- // db.updateTable('table').set(data).where('id', '=', id).returningAll()
658
- let query = chain(chain(chain(updateTable(tableRef), "set", [id("data")]), "where", [str(pkColName), str("="), id(fieldName)]), "returningAll");
659
- if (executeQueries) {
660
- query = chain(query, "executeTakeFirstOrThrow");
661
- }
662
- // Use Updateable<TableTypeName> for the data parameter
663
- const fn = arrowFn([
664
- typedParam("db", ts.ref("Kysely", [ts.ref("DB")])),
665
- typedParam(fieldName, fieldType),
666
- typedParam("data", ts.ref("Updateable", [ts.ref(tableTypeName)])),
667
- ], query);
668
- return exportConst(exportName(entityName, "Update"), fn);
669
- };
670
- /**
671
- * Generate delete method:
672
- * export const UserRemove = (db, id) => db.deleteFrom('table').where('id', '=', id).execute()
673
- */
674
- const generateDelete = (ctx) => {
675
- const { entity, executeQueries, defaultSchemas, entityName, exportName } = ctx;
676
- if (!entity.primaryKey || !entity.permissions.canDelete)
677
- return undefined;
678
- const pkColName = entity.primaryKey.columns[0];
679
- const pkField = findRowField(entity, pkColName);
680
- if (!pkField)
681
- return undefined;
682
- const tableRef = getTableRef(entity, defaultSchemas);
683
- const fieldName = pkField.name;
684
- const fieldType = getFieldTypeAst(pkField, ctx);
685
- // db.deleteFrom('table').where('id', '=', id)
686
- let query = chain(deleteFrom(tableRef), "where", [str(pkColName), str("="), id(fieldName)]);
687
- if (executeQueries) {
688
- query = chain(query, "execute");
689
- }
690
- const fn = arrowFn([typedParam("db", ts.ref("Kysely", [ts.ref("DB")])), typedParam(fieldName, fieldType)], query);
691
- return exportConst(exportName(entityName, "Remove"), fn);
692
- };
693
- /** Generate all CRUD methods for an entity */
694
- const generateCrudMethods = (ctx) => [
695
- generateFindById(ctx),
696
- ctx.generateListMany ? generateListMany(ctx) : undefined,
697
- generateCreate(ctx),
698
- generateUpdate(ctx),
699
- generateDelete(ctx),
700
- ].filter((p) => p != null);
701
- // ============================================================================
702
- // Index-based Lookup Functions
703
- // ============================================================================
704
- /** Check if an index should generate a lookup function */
705
- const shouldGenerateLookup = (index) => !index.isPartial &&
706
- !index.hasExpressions &&
707
- index.columns.length === 1 &&
708
- index.method !== "gin" &&
709
- index.method !== "gist";
710
- /**
711
- * Generate the method name portion for an index-based lookup.
712
- * Uses semantic naming when the column corresponds to an FK relation.
713
- */
714
- const generateLookupMethodName = (entity, index, relation) => {
715
- const isUnique = isUniqueLookup(entity, index);
716
- // Uses "FindOneBy" or "FindManyBy" suffix
717
- const suffix = isUnique ? "FindOneBy" : "FindManyBy";
718
- // Use semantic name if FK relation exists, otherwise fall back to column name
719
- const columnName = index.columnNames[0];
720
- const byName = relation
721
- ? deriveSemanticName(relation, columnName)
722
- : index.columns[0];
723
- return `${suffix}${toPascalCase(byName)}`;
724
- };
725
- /**
726
- * Generate a lookup method for a single-column index.
727
- * Uses semantic parameter naming when the column corresponds to an FK relation.
728
- */
729
- const generateLookupMethod = (index, ctx) => {
730
- const { entity, executeQueries, defaultSchemas, entityName, exportName } = ctx;
731
- const tableRef = getTableRef(entity, defaultSchemas);
732
- const columnName = index.columnNames[0];
733
- const field = findRowField(entity, columnName);
734
- const fieldName = field?.name ?? index.columns[0];
735
- const isUnique = isUniqueLookup(entity, index);
736
- // Check if this index column corresponds to an FK relation
737
- const relation = findRelationForColumn(entity, columnName);
738
- // Use semantic param name if FK relation exists, otherwise use field name
739
- const paramName = relation
740
- ? deriveSemanticName(relation, columnName)
741
- : fieldName;
742
- // For FK columns, use indexed access on Selectable<TableType> to get the unwrapped type
743
- // (Kysely's Generated<T> types need Selectable to unwrap for use in where clauses)
744
- // For regular columns, use the field's type directly
745
- const useSemanticNaming = relation !== undefined && paramName !== fieldName;
746
- const tableTypeName = getTableTypeName(entity);
747
- const paramType = useSemanticNaming
748
- ? ts.indexedAccess(ts.ref("Selectable", [ts.ref(tableTypeName)]), ts.literal(fieldName))
749
- : getFieldTypeAst(field, ctx);
750
- // db.selectFrom('table').selectAll().where('col', '=', value)
751
- let query = chain(chain(selectFrom(tableRef), "selectAll"), "where", [str(columnName), str("="), id(paramName)]);
752
- if (executeQueries) {
753
- query = chain(query, isUnique ? "executeTakeFirst" : "execute");
754
- }
755
- const fn = arrowFn([typedParam("db", ts.ref("Kysely", [ts.ref("DB")])), typedParam(paramName, paramType)], query);
756
- const methodName = generateLookupMethodName(entity, index, relation);
757
- return exportConst(exportName(entityName, methodName), fn);
758
- };
759
- /**
760
- * Check if a column is covered by a unique constraint (not just unique index).
761
- * This helps determine if a non-unique B-tree index on the column still
762
- * returns at most one row.
763
- */
764
- const columnHasUniqueConstraint = (entity, columnName) => {
765
- const constraints = entity.pgClass.getConstraints();
766
- return constraints.some(c => {
767
- // 'u' = unique constraint, 'p' = primary key
768
- if (c.contype !== "u" && c.contype !== "p")
769
- return false;
770
- // Single-column constraint on our column?
771
- const conkey = c.conkey ?? [];
772
- if (conkey.length !== 1)
773
- return false;
774
- // Find the attribute with this attnum
775
- const attrs = entity.pgClass.getAttributes();
776
- const attr = attrs.find(a => a.attnum === conkey[0]);
777
- return attr?.attname === columnName;
778
- });
779
- };
780
- /**
781
- * Determine if a lookup should be treated as unique (returns one row).
782
- * True if: index is unique, index is primary, OR column has unique constraint.
783
- */
784
- const isUniqueLookup = (entity, index) => {
785
- if (index.isUnique || index.isPrimary)
786
- return true;
787
- // Check if the single column has a unique constraint
788
- const columnName = index.columnNames[0];
789
- return columnName ? columnHasUniqueConstraint(entity, columnName) : false;
790
- };
791
- /** Generate lookup methods for all eligible indexes, deduplicating by column */
792
- const generateLookupMethods = (ctx) => {
793
- const eligibleIndexes = ctx.entity.indexes
794
- .filter(index => shouldGenerateLookup(index) && !index.isPrimary && ctx.entity.permissions.canSelect);
795
- // Group by column name, keeping only one index per column
796
- // Prefer unique indexes, but also consider columns with unique constraints
797
- const byColumn = new Map();
798
- for (const index of eligibleIndexes) {
799
- const columnName = index.columnNames[0];
800
- const existing = byColumn.get(columnName);
801
- if (!existing) {
802
- byColumn.set(columnName, index);
803
- }
804
- else {
805
- // Prefer explicitly unique index over non-unique
806
- if (index.isUnique && !existing.isUnique) {
807
- byColumn.set(columnName, index);
808
- }
809
- }
810
- }
811
- return Array.from(byColumn.values()).map(index => generateLookupMethod(index, ctx));
812
- };
813
- // ============================================================================
814
- // Plugin Definition
815
- // ============================================================================
816
- export const kyselyQueriesPlugin = definePlugin({
817
- name: "kysely-queries",
818
- provides: ["queries", "queries:kysely"],
819
- requires: [], // No dependency on types:kysely for now - uses external kysely-codegen types
820
- configSchema: KyselyQueriesPluginConfigSchema,
821
- inflection: {
822
- outputFile: ctx => `${ctx.entityName}.ts`,
823
- symbolName: (entityName, artifactKind) => `${entityName}${artifactKind}`,
824
- },
825
- run: (ctx, rawConfig) => {
826
- // Resolve config with function defaults
827
- const config = {
828
- ...rawConfig,
829
- exportName: rawConfig.exportName ?? defaultExportName,
830
- functionExportName: rawConfig.functionExportName ?? defaultFunctionExportName,
831
- };
832
- const enums = getEnumEntities(ctx.ir);
833
- const defaultSchemas = ctx.ir.schemas;
834
- const { dbTypesPath, executeQueries, generateListMany, exportName, functionExportName } = config;
835
- // Pre-compute function groupings by return entity name
836
- // Functions returning entities go in that entity's file; scalars go in functions.ts
837
- const functionsByEntity = new Map();
838
- const scalarFunctions = [];
839
- if (config.generateFunctions) {
840
- const { queries, mutations } = getGeneratableFunctions(ctx.ir);
841
- const allFunctions = [...queries, ...mutations];
842
- for (const fn of allFunctions) {
843
- const resolved = resolveReturnType(fn, ctx.ir);
844
- if (resolved.returnEntity) {
845
- const entityName = resolved.returnEntity.name;
846
- const existing = functionsByEntity.get(entityName) ?? [];
847
- functionsByEntity.set(entityName, [...existing, fn]);
848
- }
849
- else {
850
- scalarFunctions.push(fn);
851
- }
852
- }
853
- }
854
- getTableEntities(ctx.ir)
855
- .filter(entity => entity.tags.omit !== true)
856
- .forEach(entity => {
857
- const entityName = ctx.inflection.entityName(entity.pgClass, entity.tags);
858
- const genCtx = { entity, enums, ir: ctx.ir, defaultSchemas, dbTypesPath, executeQueries, generateListMany, entityName, exportName };
859
- const crudStatements = [...generateCrudMethods(genCtx), ...generateLookupMethods(genCtx)];
860
- // Get functions that return this entity
861
- const entityFunctions = functionsByEntity.get(entity.name) ?? [];
862
- if (crudStatements.length === 0 && entityFunctions.length === 0)
863
- return;
864
- const fileNameCtx = {
865
- entityName,
866
- pgName: entity.pgName,
867
- schema: entity.schemaName,
868
- inflection: ctx.inflection,
869
- entity,
870
- };
871
- const filePath = `${config.outputDir}/${ctx.pluginInflection.outputFile(fileNameCtx)}`;
872
- // All statements for the file: CRUD methods + function wrappers
873
- const statements = [...crudStatements];
874
- // Add function wrappers as flat exports
875
- for (const fn of entityFunctions) {
876
- statements.push(generateFunctionWrapper(fn, ctx.ir, executeQueries, config.functionExportName));
877
- }
878
- const file = ctx
879
- .file(filePath);
880
- // Import Kysely type and DB from kysely-codegen output
881
- file.import({ kind: "package", types: ["Kysely"], from: "kysely" });
882
- file.import({ kind: "relative", types: ["DB"], from: dbTypesPath });
883
- // Import Insertable/Updateable helper types and table type if we generate create/update
884
- const tableTypeName = getTableTypeName(entity);
885
- // Check if any lookup methods use semantic naming (FK with Selectable indexed access)
886
- const hasSemanticLookups = entity.indexes.some(index => {
887
- if (!shouldGenerateLookup(index) || index.isPrimary)
888
- return false;
889
- const columnName = index.columnNames[0];
890
- const relation = findRelationForColumn(entity, columnName);
891
- if (!relation)
892
- return false;
893
- const paramName = deriveSemanticName(relation, columnName);
894
- const field = findRowField(entity, columnName);
895
- const fieldName = field?.name ?? index.columns[0];
896
- return paramName !== fieldName;
897
- });
898
- // Import table type if needed for Insertable/Updateable or semantic lookups
899
- const needsTableType = entity.permissions.canInsert || entity.permissions.canUpdate || hasSemanticLookups;
900
- if (needsTableType) {
901
- file.import({ kind: "relative", types: [tableTypeName], from: dbTypesPath });
902
- }
903
- // Import Selectable if we have semantic lookups (for unwrapping Generated<T>)
904
- if (hasSemanticLookups) {
905
- file.import({ kind: "package", types: ["Selectable"], from: "kysely" });
906
- }
907
- if (entity.permissions.canInsert) {
908
- file.import({ kind: "package", types: ["Insertable"], from: "kysely" });
909
- }
910
- if (entity.permissions.canUpdate) {
911
- file.import({ kind: "package", types: ["Updateable"], from: "kysely" });
912
- }
913
- // Import types needed by function args (for functions grouped into this file)
914
- if (entityFunctions.length > 0) {
915
- const fnTypeImports = collectFunctionTypeImports(entityFunctions, ctx.ir);
916
- // Remove the entity's own type (already in scope or self-referential)
917
- fnTypeImports.delete(entity.name);
918
- if (fnTypeImports.size > 0) {
919
- file.import({ kind: "relative", types: [...fnTypeImports], from: dbTypesPath });
920
- }
921
- }
922
- file.ast(conjure.program(...statements)).emit();
923
- });
924
- // Generate files for composite types that have functions returning them
925
- if (config.generateFunctions) {
926
- const composites = getCompositeEntities(ctx.ir);
927
- for (const composite of composites) {
928
- const compositeFunctions = functionsByEntity.get(composite.name) ?? [];
929
- if (compositeFunctions.length === 0)
930
- continue;
931
- const filePath = `${config.outputDir}/${composite.name}.ts`;
932
- const statements = compositeFunctions.map(fn => generateFunctionWrapper(fn, ctx.ir, executeQueries, config.functionExportName));
933
- const file = ctx.file(filePath);
934
- file.import({ kind: "package", types: ["Kysely"], from: "kysely" });
935
- file.import({ kind: "relative", types: ["DB"], from: dbTypesPath });
936
- // Import the composite type and any types needed by function args
937
- const fnTypeImports = collectFunctionTypeImports(compositeFunctions, ctx.ir);
938
- fnTypeImports.add(composite.name); // Always import the composite type
939
- file.import({ kind: "relative", types: [...fnTypeImports], from: dbTypesPath });
940
- file.ast(conjure.program(...statements)).emit();
941
- }
942
- }
943
- // Generate functions.ts for scalar-returning functions only
944
- if (config.generateFunctions && scalarFunctions.length > 0) {
945
- const filePath = `${config.outputDir}/${config.functionsFile}`;
946
- const statements = scalarFunctions.map(fn => generateFunctionWrapper(fn, ctx.ir, executeQueries, config.functionExportName));
947
- const file = ctx.file(filePath);
948
- // Import Kysely type and DB
949
- file.import({ kind: "package", types: ["Kysely"], from: "kysely" });
950
- file.import({ kind: "relative", types: ["DB"], from: dbTypesPath });
951
- // Import any types needed for function args (scalars don't need return type imports)
952
- const typeImports = collectFunctionTypeImports(scalarFunctions, ctx.ir);
953
- if (typeImports.size > 0) {
954
- file.import({ kind: "relative", types: [...typeImports], from: dbTypesPath });
955
- }
956
- file.ast(conjure.program(...statements)).emit();
957
- }
958
- },
959
- });
960
- //# sourceMappingURL=kysely-queries.js.map