@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.
- package/dist/cli.js +3 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +14 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -1
- package/dist/generate.d.ts +5 -9
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +27 -29
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +19 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -13
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +39 -9
- package/dist/init.js.map +1 -1
- package/dist/ir/extensions/queries.d.ts +264 -0
- package/dist/ir/extensions/queries.d.ts.map +1 -0
- package/dist/ir/extensions/queries.js +153 -0
- package/dist/ir/extensions/queries.js.map +1 -0
- package/dist/ir/extensions/schema-builder.d.ts +61 -0
- package/dist/ir/extensions/schema-builder.d.ts.map +1 -0
- package/dist/ir/extensions/schema-builder.js +5 -0
- package/dist/ir/extensions/schema-builder.js.map +1 -0
- package/dist/lib/conjure.d.ts +66 -0
- package/dist/lib/conjure.d.ts.map +1 -1
- package/dist/lib/conjure.js +127 -29
- package/dist/lib/conjure.js.map +1 -1
- package/dist/lib/hex.d.ts +10 -3
- package/dist/lib/hex.d.ts.map +1 -1
- package/dist/lib/hex.js +18 -8
- package/dist/lib/hex.js.map +1 -1
- package/dist/plugins/arktype.d.ts +27 -14
- package/dist/plugins/arktype.d.ts.map +1 -1
- package/dist/plugins/arktype.js +166 -130
- package/dist/plugins/arktype.js.map +1 -1
- package/dist/plugins/effect.d.ts +53 -0
- package/dist/plugins/effect.d.ts.map +1 -0
- package/dist/plugins/effect.js +1074 -0
- package/dist/plugins/effect.js.map +1 -0
- package/dist/plugins/http-elysia.d.ts +32 -0
- package/dist/plugins/http-elysia.d.ts.map +1 -0
- package/dist/plugins/http-elysia.js +613 -0
- package/dist/plugins/http-elysia.js.map +1 -0
- package/dist/plugins/http-express.d.ts +36 -0
- package/dist/plugins/http-express.d.ts.map +1 -0
- package/dist/plugins/http-express.js +388 -0
- package/dist/plugins/http-express.js.map +1 -0
- package/dist/plugins/http-hono.d.ts +36 -0
- package/dist/plugins/http-hono.d.ts.map +1 -0
- package/dist/plugins/http-hono.js +453 -0
- package/dist/plugins/http-hono.js.map +1 -0
- package/dist/plugins/http-orpc.d.ts +55 -0
- package/dist/plugins/http-orpc.d.ts.map +1 -0
- package/dist/plugins/http-orpc.js +370 -0
- package/dist/plugins/http-orpc.js.map +1 -0
- package/dist/plugins/http-trpc.d.ts +59 -0
- package/dist/plugins/http-trpc.d.ts.map +1 -0
- package/dist/plugins/http-trpc.js +392 -0
- package/dist/plugins/http-trpc.js.map +1 -0
- package/dist/plugins/kysely/queries.d.ts +92 -0
- package/dist/plugins/kysely/queries.d.ts.map +1 -0
- package/dist/plugins/kysely/queries.js +1169 -0
- package/dist/plugins/kysely/queries.js.map +1 -0
- package/dist/plugins/kysely/shared.d.ts +59 -0
- package/dist/plugins/kysely/shared.d.ts.map +1 -0
- package/dist/plugins/kysely/shared.js +247 -0
- package/dist/plugins/kysely/shared.js.map +1 -0
- package/dist/plugins/kysely/types.d.ts +22 -0
- package/dist/plugins/kysely/types.d.ts.map +1 -0
- package/dist/plugins/kysely/types.js +428 -0
- package/dist/plugins/kysely/types.js.map +1 -0
- package/dist/plugins/kysely.d.ts +72 -0
- package/dist/plugins/kysely.d.ts.map +1 -0
- package/dist/plugins/kysely.js +906 -0
- package/dist/plugins/kysely.js.map +1 -0
- package/dist/plugins/sql-queries.d.ts +55 -11
- package/dist/plugins/sql-queries.d.ts.map +1 -1
- package/dist/plugins/sql-queries.js +467 -218
- package/dist/plugins/sql-queries.js.map +1 -1
- package/dist/plugins/types.d.ts +20 -14
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/plugins/types.js +90 -112
- package/dist/plugins/types.js.map +1 -1
- package/dist/plugins/valibot.d.ts +45 -0
- package/dist/plugins/valibot.d.ts.map +1 -0
- package/dist/plugins/valibot.js +422 -0
- package/dist/plugins/valibot.js.map +1 -0
- package/dist/plugins/zod.d.ts +27 -14
- package/dist/plugins/zod.d.ts.map +1 -1
- package/dist/plugins/zod.js +231 -166
- package/dist/plugins/zod.js.map +1 -1
- package/dist/services/artifact-store.d.ts +11 -1
- package/dist/services/artifact-store.d.ts.map +1 -1
- package/dist/services/artifact-store.js +9 -0
- package/dist/services/artifact-store.js.map +1 -1
- package/dist/services/core-providers.d.ts +15 -0
- package/dist/services/core-providers.d.ts.map +1 -0
- package/dist/services/core-providers.js +23 -0
- package/dist/services/core-providers.js.map +1 -0
- package/dist/services/emissions.d.ts +14 -0
- package/dist/services/emissions.d.ts.map +1 -1
- package/dist/services/emissions.js +86 -47
- package/dist/services/emissions.js.map +1 -1
- package/dist/services/execution.d.ts +35 -0
- package/dist/services/execution.d.ts.map +1 -0
- package/dist/services/execution.js +86 -0
- package/dist/services/execution.js.map +1 -0
- package/dist/services/file-builder.d.ts +4 -0
- package/dist/services/file-builder.d.ts.map +1 -1
- package/dist/services/file-builder.js.map +1 -1
- package/dist/services/inflection.d.ts +2 -2
- package/dist/services/inflection.d.ts.map +1 -1
- package/dist/services/inflection.js +4 -4
- package/dist/services/inflection.js.map +1 -1
- package/dist/services/ir-builder.d.ts.map +1 -1
- package/dist/services/ir-builder.js +10 -3
- package/dist/services/ir-builder.js.map +1 -1
- package/dist/services/pg-types.d.ts +31 -0
- package/dist/services/pg-types.d.ts.map +1 -1
- package/dist/services/pg-types.js +24 -0
- package/dist/services/pg-types.js.map +1 -1
- package/dist/services/plugin-runner.d.ts +27 -37
- package/dist/services/plugin-runner.d.ts.map +1 -1
- package/dist/services/plugin-runner.js +73 -171
- package/dist/services/plugin-runner.js.map +1 -1
- package/dist/services/plugin.d.ts +349 -217
- package/dist/services/plugin.d.ts.map +1 -1
- package/dist/services/plugin.js +182 -130
- package/dist/services/plugin.js.map +1 -1
- package/dist/services/resolution.d.ts +38 -0
- package/dist/services/resolution.d.ts.map +1 -0
- package/dist/services/resolution.js +242 -0
- package/dist/services/resolution.js.map +1 -0
- package/dist/services/service-registry.d.ts +74 -0
- package/dist/services/service-registry.d.ts.map +1 -0
- package/dist/services/service-registry.js +61 -0
- package/dist/services/service-registry.js.map +1 -0
- package/dist/services/symbols.d.ts +59 -0
- package/dist/services/symbols.d.ts.map +1 -1
- package/dist/services/symbols.js +16 -0
- package/dist/services/symbols.js.map +1 -1
- package/dist/testing.d.ts +4 -25
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -23
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/plugins/effect-model.d.ts +0 -17
- package/dist/plugins/effect-model.d.ts.map +0 -1
- package/dist/plugins/effect-model.js +0 -409
- package/dist/plugins/effect-model.js.map +0 -1
- package/dist/plugins/kysely-queries.d.ts +0 -66
- package/dist/plugins/kysely-queries.d.ts.map +0 -1
- package/dist/plugins/kysely-queries.js +0 -951
- package/dist/plugins/kysely-queries.js.map +0 -1
- package/dist/plugins/kysely-types.d.ts +0 -35
- package/dist/plugins/kysely-types.d.ts.map +0 -1
- package/dist/plugins/kysely-types.js +0 -601
- package/dist/plugins/kysely-types.js.map +0 -1
|
@@ -1,28 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQL Queries
|
|
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
|
|
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
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
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")), {
|
|
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
|
-
/**
|
|
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: [
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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: [
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
132
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
//
|
|
165
|
-
const
|
|
166
|
-
|
|
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
|
|
169
|
-
const
|
|
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
|
|
186
|
-
*
|
|
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
|
|
299
|
+
const generateLookupMethodName = (index, relation, columnName) => {
|
|
189
300
|
const isUnique = index.isUnique || index.isPrimary;
|
|
190
|
-
const
|
|
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
|
|
195
|
-
|
|
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
|
|
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
|
|
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: [
|
|
326
|
+
templateParts: [
|
|
327
|
+
`${selectClause} from ${entity.schemaName}.${entity.pgName} where ${columnName} = `,
|
|
328
|
+
"",
|
|
329
|
+
],
|
|
222
330
|
params: [b.identifier(paramName)],
|
|
223
331
|
};
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
|
242
|
-
const
|
|
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
|
|
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 =>
|
|
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
|
-
//
|
|
462
|
-
const
|
|
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":
|
|
471
|
-
|
|
472
|
-
case "
|
|
473
|
-
|
|
474
|
-
case "
|
|
475
|
-
|
|
476
|
-
case "
|
|
477
|
-
|
|
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
|
|
481
|
-
const params = resolvedArgs.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
715
|
+
// Export Style Helpers
|
|
557
716
|
// ============================================================================
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
entity
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
//
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
660
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
file.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
file.ast(conjure.program(...statements)).emit();
|
|
685
|
-
}
|
|
686
|
-
},
|
|
687
|
-
});
|
|
934
|
+
},
|
|
935
|
+
});
|
|
936
|
+
}
|
|
688
937
|
//# sourceMappingURL=sql-queries.js.map
|