appflare 0.2.24 → 0.2.26
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/Documentation.md +758 -758
- package/cli/commands/index.ts +238 -238
- package/cli/generate.ts +178 -178
- package/cli/index.ts +120 -120
- package/cli/load-config.ts +184 -184
- package/cli/schema-compiler.ts +1183 -1183
- package/cli/templates/auth/README.md +156 -156
- package/cli/templates/auth/config.ts +61 -61
- package/cli/templates/auth/route-config.ts +1 -1
- package/cli/templates/auth/route-handler.ts +1 -1
- package/cli/templates/auth/route-request-utils.ts +5 -5
- package/cli/templates/auth/route.config.ts +18 -18
- package/cli/templates/auth/route.handler.ts +18 -18
- package/cli/templates/auth/route.request-utils.ts +55 -55
- package/cli/templates/auth/route.ts +14 -14
- package/cli/templates/core/README.md +266 -266
- package/cli/templates/core/app-creation.ts +19 -19
- package/cli/templates/core/client/appflare.ts +112 -112
- package/cli/templates/core/client/handlers/index.ts +748 -749
- package/cli/templates/core/client/handlers.ts +1 -1
- package/cli/templates/core/client/index.ts +7 -7
- package/cli/templates/core/client/storage.ts +180 -180
- package/cli/templates/core/client/types.ts +184 -184
- package/cli/templates/core/client-modules/appflare.ts +1 -1
- package/cli/templates/core/client-modules/handlers.ts +1 -1
- package/cli/templates/core/client-modules/index.ts +1 -1
- package/cli/templates/core/client-modules/storage.ts +1 -1
- package/cli/templates/core/client-modules/types.ts +1 -1
- package/cli/templates/core/client.artifacts.ts +39 -39
- package/cli/templates/core/client.ts +4 -4
- package/cli/templates/core/drizzle.ts +15 -15
- package/cli/templates/core/export.ts +14 -14
- package/cli/templates/core/handlers.route.ts +24 -24
- package/cli/templates/core/handlers.ts +1 -1
- package/cli/templates/core/imports.ts +9 -9
- package/cli/templates/core/server.ts +38 -38
- package/cli/templates/core/types.ts +6 -6
- package/cli/templates/core/wrangler.ts +109 -109
- package/cli/templates/dashboard/builders/functions/index.ts +17 -17
- package/cli/templates/dashboard/builders/functions/render-page/header.ts +20 -20
- package/cli/templates/dashboard/builders/functions/render-page/index.ts +33 -33
- package/cli/templates/dashboard/builders/functions/render-page/request-panel.ts +171 -171
- package/cli/templates/dashboard/builders/functions/render-page/result-panel.ts +85 -85
- package/cli/templates/dashboard/builders/functions/render-page/scripts.ts +554 -554
- package/cli/templates/dashboard/builders/navigation.ts +122 -122
- package/cli/templates/dashboard/builders/storage/index.ts +13 -13
- package/cli/templates/dashboard/builders/storage/routes/create-directory-route.ts +29 -29
- package/cli/templates/dashboard/builders/storage/routes/delete-route.ts +18 -18
- package/cli/templates/dashboard/builders/storage/routes/download-route.ts +23 -23
- package/cli/templates/dashboard/builders/storage/routes/index.ts +22 -22
- package/cli/templates/dashboard/builders/storage/routes/list-route.ts +25 -25
- package/cli/templates/dashboard/builders/storage/routes/preview-route.ts +21 -21
- package/cli/templates/dashboard/builders/storage/routes/upload-route.ts +21 -21
- package/cli/templates/dashboard/builders/storage/runtime/helpers.ts +72 -72
- package/cli/templates/dashboard/builders/storage/runtime/storage-page.ts +130 -130
- package/cli/templates/dashboard/builders/table-routes/common/drawer-panel.ts +27 -27
- package/cli/templates/dashboard/builders/table-routes/common/pagination.ts +30 -30
- package/cli/templates/dashboard/builders/table-routes/common/search-bar.ts +23 -23
- package/cli/templates/dashboard/builders/table-routes/fragments.ts +217 -217
- package/cli/templates/dashboard/builders/table-routes/helpers.ts +45 -45
- package/cli/templates/dashboard/builders/table-routes/index.ts +8 -8
- package/cli/templates/dashboard/builders/table-routes/table/actions-cell.ts +71 -71
- package/cli/templates/dashboard/builders/table-routes/table/get-route.ts +291 -291
- package/cli/templates/dashboard/builders/table-routes/table/index.ts +80 -80
- package/cli/templates/dashboard/builders/table-routes/table/post-routes.ts +163 -163
- package/cli/templates/dashboard/builders/table-routes/table-route.ts +7 -7
- package/cli/templates/dashboard/builders/table-routes/users/get-route.ts +69 -69
- package/cli/templates/dashboard/builders/table-routes/users/html/modals.ts +57 -57
- package/cli/templates/dashboard/builders/table-routes/users/html/page.ts +27 -27
- package/cli/templates/dashboard/builders/table-routes/users/html/table.ts +128 -128
- package/cli/templates/dashboard/builders/table-routes/users/index.ts +32 -32
- package/cli/templates/dashboard/builders/table-routes/users/post-routes.ts +150 -150
- package/cli/templates/dashboard/builders/table-routes/users/redirect.ts +14 -14
- package/cli/templates/dashboard/builders/table-routes/users-route.ts +10 -10
- package/cli/templates/dashboard/components/dashboard-home.ts +23 -23
- package/cli/templates/dashboard/components/layout.ts +388 -388
- package/cli/templates/dashboard/components/login-page.ts +65 -65
- package/cli/templates/dashboard/index.ts +61 -61
- package/cli/templates/dashboard/types.ts +9 -9
- package/cli/templates/handlers/README.md +353 -353
- package/cli/templates/handlers/auth.ts +37 -37
- package/cli/templates/handlers/execution.ts +42 -42
- package/cli/templates/handlers/generators/context/context-creation.ts +101 -101
- package/cli/templates/handlers/generators/context/error-helpers.ts +11 -11
- package/cli/templates/handlers/generators/context/scheduler.ts +24 -24
- package/cli/templates/handlers/generators/context/storage-api.ts +134 -112
- package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -59
- package/cli/templates/handlers/generators/context/types.ts +18 -18
- package/cli/templates/handlers/generators/context.ts +43 -43
- package/cli/templates/handlers/generators/execution.ts +15 -15
- package/cli/templates/handlers/generators/handlers.ts +13 -13
- package/cli/templates/handlers/generators/registration/modules/cron.ts +26 -26
- package/cli/templates/handlers/generators/registration/modules/realtime/auth.ts +75 -75
- package/cli/templates/handlers/generators/registration/modules/realtime/durable-object.ts +144 -144
- package/cli/templates/handlers/generators/registration/modules/realtime/index.ts +14 -14
- package/cli/templates/handlers/generators/registration/modules/realtime/publisher.ts +102 -102
- package/cli/templates/handlers/generators/registration/modules/realtime/routes.ts +164 -164
- package/cli/templates/handlers/generators/registration/modules/realtime/types.ts +30 -30
- package/cli/templates/handlers/generators/registration/modules/realtime/utils.ts +516 -516
- package/cli/templates/handlers/generators/registration/modules/scheduler.ts +56 -56
- package/cli/templates/handlers/generators/registration/modules/storage.ts +196 -194
- package/cli/templates/handlers/generators/registration/sections.ts +210 -210
- package/cli/templates/handlers/generators/types/context.ts +68 -66
- package/cli/templates/handlers/generators/types/core.ts +106 -106
- package/cli/templates/handlers/generators/types/operations.ts +135 -135
- package/cli/templates/handlers/generators/types/query-definitions/filter-and-where-types.ts +259 -259
- package/cli/templates/handlers/generators/types/query-definitions/query-api-types.ts +135 -135
- package/cli/templates/handlers/generators/types/query-definitions/query-helper-functions.ts +1031 -1031
- package/cli/templates/handlers/generators/types/query-definitions/schema-and-table-types.ts +246 -246
- package/cli/templates/handlers/generators/types/query-definitions.ts +13 -13
- package/cli/templates/handlers/generators/types/query-runtime/handled-error.ts +13 -13
- package/cli/templates/handlers/generators/types/query-runtime/runtime-aggregate-and-footer.ts +174 -174
- package/cli/templates/handlers/generators/types/query-runtime/runtime-read.ts +121 -121
- package/cli/templates/handlers/generators/types/query-runtime/runtime-setup.ts +45 -45
- package/cli/templates/handlers/generators/types/query-runtime/runtime-write.ts +676 -676
- package/cli/templates/handlers/generators/types/query-runtime.ts +15 -15
- package/cli/templates/handlers/index.ts +43 -43
- package/cli/templates/handlers/operations.ts +116 -116
- package/cli/templates/handlers/registration.ts +91 -83
- package/cli/templates/handlers/types.ts +15 -15
- package/cli/templates/handlers/utils.ts +48 -48
- package/cli/types.ts +110 -110
- package/cli/utils/handler-discovery.ts +466 -466
- package/cli/utils/json-utils.ts +24 -24
- package/cli/utils/path-utils.ts +19 -19
- package/cli/utils/schema-discovery.ts +399 -399
- package/dist/cli/index.js +61 -28
- package/dist/cli/index.mjs +61 -28
- package/index.ts +18 -18
- package/package.json +58 -58
- package/react/index.ts +5 -5
- package/react/use-infinite-query.ts +252 -252
- package/react/use-mutation.ts +89 -89
- package/react/use-query.ts +207 -207
- package/schema.ts +415 -415
- package/test-better-auth-hash.ts +2 -2
- package/tsconfig.json +6 -6
- package/tsup.config.ts +82 -82
|
@@ -1,676 +1,676 @@
|
|
|
1
|
-
export function generateQueryRuntimeWriteSection(): string {
|
|
2
|
-
return ` insert: async <TArgs extends QueryInsertArgs<TableName>>(args: TArgs) => {
|
|
3
|
-
const transaction = ($db as any).transaction;
|
|
4
|
-
|
|
5
|
-
const valuesArray = Array.isArray(args.values)
|
|
6
|
-
? args.values
|
|
7
|
-
: [args.values];
|
|
8
|
-
const baseValuesArray: Array<Record<string, unknown>> = [];
|
|
9
|
-
const postInsertRelations: Array<
|
|
10
|
-
Array<{
|
|
11
|
-
relationName: string;
|
|
12
|
-
relation: RuntimeRelation;
|
|
13
|
-
value: unknown;
|
|
14
|
-
}>
|
|
15
|
-
> = [];
|
|
16
|
-
|
|
17
|
-
const toRelationItems = (
|
|
18
|
-
relation: RuntimeRelation,
|
|
19
|
-
relationName: string,
|
|
20
|
-
value: unknown,
|
|
21
|
-
): unknown[] => {
|
|
22
|
-
if (value === undefined || value === null) {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (relation.kind === "one") {
|
|
27
|
-
if (Array.isArray(value)) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
"Relation '" +
|
|
30
|
-
tableName +
|
|
31
|
-
"." +
|
|
32
|
-
relationName +
|
|
33
|
-
"' expects a single value.",
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
return [value];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return Array.isArray(value) ? value : [value];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const resolveRelationIdentifier = async (
|
|
43
|
-
tx: any,
|
|
44
|
-
relation: RuntimeRelation,
|
|
45
|
-
relationName: string,
|
|
46
|
-
input: unknown,
|
|
47
|
-
): Promise<unknown> => {
|
|
48
|
-
if (
|
|
49
|
-
typeof input === "string" ||
|
|
50
|
-
typeof input === "number" ||
|
|
51
|
-
typeof input === "bigint"
|
|
52
|
-
) {
|
|
53
|
-
return input;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!isRecord(input)) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
"Relation '" +
|
|
59
|
-
tableName +
|
|
60
|
-
"." +
|
|
61
|
-
relationName +
|
|
62
|
-
"' expects an id or object payload.",
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const referenceField =
|
|
67
|
-
relation.kind === "manyToMany"
|
|
68
|
-
? (relation.targetReferenceField ?? "id")
|
|
69
|
-
: (relation.referenceField ?? "id");
|
|
70
|
-
const existingId = input[referenceField];
|
|
71
|
-
if (existingId !== undefined && existingId !== null) {
|
|
72
|
-
return existingId;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const targetTable = (mergedSchema as Record<string, unknown>)[
|
|
76
|
-
relation.targetTable
|
|
77
|
-
];
|
|
78
|
-
if (!targetTable) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
"Unknown target table '" +
|
|
81
|
-
relation.targetTable +
|
|
82
|
-
"' for relation '" +
|
|
83
|
-
tableName +
|
|
84
|
-
"." +
|
|
85
|
-
relationName +
|
|
86
|
-
"'.",
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
let createQuery: any = tx.insert(targetTable as any).values(input as any);
|
|
91
|
-
if (typeof createQuery.returning === "function") {
|
|
92
|
-
createQuery = createQuery.returning();
|
|
93
|
-
}
|
|
94
|
-
const createdRows = (await createQuery) as Array<Record<string, unknown>>;
|
|
95
|
-
const created = createdRows[0];
|
|
96
|
-
if (!created) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
"Failed to create relation target for '" +
|
|
99
|
-
tableName +
|
|
100
|
-
"." +
|
|
101
|
-
relationName +
|
|
102
|
-
"'.",
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const createdId = created[referenceField];
|
|
107
|
-
if (createdId === undefined || createdId === null) {
|
|
108
|
-
throw new Error(
|
|
109
|
-
"Created relation target for '" +
|
|
110
|
-
tableName +
|
|
111
|
-
"." +
|
|
112
|
-
relationName +
|
|
113
|
-
"' is missing '" +
|
|
114
|
-
referenceField +
|
|
115
|
-
"'.",
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return createdId;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const getRelationTargetTable = (
|
|
123
|
-
relation: RuntimeRelation,
|
|
124
|
-
relationName: string,
|
|
125
|
-
): any => {
|
|
126
|
-
const targetTable = (mergedSchema as Record<string, unknown>)[
|
|
127
|
-
relation.targetTable
|
|
128
|
-
];
|
|
129
|
-
if (!targetTable) {
|
|
130
|
-
throw new Error(
|
|
131
|
-
"Unknown target table '" +
|
|
132
|
-
relation.targetTable +
|
|
133
|
-
"' for relation '" +
|
|
134
|
-
tableName +
|
|
135
|
-
"." +
|
|
136
|
-
relationName +
|
|
137
|
-
"'.",
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
return targetTable;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const getRelationReferenceField = (relation: RuntimeRelation): string => {
|
|
144
|
-
if (relation.kind === "manyToMany") {
|
|
145
|
-
return relation.targetReferenceField ?? "id";
|
|
146
|
-
}
|
|
147
|
-
return relation.referenceField ?? "id";
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const fetchRelatedRowByIdentifier = async (
|
|
151
|
-
tx: any,
|
|
152
|
-
relation: RuntimeRelation,
|
|
153
|
-
relationName: string,
|
|
154
|
-
identifier: unknown,
|
|
155
|
-
): Promise<Record<string, unknown> | null> => {
|
|
156
|
-
if (identifier === undefined || identifier === null) {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const targetTable = getRelationTargetTable(relation, relationName);
|
|
161
|
-
const referenceField = getRelationReferenceField(relation);
|
|
162
|
-
const referenceColumn = (targetTable as Record<string, unknown>)[
|
|
163
|
-
referenceField
|
|
164
|
-
];
|
|
165
|
-
if (!referenceColumn) {
|
|
166
|
-
throw new Error(
|
|
167
|
-
"Target table '" +
|
|
168
|
-
relation.targetTable +
|
|
169
|
-
"' is missing column '" +
|
|
170
|
-
referenceField +
|
|
171
|
-
"' required by relation '" +
|
|
172
|
-
tableName +
|
|
173
|
-
"." +
|
|
174
|
-
relationName +
|
|
175
|
-
"'.",
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
let query: any = tx
|
|
180
|
-
.select()
|
|
181
|
-
.from(targetTable as any)
|
|
182
|
-
.where(eq(referenceColumn as any, identifier as any));
|
|
183
|
-
if (typeof query.limit === "function") {
|
|
184
|
-
query = query.limit(1);
|
|
185
|
-
}
|
|
186
|
-
const rows = (await query) as Array<Record<string, unknown>>;
|
|
187
|
-
return rows[0] ?? null;
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
for (const inputValue of valuesArray) {
|
|
191
|
-
const scalarValues: Record<string, unknown> = {};
|
|
192
|
-
const relationInputs: Array<{
|
|
193
|
-
relationName: string;
|
|
194
|
-
relation: RuntimeRelation;
|
|
195
|
-
value: unknown;
|
|
196
|
-
}> = [];
|
|
197
|
-
|
|
198
|
-
const valueRecord = (inputValue ?? {}) as Record<string, unknown>;
|
|
199
|
-
for (const [fieldName, fieldValue] of Object.entries(valueRecord)) {
|
|
200
|
-
const runtimeRelation = getRuntimeRelation(tableName, fieldName);
|
|
201
|
-
if (runtimeRelation) {
|
|
202
|
-
relationInputs.push({
|
|
203
|
-
relationName: fieldName,
|
|
204
|
-
relation: runtimeRelation,
|
|
205
|
-
value: fieldValue,
|
|
206
|
-
});
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
scalarValues[fieldName] = fieldValue;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
baseValuesArray.push(scalarValues);
|
|
213
|
-
postInsertRelations.push(relationInputs);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const executeInsertGraph = async (
|
|
217
|
-
tx: any,
|
|
218
|
-
): Promise<Array<QueryInsertResultRow<TableName, TArgs>>> => {
|
|
219
|
-
for (let index = 0; index < baseValuesArray.length; index += 1) {
|
|
220
|
-
const relationInputs = postInsertRelations[index] ?? [];
|
|
221
|
-
for (const relationInput of relationInputs) {
|
|
222
|
-
if (relationInput.relation.kind !== "one") {
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const sourceField = relationInput.relation.sourceField;
|
|
227
|
-
if (!sourceField) {
|
|
228
|
-
throw new Error(
|
|
229
|
-
"Relation '" +
|
|
230
|
-
tableName +
|
|
231
|
-
"." +
|
|
232
|
-
relationInput.relationName +
|
|
233
|
-
"' is missing sourceField metadata.",
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const oneItems = toRelationItems(
|
|
238
|
-
relationInput.relation,
|
|
239
|
-
relationInput.relationName,
|
|
240
|
-
relationInput.value,
|
|
241
|
-
);
|
|
242
|
-
if (oneItems.length === 0) {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const relationId = await resolveRelationIdentifier(
|
|
247
|
-
tx,
|
|
248
|
-
relationInput.relation,
|
|
249
|
-
relationInput.relationName,
|
|
250
|
-
oneItems[0],
|
|
251
|
-
);
|
|
252
|
-
const existingValue = baseValuesArray[index][sourceField];
|
|
253
|
-
if (
|
|
254
|
-
existingValue !== undefined &&
|
|
255
|
-
existingValue !== relationId
|
|
256
|
-
) {
|
|
257
|
-
throw new Error(
|
|
258
|
-
"Insert payload for '" +
|
|
259
|
-
tableName +
|
|
260
|
-
"' has conflicting values for '" +
|
|
261
|
-
sourceField +
|
|
262
|
-
"'.",
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
baseValuesArray[index][sourceField] = relationId;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
let insertQuery: any = tx.insert(table as any).values(baseValuesArray as any);
|
|
270
|
-
if (typeof insertQuery.returning === "function") {
|
|
271
|
-
insertQuery = insertQuery.returning();
|
|
272
|
-
}
|
|
273
|
-
const insertedRows = (await insertQuery) as Array<TableModel<TableName>>;
|
|
274
|
-
const hydratedRows: Array<Record<string, unknown>> = [];
|
|
275
|
-
|
|
276
|
-
for (let index = 0; index < insertedRows.length; index += 1) {
|
|
277
|
-
const parentRow = insertedRows[index] as unknown as Record<string, unknown>;
|
|
278
|
-
const hydratedRow: Record<string, unknown> = {
|
|
279
|
-
...parentRow,
|
|
280
|
-
};
|
|
281
|
-
const relationInputs = postInsertRelations[index] ?? [];
|
|
282
|
-
|
|
283
|
-
for (const relationInput of relationInputs) {
|
|
284
|
-
if (relationInput.relation.kind === "one") {
|
|
285
|
-
const sourceField = relationInput.relation.sourceField;
|
|
286
|
-
if (!sourceField) {
|
|
287
|
-
throw new Error(
|
|
288
|
-
"Relation '" +
|
|
289
|
-
tableName +
|
|
290
|
-
"." +
|
|
291
|
-
relationInput.relationName +
|
|
292
|
-
"' is missing sourceField metadata.",
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const relationItems = toRelationItems(
|
|
297
|
-
relationInput.relation,
|
|
298
|
-
relationInput.relationName,
|
|
299
|
-
relationInput.value,
|
|
300
|
-
);
|
|
301
|
-
if (relationItems.length === 0) {
|
|
302
|
-
hydratedRow[relationInput.relationName] = null;
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const sourceIdentifier = parentRow[sourceField];
|
|
307
|
-
hydratedRow[relationInput.relationName] =
|
|
308
|
-
(await fetchRelatedRowByIdentifier(
|
|
309
|
-
tx,
|
|
310
|
-
relationInput.relation,
|
|
311
|
-
relationInput.relationName,
|
|
312
|
-
sourceIdentifier,
|
|
313
|
-
)) ?? null;
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const sourceReferenceField = relationInput.relation.referenceField ?? "id";
|
|
318
|
-
const parentReferenceValue = parentRow[sourceReferenceField];
|
|
319
|
-
if (
|
|
320
|
-
parentReferenceValue === undefined ||
|
|
321
|
-
parentReferenceValue === null
|
|
322
|
-
) {
|
|
323
|
-
throw new Error(
|
|
324
|
-
"Inserted row for '" +
|
|
325
|
-
tableName +
|
|
326
|
-
"' is missing '" +
|
|
327
|
-
sourceReferenceField +
|
|
328
|
-
"' required by relation '" +
|
|
329
|
-
relationInput.relationName +
|
|
330
|
-
"'.",
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const relatedRows: Array<Record<string, unknown>> = [];
|
|
335
|
-
hydratedRow[relationInput.relationName] = relatedRows;
|
|
336
|
-
|
|
337
|
-
const relationItems = toRelationItems(
|
|
338
|
-
relationInput.relation,
|
|
339
|
-
relationInput.relationName,
|
|
340
|
-
relationInput.value,
|
|
341
|
-
);
|
|
342
|
-
if (relationItems.length === 0) {
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (relationInput.relation.kind === "many") {
|
|
347
|
-
const targetTable = getRelationTargetTable(
|
|
348
|
-
relationInput.relation,
|
|
349
|
-
relationInput.relationName,
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
const sourceField = relationInput.relation.sourceField;
|
|
353
|
-
const targetReferenceField =
|
|
354
|
-
relationInput.relation.referenceField ?? "id";
|
|
355
|
-
const targetReferenceColumn =
|
|
356
|
-
(targetTable as Record<string, unknown>)[targetReferenceField];
|
|
357
|
-
if (!sourceField) {
|
|
358
|
-
throw new Error(
|
|
359
|
-
"Relation '" +
|
|
360
|
-
tableName +
|
|
361
|
-
"." +
|
|
362
|
-
relationInput.relationName +
|
|
363
|
-
"' is missing sourceField metadata.",
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
if (!targetReferenceColumn) {
|
|
367
|
-
throw new Error(
|
|
368
|
-
"Target table '" +
|
|
369
|
-
relationInput.relation.targetTable +
|
|
370
|
-
"' is missing column '" +
|
|
371
|
-
targetReferenceField +
|
|
372
|
-
"' required by relation '" +
|
|
373
|
-
tableName +
|
|
374
|
-
"." +
|
|
375
|
-
relationInput.relationName +
|
|
376
|
-
"'.",
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
for (const relationItem of relationItems) {
|
|
381
|
-
if (
|
|
382
|
-
typeof relationItem === "string" ||
|
|
383
|
-
typeof relationItem === "number" ||
|
|
384
|
-
typeof relationItem === "bigint"
|
|
385
|
-
) {
|
|
386
|
-
await tx
|
|
387
|
-
.update(targetTable as any)
|
|
388
|
-
.set({ [sourceField]: parentReferenceValue } as any)
|
|
389
|
-
.where(
|
|
390
|
-
eq(targetReferenceColumn as any, relationItem as any),
|
|
391
|
-
);
|
|
392
|
-
const linkedRow = await fetchRelatedRowByIdentifier(
|
|
393
|
-
tx,
|
|
394
|
-
relationInput.relation,
|
|
395
|
-
relationInput.relationName,
|
|
396
|
-
relationItem,
|
|
397
|
-
);
|
|
398
|
-
if (linkedRow) {
|
|
399
|
-
relatedRows.push(linkedRow);
|
|
400
|
-
}
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (!isRecord(relationItem)) {
|
|
405
|
-
throw new Error(
|
|
406
|
-
"Relation '" +
|
|
407
|
-
tableName +
|
|
408
|
-
"." +
|
|
409
|
-
relationInput.relationName +
|
|
410
|
-
"' expects id or object payloads.",
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const existingId = relationItem[targetReferenceField];
|
|
415
|
-
const payload = {
|
|
416
|
-
...relationItem,
|
|
417
|
-
[sourceField]: parentReferenceValue,
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
if (existingId !== undefined && existingId !== null) {
|
|
421
|
-
const setPayload: Record<string, unknown> = {
|
|
422
|
-
...payload,
|
|
423
|
-
};
|
|
424
|
-
delete setPayload[targetReferenceField];
|
|
425
|
-
await tx
|
|
426
|
-
.update(targetTable as any)
|
|
427
|
-
.set(setPayload as any)
|
|
428
|
-
.where(
|
|
429
|
-
eq(targetReferenceColumn as any, existingId as any),
|
|
430
|
-
);
|
|
431
|
-
const linkedRow = await fetchRelatedRowByIdentifier(
|
|
432
|
-
tx,
|
|
433
|
-
relationInput.relation,
|
|
434
|
-
relationInput.relationName,
|
|
435
|
-
existingId,
|
|
436
|
-
);
|
|
437
|
-
if (linkedRow) {
|
|
438
|
-
relatedRows.push(linkedRow);
|
|
439
|
-
}
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
let createQuery: any = tx
|
|
444
|
-
.insert(targetTable as any)
|
|
445
|
-
.values(payload as any);
|
|
446
|
-
if (typeof createQuery.returning === "function") {
|
|
447
|
-
createQuery = createQuery.returning();
|
|
448
|
-
}
|
|
449
|
-
const createdRows = (await createQuery) as Array<
|
|
450
|
-
Record<string, unknown>
|
|
451
|
-
>;
|
|
452
|
-
const createdRow = createdRows[0];
|
|
453
|
-
if (createdRow) {
|
|
454
|
-
relatedRows.push(createdRow);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const junctionTable = (mergedSchema as Record<string, unknown>)[
|
|
461
|
-
relationInput.relation.junctionTable
|
|
462
|
-
];
|
|
463
|
-
if (!junctionTable) {
|
|
464
|
-
throw new Error(
|
|
465
|
-
"Unknown junction table '" +
|
|
466
|
-
relationInput.relation.junctionTable +
|
|
467
|
-
"' for relation '" +
|
|
468
|
-
tableName +
|
|
469
|
-
"." +
|
|
470
|
-
relationInput.relationName +
|
|
471
|
-
"'.",
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const sourceField = relationInput.relation.sourceField;
|
|
476
|
-
const targetField = relationInput.relation.targetField;
|
|
477
|
-
if (!sourceField || !targetField) {
|
|
478
|
-
throw new Error(
|
|
479
|
-
"Relation '" +
|
|
480
|
-
tableName +
|
|
481
|
-
"." +
|
|
482
|
-
relationInput.relationName +
|
|
483
|
-
"' is missing junction metadata fields.",
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const linkValues: Array<Record<string, unknown>> = [];
|
|
488
|
-
const targetIdentifiers: unknown[] = [];
|
|
489
|
-
for (const relationItem of relationItems) {
|
|
490
|
-
const targetId = await resolveRelationIdentifier(
|
|
491
|
-
tx,
|
|
492
|
-
relationInput.relation,
|
|
493
|
-
relationInput.relationName,
|
|
494
|
-
relationItem,
|
|
495
|
-
);
|
|
496
|
-
targetIdentifiers.push(targetId);
|
|
497
|
-
|
|
498
|
-
linkValues.push({
|
|
499
|
-
[sourceField]: parentReferenceValue,
|
|
500
|
-
[targetField]: targetId,
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (linkValues.length > 0) {
|
|
505
|
-
await tx.insert(junctionTable as any).values(linkValues as any);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
for (const targetId of targetIdentifiers) {
|
|
509
|
-
const linkedRow = await fetchRelatedRowByIdentifier(
|
|
510
|
-
tx,
|
|
511
|
-
relationInput.relation,
|
|
512
|
-
relationInput.relationName,
|
|
513
|
-
targetId,
|
|
514
|
-
);
|
|
515
|
-
if (linkedRow) {
|
|
516
|
-
relatedRows.push(linkedRow);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
hydratedRows.push(hydratedRow);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
return hydratedRows as Array<QueryInsertResultRow<TableName, TArgs>>;
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
let rows: Array<QueryInsertResultRow<TableName, TArgs>>;
|
|
528
|
-
if (typeof transaction === "function") {
|
|
529
|
-
try {
|
|
530
|
-
rows = await transaction.call($db, (tx: any) =>
|
|
531
|
-
executeInsertGraph(tx),
|
|
532
|
-
);
|
|
533
|
-
} catch (error) {
|
|
534
|
-
const message =
|
|
535
|
-
error instanceof Error ? error.message : String(error);
|
|
536
|
-
const lowered = message.toLowerCase();
|
|
537
|
-
if (
|
|
538
|
-
lowered.includes("failed query: begin") ||
|
|
539
|
-
lowered.includes('near "begin"') ||
|
|
540
|
-
lowered.includes("cannot start a transaction")
|
|
541
|
-
) {
|
|
542
|
-
rows = await executeInsertGraph($db as any);
|
|
543
|
-
} else {
|
|
544
|
-
throw error;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
} else {
|
|
548
|
-
rows = await executeInsertGraph($db as any);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
emitMutation(
|
|
552
|
-
"insert",
|
|
553
|
-
{ values: args.values as unknown as Record<string, unknown> },
|
|
554
|
-
rows,
|
|
555
|
-
);
|
|
556
|
-
return rows;
|
|
557
|
-
},
|
|
558
|
-
update: async (args: QueryUpdateArgs<TableName>) => {
|
|
559
|
-
const whereFilter = buildWhereFilter(
|
|
560
|
-
table,
|
|
561
|
-
args.where as Record<string, unknown> | undefined,
|
|
562
|
-
tableName,
|
|
563
|
-
);
|
|
564
|
-
let updateQuery: any = ($db as any)
|
|
565
|
-
.update(table as any)
|
|
566
|
-
.set(args.set as any);
|
|
567
|
-
|
|
568
|
-
if (whereFilter) {
|
|
569
|
-
updateQuery = updateQuery.where(whereFilter);
|
|
570
|
-
}
|
|
571
|
-
if (typeof args.limit === "number" && typeof updateQuery.limit === "function") {
|
|
572
|
-
updateQuery = updateQuery.limit(args.limit);
|
|
573
|
-
}
|
|
574
|
-
if (typeof updateQuery.returning === "function") {
|
|
575
|
-
updateQuery = updateQuery.returning();
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const rows = (await updateQuery) as Array<TableModel<TableName>>;
|
|
579
|
-
emitMutation(
|
|
580
|
-
"update",
|
|
581
|
-
{
|
|
582
|
-
set: args.set as unknown as Record<string, unknown>,
|
|
583
|
-
where: (args.where ?? undefined) as unknown as Record<string, unknown>,
|
|
584
|
-
limit: args.limit,
|
|
585
|
-
},
|
|
586
|
-
rows,
|
|
587
|
-
);
|
|
588
|
-
return rows;
|
|
589
|
-
},
|
|
590
|
-
upsert: async (args: QueryUpsertArgs<TableName>) => {
|
|
591
|
-
const valuesArray = Array.isArray(args.values)
|
|
592
|
-
? args.values
|
|
593
|
-
: [args.values];
|
|
594
|
-
const values = Array.isArray(args.values)
|
|
595
|
-
? args.values
|
|
596
|
-
: args.values;
|
|
597
|
-
|
|
598
|
-
const targets = args.target
|
|
599
|
-
? Array.isArray(args.target)
|
|
600
|
-
? args.target
|
|
601
|
-
: [args.target]
|
|
602
|
-
: inferConflictTarget(table);
|
|
603
|
-
|
|
604
|
-
if (targets.length === 0) {
|
|
605
|
-
throw new Error(
|
|
606
|
-
"Unable to infer conflict target for table " + tableName + ". Provide target explicitly.",
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const tableColumns = getTableColumns(table as never) as Record<string, unknown>;
|
|
611
|
-
const targetColumns = targets
|
|
612
|
-
.map((target) => tableColumns[target])
|
|
613
|
-
.filter(Boolean);
|
|
614
|
-
|
|
615
|
-
if (targetColumns.length === 0) {
|
|
616
|
-
throw new Error(
|
|
617
|
-
"Invalid conflict target for table " + tableName + ".",
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const setPayload = args.set ?? valuesArray[0] ?? {};
|
|
622
|
-
let upsertQuery: any = ($db as any)
|
|
623
|
-
.insert(table as any)
|
|
624
|
-
.values(values as any)
|
|
625
|
-
.onConflictDoUpdate({
|
|
626
|
-
target: targetColumns as any,
|
|
627
|
-
set: setPayload as any,
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
if (typeof upsertQuery.returning === "function") {
|
|
631
|
-
upsertQuery = upsertQuery.returning();
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const rows = (await upsertQuery) as Array<TableModel<TableName>>;
|
|
635
|
-
emitMutation(
|
|
636
|
-
"upsert",
|
|
637
|
-
{
|
|
638
|
-
values: args.values as unknown as Record<string, unknown>,
|
|
639
|
-
target: targets as unknown as Record<string, unknown>,
|
|
640
|
-
set: (args.set ?? undefined) as unknown as Record<string, unknown>,
|
|
641
|
-
},
|
|
642
|
-
rows,
|
|
643
|
-
);
|
|
644
|
-
return rows;
|
|
645
|
-
},
|
|
646
|
-
delete: async (args?: QueryDeleteArgs<TableName>) => {
|
|
647
|
-
const whereFilter = buildWhereFilter(
|
|
648
|
-
table,
|
|
649
|
-
args?.where as Record<string, unknown> | undefined,
|
|
650
|
-
tableName,
|
|
651
|
-
);
|
|
652
|
-
let deleteQuery: any = ($db as any).delete(table as any);
|
|
653
|
-
|
|
654
|
-
if (whereFilter) {
|
|
655
|
-
deleteQuery = deleteQuery.where(whereFilter);
|
|
656
|
-
}
|
|
657
|
-
if (typeof args?.limit === "number" && typeof deleteQuery.limit === "function") {
|
|
658
|
-
deleteQuery = deleteQuery.limit(args.limit);
|
|
659
|
-
}
|
|
660
|
-
if (typeof deleteQuery.returning === "function") {
|
|
661
|
-
deleteQuery = deleteQuery.returning();
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const rows = (await deleteQuery) as Array<TableModel<TableName>>;
|
|
665
|
-
emitMutation(
|
|
666
|
-
"delete",
|
|
667
|
-
{
|
|
668
|
-
where: (args?.where ?? undefined) as unknown as Record<string, unknown>,
|
|
669
|
-
limit: args?.limit,
|
|
670
|
-
},
|
|
671
|
-
rows,
|
|
672
|
-
);
|
|
673
|
-
return rows;
|
|
674
|
-
},
|
|
675
|
-
`;
|
|
676
|
-
}
|
|
1
|
+
export function generateQueryRuntimeWriteSection(): string {
|
|
2
|
+
return ` insert: async <TArgs extends QueryInsertArgs<TableName>>(args: TArgs) => {
|
|
3
|
+
const transaction = ($db as any).transaction;
|
|
4
|
+
|
|
5
|
+
const valuesArray = Array.isArray(args.values)
|
|
6
|
+
? args.values
|
|
7
|
+
: [args.values];
|
|
8
|
+
const baseValuesArray: Array<Record<string, unknown>> = [];
|
|
9
|
+
const postInsertRelations: Array<
|
|
10
|
+
Array<{
|
|
11
|
+
relationName: string;
|
|
12
|
+
relation: RuntimeRelation;
|
|
13
|
+
value: unknown;
|
|
14
|
+
}>
|
|
15
|
+
> = [];
|
|
16
|
+
|
|
17
|
+
const toRelationItems = (
|
|
18
|
+
relation: RuntimeRelation,
|
|
19
|
+
relationName: string,
|
|
20
|
+
value: unknown,
|
|
21
|
+
): unknown[] => {
|
|
22
|
+
if (value === undefined || value === null) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (relation.kind === "one") {
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Relation '" +
|
|
30
|
+
tableName +
|
|
31
|
+
"." +
|
|
32
|
+
relationName +
|
|
33
|
+
"' expects a single value.",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return [value];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Array.isArray(value) ? value : [value];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const resolveRelationIdentifier = async (
|
|
43
|
+
tx: any,
|
|
44
|
+
relation: RuntimeRelation,
|
|
45
|
+
relationName: string,
|
|
46
|
+
input: unknown,
|
|
47
|
+
): Promise<unknown> => {
|
|
48
|
+
if (
|
|
49
|
+
typeof input === "string" ||
|
|
50
|
+
typeof input === "number" ||
|
|
51
|
+
typeof input === "bigint"
|
|
52
|
+
) {
|
|
53
|
+
return input;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!isRecord(input)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Relation '" +
|
|
59
|
+
tableName +
|
|
60
|
+
"." +
|
|
61
|
+
relationName +
|
|
62
|
+
"' expects an id or object payload.",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const referenceField =
|
|
67
|
+
relation.kind === "manyToMany"
|
|
68
|
+
? (relation.targetReferenceField ?? "id")
|
|
69
|
+
: (relation.referenceField ?? "id");
|
|
70
|
+
const existingId = input[referenceField];
|
|
71
|
+
if (existingId !== undefined && existingId !== null) {
|
|
72
|
+
return existingId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const targetTable = (mergedSchema as Record<string, unknown>)[
|
|
76
|
+
relation.targetTable
|
|
77
|
+
];
|
|
78
|
+
if (!targetTable) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Unknown target table '" +
|
|
81
|
+
relation.targetTable +
|
|
82
|
+
"' for relation '" +
|
|
83
|
+
tableName +
|
|
84
|
+
"." +
|
|
85
|
+
relationName +
|
|
86
|
+
"'.",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let createQuery: any = tx.insert(targetTable as any).values(input as any);
|
|
91
|
+
if (typeof createQuery.returning === "function") {
|
|
92
|
+
createQuery = createQuery.returning();
|
|
93
|
+
}
|
|
94
|
+
const createdRows = (await createQuery) as Array<Record<string, unknown>>;
|
|
95
|
+
const created = createdRows[0];
|
|
96
|
+
if (!created) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"Failed to create relation target for '" +
|
|
99
|
+
tableName +
|
|
100
|
+
"." +
|
|
101
|
+
relationName +
|
|
102
|
+
"'.",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const createdId = created[referenceField];
|
|
107
|
+
if (createdId === undefined || createdId === null) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Created relation target for '" +
|
|
110
|
+
tableName +
|
|
111
|
+
"." +
|
|
112
|
+
relationName +
|
|
113
|
+
"' is missing '" +
|
|
114
|
+
referenceField +
|
|
115
|
+
"'.",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return createdId;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const getRelationTargetTable = (
|
|
123
|
+
relation: RuntimeRelation,
|
|
124
|
+
relationName: string,
|
|
125
|
+
): any => {
|
|
126
|
+
const targetTable = (mergedSchema as Record<string, unknown>)[
|
|
127
|
+
relation.targetTable
|
|
128
|
+
];
|
|
129
|
+
if (!targetTable) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
"Unknown target table '" +
|
|
132
|
+
relation.targetTable +
|
|
133
|
+
"' for relation '" +
|
|
134
|
+
tableName +
|
|
135
|
+
"." +
|
|
136
|
+
relationName +
|
|
137
|
+
"'.",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return targetTable;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const getRelationReferenceField = (relation: RuntimeRelation): string => {
|
|
144
|
+
if (relation.kind === "manyToMany") {
|
|
145
|
+
return relation.targetReferenceField ?? "id";
|
|
146
|
+
}
|
|
147
|
+
return relation.referenceField ?? "id";
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const fetchRelatedRowByIdentifier = async (
|
|
151
|
+
tx: any,
|
|
152
|
+
relation: RuntimeRelation,
|
|
153
|
+
relationName: string,
|
|
154
|
+
identifier: unknown,
|
|
155
|
+
): Promise<Record<string, unknown> | null> => {
|
|
156
|
+
if (identifier === undefined || identifier === null) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const targetTable = getRelationTargetTable(relation, relationName);
|
|
161
|
+
const referenceField = getRelationReferenceField(relation);
|
|
162
|
+
const referenceColumn = (targetTable as Record<string, unknown>)[
|
|
163
|
+
referenceField
|
|
164
|
+
];
|
|
165
|
+
if (!referenceColumn) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
"Target table '" +
|
|
168
|
+
relation.targetTable +
|
|
169
|
+
"' is missing column '" +
|
|
170
|
+
referenceField +
|
|
171
|
+
"' required by relation '" +
|
|
172
|
+
tableName +
|
|
173
|
+
"." +
|
|
174
|
+
relationName +
|
|
175
|
+
"'.",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let query: any = tx
|
|
180
|
+
.select()
|
|
181
|
+
.from(targetTable as any)
|
|
182
|
+
.where(eq(referenceColumn as any, identifier as any));
|
|
183
|
+
if (typeof query.limit === "function") {
|
|
184
|
+
query = query.limit(1);
|
|
185
|
+
}
|
|
186
|
+
const rows = (await query) as Array<Record<string, unknown>>;
|
|
187
|
+
return rows[0] ?? null;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
for (const inputValue of valuesArray) {
|
|
191
|
+
const scalarValues: Record<string, unknown> = {};
|
|
192
|
+
const relationInputs: Array<{
|
|
193
|
+
relationName: string;
|
|
194
|
+
relation: RuntimeRelation;
|
|
195
|
+
value: unknown;
|
|
196
|
+
}> = [];
|
|
197
|
+
|
|
198
|
+
const valueRecord = (inputValue ?? {}) as Record<string, unknown>;
|
|
199
|
+
for (const [fieldName, fieldValue] of Object.entries(valueRecord)) {
|
|
200
|
+
const runtimeRelation = getRuntimeRelation(tableName, fieldName);
|
|
201
|
+
if (runtimeRelation) {
|
|
202
|
+
relationInputs.push({
|
|
203
|
+
relationName: fieldName,
|
|
204
|
+
relation: runtimeRelation,
|
|
205
|
+
value: fieldValue,
|
|
206
|
+
});
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
scalarValues[fieldName] = fieldValue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
baseValuesArray.push(scalarValues);
|
|
213
|
+
postInsertRelations.push(relationInputs);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const executeInsertGraph = async (
|
|
217
|
+
tx: any,
|
|
218
|
+
): Promise<Array<QueryInsertResultRow<TableName, TArgs>>> => {
|
|
219
|
+
for (let index = 0; index < baseValuesArray.length; index += 1) {
|
|
220
|
+
const relationInputs = postInsertRelations[index] ?? [];
|
|
221
|
+
for (const relationInput of relationInputs) {
|
|
222
|
+
if (relationInput.relation.kind !== "one") {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const sourceField = relationInput.relation.sourceField;
|
|
227
|
+
if (!sourceField) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
"Relation '" +
|
|
230
|
+
tableName +
|
|
231
|
+
"." +
|
|
232
|
+
relationInput.relationName +
|
|
233
|
+
"' is missing sourceField metadata.",
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const oneItems = toRelationItems(
|
|
238
|
+
relationInput.relation,
|
|
239
|
+
relationInput.relationName,
|
|
240
|
+
relationInput.value,
|
|
241
|
+
);
|
|
242
|
+
if (oneItems.length === 0) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const relationId = await resolveRelationIdentifier(
|
|
247
|
+
tx,
|
|
248
|
+
relationInput.relation,
|
|
249
|
+
relationInput.relationName,
|
|
250
|
+
oneItems[0],
|
|
251
|
+
);
|
|
252
|
+
const existingValue = baseValuesArray[index][sourceField];
|
|
253
|
+
if (
|
|
254
|
+
existingValue !== undefined &&
|
|
255
|
+
existingValue !== relationId
|
|
256
|
+
) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
"Insert payload for '" +
|
|
259
|
+
tableName +
|
|
260
|
+
"' has conflicting values for '" +
|
|
261
|
+
sourceField +
|
|
262
|
+
"'.",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
baseValuesArray[index][sourceField] = relationId;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let insertQuery: any = tx.insert(table as any).values(baseValuesArray as any);
|
|
270
|
+
if (typeof insertQuery.returning === "function") {
|
|
271
|
+
insertQuery = insertQuery.returning();
|
|
272
|
+
}
|
|
273
|
+
const insertedRows = (await insertQuery) as Array<TableModel<TableName>>;
|
|
274
|
+
const hydratedRows: Array<Record<string, unknown>> = [];
|
|
275
|
+
|
|
276
|
+
for (let index = 0; index < insertedRows.length; index += 1) {
|
|
277
|
+
const parentRow = insertedRows[index] as unknown as Record<string, unknown>;
|
|
278
|
+
const hydratedRow: Record<string, unknown> = {
|
|
279
|
+
...parentRow,
|
|
280
|
+
};
|
|
281
|
+
const relationInputs = postInsertRelations[index] ?? [];
|
|
282
|
+
|
|
283
|
+
for (const relationInput of relationInputs) {
|
|
284
|
+
if (relationInput.relation.kind === "one") {
|
|
285
|
+
const sourceField = relationInput.relation.sourceField;
|
|
286
|
+
if (!sourceField) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
"Relation '" +
|
|
289
|
+
tableName +
|
|
290
|
+
"." +
|
|
291
|
+
relationInput.relationName +
|
|
292
|
+
"' is missing sourceField metadata.",
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const relationItems = toRelationItems(
|
|
297
|
+
relationInput.relation,
|
|
298
|
+
relationInput.relationName,
|
|
299
|
+
relationInput.value,
|
|
300
|
+
);
|
|
301
|
+
if (relationItems.length === 0) {
|
|
302
|
+
hydratedRow[relationInput.relationName] = null;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const sourceIdentifier = parentRow[sourceField];
|
|
307
|
+
hydratedRow[relationInput.relationName] =
|
|
308
|
+
(await fetchRelatedRowByIdentifier(
|
|
309
|
+
tx,
|
|
310
|
+
relationInput.relation,
|
|
311
|
+
relationInput.relationName,
|
|
312
|
+
sourceIdentifier,
|
|
313
|
+
)) ?? null;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const sourceReferenceField = relationInput.relation.referenceField ?? "id";
|
|
318
|
+
const parentReferenceValue = parentRow[sourceReferenceField];
|
|
319
|
+
if (
|
|
320
|
+
parentReferenceValue === undefined ||
|
|
321
|
+
parentReferenceValue === null
|
|
322
|
+
) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
"Inserted row for '" +
|
|
325
|
+
tableName +
|
|
326
|
+
"' is missing '" +
|
|
327
|
+
sourceReferenceField +
|
|
328
|
+
"' required by relation '" +
|
|
329
|
+
relationInput.relationName +
|
|
330
|
+
"'.",
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const relatedRows: Array<Record<string, unknown>> = [];
|
|
335
|
+
hydratedRow[relationInput.relationName] = relatedRows;
|
|
336
|
+
|
|
337
|
+
const relationItems = toRelationItems(
|
|
338
|
+
relationInput.relation,
|
|
339
|
+
relationInput.relationName,
|
|
340
|
+
relationInput.value,
|
|
341
|
+
);
|
|
342
|
+
if (relationItems.length === 0) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (relationInput.relation.kind === "many") {
|
|
347
|
+
const targetTable = getRelationTargetTable(
|
|
348
|
+
relationInput.relation,
|
|
349
|
+
relationInput.relationName,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const sourceField = relationInput.relation.sourceField;
|
|
353
|
+
const targetReferenceField =
|
|
354
|
+
relationInput.relation.referenceField ?? "id";
|
|
355
|
+
const targetReferenceColumn =
|
|
356
|
+
(targetTable as Record<string, unknown>)[targetReferenceField];
|
|
357
|
+
if (!sourceField) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
"Relation '" +
|
|
360
|
+
tableName +
|
|
361
|
+
"." +
|
|
362
|
+
relationInput.relationName +
|
|
363
|
+
"' is missing sourceField metadata.",
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (!targetReferenceColumn) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
"Target table '" +
|
|
369
|
+
relationInput.relation.targetTable +
|
|
370
|
+
"' is missing column '" +
|
|
371
|
+
targetReferenceField +
|
|
372
|
+
"' required by relation '" +
|
|
373
|
+
tableName +
|
|
374
|
+
"." +
|
|
375
|
+
relationInput.relationName +
|
|
376
|
+
"'.",
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for (const relationItem of relationItems) {
|
|
381
|
+
if (
|
|
382
|
+
typeof relationItem === "string" ||
|
|
383
|
+
typeof relationItem === "number" ||
|
|
384
|
+
typeof relationItem === "bigint"
|
|
385
|
+
) {
|
|
386
|
+
await tx
|
|
387
|
+
.update(targetTable as any)
|
|
388
|
+
.set({ [sourceField]: parentReferenceValue } as any)
|
|
389
|
+
.where(
|
|
390
|
+
eq(targetReferenceColumn as any, relationItem as any),
|
|
391
|
+
);
|
|
392
|
+
const linkedRow = await fetchRelatedRowByIdentifier(
|
|
393
|
+
tx,
|
|
394
|
+
relationInput.relation,
|
|
395
|
+
relationInput.relationName,
|
|
396
|
+
relationItem,
|
|
397
|
+
);
|
|
398
|
+
if (linkedRow) {
|
|
399
|
+
relatedRows.push(linkedRow);
|
|
400
|
+
}
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!isRecord(relationItem)) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
"Relation '" +
|
|
407
|
+
tableName +
|
|
408
|
+
"." +
|
|
409
|
+
relationInput.relationName +
|
|
410
|
+
"' expects id or object payloads.",
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const existingId = relationItem[targetReferenceField];
|
|
415
|
+
const payload = {
|
|
416
|
+
...relationItem,
|
|
417
|
+
[sourceField]: parentReferenceValue,
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
if (existingId !== undefined && existingId !== null) {
|
|
421
|
+
const setPayload: Record<string, unknown> = {
|
|
422
|
+
...payload,
|
|
423
|
+
};
|
|
424
|
+
delete setPayload[targetReferenceField];
|
|
425
|
+
await tx
|
|
426
|
+
.update(targetTable as any)
|
|
427
|
+
.set(setPayload as any)
|
|
428
|
+
.where(
|
|
429
|
+
eq(targetReferenceColumn as any, existingId as any),
|
|
430
|
+
);
|
|
431
|
+
const linkedRow = await fetchRelatedRowByIdentifier(
|
|
432
|
+
tx,
|
|
433
|
+
relationInput.relation,
|
|
434
|
+
relationInput.relationName,
|
|
435
|
+
existingId,
|
|
436
|
+
);
|
|
437
|
+
if (linkedRow) {
|
|
438
|
+
relatedRows.push(linkedRow);
|
|
439
|
+
}
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let createQuery: any = tx
|
|
444
|
+
.insert(targetTable as any)
|
|
445
|
+
.values(payload as any);
|
|
446
|
+
if (typeof createQuery.returning === "function") {
|
|
447
|
+
createQuery = createQuery.returning();
|
|
448
|
+
}
|
|
449
|
+
const createdRows = (await createQuery) as Array<
|
|
450
|
+
Record<string, unknown>
|
|
451
|
+
>;
|
|
452
|
+
const createdRow = createdRows[0];
|
|
453
|
+
if (createdRow) {
|
|
454
|
+
relatedRows.push(createdRow);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const junctionTable = (mergedSchema as Record<string, unknown>)[
|
|
461
|
+
relationInput.relation.junctionTable
|
|
462
|
+
];
|
|
463
|
+
if (!junctionTable) {
|
|
464
|
+
throw new Error(
|
|
465
|
+
"Unknown junction table '" +
|
|
466
|
+
relationInput.relation.junctionTable +
|
|
467
|
+
"' for relation '" +
|
|
468
|
+
tableName +
|
|
469
|
+
"." +
|
|
470
|
+
relationInput.relationName +
|
|
471
|
+
"'.",
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const sourceField = relationInput.relation.sourceField;
|
|
476
|
+
const targetField = relationInput.relation.targetField;
|
|
477
|
+
if (!sourceField || !targetField) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
"Relation '" +
|
|
480
|
+
tableName +
|
|
481
|
+
"." +
|
|
482
|
+
relationInput.relationName +
|
|
483
|
+
"' is missing junction metadata fields.",
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const linkValues: Array<Record<string, unknown>> = [];
|
|
488
|
+
const targetIdentifiers: unknown[] = [];
|
|
489
|
+
for (const relationItem of relationItems) {
|
|
490
|
+
const targetId = await resolveRelationIdentifier(
|
|
491
|
+
tx,
|
|
492
|
+
relationInput.relation,
|
|
493
|
+
relationInput.relationName,
|
|
494
|
+
relationItem,
|
|
495
|
+
);
|
|
496
|
+
targetIdentifiers.push(targetId);
|
|
497
|
+
|
|
498
|
+
linkValues.push({
|
|
499
|
+
[sourceField]: parentReferenceValue,
|
|
500
|
+
[targetField]: targetId,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (linkValues.length > 0) {
|
|
505
|
+
await tx.insert(junctionTable as any).values(linkValues as any);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
for (const targetId of targetIdentifiers) {
|
|
509
|
+
const linkedRow = await fetchRelatedRowByIdentifier(
|
|
510
|
+
tx,
|
|
511
|
+
relationInput.relation,
|
|
512
|
+
relationInput.relationName,
|
|
513
|
+
targetId,
|
|
514
|
+
);
|
|
515
|
+
if (linkedRow) {
|
|
516
|
+
relatedRows.push(linkedRow);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
hydratedRows.push(hydratedRow);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return hydratedRows as Array<QueryInsertResultRow<TableName, TArgs>>;
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
let rows: Array<QueryInsertResultRow<TableName, TArgs>>;
|
|
528
|
+
if (typeof transaction === "function") {
|
|
529
|
+
try {
|
|
530
|
+
rows = await transaction.call($db, (tx: any) =>
|
|
531
|
+
executeInsertGraph(tx),
|
|
532
|
+
);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
const message =
|
|
535
|
+
error instanceof Error ? error.message : String(error);
|
|
536
|
+
const lowered = message.toLowerCase();
|
|
537
|
+
if (
|
|
538
|
+
lowered.includes("failed query: begin") ||
|
|
539
|
+
lowered.includes('near "begin"') ||
|
|
540
|
+
lowered.includes("cannot start a transaction")
|
|
541
|
+
) {
|
|
542
|
+
rows = await executeInsertGraph($db as any);
|
|
543
|
+
} else {
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
548
|
+
rows = await executeInsertGraph($db as any);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
emitMutation(
|
|
552
|
+
"insert",
|
|
553
|
+
{ values: args.values as unknown as Record<string, unknown> },
|
|
554
|
+
rows,
|
|
555
|
+
);
|
|
556
|
+
return rows;
|
|
557
|
+
},
|
|
558
|
+
update: async (args: QueryUpdateArgs<TableName>) => {
|
|
559
|
+
const whereFilter = buildWhereFilter(
|
|
560
|
+
table,
|
|
561
|
+
args.where as Record<string, unknown> | undefined,
|
|
562
|
+
tableName,
|
|
563
|
+
);
|
|
564
|
+
let updateQuery: any = ($db as any)
|
|
565
|
+
.update(table as any)
|
|
566
|
+
.set(args.set as any);
|
|
567
|
+
|
|
568
|
+
if (whereFilter) {
|
|
569
|
+
updateQuery = updateQuery.where(whereFilter);
|
|
570
|
+
}
|
|
571
|
+
if (typeof args.limit === "number" && typeof updateQuery.limit === "function") {
|
|
572
|
+
updateQuery = updateQuery.limit(args.limit);
|
|
573
|
+
}
|
|
574
|
+
if (typeof updateQuery.returning === "function") {
|
|
575
|
+
updateQuery = updateQuery.returning();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const rows = (await updateQuery) as Array<TableModel<TableName>>;
|
|
579
|
+
emitMutation(
|
|
580
|
+
"update",
|
|
581
|
+
{
|
|
582
|
+
set: args.set as unknown as Record<string, unknown>,
|
|
583
|
+
where: (args.where ?? undefined) as unknown as Record<string, unknown>,
|
|
584
|
+
limit: args.limit,
|
|
585
|
+
},
|
|
586
|
+
rows,
|
|
587
|
+
);
|
|
588
|
+
return rows;
|
|
589
|
+
},
|
|
590
|
+
upsert: async (args: QueryUpsertArgs<TableName>) => {
|
|
591
|
+
const valuesArray = Array.isArray(args.values)
|
|
592
|
+
? args.values
|
|
593
|
+
: [args.values];
|
|
594
|
+
const values = Array.isArray(args.values)
|
|
595
|
+
? args.values
|
|
596
|
+
: args.values;
|
|
597
|
+
|
|
598
|
+
const targets = args.target
|
|
599
|
+
? Array.isArray(args.target)
|
|
600
|
+
? args.target
|
|
601
|
+
: [args.target]
|
|
602
|
+
: inferConflictTarget(table);
|
|
603
|
+
|
|
604
|
+
if (targets.length === 0) {
|
|
605
|
+
throw new Error(
|
|
606
|
+
"Unable to infer conflict target for table " + tableName + ". Provide target explicitly.",
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const tableColumns = getTableColumns(table as never) as Record<string, unknown>;
|
|
611
|
+
const targetColumns = targets
|
|
612
|
+
.map((target) => tableColumns[target])
|
|
613
|
+
.filter(Boolean);
|
|
614
|
+
|
|
615
|
+
if (targetColumns.length === 0) {
|
|
616
|
+
throw new Error(
|
|
617
|
+
"Invalid conflict target for table " + tableName + ".",
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const setPayload = args.set ?? valuesArray[0] ?? {};
|
|
622
|
+
let upsertQuery: any = ($db as any)
|
|
623
|
+
.insert(table as any)
|
|
624
|
+
.values(values as any)
|
|
625
|
+
.onConflictDoUpdate({
|
|
626
|
+
target: targetColumns as any,
|
|
627
|
+
set: setPayload as any,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
if (typeof upsertQuery.returning === "function") {
|
|
631
|
+
upsertQuery = upsertQuery.returning();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const rows = (await upsertQuery) as Array<TableModel<TableName>>;
|
|
635
|
+
emitMutation(
|
|
636
|
+
"upsert",
|
|
637
|
+
{
|
|
638
|
+
values: args.values as unknown as Record<string, unknown>,
|
|
639
|
+
target: targets as unknown as Record<string, unknown>,
|
|
640
|
+
set: (args.set ?? undefined) as unknown as Record<string, unknown>,
|
|
641
|
+
},
|
|
642
|
+
rows,
|
|
643
|
+
);
|
|
644
|
+
return rows;
|
|
645
|
+
},
|
|
646
|
+
delete: async (args?: QueryDeleteArgs<TableName>) => {
|
|
647
|
+
const whereFilter = buildWhereFilter(
|
|
648
|
+
table,
|
|
649
|
+
args?.where as Record<string, unknown> | undefined,
|
|
650
|
+
tableName,
|
|
651
|
+
);
|
|
652
|
+
let deleteQuery: any = ($db as any).delete(table as any);
|
|
653
|
+
|
|
654
|
+
if (whereFilter) {
|
|
655
|
+
deleteQuery = deleteQuery.where(whereFilter);
|
|
656
|
+
}
|
|
657
|
+
if (typeof args?.limit === "number" && typeof deleteQuery.limit === "function") {
|
|
658
|
+
deleteQuery = deleteQuery.limit(args.limit);
|
|
659
|
+
}
|
|
660
|
+
if (typeof deleteQuery.returning === "function") {
|
|
661
|
+
deleteQuery = deleteQuery.returning();
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const rows = (await deleteQuery) as Array<TableModel<TableName>>;
|
|
665
|
+
emitMutation(
|
|
666
|
+
"delete",
|
|
667
|
+
{
|
|
668
|
+
where: (args?.where ?? undefined) as unknown as Record<string, unknown>,
|
|
669
|
+
limit: args?.limit,
|
|
670
|
+
},
|
|
671
|
+
rows,
|
|
672
|
+
);
|
|
673
|
+
return rows;
|
|
674
|
+
},
|
|
675
|
+
`;
|
|
676
|
+
}
|