@vantreeseba/drizzle-graphql 13.0.0 → 13.1.1
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/README.md +111 -5
- package/dist/index.cjs +334 -153
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -4
- package/dist/index.d.ts +98 -4
- package/dist/index.js +334 -153
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -165,6 +165,14 @@ const GraphQLDecimalString = new GraphQLScalarType({
|
|
|
165
165
|
const uncapitalize = (input) => input?.length ? `${input[0].toLocaleLowerCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
166
166
|
const capitalize = (input) => input?.length ? `${input[0].toLocaleUpperCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
167
167
|
/**
|
|
168
|
+
* `inProgress` / `in-progress` / `in progress` / `InProgress` → `IN_PROGRESS`; `HTTPError` →
|
|
169
|
+
* `HTTP_ERROR`. Words are split at non-alphanumeric runs and at lower→upper and
|
|
170
|
+
* acronym→word boundaries, then upper-cased and joined with `_`.
|
|
171
|
+
*
|
|
172
|
+
* The preset behind `enumValueNameMapper: 'constant'`.
|
|
173
|
+
*/
|
|
174
|
+
const constantCase = (input) => input.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.toUpperCase()).join("_");
|
|
175
|
+
/**
|
|
168
176
|
* The `typeNameMapper` preset behind `typeNameMapper: 'singularize'`.
|
|
169
177
|
*
|
|
170
178
|
* Derives the singular and plural forms of a table key with the library's own `pluralize`,
|
|
@@ -185,6 +193,8 @@ const singularizeMapper = (tableName) => ({
|
|
|
185
193
|
//#endregion
|
|
186
194
|
//#region src/util/type-converter/index.ts
|
|
187
195
|
const allowedNameChars = /^[a-zA-Z0-9_]+$/;
|
|
196
|
+
/** A GraphQL `Name` that is also allowed as an enum value — the spec reserves the three literals. */
|
|
197
|
+
const validEnumValueName = (name) => /^[_a-zA-Z][_a-zA-Z0-9]*$/.test(name) && name !== "true" && name !== "false" && name !== "null";
|
|
188
198
|
/**
|
|
189
199
|
* The enum type a column was declared with, when it is a *named* one — `pgEnum('status', …)`
|
|
190
200
|
* shared by however many tables reference it. An inline value list (`text({ enum: [...] })`,
|
|
@@ -205,20 +215,35 @@ const pascalize = (input) => input.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((p
|
|
|
205
215
|
*/
|
|
206
216
|
const enumValuesKey = (values) => JSON.stringify(values);
|
|
207
217
|
/**
|
|
218
|
+
* Identity of the GraphQL names those values were given. Kept apart from {@link enumValuesKey}
|
|
219
|
+
* because `enumValueNameMapper` sees the column, so two columns reaching one type can name its
|
|
220
|
+
* values differently — and only one set of names can win.
|
|
221
|
+
*/
|
|
222
|
+
const enumValueNamesKey = (names) => JSON.stringify(names);
|
|
223
|
+
/**
|
|
208
224
|
* Every GraphQL enum name this build has handed out. Two columns that resolve to the same
|
|
209
225
|
* name share one type — that is how the columns of one `pgEnum` end up on a single type —
|
|
210
226
|
* so the values are kept alongside it to tell a genuine collision from a re-use.
|
|
211
227
|
*/
|
|
212
228
|
let enumNamesInUse = /* @__PURE__ */ new Map();
|
|
213
229
|
let enumNameMapper;
|
|
230
|
+
let enumValueNameMapper;
|
|
231
|
+
/** The value-name presets, by the string a config passes for them. */
|
|
232
|
+
const enumValueNamePresets = { constant: ({ value }) => constantCase(value) };
|
|
214
233
|
/**
|
|
215
234
|
* Resets the enum registry for a fresh schema build and installs that build's
|
|
216
|
-
* `enumNameMapper`. Called once per `generateSchemaData`, alongside
|
|
235
|
+
* `enumNameMapper` and `enumValueNameMapper`. Called once per `generateSchemaData`, alongside
|
|
217
236
|
* {@link registerScalarOverrides} — without the reset a second build would reuse the first
|
|
218
237
|
* build's enum types, and so its naming decisions.
|
|
219
238
|
*/
|
|
220
239
|
const registerEnumConfig = (config) => {
|
|
221
240
|
enumNameMapper = config.enumNameMapper;
|
|
241
|
+
const valueMapper = config.enumValueNameMapper;
|
|
242
|
+
if (typeof valueMapper === "string") {
|
|
243
|
+
const preset = enumValueNamePresets[valueMapper];
|
|
244
|
+
if (!preset) throw new Error(`Drizzle-GraphQL Error: Unknown enumValueNameMapper preset '${valueMapper}'. The only preset is 'constant'; pass a function for anything else.`);
|
|
245
|
+
enumValueNameMapper = preset;
|
|
246
|
+
} else enumValueNameMapper = valueMapper;
|
|
222
247
|
enumNamesInUse = /* @__PURE__ */ new Map();
|
|
223
248
|
};
|
|
224
249
|
/**
|
|
@@ -247,22 +272,50 @@ const generateEnumCached = (column, columnName, tableName) => {
|
|
|
247
272
|
columnName,
|
|
248
273
|
values
|
|
249
274
|
}) ?? (declared ? `${pascalize(declared.enumName)}Enum` : `${capitalize(tableName)}${capitalize(columnName)}Enum`);
|
|
275
|
+
const valueNames = values.map((value, index) => {
|
|
276
|
+
const mapped = enumValueNameMapper?.({
|
|
277
|
+
enumName: declared?.enumName,
|
|
278
|
+
schema: declared?.schema,
|
|
279
|
+
tableName,
|
|
280
|
+
columnName,
|
|
281
|
+
values,
|
|
282
|
+
value,
|
|
283
|
+
index,
|
|
284
|
+
typeName: name
|
|
285
|
+
});
|
|
286
|
+
if (mapped === void 0) return allowedNameChars.test(value) ? value : `Option${index}`;
|
|
287
|
+
if (!validEnumValueName(mapped)) throw new Error(`Drizzle-GraphQL Error: config.enumValueNameMapper named value '${value}' of enum '${name}' (on '${tableName}.${columnName}') '${mapped}', which is not a valid GraphQL enum value name. A name must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ and not be true, false or null.`);
|
|
288
|
+
return mapped;
|
|
289
|
+
});
|
|
290
|
+
const seen = /* @__PURE__ */ new Map();
|
|
291
|
+
values.forEach((value, index) => {
|
|
292
|
+
const valueName = valueNames[index];
|
|
293
|
+
const earlier = seen.get(valueName);
|
|
294
|
+
if (earlier !== void 0) throw new Error(`Drizzle-GraphQL Error: Values '${earlier}' and '${value}' of enum '${name}' (on '${tableName}.${columnName}') both map to the GraphQL value name '${valueName}'. Give one of them a different name via config.enumValueNameMapper.`);
|
|
295
|
+
seen.set(valueName, value);
|
|
296
|
+
});
|
|
250
297
|
const valuesKey = enumValuesKey(values);
|
|
298
|
+
const namesKey = enumValueNamesKey(valueNames);
|
|
251
299
|
const claimed = enumNamesInUse.get(name);
|
|
252
300
|
if (claimed) {
|
|
253
301
|
if (claimed.valuesKey !== valuesKey) throw new Error(`Drizzle-GraphQL Error: Two different enums both map to the GraphQL type name '${name}' (most recently '${tableName}.${columnName}'). Give one of them a different name via config.enumNameMapper.`);
|
|
302
|
+
if (claimed.namesKey !== namesKey) {
|
|
303
|
+
const before = claimed.type.getValues().map((v) => v.name).join(", ");
|
|
304
|
+
throw new Error(`Drizzle-GraphQL Error: Columns sharing the GraphQL enum '${name}' name its values differently ('${tableName}.${columnName}' gives ${valueNames.join(", ")}; an earlier column gave ${before}). config.enumValueNameMapper must name a shared enum's values the same way for every column, or config.enumNameMapper must give the columns separate types.`);
|
|
305
|
+
}
|
|
254
306
|
return claimed.type;
|
|
255
307
|
}
|
|
256
308
|
const gqlEnum = new GraphQLEnumType({
|
|
257
309
|
name,
|
|
258
|
-
values: Object.fromEntries(values.map((e, index) => [
|
|
310
|
+
values: Object.fromEntries(values.map((e, index) => [valueNames[index], {
|
|
259
311
|
value: e,
|
|
260
312
|
description: `Value: ${e}`
|
|
261
313
|
}]))
|
|
262
314
|
});
|
|
263
315
|
enumNamesInUse.set(name, {
|
|
264
316
|
type: gqlEnum,
|
|
265
|
-
valuesKey
|
|
317
|
+
valuesKey,
|
|
318
|
+
namesKey
|
|
266
319
|
});
|
|
267
320
|
return gqlEnum;
|
|
268
321
|
};
|
|
@@ -1017,6 +1070,7 @@ const remapToGraphQLArrayOutput = (queryOutput, tableName, table, relationMap) =
|
|
|
1017
1070
|
return queryOutput;
|
|
1018
1071
|
};
|
|
1019
1072
|
const remapFromGraphQLCore = (value, column, columnName) => {
|
|
1073
|
+
if (value === null) return null;
|
|
1020
1074
|
if (getColumnScalarOverride(column, true)) {
|
|
1021
1075
|
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === null) return Object.assign({}, value);
|
|
1022
1076
|
return value;
|
|
@@ -3670,6 +3724,145 @@ const runMutation = async (db, context, info, txCtx, body, forceTx) => {
|
|
|
3670
3724
|
return runOnSharedTx(state, body);
|
|
3671
3725
|
};
|
|
3672
3726
|
//#endregion
|
|
3727
|
+
//#region src/util/builders/common/write-hooks.ts
|
|
3728
|
+
/**
|
|
3729
|
+
* Normalizes the two registration shapes: a bare function is the `after` hook — the position
|
|
3730
|
+
* that has rows, and the one the common audit/outbox case wants — while `{ before, after }`
|
|
3731
|
+
* names them explicitly.
|
|
3732
|
+
*/
|
|
3733
|
+
const normalizeWriteHooks = (hooks, tableColumns, where) => {
|
|
3734
|
+
if (!hooks) return;
|
|
3735
|
+
if (typeof hooks === "function") return { after: hooks };
|
|
3736
|
+
const resolved = {
|
|
3737
|
+
before: hooks.before,
|
|
3738
|
+
after: hooks.after
|
|
3739
|
+
};
|
|
3740
|
+
if (hooks.columns !== void 0) resolved.columns = resolveHookColumns(hooks.columns, !!hooks.after, tableColumns, where);
|
|
3741
|
+
return resolved.before || resolved.after ? resolved : void 0;
|
|
3742
|
+
};
|
|
3743
|
+
/**
|
|
3744
|
+
* Resolves a hook's `columns` against the table it runs on. `where` names the registration
|
|
3745
|
+
* (`config.onWrite.posts`) and, for a top-level one, the table, so the error says which.
|
|
3746
|
+
*/
|
|
3747
|
+
const resolveHookColumns = (spec, hasAfter, tableColumns, where) => {
|
|
3748
|
+
if (!hasAfter) throw new Error(`Drizzle-GraphQL Error: ${where}.columns only applies to an 'after' hook, and none is registered.`);
|
|
3749
|
+
if (spec === "all") return { ...tableColumns };
|
|
3750
|
+
if (!Array.isArray(spec)) throw new Error(`Drizzle-GraphQL Error: ${where}.columns must be an array of column names or 'all'.`);
|
|
3751
|
+
const resolved = {};
|
|
3752
|
+
for (const name of spec) {
|
|
3753
|
+
const column = typeof name === "string" && Object.keys(tableColumns).includes(name) ? tableColumns[name] : void 0;
|
|
3754
|
+
if (!column) throw new Error(`Drizzle-GraphQL Error: ${where}.columns names '${String(name)}', which is not a column of that table.`);
|
|
3755
|
+
resolved[name] = column;
|
|
3756
|
+
}
|
|
3757
|
+
return resolved;
|
|
3758
|
+
};
|
|
3759
|
+
/**
|
|
3760
|
+
* Adds a hook's `columns` to a RETURNING column map, and reports which keys it added — the
|
|
3761
|
+
* ones the client never asked for, which have to come back off before the rows are mapped.
|
|
3762
|
+
* Returns the map untouched, and no keys, when the hook named none or the selection already
|
|
3763
|
+
* covers them.
|
|
3764
|
+
*/
|
|
3765
|
+
const withHookColumns = (hooks, columns) => {
|
|
3766
|
+
const hookColumns = hooks?.columns;
|
|
3767
|
+
if (!hookColumns) return {
|
|
3768
|
+
returning: columns,
|
|
3769
|
+
added: void 0
|
|
3770
|
+
};
|
|
3771
|
+
const selected = new Set(Object.keys(columns));
|
|
3772
|
+
const added = Object.keys(hookColumns).filter((key) => !selected.has(key));
|
|
3773
|
+
if (!added.length) return {
|
|
3774
|
+
returning: columns,
|
|
3775
|
+
added: void 0
|
|
3776
|
+
};
|
|
3777
|
+
const returning = { ...columns };
|
|
3778
|
+
for (const key of added) returning[key] = hookColumns[key];
|
|
3779
|
+
return {
|
|
3780
|
+
returning,
|
|
3781
|
+
added
|
|
3782
|
+
};
|
|
3783
|
+
};
|
|
3784
|
+
/**
|
|
3785
|
+
* The rows with the hook-only keys taken off, as fresh objects: the hook was handed the
|
|
3786
|
+
* originals and may still hold them, so they are not mutated under it.
|
|
3787
|
+
*/
|
|
3788
|
+
const omitHookColumns = (rows, added) => {
|
|
3789
|
+
if (!added) return rows;
|
|
3790
|
+
return rows.map((row) => {
|
|
3791
|
+
const copy = { ...row };
|
|
3792
|
+
for (const key of added) delete copy[key];
|
|
3793
|
+
return copy;
|
|
3794
|
+
});
|
|
3795
|
+
};
|
|
3796
|
+
/**
|
|
3797
|
+
* Invokes one position of a field's hooks. Returns `undefined` rather than a resolved promise
|
|
3798
|
+
* when nothing is registered, so the awaiting call site costs nothing on an unhooked build.
|
|
3799
|
+
*/
|
|
3800
|
+
const runWriteHook = (hooks, position, payload) => {
|
|
3801
|
+
const hook = position === "before" ? hooks?.before : hooks?.after;
|
|
3802
|
+
if (!hook) return;
|
|
3803
|
+
return Promise.resolve(hook({
|
|
3804
|
+
...payload,
|
|
3805
|
+
position,
|
|
3806
|
+
rows: payload.rows ?? []
|
|
3807
|
+
}));
|
|
3808
|
+
};
|
|
3809
|
+
//#endregion
|
|
3810
|
+
//#region src/util/builders/common/write-scaffold.ts
|
|
3811
|
+
/**
|
|
3812
|
+
* Wraps one mutation body in the scaffolding above and returns the field it becomes.
|
|
3813
|
+
*
|
|
3814
|
+
* A field carrying a hook is forced into a transaction even when it would have run straight
|
|
3815
|
+
* on `db`: a hook exists to write atomically with the mutation, and one that ran outside the
|
|
3816
|
+
* transaction could not roll anything back.
|
|
3817
|
+
*/
|
|
3818
|
+
const writeResolver = ({ db, tableName, operation, single, fieldName, args: queryArgs, txCtx, policies, run }) => {
|
|
3819
|
+
const hooks = policies?.onWrite?.(tableName, operation);
|
|
3820
|
+
const errorCtx = {
|
|
3821
|
+
table: tableName,
|
|
3822
|
+
operation,
|
|
3823
|
+
field: fieldName
|
|
3824
|
+
};
|
|
3825
|
+
return {
|
|
3826
|
+
name: fieldName,
|
|
3827
|
+
resolver: async (_source, args, context, info) => {
|
|
3828
|
+
try {
|
|
3829
|
+
return await runMutation(db, context, info, txCtx, (executor) => {
|
|
3830
|
+
const payload = {
|
|
3831
|
+
table: tableName,
|
|
3832
|
+
operation,
|
|
3833
|
+
single,
|
|
3834
|
+
args,
|
|
3835
|
+
context,
|
|
3836
|
+
info,
|
|
3837
|
+
tx: executor
|
|
3838
|
+
};
|
|
3839
|
+
let added;
|
|
3840
|
+
return run({
|
|
3841
|
+
executor,
|
|
3842
|
+
args,
|
|
3843
|
+
context,
|
|
3844
|
+
info,
|
|
3845
|
+
before: () => runWriteHook(hooks, "before", payload),
|
|
3846
|
+
after: (rows) => runWriteHook(hooks, "after", {
|
|
3847
|
+
...payload,
|
|
3848
|
+
rows: rows ?? []
|
|
3849
|
+
}),
|
|
3850
|
+
returning: (columns) => {
|
|
3851
|
+
const withHook = withHookColumns(hooks, columns);
|
|
3852
|
+
added = withHook.added;
|
|
3853
|
+
return withHook.returning;
|
|
3854
|
+
},
|
|
3855
|
+
output: (rows) => omitHookColumns(rows, added)
|
|
3856
|
+
});
|
|
3857
|
+
}, !!hooks);
|
|
3858
|
+
} catch (e) {
|
|
3859
|
+
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
3860
|
+
}
|
|
3861
|
+
},
|
|
3862
|
+
args: queryArgs
|
|
3863
|
+
};
|
|
3864
|
+
};
|
|
3865
|
+
//#endregion
|
|
3673
3866
|
//#region src/util/builders/common/mutation-helpers.ts
|
|
3674
3867
|
/**
|
|
3675
3868
|
* The `hard` argument on the delete mutations of a soft-deleting table that opted into
|
|
@@ -3813,54 +4006,79 @@ const assertSingleMatch = async (executor, table, filters) => {
|
|
|
3813
4006
|
* The plural `update` and `delete` always `RETURNING *`, which is the wrong shape for
|
|
3814
4007
|
* "archive everything older than a year": the caller pays to ship rows it does not want, and
|
|
3815
4008
|
* on a large match that cost is the whole mutation. These return `Int!` instead — how many
|
|
3816
|
-
* rows the write touched
|
|
4009
|
+
* rows the write touched.
|
|
3817
4010
|
*
|
|
3818
|
-
*
|
|
3819
|
-
*
|
|
3820
|
-
*
|
|
3821
|
-
|
|
3822
|
-
|
|
4011
|
+
* Leaving the payload off changes nothing else: the write obeys every rule of the plural
|
|
4012
|
+
* mutation it mirrors, through the same {@link writeResolver} scaffolding.
|
|
4013
|
+
*
|
|
4014
|
+
* - `scope` (and the soft-delete marker) is ANDed into the `where`, so only rows the plural
|
|
4015
|
+
* write could reach are counted and written.
|
|
4016
|
+
* - On a soft-deleting table the delete is the marking `UPDATE`, not a `DELETE`, and rows that
|
|
4017
|
+
* are already marked are not matched; the update skips marked rows too. `hard: true` exists
|
|
4018
|
+
* exactly where the plural delete takes it.
|
|
4019
|
+
* - Context-derived columns are stripped from the update's `set`, as the plural update does.
|
|
4020
|
+
* - `onWrite` hooks run `before` and `after`, inside a transaction. The `after` hook is handed
|
|
4021
|
+
* rows, so a table with one pays for a `RETURNING` — of the columns the hook names in
|
|
4022
|
+
* `columns`, or of the primary key when it names none (the whole row on a table without a
|
|
4023
|
+
* key) — and the count is the number of rows returned. Every other write, and every write on
|
|
4024
|
+
* MySQL (no `RETURNING`; its hooks see `rows: []` as on every MySQL mutation), skips the
|
|
4025
|
+
* `RETURNING` and reads the count off the driver's result via {@link rowsAffected}.
|
|
4026
|
+
*/
|
|
4027
|
+
const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, fieldName, requireWhere, filterCtx, txCtx, nested, policies, pkNames = [], returnsRows }) => {
|
|
4028
|
+
const softDelete = kind === "delete" ? policies?.softDelete?.(tableName) : void 0;
|
|
4029
|
+
const canHardDelete = softDelete?.hardDelete === true;
|
|
3823
4030
|
const queryArgs = {
|
|
3824
4031
|
...setArgs ? { set: { type: new GraphQLNonNull(setArgs) } } : {},
|
|
3825
|
-
where: { type: requireWhere ? new GraphQLNonNull(filterArgs) : filterArgs }
|
|
4032
|
+
where: { type: requireWhere ? new GraphQLNonNull(filterArgs) : filterArgs },
|
|
4033
|
+
...canHardDelete ? { hard: hardDeleteArg } : {}
|
|
3826
4034
|
};
|
|
3827
|
-
const
|
|
3828
|
-
|
|
4035
|
+
const readsRows = returnsRows && !!policies?.onWrite?.(tableName, kind)?.after;
|
|
4036
|
+
const allColumns = getColumns(table);
|
|
4037
|
+
const keyColumns = pkNames.length ? Object.fromEntries(pkNames.map((name) => [name, allColumns[name]])) : allColumns;
|
|
4038
|
+
return writeResolver({
|
|
4039
|
+
db,
|
|
4040
|
+
tableName,
|
|
3829
4041
|
operation: kind,
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
else {
|
|
3849
|
-
const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
|
|
3850
|
-
if (entry && nested.hasOps(entry.ops)) throw drizzleError("This mutation does not support nested writes — use the one that returns the rows instead.", { code: "DRIZZLE_NESTED_WRITES_UNSUPPORTED" });
|
|
3851
|
-
const values = remapUpdateInput(entry ? entry.columns : set, table, tableName);
|
|
3852
|
-
if (!Object.keys(values).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
3853
|
-
query = executor.update(table).set(values);
|
|
3854
|
-
}
|
|
3855
|
-
if (filters) query = query.where(filters);
|
|
3856
|
-
return rowsAffected(await query);
|
|
3857
|
-
});
|
|
3858
|
-
} catch (e) {
|
|
3859
|
-
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
4042
|
+
single: false,
|
|
4043
|
+
fieldName,
|
|
4044
|
+
txCtx,
|
|
4045
|
+
policies,
|
|
4046
|
+
args: queryArgs,
|
|
4047
|
+
run: async ({ executor, args, context, before, after, returning }) => {
|
|
4048
|
+
const { where, set } = args;
|
|
4049
|
+
const hard = canHardDelete && args.hard === true;
|
|
4050
|
+
const scope = policies?.scope?.(context);
|
|
4051
|
+
await before();
|
|
4052
|
+
let query;
|
|
4053
|
+
if (kind === "delete") query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: softDelete.writeDeleted() }) : executor.delete(table);
|
|
4054
|
+
else {
|
|
4055
|
+
const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
|
|
4056
|
+
if (entry && nested.hasOps(entry.ops)) throw drizzleError("This mutation does not support nested writes — use the one that returns the rows instead.", { code: "DRIZZLE_NESTED_WRITES_UNSUPPORTED" });
|
|
4057
|
+
const values = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
4058
|
+
if (!Object.keys(values).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
4059
|
+
query = executor.update(table).set(values);
|
|
3860
4060
|
}
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
4061
|
+
const filters = scopedWhere({
|
|
4062
|
+
scope,
|
|
4063
|
+
tableName,
|
|
4064
|
+
table,
|
|
4065
|
+
where,
|
|
4066
|
+
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
4067
|
+
required: requireWhere,
|
|
4068
|
+
deleted: hard ? "INCLUDE" : void 0
|
|
4069
|
+
});
|
|
4070
|
+
if (filters) query = query.where(filters);
|
|
4071
|
+
if (!readsRows) {
|
|
4072
|
+
const result = await query;
|
|
4073
|
+
await after();
|
|
4074
|
+
return rowsAffected(result);
|
|
4075
|
+
}
|
|
4076
|
+
const named = returning({});
|
|
4077
|
+
const rows = await query.returning(Object.keys(named).length ? named : keyColumns);
|
|
4078
|
+
await after(rows);
|
|
4079
|
+
return rows.length;
|
|
4080
|
+
}
|
|
4081
|
+
});
|
|
3864
4082
|
};
|
|
3865
4083
|
//#endregion
|
|
3866
4084
|
//#region src/util/batch-loader/index.ts
|
|
@@ -4798,84 +5016,6 @@ const generateTableTypes = (tableName, tables, relationMap, withReturning, relat
|
|
|
4798
5016
|
};
|
|
4799
5017
|
};
|
|
4800
5018
|
//#endregion
|
|
4801
|
-
//#region src/util/builders/common/write-hooks.ts
|
|
4802
|
-
/**
|
|
4803
|
-
* Normalizes the two registration shapes: a bare function is the `after` hook — the position
|
|
4804
|
-
* that has rows, and the one the common audit/outbox case wants — while `{ before, after }`
|
|
4805
|
-
* names them explicitly.
|
|
4806
|
-
*/
|
|
4807
|
-
const normalizeWriteHooks = (hooks) => {
|
|
4808
|
-
if (!hooks) return;
|
|
4809
|
-
if (typeof hooks === "function") return { after: hooks };
|
|
4810
|
-
const resolved = {
|
|
4811
|
-
before: hooks.before,
|
|
4812
|
-
after: hooks.after
|
|
4813
|
-
};
|
|
4814
|
-
return resolved.before || resolved.after ? resolved : void 0;
|
|
4815
|
-
};
|
|
4816
|
-
/**
|
|
4817
|
-
* Invokes one position of a field's hooks. Returns `undefined` rather than a resolved promise
|
|
4818
|
-
* when nothing is registered, so the awaiting call site costs nothing on an unhooked build.
|
|
4819
|
-
*/
|
|
4820
|
-
const runWriteHook = (hooks, position, payload) => {
|
|
4821
|
-
const hook = position === "before" ? hooks?.before : hooks?.after;
|
|
4822
|
-
if (!hook) return;
|
|
4823
|
-
return Promise.resolve(hook({
|
|
4824
|
-
...payload,
|
|
4825
|
-
position,
|
|
4826
|
-
rows: payload.rows ?? []
|
|
4827
|
-
}));
|
|
4828
|
-
};
|
|
4829
|
-
//#endregion
|
|
4830
|
-
//#region src/util/builders/common/write-scaffold.ts
|
|
4831
|
-
/**
|
|
4832
|
-
* Wraps one mutation body in the scaffolding above and returns the field it becomes.
|
|
4833
|
-
*
|
|
4834
|
-
* A field carrying a hook is forced into a transaction even when it would have run straight
|
|
4835
|
-
* on `db`: a hook exists to write atomically with the mutation, and one that ran outside the
|
|
4836
|
-
* transaction could not roll anything back.
|
|
4837
|
-
*/
|
|
4838
|
-
const writeResolver = ({ db, tableName, operation, single, fieldName, args: queryArgs, txCtx, policies, run }) => {
|
|
4839
|
-
const hooks = policies?.onWrite?.(tableName, operation);
|
|
4840
|
-
const errorCtx = {
|
|
4841
|
-
table: tableName,
|
|
4842
|
-
operation,
|
|
4843
|
-
field: fieldName
|
|
4844
|
-
};
|
|
4845
|
-
return {
|
|
4846
|
-
name: fieldName,
|
|
4847
|
-
resolver: async (_source, args, context, info) => {
|
|
4848
|
-
try {
|
|
4849
|
-
return await runMutation(db, context, info, txCtx, (executor) => {
|
|
4850
|
-
const payload = {
|
|
4851
|
-
table: tableName,
|
|
4852
|
-
operation,
|
|
4853
|
-
single,
|
|
4854
|
-
args,
|
|
4855
|
-
context,
|
|
4856
|
-
info,
|
|
4857
|
-
tx: executor
|
|
4858
|
-
};
|
|
4859
|
-
return run({
|
|
4860
|
-
executor,
|
|
4861
|
-
args,
|
|
4862
|
-
context,
|
|
4863
|
-
info,
|
|
4864
|
-
before: () => runWriteHook(hooks, "before", payload),
|
|
4865
|
-
after: (rows) => runWriteHook(hooks, "after", {
|
|
4866
|
-
...payload,
|
|
4867
|
-
rows: rows ?? []
|
|
4868
|
-
})
|
|
4869
|
-
});
|
|
4870
|
-
}, !!hooks);
|
|
4871
|
-
} catch (e) {
|
|
4872
|
-
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
4873
|
-
}
|
|
4874
|
-
},
|
|
4875
|
-
args: queryArgs
|
|
4876
|
-
};
|
|
4877
|
-
};
|
|
4878
|
-
//#endregion
|
|
4879
5019
|
//#region src/util/features.ts
|
|
4880
5020
|
/**
|
|
4881
5021
|
* What a table generates when the config says nothing. Everything is on except the opt-in
|
|
@@ -5039,19 +5179,18 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5039
5179
|
}
|
|
5040
5180
|
const onWriteConfig = config?.onWrite;
|
|
5041
5181
|
const writeHooks = /* @__PURE__ */ new Map();
|
|
5042
|
-
let globalWriteHooks;
|
|
5043
5182
|
if (onWriteConfig) {
|
|
5044
|
-
if (typeof onWriteConfig === "function" || typeof onWriteConfig.before === "function" || typeof onWriteConfig.after === "function")
|
|
5183
|
+
if (typeof onWriteConfig === "function" || typeof onWriteConfig.before === "function" || typeof onWriteConfig.after === "function") for (const tableName of featureTables) writeHooks.set(tableName, normalizeWriteHooks(onWriteConfig, getColumns(schema[tableName]), `config.onWrite (on table '${tableName}')`));
|
|
5045
5184
|
else {
|
|
5046
5185
|
const tableNames = new Set(tableKeys);
|
|
5047
5186
|
for (const [tableName, hooks] of Object.entries(onWriteConfig)) {
|
|
5048
5187
|
if (!tableNames.has(tableName)) throw new Error(`Drizzle-GraphQL Error: config.onWrite names '${tableName}', which is not a table in the Drizzle schema.`);
|
|
5049
5188
|
if (typeof hooks !== "function" && typeof hooks !== "object") throw new Error(`Drizzle-GraphQL Error: config.onWrite.${tableName} must be a function or an object with 'before' and/or 'after'.`);
|
|
5050
|
-
writeHooks.set(tableName, normalizeWriteHooks(hooks));
|
|
5189
|
+
writeHooks.set(tableName, normalizeWriteHooks(hooks, getColumns(schema[tableName]), `config.onWrite.${tableName}`));
|
|
5051
5190
|
}
|
|
5052
5191
|
}
|
|
5053
5192
|
}
|
|
5054
|
-
const onWrite =
|
|
5193
|
+
const onWrite = writeHooks.size ? (tableName) => writeHooks.get(tableName) : void 0;
|
|
5055
5194
|
const policies = scopeConfig || contextValuesConfig || softDeleteInfos.size || onWrite || defaultOrderBy ? {
|
|
5056
5195
|
scope: scopeConfig ? (tableName) => scopeConfig[tableName] : void 0,
|
|
5057
5196
|
contextValues: contextValuesConfig ? (tableName) => contextValuesConfig[tableName] : void 0,
|
|
@@ -5117,6 +5256,7 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5117
5256
|
scalars: config?.scalars,
|
|
5118
5257
|
mapColumnType: config?.mapColumnType,
|
|
5119
5258
|
enumNameMapper: config?.enumNameMapper,
|
|
5259
|
+
enumValueNameMapper: config?.enumValueNameMapper,
|
|
5120
5260
|
transactions,
|
|
5121
5261
|
limits,
|
|
5122
5262
|
policies,
|
|
@@ -6877,7 +7017,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
6877
7017
|
fieldName: updateCountFieldName,
|
|
6878
7018
|
requireWhere: tableFeatures.requireWhere,
|
|
6879
7019
|
filterCtx,
|
|
6880
|
-
txCtx: mutationTxCtx
|
|
7020
|
+
txCtx: mutationTxCtx,
|
|
7021
|
+
policies,
|
|
7022
|
+
returnsRows: false
|
|
6881
7023
|
}) : void 0;
|
|
6882
7024
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
6883
7025
|
db,
|
|
@@ -6888,7 +7030,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
6888
7030
|
fieldName: deleteCountFieldName,
|
|
6889
7031
|
requireWhere: tableFeatures.requireWhere,
|
|
6890
7032
|
filterCtx,
|
|
6891
|
-
txCtx: mutationTxCtx
|
|
7033
|
+
txCtx: mutationTxCtx,
|
|
7034
|
+
policies,
|
|
7035
|
+
returnsRows: false
|
|
6892
7036
|
}) : void 0;
|
|
6893
7037
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
6894
7038
|
const withMeta = (generated, meta) => ({
|
|
@@ -6958,12 +7102,25 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
6958
7102
|
{
|
|
6959
7103
|
generated: updateCountGenerated,
|
|
6960
7104
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7105
|
+
meta: {
|
|
7106
|
+
operation: "update",
|
|
7107
|
+
single: false,
|
|
7108
|
+
targetArg: "where",
|
|
7109
|
+
count: true
|
|
7110
|
+
},
|
|
6961
7111
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
6962
7112
|
},
|
|
6963
7113
|
{
|
|
6964
7114
|
generated: deleteCountGenerated,
|
|
6965
7115
|
type: new GraphQLNonNull(GraphQLInt),
|
|
6966
|
-
|
|
7116
|
+
meta: {
|
|
7117
|
+
operation: "delete",
|
|
7118
|
+
single: false,
|
|
7119
|
+
targetArg: "where",
|
|
7120
|
+
count: true,
|
|
7121
|
+
...hardDeleteMeta
|
|
7122
|
+
},
|
|
7123
|
+
description: "How many rows the delete removed — or, on a soft-deleting table, marked deleted. The rows themselves are not read back, which is the point of this mutation."
|
|
6967
7124
|
}
|
|
6968
7125
|
],
|
|
6969
7126
|
onConflictInput,
|
|
@@ -7000,7 +7157,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7000
7157
|
txCtx,
|
|
7001
7158
|
policies,
|
|
7002
7159
|
args: queryArgs,
|
|
7003
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7160
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
|
|
7004
7161
|
const supplied = single ? [args.values] : args.values;
|
|
7005
7162
|
if (!supplied.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7006
7163
|
await before();
|
|
@@ -7022,13 +7179,13 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7022
7179
|
scope,
|
|
7023
7180
|
defaultOrderBy: policies?.defaultOrderBy
|
|
7024
7181
|
});
|
|
7025
|
-
const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
|
|
7182
|
+
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7026
7183
|
const runInsert = async (target, values) => {
|
|
7027
7184
|
let query = target.insert(table).values(values).returning(returning);
|
|
7028
7185
|
if (conflictDoNothing) query = query.onConflictDoNothing();
|
|
7029
7186
|
return await query;
|
|
7030
7187
|
};
|
|
7031
|
-
const
|
|
7188
|
+
const written = nestedEntries ? await writeWithNestedOps({
|
|
7032
7189
|
executor,
|
|
7033
7190
|
runtime: nested,
|
|
7034
7191
|
tableName,
|
|
@@ -7037,7 +7194,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7037
7194
|
write: (tx, values) => runInsert(tx, [values]),
|
|
7038
7195
|
context
|
|
7039
7196
|
}) : await runInsert(executor, input);
|
|
7040
|
-
await after(
|
|
7197
|
+
await after(written);
|
|
7198
|
+
const result = output(written);
|
|
7041
7199
|
if (single && !result[0]) {
|
|
7042
7200
|
if (!conflictDoNothing) throw drizzleError("The insert returned no row.", { code: "DRIZZLE_NO_ROW_RETURNED" });
|
|
7043
7201
|
return;
|
|
@@ -7073,7 +7231,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7073
7231
|
txCtx,
|
|
7074
7232
|
policies,
|
|
7075
7233
|
args: queryArgs,
|
|
7076
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7234
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
|
|
7077
7235
|
const supplied = single ? [args.values] : args.values;
|
|
7078
7236
|
if (!supplied.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7079
7237
|
await before();
|
|
@@ -7095,7 +7253,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7095
7253
|
scope,
|
|
7096
7254
|
defaultOrderBy: policies?.defaultOrderBy
|
|
7097
7255
|
});
|
|
7098
|
-
const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
|
|
7256
|
+
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7099
7257
|
const runUpsert = async (target, values) => {
|
|
7100
7258
|
const plan = resolveConflictPlan({
|
|
7101
7259
|
table,
|
|
@@ -7116,7 +7274,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7116
7274
|
});
|
|
7117
7275
|
return await query;
|
|
7118
7276
|
};
|
|
7119
|
-
const
|
|
7277
|
+
const written = nestedEntries ? await writeWithNestedOps({
|
|
7120
7278
|
executor,
|
|
7121
7279
|
runtime: nested,
|
|
7122
7280
|
tableName,
|
|
@@ -7125,7 +7283,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7125
7283
|
write: (tx, values) => runUpsert(tx, [values]),
|
|
7126
7284
|
context
|
|
7127
7285
|
}) : await runUpsert(executor, input);
|
|
7128
|
-
await after(
|
|
7286
|
+
await after(written);
|
|
7287
|
+
const result = output(written);
|
|
7129
7288
|
if (single && !result[0]) return;
|
|
7130
7289
|
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7131
7290
|
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
@@ -7149,7 +7308,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7149
7308
|
txCtx,
|
|
7150
7309
|
policies,
|
|
7151
7310
|
args: queryArgs,
|
|
7152
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7311
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
|
|
7153
7312
|
const { where, set } = args;
|
|
7154
7313
|
const scope = policies?.scope?.(context);
|
|
7155
7314
|
await before();
|
|
@@ -7180,14 +7339,14 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7180
7339
|
required: single || requireWhere
|
|
7181
7340
|
});
|
|
7182
7341
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7183
|
-
const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
|
|
7342
|
+
const returning = withHookReturning(nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns);
|
|
7184
7343
|
const runUpdate = async (target, values) => {
|
|
7185
7344
|
const writes = Object.keys(values).length > 0;
|
|
7186
7345
|
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
7187
7346
|
if (filters) query = query.where(filters);
|
|
7188
7347
|
return await (writes ? query.returning(returning) : query);
|
|
7189
7348
|
};
|
|
7190
|
-
const
|
|
7349
|
+
const written = nestedOps ? await updateWithNestedOps({
|
|
7191
7350
|
executor,
|
|
7192
7351
|
runtime: nested,
|
|
7193
7352
|
tableName,
|
|
@@ -7197,7 +7356,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7197
7356
|
write: runUpdate,
|
|
7198
7357
|
context
|
|
7199
7358
|
}) : await runUpdate(executor, input);
|
|
7200
|
-
await after(
|
|
7359
|
+
await after(written);
|
|
7360
|
+
const result = output(written);
|
|
7201
7361
|
if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
|
|
7202
7362
|
if (single && !result[0]) return;
|
|
7203
7363
|
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
@@ -7237,7 +7397,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7237
7397
|
txCtx,
|
|
7238
7398
|
policies,
|
|
7239
7399
|
args: queryArgs,
|
|
7240
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7400
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
|
|
7241
7401
|
const { where } = args;
|
|
7242
7402
|
const hard = canHardDelete && args.hard === true;
|
|
7243
7403
|
const scope = policies?.scope?.(context);
|
|
@@ -7257,9 +7417,10 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7257
7417
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7258
7418
|
let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
|
|
7259
7419
|
if (filters) query = query.where(filters);
|
|
7260
|
-
query = query.returning(columns);
|
|
7261
|
-
const
|
|
7262
|
-
await after(
|
|
7420
|
+
query = query.returning(withHookReturning(columns));
|
|
7421
|
+
const written = await query;
|
|
7422
|
+
await after(written);
|
|
7423
|
+
const result = output(written);
|
|
7263
7424
|
if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
|
|
7264
7425
|
if (single) return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
|
|
7265
7426
|
return remapToGraphQLArrayOutput(result, tableName, table);
|
|
@@ -7460,7 +7621,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7460
7621
|
requireWhere: tableFeatures.requireWhere,
|
|
7461
7622
|
filterCtx,
|
|
7462
7623
|
txCtx: mutationTxCtx,
|
|
7463
|
-
nested: nestedRuntime
|
|
7624
|
+
nested: nestedRuntime,
|
|
7625
|
+
policies,
|
|
7626
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7627
|
+
returnsRows: true
|
|
7464
7628
|
}) : void 0;
|
|
7465
7629
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
7466
7630
|
db,
|
|
@@ -7471,7 +7635,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7471
7635
|
fieldName: deleteCountFieldName,
|
|
7472
7636
|
requireWhere: tableFeatures.requireWhere,
|
|
7473
7637
|
filterCtx,
|
|
7474
|
-
txCtx: mutationTxCtx
|
|
7638
|
+
txCtx: mutationTxCtx,
|
|
7639
|
+
policies,
|
|
7640
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7641
|
+
returnsRows: true
|
|
7475
7642
|
}) : void 0;
|
|
7476
7643
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7477
7644
|
return {
|
|
@@ -7581,12 +7748,25 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7581
7748
|
{
|
|
7582
7749
|
generated: updateCountGenerated,
|
|
7583
7750
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7751
|
+
meta: {
|
|
7752
|
+
operation: "update",
|
|
7753
|
+
single: false,
|
|
7754
|
+
targetArg: "where",
|
|
7755
|
+
count: true
|
|
7756
|
+
},
|
|
7584
7757
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7585
7758
|
},
|
|
7586
7759
|
{
|
|
7587
7760
|
generated: deleteCountGenerated,
|
|
7588
7761
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7589
|
-
|
|
7762
|
+
meta: {
|
|
7763
|
+
operation: "delete",
|
|
7764
|
+
single: false,
|
|
7765
|
+
targetArg: "where",
|
|
7766
|
+
count: true,
|
|
7767
|
+
...hardDeleteMeta
|
|
7768
|
+
},
|
|
7769
|
+
description: "How many rows the delete removed — or, on a soft-deleting table, marked deleted. The rows themselves are not read back, which is the point of this mutation."
|
|
7590
7770
|
}
|
|
7591
7771
|
],
|
|
7592
7772
|
onConflictInput,
|
|
@@ -7648,7 +7828,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7648
7828
|
txCtx,
|
|
7649
7829
|
policies,
|
|
7650
7830
|
args: queryArgs,
|
|
7651
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7831
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output: withoutHookColumns }) => {
|
|
7652
7832
|
const { updates } = args;
|
|
7653
7833
|
if (!updates.length) throw drizzleError("No updates were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7654
7834
|
await before();
|
|
@@ -7679,7 +7859,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7679
7859
|
};
|
|
7680
7860
|
});
|
|
7681
7861
|
const anyNested = entries.some((entry) => entry.ops);
|
|
7682
|
-
const returning = anyNested ? nested.withJoinColumns(tableName, mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))), { ...columns }, table) : columns;
|
|
7862
|
+
const returning = withHookReturning(anyNested ? nested.withJoinColumns(tableName, mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))), { ...columns }, table) : columns);
|
|
7683
7863
|
const perEntry = await runBatch({
|
|
7684
7864
|
executor,
|
|
7685
7865
|
entries,
|
|
@@ -7690,8 +7870,9 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7690
7870
|
tableName,
|
|
7691
7871
|
context
|
|
7692
7872
|
});
|
|
7693
|
-
const
|
|
7694
|
-
await after(
|
|
7873
|
+
const written = perEntry.flat();
|
|
7874
|
+
await after(written);
|
|
7875
|
+
const flatRows = withoutHookColumns(written);
|
|
7695
7876
|
const enriched = await withEagerRelations(executor, tableName, flatRows, pkNames, withParams, hasRelations);
|
|
7696
7877
|
const output = [];
|
|
7697
7878
|
let offset = 0;
|