@vantreeseba/drizzle-graphql 13.1.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 +25 -2
- package/dist/index.cjs +248 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +248 -187
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -337,6 +337,13 @@ type DrizzleFieldExtension = {
|
|
|
337
337
|
* Absent everywhere else, so a wrapper can gate purging without parsing arguments.
|
|
338
338
|
*/
|
|
339
339
|
hardDelete?: boolean;
|
|
340
|
+
/**
|
|
341
|
+
* `true` on the count mutations (`update<Table>Count` / `delete<Table>Count`,
|
|
342
|
+
* `features.countMutations`): the field writes like its plural `update` / `delete` sibling
|
|
343
|
+
* — same `operation`, same rows — but resolves to how many rows it wrote (`Int!`) rather
|
|
344
|
+
* than to the rows. Absent everywhere else.
|
|
345
|
+
*/
|
|
346
|
+
count?: boolean;
|
|
340
347
|
/** For `kind: 'relation'` and `operation: 'relationAggregate'`: the relation's name. */
|
|
341
348
|
relation?: string;
|
|
342
349
|
/** For `kind: 'relation'` and `operation: 'relationAggregate'`: the table it hangs off. */
|
|
@@ -822,7 +829,8 @@ type WriteHookPayload = {
|
|
|
822
829
|
*
|
|
823
830
|
* Each row holds the columns the client's selection asked for (plus any key a relation or
|
|
824
831
|
* nested write needed), **and** every column the hook names in `columns`. A column the
|
|
825
|
-
* client did not select and the hook did not name is absent.
|
|
832
|
+
* client did not select and the hook did not name is absent. The count mutations select
|
|
833
|
+
* nothing, so there a row holds the named columns, or the primary key when none are named.
|
|
826
834
|
*/
|
|
827
835
|
rows: any[];
|
|
828
836
|
context: any;
|
|
@@ -1499,6 +1507,17 @@ type SchemaFeatures = {
|
|
|
1499
1507
|
* for nothing. Each mutation is generated only when its plural counterpart is
|
|
1500
1508
|
* (`features.update` / `features.delete`), and honours `features.requireWhere`.
|
|
1501
1509
|
*
|
|
1510
|
+
* Apart from the answer, they obey every rule the plural write does: the table's `scope`
|
|
1511
|
+
* is ANDed into the `where`; on a `softDelete` table the delete marks rows instead of
|
|
1512
|
+
* removing them (taking `hard` where the plural delete does) and neither mutation reaches
|
|
1513
|
+
* an already-marked row; `contextValues` columns are never reassigned by the update; and
|
|
1514
|
+
* `onWrite` hooks run `before` and `after`, in a transaction, with the plural payload
|
|
1515
|
+
* (`single: false`). An `after` hook needs rows, so on a table with one the write adds a
|
|
1516
|
+
* `RETURNING` of the hook's `columns` — or of the primary key if it names none (every column
|
|
1517
|
+
* on a table without one) — and the count is the number of rows returned. On MySQL, which has
|
|
1518
|
+
* no `RETURNING`, the hook gets `rows: []` as on every MySQL mutation. Their
|
|
1519
|
+
* `extensions.drizzle` carries the plural write's `operation` plus `count: true`.
|
|
1520
|
+
*
|
|
1502
1521
|
* Off unless asked for, so an existing schema does not grow new mutations on upgrade.
|
|
1503
1522
|
*
|
|
1504
1523
|
* @default false
|
package/dist/index.d.ts
CHANGED
|
@@ -337,6 +337,13 @@ type DrizzleFieldExtension = {
|
|
|
337
337
|
* Absent everywhere else, so a wrapper can gate purging without parsing arguments.
|
|
338
338
|
*/
|
|
339
339
|
hardDelete?: boolean;
|
|
340
|
+
/**
|
|
341
|
+
* `true` on the count mutations (`update<Table>Count` / `delete<Table>Count`,
|
|
342
|
+
* `features.countMutations`): the field writes like its plural `update` / `delete` sibling
|
|
343
|
+
* — same `operation`, same rows — but resolves to how many rows it wrote (`Int!`) rather
|
|
344
|
+
* than to the rows. Absent everywhere else.
|
|
345
|
+
*/
|
|
346
|
+
count?: boolean;
|
|
340
347
|
/** For `kind: 'relation'` and `operation: 'relationAggregate'`: the relation's name. */
|
|
341
348
|
relation?: string;
|
|
342
349
|
/** For `kind: 'relation'` and `operation: 'relationAggregate'`: the table it hangs off. */
|
|
@@ -822,7 +829,8 @@ type WriteHookPayload = {
|
|
|
822
829
|
*
|
|
823
830
|
* Each row holds the columns the client's selection asked for (plus any key a relation or
|
|
824
831
|
* nested write needed), **and** every column the hook names in `columns`. A column the
|
|
825
|
-
* client did not select and the hook did not name is absent.
|
|
832
|
+
* client did not select and the hook did not name is absent. The count mutations select
|
|
833
|
+
* nothing, so there a row holds the named columns, or the primary key when none are named.
|
|
826
834
|
*/
|
|
827
835
|
rows: any[];
|
|
828
836
|
context: any;
|
|
@@ -1499,6 +1507,17 @@ type SchemaFeatures = {
|
|
|
1499
1507
|
* for nothing. Each mutation is generated only when its plural counterpart is
|
|
1500
1508
|
* (`features.update` / `features.delete`), and honours `features.requireWhere`.
|
|
1501
1509
|
*
|
|
1510
|
+
* Apart from the answer, they obey every rule the plural write does: the table's `scope`
|
|
1511
|
+
* is ANDed into the `where`; on a `softDelete` table the delete marks rows instead of
|
|
1512
|
+
* removing them (taking `hard` where the plural delete does) and neither mutation reaches
|
|
1513
|
+
* an already-marked row; `contextValues` columns are never reassigned by the update; and
|
|
1514
|
+
* `onWrite` hooks run `before` and `after`, in a transaction, with the plural payload
|
|
1515
|
+
* (`single: false`). An `after` hook needs rows, so on a table with one the write adds a
|
|
1516
|
+
* `RETURNING` of the hook's `columns` — or of the primary key if it names none (every column
|
|
1517
|
+
* on a table without one) — and the count is the number of rows returned. On MySQL, which has
|
|
1518
|
+
* no `RETURNING`, the hook gets `rows: []` as on every MySQL mutation. Their
|
|
1519
|
+
* `extensions.drizzle` carries the plural write's `operation` plus `count: true`.
|
|
1520
|
+
*
|
|
1502
1521
|
* Off unless asked for, so an existing schema does not grow new mutations on upgrade.
|
|
1503
1522
|
*
|
|
1504
1523
|
* @default false
|
package/dist/index.js
CHANGED
|
@@ -3724,6 +3724,145 @@ const runMutation = async (db, context, info, txCtx, body, forceTx) => {
|
|
|
3724
3724
|
return runOnSharedTx(state, body);
|
|
3725
3725
|
};
|
|
3726
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
|
|
3727
3866
|
//#region src/util/builders/common/mutation-helpers.ts
|
|
3728
3867
|
/**
|
|
3729
3868
|
* The `hard` argument on the delete mutations of a soft-deleting table that opted into
|
|
@@ -3867,54 +4006,79 @@ const assertSingleMatch = async (executor, table, filters) => {
|
|
|
3867
4006
|
* The plural `update` and `delete` always `RETURNING *`, which is the wrong shape for
|
|
3868
4007
|
* "archive everything older than a year": the caller pays to ship rows it does not want, and
|
|
3869
4008
|
* on a large match that cost is the whole mutation. These return `Int!` instead — how many
|
|
3870
|
-
* rows the write touched
|
|
4009
|
+
* rows the write touched.
|
|
3871
4010
|
*
|
|
3872
|
-
*
|
|
3873
|
-
*
|
|
3874
|
-
*
|
|
3875
|
-
|
|
3876
|
-
|
|
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;
|
|
3877
4030
|
const queryArgs = {
|
|
3878
4031
|
...setArgs ? { set: { type: new GraphQLNonNull(setArgs) } } : {},
|
|
3879
|
-
where: { type: requireWhere ? new GraphQLNonNull(filterArgs) : filterArgs }
|
|
4032
|
+
where: { type: requireWhere ? new GraphQLNonNull(filterArgs) : filterArgs },
|
|
4033
|
+
...canHardDelete ? { hard: hardDeleteArg } : {}
|
|
3880
4034
|
};
|
|
3881
|
-
const
|
|
3882
|
-
|
|
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,
|
|
3883
4041
|
operation: kind,
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
else {
|
|
3903
|
-
const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
|
|
3904
|
-
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" });
|
|
3905
|
-
const values = remapUpdateInput(entry ? entry.columns : set, table, tableName);
|
|
3906
|
-
if (!Object.keys(values).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
3907
|
-
query = executor.update(table).set(values);
|
|
3908
|
-
}
|
|
3909
|
-
if (filters) query = query.where(filters);
|
|
3910
|
-
return rowsAffected(await query);
|
|
3911
|
-
});
|
|
3912
|
-
} catch (e) {
|
|
3913
|
-
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);
|
|
3914
4060
|
}
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
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
|
+
});
|
|
3918
4082
|
};
|
|
3919
4083
|
//#endregion
|
|
3920
4084
|
//#region src/util/batch-loader/index.ts
|
|
@@ -4852,145 +5016,6 @@ const generateTableTypes = (tableName, tables, relationMap, withReturning, relat
|
|
|
4852
5016
|
};
|
|
4853
5017
|
};
|
|
4854
5018
|
//#endregion
|
|
4855
|
-
//#region src/util/builders/common/write-hooks.ts
|
|
4856
|
-
/**
|
|
4857
|
-
* Normalizes the two registration shapes: a bare function is the `after` hook — the position
|
|
4858
|
-
* that has rows, and the one the common audit/outbox case wants — while `{ before, after }`
|
|
4859
|
-
* names them explicitly.
|
|
4860
|
-
*/
|
|
4861
|
-
const normalizeWriteHooks = (hooks, tableColumns, where) => {
|
|
4862
|
-
if (!hooks) return;
|
|
4863
|
-
if (typeof hooks === "function") return { after: hooks };
|
|
4864
|
-
const resolved = {
|
|
4865
|
-
before: hooks.before,
|
|
4866
|
-
after: hooks.after
|
|
4867
|
-
};
|
|
4868
|
-
if (hooks.columns !== void 0) resolved.columns = resolveHookColumns(hooks.columns, !!hooks.after, tableColumns, where);
|
|
4869
|
-
return resolved.before || resolved.after ? resolved : void 0;
|
|
4870
|
-
};
|
|
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
|
-
/**
|
|
4925
|
-
* Invokes one position of a field's hooks. Returns `undefined` rather than a resolved promise
|
|
4926
|
-
* when nothing is registered, so the awaiting call site costs nothing on an unhooked build.
|
|
4927
|
-
*/
|
|
4928
|
-
const runWriteHook = (hooks, position, payload) => {
|
|
4929
|
-
const hook = position === "before" ? hooks?.before : hooks?.after;
|
|
4930
|
-
if (!hook) return;
|
|
4931
|
-
return Promise.resolve(hook({
|
|
4932
|
-
...payload,
|
|
4933
|
-
position,
|
|
4934
|
-
rows: payload.rows ?? []
|
|
4935
|
-
}));
|
|
4936
|
-
};
|
|
4937
|
-
//#endregion
|
|
4938
|
-
//#region src/util/builders/common/write-scaffold.ts
|
|
4939
|
-
/**
|
|
4940
|
-
* Wraps one mutation body in the scaffolding above and returns the field it becomes.
|
|
4941
|
-
*
|
|
4942
|
-
* A field carrying a hook is forced into a transaction even when it would have run straight
|
|
4943
|
-
* on `db`: a hook exists to write atomically with the mutation, and one that ran outside the
|
|
4944
|
-
* transaction could not roll anything back.
|
|
4945
|
-
*/
|
|
4946
|
-
const writeResolver = ({ db, tableName, operation, single, fieldName, args: queryArgs, txCtx, policies, run }) => {
|
|
4947
|
-
const hooks = policies?.onWrite?.(tableName, operation);
|
|
4948
|
-
const errorCtx = {
|
|
4949
|
-
table: tableName,
|
|
4950
|
-
operation,
|
|
4951
|
-
field: fieldName
|
|
4952
|
-
};
|
|
4953
|
-
return {
|
|
4954
|
-
name: fieldName,
|
|
4955
|
-
resolver: async (_source, args, context, info) => {
|
|
4956
|
-
try {
|
|
4957
|
-
return await runMutation(db, context, info, txCtx, (executor) => {
|
|
4958
|
-
const payload = {
|
|
4959
|
-
table: tableName,
|
|
4960
|
-
operation,
|
|
4961
|
-
single,
|
|
4962
|
-
args,
|
|
4963
|
-
context,
|
|
4964
|
-
info,
|
|
4965
|
-
tx: executor
|
|
4966
|
-
};
|
|
4967
|
-
let added;
|
|
4968
|
-
return run({
|
|
4969
|
-
executor,
|
|
4970
|
-
args,
|
|
4971
|
-
context,
|
|
4972
|
-
info,
|
|
4973
|
-
before: () => runWriteHook(hooks, "before", payload),
|
|
4974
|
-
after: (rows) => runWriteHook(hooks, "after", {
|
|
4975
|
-
...payload,
|
|
4976
|
-
rows: rows ?? []
|
|
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)
|
|
4984
|
-
});
|
|
4985
|
-
}, !!hooks);
|
|
4986
|
-
} catch (e) {
|
|
4987
|
-
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
4988
|
-
}
|
|
4989
|
-
},
|
|
4990
|
-
args: queryArgs
|
|
4991
|
-
};
|
|
4992
|
-
};
|
|
4993
|
-
//#endregion
|
|
4994
5019
|
//#region src/util/features.ts
|
|
4995
5020
|
/**
|
|
4996
5021
|
* What a table generates when the config says nothing. Everything is on except the opt-in
|
|
@@ -6992,7 +7017,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
6992
7017
|
fieldName: updateCountFieldName,
|
|
6993
7018
|
requireWhere: tableFeatures.requireWhere,
|
|
6994
7019
|
filterCtx,
|
|
6995
|
-
txCtx: mutationTxCtx
|
|
7020
|
+
txCtx: mutationTxCtx,
|
|
7021
|
+
policies,
|
|
7022
|
+
returnsRows: false
|
|
6996
7023
|
}) : void 0;
|
|
6997
7024
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
6998
7025
|
db,
|
|
@@ -7003,7 +7030,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7003
7030
|
fieldName: deleteCountFieldName,
|
|
7004
7031
|
requireWhere: tableFeatures.requireWhere,
|
|
7005
7032
|
filterCtx,
|
|
7006
|
-
txCtx: mutationTxCtx
|
|
7033
|
+
txCtx: mutationTxCtx,
|
|
7034
|
+
policies,
|
|
7035
|
+
returnsRows: false
|
|
7007
7036
|
}) : void 0;
|
|
7008
7037
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7009
7038
|
const withMeta = (generated, meta) => ({
|
|
@@ -7073,12 +7102,25 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7073
7102
|
{
|
|
7074
7103
|
generated: updateCountGenerated,
|
|
7075
7104
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7105
|
+
meta: {
|
|
7106
|
+
operation: "update",
|
|
7107
|
+
single: false,
|
|
7108
|
+
targetArg: "where",
|
|
7109
|
+
count: true
|
|
7110
|
+
},
|
|
7076
7111
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7077
7112
|
},
|
|
7078
7113
|
{
|
|
7079
7114
|
generated: deleteCountGenerated,
|
|
7080
7115
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7081
|
-
|
|
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."
|
|
7082
7124
|
}
|
|
7083
7125
|
],
|
|
7084
7126
|
onConflictInput,
|
|
@@ -7579,7 +7621,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7579
7621
|
requireWhere: tableFeatures.requireWhere,
|
|
7580
7622
|
filterCtx,
|
|
7581
7623
|
txCtx: mutationTxCtx,
|
|
7582
|
-
nested: nestedRuntime
|
|
7624
|
+
nested: nestedRuntime,
|
|
7625
|
+
policies,
|
|
7626
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7627
|
+
returnsRows: true
|
|
7583
7628
|
}) : void 0;
|
|
7584
7629
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
7585
7630
|
db,
|
|
@@ -7590,7 +7635,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7590
7635
|
fieldName: deleteCountFieldName,
|
|
7591
7636
|
requireWhere: tableFeatures.requireWhere,
|
|
7592
7637
|
filterCtx,
|
|
7593
|
-
txCtx: mutationTxCtx
|
|
7638
|
+
txCtx: mutationTxCtx,
|
|
7639
|
+
policies,
|
|
7640
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7641
|
+
returnsRows: true
|
|
7594
7642
|
}) : void 0;
|
|
7595
7643
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7596
7644
|
return {
|
|
@@ -7700,12 +7748,25 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7700
7748
|
{
|
|
7701
7749
|
generated: updateCountGenerated,
|
|
7702
7750
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7751
|
+
meta: {
|
|
7752
|
+
operation: "update",
|
|
7753
|
+
single: false,
|
|
7754
|
+
targetArg: "where",
|
|
7755
|
+
count: true
|
|
7756
|
+
},
|
|
7703
7757
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7704
7758
|
},
|
|
7705
7759
|
{
|
|
7706
7760
|
generated: deleteCountGenerated,
|
|
7707
7761
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7708
|
-
|
|
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."
|
|
7709
7770
|
}
|
|
7710
7771
|
],
|
|
7711
7772
|
onConflictInput,
|