@vantreeseba/drizzle-graphql 15.1.4 → 15.1.5
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 +44 -10
- package/dist/index.cjs +194 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +194 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1548,8 +1548,15 @@ type SchemaFeatures = {
|
|
|
1548
1548
|
* relation left with no input loses its field.
|
|
1549
1549
|
* - **`onWrite`** — the hooks of the table a nested statement writes fire on the same `tx`,
|
|
1550
1550
|
* with `payload.nested` set. See {@link BuildSchemaConfig.onWrite}.
|
|
1551
|
-
* - **`scope`** and **`contextValues`** — `connect` / `disconnect` / `set`
|
|
1552
|
-
* in-scope rows
|
|
1551
|
+
* - **`scope`**, **`softDelete`** and **`contextValues`** — `connect` / `disconnect` / `set`
|
|
1552
|
+
* reach only in-scope, unmarked rows (a many-to-many `set` keeps the links it cannot
|
|
1553
|
+
* reach), and a nested `create` is stamped like any insert. A relation whose foreign key
|
|
1554
|
+
* is a `contextValues` column is not offered at all.
|
|
1555
|
+
*
|
|
1556
|
+
* A relation's own `where` limits the rows its writes reach the same way, and takes its
|
|
1557
|
+
* `create` away. A relation joining on more than one column per side is not offered. A
|
|
1558
|
+
* to-one `connect` must match exactly one row. When the parent write produces no row, a
|
|
1559
|
+
* parent-side `create` made for it is rolled back.
|
|
1553
1560
|
*
|
|
1554
1561
|
* PostgreSQL and SQLite only. MySQL cannot return the rows it just wrote, which is how a
|
|
1555
1562
|
* nested write learns the parent's key, so `buildSchema` rejects the flag there. On SQLite
|
|
@@ -2136,6 +2143,8 @@ type BuildSchemaConfig = {
|
|
|
2136
2143
|
* - Every insert stamps it, including the rows created by a nested `create`.
|
|
2137
2144
|
* - Updates never write it: an update cannot hand a row to another owner. That includes
|
|
2138
2145
|
* the conflict branch of an upsert, whose `onConflict.update` does not offer it.
|
|
2146
|
+
* - With `nestedWrites`, a relation whose foreign key is such a column gets no nested-write
|
|
2147
|
+
* field, since every nested operation on it would write the column.
|
|
2139
2148
|
* - Keyed by the Drizzle schema key, then by the column's property name (not its database
|
|
2140
2149
|
* name), the same keying {@link BuildSchemaConfig.exclude} uses.
|
|
2141
2150
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1548,8 +1548,15 @@ type SchemaFeatures = {
|
|
|
1548
1548
|
* relation left with no input loses its field.
|
|
1549
1549
|
* - **`onWrite`** — the hooks of the table a nested statement writes fire on the same `tx`,
|
|
1550
1550
|
* with `payload.nested` set. See {@link BuildSchemaConfig.onWrite}.
|
|
1551
|
-
* - **`scope`** and **`contextValues`** — `connect` / `disconnect` / `set`
|
|
1552
|
-
* in-scope rows
|
|
1551
|
+
* - **`scope`**, **`softDelete`** and **`contextValues`** — `connect` / `disconnect` / `set`
|
|
1552
|
+
* reach only in-scope, unmarked rows (a many-to-many `set` keeps the links it cannot
|
|
1553
|
+
* reach), and a nested `create` is stamped like any insert. A relation whose foreign key
|
|
1554
|
+
* is a `contextValues` column is not offered at all.
|
|
1555
|
+
*
|
|
1556
|
+
* A relation's own `where` limits the rows its writes reach the same way, and takes its
|
|
1557
|
+
* `create` away. A relation joining on more than one column per side is not offered. A
|
|
1558
|
+
* to-one `connect` must match exactly one row. When the parent write produces no row, a
|
|
1559
|
+
* parent-side `create` made for it is rolled back.
|
|
1553
1560
|
*
|
|
1554
1561
|
* PostgreSQL and SQLite only. MySQL cannot return the rows it just wrote, which is how a
|
|
1555
1562
|
* nested write learns the parent's key, so `buildSchema` rejects the flag there. On SQLite
|
|
@@ -2136,6 +2143,8 @@ type BuildSchemaConfig = {
|
|
|
2136
2143
|
* - Every insert stamps it, including the rows created by a nested `create`.
|
|
2137
2144
|
* - Updates never write it: an update cannot hand a row to another owner. That includes
|
|
2138
2145
|
* the conflict branch of an upsert, whose `onConflict.update` does not offer it.
|
|
2146
|
+
* - With `nestedWrites`, a relation whose foreign key is such a column gets no nested-write
|
|
2147
|
+
* field, since every nested operation on it would write the column.
|
|
2139
2148
|
* - Keyed by the Drizzle schema key, then by the column's property name (not its database
|
|
2140
2149
|
* name), the same keying {@link BuildSchemaConfig.exclude} uses.
|
|
2141
2150
|
*/
|
package/dist/index.js
CHANGED
|
@@ -6516,6 +6516,20 @@ const ALL_OPS = [
|
|
|
6516
6516
|
* accepts NULL.
|
|
6517
6517
|
*/
|
|
6518
6518
|
const isOptionalOnInsert = (column) => !column.notNull || !!column.hasDefault || !!column.defaultFn;
|
|
6519
|
+
/**
|
|
6520
|
+
* The plan fields that carry a relation's own `where` (see {@link relationOwnCondition}).
|
|
6521
|
+
*
|
|
6522
|
+
* A `where` on the target narrows every row selection the write makes, and takes `create`
|
|
6523
|
+
* away: the row it inserts is attached whether or not it satisfies the `where`, so the
|
|
6524
|
+
* relation could come back without it. A `where` inherited from the reverse relation filters
|
|
6525
|
+
* the parent instead, and becomes a per-parent check.
|
|
6526
|
+
*/
|
|
6527
|
+
const ownWherePlan = (relEntry, parentTable, targetTable, localCol) => {
|
|
6528
|
+
const condition = () => relationOwnCondition(relEntry, parentTable, targetTable, [localCol], [localCol]);
|
|
6529
|
+
if (!condition()) return {};
|
|
6530
|
+
if (!isRelationFilterReversed(relEntry.relation ?? relEntry)) return { ownCondition: () => condition() };
|
|
6531
|
+
return { parentShows: async (executor, key) => (await executor.select({ key: localCol }).from(parentTable).where(and(eq(localCol, key), condition())).limit(1)).length > 0 };
|
|
6532
|
+
};
|
|
6519
6533
|
/** The property name a column is stored under on its table, matched by identity. */
|
|
6520
6534
|
const propNameOf = (table, column) => {
|
|
6521
6535
|
if (!column) return;
|
|
@@ -6536,7 +6550,7 @@ const propNameOf = (table, column) => {
|
|
|
6536
6550
|
* `onUnresolved` is called when the relation's columns are of a supported shape but cannot be
|
|
6537
6551
|
* matched to the three tables — a drizzle-orm mismatch rather than an unsupported relation.
|
|
6538
6552
|
*/
|
|
6539
|
-
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
|
|
6553
|
+
const buildThroughPlan = (relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
|
|
6540
6554
|
if (is(relation, One)) return;
|
|
6541
6555
|
const junction = relation.throughTable;
|
|
6542
6556
|
const sourceEntries = relation.through?.source;
|
|
@@ -6577,6 +6591,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
6577
6591
|
targetCol
|
|
6578
6592
|
},
|
|
6579
6593
|
targetKeyColumns: { [foreignColPropName]: foreignCol },
|
|
6594
|
+
...ownWherePlan(relEntry, table, targetTable, localCol),
|
|
6580
6595
|
allowed: /* @__PURE__ */ new Set([
|
|
6581
6596
|
"connect",
|
|
6582
6597
|
"disconnect",
|
|
@@ -6591,8 +6606,9 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
6591
6606
|
* rather than a foreign-key one — see {@link buildThroughPlan}.
|
|
6592
6607
|
*
|
|
6593
6608
|
* Skipped, and so absent from the generated inputs rather than failing at runtime:
|
|
6594
|
-
* relations whose join columns cannot be read off the drizzle relation;
|
|
6595
|
-
*
|
|
6609
|
+
* relations whose join columns cannot be read off the drizzle relation; relations joining on
|
|
6610
|
+
* more than one column per side; and relations where neither join column is unique, which
|
|
6611
|
+
* leaves no way to tell which side stores the key.
|
|
6596
6612
|
*/
|
|
6597
6613
|
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
|
|
6598
6614
|
const uniqueCache = /* @__PURE__ */ new Map();
|
|
@@ -6624,10 +6640,11 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6624
6640
|
if (!targetTable) continue;
|
|
6625
6641
|
const omitted = `the '${relationName}' field of its nested-write create and update inputs`;
|
|
6626
6642
|
if (relation.through) {
|
|
6627
|
-
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
|
|
6643
|
+
const throughPlan = buildThroughPlan(relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
|
|
6628
6644
|
if (throughPlan) tablePlans[relationName] = throughPlan;
|
|
6629
6645
|
continue;
|
|
6630
6646
|
}
|
|
6647
|
+
if ((relationColumns(relation, "source")?.length ?? 0) > 1 || (relationColumns(relation, "target")?.length ?? 0) > 1) continue;
|
|
6631
6648
|
const joinColumns = extractJoinColumns(relEntry, table, targetTable);
|
|
6632
6649
|
if (!joinColumns) {
|
|
6633
6650
|
warnUnresolvedJoin(relEntry, tableName, relationName, omitted);
|
|
@@ -6650,8 +6667,15 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6650
6667
|
else continue;
|
|
6651
6668
|
const fkColumn = fkSide === "child" ? foreignCol : localCol;
|
|
6652
6669
|
const targetColumns = getColumns(targetTable);
|
|
6653
|
-
const
|
|
6670
|
+
const targetPrimaryKey = resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]);
|
|
6671
|
+
const targetKeyColumns = Object.fromEntries(targetPrimaryKey.map((name) => [name, targetColumns[name]]));
|
|
6654
6672
|
targetKeyColumns[foreignColPropName] = foreignCol;
|
|
6673
|
+
const ownWhere = ownWherePlan(relEntry, table, targetTable, localCol);
|
|
6674
|
+
const ops = !fkColumn.notNull ? isOne ? [
|
|
6675
|
+
"create",
|
|
6676
|
+
"connect",
|
|
6677
|
+
"disconnect"
|
|
6678
|
+
] : ALL_OPS : ["create", "connect"];
|
|
6655
6679
|
tablePlans[relationName] = {
|
|
6656
6680
|
relationName,
|
|
6657
6681
|
targetTableName,
|
|
@@ -6664,11 +6688,9 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6664
6688
|
fkSide,
|
|
6665
6689
|
canDetach: !fkColumn.notNull,
|
|
6666
6690
|
targetKeyColumns,
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
"disconnect"
|
|
6671
|
-
] : ALL_OPS : ["create", "connect"])
|
|
6691
|
+
targetPrimaryKey,
|
|
6692
|
+
...ownWhere,
|
|
6693
|
+
allowed: new Set(ownWhere.ownCondition ? ops.filter((op) => op !== "create") : ops)
|
|
6672
6694
|
};
|
|
6673
6695
|
}
|
|
6674
6696
|
if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
|
|
@@ -6743,6 +6765,35 @@ const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
|
|
|
6743
6765
|
}
|
|
6744
6766
|
return omitted;
|
|
6745
6767
|
};
|
|
6768
|
+
/**
|
|
6769
|
+
* Takes out every relation whose foreign key is a `contextValues` column of the table storing
|
|
6770
|
+
* it — the parent's own column for a parent-side relation, the target's for a child-side one,
|
|
6771
|
+
* either junction column for a many-to-many.
|
|
6772
|
+
*
|
|
6773
|
+
* Every operation on such a relation writes that column: `connect`, `disconnect` and `set`
|
|
6774
|
+
* by definition, and a child-side `create` too, since it fills the key in from the parent row
|
|
6775
|
+
* rather than from context. That column is the server's to set — an insert stamps it and an
|
|
6776
|
+
* update never touches it — so writing it from a nested input would let a client pick, or
|
|
6777
|
+
* change, the owner the server assigns. Whole relations are dropped, not single inputs: no
|
|
6778
|
+
* operation on them is left that does not write the key.
|
|
6779
|
+
*
|
|
6780
|
+
* Returns one line per relation taken out, for the build note.
|
|
6781
|
+
*/
|
|
6782
|
+
const dropContextValueRelations = (plans, contextValuesOf) => {
|
|
6783
|
+
const omitted = [];
|
|
6784
|
+
if (!contextValuesOf) return omitted;
|
|
6785
|
+
for (const [tableName, tablePlans] of Object.entries(plans)) {
|
|
6786
|
+
for (const [relationName, plan] of Object.entries(tablePlans)) {
|
|
6787
|
+
const stamped = (plan.through ? [[plan.through.tableName, plan.through.sourceColPropName], [plan.through.tableName, plan.through.targetColPropName]] : plan.fkSide === "parent" ? [[tableName, plan.localColPropName]] : [[plan.targetTableName, plan.foreignColPropName]]).find(([owner, column]) => owner !== void 0 && contextValuesOf(owner)?.[column]);
|
|
6788
|
+
if (stamped) {
|
|
6789
|
+
delete tablePlans[relationName];
|
|
6790
|
+
omitted.push(`${tableName}.${relationName} (${stamped[0]}.${stamped[1]} is a contextValues column)`);
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
if (!Object.keys(tablePlans).length) delete plans[tableName];
|
|
6794
|
+
}
|
|
6795
|
+
return omitted;
|
|
6796
|
+
};
|
|
6746
6797
|
const NO_COLUMNS = /* @__PURE__ */ new Set();
|
|
6747
6798
|
/**
|
|
6748
6799
|
* Builds the nested-write input types for a schema.
|
|
@@ -6773,7 +6824,8 @@ const createNestedWriteTypes = (params) => {
|
|
|
6773
6824
|
if (cached) return cached;
|
|
6774
6825
|
const omitted = plan.fkSide === "child" ? plan.foreignColPropName : void 0;
|
|
6775
6826
|
const contextColumns = cacheCtx.contextValuesOf?.(plan.targetTableName);
|
|
6776
|
-
const
|
|
6827
|
+
const markerColumn = cacheCtx.softDeleteOf?.(plan.targetTableName)?.columnName;
|
|
6828
|
+
const fields = Object.fromEntries(Object.entries(visibleColumns(plan.targetTable)).filter(([columnName]) => columnName !== omitted && !contextColumns?.[columnName] && columnName !== markerColumn).map(([columnName, column]) => [columnName, drizzleColumnToGraphQLType(column, columnName, plan.targetTableName, false, true, true)]));
|
|
6777
6829
|
const type = new GraphQLInputObjectType({
|
|
6778
6830
|
name: cacheCtx.typeName({
|
|
6779
6831
|
kind: "nestedWriteInput",
|
|
@@ -6897,6 +6949,8 @@ const createNestedWriteTypes = (params) => {
|
|
|
6897
6949
|
* join columns the whole batch's RETURNING clause needs, without inspecting each row's write.
|
|
6898
6950
|
*/
|
|
6899
6951
|
const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
|
|
6952
|
+
/** A to-one `connect` whose filter did not match exactly one row. */
|
|
6953
|
+
const connectMatchError = (relationName, matched) => drizzleError(`Drizzle-GraphQL Error: 'connect' on '${relationName}' matched ${matched} rows — it attaches a single row, so its filter must match exactly one.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6900
6954
|
const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
6901
6955
|
const supplied = (value) => value !== void 0 && value !== null;
|
|
6902
6956
|
/**
|
|
@@ -6988,6 +7042,8 @@ const registerChildSteps = (steps, plan, op) => {
|
|
|
6988
7042
|
};
|
|
6989
7043
|
/** The input that attaches a row to a to-one, which detaches whatever was attached before. */
|
|
6990
7044
|
const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
|
|
7045
|
+
/** The child-side relation in `ops` whose target is the parent's own table, if there is one. */
|
|
7046
|
+
const selfTargetingPlan = (tablePlans, tableName, ops) => Object.keys(ops).map((relationName) => tablePlans?.[relationName]).find((plan) => plan?.fkSide === "child" && !plan.through && plan.targetTableName === tableName);
|
|
6991
7047
|
const createNestedWriteRuntime = (params) => {
|
|
6992
7048
|
const { plans, filterCtx, policies, contextValues } = params;
|
|
6993
7049
|
const onWrite = policies?.onWrite;
|
|
@@ -6998,7 +7054,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6998
7054
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
6999
7055
|
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
7000
7056
|
if (!condition) throw drizzleError(`Drizzle-GraphQL Error: '${operation}' on '${plan.relationName}' needs a filter that selects rows — it was given one that matches everything.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
7001
|
-
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
7057
|
+
return withScope(scope, plan.targetTableName, plan.targetTable, and(condition, plan.ownCondition?.()));
|
|
7002
7058
|
};
|
|
7003
7059
|
const assertSingleOperation = (plan, op) => {
|
|
7004
7060
|
const given = [
|
|
@@ -7021,22 +7077,24 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7021
7077
|
* error on some of them — the read and the write share the surrounding transaction, so no
|
|
7022
7078
|
* other writer can slip a duplicate in between them.
|
|
7023
7079
|
*/
|
|
7024
|
-
const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
|
|
7080
|
+
const applyThroughOps = async (executor, plan, key, op, scope, steps, context) => {
|
|
7025
7081
|
const through = plan.through;
|
|
7026
7082
|
const link = async (operands, operation) => {
|
|
7027
7083
|
const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
7028
7084
|
const rows = [];
|
|
7029
7085
|
for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
|
|
7030
7086
|
existing.add(targetKey);
|
|
7031
|
-
rows.push({
|
|
7087
|
+
rows.push(applyContextValues({
|
|
7032
7088
|
[through.sourceColPropName]: key,
|
|
7033
7089
|
[through.targetColPropName]: targetKey
|
|
7034
|
-
});
|
|
7090
|
+
}, through.tableName ? contextValues?.(through.tableName) : void 0, context));
|
|
7035
7091
|
}
|
|
7036
7092
|
if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
|
|
7037
7093
|
};
|
|
7038
7094
|
if (supplied(op["set"])) {
|
|
7039
|
-
|
|
7095
|
+
const reachable = withScope(scope, plan.targetTableName, plan.targetTable, plan.ownCondition?.());
|
|
7096
|
+
const unlink = reachable ? and(eq(through.sourceCol, key), inArray(through.targetCol, executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(reachable))) : eq(through.sourceCol, key);
|
|
7097
|
+
await steps.run(executor.delete(through.table).where(unlink), "set", "delete");
|
|
7040
7098
|
await link(asArray(op["set"]), "set");
|
|
7041
7099
|
}
|
|
7042
7100
|
if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
|
|
@@ -7045,7 +7103,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7045
7103
|
}
|
|
7046
7104
|
if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
|
|
7047
7105
|
};
|
|
7048
|
-
const detachAll = (executor, plan, key, scope, steps, op) => steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(withScope(scope, plan.targetTableName, plan.targetTable, eq(plan.foreignCol, key))), op, "update");
|
|
7106
|
+
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, and(eq(plan.foreignCol, key), plan.ownCondition?.()))), op, "update");
|
|
7049
7107
|
return {
|
|
7050
7108
|
enabled: (tableName) => !!plans[tableName],
|
|
7051
7109
|
split: (tableName, values) => {
|
|
@@ -7072,8 +7130,24 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7072
7130
|
const plan = tablePlans[relationName];
|
|
7073
7131
|
if (plan?.fkSide === "child" && !(plan.localColPropName in columns) && allColumns[plan.localColPropName]) columns[plan.localColPropName] = allColumns[plan.localColPropName];
|
|
7074
7132
|
}
|
|
7133
|
+
for (const name of selfTargetingPlan(tablePlans, tableName, ops)?.targetPrimaryKey ?? []) if (!(name in columns) && allColumns[name]) columns[name] = allColumns[name];
|
|
7075
7134
|
return columns;
|
|
7076
7135
|
},
|
|
7136
|
+
createsParentSide: (tableName, ops) => Object.entries(ops).some(([relationName, op]) => plans[tableName]?.[relationName]?.fkSide === "parent" && supplied(op?.["create"])),
|
|
7137
|
+
refreshed: async (executor, tableName, ops, rows) => {
|
|
7138
|
+
const plan = selfTargetingPlan(plans[tableName], tableName, ops);
|
|
7139
|
+
const pk = plan?.targetPrimaryKey ?? [];
|
|
7140
|
+
if (!plan || !pk.length || !rows.length || !pk.every((name) => name in rows[0])) return rows;
|
|
7141
|
+
const columns = getColumns(plan.targetTable);
|
|
7142
|
+
const selection = Object.fromEntries(Object.keys(rows[0]).filter((name) => columns[name]).map((name) => [name, columns[name]]));
|
|
7143
|
+
const keyOf = (row) => pk.map((name) => String(row[name])).join("\0");
|
|
7144
|
+
const match = pk.length === 1 ? inArray(columns[pk[0]], rows.map((row) => row[pk[0]])) : or(...rows.map((row) => and(...pk.map((name) => eq(columns[name], row[name])))));
|
|
7145
|
+
const fresh = new Map((await executor.select(selection).from(plan.targetTable).where(match)).map((row) => [keyOf(row), row]));
|
|
7146
|
+
return rows.map((row) => ({
|
|
7147
|
+
...row,
|
|
7148
|
+
...fresh.get(keyOf(row))
|
|
7149
|
+
}));
|
|
7150
|
+
},
|
|
7077
7151
|
applyParentSide: async (executor, tableName, ops, request) => {
|
|
7078
7152
|
const tablePlans = plans[tableName];
|
|
7079
7153
|
const patch = {};
|
|
@@ -7098,10 +7172,11 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7098
7172
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
7099
7173
|
} else if (op["connect"] !== void 0) {
|
|
7100
7174
|
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
7101
|
-
if (rows.length !== 1) throw
|
|
7175
|
+
if (rows.length !== 1) throw connectMatchError(relationName, rows.length);
|
|
7102
7176
|
patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
|
|
7103
7177
|
} else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
|
|
7104
7178
|
}
|
|
7179
|
+
for (const columnName of Object.keys(contextValues?.(tableName) ?? {})) delete patch[columnName];
|
|
7105
7180
|
return patch;
|
|
7106
7181
|
},
|
|
7107
7182
|
applyChildSide: async (executor, tableName, ops, parentRows, request) => {
|
|
@@ -7119,8 +7194,9 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7119
7194
|
for (const parentRow of parentRows) {
|
|
7120
7195
|
const key = parentRow?.[plan.localColPropName];
|
|
7121
7196
|
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" });
|
|
7197
|
+
if (plan.parentShows && !await plan.parentShows(executor, key)) throw drizzleError(`Drizzle-GraphQL Error: cannot write through '${relationName}': its \`where\` hides the relation on this ${tableName} row.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
7122
7198
|
if (plan.through) {
|
|
7123
|
-
await applyThroughOps(executor, plan, key, op, scope, steps);
|
|
7199
|
+
await applyThroughOps(executor, plan, key, op, scope, steps, context);
|
|
7124
7200
|
continue;
|
|
7125
7201
|
}
|
|
7126
7202
|
if (op["set"] !== void 0) {
|
|
@@ -7132,14 +7208,18 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7132
7208
|
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
|
|
7133
7209
|
} else for (const filter of asArray(op["disconnect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(and(eq(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope))), "disconnect", "update");
|
|
7134
7210
|
}
|
|
7211
|
+
if (plan.isOne && op["connect"] !== void 0) {
|
|
7212
|
+
const matched = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
7213
|
+
if (matched.length !== 1) throw connectMatchError(relationName, matched.length);
|
|
7214
|
+
}
|
|
7135
7215
|
const attachedBy = attachingOp(op);
|
|
7136
7216
|
if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
|
|
7137
7217
|
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");
|
|
7138
7218
|
if (op["create"] !== void 0) {
|
|
7139
|
-
const rows = asArray(op["create"]).map((row) => ({
|
|
7140
|
-
...
|
|
7219
|
+
const rows = asArray(op["create"]).map((row) => applyContextValues({
|
|
7220
|
+
...remapFromGraphQLSingleInput({ ...row }, plan.targetTable),
|
|
7141
7221
|
[plan.foreignColPropName]: key
|
|
7142
|
-
}));
|
|
7222
|
+
}, contextValues?.(plan.targetTableName), context));
|
|
7143
7223
|
if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
|
|
7144
7224
|
}
|
|
7145
7225
|
}
|
|
@@ -7148,6 +7228,39 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7148
7228
|
}
|
|
7149
7229
|
};
|
|
7150
7230
|
};
|
|
7231
|
+
/** Thrown inside an entry's savepoint to undo a parent-side `create` whose parent was never written. */
|
|
7232
|
+
var NoParentRow = class extends Error {};
|
|
7233
|
+
/**
|
|
7234
|
+
* One parent write with its nested operations around it: the parent-side operations, whose
|
|
7235
|
+
* values `write` receives as a patch, then the write, then the child-side operations — and
|
|
7236
|
+
* the rows it returns read back when a child-side statement may have changed them.
|
|
7237
|
+
*
|
|
7238
|
+
* A parent write can come back empty: an update whose `where` matched nothing, an insert or
|
|
7239
|
+
* upsert that a conflict turned into a no-op. Anything a parent-side `create` inserted would
|
|
7240
|
+
* then be left with nothing pointing at it, so such an entry runs in a savepoint of its own
|
|
7241
|
+
* that is rolled back when the write produced no row, and the entry returns no rows — as it
|
|
7242
|
+
* would have without the nested operation. The rest of the batch is unaffected.
|
|
7243
|
+
*
|
|
7244
|
+
* `executor` must already be a transaction.
|
|
7245
|
+
*/
|
|
7246
|
+
const writeNestedEntry = async (params) => {
|
|
7247
|
+
const { executor, runtime, tableName, ops, write, request } = params;
|
|
7248
|
+
const guarded = runtime.createsParentSide(tableName, ops);
|
|
7249
|
+
const run = async (tx) => {
|
|
7250
|
+
const patch = await runtime.applyParentSide(tx, tableName, ops, request);
|
|
7251
|
+
const rows = await write(tx, patch);
|
|
7252
|
+
if (guarded && !rows.length) throw new NoParentRow();
|
|
7253
|
+
await runtime.applyChildSide(tx, tableName, ops, rows, request);
|
|
7254
|
+
return runtime.refreshed(tx, tableName, ops, rows);
|
|
7255
|
+
};
|
|
7256
|
+
if (!guarded) return run(executor);
|
|
7257
|
+
try {
|
|
7258
|
+
return await executor.transaction(run);
|
|
7259
|
+
} catch (error) {
|
|
7260
|
+
if (error instanceof NoParentRow) return [];
|
|
7261
|
+
throw error;
|
|
7262
|
+
}
|
|
7263
|
+
};
|
|
7151
7264
|
/**
|
|
7152
7265
|
* Runs one parent write per input entry, with that entry's nested operations around it, all
|
|
7153
7266
|
* inside a single transaction — a savepoint when the caller already runs on one, so a failing
|
|
@@ -7166,12 +7279,17 @@ const writeWithNestedOps = async (params) => {
|
|
|
7166
7279
|
return executor.transaction(async (tx) => {
|
|
7167
7280
|
const rows = [];
|
|
7168
7281
|
for (const entry of entries) {
|
|
7169
|
-
const
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7282
|
+
const written = await writeNestedEntry({
|
|
7283
|
+
executor: tx,
|
|
7284
|
+
runtime,
|
|
7285
|
+
tableName,
|
|
7286
|
+
ops: entry.ops,
|
|
7287
|
+
write: (target, patch) => write(target, {
|
|
7288
|
+
...remapValues(entry.columns),
|
|
7289
|
+
...patch
|
|
7290
|
+
}),
|
|
7291
|
+
request
|
|
7173
7292
|
});
|
|
7174
|
-
await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
|
|
7175
7293
|
rows.push(...written);
|
|
7176
7294
|
}
|
|
7177
7295
|
return rows;
|
|
@@ -7184,15 +7302,17 @@ const writeWithNestedOps = async (params) => {
|
|
|
7184
7302
|
*/
|
|
7185
7303
|
const updateWithNestedOps = async (params) => {
|
|
7186
7304
|
const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
|
|
7187
|
-
return executor.transaction(
|
|
7188
|
-
|
|
7189
|
-
|
|
7305
|
+
return executor.transaction((tx) => writeNestedEntry({
|
|
7306
|
+
executor: tx,
|
|
7307
|
+
runtime,
|
|
7308
|
+
tableName,
|
|
7309
|
+
ops,
|
|
7310
|
+
write: (target, patch) => write(target, {
|
|
7190
7311
|
...remapValues(columns),
|
|
7191
7312
|
...patch
|
|
7192
|
-
})
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
});
|
|
7313
|
+
}),
|
|
7314
|
+
request
|
|
7315
|
+
}));
|
|
7196
7316
|
};
|
|
7197
7317
|
//#endregion
|
|
7198
7318
|
//#region src/util/builders/errors.ts
|
|
@@ -7399,6 +7519,10 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7399
7519
|
console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
|
|
7400
7520
|
}
|
|
7401
7521
|
}
|
|
7522
|
+
if (nestedPlans) {
|
|
7523
|
+
const omitted = dropContextValueRelations(nestedPlans, contextValuesOf);
|
|
7524
|
+
if (omitted.length) console.warn(`Drizzle-GraphQL Warning: config.contextValues — nestedWrites leaves out the relations whose foreign key the server stamps from context: ${omitted.join(", ")}.`);
|
|
7525
|
+
}
|
|
7402
7526
|
const nestedTypes = nestedPlans ? createNestedWriteTypes({
|
|
7403
7527
|
plans: nestedPlans,
|
|
7404
7528
|
cacheCtx,
|
|
@@ -8712,16 +8836,24 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8712
8836
|
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
|
|
8713
8837
|
const results = [];
|
|
8714
8838
|
for (const entry of entries) {
|
|
8715
|
-
const
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8839
|
+
const write = async (target, patch) => {
|
|
8840
|
+
const values = {
|
|
8841
|
+
...entry.set,
|
|
8842
|
+
...patch
|
|
8843
|
+
};
|
|
8844
|
+
const writes = Object.keys(values).length > 0;
|
|
8845
|
+
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
8846
|
+
if (entry.filters) query = query.where(entry.filters);
|
|
8847
|
+
return await (writes ? query.returning(returning) : query);
|
|
8848
|
+
};
|
|
8849
|
+
results.push(entry.ops ? await writeNestedEntry({
|
|
8850
|
+
executor: tx,
|
|
8851
|
+
runtime: nested,
|
|
8852
|
+
tableName,
|
|
8853
|
+
ops: entry.ops,
|
|
8854
|
+
write,
|
|
8855
|
+
request
|
|
8856
|
+
}) : await write(tx, {}));
|
|
8725
8857
|
}
|
|
8726
8858
|
return results;
|
|
8727
8859
|
});
|
|
@@ -8853,16 +8985,24 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
|
|
|
8853
8985
|
return query.returning(returning).all();
|
|
8854
8986
|
};
|
|
8855
8987
|
const runNestedEntry = async (tx, entry) => {
|
|
8856
|
-
const
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8988
|
+
const write = async (target, patch) => {
|
|
8989
|
+
const values = {
|
|
8990
|
+
...entry.set,
|
|
8991
|
+
...patch
|
|
8992
|
+
};
|
|
8993
|
+
const writes = Object.keys(values).length > 0;
|
|
8994
|
+
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
8995
|
+
if (entry.filters) query = query.where(entry.filters);
|
|
8996
|
+
return await (writes ? query.returning(returning) : query);
|
|
8997
|
+
};
|
|
8998
|
+
return entry.ops ? writeNestedEntry({
|
|
8999
|
+
executor: tx,
|
|
9000
|
+
runtime: nested,
|
|
9001
|
+
tableName,
|
|
9002
|
+
ops: entry.ops,
|
|
9003
|
+
write,
|
|
9004
|
+
request
|
|
9005
|
+
}) : write(tx, {});
|
|
8866
9006
|
};
|
|
8867
9007
|
return executor.transaction((tx) => {
|
|
8868
9008
|
if (anyNested) return (async () => {
|