@vantreeseba/drizzle-graphql 13.1.0 → 14.0.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 +106 -7
- package/dist/index.cjs +547 -268
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -8
- package/dist/index.d.ts +86 -8
- package/dist/index.js +547 -268
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
@@ -5202,7 +5227,6 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5202
5227
|
const missing = [!tableFeatures.insert ? "insert" : void 0, !tableFeatures.update ? "update" : void 0].filter(Boolean).join(" and ");
|
|
5203
5228
|
if (missing) noteConflict(`upsert is on while ${missing} is off, so the upsert mutations are a second write path past the operation you turned off`, tableName);
|
|
5204
5229
|
}
|
|
5205
|
-
if (config?.features?.nestedWrites && (!tableFeatures.insert || !tableFeatures.update)) noteConflict("nestedWrites is on while this table's own insert or update is off, so a nested `create` or `connect` under another table's mutation can still write it", tableName);
|
|
5206
5230
|
if (config?.features?.updateMany !== void 0 && tableFeatures.updateMany && !tableFeatures.update) noteConflict("updateMany is on while update is off, so no batch update is generated", tableName);
|
|
5207
5231
|
if (config?.features?.groupBy !== void 0 && tableFeatures.groupBy && !tableFeatures.aggregates) noteConflict("groupBy is on while aggregates is off, so no grouped query is generated", tableName);
|
|
5208
5232
|
if (tableFeatures.countMutations && !tableFeatures.update && !tableFeatures.delete) noteConflict("countMutations is on while update and delete are both off, so no count mutation is generated", tableName);
|
|
@@ -5808,6 +5832,12 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
5808
5832
|
};
|
|
5809
5833
|
//#endregion
|
|
5810
5834
|
//#region src/util/builders/nested-writes.ts
|
|
5835
|
+
const ALL_OPS = [
|
|
5836
|
+
"create",
|
|
5837
|
+
"connect",
|
|
5838
|
+
"disconnect",
|
|
5839
|
+
"set"
|
|
5840
|
+
];
|
|
5811
5841
|
/**
|
|
5812
5842
|
* Whether a column can be left out of an INSERT — either the database fills it in or it
|
|
5813
5843
|
* accepts NULL.
|
|
@@ -5836,7 +5866,7 @@ const throughColumn = (entry) => entry && is(entry, Column) ? entry : entry?._?.
|
|
|
5836
5866
|
* joining on more than one column per side; and a junction carrying a further NOT NULL column
|
|
5837
5867
|
* with no default, which no `connect` operand could supply a value for.
|
|
5838
5868
|
*/
|
|
5839
|
-
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable) => {
|
|
5869
|
+
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf) => {
|
|
5840
5870
|
if (is(relation, One)) return;
|
|
5841
5871
|
const junction = relation.throughTable;
|
|
5842
5872
|
const sourceEntries = relation.through?.source;
|
|
@@ -5867,11 +5897,18 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
5867
5897
|
canDetach: true,
|
|
5868
5898
|
through: {
|
|
5869
5899
|
table: junction,
|
|
5900
|
+
tableName: tableNameOf(junction),
|
|
5870
5901
|
sourceColPropName,
|
|
5871
5902
|
targetColPropName,
|
|
5872
5903
|
sourceCol,
|
|
5873
5904
|
targetCol
|
|
5874
|
-
}
|
|
5905
|
+
},
|
|
5906
|
+
targetKeyColumns: { [foreignColPropName]: foreignCol },
|
|
5907
|
+
allowed: /* @__PURE__ */ new Set([
|
|
5908
|
+
"connect",
|
|
5909
|
+
"disconnect",
|
|
5910
|
+
"set"
|
|
5911
|
+
])
|
|
5875
5912
|
};
|
|
5876
5913
|
};
|
|
5877
5914
|
/**
|
|
@@ -5884,7 +5921,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
5884
5921
|
* relations whose join columns cannot be read off the drizzle relation; and relations where
|
|
5885
5922
|
* neither join column is unique, which leaves no way to tell which side stores the key.
|
|
5886
5923
|
*/
|
|
5887
|
-
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns) => {
|
|
5924
|
+
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
|
|
5888
5925
|
const uniqueCache = /* @__PURE__ */ new Map();
|
|
5889
5926
|
const singleColumnUniques = (tableName, table) => {
|
|
5890
5927
|
let cached = uniqueCache.get(tableName);
|
|
@@ -5913,7 +5950,7 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5913
5950
|
const targetTable = tables[targetTableName];
|
|
5914
5951
|
if (!targetTable) continue;
|
|
5915
5952
|
if (relation.through) {
|
|
5916
|
-
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable);
|
|
5953
|
+
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf);
|
|
5917
5954
|
if (throughPlan) tablePlans[relationName] = throughPlan;
|
|
5918
5955
|
continue;
|
|
5919
5956
|
}
|
|
@@ -5934,6 +5971,10 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5934
5971
|
else if (foreignIsUnique) fkSide = "parent";
|
|
5935
5972
|
else if (localIsUnique) fkSide = "child";
|
|
5936
5973
|
else continue;
|
|
5974
|
+
const fkColumn = fkSide === "child" ? foreignCol : localCol;
|
|
5975
|
+
const targetColumns = getColumns(targetTable);
|
|
5976
|
+
const targetKeyColumns = Object.fromEntries(resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]).map((name) => [name, targetColumns[name]]));
|
|
5977
|
+
targetKeyColumns[foreignColPropName] = foreignCol;
|
|
5937
5978
|
tablePlans[relationName] = {
|
|
5938
5979
|
relationName,
|
|
5939
5980
|
targetTableName,
|
|
@@ -5944,13 +5985,87 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5944
5985
|
localCol,
|
|
5945
5986
|
isOne,
|
|
5946
5987
|
fkSide,
|
|
5947
|
-
canDetach: !
|
|
5988
|
+
canDetach: !fkColumn.notNull,
|
|
5989
|
+
targetKeyColumns,
|
|
5990
|
+
allowed: new Set(!fkColumn.notNull ? isOne ? [
|
|
5991
|
+
"create",
|
|
5992
|
+
"connect",
|
|
5993
|
+
"disconnect"
|
|
5994
|
+
] : ALL_OPS : ["create", "connect"])
|
|
5948
5995
|
};
|
|
5949
5996
|
}
|
|
5950
5997
|
if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
|
|
5951
5998
|
}
|
|
5952
5999
|
return plans;
|
|
5953
6000
|
};
|
|
6001
|
+
/**
|
|
6002
|
+
* Which of the target's (or junction's) write features each input of a relation needs. A
|
|
6003
|
+
* parent-side `connect` / `disconnect` only rewrites the parent's own column, so it needs
|
|
6004
|
+
* nothing of the target; a child-side one moves the target's foreign key, which is an update
|
|
6005
|
+
* of the target. A to-one child-side `create` also detaches the row attached before it, when
|
|
6006
|
+
* the key is nullable.
|
|
6007
|
+
*/
|
|
6008
|
+
const requiredWrites = (plan, op) => {
|
|
6009
|
+
if (plan.through) {
|
|
6010
|
+
const junction = plan.through.tableName;
|
|
6011
|
+
if (op === "connect") return [{
|
|
6012
|
+
tableName: junction,
|
|
6013
|
+
feature: "insert"
|
|
6014
|
+
}];
|
|
6015
|
+
if (op === "disconnect") return [{
|
|
6016
|
+
tableName: junction,
|
|
6017
|
+
feature: "delete"
|
|
6018
|
+
}];
|
|
6019
|
+
if (op === "set") return [{
|
|
6020
|
+
tableName: junction,
|
|
6021
|
+
feature: "delete"
|
|
6022
|
+
}, {
|
|
6023
|
+
tableName: junction,
|
|
6024
|
+
feature: "insert"
|
|
6025
|
+
}];
|
|
6026
|
+
return [];
|
|
6027
|
+
}
|
|
6028
|
+
const target = plan.targetTableName;
|
|
6029
|
+
if (op === "create") return plan.fkSide === "child" && plan.isOne && plan.canDetach ? [{
|
|
6030
|
+
tableName: target,
|
|
6031
|
+
feature: "insert"
|
|
6032
|
+
}, {
|
|
6033
|
+
tableName: target,
|
|
6034
|
+
feature: "update"
|
|
6035
|
+
}] : [{
|
|
6036
|
+
tableName: target,
|
|
6037
|
+
feature: "insert"
|
|
6038
|
+
}];
|
|
6039
|
+
return plan.fkSide === "child" ? [{
|
|
6040
|
+
tableName: target,
|
|
6041
|
+
feature: "update"
|
|
6042
|
+
}] : [];
|
|
6043
|
+
};
|
|
6044
|
+
/**
|
|
6045
|
+
* Narrows every plan's `allowed` inputs to the ones whose writes the table they land on
|
|
6046
|
+
* permits: its `features.insert` / `update` / `delete`, and for a junction table that it is
|
|
6047
|
+
* not excluded. A plan left with nothing is dropped, so its relation field is not generated.
|
|
6048
|
+
*
|
|
6049
|
+
* Returns one line per input taken out, for the build note.
|
|
6050
|
+
*/
|
|
6051
|
+
const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
|
|
6052
|
+
const omitted = [];
|
|
6053
|
+
for (const [tableName, tablePlans] of Object.entries(plans)) {
|
|
6054
|
+
for (const [relationName, plan] of Object.entries(tablePlans)) {
|
|
6055
|
+
for (const op of [...plan.allowed]) {
|
|
6056
|
+
const blocked = requiredWrites(plan, op).filter(({ tableName: target, feature }) => target !== void 0 && (isExcluded(target) || !featureOf(target)[feature]));
|
|
6057
|
+
if (blocked.length) {
|
|
6058
|
+
plan.allowed.delete(op);
|
|
6059
|
+
const reasons = blocked.map(({ tableName: target, feature }) => isExcluded(target) ? `${target} is excluded` : `${target}.${feature} is off`);
|
|
6060
|
+
omitted.push(`${tableName}.${relationName}.${op} (${[...new Set(reasons)].join(", ")})`);
|
|
6061
|
+
}
|
|
6062
|
+
}
|
|
6063
|
+
if (!plan.allowed.size) delete tablePlans[relationName];
|
|
6064
|
+
}
|
|
6065
|
+
if (!Object.keys(tablePlans).length) delete plans[tableName];
|
|
6066
|
+
}
|
|
6067
|
+
return omitted;
|
|
6068
|
+
};
|
|
5954
6069
|
const NO_COLUMNS = /* @__PURE__ */ new Set();
|
|
5955
6070
|
/**
|
|
5956
6071
|
* Builds the nested-write input types for a schema.
|
|
@@ -6005,8 +6120,11 @@ const createNestedWriteTypes = (params) => {
|
|
|
6005
6120
|
*
|
|
6006
6121
|
* A many-to-many relation offers no `create`: inserting a row *and* linking it is two
|
|
6007
6122
|
* writes to two tables, and the link half is what the relation field is for. Its
|
|
6008
|
-
* `disconnect` and `set`
|
|
6123
|
+
* `disconnect` and `set` do not depend on nullability, since breaking a link deletes a
|
|
6009
6124
|
* junction row rather than nulling a column that might be NOT NULL.
|
|
6125
|
+
*
|
|
6126
|
+
* Each input is also subject to `plan.allowed` — the table it writes has to have that
|
|
6127
|
+
* write turned on (see {@link gateNestedWritePlans}).
|
|
6010
6128
|
*/
|
|
6011
6129
|
const wrapperType = (tableName, typeName, plan, forUpdate) => {
|
|
6012
6130
|
const key = `${tableName}.${plan.relationName}.${forUpdate ? "update" : "create"}`;
|
|
@@ -6015,57 +6133,52 @@ const createNestedWriteTypes = (params) => {
|
|
|
6015
6133
|
const filters = targetFilters(plan);
|
|
6016
6134
|
const targetTypeName = resolveTypeName(plan.targetTableName, typeNameMapper);
|
|
6017
6135
|
const fields = {};
|
|
6136
|
+
const offers = (op) => plan.allowed.has(op);
|
|
6018
6137
|
if (plan.through) {
|
|
6019
|
-
if (
|
|
6020
|
-
fields["connect"] = {
|
|
6138
|
+
if (filters && offers("connect")) fields["connect"] = {
|
|
6021
6139
|
type: listOf(filters),
|
|
6022
6140
|
description: `Link every existing ${targetTypeName} row these match. Already-linked rows are left alone.`
|
|
6023
6141
|
};
|
|
6024
|
-
if (forUpdate) {
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
};
|
|
6033
|
-
}
|
|
6142
|
+
if (filters && forUpdate && offers("disconnect")) fields["disconnect"] = {
|
|
6143
|
+
type: listOf(filters),
|
|
6144
|
+
description: `Unlink every linked ${targetTypeName} row these match, leaving the rows in place`
|
|
6145
|
+
};
|
|
6146
|
+
if (filters && forUpdate && offers("set")) fields["set"] = {
|
|
6147
|
+
type: listOf(filters),
|
|
6148
|
+
description: `Replace the whole set: unlink everything linked, then link every ${targetTypeName} row these match. \`set: []\` clears the relation. Applied before \`disconnect\` and \`connect\`.`
|
|
6149
|
+
};
|
|
6034
6150
|
} else if (plan.isOne) {
|
|
6035
|
-
fields["create"] = {
|
|
6151
|
+
if (offers("create")) fields["create"] = {
|
|
6036
6152
|
type: payloadType(tableName, typeName, plan),
|
|
6037
6153
|
description: `Insert a ${targetTypeName} row and attach it`
|
|
6038
6154
|
};
|
|
6039
|
-
if (filters) fields["connect"] = {
|
|
6155
|
+
if (filters && offers("connect")) fields["connect"] = {
|
|
6040
6156
|
type: filters,
|
|
6041
6157
|
description: `Attach the existing ${targetTypeName} row this matches. Must match exactly one row.`
|
|
6042
6158
|
};
|
|
6043
|
-
if (forUpdate &&
|
|
6159
|
+
if (forUpdate && offers("disconnect")) fields["disconnect"] = {
|
|
6044
6160
|
type: GraphQLBoolean,
|
|
6045
6161
|
description: `Detach the currently attached ${targetTypeName} row, leaving it in place`
|
|
6046
6162
|
};
|
|
6047
6163
|
} else {
|
|
6048
|
-
fields["create"] = {
|
|
6164
|
+
if (offers("create")) fields["create"] = {
|
|
6049
6165
|
type: listOf(payloadType(tableName, typeName, plan)),
|
|
6050
6166
|
description: `Insert these ${targetTypeName} rows and attach them`
|
|
6051
6167
|
};
|
|
6052
|
-
if (filters) {
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
description: `Replace the whole set: detach everything attached, then attach every ${targetTypeName} row these match. Applied before \`disconnect\`, \`connect\` and \`create\`.`
|
|
6065
|
-
};
|
|
6066
|
-
}
|
|
6067
|
-
}
|
|
6168
|
+
if (filters && offers("connect")) fields["connect"] = {
|
|
6169
|
+
type: listOf(filters),
|
|
6170
|
+
description: `Attach every existing ${targetTypeName} row these match`
|
|
6171
|
+
};
|
|
6172
|
+
if (filters && forUpdate && offers("disconnect")) fields["disconnect"] = {
|
|
6173
|
+
type: listOf(filters),
|
|
6174
|
+
description: `Detach every attached ${targetTypeName} row these match, leaving the rows in place`
|
|
6175
|
+
};
|
|
6176
|
+
if (filters && forUpdate && offers("set")) fields["set"] = {
|
|
6177
|
+
type: listOf(filters),
|
|
6178
|
+
description: `Replace the whole set: detach everything attached, then attach every ${targetTypeName} row these match. Applied before \`disconnect\`, \`connect\` and \`create\`.`
|
|
6179
|
+
};
|
|
6068
6180
|
}
|
|
6181
|
+
if (!Object.keys(fields).length) return;
|
|
6069
6182
|
const type = new GraphQLInputObjectType({
|
|
6070
6183
|
name: cacheCtx.typeName({
|
|
6071
6184
|
kind: "nestedWriteInput",
|
|
@@ -6097,7 +6210,7 @@ const createNestedWriteTypes = (params) => {
|
|
|
6097
6210
|
const tablePlans = plans[tableName];
|
|
6098
6211
|
if (!tablePlans) return NO_COLUMNS;
|
|
6099
6212
|
const relaxed = /* @__PURE__ */ new Set();
|
|
6100
|
-
for (const plan of Object.values(tablePlans)) if (plan.fkSide === "parent" && !isOptionalOnInsert(plan.localCol)) relaxed.add(plan.localColPropName);
|
|
6213
|
+
for (const plan of Object.values(tablePlans)) if (plan.fkSide === "parent" && (plan.allowed.has("create") || plan.allowed.has("connect")) && !isOptionalOnInsert(plan.localCol)) relaxed.add(plan.localColPropName);
|
|
6101
6214
|
return relaxed.size ? relaxed : NO_COLUMNS;
|
|
6102
6215
|
}
|
|
6103
6216
|
};
|
|
@@ -6108,8 +6221,99 @@ const createNestedWriteTypes = (params) => {
|
|
|
6108
6221
|
*/
|
|
6109
6222
|
const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
|
|
6110
6223
|
const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
6224
|
+
const supplied = (value) => value !== void 0 && value !== null;
|
|
6225
|
+
/**
|
|
6226
|
+
* The hook calls one relation field makes. Every step is registered before the field's first
|
|
6227
|
+
* statement, so each `before` runs ahead of all of them and each `after` once the last has
|
|
6228
|
+
* returned. A table without hooks registers nothing, and its statements run exactly as they
|
|
6229
|
+
* did before hooks reached nested writes — no RETURNING clause is added.
|
|
6230
|
+
*/
|
|
6231
|
+
const createHookSteps = (onWrite, executor, request, parentTable, plan) => {
|
|
6232
|
+
const steps = /* @__PURE__ */ new Map();
|
|
6233
|
+
const keyOf = (op, operation) => `${op}:${operation}`;
|
|
6234
|
+
const payload = (step) => ({
|
|
6235
|
+
table: step.tableName,
|
|
6236
|
+
operation: step.operation,
|
|
6237
|
+
single: plan.isOne,
|
|
6238
|
+
args: request?.args,
|
|
6239
|
+
context: request?.context,
|
|
6240
|
+
info: request?.info,
|
|
6241
|
+
tx: executor,
|
|
6242
|
+
nested: {
|
|
6243
|
+
parentTable,
|
|
6244
|
+
relation: plan.relationName,
|
|
6245
|
+
op: step.op
|
|
6246
|
+
}
|
|
6247
|
+
});
|
|
6248
|
+
return {
|
|
6249
|
+
/** Registers the hook call for `op`'s `operation` statements, if that table has hooks. */
|
|
6250
|
+
add: (op, operation) => {
|
|
6251
|
+
const through = plan.through;
|
|
6252
|
+
const tableName = through ? through.tableName : plan.targetTableName;
|
|
6253
|
+
const hooks = tableName ? onWrite?.(tableName, operation) : void 0;
|
|
6254
|
+
if (!tableName || !hooks || steps.has(keyOf(op, operation))) return;
|
|
6255
|
+
const keyColumns = through ? {
|
|
6256
|
+
[through.sourceColPropName]: through.sourceCol,
|
|
6257
|
+
[through.targetColPropName]: through.targetCol
|
|
6258
|
+
} : { ...plan.targetKeyColumns };
|
|
6259
|
+
steps.set(keyOf(op, operation), {
|
|
6260
|
+
tableName,
|
|
6261
|
+
operation,
|
|
6262
|
+
op,
|
|
6263
|
+
hooks,
|
|
6264
|
+
returning: withHookColumns(hooks, keyColumns).returning,
|
|
6265
|
+
rows: []
|
|
6266
|
+
});
|
|
6267
|
+
},
|
|
6268
|
+
get: (op, operation) => steps.get(keyOf(op, operation)),
|
|
6269
|
+
/** Runs one statement, returning its rows to the step it belongs to when there is one. */
|
|
6270
|
+
run: async (query, op, operation) => {
|
|
6271
|
+
const step = steps.get(keyOf(op, operation));
|
|
6272
|
+
if (!step) {
|
|
6273
|
+
await query;
|
|
6274
|
+
return;
|
|
6275
|
+
}
|
|
6276
|
+
step.rows.push(...await query.returning(step.returning));
|
|
6277
|
+
},
|
|
6278
|
+
before: async () => {
|
|
6279
|
+
for (const step of steps.values()) await runWriteHook(step.hooks, "before", payload(step));
|
|
6280
|
+
},
|
|
6281
|
+
after: async () => {
|
|
6282
|
+
for (const step of steps.values()) await runWriteHook(step.hooks, "after", {
|
|
6283
|
+
...payload(step),
|
|
6284
|
+
rows: step.rows
|
|
6285
|
+
});
|
|
6286
|
+
}
|
|
6287
|
+
};
|
|
6288
|
+
};
|
|
6289
|
+
/**
|
|
6290
|
+
* Registers the hook calls a child-side relation field will make, in the order its
|
|
6291
|
+
* statements run, so the `before` hooks fire in that order too. Mirrors the branches of
|
|
6292
|
+
* `applyChildSide` and `applyThroughOps` exactly: a step registered for an input that runs no
|
|
6293
|
+
* statement would fire with no rows, and one missing would miss them.
|
|
6294
|
+
*/
|
|
6295
|
+
const registerChildSteps = (steps, plan, op) => {
|
|
6296
|
+
if (plan.through) {
|
|
6297
|
+
if (supplied(op["set"])) {
|
|
6298
|
+
steps.add("set", "delete");
|
|
6299
|
+
steps.add("set", "insert");
|
|
6300
|
+
}
|
|
6301
|
+
if (supplied(op["disconnect"])) steps.add("disconnect", "delete");
|
|
6302
|
+
if (supplied(op["connect"])) steps.add("connect", "insert");
|
|
6303
|
+
return;
|
|
6304
|
+
}
|
|
6305
|
+
if (op["set"] !== void 0) steps.add("set", "update");
|
|
6306
|
+
if (op["disconnect"] !== void 0 && (!plan.isOne || op["disconnect"] === true)) steps.add("disconnect", "update");
|
|
6307
|
+
const attachedBy = attachingOp(op);
|
|
6308
|
+
if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) steps.add(attachedBy, "update");
|
|
6309
|
+
if (op["connect"] !== void 0) steps.add("connect", "update");
|
|
6310
|
+
if (op["create"] !== void 0) steps.add("create", "insert");
|
|
6311
|
+
};
|
|
6312
|
+
/** The input that attaches a row to a to-one, which detaches whatever was attached before. */
|
|
6313
|
+
const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
|
|
6111
6314
|
const createNestedWriteRuntime = (params) => {
|
|
6112
6315
|
const { plans, filterCtx, policies, contextValues } = params;
|
|
6316
|
+
const onWrite = policies?.onWrite;
|
|
6113
6317
|
/**
|
|
6114
6318
|
* A `connect` / `disconnect` / `set` operand compiled to SQL. An operand that compiles to
|
|
6115
6319
|
* nothing is rejected: `connect: {}` would otherwise attach every row in the table.
|
|
@@ -6120,12 +6324,12 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6120
6324
|
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
6121
6325
|
};
|
|
6122
6326
|
const assertSingleOperation = (plan, op) => {
|
|
6123
|
-
const
|
|
6327
|
+
const given = [
|
|
6124
6328
|
"create",
|
|
6125
6329
|
"connect",
|
|
6126
6330
|
"disconnect"
|
|
6127
|
-
].filter((key) => op[key]
|
|
6128
|
-
if (
|
|
6331
|
+
].filter((key) => supplied(op[key]));
|
|
6332
|
+
if (given.length > 1) throw drizzleError(`Drizzle-GraphQL Error: '${plan.relationName}' takes one of ${given.join(", ")} at a time — it holds a single row.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6129
6333
|
};
|
|
6130
6334
|
/** The values the junction stores for the target rows one operand selects. */
|
|
6131
6335
|
const selectedTargetKeys = async (executor, plan, filter, operation, scope) => {
|
|
@@ -6140,9 +6344,8 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6140
6344
|
* error on some of them — the read and the write share the surrounding transaction, so no
|
|
6141
6345
|
* other writer can slip a duplicate in between them.
|
|
6142
6346
|
*/
|
|
6143
|
-
const applyThroughOps = async (executor, plan, key, op, scope) => {
|
|
6347
|
+
const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
|
|
6144
6348
|
const through = plan.through;
|
|
6145
|
-
const unlinkAll = () => executor.delete(through.table).where(eq(through.sourceCol, key));
|
|
6146
6349
|
const link = async (operands, operation) => {
|
|
6147
6350
|
const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
6148
6351
|
const rows = [];
|
|
@@ -6153,19 +6356,19 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6153
6356
|
[through.targetColPropName]: targetKey
|
|
6154
6357
|
});
|
|
6155
6358
|
}
|
|
6156
|
-
if (rows.length) await executor.insert(through.table).values(rows);
|
|
6359
|
+
if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
|
|
6157
6360
|
};
|
|
6158
|
-
if (op["set"]
|
|
6159
|
-
await
|
|
6361
|
+
if (supplied(op["set"])) {
|
|
6362
|
+
await steps.run(executor.delete(through.table).where(eq(through.sourceCol, key)), "set", "delete");
|
|
6160
6363
|
await link(asArray(op["set"]), "set");
|
|
6161
6364
|
}
|
|
6162
|
-
if (op["disconnect"]
|
|
6365
|
+
if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
|
|
6163
6366
|
const targetKeys = await selectedTargetKeys(executor, plan, filter, "disconnect", scope);
|
|
6164
|
-
if (targetKeys.length) await executor.delete(through.table).where(and(eq(through.sourceCol, key), inArray(through.targetCol, targetKeys)));
|
|
6367
|
+
if (targetKeys.length) await steps.run(executor.delete(through.table).where(and(eq(through.sourceCol, key), inArray(through.targetCol, targetKeys))), "disconnect", "delete");
|
|
6165
6368
|
}
|
|
6166
|
-
if (op["connect"]
|
|
6369
|
+
if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
|
|
6167
6370
|
};
|
|
6168
|
-
const detachAll =
|
|
6371
|
+
const detachAll = (executor, plan, key, scope, steps, op) => steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(withScope(scope, plan.targetTableName, plan.targetTable, eq(plan.foreignCol, key))), op, "update");
|
|
6169
6372
|
return {
|
|
6170
6373
|
enabled: (tableName) => !!plans[tableName],
|
|
6171
6374
|
split: (tableName, values) => {
|
|
@@ -6194,19 +6397,27 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6194
6397
|
}
|
|
6195
6398
|
return columns;
|
|
6196
6399
|
},
|
|
6197
|
-
applyParentSide: async (executor, tableName, ops,
|
|
6400
|
+
applyParentSide: async (executor, tableName, ops, request) => {
|
|
6198
6401
|
const tablePlans = plans[tableName];
|
|
6199
6402
|
const patch = {};
|
|
6200
6403
|
if (!tablePlans) return patch;
|
|
6404
|
+
const context = request?.context;
|
|
6201
6405
|
const scope = resolveScope(policies, context, filterCtx);
|
|
6202
6406
|
for (const [relationName, op] of Object.entries(ops)) {
|
|
6203
6407
|
const plan = tablePlans[relationName];
|
|
6204
6408
|
if (plan?.fkSide !== "parent") continue;
|
|
6205
6409
|
assertSingleOperation(plan, op);
|
|
6206
6410
|
if (op["create"] !== void 0) {
|
|
6411
|
+
const steps = createHookSteps(onWrite, executor, request, tableName, plan);
|
|
6412
|
+
steps.add("create", "insert");
|
|
6413
|
+
await steps.before();
|
|
6207
6414
|
const values = applyContextValues(remapFromGraphQLSingleInput({ ...op["create"] }, plan.targetTable), contextValues?.(plan.targetTableName), context);
|
|
6208
|
-
const
|
|
6415
|
+
const step = steps.get("create", "insert");
|
|
6416
|
+
const inserted = await executor.insert(plan.targetTable).values(values).returning(step ? step.returning : { [plan.foreignColPropName]: plan.foreignCol });
|
|
6417
|
+
step?.rows.push(...inserted);
|
|
6418
|
+
const created = inserted[0];
|
|
6209
6419
|
if (!created) throw drizzleError(`Drizzle-GraphQL Error: 'create' on '${relationName}' inserted no row, so there is nothing to attach.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6420
|
+
await steps.after();
|
|
6210
6421
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
6211
6422
|
} else if (op["connect"] !== void 0) {
|
|
6212
6423
|
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
@@ -6216,41 +6427,46 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6216
6427
|
}
|
|
6217
6428
|
return patch;
|
|
6218
6429
|
},
|
|
6219
|
-
applyChildSide: async (executor, tableName, ops, parentRows,
|
|
6430
|
+
applyChildSide: async (executor, tableName, ops, parentRows, request) => {
|
|
6220
6431
|
const tablePlans = plans[tableName];
|
|
6221
6432
|
if (!tablePlans) return;
|
|
6433
|
+
const context = request?.context;
|
|
6222
6434
|
const scope = resolveScope(policies, context, filterCtx);
|
|
6223
6435
|
for (const [relationName, op] of Object.entries(ops)) {
|
|
6224
6436
|
const plan = tablePlans[relationName];
|
|
6225
6437
|
if (plan?.fkSide !== "child") continue;
|
|
6226
6438
|
if (plan.isOne) assertSingleOperation(plan, op);
|
|
6439
|
+
const steps = createHookSteps(onWrite, executor, request, tableName, plan);
|
|
6440
|
+
registerChildSteps(steps, plan, op);
|
|
6441
|
+
await steps.before();
|
|
6227
6442
|
for (const parentRow of parentRows) {
|
|
6228
6443
|
const key = parentRow?.[plan.localColPropName];
|
|
6229
6444
|
if (key === void 0 || key === null) throw drizzleError(`Drizzle-GraphQL Error: cannot write through '${relationName}': the ${tableName} row has no '${plan.localColPropName}' value to attach to.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6230
6445
|
if (plan.through) {
|
|
6231
|
-
await applyThroughOps(executor, plan, key, op, scope);
|
|
6446
|
+
await applyThroughOps(executor, plan, key, op, scope, steps);
|
|
6232
6447
|
continue;
|
|
6233
6448
|
}
|
|
6234
6449
|
if (op["set"] !== void 0) {
|
|
6235
|
-
await detachAll(executor, plan, key, scope);
|
|
6236
|
-
for (const filter of asArray(op["set"])) await executor.update(plan.targetTable).set({ [plan.foreignColPropName]: key }).where(conditionOf(plan, filter, "set", scope));
|
|
6450
|
+
await detachAll(executor, plan, key, scope, steps, "set");
|
|
6451
|
+
for (const filter of asArray(op["set"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: key }).where(conditionOf(plan, filter, "set", scope)), "set", "update");
|
|
6237
6452
|
}
|
|
6238
6453
|
if (op["disconnect"] !== void 0) {
|
|
6239
6454
|
if (plan.isOne) {
|
|
6240
|
-
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope);
|
|
6241
|
-
} else for (const filter of asArray(op["disconnect"])) await executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(and(eq(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope)));
|
|
6455
|
+
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
|
|
6456
|
+
} else for (const filter of asArray(op["disconnect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(and(eq(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope))), "disconnect", "update");
|
|
6242
6457
|
}
|
|
6243
|
-
const
|
|
6244
|
-
if (plan.isOne &&
|
|
6245
|
-
if (op["connect"] !== void 0) for (const filter of asArray(op["connect"])) await executor.update(plan.targetTable).set({ [plan.foreignColPropName]: key }).where(conditionOf(plan, filter, "connect", scope));
|
|
6458
|
+
const attachedBy = attachingOp(op);
|
|
6459
|
+
if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
|
|
6460
|
+
if (op["connect"] !== void 0) for (const filter of asArray(op["connect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: key }).where(conditionOf(plan, filter, "connect", scope)), "connect", "update");
|
|
6246
6461
|
if (op["create"] !== void 0) {
|
|
6247
6462
|
const rows = asArray(op["create"]).map((row) => ({
|
|
6248
6463
|
...applyContextValues(remapFromGraphQLSingleInput({ ...row }, plan.targetTable), contextValues?.(plan.targetTableName), context),
|
|
6249
6464
|
[plan.foreignColPropName]: key
|
|
6250
6465
|
}));
|
|
6251
|
-
if (rows.length) await executor.insert(plan.targetTable).values(rows);
|
|
6466
|
+
if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
|
|
6252
6467
|
}
|
|
6253
6468
|
}
|
|
6469
|
+
await steps.after();
|
|
6254
6470
|
}
|
|
6255
6471
|
}
|
|
6256
6472
|
};
|
|
@@ -6260,21 +6476,25 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6260
6476
|
* inside a single transaction — a savepoint when the caller already runs on one, so a failing
|
|
6261
6477
|
* entry rolls the whole batch back.
|
|
6262
6478
|
*
|
|
6479
|
+
* That transaction is also the `tx` the target tables' `onWrite` hooks run on. Every nested
|
|
6480
|
+
* write opens it, so a hook on a nested target has the whole tree to roll back even when the
|
|
6481
|
+
* root field carries no hook of its own and was not forced into a transaction for one.
|
|
6482
|
+
*
|
|
6263
6483
|
* Entries are written one at a time rather than as a single multi-row statement: a nested
|
|
6264
6484
|
* operation attaches to the row its own entry produced, so the two have to stay correlated.
|
|
6265
6485
|
* The batch path is untouched when no entry carries a nested operation.
|
|
6266
6486
|
*/
|
|
6267
6487
|
const writeWithNestedOps = async (params) => {
|
|
6268
|
-
const { executor, runtime, tableName, entries, remapValues, write,
|
|
6488
|
+
const { executor, runtime, tableName, entries, remapValues, write, request } = params;
|
|
6269
6489
|
return executor.transaction(async (tx) => {
|
|
6270
6490
|
const rows = [];
|
|
6271
6491
|
for (const entry of entries) {
|
|
6272
|
-
const patch = await runtime.applyParentSide(tx, tableName, entry.ops,
|
|
6492
|
+
const patch = await runtime.applyParentSide(tx, tableName, entry.ops, request);
|
|
6273
6493
|
const written = await write(tx, {
|
|
6274
6494
|
...remapValues(entry.columns),
|
|
6275
6495
|
...patch
|
|
6276
6496
|
});
|
|
6277
|
-
await runtime.applyChildSide(tx, tableName, entry.ops, written,
|
|
6497
|
+
await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
|
|
6278
6498
|
rows.push(...written);
|
|
6279
6499
|
}
|
|
6280
6500
|
return rows;
|
|
@@ -6286,14 +6506,14 @@ const writeWithNestedOps = async (params) => {
|
|
|
6286
6506
|
* attaches to each row that came back.
|
|
6287
6507
|
*/
|
|
6288
6508
|
const updateWithNestedOps = async (params) => {
|
|
6289
|
-
const { executor, runtime, tableName, columns, ops, remapValues, write,
|
|
6509
|
+
const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
|
|
6290
6510
|
return executor.transaction(async (tx) => {
|
|
6291
|
-
const patch = await runtime.applyParentSide(tx, tableName, ops,
|
|
6511
|
+
const patch = await runtime.applyParentSide(tx, tableName, ops, request);
|
|
6292
6512
|
const rows = await write(tx, {
|
|
6293
6513
|
...remapValues(columns),
|
|
6294
6514
|
...patch
|
|
6295
6515
|
});
|
|
6296
|
-
await runtime.applyChildSide(tx, tableName, ops, rows,
|
|
6516
|
+
await runtime.applyChildSide(tx, tableName, ops, rows, request);
|
|
6297
6517
|
return rows;
|
|
6298
6518
|
});
|
|
6299
6519
|
};
|
|
@@ -6494,7 +6714,14 @@ const createSchemaBuilder = (adapter) => {
|
|
|
6494
6714
|
uniqueKeysOf: (tableName) => uniqueKeys[tableName]
|
|
6495
6715
|
};
|
|
6496
6716
|
adapter.preflight?.(db, options);
|
|
6497
|
-
const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) => uniqueColumnSets(target), (target) => primaryKeyPropNames(target), extractRelationJoinColumns) : void 0;
|
|
6717
|
+
const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) => uniqueColumnSets(target), (target) => primaryKeyPropNames(target), extractRelationJoinColumns, (junction) => schemaEntries.find(([, value]) => value === junction)?.[0]) : void 0;
|
|
6718
|
+
if (nestedPlans) {
|
|
6719
|
+
const omitted = gateNestedWritePlans(nestedPlans, featureOf, (tableName) => excludedTables.has(tableName));
|
|
6720
|
+
if (omitted.length) {
|
|
6721
|
+
const listed = omitted.length > 5 ? `${omitted.slice(0, 5).join(", ")} and ${omitted.length - 5} more` : omitted.join(", ");
|
|
6722
|
+
console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6498
6725
|
const nestedTypes = nestedPlans ? createNestedWriteTypes({
|
|
6499
6726
|
plans: nestedPlans,
|
|
6500
6727
|
cacheCtx,
|
|
@@ -6992,7 +7219,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
6992
7219
|
fieldName: updateCountFieldName,
|
|
6993
7220
|
requireWhere: tableFeatures.requireWhere,
|
|
6994
7221
|
filterCtx,
|
|
6995
|
-
txCtx: mutationTxCtx
|
|
7222
|
+
txCtx: mutationTxCtx,
|
|
7223
|
+
policies,
|
|
7224
|
+
returnsRows: false
|
|
6996
7225
|
}) : void 0;
|
|
6997
7226
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
6998
7227
|
db,
|
|
@@ -7003,7 +7232,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7003
7232
|
fieldName: deleteCountFieldName,
|
|
7004
7233
|
requireWhere: tableFeatures.requireWhere,
|
|
7005
7234
|
filterCtx,
|
|
7006
|
-
txCtx: mutationTxCtx
|
|
7235
|
+
txCtx: mutationTxCtx,
|
|
7236
|
+
policies,
|
|
7237
|
+
returnsRows: false
|
|
7007
7238
|
}) : void 0;
|
|
7008
7239
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7009
7240
|
const withMeta = (generated, meta) => ({
|
|
@@ -7073,12 +7304,25 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7073
7304
|
{
|
|
7074
7305
|
generated: updateCountGenerated,
|
|
7075
7306
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7307
|
+
meta: {
|
|
7308
|
+
operation: "update",
|
|
7309
|
+
single: false,
|
|
7310
|
+
targetArg: "where",
|
|
7311
|
+
count: true
|
|
7312
|
+
},
|
|
7076
7313
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7077
7314
|
},
|
|
7078
7315
|
{
|
|
7079
7316
|
generated: deleteCountGenerated,
|
|
7080
7317
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7081
|
-
|
|
7318
|
+
meta: {
|
|
7319
|
+
operation: "delete",
|
|
7320
|
+
single: false,
|
|
7321
|
+
targetArg: "where",
|
|
7322
|
+
count: true,
|
|
7323
|
+
...hardDeleteMeta
|
|
7324
|
+
},
|
|
7325
|
+
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
7326
|
}
|
|
7083
7327
|
],
|
|
7084
7328
|
onConflictInput,
|
|
@@ -7150,7 +7394,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7150
7394
|
entries: nestedEntries,
|
|
7151
7395
|
remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
|
|
7152
7396
|
write: (tx, values) => runInsert(tx, [values]),
|
|
7153
|
-
|
|
7397
|
+
request: {
|
|
7398
|
+
context,
|
|
7399
|
+
args,
|
|
7400
|
+
info
|
|
7401
|
+
}
|
|
7154
7402
|
}) : await runInsert(executor, input);
|
|
7155
7403
|
await after(written);
|
|
7156
7404
|
const result = output(written);
|
|
@@ -7239,7 +7487,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7239
7487
|
entries: nestedEntries,
|
|
7240
7488
|
remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
|
|
7241
7489
|
write: (tx, values) => runUpsert(tx, [values]),
|
|
7242
|
-
|
|
7490
|
+
request: {
|
|
7491
|
+
context,
|
|
7492
|
+
args,
|
|
7493
|
+
info
|
|
7494
|
+
}
|
|
7243
7495
|
}) : await runUpsert(executor, input);
|
|
7244
7496
|
await after(written);
|
|
7245
7497
|
const result = output(written);
|
|
@@ -7312,7 +7564,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7312
7564
|
ops: nestedOps,
|
|
7313
7565
|
remapValues: (values) => values,
|
|
7314
7566
|
write: runUpdate,
|
|
7315
|
-
|
|
7567
|
+
request: {
|
|
7568
|
+
context,
|
|
7569
|
+
args,
|
|
7570
|
+
info
|
|
7571
|
+
}
|
|
7316
7572
|
}) : await runUpdate(executor, input);
|
|
7317
7573
|
await after(written);
|
|
7318
7574
|
const result = output(written);
|
|
@@ -7579,7 +7835,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7579
7835
|
requireWhere: tableFeatures.requireWhere,
|
|
7580
7836
|
filterCtx,
|
|
7581
7837
|
txCtx: mutationTxCtx,
|
|
7582
|
-
nested: nestedRuntime
|
|
7838
|
+
nested: nestedRuntime,
|
|
7839
|
+
policies,
|
|
7840
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7841
|
+
returnsRows: true
|
|
7583
7842
|
}) : void 0;
|
|
7584
7843
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
7585
7844
|
db,
|
|
@@ -7590,7 +7849,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7590
7849
|
fieldName: deleteCountFieldName,
|
|
7591
7850
|
requireWhere: tableFeatures.requireWhere,
|
|
7592
7851
|
filterCtx,
|
|
7593
|
-
txCtx: mutationTxCtx
|
|
7852
|
+
txCtx: mutationTxCtx,
|
|
7853
|
+
policies,
|
|
7854
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7855
|
+
returnsRows: true
|
|
7594
7856
|
}) : void 0;
|
|
7595
7857
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7596
7858
|
return {
|
|
@@ -7700,12 +7962,25 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7700
7962
|
{
|
|
7701
7963
|
generated: updateCountGenerated,
|
|
7702
7964
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7965
|
+
meta: {
|
|
7966
|
+
operation: "update",
|
|
7967
|
+
single: false,
|
|
7968
|
+
targetArg: "where",
|
|
7969
|
+
count: true
|
|
7970
|
+
},
|
|
7703
7971
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7704
7972
|
},
|
|
7705
7973
|
{
|
|
7706
7974
|
generated: deleteCountGenerated,
|
|
7707
7975
|
type: new GraphQLNonNull(GraphQLInt),
|
|
7708
|
-
|
|
7976
|
+
meta: {
|
|
7977
|
+
operation: "delete",
|
|
7978
|
+
single: false,
|
|
7979
|
+
targetArg: "where",
|
|
7980
|
+
count: true,
|
|
7981
|
+
...hardDeleteMeta
|
|
7982
|
+
},
|
|
7983
|
+
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
7984
|
}
|
|
7710
7985
|
],
|
|
7711
7986
|
onConflictInput,
|
|
@@ -7723,18 +7998,18 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7723
7998
|
* the shared multi-mutation transaction opened by `runMutation` — this opens a savepoint, so
|
|
7724
7999
|
* the batch stays atomic without breaking the outer transaction.
|
|
7725
8000
|
*/
|
|
7726
|
-
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName,
|
|
8001
|
+
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
|
|
7727
8002
|
const results = [];
|
|
7728
8003
|
for (const entry of entries) {
|
|
7729
8004
|
const values = entry.ops ? {
|
|
7730
8005
|
...entry.set,
|
|
7731
|
-
...await nested.applyParentSide(tx, tableName, entry.ops,
|
|
8006
|
+
...await nested.applyParentSide(tx, tableName, entry.ops, request)
|
|
7732
8007
|
} : entry.set;
|
|
7733
8008
|
const writes = Object.keys(values).length > 0;
|
|
7734
8009
|
let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
|
|
7735
8010
|
if (entry.filters) query = query.where(entry.filters);
|
|
7736
8011
|
const rows = await (writes ? query.returning(returning) : query);
|
|
7737
|
-
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows,
|
|
8012
|
+
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
|
|
7738
8013
|
results.push(rows);
|
|
7739
8014
|
}
|
|
7740
8015
|
return results;
|
|
@@ -7807,7 +8082,11 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7807
8082
|
anyNested,
|
|
7808
8083
|
nested,
|
|
7809
8084
|
tableName,
|
|
7810
|
-
|
|
8085
|
+
request: {
|
|
8086
|
+
context,
|
|
8087
|
+
args,
|
|
8088
|
+
info
|
|
8089
|
+
}
|
|
7811
8090
|
});
|
|
7812
8091
|
const written = perEntry.flat();
|
|
7813
8092
|
await after(written);
|
|
@@ -7856,7 +8135,7 @@ const sqlitePrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(ta
|
|
|
7856
8135
|
* would run after the COMMIT. The driver kind is probed from the first statement's result
|
|
7857
8136
|
* rather than declared, so the same resolver serves both driver families.
|
|
7858
8137
|
*/
|
|
7859
|
-
const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNested, nested, tableName,
|
|
8138
|
+
const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNested, nested, tableName, request }) => {
|
|
7860
8139
|
const runEntry = (tx, entry) => {
|
|
7861
8140
|
let query = tx.update(table).set(entry.set);
|
|
7862
8141
|
if (entry.filters) query = query.where(entry.filters);
|
|
@@ -7865,13 +8144,13 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
|
|
|
7865
8144
|
const runNestedEntry = async (tx, entry) => {
|
|
7866
8145
|
const values = entry.ops ? {
|
|
7867
8146
|
...entry.set,
|
|
7868
|
-
...await nested.applyParentSide(tx, tableName, entry.ops,
|
|
8147
|
+
...await nested.applyParentSide(tx, tableName, entry.ops, request)
|
|
7869
8148
|
} : entry.set;
|
|
7870
8149
|
const writes = Object.keys(values).length > 0;
|
|
7871
8150
|
let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
|
|
7872
8151
|
if (entry.filters) query = query.where(entry.filters);
|
|
7873
8152
|
const rows = await (writes ? query.returning(returning) : query);
|
|
7874
|
-
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows,
|
|
8153
|
+
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
|
|
7875
8154
|
return rows;
|
|
7876
8155
|
};
|
|
7877
8156
|
return executor.transaction((tx) => {
|