appflare 0.2.16 → 0.2.17
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/cli/templates/dashboard/builders/table-routes/fragments.ts +8 -5
- package/cli/templates/dashboard/builders/table-routes/table/index.ts +1 -1
- package/cli/templates/dashboard/builders/table-routes/users/html/table.ts +3 -2
- package/cli/templates/handlers/generators/types/query-definitions/filter-and-where-types.ts +36 -26
- package/cli/templates/handlers/generators/types/query-definitions/query-api-types.ts +3 -1
- package/cli/templates/handlers/generators/types/query-definitions/schema-and-table-types.ts +29 -35
- package/cli/templates/handlers/generators/types/query-runtime/runtime-write.ts +190 -70
- package/dist/cli/index.js +297 -170
- package/dist/cli/index.mjs +297 -170
- package/package.json +1 -1
- package/dist/cli/index.d.mts +0 -2
- package/dist/cli/index.d.ts +0 -2
|
@@ -52,12 +52,15 @@ export function buildColumnHeaders(
|
|
|
52
52
|
.join("");
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export function buildRowCells(columns: string[]): string {
|
|
55
|
+
export function buildRowCells(columns: string[], primaryKey?: string): string {
|
|
56
56
|
return columns
|
|
57
|
-
.map(
|
|
58
|
-
(column)
|
|
59
|
-
`<td><
|
|
60
|
-
|
|
57
|
+
.map((column) => {
|
|
58
|
+
if (primaryKey && column === primaryKey) {
|
|
59
|
+
return `<td><button type="button" class="truncate max-w-[200px] text-sm font-mono text-xs opacity-70 hover:opacity-100 cursor-copy text-left" title="Click to copy: \${String((row as any).${column} ?? '')}" data-copy-value="\${String((row as any).${column} ?? '')}" onclick="navigator.clipboard?.writeText(this.dataset.copyValue || '')">\${String((row as any).${column} ?? '')}</button></td>`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return `<td><div class="truncate max-w-[200px] text-sm" title="\${String((row as any).${column} ?? '')}">\${String((row as any).${column} ?? '')}</div></td>`;
|
|
63
|
+
})
|
|
61
64
|
.join("");
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -31,7 +31,7 @@ export function buildTableRoute(table: DiscoveredTable): string {
|
|
|
31
31
|
);
|
|
32
32
|
const searchConditions = buildSearchConditions(table);
|
|
33
33
|
const headers = buildColumnHeaders(table, columns);
|
|
34
|
-
const rowCells = buildRowCells(columns);
|
|
34
|
+
const rowCells = buildRowCells(columns, primaryKey);
|
|
35
35
|
const createInputs = createColumns
|
|
36
36
|
.map((columnName) => buildFieldInput(table, columnName, "create"))
|
|
37
37
|
.join("");
|
|
@@ -55,7 +55,7 @@ export function buildUsersTableHtml(): string {
|
|
|
55
55
|
\t\t\t\t\t\t<tbody>
|
|
56
56
|
\t\t\t\t\t\t\t\${data.map((row) => html\`
|
|
57
57
|
\t\t\t\t\t\t\t\t<tr class="hover:bg-base-200/30 transition-colors">
|
|
58
|
-
\t\t\t\t\t\t\t\t\t<td><
|
|
58
|
+
\t\t\t\t\t\t\t\t\t<td><button type="button" class="truncate max-w-[220px] text-sm font-mono text-xs opacity-70 hover:opacity-100 cursor-copy text-left" title="Click to copy: \${String((row as any).id ?? '')}" data-copy-value="\${String((row as any).id ?? '')}" onclick="navigator.clipboard?.writeText(this.dataset.copyValue || '')">\${String((row as any).id ?? '')}</button></td>
|
|
59
59
|
\t\t\t\t\t\t\t\t\t<td><div class="truncate max-w-[220px] text-sm" title="\${String((row as any).name ?? '')}">\${String((row as any).name ?? '')}</div></td>
|
|
60
60
|
\t\t\t\t\t\t\t\t\t<td><div class="truncate max-w-[260px] text-sm" title="\${String((row as any).email ?? '')}">\${String((row as any).email ?? '')}</div></td>
|
|
61
61
|
\t\t\t\t\t\t\t\t\t<td><span class="badge badge-sm \${String((row as any).role ?? '') === 'admin' ? 'badge-primary' : 'badge-ghost'}">\${String((row as any).role ?? '')}</span></td>
|
|
@@ -123,5 +123,6 @@ ${buildDeleteUserModal()}
|
|
|
123
123
|
\t\t\t\t</div>
|
|
124
124
|
\t\t\t\t${paginationHtml}
|
|
125
125
|
\t\t\t</div>
|
|
126
|
-
\t\t
|
|
126
|
+
\t\t\`;
|
|
127
|
+
`;
|
|
127
128
|
}
|
|
@@ -143,11 +143,11 @@ type QueryWithRelationInputForTable<
|
|
|
143
143
|
TSourceTable extends TableName,
|
|
144
144
|
TRelationName extends string,
|
|
145
145
|
TEntry,
|
|
146
|
-
> = ManyToManyTargetTableName<TSourceTable, TRelationName> extends
|
|
147
|
-
?
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
> = [ManyToManyTargetTableName<TSourceTable, TRelationName>] extends [never]
|
|
147
|
+
? QueryNativeWithRelationInput<TEntry>
|
|
148
|
+
: QueryManyToManyWithRelationInput<
|
|
149
|
+
Extract<ManyToManyTargetTableName<TSourceTable, TRelationName>, TableName>
|
|
150
|
+
>;
|
|
151
151
|
|
|
152
152
|
type QueryWithInputForTable<
|
|
153
153
|
TSourceTable extends TableName,
|
|
@@ -183,13 +183,6 @@ export type QueryFindFirstArgs<TName extends TableName> = Omit<
|
|
|
183
183
|
with?: QueryWithInput<TName, NativeFindFirstWith<TName>>;
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
-
type SingularizeRelationName<TName extends string> =
|
|
187
|
-
TName extends \`\${infer TRoot\}ies\`
|
|
188
|
-
? \`\${TRoot\}y\`
|
|
189
|
-
: TName extends \`\${infer TRoot\}s\`
|
|
190
|
-
? TRoot
|
|
191
|
-
: TName;
|
|
192
|
-
|
|
193
186
|
type RelationInsertItem<TTargetTable extends TableName> =
|
|
194
187
|
| TableModel<TTargetTable>
|
|
195
188
|
| TableInsertModel<TTargetTable>
|
|
@@ -215,21 +208,38 @@ type RelationInsertFields<TName extends TableName> = {
|
|
|
215
208
|
>;
|
|
216
209
|
};
|
|
217
210
|
|
|
218
|
-
type RelationInsertIdAliasFields<TName extends TableName> = {
|
|
219
|
-
[TRelationName in RuntimeRelationName<TName> as \`\${TRelationName\}Id\`]?: RelationInsertValue<
|
|
220
|
-
TName,
|
|
221
|
-
TRelationName
|
|
222
|
-
>;
|
|
223
|
-
} & {
|
|
224
|
-
[TRelationName in RuntimeRelationName<TName> as \`\${SingularizeRelationName<TRelationName>\}Id\`]?: RelationInsertValue<
|
|
225
|
-
TName,
|
|
226
|
-
TRelationName
|
|
227
|
-
>;
|
|
228
|
-
};
|
|
229
|
-
|
|
230
211
|
type QueryInsertValue<TName extends TableName> = TableInsertScalarModel<TName> &
|
|
231
|
-
RelationInsertFields<TName
|
|
232
|
-
|
|
212
|
+
RelationInsertFields<TName>;
|
|
213
|
+
|
|
214
|
+
type InsertItemValue<TArgs> = TArgs extends { values: infer TValue }
|
|
215
|
+
? TValue extends Array<infer TItem>
|
|
216
|
+
? TItem
|
|
217
|
+
: TValue
|
|
218
|
+
: never;
|
|
219
|
+
|
|
220
|
+
type InsertRelationNameFromArgs<
|
|
221
|
+
TName extends TableName,
|
|
222
|
+
TArgs,
|
|
223
|
+
> = Extract<keyof InsertItemValue<TArgs>, RuntimeRelationName<TName>>;
|
|
224
|
+
|
|
225
|
+
type InsertRelationResultValue<
|
|
226
|
+
TSourceTable extends TableName,
|
|
227
|
+
TRelationName extends RuntimeRelationName<TSourceTable>,
|
|
228
|
+
> = RuntimeRelationTargetTable<TSourceTable, TRelationName> extends infer TTargetTable
|
|
229
|
+
? TTargetTable extends TableName
|
|
230
|
+
? RuntimeRelationKind<TSourceTable, TRelationName> extends "one"
|
|
231
|
+
? TableModel<TTargetTable> | null
|
|
232
|
+
: Array<TableModel<TTargetTable>>
|
|
233
|
+
: never
|
|
234
|
+
: never;
|
|
235
|
+
|
|
236
|
+
export type QueryInsertResultRow<
|
|
237
|
+
TName extends TableName,
|
|
238
|
+
TArgs extends QueryInsertArgs<TName>,
|
|
239
|
+
> = TableModel<TName> & {
|
|
240
|
+
[TRelationName in InsertRelationNameFromArgs<TName, TArgs>]:
|
|
241
|
+
InsertRelationResultValue<TName, TRelationName>;
|
|
242
|
+
};
|
|
233
243
|
|
|
234
244
|
export type QueryInsertArgs<TName extends TableName> = {
|
|
235
245
|
values: QueryInsertValue<TName> | Array<QueryInsertValue<TName>>;
|
|
@@ -105,7 +105,9 @@ export type QueryTableApi<TName extends TableName> = {
|
|
|
105
105
|
>(
|
|
106
106
|
args?: TArgs,
|
|
107
107
|
) => ApplyFindFirstWithAggregateResult<TableFindFirstResult<TName, TArgs>, TArgs>;
|
|
108
|
-
insert:
|
|
108
|
+
insert: <TArgs extends QueryInsertArgs<TName>>(
|
|
109
|
+
args: TArgs,
|
|
110
|
+
) => Promise<Array<QueryInsertResultRow<TName, TArgs>>>;
|
|
109
111
|
update: (args: QueryUpdateArgs<TName>) => Promise<Array<TableModel<TName>>>;
|
|
110
112
|
upsert: (args: QueryUpsertArgs<TName>) => Promise<Array<TableModel<TName>>>;
|
|
111
113
|
delete: (args?: QueryDeleteArgs<TName>) => Promise<Array<TableModel<TName>>>;
|
|
@@ -48,45 +48,44 @@ type TableModel<TName extends TableName> = InferSelectModel<
|
|
|
48
48
|
(typeof mergedSchema)[TName]
|
|
49
49
|
>;
|
|
50
50
|
type ManyToManySchemaMap = typeof schema extends {
|
|
51
|
-
__appflareManyToMany: infer TMap
|
|
51
|
+
__appflareManyToMany: infer TMap extends Record<string, unknown>;
|
|
52
52
|
}
|
|
53
|
-
? TMap
|
|
54
|
-
? TMap
|
|
55
|
-
: {}
|
|
53
|
+
? TMap
|
|
56
54
|
: {};
|
|
55
|
+
type ManyToManySourceTableMap<TSourceTable extends TableName> =
|
|
56
|
+
TSourceTable extends keyof ManyToManySchemaMap
|
|
57
|
+
? ManyToManySchemaMap[TSourceTable] extends Record<string, unknown>
|
|
58
|
+
? ManyToManySchemaMap[TSourceTable]
|
|
59
|
+
: {}
|
|
60
|
+
: {};
|
|
57
61
|
type ManyToManyTargetTableName<
|
|
58
62
|
TSourceTable extends TableName,
|
|
59
63
|
TRelationName extends string,
|
|
60
|
-
> =
|
|
61
|
-
? TRelationName extends
|
|
62
|
-
? ManyToManySchemaMap[TSourceTable][TRelationName] extends {
|
|
64
|
+
> = TRelationName extends keyof ManyToManySourceTableMap<TSourceTable>
|
|
65
|
+
? ManyToManySourceTableMap<TSourceTable>[TRelationName] extends {
|
|
63
66
|
targetTable: infer TTarget extends string;
|
|
64
67
|
}
|
|
65
68
|
? Extract<TTarget, TableName>
|
|
66
69
|
: never
|
|
67
|
-
: never
|
|
68
70
|
: never;
|
|
69
71
|
type RuntimeRelationsSchemaMap = typeof schema extends {
|
|
70
|
-
__appflareRelations: infer TMap
|
|
72
|
+
__appflareRelations: infer TMap extends Record<string, unknown>;
|
|
71
73
|
}
|
|
72
|
-
? TMap
|
|
73
|
-
string,
|
|
74
|
-
Record<string, { kind: string; targetTable: string }>
|
|
75
|
-
>
|
|
76
|
-
? TMap
|
|
77
|
-
: {}
|
|
74
|
+
? TMap
|
|
78
75
|
: {};
|
|
79
|
-
type
|
|
76
|
+
type RuntimeRelationsSourceTableMap<TSourceTable extends TableName> =
|
|
80
77
|
TSourceTable extends keyof RuntimeRelationsSchemaMap
|
|
81
|
-
?
|
|
82
|
-
|
|
78
|
+
? RuntimeRelationsSchemaMap[TSourceTable] extends Record<string, unknown>
|
|
79
|
+
? RuntimeRelationsSchemaMap[TSourceTable]
|
|
80
|
+
: {}
|
|
81
|
+
: {};
|
|
82
|
+
type RuntimeRelationName<TSourceTable extends TableName> =
|
|
83
|
+
Extract<keyof RuntimeRelationsSourceTableMap<TSourceTable>, string>;
|
|
83
84
|
type RuntimeRelationConfig<
|
|
84
85
|
TSourceTable extends TableName,
|
|
85
86
|
TRelationName extends string,
|
|
86
|
-
> =
|
|
87
|
-
? TRelationName
|
|
88
|
-
? RuntimeRelationsSchemaMap[TSourceTable][TRelationName]
|
|
89
|
-
: never
|
|
87
|
+
> = TRelationName extends keyof RuntimeRelationsSourceTableMap<TSourceTable>
|
|
88
|
+
? RuntimeRelationsSourceTableMap<TSourceTable>[TRelationName]
|
|
90
89
|
: never;
|
|
91
90
|
type RuntimeRelationKind<
|
|
92
91
|
TSourceTable extends TableName,
|
|
@@ -104,10 +103,7 @@ type RuntimeRelationTargetTable<
|
|
|
104
103
|
}
|
|
105
104
|
? Extract<TTarget, TableName>
|
|
106
105
|
: never;
|
|
107
|
-
type TableInsertScalarModel<TName extends TableName> =
|
|
108
|
-
TableInsertModel<TName>,
|
|
109
|
-
RuntimeRelationName<TName>
|
|
110
|
-
>;
|
|
106
|
+
type TableInsertScalarModel<TName extends TableName> = TableInsertModel<TName>;
|
|
111
107
|
type FindManyWithFromArgs<TArgs> = TArgs extends { with?: infer TWith }
|
|
112
108
|
? TWith
|
|
113
109
|
: undefined;
|
|
@@ -136,11 +132,12 @@ type ReplaceManyToManyRelationsInRow<
|
|
|
136
132
|
[K in keyof TRow]: K extends string
|
|
137
133
|
? TWith extends Record<string, unknown>
|
|
138
134
|
? K extends keyof TWith
|
|
139
|
-
? ManyToManyTargetTableName<TSourceTable, K> extends
|
|
140
|
-
?
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
? [ManyToManyTargetTableName<TSourceTable, K>] extends [never]
|
|
136
|
+
? TRow[K]
|
|
137
|
+
: ManyToManyRelationRows<
|
|
138
|
+
Extract<ManyToManyTargetTableName<TSourceTable, K>, TableName>,
|
|
139
|
+
TWith[K]
|
|
140
|
+
>
|
|
144
141
|
: TRow[K]
|
|
145
142
|
: TRow[K]
|
|
146
143
|
: TRow[K];
|
|
@@ -202,10 +199,7 @@ type TableFindFirstArgs<TName extends TableName> = Omit<
|
|
|
202
199
|
TableFindManyArgs<TName>,
|
|
203
200
|
"limit"
|
|
204
201
|
>;
|
|
205
|
-
type NativeFindFirstWith<TName extends TableName> =
|
|
206
|
-
NonNullable<TableFindFirstArgs<TName>> extends { with?: infer TWith }
|
|
207
|
-
? TWith
|
|
208
|
-
: never;
|
|
202
|
+
type NativeFindFirstWith<TName extends TableName> = NativeFindManyWith<TName>;
|
|
209
203
|
type ResolveNativeFindFirstWith<
|
|
210
204
|
TName extends TableName,
|
|
211
205
|
TArgs extends QueryFindFirstArgs<TName> | undefined,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function generateQueryRuntimeWriteSection(): string {
|
|
2
|
-
return ` insert: async (args:
|
|
2
|
+
return ` insert: async <TArgs extends QueryInsertArgs<TableName>>(args: TArgs) => {
|
|
3
3
|
const transaction = ($db as any).transaction;
|
|
4
4
|
|
|
5
5
|
const valuesArray = Array.isArray(args.values)
|
|
@@ -14,16 +14,6 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
14
14
|
}>
|
|
15
15
|
> = [];
|
|
16
16
|
|
|
17
|
-
const resolveSingularAlias = (relationName: string): string => {
|
|
18
|
-
if (relationName.endsWith("ies")) {
|
|
19
|
-
return relationName.slice(0, -3) + "yId";
|
|
20
|
-
}
|
|
21
|
-
if (relationName.endsWith("s") && relationName.length > 1) {
|
|
22
|
-
return relationName.slice(0, -1) + "Id";
|
|
23
|
-
}
|
|
24
|
-
return relationName + "Id";
|
|
25
|
-
};
|
|
26
|
-
|
|
27
17
|
const toRelationItems = (
|
|
28
18
|
relation: RuntimeRelation,
|
|
29
19
|
relationName: string,
|
|
@@ -76,7 +66,7 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
76
66
|
const referenceField =
|
|
77
67
|
relation.kind === "manyToMany"
|
|
78
68
|
? (relation.targetReferenceField ?? "id")
|
|
79
|
-
: "id";
|
|
69
|
+
: (relation.referenceField ?? "id");
|
|
80
70
|
const existingId = input[referenceField];
|
|
81
71
|
if (existingId !== undefined && existingId !== null) {
|
|
82
72
|
return existingId;
|
|
@@ -129,6 +119,74 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
129
119
|
return createdId;
|
|
130
120
|
};
|
|
131
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
|
+
|
|
132
190
|
for (const inputValue of valuesArray) {
|
|
133
191
|
const scalarValues: Record<string, unknown> = {};
|
|
134
192
|
const relationInputs: Array<{
|
|
@@ -148,50 +206,16 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
148
206
|
});
|
|
149
207
|
continue;
|
|
150
208
|
}
|
|
151
|
-
|
|
152
|
-
let matchedAlias = false;
|
|
153
|
-
if (fieldName.endsWith("Id")) {
|
|
154
|
-
for (const [relationName, relationValue] of Object.entries(
|
|
155
|
-
runtimeRelationMap[tableName] ?? {},
|
|
156
|
-
)) {
|
|
157
|
-
const pluralAlias = relationName + "Id";
|
|
158
|
-
const singularAlias = resolveSingularAlias(relationName);
|
|
159
|
-
if (fieldName !== pluralAlias && fieldName !== singularAlias) {
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (valueRecord[relationName] !== undefined) {
|
|
164
|
-
throw new Error(
|
|
165
|
-
"Insert payload for '" +
|
|
166
|
-
tableName +
|
|
167
|
-
"' cannot contain both '" +
|
|
168
|
-
relationName +
|
|
169
|
-
"' and '" +
|
|
170
|
-
fieldName +
|
|
171
|
-
"'.",
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
relationInputs.push({
|
|
176
|
-
relationName,
|
|
177
|
-
relation: relationValue,
|
|
178
|
-
value: fieldValue,
|
|
179
|
-
});
|
|
180
|
-
matchedAlias = true;
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (!matchedAlias) {
|
|
186
|
-
scalarValues[fieldName] = fieldValue;
|
|
187
|
-
}
|
|
209
|
+
scalarValues[fieldName] = fieldValue;
|
|
188
210
|
}
|
|
189
211
|
|
|
190
212
|
baseValuesArray.push(scalarValues);
|
|
191
213
|
postInsertRelations.push(relationInputs);
|
|
192
214
|
}
|
|
193
215
|
|
|
194
|
-
const executeInsertGraph = async (
|
|
216
|
+
const executeInsertGraph = async (
|
|
217
|
+
tx: any,
|
|
218
|
+
): Promise<Array<QueryInsertResultRow<TableName, TArgs>>> => {
|
|
195
219
|
for (let index = 0; index < baseValuesArray.length; index += 1) {
|
|
196
220
|
const relationInputs = postInsertRelations[index] ?? [];
|
|
197
221
|
for (const relationInput of relationInputs) {
|
|
@@ -247,13 +271,46 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
247
271
|
insertQuery = insertQuery.returning();
|
|
248
272
|
}
|
|
249
273
|
const insertedRows = (await insertQuery) as Array<TableModel<TableName>>;
|
|
274
|
+
const hydratedRows: Array<Record<string, unknown>> = [];
|
|
250
275
|
|
|
251
276
|
for (let index = 0; index < insertedRows.length; index += 1) {
|
|
252
277
|
const parentRow = insertedRows[index] as unknown as Record<string, unknown>;
|
|
278
|
+
const hydratedRow: Record<string, unknown> = {
|
|
279
|
+
...parentRow,
|
|
280
|
+
};
|
|
253
281
|
const relationInputs = postInsertRelations[index] ?? [];
|
|
254
282
|
|
|
255
283
|
for (const relationInput of relationInputs) {
|
|
256
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;
|
|
257
314
|
continue;
|
|
258
315
|
}
|
|
259
316
|
|
|
@@ -274,6 +331,9 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
274
331
|
);
|
|
275
332
|
}
|
|
276
333
|
|
|
334
|
+
const relatedRows: Array<Record<string, unknown>> = [];
|
|
335
|
+
hydratedRow[relationInput.relationName] = relatedRows;
|
|
336
|
+
|
|
277
337
|
const relationItems = toRelationItems(
|
|
278
338
|
relationInput.relation,
|
|
279
339
|
relationInput.relationName,
|
|
@@ -284,29 +344,36 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
284
344
|
}
|
|
285
345
|
|
|
286
346
|
if (relationInput.relation.kind === "many") {
|
|
287
|
-
const targetTable = (
|
|
288
|
-
relationInput.relation
|
|
289
|
-
|
|
290
|
-
|
|
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) {
|
|
291
358
|
throw new Error(
|
|
292
|
-
"
|
|
293
|
-
relationInput.relation.targetTable +
|
|
294
|
-
"' for relation '" +
|
|
359
|
+
"Relation '" +
|
|
295
360
|
tableName +
|
|
296
361
|
"." +
|
|
297
362
|
relationInput.relationName +
|
|
298
|
-
"'.",
|
|
363
|
+
"' is missing sourceField metadata.",
|
|
299
364
|
);
|
|
300
365
|
}
|
|
301
|
-
|
|
302
|
-
const sourceField = relationInput.relation.sourceField;
|
|
303
|
-
if (!sourceField) {
|
|
366
|
+
if (!targetReferenceColumn) {
|
|
304
367
|
throw new Error(
|
|
305
|
-
"
|
|
368
|
+
"Target table '" +
|
|
369
|
+
relationInput.relation.targetTable +
|
|
370
|
+
"' is missing column '" +
|
|
371
|
+
targetReferenceField +
|
|
372
|
+
"' required by relation '" +
|
|
306
373
|
tableName +
|
|
307
374
|
"." +
|
|
308
375
|
relationInput.relationName +
|
|
309
|
-
"'
|
|
376
|
+
"'.",
|
|
310
377
|
);
|
|
311
378
|
}
|
|
312
379
|
|
|
@@ -319,7 +386,18 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
319
386
|
await tx
|
|
320
387
|
.update(targetTable as any)
|
|
321
388
|
.set({ [sourceField]: parentReferenceValue } as any)
|
|
322
|
-
.where(
|
|
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
|
+
}
|
|
323
401
|
continue;
|
|
324
402
|
}
|
|
325
403
|
|
|
@@ -333,22 +411,48 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
333
411
|
);
|
|
334
412
|
}
|
|
335
413
|
|
|
336
|
-
const existingId = relationItem
|
|
414
|
+
const existingId = relationItem[targetReferenceField];
|
|
337
415
|
const payload = {
|
|
338
416
|
...relationItem,
|
|
339
417
|
[sourceField]: parentReferenceValue,
|
|
340
418
|
};
|
|
341
419
|
|
|
342
420
|
if (existingId !== undefined && existingId !== null) {
|
|
343
|
-
const
|
|
421
|
+
const setPayload: Record<string, unknown> = {
|
|
422
|
+
...payload,
|
|
423
|
+
};
|
|
424
|
+
delete setPayload[targetReferenceField];
|
|
344
425
|
await tx
|
|
345
426
|
.update(targetTable as any)
|
|
346
427
|
.set(setPayload as any)
|
|
347
|
-
.where(
|
|
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
|
+
}
|
|
348
440
|
continue;
|
|
349
441
|
}
|
|
350
442
|
|
|
351
|
-
|
|
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
|
+
}
|
|
352
456
|
}
|
|
353
457
|
continue;
|
|
354
458
|
}
|
|
@@ -381,6 +485,7 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
381
485
|
}
|
|
382
486
|
|
|
383
487
|
const linkValues: Array<Record<string, unknown>> = [];
|
|
488
|
+
const targetIdentifiers: unknown[] = [];
|
|
384
489
|
for (const relationItem of relationItems) {
|
|
385
490
|
const targetId = await resolveRelationIdentifier(
|
|
386
491
|
tx,
|
|
@@ -388,6 +493,7 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
388
493
|
relationInput.relationName,
|
|
389
494
|
relationItem,
|
|
390
495
|
);
|
|
496
|
+
targetIdentifiers.push(targetId);
|
|
391
497
|
|
|
392
498
|
linkValues.push({
|
|
393
499
|
[sourceField]: parentReferenceValue,
|
|
@@ -398,13 +504,27 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
398
504
|
if (linkValues.length > 0) {
|
|
399
505
|
await tx.insert(junctionTable as any).values(linkValues as any);
|
|
400
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
|
+
}
|
|
401
519
|
}
|
|
520
|
+
|
|
521
|
+
hydratedRows.push(hydratedRow);
|
|
402
522
|
}
|
|
403
523
|
|
|
404
|
-
return
|
|
524
|
+
return hydratedRows as Array<QueryInsertResultRow<TableName, TArgs>>;
|
|
405
525
|
};
|
|
406
526
|
|
|
407
|
-
let rows: Array<
|
|
527
|
+
let rows: Array<QueryInsertResultRow<TableName, TArgs>>;
|
|
408
528
|
if (typeof transaction === "function") {
|
|
409
529
|
try {
|
|
410
530
|
rows = await transaction.call($db, (tx: any) =>
|