@vantreeseba/drizzle-graphql 13.1.1 → 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 +81 -5
- package/dist/index.cjs +299 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -7
- package/dist/index.d.ts +66 -7
- package/dist/index.js +299 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5251,7 +5251,6 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5251
5251
|
const missing = [!tableFeatures.insert ? "insert" : void 0, !tableFeatures.update ? "update" : void 0].filter(Boolean).join(" and ");
|
|
5252
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);
|
|
5253
5253
|
}
|
|
5254
|
-
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);
|
|
5255
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);
|
|
5256
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);
|
|
5257
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);
|
|
@@ -5857,6 +5856,12 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
5857
5856
|
};
|
|
5858
5857
|
//#endregion
|
|
5859
5858
|
//#region src/util/builders/nested-writes.ts
|
|
5859
|
+
const ALL_OPS = [
|
|
5860
|
+
"create",
|
|
5861
|
+
"connect",
|
|
5862
|
+
"disconnect",
|
|
5863
|
+
"set"
|
|
5864
|
+
];
|
|
5860
5865
|
/**
|
|
5861
5866
|
* Whether a column can be left out of an INSERT — either the database fills it in or it
|
|
5862
5867
|
* accepts NULL.
|
|
@@ -5885,7 +5890,7 @@ const throughColumn = (entry) => entry && (0, drizzle_orm.is)(entry, drizzle_orm
|
|
|
5885
5890
|
* joining on more than one column per side; and a junction carrying a further NOT NULL column
|
|
5886
5891
|
* with no default, which no `connect` operand could supply a value for.
|
|
5887
5892
|
*/
|
|
5888
|
-
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable) => {
|
|
5893
|
+
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf) => {
|
|
5889
5894
|
if ((0, drizzle_orm.is)(relation, drizzle_orm.One)) return;
|
|
5890
5895
|
const junction = relation.throughTable;
|
|
5891
5896
|
const sourceEntries = relation.through?.source;
|
|
@@ -5916,11 +5921,18 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
5916
5921
|
canDetach: true,
|
|
5917
5922
|
through: {
|
|
5918
5923
|
table: junction,
|
|
5924
|
+
tableName: tableNameOf(junction),
|
|
5919
5925
|
sourceColPropName,
|
|
5920
5926
|
targetColPropName,
|
|
5921
5927
|
sourceCol,
|
|
5922
5928
|
targetCol
|
|
5923
|
-
}
|
|
5929
|
+
},
|
|
5930
|
+
targetKeyColumns: { [foreignColPropName]: foreignCol },
|
|
5931
|
+
allowed: /* @__PURE__ */ new Set([
|
|
5932
|
+
"connect",
|
|
5933
|
+
"disconnect",
|
|
5934
|
+
"set"
|
|
5935
|
+
])
|
|
5924
5936
|
};
|
|
5925
5937
|
};
|
|
5926
5938
|
/**
|
|
@@ -5933,7 +5945,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
5933
5945
|
* relations whose join columns cannot be read off the drizzle relation; and relations where
|
|
5934
5946
|
* neither join column is unique, which leaves no way to tell which side stores the key.
|
|
5935
5947
|
*/
|
|
5936
|
-
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns) => {
|
|
5948
|
+
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
|
|
5937
5949
|
const uniqueCache = /* @__PURE__ */ new Map();
|
|
5938
5950
|
const singleColumnUniques = (tableName, table) => {
|
|
5939
5951
|
let cached = uniqueCache.get(tableName);
|
|
@@ -5962,7 +5974,7 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5962
5974
|
const targetTable = tables[targetTableName];
|
|
5963
5975
|
if (!targetTable) continue;
|
|
5964
5976
|
if (relation.through) {
|
|
5965
|
-
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable);
|
|
5977
|
+
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf);
|
|
5966
5978
|
if (throughPlan) tablePlans[relationName] = throughPlan;
|
|
5967
5979
|
continue;
|
|
5968
5980
|
}
|
|
@@ -5983,6 +5995,10 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5983
5995
|
else if (foreignIsUnique) fkSide = "parent";
|
|
5984
5996
|
else if (localIsUnique) fkSide = "child";
|
|
5985
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;
|
|
5986
6002
|
tablePlans[relationName] = {
|
|
5987
6003
|
relationName,
|
|
5988
6004
|
targetTableName,
|
|
@@ -5993,13 +6009,87 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
5993
6009
|
localCol,
|
|
5994
6010
|
isOne,
|
|
5995
6011
|
fkSide,
|
|
5996
|
-
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"])
|
|
5997
6019
|
};
|
|
5998
6020
|
}
|
|
5999
6021
|
if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
|
|
6000
6022
|
}
|
|
6001
6023
|
return plans;
|
|
6002
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
|
+
};
|
|
6003
6093
|
const NO_COLUMNS = /* @__PURE__ */ new Set();
|
|
6004
6094
|
/**
|
|
6005
6095
|
* Builds the nested-write input types for a schema.
|
|
@@ -6054,8 +6144,11 @@ const createNestedWriteTypes = (params) => {
|
|
|
6054
6144
|
*
|
|
6055
6145
|
* A many-to-many relation offers no `create`: inserting a row *and* linking it is two
|
|
6056
6146
|
* writes to two tables, and the link half is what the relation field is for. Its
|
|
6057
|
-
* `disconnect` and `set`
|
|
6147
|
+
* `disconnect` and `set` do not depend on nullability, since breaking a link deletes a
|
|
6058
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}).
|
|
6059
6152
|
*/
|
|
6060
6153
|
const wrapperType = (tableName, typeName, plan, forUpdate) => {
|
|
6061
6154
|
const key = `${tableName}.${plan.relationName}.${forUpdate ? "update" : "create"}`;
|
|
@@ -6064,57 +6157,52 @@ const createNestedWriteTypes = (params) => {
|
|
|
6064
6157
|
const filters = targetFilters(plan);
|
|
6065
6158
|
const targetTypeName = resolveTypeName(plan.targetTableName, typeNameMapper);
|
|
6066
6159
|
const fields = {};
|
|
6160
|
+
const offers = (op) => plan.allowed.has(op);
|
|
6067
6161
|
if (plan.through) {
|
|
6068
|
-
if (
|
|
6069
|
-
fields["connect"] = {
|
|
6162
|
+
if (filters && offers("connect")) fields["connect"] = {
|
|
6070
6163
|
type: listOf(filters),
|
|
6071
6164
|
description: `Link every existing ${targetTypeName} row these match. Already-linked rows are left alone.`
|
|
6072
6165
|
};
|
|
6073
|
-
if (forUpdate) {
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
};
|
|
6082
|
-
}
|
|
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
|
+
};
|
|
6083
6174
|
} else if (plan.isOne) {
|
|
6084
|
-
fields["create"] = {
|
|
6175
|
+
if (offers("create")) fields["create"] = {
|
|
6085
6176
|
type: payloadType(tableName, typeName, plan),
|
|
6086
6177
|
description: `Insert a ${targetTypeName} row and attach it`
|
|
6087
6178
|
};
|
|
6088
|
-
if (filters) fields["connect"] = {
|
|
6179
|
+
if (filters && offers("connect")) fields["connect"] = {
|
|
6089
6180
|
type: filters,
|
|
6090
6181
|
description: `Attach the existing ${targetTypeName} row this matches. Must match exactly one row.`
|
|
6091
6182
|
};
|
|
6092
|
-
if (forUpdate &&
|
|
6183
|
+
if (forUpdate && offers("disconnect")) fields["disconnect"] = {
|
|
6093
6184
|
type: graphql.GraphQLBoolean,
|
|
6094
6185
|
description: `Detach the currently attached ${targetTypeName} row, leaving it in place`
|
|
6095
6186
|
};
|
|
6096
6187
|
} else {
|
|
6097
|
-
fields["create"] = {
|
|
6188
|
+
if (offers("create")) fields["create"] = {
|
|
6098
6189
|
type: listOf(payloadType(tableName, typeName, plan)),
|
|
6099
6190
|
description: `Insert these ${targetTypeName} rows and attach them`
|
|
6100
6191
|
};
|
|
6101
|
-
if (filters) {
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
description: `Replace the whole set: detach everything attached, then attach every ${targetTypeName} row these match. Applied before \`disconnect\`, \`connect\` and \`create\`.`
|
|
6114
|
-
};
|
|
6115
|
-
}
|
|
6116
|
-
}
|
|
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
|
+
};
|
|
6117
6204
|
}
|
|
6205
|
+
if (!Object.keys(fields).length) return;
|
|
6118
6206
|
const type = new graphql.GraphQLInputObjectType({
|
|
6119
6207
|
name: cacheCtx.typeName({
|
|
6120
6208
|
kind: "nestedWriteInput",
|
|
@@ -6146,7 +6234,7 @@ const createNestedWriteTypes = (params) => {
|
|
|
6146
6234
|
const tablePlans = plans[tableName];
|
|
6147
6235
|
if (!tablePlans) return NO_COLUMNS;
|
|
6148
6236
|
const relaxed = /* @__PURE__ */ new Set();
|
|
6149
|
-
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);
|
|
6150
6238
|
return relaxed.size ? relaxed : NO_COLUMNS;
|
|
6151
6239
|
}
|
|
6152
6240
|
};
|
|
@@ -6157,8 +6245,99 @@ const createNestedWriteTypes = (params) => {
|
|
|
6157
6245
|
*/
|
|
6158
6246
|
const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
|
|
6159
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;
|
|
6160
6338
|
const createNestedWriteRuntime = (params) => {
|
|
6161
6339
|
const { plans, filterCtx, policies, contextValues } = params;
|
|
6340
|
+
const onWrite = policies?.onWrite;
|
|
6162
6341
|
/**
|
|
6163
6342
|
* A `connect` / `disconnect` / `set` operand compiled to SQL. An operand that compiles to
|
|
6164
6343
|
* nothing is rejected: `connect: {}` would otherwise attach every row in the table.
|
|
@@ -6169,12 +6348,12 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6169
6348
|
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
6170
6349
|
};
|
|
6171
6350
|
const assertSingleOperation = (plan, op) => {
|
|
6172
|
-
const
|
|
6351
|
+
const given = [
|
|
6173
6352
|
"create",
|
|
6174
6353
|
"connect",
|
|
6175
6354
|
"disconnect"
|
|
6176
|
-
].filter((key) => op[key]
|
|
6177
|
-
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" });
|
|
6178
6357
|
};
|
|
6179
6358
|
/** The values the junction stores for the target rows one operand selects. */
|
|
6180
6359
|
const selectedTargetKeys = async (executor, plan, filter, operation, scope) => {
|
|
@@ -6189,9 +6368,8 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6189
6368
|
* error on some of them — the read and the write share the surrounding transaction, so no
|
|
6190
6369
|
* other writer can slip a duplicate in between them.
|
|
6191
6370
|
*/
|
|
6192
|
-
const applyThroughOps = async (executor, plan, key, op, scope) => {
|
|
6371
|
+
const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
|
|
6193
6372
|
const through = plan.through;
|
|
6194
|
-
const unlinkAll = () => executor.delete(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key));
|
|
6195
6373
|
const link = async (operands, operation) => {
|
|
6196
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]));
|
|
6197
6375
|
const rows = [];
|
|
@@ -6202,19 +6380,19 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6202
6380
|
[through.targetColPropName]: targetKey
|
|
6203
6381
|
});
|
|
6204
6382
|
}
|
|
6205
|
-
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");
|
|
6206
6384
|
};
|
|
6207
|
-
if (op["set"]
|
|
6208
|
-
await
|
|
6385
|
+
if (supplied(op["set"])) {
|
|
6386
|
+
await steps.run(executor.delete(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key)), "set", "delete");
|
|
6209
6387
|
await link(asArray(op["set"]), "set");
|
|
6210
6388
|
}
|
|
6211
|
-
if (op["disconnect"]
|
|
6389
|
+
if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
|
|
6212
6390
|
const targetKeys = await selectedTargetKeys(executor, plan, filter, "disconnect", scope);
|
|
6213
|
-
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");
|
|
6214
6392
|
}
|
|
6215
|
-
if (op["connect"]
|
|
6393
|
+
if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
|
|
6216
6394
|
};
|
|
6217
|
-
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");
|
|
6218
6396
|
return {
|
|
6219
6397
|
enabled: (tableName) => !!plans[tableName],
|
|
6220
6398
|
split: (tableName, values) => {
|
|
@@ -6243,19 +6421,27 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6243
6421
|
}
|
|
6244
6422
|
return columns;
|
|
6245
6423
|
},
|
|
6246
|
-
applyParentSide: async (executor, tableName, ops,
|
|
6424
|
+
applyParentSide: async (executor, tableName, ops, request) => {
|
|
6247
6425
|
const tablePlans = plans[tableName];
|
|
6248
6426
|
const patch = {};
|
|
6249
6427
|
if (!tablePlans) return patch;
|
|
6428
|
+
const context = request?.context;
|
|
6250
6429
|
const scope = resolveScope(policies, context, filterCtx);
|
|
6251
6430
|
for (const [relationName, op] of Object.entries(ops)) {
|
|
6252
6431
|
const plan = tablePlans[relationName];
|
|
6253
6432
|
if (plan?.fkSide !== "parent") continue;
|
|
6254
6433
|
assertSingleOperation(plan, op);
|
|
6255
6434
|
if (op["create"] !== void 0) {
|
|
6435
|
+
const steps = createHookSteps(onWrite, executor, request, tableName, plan);
|
|
6436
|
+
steps.add("create", "insert");
|
|
6437
|
+
await steps.before();
|
|
6256
6438
|
const values = applyContextValues(remapFromGraphQLSingleInput({ ...op["create"] }, plan.targetTable), contextValues?.(plan.targetTableName), context);
|
|
6257
|
-
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];
|
|
6258
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();
|
|
6259
6445
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
6260
6446
|
} else if (op["connect"] !== void 0) {
|
|
6261
6447
|
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
@@ -6265,41 +6451,46 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6265
6451
|
}
|
|
6266
6452
|
return patch;
|
|
6267
6453
|
},
|
|
6268
|
-
applyChildSide: async (executor, tableName, ops, parentRows,
|
|
6454
|
+
applyChildSide: async (executor, tableName, ops, parentRows, request) => {
|
|
6269
6455
|
const tablePlans = plans[tableName];
|
|
6270
6456
|
if (!tablePlans) return;
|
|
6457
|
+
const context = request?.context;
|
|
6271
6458
|
const scope = resolveScope(policies, context, filterCtx);
|
|
6272
6459
|
for (const [relationName, op] of Object.entries(ops)) {
|
|
6273
6460
|
const plan = tablePlans[relationName];
|
|
6274
6461
|
if (plan?.fkSide !== "child") continue;
|
|
6275
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();
|
|
6276
6466
|
for (const parentRow of parentRows) {
|
|
6277
6467
|
const key = parentRow?.[plan.localColPropName];
|
|
6278
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" });
|
|
6279
6469
|
if (plan.through) {
|
|
6280
|
-
await applyThroughOps(executor, plan, key, op, scope);
|
|
6470
|
+
await applyThroughOps(executor, plan, key, op, scope, steps);
|
|
6281
6471
|
continue;
|
|
6282
6472
|
}
|
|
6283
6473
|
if (op["set"] !== void 0) {
|
|
6284
|
-
await detachAll(executor, plan, key, scope);
|
|
6285
|
-
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");
|
|
6286
6476
|
}
|
|
6287
6477
|
if (op["disconnect"] !== void 0) {
|
|
6288
6478
|
if (plan.isOne) {
|
|
6289
|
-
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope);
|
|
6290
|
-
} 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");
|
|
6291
6481
|
}
|
|
6292
|
-
const
|
|
6293
|
-
if (plan.isOne &&
|
|
6294
|
-
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");
|
|
6295
6485
|
if (op["create"] !== void 0) {
|
|
6296
6486
|
const rows = asArray(op["create"]).map((row) => ({
|
|
6297
6487
|
...applyContextValues(remapFromGraphQLSingleInput({ ...row }, plan.targetTable), contextValues?.(plan.targetTableName), context),
|
|
6298
6488
|
[plan.foreignColPropName]: key
|
|
6299
6489
|
}));
|
|
6300
|
-
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");
|
|
6301
6491
|
}
|
|
6302
6492
|
}
|
|
6493
|
+
await steps.after();
|
|
6303
6494
|
}
|
|
6304
6495
|
}
|
|
6305
6496
|
};
|
|
@@ -6309,21 +6500,25 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6309
6500
|
* inside a single transaction — a savepoint when the caller already runs on one, so a failing
|
|
6310
6501
|
* entry rolls the whole batch back.
|
|
6311
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
|
+
*
|
|
6312
6507
|
* Entries are written one at a time rather than as a single multi-row statement: a nested
|
|
6313
6508
|
* operation attaches to the row its own entry produced, so the two have to stay correlated.
|
|
6314
6509
|
* The batch path is untouched when no entry carries a nested operation.
|
|
6315
6510
|
*/
|
|
6316
6511
|
const writeWithNestedOps = async (params) => {
|
|
6317
|
-
const { executor, runtime, tableName, entries, remapValues, write,
|
|
6512
|
+
const { executor, runtime, tableName, entries, remapValues, write, request } = params;
|
|
6318
6513
|
return executor.transaction(async (tx) => {
|
|
6319
6514
|
const rows = [];
|
|
6320
6515
|
for (const entry of entries) {
|
|
6321
|
-
const patch = await runtime.applyParentSide(tx, tableName, entry.ops,
|
|
6516
|
+
const patch = await runtime.applyParentSide(tx, tableName, entry.ops, request);
|
|
6322
6517
|
const written = await write(tx, {
|
|
6323
6518
|
...remapValues(entry.columns),
|
|
6324
6519
|
...patch
|
|
6325
6520
|
});
|
|
6326
|
-
await runtime.applyChildSide(tx, tableName, entry.ops, written,
|
|
6521
|
+
await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
|
|
6327
6522
|
rows.push(...written);
|
|
6328
6523
|
}
|
|
6329
6524
|
return rows;
|
|
@@ -6335,14 +6530,14 @@ const writeWithNestedOps = async (params) => {
|
|
|
6335
6530
|
* attaches to each row that came back.
|
|
6336
6531
|
*/
|
|
6337
6532
|
const updateWithNestedOps = async (params) => {
|
|
6338
|
-
const { executor, runtime, tableName, columns, ops, remapValues, write,
|
|
6533
|
+
const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
|
|
6339
6534
|
return executor.transaction(async (tx) => {
|
|
6340
|
-
const patch = await runtime.applyParentSide(tx, tableName, ops,
|
|
6535
|
+
const patch = await runtime.applyParentSide(tx, tableName, ops, request);
|
|
6341
6536
|
const rows = await write(tx, {
|
|
6342
6537
|
...remapValues(columns),
|
|
6343
6538
|
...patch
|
|
6344
6539
|
});
|
|
6345
|
-
await runtime.applyChildSide(tx, tableName, ops, rows,
|
|
6540
|
+
await runtime.applyChildSide(tx, tableName, ops, rows, request);
|
|
6346
6541
|
return rows;
|
|
6347
6542
|
});
|
|
6348
6543
|
};
|
|
@@ -6543,7 +6738,14 @@ const createSchemaBuilder = (adapter) => {
|
|
|
6543
6738
|
uniqueKeysOf: (tableName) => uniqueKeys[tableName]
|
|
6544
6739
|
};
|
|
6545
6740
|
adapter.preflight?.(db, options);
|
|
6546
|
-
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
|
+
}
|
|
6547
6749
|
const nestedTypes = nestedPlans ? createNestedWriteTypes({
|
|
6548
6750
|
plans: nestedPlans,
|
|
6549
6751
|
cacheCtx,
|
|
@@ -7216,7 +7418,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7216
7418
|
entries: nestedEntries,
|
|
7217
7419
|
remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
|
|
7218
7420
|
write: (tx, values) => runInsert(tx, [values]),
|
|
7219
|
-
|
|
7421
|
+
request: {
|
|
7422
|
+
context,
|
|
7423
|
+
args,
|
|
7424
|
+
info
|
|
7425
|
+
}
|
|
7220
7426
|
}) : await runInsert(executor, input);
|
|
7221
7427
|
await after(written);
|
|
7222
7428
|
const result = output(written);
|
|
@@ -7305,7 +7511,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7305
7511
|
entries: nestedEntries,
|
|
7306
7512
|
remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
|
|
7307
7513
|
write: (tx, values) => runUpsert(tx, [values]),
|
|
7308
|
-
|
|
7514
|
+
request: {
|
|
7515
|
+
context,
|
|
7516
|
+
args,
|
|
7517
|
+
info
|
|
7518
|
+
}
|
|
7309
7519
|
}) : await runUpsert(executor, input);
|
|
7310
7520
|
await after(written);
|
|
7311
7521
|
const result = output(written);
|
|
@@ -7378,7 +7588,11 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7378
7588
|
ops: nestedOps,
|
|
7379
7589
|
remapValues: (values) => values,
|
|
7380
7590
|
write: runUpdate,
|
|
7381
|
-
|
|
7591
|
+
request: {
|
|
7592
|
+
context,
|
|
7593
|
+
args,
|
|
7594
|
+
info
|
|
7595
|
+
}
|
|
7382
7596
|
}) : await runUpdate(executor, input);
|
|
7383
7597
|
await after(written);
|
|
7384
7598
|
const result = output(written);
|
|
@@ -7808,18 +8022,18 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7808
8022
|
* the shared multi-mutation transaction opened by `runMutation` — this opens a savepoint, so
|
|
7809
8023
|
* the batch stays atomic without breaking the outer transaction.
|
|
7810
8024
|
*/
|
|
7811
|
-
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName,
|
|
8025
|
+
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
|
|
7812
8026
|
const results = [];
|
|
7813
8027
|
for (const entry of entries) {
|
|
7814
8028
|
const values = entry.ops ? {
|
|
7815
8029
|
...entry.set,
|
|
7816
|
-
...await nested.applyParentSide(tx, tableName, entry.ops,
|
|
8030
|
+
...await nested.applyParentSide(tx, tableName, entry.ops, request)
|
|
7817
8031
|
} : entry.set;
|
|
7818
8032
|
const writes = Object.keys(values).length > 0;
|
|
7819
8033
|
let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
|
|
7820
8034
|
if (entry.filters) query = query.where(entry.filters);
|
|
7821
8035
|
const rows = await (writes ? query.returning(returning) : query);
|
|
7822
|
-
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows,
|
|
8036
|
+
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
|
|
7823
8037
|
results.push(rows);
|
|
7824
8038
|
}
|
|
7825
8039
|
return results;
|
|
@@ -7892,7 +8106,11 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7892
8106
|
anyNested,
|
|
7893
8107
|
nested,
|
|
7894
8108
|
tableName,
|
|
7895
|
-
|
|
8109
|
+
request: {
|
|
8110
|
+
context,
|
|
8111
|
+
args,
|
|
8112
|
+
info
|
|
8113
|
+
}
|
|
7896
8114
|
});
|
|
7897
8115
|
const written = perEntry.flat();
|
|
7898
8116
|
await after(written);
|
|
@@ -7941,7 +8159,7 @@ const sqlitePrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(ta
|
|
|
7941
8159
|
* would run after the COMMIT. The driver kind is probed from the first statement's result
|
|
7942
8160
|
* rather than declared, so the same resolver serves both driver families.
|
|
7943
8161
|
*/
|
|
7944
|
-
const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNested, nested, tableName,
|
|
8162
|
+
const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNested, nested, tableName, request }) => {
|
|
7945
8163
|
const runEntry = (tx, entry) => {
|
|
7946
8164
|
let query = tx.update(table).set(entry.set);
|
|
7947
8165
|
if (entry.filters) query = query.where(entry.filters);
|
|
@@ -7950,13 +8168,13 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
|
|
|
7950
8168
|
const runNestedEntry = async (tx, entry) => {
|
|
7951
8169
|
const values = entry.ops ? {
|
|
7952
8170
|
...entry.set,
|
|
7953
|
-
...await nested.applyParentSide(tx, tableName, entry.ops,
|
|
8171
|
+
...await nested.applyParentSide(tx, tableName, entry.ops, request)
|
|
7954
8172
|
} : entry.set;
|
|
7955
8173
|
const writes = Object.keys(values).length > 0;
|
|
7956
8174
|
let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
|
|
7957
8175
|
if (entry.filters) query = query.where(entry.filters);
|
|
7958
8176
|
const rows = await (writes ? query.returning(returning) : query);
|
|
7959
|
-
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows,
|
|
8177
|
+
if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
|
|
7960
8178
|
return rows;
|
|
7961
8179
|
};
|
|
7962
8180
|
return executor.transaction((tx) => {
|