@vantreeseba/drizzle-graphql 13.1.0 → 13.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -2
- package/dist/index.cjs +248 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +248 -187
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1623,9 +1623,27 @@ mutation {
|
|
|
1623
1623
|
- Both return `Int!` — the number of rows the write affected — and add no new output type
|
|
1624
1624
|
to the schema
|
|
1625
1625
|
- Arguments are exactly the plural mutation's, so a client swaps one field name for the
|
|
1626
|
-
other; `features.requireWhere` applies to them the same way
|
|
1626
|
+
other; `features.requireWhere` applies to them the same way, and on a soft-deleting
|
|
1627
|
+
table that opted into `hardDelete` the delete takes `hard` too
|
|
1627
1628
|
- They follow the feature switch of the write they mirror: `updateCount` needs
|
|
1628
1629
|
`features.update`, `deleteCount` needs `features.delete`
|
|
1630
|
+
- They obey the same row policies as the plural write — only the answer differs:
|
|
1631
|
+
- [`scope`](#row-scoping-and-multi-tenancy) is ANDed into the `where`, so they reach,
|
|
1632
|
+
and count, only in-scope rows
|
|
1633
|
+
- on a [soft-deleting](#soft-delete) table `delete<Table>Count` marks the rows rather
|
|
1634
|
+
than removing them, and neither mutation reaches a row that is already marked
|
|
1635
|
+
- a [`contextValues`](#row-scoping-and-multi-tenancy) column is never reassigned by the
|
|
1636
|
+
update
|
|
1637
|
+
- [`onWrite`](#write-hooks) hooks run `before` and `after`, in a transaction, with the
|
|
1638
|
+
plural write's payload (`operation: 'update'` / `'delete'`, `single: false`). An
|
|
1639
|
+
`after` hook is handed rows, so a table with one gives up the returnless write: the
|
|
1640
|
+
statement `RETURNING`s the hook's `columns`, or just the primary key when it names
|
|
1641
|
+
none (every column, on a table without a key), and the count is the number of rows
|
|
1642
|
+
returned. Without an `after` hook there is no `RETURNING` at all. On MySQL the hook
|
|
1643
|
+
gets `rows: []`, as it does on every MySQL mutation
|
|
1644
|
+
- Their [`extensions.drizzle`](#identifying-generated-fields) is the plural write's
|
|
1645
|
+
(`operation: 'update'` / `'delete'`, `targetArg: 'where'`) plus `count: true`, so a
|
|
1646
|
+
wrapper that gates writes on `operation` covers them too
|
|
1629
1647
|
- **MySQL** gets them too, and they are arguably most useful there: MySQL's other mutations
|
|
1630
1648
|
return only `MutationReturn { isSuccess }`, so a row count is strictly more information
|
|
1631
1649
|
- `update<Table>Count` **rejects** nested writes with a `GraphQLError` — a nested write
|
|
@@ -2122,6 +2140,9 @@ onWrite: {
|
|
|
2122
2140
|
works.
|
|
2123
2141
|
- The columns are added to `RETURNING` for every write the hook is attached to: insert, upsert,
|
|
2124
2142
|
update, `updateMany`, delete and restore. A delete's rows are the rows it removed.
|
|
2143
|
+
- The [count mutations](#row-counts-instead-of-rows) fire the hooks as `update` / `delete`.
|
|
2144
|
+
The client selects nothing there, so an `after` hook's rows hold exactly its `columns`, or
|
|
2145
|
+
just the primary key when it names none — the one `RETURNING` those mutations pay for.
|
|
2125
2146
|
- **The client's response is unchanged.** Columns the hook asked for but the client did not
|
|
2126
2147
|
select are taken back off before the rows are mapped, so they reach neither the response nor
|
|
2127
2148
|
a custom resolver calling the generated one. The hook gets its own copy of the rows.
|
|
@@ -2329,7 +2350,9 @@ the check to make before treating a field as one of ours. Relation fields (and
|
|
|
2329
2350
|
naming the relation's **target** — so `Users.posts` reads as
|
|
2330
2351
|
`{ table: 'Posts', parentTable: 'Users', relation: 'posts' }`. Object types carry
|
|
2331
2352
|
`{ table, kind: 'type', primaryKey }`, which is what a stitching layer needs to map a type
|
|
2332
|
-
back to its source.
|
|
2353
|
+
back to its source. The [count mutations](#row-counts-instead-of-rows) carry their plural
|
|
2354
|
+
write's `operation` plus `count: true` — they write the same rows but resolve to an `Int!`, so
|
|
2355
|
+
a wrapper that reads the result as rows should check `count` first.
|
|
2333
2356
|
|
|
2334
2357
|
Because the row identity of a mutation lives in a different argument for every operation,
|
|
2335
2358
|
`identifyRows` reads whichever one applies and hands back primary-key values:
|
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
|
|
@@ -7016,7 +7041,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7016
7041
|
fieldName: updateCountFieldName,
|
|
7017
7042
|
requireWhere: tableFeatures.requireWhere,
|
|
7018
7043
|
filterCtx,
|
|
7019
|
-
txCtx: mutationTxCtx
|
|
7044
|
+
txCtx: mutationTxCtx,
|
|
7045
|
+
policies,
|
|
7046
|
+
returnsRows: false
|
|
7020
7047
|
}) : void 0;
|
|
7021
7048
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
7022
7049
|
db,
|
|
@@ -7027,7 +7054,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7027
7054
|
fieldName: deleteCountFieldName,
|
|
7028
7055
|
requireWhere: tableFeatures.requireWhere,
|
|
7029
7056
|
filterCtx,
|
|
7030
|
-
txCtx: mutationTxCtx
|
|
7057
|
+
txCtx: mutationTxCtx,
|
|
7058
|
+
policies,
|
|
7059
|
+
returnsRows: false
|
|
7031
7060
|
}) : void 0;
|
|
7032
7061
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7033
7062
|
const withMeta = (generated, meta) => ({
|
|
@@ -7097,12 +7126,25 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
7097
7126
|
{
|
|
7098
7127
|
generated: updateCountGenerated,
|
|
7099
7128
|
type: new graphql.GraphQLNonNull(graphql.GraphQLInt),
|
|
7129
|
+
meta: {
|
|
7130
|
+
operation: "update",
|
|
7131
|
+
single: false,
|
|
7132
|
+
targetArg: "where",
|
|
7133
|
+
count: true
|
|
7134
|
+
},
|
|
7100
7135
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7101
7136
|
},
|
|
7102
7137
|
{
|
|
7103
7138
|
generated: deleteCountGenerated,
|
|
7104
7139
|
type: new graphql.GraphQLNonNull(graphql.GraphQLInt),
|
|
7105
|
-
|
|
7140
|
+
meta: {
|
|
7141
|
+
operation: "delete",
|
|
7142
|
+
single: false,
|
|
7143
|
+
targetArg: "where",
|
|
7144
|
+
count: true,
|
|
7145
|
+
...hardDeleteMeta
|
|
7146
|
+
},
|
|
7147
|
+
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
7148
|
}
|
|
7107
7149
|
],
|
|
7108
7150
|
onConflictInput,
|
|
@@ -7603,7 +7645,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7603
7645
|
requireWhere: tableFeatures.requireWhere,
|
|
7604
7646
|
filterCtx,
|
|
7605
7647
|
txCtx: mutationTxCtx,
|
|
7606
|
-
nested: nestedRuntime
|
|
7648
|
+
nested: nestedRuntime,
|
|
7649
|
+
policies,
|
|
7650
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7651
|
+
returnsRows: true
|
|
7607
7652
|
}) : void 0;
|
|
7608
7653
|
const deleteCountGenerated = tableFeatures.delete && tableFeatures.countMutations ? generateWriteCount({
|
|
7609
7654
|
db,
|
|
@@ -7614,7 +7659,10 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7614
7659
|
fieldName: deleteCountFieldName,
|
|
7615
7660
|
requireWhere: tableFeatures.requireWhere,
|
|
7616
7661
|
filterCtx,
|
|
7617
|
-
txCtx: mutationTxCtx
|
|
7662
|
+
txCtx: mutationTxCtx,
|
|
7663
|
+
policies,
|
|
7664
|
+
pkNames: primaryKeyPropNames(schema[tableName]),
|
|
7665
|
+
returnsRows: true
|
|
7618
7666
|
}) : void 0;
|
|
7619
7667
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7620
7668
|
return {
|
|
@@ -7724,12 +7772,25 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7724
7772
|
{
|
|
7725
7773
|
generated: updateCountGenerated,
|
|
7726
7774
|
type: new graphql.GraphQLNonNull(graphql.GraphQLInt),
|
|
7775
|
+
meta: {
|
|
7776
|
+
operation: "update",
|
|
7777
|
+
single: false,
|
|
7778
|
+
targetArg: "where",
|
|
7779
|
+
count: true
|
|
7780
|
+
},
|
|
7727
7781
|
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7728
7782
|
},
|
|
7729
7783
|
{
|
|
7730
7784
|
generated: deleteCountGenerated,
|
|
7731
7785
|
type: new graphql.GraphQLNonNull(graphql.GraphQLInt),
|
|
7732
|
-
|
|
7786
|
+
meta: {
|
|
7787
|
+
operation: "delete",
|
|
7788
|
+
single: false,
|
|
7789
|
+
targetArg: "where",
|
|
7790
|
+
count: true,
|
|
7791
|
+
...hardDeleteMeta
|
|
7792
|
+
},
|
|
7793
|
+
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
7794
|
}
|
|
7734
7795
|
],
|
|
7735
7796
|
onConflictInput,
|