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