@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.cjs
CHANGED
|
@@ -3748,6 +3748,145 @@ const runMutation = async (db, context, info, txCtx, body, forceTx) => {
|
|
|
3748
3748
|
return runOnSharedTx(state, body);
|
|
3749
3749
|
};
|
|
3750
3750
|
//#endregion
|
|
3751
|
+
//#region src/util/builders/common/write-hooks.ts
|
|
3752
|
+
/**
|
|
3753
|
+
* Normalizes the two registration shapes: a bare function is the `after` hook — the position
|
|
3754
|
+
* that has rows, and the one the common audit/outbox case wants — while `{ before, after }`
|
|
3755
|
+
* names them explicitly.
|
|
3756
|
+
*/
|
|
3757
|
+
const normalizeWriteHooks = (hooks, tableColumns, where) => {
|
|
3758
|
+
if (!hooks) return;
|
|
3759
|
+
if (typeof hooks === "function") return { after: hooks };
|
|
3760
|
+
const resolved = {
|
|
3761
|
+
before: hooks.before,
|
|
3762
|
+
after: hooks.after
|
|
3763
|
+
};
|
|
3764
|
+
if (hooks.columns !== void 0) resolved.columns = resolveHookColumns(hooks.columns, !!hooks.after, tableColumns, where);
|
|
3765
|
+
return resolved.before || resolved.after ? resolved : void 0;
|
|
3766
|
+
};
|
|
3767
|
+
/**
|
|
3768
|
+
* Resolves a hook's `columns` against the table it runs on. `where` names the registration
|
|
3769
|
+
* (`config.onWrite.posts`) and, for a top-level one, the table, so the error says which.
|
|
3770
|
+
*/
|
|
3771
|
+
const resolveHookColumns = (spec, hasAfter, tableColumns, where) => {
|
|
3772
|
+
if (!hasAfter) throw new Error(`Drizzle-GraphQL Error: ${where}.columns only applies to an 'after' hook, and none is registered.`);
|
|
3773
|
+
if (spec === "all") return { ...tableColumns };
|
|
3774
|
+
if (!Array.isArray(spec)) throw new Error(`Drizzle-GraphQL Error: ${where}.columns must be an array of column names or 'all'.`);
|
|
3775
|
+
const resolved = {};
|
|
3776
|
+
for (const name of spec) {
|
|
3777
|
+
const column = typeof name === "string" && Object.keys(tableColumns).includes(name) ? tableColumns[name] : void 0;
|
|
3778
|
+
if (!column) throw new Error(`Drizzle-GraphQL Error: ${where}.columns names '${String(name)}', which is not a column of that table.`);
|
|
3779
|
+
resolved[name] = column;
|
|
3780
|
+
}
|
|
3781
|
+
return resolved;
|
|
3782
|
+
};
|
|
3783
|
+
/**
|
|
3784
|
+
* Adds a hook's `columns` to a RETURNING column map, and reports which keys it added — the
|
|
3785
|
+
* ones the client never asked for, which have to come back off before the rows are mapped.
|
|
3786
|
+
* Returns the map untouched, and no keys, when the hook named none or the selection already
|
|
3787
|
+
* covers them.
|
|
3788
|
+
*/
|
|
3789
|
+
const withHookColumns = (hooks, columns) => {
|
|
3790
|
+
const hookColumns = hooks?.columns;
|
|
3791
|
+
if (!hookColumns) return {
|
|
3792
|
+
returning: columns,
|
|
3793
|
+
added: void 0
|
|
3794
|
+
};
|
|
3795
|
+
const selected = new Set(Object.keys(columns));
|
|
3796
|
+
const added = Object.keys(hookColumns).filter((key) => !selected.has(key));
|
|
3797
|
+
if (!added.length) return {
|
|
3798
|
+
returning: columns,
|
|
3799
|
+
added: void 0
|
|
3800
|
+
};
|
|
3801
|
+
const returning = { ...columns };
|
|
3802
|
+
for (const key of added) returning[key] = hookColumns[key];
|
|
3803
|
+
return {
|
|
3804
|
+
returning,
|
|
3805
|
+
added
|
|
3806
|
+
};
|
|
3807
|
+
};
|
|
3808
|
+
/**
|
|
3809
|
+
* The rows with the hook-only keys taken off, as fresh objects: the hook was handed the
|
|
3810
|
+
* originals and may still hold them, so they are not mutated under it.
|
|
3811
|
+
*/
|
|
3812
|
+
const omitHookColumns = (rows, added) => {
|
|
3813
|
+
if (!added) return rows;
|
|
3814
|
+
return rows.map((row) => {
|
|
3815
|
+
const copy = { ...row };
|
|
3816
|
+
for (const key of added) delete copy[key];
|
|
3817
|
+
return copy;
|
|
3818
|
+
});
|
|
3819
|
+
};
|
|
3820
|
+
/**
|
|
3821
|
+
* Invokes one position of a field's hooks. Returns `undefined` rather than a resolved promise
|
|
3822
|
+
* when nothing is registered, so the awaiting call site costs nothing on an unhooked build.
|
|
3823
|
+
*/
|
|
3824
|
+
const runWriteHook = (hooks, position, payload) => {
|
|
3825
|
+
const hook = position === "before" ? hooks?.before : hooks?.after;
|
|
3826
|
+
if (!hook) return;
|
|
3827
|
+
return Promise.resolve(hook({
|
|
3828
|
+
...payload,
|
|
3829
|
+
position,
|
|
3830
|
+
rows: payload.rows ?? []
|
|
3831
|
+
}));
|
|
3832
|
+
};
|
|
3833
|
+
//#endregion
|
|
3834
|
+
//#region src/util/builders/common/write-scaffold.ts
|
|
3835
|
+
/**
|
|
3836
|
+
* Wraps one mutation body in the scaffolding above and returns the field it becomes.
|
|
3837
|
+
*
|
|
3838
|
+
* A field carrying a hook is forced into a transaction even when it would have run straight
|
|
3839
|
+
* on `db`: a hook exists to write atomically with the mutation, and one that ran outside the
|
|
3840
|
+
* transaction could not roll anything back.
|
|
3841
|
+
*/
|
|
3842
|
+
const writeResolver = ({ db, tableName, operation, single, fieldName, args: queryArgs, txCtx, policies, run }) => {
|
|
3843
|
+
const hooks = policies?.onWrite?.(tableName, operation);
|
|
3844
|
+
const errorCtx = {
|
|
3845
|
+
table: tableName,
|
|
3846
|
+
operation,
|
|
3847
|
+
field: fieldName
|
|
3848
|
+
};
|
|
3849
|
+
return {
|
|
3850
|
+
name: fieldName,
|
|
3851
|
+
resolver: async (_source, args, context, info) => {
|
|
3852
|
+
try {
|
|
3853
|
+
return await runMutation(db, context, info, txCtx, (executor) => {
|
|
3854
|
+
const payload = {
|
|
3855
|
+
table: tableName,
|
|
3856
|
+
operation,
|
|
3857
|
+
single,
|
|
3858
|
+
args,
|
|
3859
|
+
context,
|
|
3860
|
+
info,
|
|
3861
|
+
tx: executor
|
|
3862
|
+
};
|
|
3863
|
+
let added;
|
|
3864
|
+
return run({
|
|
3865
|
+
executor,
|
|
3866
|
+
args,
|
|
3867
|
+
context,
|
|
3868
|
+
info,
|
|
3869
|
+
before: () => runWriteHook(hooks, "before", payload),
|
|
3870
|
+
after: (rows) => runWriteHook(hooks, "after", {
|
|
3871
|
+
...payload,
|
|
3872
|
+
rows: rows ?? []
|
|
3873
|
+
}),
|
|
3874
|
+
returning: (columns) => {
|
|
3875
|
+
const withHook = withHookColumns(hooks, columns);
|
|
3876
|
+
added = withHook.added;
|
|
3877
|
+
return withHook.returning;
|
|
3878
|
+
},
|
|
3879
|
+
output: (rows) => omitHookColumns(rows, added)
|
|
3880
|
+
});
|
|
3881
|
+
}, !!hooks);
|
|
3882
|
+
} catch (e) {
|
|
3883
|
+
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
3884
|
+
}
|
|
3885
|
+
},
|
|
3886
|
+
args: queryArgs
|
|
3887
|
+
};
|
|
3888
|
+
};
|
|
3889
|
+
//#endregion
|
|
3751
3890
|
//#region src/util/builders/common/mutation-helpers.ts
|
|
3752
3891
|
/**
|
|
3753
3892
|
* The `hard` argument on the delete mutations of a soft-deleting table that opted into
|
|
@@ -3891,54 +4030,79 @@ const assertSingleMatch = async (executor, table, filters) => {
|
|
|
3891
4030
|
* The plural `update` and `delete` always `RETURNING *`, which is the wrong shape for
|
|
3892
4031
|
* "archive everything older than a year": the caller pays to ship rows it does not want, and
|
|
3893
4032
|
* on a large match that cost is the whole mutation. These return `Int!` instead — how many
|
|
3894
|
-
* rows the write touched
|
|
4033
|
+
* rows the write touched.
|
|
3895
4034
|
*
|
|
3896
|
-
*
|
|
3897
|
-
*
|
|
3898
|
-
*
|
|
3899
|
-
|
|
3900
|
-
|
|
4035
|
+
* Leaving the payload off changes nothing else: the write obeys every rule of the plural
|
|
4036
|
+
* mutation it mirrors, through the same {@link writeResolver} scaffolding.
|
|
4037
|
+
*
|
|
4038
|
+
* - `scope` (and the soft-delete marker) is ANDed into the `where`, so only rows the plural
|
|
4039
|
+
* write could reach are counted and written.
|
|
4040
|
+
* - On a soft-deleting table the delete is the marking `UPDATE`, not a `DELETE`, and rows that
|
|
4041
|
+
* are already marked are not matched; the update skips marked rows too. `hard: true` exists
|
|
4042
|
+
* exactly where the plural delete takes it.
|
|
4043
|
+
* - Context-derived columns are stripped from the update's `set`, as the plural update does.
|
|
4044
|
+
* - `onWrite` hooks run `before` and `after`, inside a transaction. The `after` hook is handed
|
|
4045
|
+
* rows, so a table with one pays for a `RETURNING` — of the columns the hook names in
|
|
4046
|
+
* `columns`, or of the primary key when it names none (the whole row on a table without a
|
|
4047
|
+
* key) — and the count is the number of rows returned. Every other write, and every write on
|
|
4048
|
+
* MySQL (no `RETURNING`; its hooks see `rows: []` as on every MySQL mutation), skips the
|
|
4049
|
+
* `RETURNING` and reads the count off the driver's result via {@link rowsAffected}.
|
|
4050
|
+
*/
|
|
4051
|
+
const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, fieldName, requireWhere, filterCtx, txCtx, nested, policies, pkNames = [], returnsRows }) => {
|
|
4052
|
+
const softDelete = kind === "delete" ? policies?.softDelete?.(tableName) : void 0;
|
|
4053
|
+
const canHardDelete = softDelete?.hardDelete === true;
|
|
3901
4054
|
const queryArgs = {
|
|
3902
4055
|
...setArgs ? { set: { type: new graphql.GraphQLNonNull(setArgs) } } : {},
|
|
3903
|
-
where: { type: requireWhere ? new graphql.GraphQLNonNull(filterArgs) : filterArgs }
|
|
4056
|
+
where: { type: requireWhere ? new graphql.GraphQLNonNull(filterArgs) : filterArgs },
|
|
4057
|
+
...canHardDelete ? { hard: hardDeleteArg } : {}
|
|
3904
4058
|
};
|
|
3905
|
-
const
|
|
3906
|
-
|
|
4059
|
+
const readsRows = returnsRows && !!policies?.onWrite?.(tableName, kind)?.after;
|
|
4060
|
+
const allColumns = (0, drizzle_orm.getColumns)(table);
|
|
4061
|
+
const keyColumns = pkNames.length ? Object.fromEntries(pkNames.map((name) => [name, allColumns[name]])) : allColumns;
|
|
4062
|
+
return writeResolver({
|
|
4063
|
+
db,
|
|
4064
|
+
tableName,
|
|
3907
4065
|
operation: kind,
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
else {
|
|
3927
|
-
const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
|
|
3928
|
-
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" });
|
|
3929
|
-
const values = remapUpdateInput(entry ? entry.columns : set, table, tableName);
|
|
3930
|
-
if (!Object.keys(values).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
3931
|
-
query = executor.update(table).set(values);
|
|
3932
|
-
}
|
|
3933
|
-
if (filters) query = query.where(filters);
|
|
3934
|
-
return rowsAffected(await query);
|
|
3935
|
-
});
|
|
3936
|
-
} catch (e) {
|
|
3937
|
-
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
4066
|
+
single: false,
|
|
4067
|
+
fieldName,
|
|
4068
|
+
txCtx,
|
|
4069
|
+
policies,
|
|
4070
|
+
args: queryArgs,
|
|
4071
|
+
run: async ({ executor, args, context, before, after, returning }) => {
|
|
4072
|
+
const { where, set } = args;
|
|
4073
|
+
const hard = canHardDelete && args.hard === true;
|
|
4074
|
+
const scope = policies?.scope?.(context);
|
|
4075
|
+
await before();
|
|
4076
|
+
let query;
|
|
4077
|
+
if (kind === "delete") query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: softDelete.writeDeleted() }) : executor.delete(table);
|
|
4078
|
+
else {
|
|
4079
|
+
const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
|
|
4080
|
+
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" });
|
|
4081
|
+
const values = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
4082
|
+
if (!Object.keys(values).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
4083
|
+
query = executor.update(table).set(values);
|
|
3938
4084
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
4085
|
+
const filters = scopedWhere({
|
|
4086
|
+
scope,
|
|
4087
|
+
tableName,
|
|
4088
|
+
table,
|
|
4089
|
+
where,
|
|
4090
|
+
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
4091
|
+
required: requireWhere,
|
|
4092
|
+
deleted: hard ? "INCLUDE" : void 0
|
|
4093
|
+
});
|
|
4094
|
+
if (filters) query = query.where(filters);
|
|
4095
|
+
if (!readsRows) {
|
|
4096
|
+
const result = await query;
|
|
4097
|
+
await after();
|
|
4098
|
+
return rowsAffected(result);
|
|
4099
|
+
}
|
|
4100
|
+
const named = returning({});
|
|
4101
|
+
const rows = await query.returning(Object.keys(named).length ? named : keyColumns);
|
|
4102
|
+
await after(rows);
|
|
4103
|
+
return rows.length;
|
|
4104
|
+
}
|
|
4105
|
+
});
|
|
3942
4106
|
};
|
|
3943
4107
|
//#endregion
|
|
3944
4108
|
//#region src/util/batch-loader/index.ts
|
|
@@ -4876,145 +5040,6 @@ const generateTableTypes = (tableName, tables, relationMap, withReturning, relat
|
|
|
4876
5040
|
};
|
|
4877
5041
|
};
|
|
4878
5042
|
//#endregion
|
|
4879
|
-
//#region src/util/builders/common/write-hooks.ts
|
|
4880
|
-
/**
|
|
4881
|
-
* Normalizes the two registration shapes: a bare function is the `after` hook — the position
|
|
4882
|
-
* that has rows, and the one the common audit/outbox case wants — while `{ before, after }`
|
|
4883
|
-
* names them explicitly.
|
|
4884
|
-
*/
|
|
4885
|
-
const normalizeWriteHooks = (hooks, tableColumns, where) => {
|
|
4886
|
-
if (!hooks) return;
|
|
4887
|
-
if (typeof hooks === "function") return { after: hooks };
|
|
4888
|
-
const resolved = {
|
|
4889
|
-
before: hooks.before,
|
|
4890
|
-
after: hooks.after
|
|
4891
|
-
};
|
|
4892
|
-
if (hooks.columns !== void 0) resolved.columns = resolveHookColumns(hooks.columns, !!hooks.after, tableColumns, where);
|
|
4893
|
-
return resolved.before || resolved.after ? resolved : void 0;
|
|
4894
|
-
};
|
|
4895
|
-
/**
|
|
4896
|
-
* Resolves a hook's `columns` against the table it runs on. `where` names the registration
|
|
4897
|
-
* (`config.onWrite.posts`) and, for a top-level one, the table, so the error says which.
|
|
4898
|
-
*/
|
|
4899
|
-
const resolveHookColumns = (spec, hasAfter, tableColumns, where) => {
|
|
4900
|
-
if (!hasAfter) throw new Error(`Drizzle-GraphQL Error: ${where}.columns only applies to an 'after' hook, and none is registered.`);
|
|
4901
|
-
if (spec === "all") return { ...tableColumns };
|
|
4902
|
-
if (!Array.isArray(spec)) throw new Error(`Drizzle-GraphQL Error: ${where}.columns must be an array of column names or 'all'.`);
|
|
4903
|
-
const resolved = {};
|
|
4904
|
-
for (const name of spec) {
|
|
4905
|
-
const column = typeof name === "string" && Object.keys(tableColumns).includes(name) ? tableColumns[name] : void 0;
|
|
4906
|
-
if (!column) throw new Error(`Drizzle-GraphQL Error: ${where}.columns names '${String(name)}', which is not a column of that table.`);
|
|
4907
|
-
resolved[name] = column;
|
|
4908
|
-
}
|
|
4909
|
-
return resolved;
|
|
4910
|
-
};
|
|
4911
|
-
/**
|
|
4912
|
-
* Adds a hook's `columns` to a RETURNING column map, and reports which keys it added — the
|
|
4913
|
-
* ones the client never asked for, which have to come back off before the rows are mapped.
|
|
4914
|
-
* Returns the map untouched, and no keys, when the hook named none or the selection already
|
|
4915
|
-
* covers them.
|
|
4916
|
-
*/
|
|
4917
|
-
const withHookColumns = (hooks, columns) => {
|
|
4918
|
-
const hookColumns = hooks?.columns;
|
|
4919
|
-
if (!hookColumns) return {
|
|
4920
|
-
returning: columns,
|
|
4921
|
-
added: void 0
|
|
4922
|
-
};
|
|
4923
|
-
const selected = new Set(Object.keys(columns));
|
|
4924
|
-
const added = Object.keys(hookColumns).filter((key) => !selected.has(key));
|
|
4925
|
-
if (!added.length) return {
|
|
4926
|
-
returning: columns,
|
|
4927
|
-
added: void 0
|
|
4928
|
-
};
|
|
4929
|
-
const returning = { ...columns };
|
|
4930
|
-
for (const key of added) returning[key] = hookColumns[key];
|
|
4931
|
-
return {
|
|
4932
|
-
returning,
|
|
4933
|
-
added
|
|
4934
|
-
};
|
|
4935
|
-
};
|
|
4936
|
-
/**
|
|
4937
|
-
* The rows with the hook-only keys taken off, as fresh objects: the hook was handed the
|
|
4938
|
-
* originals and may still hold them, so they are not mutated under it.
|
|
4939
|
-
*/
|
|
4940
|
-
const omitHookColumns = (rows, added) => {
|
|
4941
|
-
if (!added) return rows;
|
|
4942
|
-
return rows.map((row) => {
|
|
4943
|
-
const copy = { ...row };
|
|
4944
|
-
for (const key of added) delete copy[key];
|
|
4945
|
-
return copy;
|
|
4946
|
-
});
|
|
4947
|
-
};
|
|
4948
|
-
/**
|
|
4949
|
-
* Invokes one position of a field's hooks. Returns `undefined` rather than a resolved promise
|
|
4950
|
-
* when nothing is registered, so the awaiting call site costs nothing on an unhooked build.
|
|
4951
|
-
*/
|
|
4952
|
-
const runWriteHook = (hooks, position, payload) => {
|
|
4953
|
-
const hook = position === "before" ? hooks?.before : hooks?.after;
|
|
4954
|
-
if (!hook) return;
|
|
4955
|
-
return Promise.resolve(hook({
|
|
4956
|
-
...payload,
|
|
4957
|
-
position,
|
|
4958
|
-
rows: payload.rows ?? []
|
|
4959
|
-
}));
|
|
4960
|
-
};
|
|
4961
|
-
//#endregion
|
|
4962
|
-
//#region src/util/builders/common/write-scaffold.ts
|
|
4963
|
-
/**
|
|
4964
|
-
* Wraps one mutation body in the scaffolding above and returns the field it becomes.
|
|
4965
|
-
*
|
|
4966
|
-
* A field carrying a hook is forced into a transaction even when it would have run straight
|
|
4967
|
-
* on `db`: a hook exists to write atomically with the mutation, and one that ran outside the
|
|
4968
|
-
* transaction could not roll anything back.
|
|
4969
|
-
*/
|
|
4970
|
-
const writeResolver = ({ db, tableName, operation, single, fieldName, args: queryArgs, txCtx, policies, run }) => {
|
|
4971
|
-
const hooks = policies?.onWrite?.(tableName, operation);
|
|
4972
|
-
const errorCtx = {
|
|
4973
|
-
table: tableName,
|
|
4974
|
-
operation,
|
|
4975
|
-
field: fieldName
|
|
4976
|
-
};
|
|
4977
|
-
return {
|
|
4978
|
-
name: fieldName,
|
|
4979
|
-
resolver: async (_source, args, context, info) => {
|
|
4980
|
-
try {
|
|
4981
|
-
return await runMutation(db, context, info, txCtx, (executor) => {
|
|
4982
|
-
const payload = {
|
|
4983
|
-
table: tableName,
|
|
4984
|
-
operation,
|
|
4985
|
-
single,
|
|
4986
|
-
args,
|
|
4987
|
-
context,
|
|
4988
|
-
info,
|
|
4989
|
-
tx: executor
|
|
4990
|
-
};
|
|
4991
|
-
let added;
|
|
4992
|
-
return run({
|
|
4993
|
-
executor,
|
|
4994
|
-
args,
|
|
4995
|
-
context,
|
|
4996
|
-
info,
|
|
4997
|
-
before: () => runWriteHook(hooks, "before", payload),
|
|
4998
|
-
after: (rows) => runWriteHook(hooks, "after", {
|
|
4999
|
-
...payload,
|
|
5000
|
-
rows: rows ?? []
|
|
5001
|
-
}),
|
|
5002
|
-
returning: (columns) => {
|
|
5003
|
-
const withHook = withHookColumns(hooks, columns);
|
|
5004
|
-
added = withHook.added;
|
|
5005
|
-
return withHook.returning;
|
|
5006
|
-
},
|
|
5007
|
-
output: (rows) => omitHookColumns(rows, added)
|
|
5008
|
-
});
|
|
5009
|
-
}, !!hooks);
|
|
5010
|
-
} catch (e) {
|
|
5011
|
-
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
5012
|
-
}
|
|
5013
|
-
},
|
|
5014
|
-
args: queryArgs
|
|
5015
|
-
};
|
|
5016
|
-
};
|
|
5017
|
-
//#endregion
|
|
5018
5043
|
//#region src/util/features.ts
|
|
5019
5044
|
/**
|
|
5020
5045
|
* What a table generates when the config says nothing. Everything is on except the opt-in
|
|
@@ -5226,7 +5251,6 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5226
5251
|
const missing = [!tableFeatures.insert ? "insert" : void 0, !tableFeatures.update ? "update" : void 0].filter(Boolean).join(" and ");
|
|
5227
5252
|
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);
|
|
5228
5253
|
}
|
|
5229
|
-
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);
|
|
5230
5254
|
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);
|
|
5231
5255
|
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);
|
|
5232
5256
|
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);
|
|
@@ -5832,6 +5856,12 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
5832
5856
|
};
|
|
5833
5857
|
//#endregion
|
|
5834
5858
|
//#region src/util/builders/nested-writes.ts
|
|
5859
|
+
const ALL_OPS = [
|
|
5860
|
+
"create",
|
|
5861
|
+
"connect",
|
|
5862
|
+
"disconnect",
|
|
5863
|
+
"set"
|
|
5864
|
+
];
|
|
5835
5865
|
/**
|
|
5836
5866
|
* Whether a column can be left out of an INSERT — either the database fills it in or it
|
|
5837
5867
|
* accepts NULL.
|
|
@@ -5860,7 +5890,7 @@ const throughColumn = (entry) => entry && (0, drizzle_orm.is)(entry, drizzle_orm
|
|
|
5860
5890
|
* joining on more than one column per side; and a junction carrying a further NOT NULL column
|
|
5861
5891
|
* with no default, which no `connect` operand could supply a value for.
|
|
5862
5892
|
*/
|
|
5863
|
-
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable) => {
|
|
5893
|
+
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf) => {
|
|
5864
5894
|
if ((0, drizzle_orm.is)(relation, drizzle_orm.One)) return;
|
|
5865
5895
|
const junction = relation.throughTable;
|
|
5866
5896
|
const sourceEntries = relation.through?.source;
|
|
@@ -5891,11 +5921,18 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
5891
5921
|
canDetach: true,
|
|
5892
5922
|
through: {
|
|
5893
5923
|
table: junction,
|
|
5924
|
+
tableName: tableNameOf(junction),
|
|
5894
5925
|
sourceColPropName,
|
|
5895
5926
|
targetColPropName,
|
|
5896
5927
|
sourceCol,
|
|
5897
5928
|
targetCol
|
|
5898
|
-
}
|
|
5929
|
+
},
|
|
5930
|
+
targetKeyColumns: { [foreignColPropName]: foreignCol },
|
|
5931
|
+
allowed: /* @__PURE__ */ new Set([
|
|
5932
|
+
"connect",
|
|
5933
|
+
"disconnect",
|
|
5934
|
+
"set"
|
|
5935
|
+
])
|
|
5899
5936
|
};
|
|
5900
5937
|
};
|
|
5901
5938
|
/**
|
|
@@ -5908,7 +5945,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
5908
5945
|
* relations whose join columns cannot be read off the drizzle relation; and relations where
|
|
5909
5946
|
* neither join column is unique, which leaves no way to tell which side stores the key.
|
|
5910
5947
|
*/
|
|
5911
|
-
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns) => {
|
|
5948
|
+
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
|
|
5912
5949
|
const uniqueCache = /* @__PURE__ */ new Map();
|
|
5913
5950
|
const singleColumnUniques = (tableName, table) => {
|
|
5914
5951
|
let cached = uniqueCache.get(tableName);
|
|
@@ -5937,7 +5974,7 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5937
5974
|
const targetTable = tables[targetTableName];
|
|
5938
5975
|
if (!targetTable) continue;
|
|
5939
5976
|
if (relation.through) {
|
|
5940
|
-
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable);
|
|
5977
|
+
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf);
|
|
5941
5978
|
if (throughPlan) tablePlans[relationName] = throughPlan;
|
|
5942
5979
|
continue;
|
|
5943
5980
|
}
|
|
@@ -5958,6 +5995,10 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5958
5995
|
else if (foreignIsUnique) fkSide = "parent";
|
|
5959
5996
|
else if (localIsUnique) fkSide = "child";
|
|
5960
5997
|
else continue;
|
|
5998
|
+
const fkColumn = fkSide === "child" ? foreignCol : localCol;
|
|
5999
|
+
const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
|
|
6000
|
+
const targetKeyColumns = Object.fromEntries(resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]).map((name) => [name, targetColumns[name]]));
|
|
6001
|
+
targetKeyColumns[foreignColPropName] = foreignCol;
|
|
5961
6002
|
tablePlans[relationName] = {
|
|
5962
6003
|
relationName,
|
|
5963
6004
|
targetTableName,
|
|
@@ -5968,13 +6009,87 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5968
6009
|
localCol,
|
|
5969
6010
|
isOne,
|
|
5970
6011
|
fkSide,
|
|
5971
|
-
canDetach: !
|
|
6012
|
+
canDetach: !fkColumn.notNull,
|
|
6013
|
+
targetKeyColumns,
|
|
6014
|
+
allowed: new Set(!fkColumn.notNull ? isOne ? [
|
|
6015
|
+
"create",
|
|
6016
|
+
"connect",
|
|
6017
|
+
"disconnect"
|
|
6018
|
+
] : ALL_OPS : ["create", "connect"])
|
|
5972
6019
|
};
|
|
5973
6020
|
}
|
|
5974
6021
|
if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
|
|
5975
6022
|
}
|
|
5976
6023
|
return plans;
|
|
5977
6024
|
};
|
|
6025
|
+
/**
|
|
6026
|
+
* Which of the target's (or junction's) write features each input of a relation needs. A
|
|
6027
|
+
* parent-side `connect` / `disconnect` only rewrites the parent's own column, so it needs
|
|
6028
|
+
* nothing of the target; a child-side one moves the target's foreign key, which is an update
|
|
6029
|
+
* of the target. A to-one child-side `create` also detaches the row attached before it, when
|
|
6030
|
+
* the key is nullable.
|
|
6031
|
+
*/
|
|
6032
|
+
const requiredWrites = (plan, op) => {
|
|
6033
|
+
if (plan.through) {
|
|
6034
|
+
const junction = plan.through.tableName;
|
|
6035
|
+
if (op === "connect") return [{
|
|
6036
|
+
tableName: junction,
|
|
6037
|
+
feature: "insert"
|
|
6038
|
+
}];
|
|
6039
|
+
if (op === "disconnect") return [{
|
|
6040
|
+
tableName: junction,
|
|
6041
|
+
feature: "delete"
|
|
6042
|
+
}];
|
|
6043
|
+
if (op === "set") return [{
|
|
6044
|
+
tableName: junction,
|
|
6045
|
+
feature: "delete"
|
|
6046
|
+
}, {
|
|
6047
|
+
tableName: junction,
|
|
6048
|
+
feature: "insert"
|
|
6049
|
+
}];
|
|
6050
|
+
return [];
|
|
6051
|
+
}
|
|
6052
|
+
const target = plan.targetTableName;
|
|
6053
|
+
if (op === "create") return plan.fkSide === "child" && plan.isOne && plan.canDetach ? [{
|
|
6054
|
+
tableName: target,
|
|
6055
|
+
feature: "insert"
|
|
6056
|
+
}, {
|
|
6057
|
+
tableName: target,
|
|
6058
|
+
feature: "update"
|
|
6059
|
+
}] : [{
|
|
6060
|
+
tableName: target,
|
|
6061
|
+
feature: "insert"
|
|
6062
|
+
}];
|
|
6063
|
+
return plan.fkSide === "child" ? [{
|
|
6064
|
+
tableName: target,
|
|
6065
|
+
feature: "update"
|
|
6066
|
+
}] : [];
|
|
6067
|
+
};
|
|
6068
|
+
/**
|
|
6069
|
+
* Narrows every plan's `allowed` inputs to the ones whose writes the table they land on
|
|
6070
|
+
* permits: its `features.insert` / `update` / `delete`, and for a junction table that it is
|
|
6071
|
+
* not excluded. A plan left with nothing is dropped, so its relation field is not generated.
|
|
6072
|
+
*
|
|
6073
|
+
* Returns one line per input taken out, for the build note.
|
|
6074
|
+
*/
|
|
6075
|
+
const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
|
|
6076
|
+
const omitted = [];
|
|
6077
|
+
for (const [tableName, tablePlans] of Object.entries(plans)) {
|
|
6078
|
+
for (const [relationName, plan] of Object.entries(tablePlans)) {
|
|
6079
|
+
for (const op of [...plan.allowed]) {
|
|
6080
|
+
const blocked = requiredWrites(plan, op).filter(({ tableName: target, feature }) => target !== void 0 && (isExcluded(target) || !featureOf(target)[feature]));
|
|
6081
|
+
if (blocked.length) {
|
|
6082
|
+
plan.allowed.delete(op);
|
|
6083
|
+
const reasons = blocked.map(({ tableName: target, feature }) => isExcluded(target) ? `${target} is excluded` : `${target}.${feature} is off`);
|
|
6084
|
+
omitted.push(`${tableName}.${relationName}.${op} (${[...new Set(reasons)].join(", ")})`);
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
if (!plan.allowed.size) delete tablePlans[relationName];
|
|
6088
|
+
}
|
|
6089
|
+
if (!Object.keys(tablePlans).length) delete plans[tableName];
|
|
6090
|
+
}
|
|
6091
|
+
return omitted;
|
|
6092
|
+
};
|
|
5978
6093
|
const NO_COLUMNS = /* @__PURE__ */ new Set();
|
|
5979
6094
|
/**
|
|
5980
6095
|
* Builds the nested-write input types for a schema.
|
|
@@ -6029,8 +6144,11 @@ const createNestedWriteTypes = (params) => {
|
|
|
6029
6144
|
*
|
|
6030
6145
|
* A many-to-many relation offers no `create`: inserting a row *and* linking it is two
|
|
6031
6146
|
* writes to two tables, and the link half is what the relation field is for. Its
|
|
6032
|
-
* `disconnect` and `set`
|
|
6147
|
+
* `disconnect` and `set` do not depend on nullability, since breaking a link deletes a
|
|
6033
6148
|
* junction row rather than nulling a column that might be NOT NULL.
|
|
6149
|
+
*
|
|
6150
|
+
* Each input is also subject to `plan.allowed` — the table it writes has to have that
|
|
6151
|
+
* write turned on (see {@link gateNestedWritePlans}).
|
|
6034
6152
|
*/
|
|
6035
6153
|
const wrapperType = (tableName, typeName, plan, forUpdate) => {
|
|
6036
6154
|
const key = `${tableName}.${plan.relationName}.${forUpdate ? "update" : "create"}`;
|
|
@@ -6039,57 +6157,52 @@ const createNestedWriteTypes = (params) => {
|
|
|
6039
6157
|
const filters = targetFilters(plan);
|
|
6040
6158
|
const targetTypeName = resolveTypeName(plan.targetTableName, typeNameMapper);
|
|
6041
6159
|
const fields = {};
|
|
6160
|
+
const offers = (op) => plan.allowed.has(op);
|
|
6042
6161
|
if (plan.through) {
|
|
6043
|
-
if (
|
|
6044
|
-
fields["connect"] = {
|
|
6162
|
+
if (filters && offers("connect")) fields["connect"] = {
|
|
6045
6163
|
type: listOf(filters),
|
|
6046
6164
|
description: `Link every existing ${targetTypeName} row these match. Already-linked rows are left alone.`
|
|
6047
6165
|
};
|
|
6048
|
-
if (forUpdate) {
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
};
|
|
6057
|
-
}
|
|
6166
|
+
if (filters && forUpdate && offers("disconnect")) fields["disconnect"] = {
|
|
6167
|
+
type: listOf(filters),
|
|
6168
|
+
description: `Unlink every linked ${targetTypeName} row these match, leaving the rows in place`
|
|
6169
|
+
};
|
|
6170
|
+
if (filters && forUpdate && offers("set")) fields["set"] = {
|
|
6171
|
+
type: listOf(filters),
|
|
6172
|
+
description: `Replace the whole set: unlink everything linked, then link every ${targetTypeName} row these match. \`set: []\` clears the relation. Applied before \`disconnect\` and \`connect\`.`
|
|
6173
|
+
};
|
|
6058
6174
|
} else if (plan.isOne) {
|
|
6059
|
-
fields["create"] = {
|
|
6175
|
+
if (offers("create")) fields["create"] = {
|
|
6060
6176
|
type: payloadType(tableName, typeName, plan),
|
|
6061
6177
|
description: `Insert a ${targetTypeName} row and attach it`
|
|
6062
6178
|
};
|
|
6063
|
-
if (filters) fields["connect"] = {
|
|
6179
|
+
if (filters && offers("connect")) fields["connect"] = {
|
|
6064
6180
|
type: filters,
|
|
6065
6181
|
description: `Attach the existing ${targetTypeName} row this matches. Must match exactly one row.`
|
|
6066
6182
|
};
|
|
6067
|
-
if (forUpdate &&
|
|
6183
|
+
if (forUpdate && offers("disconnect")) fields["disconnect"] = {
|
|
6068
6184
|
type: graphql.GraphQLBoolean,
|
|
6069
6185
|
description: `Detach the currently attached ${targetTypeName} row, leaving it in place`
|
|
6070
6186
|
};
|
|
6071
6187
|
} else {
|
|
6072
|
-
fields["create"] = {
|
|
6188
|
+
if (offers("create")) fields["create"] = {
|
|
6073
6189
|
type: listOf(payloadType(tableName, typeName, plan)),
|
|
6074
6190
|
description: `Insert these ${targetTypeName} rows and attach them`
|
|
6075
6191
|
};
|
|
6076
|
-
if (filters) {
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
description: `Replace the whole set: detach everything attached, then attach every ${targetTypeName} row these match. Applied before \`disconnect\`, \`connect\` and \`create\`.`
|
|
6089
|
-
};
|
|
6090
|
-
}
|
|
6091
|
-
}
|
|
6192
|
+
if (filters && offers("connect")) fields["connect"] = {
|
|
6193
|
+
type: listOf(filters),
|
|
6194
|
+
description: `Attach every existing ${targetTypeName} row these match`
|
|
6195
|
+
};
|
|
6196
|
+
if (filters && forUpdate && offers("disconnect")) fields["disconnect"] = {
|
|
6197
|
+
type: listOf(filters),
|
|
6198
|
+
description: `Detach every attached ${targetTypeName} row these match, leaving the rows in place`
|
|
6199
|
+
};
|
|
6200
|
+
if (filters && forUpdate && offers("set")) fields["set"] = {
|
|
6201
|
+
type: listOf(filters),
|
|
6202
|
+
description: `Replace the whole set: detach everything attached, then attach every ${targetTypeName} row these match. Applied before \`disconnect\`, \`connect\` and \`create\`.`
|
|
6203
|
+
};
|
|
6092
6204
|
}
|
|
6205
|
+
if (!Object.keys(fields).length) return;
|
|
6093
6206
|
const type = new graphql.GraphQLInputObjectType({
|
|
6094
6207
|
name: cacheCtx.typeName({
|
|
6095
6208
|
kind: "nestedWriteInput",
|
|
@@ -6121,7 +6234,7 @@ const createNestedWriteTypes = (params) => {
|
|
|
6121
6234
|
const tablePlans = plans[tableName];
|
|
6122
6235
|
if (!tablePlans) return NO_COLUMNS;
|
|
6123
6236
|
const relaxed = /* @__PURE__ */ new Set();
|
|
6124
|
-
for (const plan of Object.values(tablePlans)) if (plan.fkSide === "parent" && !isOptionalOnInsert(plan.localCol)) relaxed.add(plan.localColPropName);
|
|
6237
|
+
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);
|
|
6125
6238
|
return relaxed.size ? relaxed : NO_COLUMNS;
|
|
6126
6239
|
}
|
|
6127
6240
|
};
|
|
@@ -6132,8 +6245,99 @@ const createNestedWriteTypes = (params) => {
|
|
|
6132
6245
|
*/
|
|
6133
6246
|
const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
|
|
6134
6247
|
const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
6248
|
+
const supplied = (value) => value !== void 0 && value !== null;
|
|
6249
|
+
/**
|
|
6250
|
+
* The hook calls one relation field makes. Every step is registered before the field's first
|
|
6251
|
+
* statement, so each `before` runs ahead of all of them and each `after` once the last has
|
|
6252
|
+
* returned. A table without hooks registers nothing, and its statements run exactly as they
|
|
6253
|
+
* did before hooks reached nested writes — no RETURNING clause is added.
|
|
6254
|
+
*/
|
|
6255
|
+
const createHookSteps = (onWrite, executor, request, parentTable, plan) => {
|
|
6256
|
+
const steps = /* @__PURE__ */ new Map();
|
|
6257
|
+
const keyOf = (op, operation) => `${op}:${operation}`;
|
|
6258
|
+
const payload = (step) => ({
|
|
6259
|
+
table: step.tableName,
|
|
6260
|
+
operation: step.operation,
|
|
6261
|
+
single: plan.isOne,
|
|
6262
|
+
args: request?.args,
|
|
6263
|
+
context: request?.context,
|
|
6264
|
+
info: request?.info,
|
|
6265
|
+
tx: executor,
|
|
6266
|
+
nested: {
|
|
6267
|
+
parentTable,
|
|
6268
|
+
relation: plan.relationName,
|
|
6269
|
+
op: step.op
|
|
6270
|
+
}
|
|
6271
|
+
});
|
|
6272
|
+
return {
|
|
6273
|
+
/** Registers the hook call for `op`'s `operation` statements, if that table has hooks. */
|
|
6274
|
+
add: (op, operation) => {
|
|
6275
|
+
const through = plan.through;
|
|
6276
|
+
const tableName = through ? through.tableName : plan.targetTableName;
|
|
6277
|
+
const hooks = tableName ? onWrite?.(tableName, operation) : void 0;
|
|
6278
|
+
if (!tableName || !hooks || steps.has(keyOf(op, operation))) return;
|
|
6279
|
+
const keyColumns = through ? {
|
|
6280
|
+
[through.sourceColPropName]: through.sourceCol,
|
|
6281
|
+
[through.targetColPropName]: through.targetCol
|
|
6282
|
+
} : { ...plan.targetKeyColumns };
|
|
6283
|
+
steps.set(keyOf(op, operation), {
|
|
6284
|
+
tableName,
|
|
6285
|
+
operation,
|
|
6286
|
+
op,
|
|
6287
|
+
hooks,
|
|
6288
|
+
returning: withHookColumns(hooks, keyColumns).returning,
|
|
6289
|
+
rows: []
|
|
6290
|
+
});
|
|
6291
|
+
},
|
|
6292
|
+
get: (op, operation) => steps.get(keyOf(op, operation)),
|
|
6293
|
+
/** Runs one statement, returning its rows to the step it belongs to when there is one. */
|
|
6294
|
+
run: async (query, op, operation) => {
|
|
6295
|
+
const step = steps.get(keyOf(op, operation));
|
|
6296
|
+
if (!step) {
|
|
6297
|
+
await query;
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6300
|
+
step.rows.push(...await query.returning(step.returning));
|
|
6301
|
+
},
|
|
6302
|
+
before: async () => {
|
|
6303
|
+
for (const step of steps.values()) await runWriteHook(step.hooks, "before", payload(step));
|
|
6304
|
+
},
|
|
6305
|
+
after: async () => {
|
|
6306
|
+
for (const step of steps.values()) await runWriteHook(step.hooks, "after", {
|
|
6307
|
+
...payload(step),
|
|
6308
|
+
rows: step.rows
|
|
6309
|
+
});
|
|
6310
|
+
}
|
|
6311
|
+
};
|
|
6312
|
+
};
|
|
6313
|
+
/**
|
|
6314
|
+
* Registers the hook calls a child-side relation field will make, in the order its
|
|
6315
|
+
* statements run, so the `before` hooks fire in that order too. Mirrors the branches of
|
|
6316
|
+
* `applyChildSide` and `applyThroughOps` exactly: a step registered for an input that runs no
|
|
6317
|
+
* statement would fire with no rows, and one missing would miss them.
|
|
6318
|
+
*/
|
|
6319
|
+
const registerChildSteps = (steps, plan, op) => {
|
|
6320
|
+
if (plan.through) {
|
|
6321
|
+
if (supplied(op["set"])) {
|
|
6322
|
+
steps.add("set", "delete");
|
|
6323
|
+
steps.add("set", "insert");
|
|
6324
|
+
}
|
|
6325
|
+
if (supplied(op["disconnect"])) steps.add("disconnect", "delete");
|
|
6326
|
+
if (supplied(op["connect"])) steps.add("connect", "insert");
|
|
6327
|
+
return;
|
|
6328
|
+
}
|
|
6329
|
+
if (op["set"] !== void 0) steps.add("set", "update");
|
|
6330
|
+
if (op["disconnect"] !== void 0 && (!plan.isOne || op["disconnect"] === true)) steps.add("disconnect", "update");
|
|
6331
|
+
const attachedBy = attachingOp(op);
|
|
6332
|
+
if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) steps.add(attachedBy, "update");
|
|
6333
|
+
if (op["connect"] !== void 0) steps.add("connect", "update");
|
|
6334
|
+
if (op["create"] !== void 0) steps.add("create", "insert");
|
|
6335
|
+
};
|
|
6336
|
+
/** The input that attaches a row to a to-one, which detaches whatever was attached before. */
|
|
6337
|
+
const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
|
|
6135
6338
|
const createNestedWriteRuntime = (params) => {
|
|
6136
6339
|
const { plans, filterCtx, policies, contextValues } = params;
|
|
6340
|
+
const onWrite = policies?.onWrite;
|
|
6137
6341
|
/**
|
|
6138
6342
|
* A `connect` / `disconnect` / `set` operand compiled to SQL. An operand that compiles to
|
|
6139
6343
|
* nothing is rejected: `connect: {}` would otherwise attach every row in the table.
|
|
@@ -6144,12 +6348,12 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6144
6348
|
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
6145
6349
|
};
|
|
6146
6350
|
const assertSingleOperation = (plan, op) => {
|
|
6147
|
-
const
|
|
6351
|
+
const given = [
|
|
6148
6352
|
"create",
|
|
6149
6353
|
"connect",
|
|
6150
6354
|
"disconnect"
|
|
6151
|
-
].filter((key) => op[key]
|
|
6152
|
-
if (
|
|
6355
|
+
].filter((key) => supplied(op[key]));
|
|
6356
|
+
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" });
|
|
6153
6357
|
};
|
|
6154
6358
|
/** The values the junction stores for the target rows one operand selects. */
|
|
6155
6359
|
const selectedTargetKeys = async (executor, plan, filter, operation, scope) => {
|
|
@@ -6164,9 +6368,8 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6164
6368
|
* error on some of them — the read and the write share the surrounding transaction, so no
|
|
6165
6369
|
* other writer can slip a duplicate in between them.
|
|
6166
6370
|
*/
|
|
6167
|
-
const applyThroughOps = async (executor, plan, key, op, scope) => {
|
|
6371
|
+
const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
|
|
6168
6372
|
const through = plan.through;
|
|
6169
|
-
const unlinkAll = () => executor.delete(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key));
|
|
6170
6373
|
const link = async (operands, operation) => {
|
|
6171
6374
|
const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
6172
6375
|
const rows = [];
|
|
@@ -6177,19 +6380,19 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6177
6380
|
[through.targetColPropName]: targetKey
|
|
6178
6381
|
});
|
|
6179
6382
|
}
|
|
6180
|
-
if (rows.length) await executor.insert(through.table).values(rows);
|
|
6383
|
+
if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
|
|
6181
6384
|
};
|
|
6182
|
-
if (op["set"]
|
|
6183
|
-
await
|
|
6385
|
+
if (supplied(op["set"])) {
|
|
6386
|
+
await steps.run(executor.delete(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key)), "set", "delete");
|
|
6184
6387
|
await link(asArray(op["set"]), "set");
|
|
6185
6388
|
}
|
|
6186
|
-
if (op["disconnect"]
|
|
6389
|
+
if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
|
|
6187
6390
|
const targetKeys = await selectedTargetKeys(executor, plan, filter, "disconnect", scope);
|
|
6188
|
-
if (targetKeys.length) await executor.delete(through.table).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(through.sourceCol, key), (0, drizzle_orm.inArray)(through.targetCol, targetKeys)));
|
|
6391
|
+
if (targetKeys.length) await steps.run(executor.delete(through.table).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(through.sourceCol, key), (0, drizzle_orm.inArray)(through.targetCol, targetKeys))), "disconnect", "delete");
|
|
6189
6392
|
}
|
|
6190
|
-
if (op["connect"]
|
|
6393
|
+
if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
|
|
6191
6394
|
};
|
|
6192
|
-
const detachAll =
|
|
6395
|
+
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, (0, drizzle_orm.eq)(plan.foreignCol, key))), op, "update");
|
|
6193
6396
|
return {
|
|
6194
6397
|
enabled: (tableName) => !!plans[tableName],
|
|
6195
6398
|
split: (tableName, values) => {
|
|
@@ -6218,19 +6421,27 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6218
6421
|
}
|
|
6219
6422
|
return columns;
|
|
6220
6423
|
},
|
|
6221
|
-
applyParentSide: async (executor, tableName, ops,
|
|
6424
|
+
applyParentSide: async (executor, tableName, ops, request) => {
|
|
6222
6425
|
const tablePlans = plans[tableName];
|
|
6223
6426
|
const patch = {};
|
|
6224
6427
|
if (!tablePlans) return patch;
|
|
6428
|
+
const context = request?.context;
|
|
6225
6429
|
const scope = resolveScope(policies, context, filterCtx);
|
|
6226
6430
|
for (const [relationName, op] of Object.entries(ops)) {
|
|
6227
6431
|
const plan = tablePlans[relationName];
|
|
6228
6432
|
if (plan?.fkSide !== "parent") continue;
|
|
6229
6433
|
assertSingleOperation(plan, op);
|
|
6230
6434
|
if (op["create"] !== void 0) {
|
|
6435
|
+
const steps = createHookSteps(onWrite, executor, request, tableName, plan);
|
|
6436
|
+
steps.add("create", "insert");
|
|
6437
|
+
await steps.before();
|
|
6231
6438
|
const values = applyContextValues(remapFromGraphQLSingleInput({ ...op["create"] }, plan.targetTable), contextValues?.(plan.targetTableName), context);
|
|
6232
|
-
const
|
|
6439
|
+
const step = steps.get("create", "insert");
|
|
6440
|
+
const inserted = await executor.insert(plan.targetTable).values(values).returning(step ? step.returning : { [plan.foreignColPropName]: plan.foreignCol });
|
|
6441
|
+
step?.rows.push(...inserted);
|
|
6442
|
+
const created = inserted[0];
|
|
6233
6443
|
if (!created) throw drizzleError(`Drizzle-GraphQL Error: 'create' on '${relationName}' inserted no row, so there is nothing to attach.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6444
|
+
await steps.after();
|
|
6234
6445
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
6235
6446
|
} else if (op["connect"] !== void 0) {
|
|
6236
6447
|
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
@@ -6240,41 +6451,46 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6240
6451
|
}
|
|
6241
6452
|
return patch;
|
|
6242
6453
|
},
|
|
6243
|
-
applyChildSide: async (executor, tableName, ops, parentRows,
|
|
6454
|
+
applyChildSide: async (executor, tableName, ops, parentRows, request) => {
|
|
6244
6455
|
const tablePlans = plans[tableName];
|
|
6245
6456
|
if (!tablePlans) return;
|
|
6457
|
+
const context = request?.context;
|
|
6246
6458
|
const scope = resolveScope(policies, context, filterCtx);
|
|
6247
6459
|
for (const [relationName, op] of Object.entries(ops)) {
|
|
6248
6460
|
const plan = tablePlans[relationName];
|
|
6249
6461
|
if (plan?.fkSide !== "child") continue;
|
|
6250
6462
|
if (plan.isOne) assertSingleOperation(plan, op);
|
|
6463
|
+
const steps = createHookSteps(onWrite, executor, request, tableName, plan);
|
|
6464
|
+
registerChildSteps(steps, plan, op);
|
|
6465
|
+
await steps.before();
|
|
6251
6466
|
for (const parentRow of parentRows) {
|
|
6252
6467
|
const key = parentRow?.[plan.localColPropName];
|
|
6253
6468
|
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" });
|
|
6254
6469
|
if (plan.through) {
|
|
6255
|
-
await applyThroughOps(executor, plan, key, op, scope);
|
|
6470
|
+
await applyThroughOps(executor, plan, key, op, scope, steps);
|
|
6256
6471
|
continue;
|
|
6257
6472
|
}
|
|
6258
6473
|
if (op["set"] !== void 0) {
|
|
6259
|
-
await detachAll(executor, plan, key, scope);
|
|
6260
|
-
for (const filter of asArray(op["set"])) await executor.update(plan.targetTable).set({ [plan.foreignColPropName]: key }).where(conditionOf(plan, filter, "set", scope));
|
|
6474
|
+
await detachAll(executor, plan, key, scope, steps, "set");
|
|
6475
|
+
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");
|
|
6261
6476
|
}
|
|
6262
6477
|
if (op["disconnect"] !== void 0) {
|
|
6263
6478
|
if (plan.isOne) {
|
|
6264
|
-
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope);
|
|
6265
|
-
} else for (const filter of asArray(op["disconnect"])) await executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope)));
|
|
6479
|
+
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
|
|
6480
|
+
} else for (const filter of asArray(op["disconnect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope))), "disconnect", "update");
|
|
6266
6481
|
}
|
|
6267
|
-
const
|
|
6268
|
-
if (plan.isOne &&
|
|
6269
|
-
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));
|
|
6482
|
+
const attachedBy = attachingOp(op);
|
|
6483
|
+
if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
|
|
6484
|
+
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");
|
|
6270
6485
|
if (op["create"] !== void 0) {
|
|
6271
6486
|
const rows = asArray(op["create"]).map((row) => ({
|
|
6272
6487
|
...applyContextValues(remapFromGraphQLSingleInput({ ...row }, plan.targetTable), contextValues?.(plan.targetTableName), context),
|
|
6273
6488
|
[plan.foreignColPropName]: key
|
|
6274
6489
|
}));
|
|
6275
|
-
if (rows.length) await executor.insert(plan.targetTable).values(rows);
|
|
6490
|
+
if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
|
|
6276
6491
|
}
|
|
6277
6492
|
}
|
|
6493
|
+
await steps.after();
|
|
6278
6494
|
}
|
|
6279
6495
|
}
|
|
6280
6496
|
};
|
|
@@ -6284,21 +6500,25 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6284
6500
|
* inside a single transaction — a savepoint when the caller already runs on one, so a failing
|
|
6285
6501
|
* entry rolls the whole batch back.
|
|
6286
6502
|
*
|
|
6503
|
+
* That transaction is also the `tx` the target tables' `onWrite` hooks run on. Every nested
|
|
6504
|
+
* write opens it, so a hook on a nested target has the whole tree to roll back even when the
|
|
6505
|
+
* root field carries no hook of its own and was not forced into a transaction for one.
|
|
6506
|
+
*
|
|
6287
6507
|
* Entries are written one at a time rather than as a single multi-row statement: a nested
|
|
6288
6508
|
* operation attaches to the row its own entry produced, so the two have to stay correlated.
|
|
6289
6509
|
* The batch path is untouched when no entry carries a nested operation.
|
|
6290
6510
|
*/
|
|
6291
6511
|
const writeWithNestedOps = async (params) => {
|
|
6292
|
-
const { executor, runtime, tableName, entries, remapValues, write,
|
|
6512
|
+
const { executor, runtime, tableName, entries, remapValues, write, request } = params;
|
|
6293
6513
|
return executor.transaction(async (tx) => {
|
|
6294
6514
|
const rows = [];
|
|
6295
6515
|
for (const entry of entries) {
|
|
6296
|
-
const patch = await runtime.applyParentSide(tx, tableName, entry.ops,
|
|
6516
|
+
const patch = await runtime.applyParentSide(tx, tableName, entry.ops, request);
|
|
6297
6517
|
const written = await write(tx, {
|
|
6298
6518
|
...remapValues(entry.columns),
|
|
6299
6519
|
...patch
|
|
6300
6520
|
});
|
|
6301
|
-
await runtime.applyChildSide(tx, tableName, entry.ops, written,
|
|
6521
|
+
await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
|
|
6302
6522
|
rows.push(...written);
|
|
6303
6523
|
}
|
|
6304
6524
|
return rows;
|
|
@@ -6310,14 +6530,14 @@ const writeWithNestedOps = async (params) => {
|
|
|
6310
6530
|
* attaches to each row that came back.
|
|
6311
6531
|
*/
|
|
6312
6532
|
const updateWithNestedOps = async (params) => {
|
|
6313
|
-
const { executor, runtime, tableName, columns, ops, remapValues, write,
|
|
6533
|
+
const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
|
|
6314
6534
|
return executor.transaction(async (tx) => {
|
|
6315
|
-
const patch = await runtime.applyParentSide(tx, tableName, ops,
|
|
6535
|
+
const patch = await runtime.applyParentSide(tx, tableName, ops, request);
|
|
6316
6536
|
const rows = await write(tx, {
|
|
6317
6537
|
...remapValues(columns),
|
|
6318
6538
|
...patch
|
|
6319
6539
|
});
|
|
6320
|
-
await runtime.applyChildSide(tx, tableName, ops, rows,
|
|
6540
|
+
await runtime.applyChildSide(tx, tableName, ops, rows, request);
|
|
6321
6541
|
return rows;
|
|
6322
6542
|
});
|
|
6323
6543
|
};
|
|
@@ -6518,7 +6738,14 @@ const createSchemaBuilder = (adapter) => {
|
|
|
6518
6738
|
uniqueKeysOf: (tableName) => uniqueKeys[tableName]
|
|
6519
6739
|
};
|
|
6520
6740
|
adapter.preflight?.(db, options);
|
|
6521
|
-
const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) => uniqueColumnSets(target), (target) => primaryKeyPropNames(target), extractRelationJoinColumns) : void 0;
|
|
6741
|
+
const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) => uniqueColumnSets(target), (target) => primaryKeyPropNames(target), extractRelationJoinColumns, (junction) => schemaEntries.find(([, value]) => value === junction)?.[0]) : void 0;
|
|
6742
|
+
if (nestedPlans) {
|
|
6743
|
+
const omitted = gateNestedWritePlans(nestedPlans, featureOf, (tableName) => excludedTables.has(tableName));
|
|
6744
|
+
if (omitted.length) {
|
|
6745
|
+
const listed = omitted.length > 5 ? `${omitted.slice(0, 5).join(", ")} and ${omitted.length - 5} more` : omitted.join(", ");
|
|
6746
|
+
console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6522
6749
|
const nestedTypes = nestedPlans ? createNestedWriteTypes({
|
|
6523
6750
|
plans: nestedPlans,
|
|
6524
6751
|
cacheCtx,
|
|
@@ -7016,7 +7243,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7016
7243
|
fieldName: updateCountFieldName,
|
|
7017
7244
|
requireWhere: tableFeatures.requireWhere,
|
|
7018
7245
|
filterCtx,
|
|
7019
|
-
txCtx: mutationTxCtx
|
|
7246
|
+
txCtx: mutationTxCtx,
|
|
7247
|
+
policies,
|
|
7248
|
+
returnsRows: false
|
|
7020
7249
|
}) : void 0;
|
|
7021
7250
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
7022
7251
|
db,
|
|
@@ -7027,7 +7256,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7027
7256
|
fieldName: deleteCountFieldName,
|
|
7028
7257
|
requireWhere: tableFeatures.requireWhere,
|
|
7029
7258
|
filterCtx,
|
|
7030
|
-
txCtx: mutationTxCtx
|
|
7259
|
+
txCtx: mutationTxCtx,
|
|
7260
|
+
policies,
|
|
7261
|
+
returnsRows: false
|
|
7031
7262
|
}) : void 0;
|
|
7032
7263
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7033
7264
|
const withMeta = (generated, meta) => ({
|
|
@@ -7097,12 +7328,25 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7097
7328
|
{
|
|
7098
7329
|
generated: updateCountGenerated,
|
|
7099
7330
|
type: new graphql.GraphQLNonNull(graphql.GraphQLInt),
|
|
7331
|
+
meta: {
|
|
7332
|
+
operation: "update",
|
|
7333
|
+
single: false,
|
|
7334
|
+
targetArg: "where",
|
|
7335
|
+
count: true
|
|
7336
|
+
},
|
|
7100
7337
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7101
7338
|
},
|
|
7102
7339
|
{
|
|
7103
7340
|
generated: deleteCountGenerated,
|
|
7104
7341
|
type: new graphql.GraphQLNonNull(graphql.GraphQLInt),
|
|
7105
|
-
|
|
7342
|
+
meta: {
|
|
7343
|
+
operation: "delete",
|
|
7344
|
+
single: false,
|
|
7345
|
+
targetArg: "where",
|
|
7346
|
+
count: true,
|
|
7347
|
+
...hardDeleteMeta
|
|
7348
|
+
},
|
|
7349
|
+
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."
|
|
7106
7350
|
}
|
|
7107
7351
|
],
|
|
7108
7352
|
onConflictInput,
|
|
@@ -7174,7 +7418,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7174
7418
|
entries: nestedEntries,
|
|
7175
7419
|
remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
|
|
7176
7420
|
write: (tx, values) => runInsert(tx, [values]),
|
|
7177
|
-
|
|
7421
|
+
request: {
|
|
7422
|
+
context,
|
|
7423
|
+
args,
|
|
7424
|
+
info
|
|
7425
|
+
}
|
|
7178
7426
|
}) : await runInsert(executor, input);
|
|
7179
7427
|
await after(written);
|
|
7180
7428
|
const result = output(written);
|
|
@@ -7263,7 +7511,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7263
7511
|
entries: nestedEntries,
|
|
7264
7512
|
remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
|
|
7265
7513
|
write: (tx, values) => runUpsert(tx, [values]),
|
|
7266
|
-
|
|
7514
|
+
request: {
|
|
7515
|
+
context,
|
|
7516
|
+
args,
|
|
7517
|
+
info
|
|
7518
|
+
}
|
|
7267
7519
|
}) : await runUpsert(executor, input);
|
|
7268
7520
|
await after(written);
|
|
7269
7521
|
const result = output(written);
|
|
@@ -7336,7 +7588,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7336
7588
|
ops: nestedOps,
|
|
7337
7589
|
remapValues: (values) => values,
|
|
7338
7590
|
write: runUpdate,
|
|
7339
|
-
|
|
7591
|
+
request: {
|
|
7592
|
+
context,
|
|
7593
|
+
args,
|
|
7594
|
+
info
|
|
7595
|
+
}
|
|
7340
7596
|
}) : await runUpdate(executor, input);
|
|
7341
7597
|
await after(written);
|
|
7342
7598
|
const result = output(written);
|
|
@@ -7603,7 +7859,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7603
7859
|
requireWhere: tableFeatures.requireWhere,
|
|
7604
7860
|
filterCtx,
|
|
7605
7861
|
txCtx: mutationTxCtx,
|
|
7606
|
-
nested: nestedRuntime
|
|
7862
|
+
nested: nestedRuntime,
|
|
7863
|
+
policies,
|
|
7864
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7865
|
+
returnsRows: true
|
|
7607
7866
|
}) : void 0;
|
|
7608
7867
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
7609
7868
|
db,
|
|
@@ -7614,7 +7873,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7614
7873
|
fieldName: deleteCountFieldName,
|
|
7615
7874
|
requireWhere: tableFeatures.requireWhere,
|
|
7616
7875
|
filterCtx,
|
|
7617
|
-
txCtx: mutationTxCtx
|
|
7876
|
+
txCtx: mutationTxCtx,
|
|
7877
|
+
policies,
|
|
7878
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7879
|
+
returnsRows: true
|
|
7618
7880
|
}) : void 0;
|
|
7619
7881
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7620
7882
|
return {
|
|
@@ -7724,12 +7986,25 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7724
7986
|
{
|
|
7725
7987
|
generated: updateCountGenerated,
|
|
7726
7988
|
type: new graphql.GraphQLNonNull(graphql.GraphQLInt),
|
|
7989
|
+
meta: {
|
|
7990
|
+
operation: "update",
|
|
7991
|
+
single: false,
|
|
7992
|
+
targetArg: "where",
|
|
7993
|
+
count: true
|
|
7994
|
+
},
|
|
7727
7995
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7728
7996
|
},
|
|
7729
7997
|
{
|
|
7730
7998
|
generated: deleteCountGenerated,
|
|
7731
7999
|
type: new graphql.GraphQLNonNull(graphql.GraphQLInt),
|
|
7732
|
-
|
|
8000
|
+
meta: {
|
|
8001
|
+
operation: "delete",
|
|
8002
|
+
single: false,
|
|
8003
|
+
targetArg: "where",
|
|
8004
|
+
count: true,
|
|
8005
|
+
...hardDeleteMeta
|
|
8006
|
+
},
|
|
8007
|
+
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."
|
|
7733
8008
|
}
|
|
7734
8009
|
],
|
|
7735
8010
|
onConflictInput,
|
|
@@ -7747,18 +8022,18 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7747
8022
|
* the shared multi-mutation transaction opened by `runMutation` — this opens a savepoint, so
|
|
7748
8023
|
* the batch stays atomic without breaking the outer transaction.
|
|
7749
8024
|
*/
|
|
7750
|
-
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName,
|
|
8025
|
+
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
|
|
7751
8026
|
const results = [];
|
|
7752
8027
|
for (const entry of entries) {
|
|
7753
8028
|
const values = entry.ops ? {
|
|
7754
8029
|
...entry.set,
|
|
7755
|
-
...await nested.applyParentSide(tx, tableName, entry.ops,
|
|
8030
|
+
...await nested.applyParentSide(tx, tableName, entry.ops, request)
|
|
7756
8031
|
} : entry.set;
|
|
7757
8032
|
const writes = Object.keys(values).length > 0;
|
|
7758
8033
|
let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
|
|
7759
8034
|
if (entry.filters) query = query.where(entry.filters);
|
|
7760
8035
|
const rows = await (writes ? query.returning(returning) : query);
|
|
7761
|
-
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows,
|
|
8036
|
+
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
|
|
7762
8037
|
results.push(rows);
|
|
7763
8038
|
}
|
|
7764
8039
|
return results;
|
|
@@ -7831,7 +8106,11 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7831
8106
|
anyNested,
|
|
7832
8107
|
nested,
|
|
7833
8108
|
tableName,
|
|
7834
|
-
|
|
8109
|
+
request: {
|
|
8110
|
+
context,
|
|
8111
|
+
args,
|
|
8112
|
+
info
|
|
8113
|
+
}
|
|
7835
8114
|
});
|
|
7836
8115
|
const written = perEntry.flat();
|
|
7837
8116
|
await after(written);
|
|
@@ -7880,7 +8159,7 @@ const sqlitePrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(ta
|
|
|
7880
8159
|
* would run after the COMMIT. The driver kind is probed from the first statement's result
|
|
7881
8160
|
* rather than declared, so the same resolver serves both driver families.
|
|
7882
8161
|
*/
|
|
7883
|
-
const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNested, nested, tableName,
|
|
8162
|
+
const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNested, nested, tableName, request }) => {
|
|
7884
8163
|
const runEntry = (tx, entry) => {
|
|
7885
8164
|
let query = tx.update(table).set(entry.set);
|
|
7886
8165
|
if (entry.filters) query = query.where(entry.filters);
|
|
@@ -7889,13 +8168,13 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
|
|
|
7889
8168
|
const runNestedEntry = async (tx, entry) => {
|
|
7890
8169
|
const values = entry.ops ? {
|
|
7891
8170
|
...entry.set,
|
|
7892
|
-
...await nested.applyParentSide(tx, tableName, entry.ops,
|
|
8171
|
+
...await nested.applyParentSide(tx, tableName, entry.ops, request)
|
|
7893
8172
|
} : entry.set;
|
|
7894
8173
|
const writes = Object.keys(values).length > 0;
|
|
7895
8174
|
let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
|
|
7896
8175
|
if (entry.filters) query = query.where(entry.filters);
|
|
7897
8176
|
const rows = await (writes ? query.returning(returning) : query);
|
|
7898
|
-
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows,
|
|
8177
|
+
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
|
|
7899
8178
|
return rows;
|
|
7900
8179
|
};
|
|
7901
8180
|
return executor.transaction((tx) => {
|