@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/README.md +86 -3
- package/dist/index.cjs +149 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -4
- package/dist/index.d.ts +79 -4
- package/dist/index.js +149 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -711,6 +711,51 @@ lists is the build-time error above.
|
|
|
711
711
|
> `${tableName}${columnName[0]!.toUpperCase()}${columnName.slice(1)}Enum`
|
|
712
712
|
> ```
|
|
713
713
|
|
|
714
|
+
### Enum value names
|
|
715
|
+
|
|
716
|
+
A value's GraphQL name is the stored value itself, or `Option<index>` when the stored value
|
|
717
|
+
is not a usable GraphQL name. `enumValueNameMapper` renames the values — most often to follow
|
|
718
|
+
GraphQL's SCREAMING_CASE convention while the database keeps its lowercase values:
|
|
719
|
+
|
|
720
|
+
```Typescript
|
|
721
|
+
export const Runs = pgTable('runs', {
|
|
722
|
+
status: text('status', { enum: ['running', 'ok', 'error', 'stopped'] }),
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
const { schema } = buildSchema(db, {
|
|
726
|
+
// The built-in preset: `inProgress` / `in-progress` → `IN_PROGRESS`
|
|
727
|
+
enumValueNameMapper: 'constant',
|
|
728
|
+
})
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
```graphql
|
|
732
|
+
enum RunsStatusEnum {
|
|
733
|
+
RUNNING # stored as 'running'
|
|
734
|
+
OK
|
|
735
|
+
ERROR
|
|
736
|
+
STOPPED
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
- Only the GraphQL name changes. Each value's internal `value` is still the stored string,
|
|
741
|
+
so filters (`eq`, `inArray`, …), create/update inputs, outputs and your own resolvers keep
|
|
742
|
+
sending and receiving exactly what the database stores. The generated TypeScript types
|
|
743
|
+
keep the stored values too
|
|
744
|
+
- The function form receives the stored `value`, its `index`, the enum's GraphQL `typeName`
|
|
745
|
+
(after `enumNameMapper`) and everything `enumNameMapper` receives. Returning `undefined`
|
|
746
|
+
keeps the default name for that value:
|
|
747
|
+
|
|
748
|
+
```Typescript
|
|
749
|
+
enumValueNameMapper: ({ value, typeName }) =>
|
|
750
|
+
typeName === 'RunsStatusEnum' ? value.toUpperCase() : undefined,
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
- The build fails, rather than producing an invalid or lossy schema, when a returned name is
|
|
754
|
+
not a valid GraphQL enum value name, when two values of one enum map to the same name, or
|
|
755
|
+
when columns that share one enum type (the columns of one `pgEnum`) would name its values
|
|
756
|
+
differently — give those columns separate types with `enumNameMapper` if they really
|
|
757
|
+
should differ
|
|
758
|
+
|
|
714
759
|
## Documenting the schema
|
|
715
760
|
|
|
716
761
|
Four hooks put descriptions and deprecations on the generated types. Each is called per
|
|
@@ -2019,8 +2064,12 @@ outside the resolver, so its after-position is after the commit.
|
|
|
2019
2064
|
```Typescript
|
|
2020
2065
|
const { schema } = buildSchema(db, {
|
|
2021
2066
|
onWrite: {
|
|
2022
|
-
posts:
|
|
2023
|
-
|
|
2067
|
+
posts: {
|
|
2068
|
+
after: async ({ table, operation, rows, tx }) => {
|
|
2069
|
+
await tx.insert(auditLog).values(rows.map((row) => ({ table, operation, rowId: row.id })))
|
|
2070
|
+
},
|
|
2071
|
+
// Every row carries `id`, even when the client's mutation did not select it.
|
|
2072
|
+
columns: ['id'],
|
|
2024
2073
|
},
|
|
2025
2074
|
},
|
|
2026
2075
|
})
|
|
@@ -2046,6 +2095,39 @@ Registering a bare function (or a positions object) at the top level applies it
|
|
|
2046
2095
|
anything else is read as a table map keyed by the **Drizzle schema key**, and a name that
|
|
2047
2096
|
matches nothing throws at build time.
|
|
2048
2097
|
|
|
2098
|
+
### Which columns `rows` holds
|
|
2099
|
+
|
|
2100
|
+
The statement's `RETURNING` list is built from the client's selection, so by default a row
|
|
2101
|
+
holds only the columns the mutation's document asked for (plus any key a selected relation or a
|
|
2102
|
+
nested write needed). A hook that reads a column the client may not select — `row.id` above, or
|
|
2103
|
+
the `personId` an `addresses` hook needs to bump its person's revision — names it in `columns`:
|
|
2104
|
+
|
|
2105
|
+
```Typescript
|
|
2106
|
+
onWrite: {
|
|
2107
|
+
addresses: {
|
|
2108
|
+
after: async ({ rows, tx }) => { /* every row has `id` and `personId` */ },
|
|
2109
|
+
columns: ['id', 'personId'],
|
|
2110
|
+
},
|
|
2111
|
+
contactInfos: {
|
|
2112
|
+
after: async ({ rows, tx }) => { /* every row has every column */ },
|
|
2113
|
+
columns: 'all',
|
|
2114
|
+
},
|
|
2115
|
+
}
|
|
2116
|
+
```
|
|
2117
|
+
|
|
2118
|
+
- The names are **column keys** — the property names on the Drizzle table object, which is
|
|
2119
|
+
what `rows` is keyed by. An unknown name throws at build time, and so does `columns` on a
|
|
2120
|
+
registration with no `after` hook (only `after` is handed rows).
|
|
2121
|
+
- On a top-level registration, a named column must exist on **every** table; `'all'` always
|
|
2122
|
+
works.
|
|
2123
|
+
- The columns are added to `RETURNING` for every write the hook is attached to: insert, upsert,
|
|
2124
|
+
update, `updateMany`, delete and restore. A delete's rows are the rows it removed.
|
|
2125
|
+
- **The client's response is unchanged.** Columns the hook asked for but the client did not
|
|
2126
|
+
select are taken back off before the rows are mapped, so they reach neither the response nor
|
|
2127
|
+
a custom resolver calling the generated one. The hook gets its own copy of the rows.
|
|
2128
|
+
- **MySQL** has no `RETURNING`, so its mutations return no rows and `columns` has nothing to
|
|
2129
|
+
add to: `rows` stays empty there. The names are still checked at build time.
|
|
2130
|
+
|
|
2049
2131
|
The payload:
|
|
2050
2132
|
|
|
2051
2133
|
| Field | |
|
|
@@ -2055,7 +2137,7 @@ The payload:
|
|
|
2055
2137
|
| `position` | `'before'` or `'after'` |
|
|
2056
2138
|
| `single` | whether the field writes one row or a list |
|
|
2057
2139
|
| `args` | the field's GraphQL arguments, as the resolver received them |
|
|
2058
|
-
| `rows` | the post-write rows **as the database returned them**, before the output mapper |
|
|
2140
|
+
| `rows` | the post-write rows **as the database returned them**, before the output mapper — the selected columns plus the hook's [`columns`](#which-columns-rows-holds) |
|
|
2059
2141
|
| `context`, `info` | the GraphQL context value and resolve info |
|
|
2060
2142
|
| `tx` | the executor the mutation ran on |
|
|
2061
2143
|
|
|
@@ -2285,6 +2367,7 @@ that explains it, where there is one.
|
|
|
2285
2367
|
| `typeNamePrefix` / `typeNameSuffix` / `derivedTypeNameMapper` | none | [names of the derived types](#renaming-the-derived-types) — filters, inputs, aggregates, shared inputs |
|
|
2286
2368
|
| `scalars` / `mapColumnType` | built-in detection | [override a column's GraphQL type](#overriding-a-columns-scalar), by name or by rule |
|
|
2287
2369
|
| `enumNameMapper` | shared `<EnumName>Enum` per database enum | [names the enum a column maps to](#enum-types) |
|
|
2370
|
+
| `enumValueNameMapper` | the stored value | [names the enum's values](#enum-value-names); `'constant'` for CONSTANT_CASE |
|
|
2288
2371
|
| `describeColumn` / `describeTable` / `describeRelation` | none | [GraphQL descriptions](#documenting-the-schema) |
|
|
2289
2372
|
| `deprecateColumn` | none | `@deprecated` on a column's output and optional input fields |
|
|
2290
2373
|
| `conflictDoNothing` | `false` | PostgreSQL only — `ON CONFLICT DO NOTHING` on inserts, which makes `create<Table>Single` nullable |
|
package/dist/index.cjs
CHANGED
|
@@ -189,6 +189,14 @@ const GraphQLDecimalString = new graphql.GraphQLScalarType({
|
|
|
189
189
|
const uncapitalize = (input) => input?.length ? `${input[0].toLocaleLowerCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
190
190
|
const capitalize = (input) => input?.length ? `${input[0].toLocaleUpperCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
191
191
|
/**
|
|
192
|
+
* `inProgress` / `in-progress` / `in progress` / `InProgress` → `IN_PROGRESS`; `HTTPError` →
|
|
193
|
+
* `HTTP_ERROR`. Words are split at non-alphanumeric runs and at lower→upper and
|
|
194
|
+
* acronym→word boundaries, then upper-cased and joined with `_`.
|
|
195
|
+
*
|
|
196
|
+
* The preset behind `enumValueNameMapper: 'constant'`.
|
|
197
|
+
*/
|
|
198
|
+
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("_");
|
|
199
|
+
/**
|
|
192
200
|
* The `typeNameMapper` preset behind `typeNameMapper: 'singularize'`.
|
|
193
201
|
*
|
|
194
202
|
* Derives the singular and plural forms of a table key with the library's own `pluralize`,
|
|
@@ -209,6 +217,8 @@ const singularizeMapper = (tableName) => ({
|
|
|
209
217
|
//#endregion
|
|
210
218
|
//#region src/util/type-converter/index.ts
|
|
211
219
|
const allowedNameChars = /^[a-zA-Z0-9_]+$/;
|
|
220
|
+
/** A GraphQL `Name` that is also allowed as an enum value — the spec reserves the three literals. */
|
|
221
|
+
const validEnumValueName = (name) => /^[_a-zA-Z][_a-zA-Z0-9]*$/.test(name) && name !== "true" && name !== "false" && name !== "null";
|
|
212
222
|
/**
|
|
213
223
|
* The enum type a column was declared with, when it is a *named* one — `pgEnum('status', …)`
|
|
214
224
|
* shared by however many tables reference it. An inline value list (`text({ enum: [...] })`,
|
|
@@ -229,20 +239,35 @@ const pascalize = (input) => input.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((p
|
|
|
229
239
|
*/
|
|
230
240
|
const enumValuesKey = (values) => JSON.stringify(values);
|
|
231
241
|
/**
|
|
242
|
+
* Identity of the GraphQL names those values were given. Kept apart from {@link enumValuesKey}
|
|
243
|
+
* because `enumValueNameMapper` sees the column, so two columns reaching one type can name its
|
|
244
|
+
* values differently — and only one set of names can win.
|
|
245
|
+
*/
|
|
246
|
+
const enumValueNamesKey = (names) => JSON.stringify(names);
|
|
247
|
+
/**
|
|
232
248
|
* Every GraphQL enum name this build has handed out. Two columns that resolve to the same
|
|
233
249
|
* name share one type — that is how the columns of one `pgEnum` end up on a single type —
|
|
234
250
|
* so the values are kept alongside it to tell a genuine collision from a re-use.
|
|
235
251
|
*/
|
|
236
252
|
let enumNamesInUse = /* @__PURE__ */ new Map();
|
|
237
253
|
let enumNameMapper;
|
|
254
|
+
let enumValueNameMapper;
|
|
255
|
+
/** The value-name presets, by the string a config passes for them. */
|
|
256
|
+
const enumValueNamePresets = { constant: ({ value }) => constantCase(value) };
|
|
238
257
|
/**
|
|
239
258
|
* Resets the enum registry for a fresh schema build and installs that build's
|
|
240
|
-
* `enumNameMapper`. Called once per `generateSchemaData`, alongside
|
|
259
|
+
* `enumNameMapper` and `enumValueNameMapper`. Called once per `generateSchemaData`, alongside
|
|
241
260
|
* {@link registerScalarOverrides} — without the reset a second build would reuse the first
|
|
242
261
|
* build's enum types, and so its naming decisions.
|
|
243
262
|
*/
|
|
244
263
|
const registerEnumConfig = (config) => {
|
|
245
264
|
enumNameMapper = config.enumNameMapper;
|
|
265
|
+
const valueMapper = config.enumValueNameMapper;
|
|
266
|
+
if (typeof valueMapper === "string") {
|
|
267
|
+
const preset = enumValueNamePresets[valueMapper];
|
|
268
|
+
if (!preset) throw new Error(`Drizzle-GraphQL Error: Unknown enumValueNameMapper preset '${valueMapper}'. The only preset is 'constant'; pass a function for anything else.`);
|
|
269
|
+
enumValueNameMapper = preset;
|
|
270
|
+
} else enumValueNameMapper = valueMapper;
|
|
246
271
|
enumNamesInUse = /* @__PURE__ */ new Map();
|
|
247
272
|
};
|
|
248
273
|
/**
|
|
@@ -271,22 +296,50 @@ const generateEnumCached = (column, columnName, tableName) => {
|
|
|
271
296
|
columnName,
|
|
272
297
|
values
|
|
273
298
|
}) ?? (declared ? `${pascalize(declared.enumName)}Enum` : `${capitalize(tableName)}${capitalize(columnName)}Enum`);
|
|
299
|
+
const valueNames = values.map((value, index) => {
|
|
300
|
+
const mapped = enumValueNameMapper?.({
|
|
301
|
+
enumName: declared?.enumName,
|
|
302
|
+
schema: declared?.schema,
|
|
303
|
+
tableName,
|
|
304
|
+
columnName,
|
|
305
|
+
values,
|
|
306
|
+
value,
|
|
307
|
+
index,
|
|
308
|
+
typeName: name
|
|
309
|
+
});
|
|
310
|
+
if (mapped === void 0) return allowedNameChars.test(value) ? value : `Option${index}`;
|
|
311
|
+
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.`);
|
|
312
|
+
return mapped;
|
|
313
|
+
});
|
|
314
|
+
const seen = /* @__PURE__ */ new Map();
|
|
315
|
+
values.forEach((value, index) => {
|
|
316
|
+
const valueName = valueNames[index];
|
|
317
|
+
const earlier = seen.get(valueName);
|
|
318
|
+
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.`);
|
|
319
|
+
seen.set(valueName, value);
|
|
320
|
+
});
|
|
274
321
|
const valuesKey = enumValuesKey(values);
|
|
322
|
+
const namesKey = enumValueNamesKey(valueNames);
|
|
275
323
|
const claimed = enumNamesInUse.get(name);
|
|
276
324
|
if (claimed) {
|
|
277
325
|
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.`);
|
|
326
|
+
if (claimed.namesKey !== namesKey) {
|
|
327
|
+
const before = claimed.type.getValues().map((v) => v.name).join(", ");
|
|
328
|
+
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.`);
|
|
329
|
+
}
|
|
278
330
|
return claimed.type;
|
|
279
331
|
}
|
|
280
332
|
const gqlEnum = new graphql.GraphQLEnumType({
|
|
281
333
|
name,
|
|
282
|
-
values: Object.fromEntries(values.map((e, index) => [
|
|
334
|
+
values: Object.fromEntries(values.map((e, index) => [valueNames[index], {
|
|
283
335
|
value: e,
|
|
284
336
|
description: `Value: ${e}`
|
|
285
337
|
}]))
|
|
286
338
|
});
|
|
287
339
|
enumNamesInUse.set(name, {
|
|
288
340
|
type: gqlEnum,
|
|
289
|
-
valuesKey
|
|
341
|
+
valuesKey,
|
|
342
|
+
namesKey
|
|
290
343
|
});
|
|
291
344
|
return gqlEnum;
|
|
292
345
|
};
|
|
@@ -1041,6 +1094,7 @@ const remapToGraphQLArrayOutput = (queryOutput, tableName, table, relationMap) =
|
|
|
1041
1094
|
return queryOutput;
|
|
1042
1095
|
};
|
|
1043
1096
|
const remapFromGraphQLCore = (value, column, columnName) => {
|
|
1097
|
+
if (value === null) return null;
|
|
1044
1098
|
if (getColumnScalarOverride(column, true)) {
|
|
1045
1099
|
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === null) return Object.assign({}, value);
|
|
1046
1100
|
return value;
|
|
@@ -4828,16 +4882,70 @@ const generateTableTypes = (tableName, tables, relationMap, withReturning, relat
|
|
|
4828
4882
|
* that has rows, and the one the common audit/outbox case wants — while `{ before, after }`
|
|
4829
4883
|
* names them explicitly.
|
|
4830
4884
|
*/
|
|
4831
|
-
const normalizeWriteHooks = (hooks) => {
|
|
4885
|
+
const normalizeWriteHooks = (hooks, tableColumns, where) => {
|
|
4832
4886
|
if (!hooks) return;
|
|
4833
4887
|
if (typeof hooks === "function") return { after: hooks };
|
|
4834
4888
|
const resolved = {
|
|
4835
4889
|
before: hooks.before,
|
|
4836
4890
|
after: hooks.after
|
|
4837
4891
|
};
|
|
4892
|
+
if (hooks.columns !== void 0) resolved.columns = resolveHookColumns(hooks.columns, !!hooks.after, tableColumns, where);
|
|
4838
4893
|
return resolved.before || resolved.after ? resolved : void 0;
|
|
4839
4894
|
};
|
|
4840
4895
|
/**
|
|
4896
|
+
* Resolves a hook's `columns` against the table it runs on. `where` names the registration
|
|
4897
|
+
* (`config.onWrite.posts`) and, for a top-level one, the table, so the error says which.
|
|
4898
|
+
*/
|
|
4899
|
+
const resolveHookColumns = (spec, hasAfter, tableColumns, where) => {
|
|
4900
|
+
if (!hasAfter) throw new Error(`Drizzle-GraphQL Error: ${where}.columns only applies to an 'after' hook, and none is registered.`);
|
|
4901
|
+
if (spec === "all") return { ...tableColumns };
|
|
4902
|
+
if (!Array.isArray(spec)) throw new Error(`Drizzle-GraphQL Error: ${where}.columns must be an array of column names or 'all'.`);
|
|
4903
|
+
const resolved = {};
|
|
4904
|
+
for (const name of spec) {
|
|
4905
|
+
const column = typeof name === "string" && Object.keys(tableColumns).includes(name) ? tableColumns[name] : void 0;
|
|
4906
|
+
if (!column) throw new Error(`Drizzle-GraphQL Error: ${where}.columns names '${String(name)}', which is not a column of that table.`);
|
|
4907
|
+
resolved[name] = column;
|
|
4908
|
+
}
|
|
4909
|
+
return resolved;
|
|
4910
|
+
};
|
|
4911
|
+
/**
|
|
4912
|
+
* Adds a hook's `columns` to a RETURNING column map, and reports which keys it added — the
|
|
4913
|
+
* ones the client never asked for, which have to come back off before the rows are mapped.
|
|
4914
|
+
* Returns the map untouched, and no keys, when the hook named none or the selection already
|
|
4915
|
+
* covers them.
|
|
4916
|
+
*/
|
|
4917
|
+
const withHookColumns = (hooks, columns) => {
|
|
4918
|
+
const hookColumns = hooks?.columns;
|
|
4919
|
+
if (!hookColumns) return {
|
|
4920
|
+
returning: columns,
|
|
4921
|
+
added: void 0
|
|
4922
|
+
};
|
|
4923
|
+
const selected = new Set(Object.keys(columns));
|
|
4924
|
+
const added = Object.keys(hookColumns).filter((key) => !selected.has(key));
|
|
4925
|
+
if (!added.length) return {
|
|
4926
|
+
returning: columns,
|
|
4927
|
+
added: void 0
|
|
4928
|
+
};
|
|
4929
|
+
const returning = { ...columns };
|
|
4930
|
+
for (const key of added) returning[key] = hookColumns[key];
|
|
4931
|
+
return {
|
|
4932
|
+
returning,
|
|
4933
|
+
added
|
|
4934
|
+
};
|
|
4935
|
+
};
|
|
4936
|
+
/**
|
|
4937
|
+
* The rows with the hook-only keys taken off, as fresh objects: the hook was handed the
|
|
4938
|
+
* originals and may still hold them, so they are not mutated under it.
|
|
4939
|
+
*/
|
|
4940
|
+
const omitHookColumns = (rows, added) => {
|
|
4941
|
+
if (!added) return rows;
|
|
4942
|
+
return rows.map((row) => {
|
|
4943
|
+
const copy = { ...row };
|
|
4944
|
+
for (const key of added) delete copy[key];
|
|
4945
|
+
return copy;
|
|
4946
|
+
});
|
|
4947
|
+
};
|
|
4948
|
+
/**
|
|
4841
4949
|
* Invokes one position of a field's hooks. Returns `undefined` rather than a resolved promise
|
|
4842
4950
|
* when nothing is registered, so the awaiting call site costs nothing on an unhooked build.
|
|
4843
4951
|
*/
|
|
@@ -4880,6 +4988,7 @@ const writeResolver = ({ db, tableName, operation, single, fieldName, args: quer
|
|
|
4880
4988
|
info,
|
|
4881
4989
|
tx: executor
|
|
4882
4990
|
};
|
|
4991
|
+
let added;
|
|
4883
4992
|
return run({
|
|
4884
4993
|
executor,
|
|
4885
4994
|
args,
|
|
@@ -4889,7 +4998,13 @@ const writeResolver = ({ db, tableName, operation, single, fieldName, args: quer
|
|
|
4889
4998
|
after: (rows) => runWriteHook(hooks, "after", {
|
|
4890
4999
|
...payload,
|
|
4891
5000
|
rows: rows ?? []
|
|
4892
|
-
})
|
|
5001
|
+
}),
|
|
5002
|
+
returning: (columns) => {
|
|
5003
|
+
const withHook = withHookColumns(hooks, columns);
|
|
5004
|
+
added = withHook.added;
|
|
5005
|
+
return withHook.returning;
|
|
5006
|
+
},
|
|
5007
|
+
output: (rows) => omitHookColumns(rows, added)
|
|
4893
5008
|
});
|
|
4894
5009
|
}, !!hooks);
|
|
4895
5010
|
} catch (e) {
|
|
@@ -5063,19 +5178,18 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5063
5178
|
}
|
|
5064
5179
|
const onWriteConfig = config?.onWrite;
|
|
5065
5180
|
const writeHooks = /* @__PURE__ */ new Map();
|
|
5066
|
-
let globalWriteHooks;
|
|
5067
5181
|
if (onWriteConfig) {
|
|
5068
|
-
if (typeof onWriteConfig === "function" || typeof onWriteConfig.before === "function" || typeof onWriteConfig.after === "function")
|
|
5182
|
+
if (typeof onWriteConfig === "function" || typeof onWriteConfig.before === "function" || typeof onWriteConfig.after === "function") for (const tableName of featureTables) writeHooks.set(tableName, normalizeWriteHooks(onWriteConfig, (0, drizzle_orm.getColumns)(schema[tableName]), `config.onWrite (on table '${tableName}')`));
|
|
5069
5183
|
else {
|
|
5070
5184
|
const tableNames = new Set(tableKeys);
|
|
5071
5185
|
for (const [tableName, hooks] of Object.entries(onWriteConfig)) {
|
|
5072
5186
|
if (!tableNames.has(tableName)) throw new Error(`Drizzle-GraphQL Error: config.onWrite names '${tableName}', which is not a table in the Drizzle schema.`);
|
|
5073
5187
|
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'.`);
|
|
5074
|
-
writeHooks.set(tableName, normalizeWriteHooks(hooks));
|
|
5188
|
+
writeHooks.set(tableName, normalizeWriteHooks(hooks, (0, drizzle_orm.getColumns)(schema[tableName]), `config.onWrite.${tableName}`));
|
|
5075
5189
|
}
|
|
5076
5190
|
}
|
|
5077
5191
|
}
|
|
5078
|
-
const onWrite =
|
|
5192
|
+
const onWrite = writeHooks.size ? (tableName) => writeHooks.get(tableName) : void 0;
|
|
5079
5193
|
const policies = scopeConfig || contextValuesConfig || softDeleteInfos.size || onWrite || defaultOrderBy ? {
|
|
5080
5194
|
scope: scopeConfig ? (tableName) => scopeConfig[tableName] : void 0,
|
|
5081
5195
|
contextValues: contextValuesConfig ? (tableName) => contextValuesConfig[tableName] : void 0,
|
|
@@ -5141,6 +5255,7 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5141
5255
|
scalars: config?.scalars,
|
|
5142
5256
|
mapColumnType: config?.mapColumnType,
|
|
5143
5257
|
enumNameMapper: config?.enumNameMapper,
|
|
5258
|
+
enumValueNameMapper: config?.enumValueNameMapper,
|
|
5144
5259
|
transactions,
|
|
5145
5260
|
limits,
|
|
5146
5261
|
policies,
|
|
@@ -7024,7 +7139,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7024
7139
|
txCtx,
|
|
7025
7140
|
policies,
|
|
7026
7141
|
args: queryArgs,
|
|
7027
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7142
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
|
|
7028
7143
|
const supplied = single ? [args.values] : args.values;
|
|
7029
7144
|
if (!supplied.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7030
7145
|
await before();
|
|
@@ -7046,13 +7161,13 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7046
7161
|
scope,
|
|
7047
7162
|
defaultOrderBy: policies?.defaultOrderBy
|
|
7048
7163
|
});
|
|
7049
|
-
const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
|
|
7164
|
+
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7050
7165
|
const runInsert = async (target, values) => {
|
|
7051
7166
|
let query = target.insert(table).values(values).returning(returning);
|
|
7052
7167
|
if (conflictDoNothing) query = query.onConflictDoNothing();
|
|
7053
7168
|
return await query;
|
|
7054
7169
|
};
|
|
7055
|
-
const
|
|
7170
|
+
const written = nestedEntries ? await writeWithNestedOps({
|
|
7056
7171
|
executor,
|
|
7057
7172
|
runtime: nested,
|
|
7058
7173
|
tableName,
|
|
@@ -7061,7 +7176,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7061
7176
|
write: (tx, values) => runInsert(tx, [values]),
|
|
7062
7177
|
context
|
|
7063
7178
|
}) : await runInsert(executor, input);
|
|
7064
|
-
await after(
|
|
7179
|
+
await after(written);
|
|
7180
|
+
const result = output(written);
|
|
7065
7181
|
if (single && !result[0]) {
|
|
7066
7182
|
if (!conflictDoNothing) throw drizzleError("The insert returned no row.", { code: "DRIZZLE_NO_ROW_RETURNED" });
|
|
7067
7183
|
return;
|
|
@@ -7097,7 +7213,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7097
7213
|
txCtx,
|
|
7098
7214
|
policies,
|
|
7099
7215
|
args: queryArgs,
|
|
7100
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7216
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
|
|
7101
7217
|
const supplied = single ? [args.values] : args.values;
|
|
7102
7218
|
if (!supplied.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7103
7219
|
await before();
|
|
@@ -7119,7 +7235,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7119
7235
|
scope,
|
|
7120
7236
|
defaultOrderBy: policies?.defaultOrderBy
|
|
7121
7237
|
});
|
|
7122
|
-
const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
|
|
7238
|
+
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7123
7239
|
const runUpsert = async (target, values) => {
|
|
7124
7240
|
const plan = resolveConflictPlan({
|
|
7125
7241
|
table,
|
|
@@ -7140,7 +7256,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7140
7256
|
});
|
|
7141
7257
|
return await query;
|
|
7142
7258
|
};
|
|
7143
|
-
const
|
|
7259
|
+
const written = nestedEntries ? await writeWithNestedOps({
|
|
7144
7260
|
executor,
|
|
7145
7261
|
runtime: nested,
|
|
7146
7262
|
tableName,
|
|
@@ -7149,7 +7265,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7149
7265
|
write: (tx, values) => runUpsert(tx, [values]),
|
|
7150
7266
|
context
|
|
7151
7267
|
}) : await runUpsert(executor, input);
|
|
7152
|
-
await after(
|
|
7268
|
+
await after(written);
|
|
7269
|
+
const result = output(written);
|
|
7153
7270
|
if (single && !result[0]) return;
|
|
7154
7271
|
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7155
7272
|
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
@@ -7173,7 +7290,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7173
7290
|
txCtx,
|
|
7174
7291
|
policies,
|
|
7175
7292
|
args: queryArgs,
|
|
7176
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7293
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
|
|
7177
7294
|
const { where, set } = args;
|
|
7178
7295
|
const scope = policies?.scope?.(context);
|
|
7179
7296
|
await before();
|
|
@@ -7204,14 +7321,14 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7204
7321
|
required: single || requireWhere
|
|
7205
7322
|
});
|
|
7206
7323
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7207
|
-
const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
|
|
7324
|
+
const returning = withHookReturning(nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns);
|
|
7208
7325
|
const runUpdate = async (target, values) => {
|
|
7209
7326
|
const writes = Object.keys(values).length > 0;
|
|
7210
7327
|
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
7211
7328
|
if (filters) query = query.where(filters);
|
|
7212
7329
|
return await (writes ? query.returning(returning) : query);
|
|
7213
7330
|
};
|
|
7214
|
-
const
|
|
7331
|
+
const written = nestedOps ? await updateWithNestedOps({
|
|
7215
7332
|
executor,
|
|
7216
7333
|
runtime: nested,
|
|
7217
7334
|
tableName,
|
|
@@ -7221,7 +7338,8 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7221
7338
|
write: runUpdate,
|
|
7222
7339
|
context
|
|
7223
7340
|
}) : await runUpdate(executor, input);
|
|
7224
|
-
await after(
|
|
7341
|
+
await after(written);
|
|
7342
|
+
const result = output(written);
|
|
7225
7343
|
if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
|
|
7226
7344
|
if (single && !result[0]) return;
|
|
7227
7345
|
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
@@ -7261,7 +7379,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7261
7379
|
txCtx,
|
|
7262
7380
|
policies,
|
|
7263
7381
|
args: queryArgs,
|
|
7264
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7382
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output }) => {
|
|
7265
7383
|
const { where } = args;
|
|
7266
7384
|
const hard = canHardDelete && args.hard === true;
|
|
7267
7385
|
const scope = policies?.scope?.(context);
|
|
@@ -7281,9 +7399,10 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7281
7399
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7282
7400
|
let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
|
|
7283
7401
|
if (filters) query = query.where(filters);
|
|
7284
|
-
query = query.returning(columns);
|
|
7285
|
-
const
|
|
7286
|
-
await after(
|
|
7402
|
+
query = query.returning(withHookReturning(columns));
|
|
7403
|
+
const written = await query;
|
|
7404
|
+
await after(written);
|
|
7405
|
+
const result = output(written);
|
|
7287
7406
|
if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
|
|
7288
7407
|
if (single) return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
|
|
7289
7408
|
return remapToGraphQLArrayOutput(result, tableName, table);
|
|
@@ -7672,7 +7791,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7672
7791
|
txCtx,
|
|
7673
7792
|
policies,
|
|
7674
7793
|
args: queryArgs,
|
|
7675
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
7794
|
+
run: async ({ executor, args, context, info, before, after, returning: withHookReturning, output: withoutHookColumns }) => {
|
|
7676
7795
|
const { updates } = args;
|
|
7677
7796
|
if (!updates.length) throw drizzleError("No updates were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7678
7797
|
await before();
|
|
@@ -7703,7 +7822,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7703
7822
|
};
|
|
7704
7823
|
});
|
|
7705
7824
|
const anyNested = entries.some((entry) => entry.ops);
|
|
7706
|
-
const returning = anyNested ? nested.withJoinColumns(tableName, mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))), { ...columns }, table) : columns;
|
|
7825
|
+
const returning = withHookReturning(anyNested ? nested.withJoinColumns(tableName, mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))), { ...columns }, table) : columns);
|
|
7707
7826
|
const perEntry = await runBatch({
|
|
7708
7827
|
executor,
|
|
7709
7828
|
entries,
|
|
@@ -7714,8 +7833,9 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7714
7833
|
tableName,
|
|
7715
7834
|
context
|
|
7716
7835
|
});
|
|
7717
|
-
const
|
|
7718
|
-
await after(
|
|
7836
|
+
const written = perEntry.flat();
|
|
7837
|
+
await after(written);
|
|
7838
|
+
const flatRows = withoutHookColumns(written);
|
|
7719
7839
|
const enriched = await withEagerRelations(executor, tableName, flatRows, pkNames, withParams, hasRelations);
|
|
7720
7840
|
const output = [];
|
|
7721
7841
|
let offset = 0;
|