@danielfgray/pg-sourcerer 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,28 +1,65 @@
1
1
  /**
2
- * SQL Queries Plugin - Generate raw SQL query functions using template strings
2
+ * SQL Queries Provider - Generate raw SQL query functions using template strings
3
3
  */
4
4
  import { Schema as S } from "effect";
5
5
  import { definePlugin } from "../services/plugin.js";
6
- import { getTableEntities, getEnumEntities, getFunctionEntities, getCompositeEntities } from "../ir/semantic-ir.js";
7
- import { conjure, cast } from "../lib/conjure.js";
6
+ import { getTableEntities, getEnumEntities, getFunctionEntities, getCompositeEntities, } from "../ir/semantic-ir.js";
7
+ import { conjure } from "../lib/conjure.js";
8
8
  import { hex } from "../lib/hex.js";
9
9
  import { resolveFieldType, tsTypeToAst } from "../lib/field-utils.js";
10
10
  import { inflect } from "../services/inflection.js";
11
- const { ts, b, param } = conjure;
12
- // ============================================================================
13
- // Configuration
14
- // ============================================================================
15
- const SqlQueriesPluginConfig = S.Struct({
11
+ const { ts, b, param, asyncFn } = conjure;
12
+ /** Default export name: camelCase of methodName + entityName (e.g., "findUserById") */
13
+ const defaultExportName = (entityName, methodName) => {
14
+ // methodName is like "FindById", "Insert", "GetByUsername"
15
+ // We want: findUserById, insertUser, getUserByUsername
16
+ const camelMethod = methodName.charAt(0).toLowerCase() + methodName.slice(1);
17
+ // Insert entity name after the verb (find, insert, get, delete)
18
+ // Pattern: verb + Entity + rest (e.g., find + User + ById)
19
+ const verbMatch = camelMethod.match(/^(find|insert|delete|get)(.*)$/);
20
+ if (verbMatch) {
21
+ const [, verb, rest] = verbMatch;
22
+ return `${verb}${entityName}${rest}`;
23
+ }
24
+ // Fallback: just prepend entity
25
+ return `${camelMethod}${entityName}`;
26
+ };
27
+ const SqlQueriesPluginConfigSchema = S.Struct({
16
28
  outputDir: S.optionalWith(S.String, { default: () => "sql-queries" }),
29
+ /**
30
+ * Header content to prepend to each generated file.
31
+ * Must include the SQL client import (e.g., `import { sql } from "../db"`).
32
+ */
33
+ header: S.String,
17
34
  /** SQL query style. Defaults to "tag" (tagged template literals) */
18
- sqlStyle: S.optionalWith(S.Union(S.Literal("tag"), S.Literal("string")), { default: () => "tag" }),
35
+ sqlStyle: S.optionalWith(S.Union(S.Literal("tag"), S.Literal("string")), {
36
+ default: () => "tag",
37
+ }),
38
+ /**
39
+ * Use explicit column lists instead of SELECT *.
40
+ * When true, generates "SELECT col1, col2" which excludes omitted fields at runtime.
41
+ * Defaults to true.
42
+ */
43
+ explicitColumns: S.optionalWith(S.Boolean, { default: () => true }),
19
44
  /** Generate wrappers for PostgreSQL functions. Defaults to true. */
20
45
  generateFunctions: S.optionalWith(S.Boolean, { default: () => true }),
21
46
  /** Output file for scalar-returning functions. Defaults to "functions.ts". */
22
47
  functionsFile: S.optionalWith(S.String, { default: () => "functions.ts" }),
48
+ /** Export name function - use S.Any for schema, properly typed after resolution */
49
+ exportName: S.optional(S.Any),
50
+ /**
51
+ * Export style for generated query functions.
52
+ * - "flat": Individual exports (e.g., `export async function findById() {...}`)
53
+ * - "namespace": Single object export (e.g., `export const User = { findById: ... }`)
54
+ */
55
+ exportStyle: S.optionalWith(S.Literal("flat", "namespace"), { default: () => "flat" }),
23
56
  });
24
57
  /** Find a field in the row shape by column name */
25
58
  const findRowField = (entity, columnName) => entity.shapes.row.fields.find(f => f.columnName === columnName);
59
+ /** Build comma-separated column list from row shape fields */
60
+ const buildColumnList = (entity) => entity.shapes.row.fields.map(f => f.columnName).join(", ");
61
+ /** Build SELECT clause - explicit columns or * based on config */
62
+ const buildSelectClause = (entity, explicitColumns) => explicitColumns ? `select ${buildColumnList(entity)}` : "select *";
26
63
  /** Get the TypeScript type AST for a field */
27
64
  const getFieldTypeAst = (field, ctx) => {
28
65
  if (!field)
@@ -71,9 +108,16 @@ const toPascalCase = (s) => inflect.pascalCase(s);
71
108
  // ============================================================================
72
109
  // CRUD Function Generators
73
110
  // ============================================================================
74
- /** Generate findById function if entity has a primary key and canSelect permission */
111
+ /** Get TypeScript type string for a field */
112
+ const getFieldTypeString = (field, ctx) => {
113
+ if (!field)
114
+ return "string";
115
+ const resolved = resolveFieldType(field, ctx.enums, ctx.ir.extensions);
116
+ return resolved.enumDef ? resolved.enumDef.name : resolved.tsType;
117
+ };
118
+ /** Generate findById method if entity has a primary key and canSelect permission */
75
119
  const generateFindById = (ctx) => {
76
- const { entity, sqlStyle } = ctx;
120
+ const { entity, sqlStyle, entityName, exportName, explicitColumns } = ctx;
77
121
  if (!entity.primaryKey || !entity.permissions.canSelect)
78
122
  return undefined;
79
123
  const pkColName = entity.primaryKey.columns[0];
@@ -82,38 +126,73 @@ const generateFindById = (ctx) => {
82
126
  return undefined;
83
127
  const rowType = entity.shapes.row.name;
84
128
  const fieldName = pkField.name; // JS property name (e.g., "id")
129
+ const selectClause = buildSelectClause(entity, explicitColumns);
85
130
  const parts = {
86
- templateParts: [`select * from ${entity.schemaName}.${entity.pgName} where ${pkColName} = `, ""],
131
+ templateParts: [
132
+ `${selectClause} from ${entity.schemaName}.${entity.pgName} where ${pkColName} = `,
133
+ "",
134
+ ],
87
135
  params: [b.identifier(fieldName)],
88
136
  };
89
137
  // Build query and extract first row
90
138
  const queryExpr = hex.query(sqlStyle, parts, ts.array(ts.ref(rowType)));
91
139
  const varDecl = hex.firstRowDecl(sqlStyle, "result", queryExpr);
92
- return conjure.export.fn(hex.asyncFn(`find${entity.name}ById`, [param.pick([fieldName], rowType)], [
93
- varDecl,
94
- b.returnStatement(b.identifier("result")),
95
- ]));
140
+ const name = exportName(entityName, "FindById");
141
+ const fn = asyncFn(name, [param.pick([fieldName], rowType)], [varDecl, b.returnStatement(b.identifier("result"))]);
142
+ const meta = {
143
+ name,
144
+ kind: "read",
145
+ params: [
146
+ {
147
+ name: fieldName,
148
+ type: getFieldTypeString(pkField, ctx),
149
+ required: true,
150
+ columnName: pkColName,
151
+ source: "pk",
152
+ },
153
+ ],
154
+ returns: { type: rowType, nullable: true, isArray: false },
155
+ callSignature: { style: "named" },
156
+ };
157
+ return { name, fn, meta };
96
158
  };
97
- /** Generate findMany function with pagination if entity has canSelect permission */
159
+ /** Generate findMany method with pagination if entity has canSelect permission */
98
160
  const generateFindMany = (ctx) => {
99
- const { entity, sqlStyle } = ctx;
161
+ const { entity, sqlStyle, entityName, exportName, explicitColumns } = ctx;
100
162
  if (!entity.permissions.canSelect)
101
163
  return undefined;
102
164
  const rowType = entity.shapes.row.name;
165
+ const selectClause = buildSelectClause(entity, explicitColumns);
103
166
  const parts = {
104
- templateParts: [`select * from ${entity.schemaName}.${entity.pgName} limit `, ` offset `, ""],
167
+ templateParts: [
168
+ `${selectClause} from ${entity.schemaName}.${entity.pgName} limit `,
169
+ ` offset `,
170
+ "",
171
+ ],
105
172
  params: [b.identifier("limit"), b.identifier("offset")],
106
173
  };
107
- return conjure.export.fn(hex.asyncFn(`findMany${entity.name}s`, [
174
+ const name = exportName(entityName, "FindManys");
175
+ const fn = asyncFn(name, [
108
176
  param.destructured([
109
177
  { name: "limit", type: ts.number(), optional: true, defaultValue: b.numericLiteral(50) },
110
178
  { name: "offset", type: ts.number(), optional: true, defaultValue: b.numericLiteral(0) },
111
179
  ]),
112
- ], hex.returnQuery(sqlStyle, parts, ts.array(ts.ref(rowType)))));
180
+ ], hex.returnQuery(sqlStyle, parts, ts.array(ts.ref(rowType))));
181
+ const meta = {
182
+ name,
183
+ kind: "list",
184
+ params: [
185
+ { name: "limit", type: "number", required: false, source: "pagination" },
186
+ { name: "offset", type: "number", required: false, source: "pagination" },
187
+ ],
188
+ returns: { type: rowType, nullable: false, isArray: true },
189
+ callSignature: { style: "named" },
190
+ };
191
+ return { name, fn, meta };
113
192
  };
114
- /** Generate delete function if entity has a primary key and canDelete permission */
193
+ /** Generate delete method if entity has a primary key and canDelete permission */
115
194
  const generateDelete = (ctx) => {
116
- const { entity, sqlStyle } = ctx;
195
+ const { entity, sqlStyle, entityName, exportName } = ctx;
117
196
  if (!entity.primaryKey || !entity.permissions.canDelete)
118
197
  return undefined;
119
198
  const pkColName = entity.primaryKey.columns[0];
@@ -128,13 +207,28 @@ const generateDelete = (ctx) => {
128
207
  };
129
208
  // Delete returns void, no type parameter needed
130
209
  const queryExpr = hex.query(sqlStyle, parts);
131
- return conjure.export.fn(hex.asyncFn(`delete${entity.name}`, [param.pick([fieldName], rowType)], [
132
- b.expressionStatement(queryExpr),
133
- ]));
210
+ const name = exportName(entityName, "Delete");
211
+ const fn = asyncFn(name, [param.pick([fieldName], rowType)], [b.expressionStatement(queryExpr)]);
212
+ const meta = {
213
+ name,
214
+ kind: "delete",
215
+ params: [
216
+ {
217
+ name: fieldName,
218
+ type: getFieldTypeString(pkField, ctx),
219
+ required: true,
220
+ columnName: pkColName,
221
+ source: "pk",
222
+ },
223
+ ],
224
+ returns: { type: "void", nullable: false, isArray: false },
225
+ callSignature: { style: "named" },
226
+ };
227
+ return { name, fn, meta };
134
228
  };
135
- /** Generate insert function if entity has canInsert permission */
229
+ /** Generate insert method if entity has canInsert permission */
136
230
  const generateInsert = (ctx) => {
137
- const { entity, sqlStyle } = ctx;
231
+ const { entity, sqlStyle, entityName, exportName } = ctx;
138
232
  if (!entity.permissions.canInsert)
139
233
  return undefined;
140
234
  // Use insert shape if available, otherwise fall back to row
@@ -146,10 +240,15 @@ const generateInsert = (ctx) => {
146
240
  if (insertableFields.length === 0)
147
241
  return undefined;
148
242
  const columnNames = insertableFields.map(f => f.columnName);
149
- const fieldNames = insertableFields.map(f => f.name);
150
- // Build: insert into schema.table (col1, col2) values ($data.field1, $data.field2) returning *
243
+ // Build: insert into schema.table (col1, col2) values ($field1, $field2) returning *
151
244
  const columnList = columnNames.join(", ");
152
- const valuePlaceholders = fieldNames.map((_, i) => (i === 0 ? "" : ", "));
245
+ const valuePlaceholders = insertableFields.map((_, i) => (i === 0 ? "" : ", "));
246
+ // For optional fields (nullable or has default), use DEFAULT when undefined
247
+ // Required fields use the value directly
248
+ const paramExprs = insertableFields.map(f => {
249
+ const isOptional = f.optional || f.nullable;
250
+ return isOptional ? hex.defaultIfUndefined(f.name) : b.identifier(f.name);
251
+ });
153
252
  // Template parts: "insert into ... values (" + "" + ", " + ", " + ... + ") returning *"
154
253
  const parts = {
155
254
  templateParts: [
@@ -157,21 +256,33 @@ const generateInsert = (ctx) => {
157
256
  ...valuePlaceholders.slice(1),
158
257
  `) returning *`,
159
258
  ],
160
- params: fieldNames.map(f => b.memberExpression(b.identifier("data"), b.identifier(f), false)),
259
+ params: paramExprs,
161
260
  };
162
261
  const queryExpr = hex.query(sqlStyle, parts, ts.array(ts.ref(rowType)));
163
262
  const varDecl = hex.firstRowDecl(sqlStyle, "result", queryExpr);
164
- // Simple typed parameter: data: InsertType
165
- const dataParam = param.typed("data", ts.ref(insertType));
166
- return conjure.export.fn(hex.asyncFn(`insert${entity.name}`, [dataParam], [varDecl, b.returnStatement(b.identifier("result"))]));
263
+ // Destructured parameter - use Pick from insert type
264
+ const fieldNames = insertableFields.map(f => f.name);
265
+ const dataParam = param.pick(fieldNames, insertType);
266
+ const name = exportName(entityName, "Insert");
267
+ const fn = asyncFn(name, [dataParam], [varDecl, b.returnStatement(b.identifier("result"))]);
268
+ const meta = {
269
+ name,
270
+ kind: "create",
271
+ params: [
272
+ {
273
+ name: "data",
274
+ type: insertType,
275
+ required: true,
276
+ source: "body",
277
+ },
278
+ ],
279
+ returns: { type: rowType, nullable: false, isArray: false },
280
+ callSignature: { style: "named", bodyStyle: "spread" },
281
+ };
282
+ return { name, fn, meta };
167
283
  };
168
- /** Generate all CRUD functions for an entity */
169
- const generateCrudFunctions = (ctx) => [
170
- generateFindById(ctx),
171
- generateFindMany(ctx),
172
- generateInsert(ctx),
173
- generateDelete(ctx),
174
- ].filter((s) => s != null);
284
+ /** Generate all CRUD methods for an entity */
285
+ const generateCrudMethods = (ctx) => [generateFindById(ctx), generateFindMany(ctx), generateInsert(ctx), generateDelete(ctx)].filter((m) => m != null);
175
286
  // ============================================================================
176
287
  // Index-based Lookup Functions
177
288
  // ============================================================================
@@ -182,27 +293,22 @@ const shouldGenerateLookup = (index) => !index.isPartial &&
182
293
  index.method !== "gin" &&
183
294
  index.method !== "gist";
184
295
  /**
185
- * Generate a function name for an index-based lookup.
186
- * Uses semantic naming when the column corresponds to an FK relation.
296
+ * Generate the method name portion for an index-based lookup.
297
+ * Returns PascalCase like "GetByUsername" or "GetsByUser" for use with exportName.
187
298
  */
188
- const generateLookupName = (entity, index, relation) => {
299
+ const generateLookupMethodName = (index, relation, columnName) => {
189
300
  const isUnique = index.isUnique || index.isPrimary;
190
- const entityName = isUnique
191
- ? entity.name.replace(/s$/, "") // singular for unique
192
- : entity.name.replace(/s$/, "") + "s"; // plural for non-unique
301
+ const prefix = isUnique ? "GetBy" : "GetsBy";
193
302
  // Use semantic name if FK relation exists, otherwise fall back to column name
194
- const columnName = index.columnNames[0];
195
- const byName = relation
196
- ? deriveSemanticName(relation, columnName)
197
- : index.columns[0];
198
- return `get${entityName}By${toPascalCase(byName)}`;
303
+ const byName = relation ? deriveSemanticName(relation, columnName) : index.columns[0];
304
+ return `${prefix}${toPascalCase(byName)}`;
199
305
  };
200
306
  /**
201
- * Generate a lookup function for a single-column index.
307
+ * Generate a lookup method for a single-column index.
202
308
  * Uses semantic parameter naming when the column corresponds to an FK relation.
203
309
  */
204
- const generateLookupFunction = (index, ctx) => {
205
- const { entity, sqlStyle } = ctx;
310
+ const generateLookupMethod = (index, ctx) => {
311
+ const { entity, sqlStyle, entityName, exportName, explicitColumns } = ctx;
206
312
  const rowType = entity.shapes.row.name;
207
313
  const columnName = index.columnNames[0];
208
314
  const field = findRowField(entity, columnName);
@@ -211,48 +317,76 @@ const generateLookupFunction = (index, ctx) => {
211
317
  // Check if this index column corresponds to an FK relation
212
318
  const relation = findRelationForColumn(entity, columnName);
213
319
  // Use semantic param name if FK relation exists, otherwise use field name
214
- const paramName = relation
215
- ? deriveSemanticName(relation, columnName)
216
- : fieldName;
320
+ const paramName = relation ? deriveSemanticName(relation, columnName) : fieldName;
217
321
  // For semantic naming, use indexed access type (Post["userId"])
218
322
  // For regular naming, use Pick<Post, "fieldName">
219
323
  const useSemanticNaming = relation !== undefined && paramName !== fieldName;
324
+ const selectClause = buildSelectClause(entity, explicitColumns);
220
325
  const parts = {
221
- templateParts: [`select * from ${entity.schemaName}.${entity.pgName} where ${columnName} = `, ""],
326
+ templateParts: [
327
+ `${selectClause} from ${entity.schemaName}.${entity.pgName} where ${columnName} = `,
328
+ "",
329
+ ],
222
330
  params: [b.identifier(paramName)],
223
331
  };
224
- const fnName = generateLookupName(entity, index, relation);
225
- // Build the parameter - use indexed access type for semantic naming
226
- const paramNode = useSemanticNaming
227
- ? param.typed(paramName, ts.indexedAccess(ts.ref(rowType), ts.literal(fieldName)))
228
- : param.pick([fieldName], rowType);
332
+ const methodName = generateLookupMethodName(index, relation, columnName);
333
+ const name = exportName(entityName, methodName);
334
+ // Build the parameter - use destructured style for both cases
335
+ // Lookup params must be non-nullable (you're searching FOR a value, not handling null)
336
+ // Semantic naming: { user }: { user: NonNullable<Post["user_id"]> }
337
+ // Regular naming: { fieldName }: { fieldName: NonNullable<Post["fieldName"]> }
338
+ const indexedType = ts.indexedAccess(ts.ref(rowType), ts.literal(fieldName));
339
+ const paramType = ts.ref("NonNullable", [indexedType]);
340
+ const paramNode = param.destructured([{ name: paramName, type: paramType }]);
341
+ // Build metadata for the lookup method
342
+ const meta = {
343
+ name,
344
+ kind: "lookup",
345
+ params: [
346
+ {
347
+ name: paramName,
348
+ type: getFieldTypeString(field, ctx),
349
+ required: true,
350
+ columnName,
351
+ source: relation ? "fk" : "lookup",
352
+ },
353
+ ],
354
+ returns: {
355
+ type: rowType,
356
+ nullable: isUnique,
357
+ isArray: !isUnique,
358
+ },
359
+ lookupField: fieldName,
360
+ isUniqueLookup: isUnique,
361
+ callSignature: { style: "named" },
362
+ };
229
363
  if (isUnique) {
230
364
  // Extract first row for unique lookups
231
365
  const queryExpr = hex.query(sqlStyle, parts, ts.array(ts.ref(rowType)));
232
366
  const varDecl = hex.firstRowDecl(sqlStyle, "result", queryExpr);
233
- return conjure.export.fn(hex.asyncFn(fnName, [paramNode], [
234
- varDecl,
235
- b.returnStatement(b.identifier("result")),
236
- ]));
367
+ const fn = asyncFn(name, [paramNode], [varDecl, b.returnStatement(b.identifier("result"))]);
368
+ return { name, fn, meta };
237
369
  }
238
370
  // Non-unique: return all matching rows
239
- return conjure.export.fn(hex.asyncFn(fnName, [paramNode], hex.returnQuery(sqlStyle, parts, ts.array(ts.ref(rowType)))));
371
+ const fn = asyncFn(name, [paramNode], hex.returnQuery(sqlStyle, parts, ts.array(ts.ref(rowType))));
372
+ return { name, fn, meta };
240
373
  };
241
- /** Generate lookup functions for all eligible indexes, deduplicating by name */
242
- const generateLookupFunctions = (ctx) => {
374
+ /** Generate lookup methods for all eligible indexes, deduplicating by name */
375
+ const generateLookupMethods = (ctx) => {
243
376
  const seen = new Set();
244
377
  return ctx.entity.indexes
245
378
  .filter(index => shouldGenerateLookup(index) && !index.isPrimary)
246
379
  .filter(index => {
247
380
  const columnName = index.columnNames[0];
248
381
  const relation = findRelationForColumn(ctx.entity, columnName);
249
- const name = generateLookupName(ctx.entity, index, relation);
382
+ const methodName = generateLookupMethodName(index, relation, columnName);
383
+ const name = ctx.exportName(ctx.entityName, methodName);
250
384
  if (seen.has(name))
251
385
  return false;
252
386
  seen.add(name);
253
387
  return true;
254
388
  })
255
- .map(index => generateLookupFunction(index, ctx));
389
+ .map(index => generateLookupMethod(index, ctx));
256
390
  };
257
391
  // ============================================================================
258
392
  // Function Wrapper Generation
@@ -427,9 +561,7 @@ const resolveArg = (arg, ir) => {
427
561
  };
428
562
  }
429
563
  // Scalar type - map via type name
430
- const scalarBase = baseTypeName.includes(".")
431
- ? baseTypeName.split(".").pop()
432
- : baseTypeName;
564
+ const scalarBase = baseTypeName.includes(".") ? baseTypeName.split(".").pop() : baseTypeName;
433
565
  const scalarTs = pgTypeNameToTs(scalarBase);
434
566
  const tsType = isArrayType ? `${scalarTs}[]` : scalarTs;
435
567
  return {
@@ -458,8 +590,8 @@ const generateFunctionWrapper = (fn, ir, sqlStyle) => {
458
590
  const resolvedReturn = resolveReturnType(fn, ir);
459
591
  const resolvedArgs = resolveArgs(fn, ir);
460
592
  const qualifiedName = getFunctionQualifiedName(fn);
461
- // Build camelCase function name
462
- const exportName = inflect.camelCase(fn.pgName);
593
+ // Use fn.name which is already inflected by the IR builder
594
+ const name = fn.name;
463
595
  // Helper to convert resolved type string to AST
464
596
  const typeStrToAst = (typeStr) => {
465
597
  if (typeStr.endsWith("[]")) {
@@ -467,24 +599,34 @@ const generateFunctionWrapper = (fn, ir, sqlStyle) => {
467
599
  return ts.array(typeStrToAst(elemType));
468
600
  }
469
601
  switch (typeStr) {
470
- case "string": return ts.string();
471
- case "number": return ts.number();
472
- case "boolean": return ts.boolean();
473
- case "void": return ts.void();
474
- case "unknown": return ts.unknown();
475
- case "Date": return ts.ref("Date");
476
- case "Buffer": return ts.ref("Buffer");
477
- default: return ts.ref(typeStr);
602
+ case "string":
603
+ return ts.string();
604
+ case "number":
605
+ return ts.number();
606
+ case "boolean":
607
+ return ts.boolean();
608
+ case "void":
609
+ return ts.void();
610
+ case "unknown":
611
+ return ts.unknown();
612
+ case "Date":
613
+ return ts.ref("Date");
614
+ case "Buffer":
615
+ return ts.ref("Buffer");
616
+ default:
617
+ return ts.ref(typeStr);
478
618
  }
479
619
  };
480
- // Build parameter list for the generated function
481
- const params = resolvedArgs.map(arg => {
482
- const p = b.identifier(arg.name);
483
- p.typeAnnotation = b.tsTypeAnnotation(cast.toTSType(typeStrToAst(arg.tsType)));
484
- if (arg.isOptional)
485
- p.optional = true;
486
- return p;
487
- });
620
+ // Build parameter: destructured object for named style (zero-arg functions have no params)
621
+ const params = resolvedArgs.length === 0
622
+ ? []
623
+ : [
624
+ param.destructured(resolvedArgs.map(arg => ({
625
+ name: arg.name,
626
+ type: typeStrToAst(arg.tsType),
627
+ optional: arg.isOptional,
628
+ }))),
629
+ ];
488
630
  // Build SQL based on return type
489
631
  let sql;
490
632
  let resultType;
@@ -532,7 +674,24 @@ const generateFunctionWrapper = (fn, ir, sqlStyle) => {
532
674
  const varDecl = hex.firstRowDecl(sqlStyle, "result", queryExpr);
533
675
  body = [varDecl, b.returnStatement(b.identifier("result"))];
534
676
  }
535
- return conjure.export.fn(hex.asyncFn(exportName, params, body));
677
+ const fnDecl = asyncFn(name, params, body);
678
+ // Build metadata for the function wrapper
679
+ const meta = {
680
+ name,
681
+ kind: "function",
682
+ params: resolvedArgs.map(arg => ({
683
+ name: arg.name,
684
+ type: arg.tsType,
685
+ required: !arg.isOptional,
686
+ })),
687
+ returns: {
688
+ type: resolvedReturn.tsType,
689
+ nullable: resolvedReturn.isScalar || !resolvedReturn.isArray, // Scalars and single rows can be null
690
+ isArray: resolvedReturn.isArray,
691
+ },
692
+ callSignature: { style: "named" },
693
+ };
694
+ return { name, fn: fnDecl, meta };
536
695
  };
537
696
  /**
538
697
  * Collect type imports needed for function wrappers.
@@ -553,136 +712,226 @@ const collectFunctionTypeImports = (functions, ir) => {
553
712
  return imports;
554
713
  };
555
714
  // ============================================================================
556
- // Plugin Definition
715
+ // Export Style Helpers
557
716
  // ============================================================================
558
- export const sqlQueriesPlugin = definePlugin({
559
- name: "sql-queries",
560
- provides: ["queries", "queries:sql"],
561
- requires: ["types"],
562
- configSchema: SqlQueriesPluginConfig,
563
- inflection: {
564
- outputFile: ctx => `${ctx.entityName}.ts`,
565
- symbolName: (entityName, artifactKind) => `${entityName}${artifactKind}`,
566
- },
567
- run: (ctx, config) => {
568
- const enums = getEnumEntities(ctx.ir);
569
- const { sqlStyle, generateFunctions } = config;
570
- // Pre-compute function groupings by return entity name
571
- // Functions returning entities go in that entity's file; scalars go in functions.ts
572
- const functionsByEntity = new Map();
573
- const scalarFunctions = [];
574
- if (generateFunctions) {
575
- const { queries, mutations } = getGeneratableFunctions(ctx.ir);
576
- const allFunctions = [...queries, ...mutations];
577
- for (const fn of allFunctions) {
578
- const resolved = resolveReturnType(fn, ctx.ir);
579
- if (resolved.returnEntity) {
580
- const entityName = resolved.returnEntity.name;
581
- const existing = functionsByEntity.get(entityName) ?? [];
582
- functionsByEntity.set(entityName, [...existing, fn]);
583
- }
584
- else {
585
- scalarFunctions.push(fn);
717
+ /**
718
+ * Convert MethodDef array to flat export statements.
719
+ * Each method becomes: export function methodName(...) { ... }
720
+ */
721
+ const toFlatExports = (methods) => methods.map(m => conjure.export.fn(m.fn));
722
+ /**
723
+ * Convert a FunctionDeclaration to a FunctionExpression for object property use.
724
+ */
725
+ const fnDeclToExpr = (fn) => {
726
+ const expr = b.functionExpression(null, fn.params, fn.body);
727
+ expr.async = fn.async;
728
+ expr.generator = fn.generator;
729
+ return expr;
730
+ };
731
+ /**
732
+ * Convert MethodDef array to a single namespace object export.
733
+ * All methods become: export const EntityName = { methodName: async function(...) { ... }, ... }
734
+ */
735
+ const toNamespaceExport = (entityName, methods) => {
736
+ const properties = methods.map(m => b.objectProperty(b.identifier(m.name), fnDeclToExpr(m.fn)));
737
+ const obj = b.objectExpression(properties);
738
+ return conjure.export.const(entityName, obj);
739
+ };
740
+ /**
741
+ * Convert MethodDef array to statements based on export style.
742
+ */
743
+ const toStatements = (methods, exportStyle, entityName) => {
744
+ if (methods.length === 0)
745
+ return [];
746
+ return exportStyle === "namespace"
747
+ ? [toNamespaceExport(entityName, methods)]
748
+ : toFlatExports(methods);
749
+ };
750
+ /**
751
+ * Create a SQL queries provider that generates raw SQL query functions.
752
+ *
753
+ * @example
754
+ * ```typescript
755
+ * import { sqlQueries } from "pg-sourcerer"
756
+ *
757
+ * export default defineConfig({
758
+ * plugins: [
759
+ * types(),
760
+ * sqlQueries({ header: 'import { sql } from "../db"' }),
761
+ * ],
762
+ * })
763
+ * ```
764
+ */
765
+ export function sqlQueries(config) {
766
+ const parsed = S.decodeUnknownSync(SqlQueriesPluginConfigSchema)(config);
767
+ // Resolve config with properly typed exportName
768
+ const resolvedConfig = {
769
+ ...parsed,
770
+ exportName: config.exportName ?? defaultExportName,
771
+ };
772
+ return definePlugin({
773
+ name: "sql-queries",
774
+ kind: "queries",
775
+ singleton: true,
776
+ canProvide: () => true,
777
+ provide: (_params, _deps, ctx) => {
778
+ const { ir, inflection } = ctx;
779
+ const enums = getEnumEntities(ir);
780
+ const { sqlStyle, generateFunctions, exportName, exportStyle, outputDir, header, functionsFile, explicitColumns, } = resolvedConfig;
781
+ // Pre-compute function groupings by return entity name
782
+ // Functions returning entities go in that entity's file; scalars go in functions.ts
783
+ const functionsByEntity = new Map();
784
+ const scalarFunctions = [];
785
+ if (generateFunctions) {
786
+ const { queries, mutations } = getGeneratableFunctions(ir);
787
+ const allFunctions = [...queries, ...mutations];
788
+ for (const fn of allFunctions) {
789
+ const resolved = resolveReturnType(fn, ir);
790
+ if (resolved.returnEntity) {
791
+ const entityName = resolved.returnEntity.name;
792
+ const existing = functionsByEntity.get(entityName) ?? [];
793
+ functionsByEntity.set(entityName, [...existing, fn]);
794
+ }
795
+ else {
796
+ scalarFunctions.push(fn);
797
+ }
586
798
  }
587
799
  }
588
- }
589
- getTableEntities(ctx.ir)
590
- .filter(entity => entity.tags.omit !== true)
591
- .forEach(entity => {
592
- const genCtx = { entity, enums, ir: ctx.ir, sqlStyle };
593
- const crudStatements = [...generateCrudFunctions(genCtx), ...generateLookupFunctions(genCtx)];
594
- // Get functions that return this entity
595
- const entityFunctions = functionsByEntity.get(entity.name) ?? [];
596
- if (crudStatements.length === 0 && entityFunctions.length === 0)
597
- return;
598
- const entityName = ctx.inflection.entityName(entity.pgClass, entity.tags);
599
- const fileNameCtx = {
600
- entityName,
601
- pgName: entity.pgName,
602
- schema: entity.schemaName,
603
- inflection: ctx.inflection,
604
- entity,
605
- };
606
- const filePath = `${config.outputDir}/${ctx.pluginInflection.outputFile(fileNameCtx)}`;
607
- // All statements for the file: CRUD methods + function wrappers
608
- const statements = [...crudStatements];
609
- // Add function wrappers
610
- for (const fn of entityFunctions) {
611
- statements.push(generateFunctionWrapper(fn, ctx.ir, sqlStyle));
612
- }
613
- const file = ctx.file(filePath);
614
- // Import the appropriate SQL client based on style
615
- if (sqlStyle === "tag") {
616
- file.import({ kind: "relative", names: ["sql"], from: "../db" });
617
- }
618
- else {
619
- file.import({ kind: "relative", names: ["pool"], from: "../db" });
620
- }
621
- file.import({
622
- kind: "symbol",
623
- ref: { capability: "types", entity: entity.name, shape: "row" },
624
- });
625
- // Import insert type if insert function is generated
626
- if (entity.permissions.canInsert) {
627
- const insertShape = entity.shapes.insert ?? entity.shapes.row;
628
- // Only import if it's a different type than row
629
- if (insertShape !== entity.shapes.row) {
630
- file.import({
631
- kind: "symbol",
632
- ref: { capability: "types", entity: entity.name, shape: "insert" },
633
- });
800
+ getTableEntities(ir)
801
+ .filter(entity => entity.tags.omit !== true)
802
+ .forEach(entity => {
803
+ const entityName = inflection.entityName(entity.pgClass, entity.tags);
804
+ const genCtx = {
805
+ entity,
806
+ enums,
807
+ ir,
808
+ sqlStyle,
809
+ entityName,
810
+ exportName,
811
+ explicitColumns,
812
+ };
813
+ // Generate CRUD and lookup methods
814
+ const crudMethods = [...generateCrudMethods(genCtx), ...generateLookupMethods(genCtx)];
815
+ // Get functions that return this entity
816
+ const entityFunctions = functionsByEntity.get(entity.name) ?? [];
817
+ if (crudMethods.length === 0 && entityFunctions.length === 0)
818
+ return;
819
+ const filePath = `${outputDir}/${entityName}.ts`;
820
+ // Convert methods to statements based on export style
821
+ const statements = toStatements(crudMethods, exportStyle, entityName);
822
+ // Add function wrappers (these are always flat exports for now)
823
+ for (const fn of entityFunctions) {
824
+ const wrapper = generateFunctionWrapper(fn, ir, sqlStyle);
825
+ statements.push(conjure.export.fn(wrapper.fn));
634
826
  }
635
- }
636
- // Import types needed by function args (for functions grouped into this file)
637
- if (entityFunctions.length > 0) {
638
- const fnTypeImports = collectFunctionTypeImports(entityFunctions, ctx.ir);
639
- // Remove the entity's own type (already in scope)
640
- fnTypeImports.delete(entity.name);
641
- // TODO: Import these types when symbol registry supports it
642
- }
643
- file.ast(conjure.program(...statements)).emit();
644
- });
645
- // Generate files for composite types that have functions returning them
646
- if (generateFunctions) {
647
- const composites = getCompositeEntities(ctx.ir);
648
- for (const composite of composites) {
649
- const compositeFunctions = functionsByEntity.get(composite.name) ?? [];
650
- if (compositeFunctions.length === 0)
651
- continue;
652
- const filePath = `${config.outputDir}/${composite.name}.ts`;
653
- const statements = compositeFunctions.map(fn => generateFunctionWrapper(fn, ctx.ir, sqlStyle));
654
827
  const file = ctx.file(filePath);
655
- // Import the appropriate SQL client based on style
656
- if (sqlStyle === "tag") {
657
- file.import({ kind: "relative", names: ["sql"], from: "../db" });
828
+ // Add user-provided header (must include SQL client import)
829
+ file.header(header);
830
+ file.import({
831
+ kind: "symbol",
832
+ ref: { capability: "types", entity: entity.name, shape: "row" },
833
+ });
834
+ // Import insert type if insert function is generated
835
+ if (entity.permissions.canInsert) {
836
+ const insertShape = entity.shapes.insert ?? entity.shapes.row;
837
+ // Only import if it's a different type than row
838
+ if (insertShape !== entity.shapes.row) {
839
+ file.import({
840
+ kind: "symbol",
841
+ ref: { capability: "types", entity: entity.name, shape: "insert" },
842
+ });
843
+ }
658
844
  }
659
- else {
660
- file.import({ kind: "relative", names: ["pool"], from: "../db" });
845
+ // Import types needed by function args (for functions grouped into this file)
846
+ if (entityFunctions.length > 0) {
847
+ const fnTypeImports = collectFunctionTypeImports(entityFunctions, ir);
848
+ // Remove the entity's own type (already in scope)
849
+ fnTypeImports.delete(entity.name);
850
+ for (const typeName of fnTypeImports) {
851
+ file.import({
852
+ kind: "symbol",
853
+ ref: { capability: "types", entity: typeName },
854
+ });
855
+ }
661
856
  }
662
- // Import the composite type and any types needed by function args
663
- const fnTypeImports = collectFunctionTypeImports(compositeFunctions, ctx.ir);
664
- fnTypeImports.add(composite.name); // Always import the composite type
665
- // TODO: Import these types when symbol registry supports it
666
857
  file.ast(conjure.program(...statements)).emit();
858
+ // Collect metadata for QueryArtifact
859
+ const pkField = entity.primaryKey?.columns[0]
860
+ ? findRowField(entity, entity.primaryKey.columns[0])
861
+ : undefined;
862
+ const pkType = pkField ? getFieldTypeString(pkField, genCtx) : undefined;
863
+ // Combine CRUD method metadata with entity-function metadata
864
+ const allMethodMetas = [
865
+ ...crudMethods.map(m => m.meta),
866
+ ...entityFunctions.map(fn => generateFunctionWrapper(fn, ir, sqlStyle).meta),
867
+ ];
868
+ // Register entity methods to symbol registry for HTTP providers
869
+ ctx.symbols.registerEntityMethods({
870
+ entity: entityName,
871
+ importPath: filePath,
872
+ pkType,
873
+ hasCompositePk: (entity.primaryKey?.columns.length ?? 0) > 1,
874
+ methods: allMethodMetas.map(m => ({
875
+ name: m.name,
876
+ file: filePath,
877
+ entity: entityName,
878
+ kind: m.kind,
879
+ params: m.params,
880
+ returns: m.returns,
881
+ lookupField: m.lookupField,
882
+ isUniqueLookup: m.isUniqueLookup,
883
+ callSignature: m.callSignature,
884
+ })),
885
+ }, "sql-queries");
886
+ });
887
+ // Generate files for composite types that have functions returning them
888
+ if (generateFunctions) {
889
+ const composites = getCompositeEntities(ir);
890
+ for (const composite of composites) {
891
+ const compositeFunctions = functionsByEntity.get(composite.name) ?? [];
892
+ if (compositeFunctions.length === 0)
893
+ continue;
894
+ const filePath = `${outputDir}/${composite.name}.ts`;
895
+ const methods = compositeFunctions.map(fn => generateFunctionWrapper(fn, ir, sqlStyle));
896
+ // Function wrappers are always flat exports
897
+ const statements = methods.map(m => conjure.export.fn(m.fn));
898
+ const file = ctx.file(filePath);
899
+ // Add user-provided header (must include SQL client import)
900
+ file.header(header);
901
+ // Import the composite type and any types needed by function args
902
+ const fnTypeImports = collectFunctionTypeImports(compositeFunctions, ir);
903
+ fnTypeImports.add(composite.name); // Always import the composite type
904
+ for (const typeName of fnTypeImports) {
905
+ file.import({
906
+ kind: "symbol",
907
+ ref: { capability: "types", entity: typeName },
908
+ });
909
+ }
910
+ file.ast(conjure.program(...statements)).emit();
911
+ }
667
912
  }
668
- }
669
- // Generate functions.ts for scalar-returning functions only
670
- if (generateFunctions && scalarFunctions.length > 0) {
671
- const filePath = `${config.outputDir}/${config.functionsFile}`;
672
- const statements = scalarFunctions.map(fn => generateFunctionWrapper(fn, ctx.ir, sqlStyle));
673
- const file = ctx.file(filePath);
674
- // Import the appropriate SQL client based on style
675
- if (sqlStyle === "tag") {
676
- file.import({ kind: "relative", names: ["sql"], from: "../db" });
677
- }
678
- else {
679
- file.import({ kind: "relative", names: ["pool"], from: "../db" });
913
+ // Generate functions.ts for scalar-returning functions only
914
+ if (generateFunctions && scalarFunctions.length > 0) {
915
+ const filePath = `${outputDir}/${functionsFile}`;
916
+ const methods = scalarFunctions.map(fn => generateFunctionWrapper(fn, ir, sqlStyle));
917
+ // Function wrappers are always flat exports
918
+ const statements = methods.map(m => conjure.export.fn(m.fn));
919
+ const file = ctx.file(filePath);
920
+ // Add user-provided header (must include SQL client import)
921
+ file.header(header);
922
+ // Import types needed by function args
923
+ const fnTypeImports = collectFunctionTypeImports(scalarFunctions, ir);
924
+ for (const typeName of fnTypeImports) {
925
+ file.import({
926
+ kind: "symbol",
927
+ ref: { capability: "types", entity: typeName },
928
+ });
929
+ }
930
+ file.ast(conjure.program(...statements)).emit();
931
+ // TODO: Register standalone functions to symbol registry when HTTP plugins need them
932
+ // For now, standalone functions are not exposed via routes
680
933
  }
681
- // Import types needed by function args
682
- const fnTypeImports = collectFunctionTypeImports(scalarFunctions, ctx.ir);
683
- // TODO: Import these types when symbol registry supports it
684
- file.ast(conjure.program(...statements)).emit();
685
- }
686
- },
687
- });
934
+ },
935
+ });
936
+ }
688
937
  //# sourceMappingURL=sql-queries.js.map