@vantreeseba/drizzle-graphql 13.0.0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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) => [allowedNameChars.test(e) ? e : `Option${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;
@@ -4804,16 +4858,70 @@ const generateTableTypes = (tableName, tables, relationMap, withReturning, relat
4804
4858
  * that has rows, and the one the common audit/outbox case wants — while `{ before, after }`
4805
4859
  * names them explicitly.
4806
4860
  */
4807
- const normalizeWriteHooks = (hooks) => {
4861
+ const normalizeWriteHooks = (hooks, tableColumns, where) => {
4808
4862
  if (!hooks) return;
4809
4863
  if (typeof hooks === "function") return { after: hooks };
4810
4864
  const resolved = {
4811
4865
  before: hooks.before,
4812
4866
  after: hooks.after
4813
4867
  };
4868
+ if (hooks.columns !== void 0) resolved.columns = resolveHookColumns(hooks.columns, !!hooks.after, tableColumns, where);
4814
4869
  return resolved.before || resolved.after ? resolved : void 0;
4815
4870
  };
4816
4871
  /**
4872
+ * Resolves a hook's `columns` against the table it runs on. `where` names the registration
4873
+ * (`config.onWrite.posts`) and, for a top-level one, the table, so the error says which.
4874
+ */
4875
+ const resolveHookColumns = (spec, hasAfter, tableColumns, where) => {
4876
+ if (!hasAfter) throw new Error(`Drizzle-GraphQL Error: ${where}.columns only applies to an 'after' hook, and none is registered.`);
4877
+ if (spec === "all") return { ...tableColumns };
4878
+ if (!Array.isArray(spec)) throw new Error(`Drizzle-GraphQL Error: ${where}.columns must be an array of column names or 'all'.`);
4879
+ const resolved = {};
4880
+ for (const name of spec) {
4881
+ const column = typeof name === "string" && Object.keys(tableColumns).includes(name) ? tableColumns[name] : void 0;
4882
+ if (!column) throw new Error(`Drizzle-GraphQL Error: ${where}.columns names '${String(name)}', which is not a column of that table.`);
4883
+ resolved[name] = column;
4884
+ }
4885
+ return resolved;
4886
+ };
4887
+ /**
4888
+ * Adds a hook's `columns` to a RETURNING column map, and reports which keys it added — the
4889
+ * ones the client never asked for, which have to come back off before the rows are mapped.
4890
+ * Returns the map untouched, and no keys, when the hook named none or the selection already
4891
+ * covers them.
4892
+ */
4893
+ const withHookColumns = (hooks, columns) => {
4894
+ const hookColumns = hooks?.columns;
4895
+ if (!hookColumns) return {
4896
+ returning: columns,
4897
+ added: void 0
4898
+ };
4899
+ const selected = new Set(Object.keys(columns));
4900
+ const added = Object.keys(hookColumns).filter((key) => !selected.has(key));
4901
+ if (!added.length) return {
4902
+ returning: columns,
4903
+ added: void 0
4904
+ };
4905
+ const returning = { ...columns };
4906
+ for (const key of added) returning[key] = hookColumns[key];
4907
+ return {
4908
+ returning,
4909
+ added
4910
+ };
4911
+ };
4912
+ /**
4913
+ * The rows with the hook-only keys taken off, as fresh objects: the hook was handed the
4914
+ * originals and may still hold them, so they are not mutated under it.
4915
+ */
4916
+ const omitHookColumns = (rows, added) => {
4917
+ if (!added) return rows;
4918
+ return rows.map((row) => {
4919
+ const copy = { ...row };
4920
+ for (const key of added) delete copy[key];
4921
+ return copy;
4922
+ });
4923
+ };
4924
+ /**
4817
4925
  * Invokes one position of a field's hooks. Returns `undefined` rather than a resolved promise
4818
4926
  * when nothing is registered, so the awaiting call site costs nothing on an unhooked build.
4819
4927
  */
@@ -4856,6 +4964,7 @@ const writeResolver = ({ db, tableName, operation, single, fieldName, args: quer
4856
4964
  info,
4857
4965
  tx: executor
4858
4966
  };
4967
+ let added;
4859
4968
  return run({
4860
4969
  executor,
4861
4970
  args,
@@ -4865,7 +4974,13 @@ const writeResolver = ({ db, tableName, operation, single, fieldName, args: quer
4865
4974
  after: (rows) => runWriteHook(hooks, "after", {
4866
4975
  ...payload,
4867
4976
  rows: rows ?? []
4868
- })
4977
+ }),
4978
+ returning: (columns) => {
4979
+ const withHook = withHookColumns(hooks, columns);
4980
+ added = withHook.added;
4981
+ return withHook.returning;
4982
+ },
4983
+ output: (rows) => omitHookColumns(rows, added)
4869
4984
  });
4870
4985
  }, !!hooks);
4871
4986
  } catch (e) {
@@ -5039,19 +5154,18 @@ const resolveBuildConfig = (config, schema) => {
5039
5154
  }
5040
5155
  const onWriteConfig = config?.onWrite;
5041
5156
  const writeHooks = /* @__PURE__ */ new Map();
5042
- let globalWriteHooks;
5043
5157
  if (onWriteConfig) {
5044
- if (typeof onWriteConfig === "function" || typeof onWriteConfig.before === "function" || typeof onWriteConfig.after === "function") globalWriteHooks = normalizeWriteHooks(onWriteConfig);
5158
+ 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
5159
  else {
5046
5160
  const tableNames = new Set(tableKeys);
5047
5161
  for (const [tableName, hooks] of Object.entries(onWriteConfig)) {
5048
5162
  if (!tableNames.has(tableName)) throw new Error(`Drizzle-GraphQL Error: config.onWrite names '${tableName}', which is not a table in the Drizzle schema.`);
5049
5163
  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));
5164
+ writeHooks.set(tableName, normalizeWriteHooks(hooks, getColumns(schema[tableName]), `config.onWrite.${tableName}`));
5051
5165
  }
5052
5166
  }
5053
5167
  }
5054
- const onWrite = globalWriteHooks || writeHooks.size ? (tableName) => globalWriteHooks ?? writeHooks.get(tableName) : void 0;
5168
+ const onWrite = writeHooks.size ? (tableName) => writeHooks.get(tableName) : void 0;
5055
5169
  const policies = scopeConfig || contextValuesConfig || softDeleteInfos.size || onWrite || defaultOrderBy ? {
5056
5170
  scope: scopeConfig ? (tableName) => scopeConfig[tableName] : void 0,
5057
5171
  contextValues: contextValuesConfig ? (tableName) => contextValuesConfig[tableName] : void 0,
@@ -5117,6 +5231,7 @@ const resolveBuildConfig = (config, schema) => {
5117
5231
  scalars: config?.scalars,
5118
5232
  mapColumnType: config?.mapColumnType,
5119
5233
  enumNameMapper: config?.enumNameMapper,
5234
+ enumValueNameMapper: config?.enumValueNameMapper,
5120
5235
  transactions,
5121
5236
  limits,
5122
5237
  policies,
@@ -7000,7 +7115,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7000
7115
  txCtx,
7001
7116
  policies,
7002
7117
  args: queryArgs,
7003
- run: async ({ executor, args, context, info, before, after }) => {
7118
+ run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
7004
7119
  const supplied = single ? [args.values] : args.values;
7005
7120
  if (!supplied.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
7006
7121
  await before();
@@ -7022,13 +7137,13 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7022
7137
  scope,
7023
7138
  defaultOrderBy: policies?.defaultOrderBy
7024
7139
  });
7025
- const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
7140
+ const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
7026
7141
  const runInsert = async (target, values) => {
7027
7142
  let query = target.insert(table).values(values).returning(returning);
7028
7143
  if (conflictDoNothing) query = query.onConflictDoNothing();
7029
7144
  return await query;
7030
7145
  };
7031
- const result = nestedEntries ? await writeWithNestedOps({
7146
+ const written = nestedEntries ? await writeWithNestedOps({
7032
7147
  executor,
7033
7148
  runtime: nested,
7034
7149
  tableName,
@@ -7037,7 +7152,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7037
7152
  write: (tx, values) => runInsert(tx, [values]),
7038
7153
  context
7039
7154
  }) : await runInsert(executor, input);
7040
- await after(result);
7155
+ await after(written);
7156
+ const result = output(written);
7041
7157
  if (single && !result[0]) {
7042
7158
  if (!conflictDoNothing) throw drizzleError("The insert returned no row.", { code: "DRIZZLE_NO_ROW_RETURNED" });
7043
7159
  return;
@@ -7073,7 +7189,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7073
7189
  txCtx,
7074
7190
  policies,
7075
7191
  args: queryArgs,
7076
- run: async ({ executor, args, context, info, before, after }) => {
7192
+ run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
7077
7193
  const supplied = single ? [args.values] : args.values;
7078
7194
  if (!supplied.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
7079
7195
  await before();
@@ -7095,7 +7211,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7095
7211
  scope,
7096
7212
  defaultOrderBy: policies?.defaultOrderBy
7097
7213
  });
7098
- const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
7214
+ const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
7099
7215
  const runUpsert = async (target, values) => {
7100
7216
  const plan = resolveConflictPlan({
7101
7217
  table,
@@ -7116,7 +7232,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7116
7232
  });
7117
7233
  return await query;
7118
7234
  };
7119
- const result = nestedEntries ? await writeWithNestedOps({
7235
+ const written = nestedEntries ? await writeWithNestedOps({
7120
7236
  executor,
7121
7237
  runtime: nested,
7122
7238
  tableName,
@@ -7125,7 +7241,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7125
7241
  write: (tx, values) => runUpsert(tx, [values]),
7126
7242
  context
7127
7243
  }) : await runUpsert(executor, input);
7128
- await after(result);
7244
+ await after(written);
7245
+ const result = output(written);
7129
7246
  if (single && !result[0]) return;
7130
7247
  const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
7131
7248
  return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
@@ -7149,7 +7266,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7149
7266
  txCtx,
7150
7267
  policies,
7151
7268
  args: queryArgs,
7152
- run: async ({ executor, args, context, info, before, after }) => {
7269
+ run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
7153
7270
  const { where, set } = args;
7154
7271
  const scope = policies?.scope?.(context);
7155
7272
  await before();
@@ -7180,14 +7297,14 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7180
7297
  required: single || requireWhere
7181
7298
  });
7182
7299
  if (single) await assertSingleMatch(executor, table, filters);
7183
- const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
7300
+ const returning = withHookReturning(nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns);
7184
7301
  const runUpdate = async (target, values) => {
7185
7302
  const writes = Object.keys(values).length > 0;
7186
7303
  let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
7187
7304
  if (filters) query = query.where(filters);
7188
7305
  return await (writes ? query.returning(returning) : query);
7189
7306
  };
7190
- const result = nestedOps ? await updateWithNestedOps({
7307
+ const written = nestedOps ? await updateWithNestedOps({
7191
7308
  executor,
7192
7309
  runtime: nested,
7193
7310
  tableName,
@@ -7197,7 +7314,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7197
7314
  write: runUpdate,
7198
7315
  context
7199
7316
  }) : await runUpdate(executor, input);
7200
- await after(result);
7317
+ await after(written);
7318
+ const result = output(written);
7201
7319
  if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
7202
7320
  if (single && !result[0]) return;
7203
7321
  const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
@@ -7237,7 +7355,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7237
7355
  txCtx,
7238
7356
  policies,
7239
7357
  args: queryArgs,
7240
- run: async ({ executor, args, context, info, before, after }) => {
7358
+ run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
7241
7359
  const { where } = args;
7242
7360
  const hard = canHardDelete && args.hard === true;
7243
7361
  const scope = policies?.scope?.(context);
@@ -7257,9 +7375,10 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7257
7375
  if (single) await assertSingleMatch(executor, table, filters);
7258
7376
  let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
7259
7377
  if (filters) query = query.where(filters);
7260
- query = query.returning(columns);
7261
- const result = await query;
7262
- await after(result);
7378
+ query = query.returning(withHookReturning(columns));
7379
+ const written = await query;
7380
+ await after(written);
7381
+ const result = output(written);
7263
7382
  if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
7264
7383
  if (single) return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
7265
7384
  return remapToGraphQLArrayOutput(result, tableName, table);
@@ -7648,7 +7767,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
7648
7767
  txCtx,
7649
7768
  policies,
7650
7769
  args: queryArgs,
7651
- run: async ({ executor, args, context, info, before, after }) => {
7770
+ run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output: withoutHookColumns }) => {
7652
7771
  const { updates } = args;
7653
7772
  if (!updates.length) throw drizzleError("No updates were provided!", { code: "DRIZZLE_NO_VALUES" });
7654
7773
  await before();
@@ -7679,7 +7798,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
7679
7798
  };
7680
7799
  });
7681
7800
  const anyNested = entries.some((entry) => entry.ops);
7682
- const returning = anyNested ? nested.withJoinColumns(tableName, mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))), { ...columns }, table) : columns;
7801
+ const returning = withHookReturning(anyNested ? nested.withJoinColumns(tableName, mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))), { ...columns }, table) : columns);
7683
7802
  const perEntry = await runBatch({
7684
7803
  executor,
7685
7804
  entries,
@@ -7690,8 +7809,9 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
7690
7809
  tableName,
7691
7810
  context
7692
7811
  });
7693
- const flatRows = perEntry.flat();
7694
- await after(flatRows);
7812
+ const written = perEntry.flat();
7813
+ await after(written);
7814
+ const flatRows = withoutHookColumns(written);
7695
7815
  const enriched = await withEagerRelations(executor, tableName, flatRows, pkNames, withParams, hasRelations);
7696
7816
  const output = [];
7697
7817
  let offset = 0;