@vantreeseba/drizzle-graphql 15.1.2 → 15.1.3
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 +48 -3
- package/dist/index.cjs +180 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -4
- package/dist/index.d.ts +57 -4
- package/dist/index.js +181 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -249,6 +249,12 @@ Two details worth stating plainly:
|
|
|
249
249
|
- **Nothing changes below the schema.** An excluded column is still written by its
|
|
250
250
|
default, still read by the primary-key and cursor machinery, and still usable by your
|
|
251
251
|
own resolvers. Only the generated schema stops mentioning it.
|
|
252
|
+
- **A cursor carries the primary key in readable form.** A [cursor](#cursor-pagination)
|
|
253
|
+
is base64url JSON of the row's ordering values, and the primary key is always one of
|
|
254
|
+
them. Excluding the primary key removes the field, but anyone holding a cursor can still
|
|
255
|
+
decode the key's value. If the key itself must stay secret, wrap the cursors in your own
|
|
256
|
+
opaque or encrypted form. A `defaults.<table>.orderBy` that names an excluded column
|
|
257
|
+
throws at build time, because its values would ride along in every cursor too.
|
|
252
258
|
|
|
253
259
|
Names that match nothing throw at build time, so renaming a column fails the build instead
|
|
254
260
|
of quietly un-hiding it. Excluding a `NOT NULL` column with no default is allowed — the
|
|
@@ -291,6 +297,8 @@ narrow the scope, never widen it. It reaches every path that touches the table:
|
|
|
291
297
|
| `postsAggregate`, `postsGroupBy` | counted over in-scope rows only |
|
|
292
298
|
| `user.posts`, `user.postsAggregate` | scoped on both the batched and the eager (`with:`) path |
|
|
293
299
|
| cursor pages | the keyset page is taken within the scope |
|
|
300
|
+
| relation filters — `users(where: { posts: { some: … } })`, `none`, `every`, to-one, `.through()` | only in-scope posts can match, block `none` or fail `every` — on reads, aggregates, `groupBy`, and the `where` of updates and deletes |
|
|
301
|
+
| `orderBy` through a to-one — `comments(orderBy: { post: { title: … } })` | an out-of-scope post sorts as `NULL` |
|
|
294
302
|
| `updatePosts`, `updatePostsMany`, `deletePosts` | an out-of-scope row is simply not matched |
|
|
295
303
|
| `upsertPosts` (conflict branch) | a conflicting row outside the scope is left alone, not taken over |
|
|
296
304
|
| nested `connect` / `disconnect` / `set` | only in-scope rows can be attached or detached |
|
|
@@ -303,6 +311,11 @@ Two things it deliberately does not do:
|
|
|
303
311
|
- **It does not apply to a plain insert.** There is no existing row to filter. `contextValues`
|
|
304
312
|
is the write-side half.
|
|
305
313
|
|
|
314
|
+
A relation filter or an ordering through a relation sees exactly the rows the relation field
|
|
315
|
+
would return. Through a `.through()` relation, the junction table's own `scope` and soft delete
|
|
316
|
+
apply as well. A scope that returns a filter object is the server's own predicate, so the
|
|
317
|
+
relations it reaches through are read without their targets' scopes.
|
|
318
|
+
|
|
306
319
|
Returning `undefined` means *no restriction for this context*, so one hook can let an
|
|
307
320
|
administrator through:
|
|
308
321
|
|
|
@@ -477,6 +490,8 @@ What a declared table gets:
|
|
|
477
490
|
| `posts`, `postsSingle`, cursor pages | marked rows are not returned |
|
|
478
491
|
| `postsAggregate`, `postsGroupBy` | counted over unmarked rows only |
|
|
479
492
|
| `user.posts`, `user.postsAggregate` | hidden on both the batched and the eager (`with:`) path |
|
|
493
|
+
| relation filters — `users(where: { posts: { some: {} } })`, `none`, `every`, `.through()` junctions | a marked post neither matches nor blocks `none`, with the same default as the relation field (see below) |
|
|
494
|
+
| `orderBy` through a to-one | a marked target the relation field hides sorts as `NULL` |
|
|
480
495
|
| `updatePosts`, `updatePostsMany`, a second `deletePosts` | a marked row is simply not matched |
|
|
481
496
|
| nested `connect` / `disconnect` / `set` | a marked row cannot be attached or detached |
|
|
482
497
|
| `CreatePostsInput`, `UpdatePostsInput` | the marker column is **removed** — only the mutations write it |
|
|
@@ -540,6 +555,10 @@ The `deleted` argument is generated on relation fields either way, so a request
|
|
|
540
555
|
whichever default applies — `altKind(deleted: EXCLUDE)` under `'root'`, `altKind(deleted:
|
|
541
556
|
INCLUDE)` under `'all'`.
|
|
542
557
|
|
|
558
|
+
A relation filter (`items(where: { kind: { name: … } })`) and an `orderBy` through a to-one
|
|
559
|
+
use the same default as the relation field, so they match and sort over exactly the rows the
|
|
560
|
+
field shows. They take no `deleted` argument of their own.
|
|
561
|
+
|
|
543
562
|
`scope` changes reads only. A nested `connect` / `disconnect` / `set` still refuses to attach a
|
|
544
563
|
marked row under either setting, and the table's own writes are unaffected.
|
|
545
564
|
|
|
@@ -1495,6 +1514,12 @@ field, and passing it back as `after` resumes strictly after that row.
|
|
|
1495
1514
|
millisecond JS `Date` that `mode: 'date'` reads it into, so the cursor carries the
|
|
1496
1515
|
database's own text of the value instead, and rows a microsecond apart are still distinct
|
|
1497
1516
|
pages
|
|
1517
|
+
- A cursor is opaque by convention only. It is base64url-encoded JSON of the row's
|
|
1518
|
+
ordering values, primary key included, so a client can decode it and read those values,
|
|
1519
|
+
even for a primary key hidden by [`exclude`](#excluding-tables-and-columns). It is not
|
|
1520
|
+
signed or encrypted. If the ordering values must stay private, wrap the cursor in your
|
|
1521
|
+
own opaque or encrypted token before it leaves the server, and unwrap it on the way back
|
|
1522
|
+
in
|
|
1498
1523
|
- A cursor is only valid under the ordering it was issued for — reusing it with a
|
|
1499
1524
|
different `orderBy` (or a malformed/corrupted cursor) returns a `GraphQLError`
|
|
1500
1525
|
- `after` cannot be combined with `distinct`, with `matchFilterOrder`, or with an `orderBy`
|
|
@@ -2519,12 +2544,13 @@ import { resolveSelection, selectionToWith } from '@vantreeseba/drizzle-graphql'
|
|
|
2519
2544
|
const rows = await resolveSelection(info, {
|
|
2520
2545
|
db,
|
|
2521
2546
|
table: 'Users',
|
|
2547
|
+
context, // the resolver's context — required when the build configures `scope`
|
|
2522
2548
|
where: { id: { eq: userId } },
|
|
2523
2549
|
executor: tx, // defaults to `db`; pass a transaction to see its own writes
|
|
2524
2550
|
})
|
|
2525
2551
|
|
|
2526
2552
|
// Or just the `with:` tree, if you are building the query yourself.
|
|
2527
|
-
const withTree = selectionToWith(info, { db, table: 'Users' })
|
|
2553
|
+
const withTree = selectionToWith(info, { db, table: 'Users', context })
|
|
2528
2554
|
const row = await tx.query.Users.findFirst({ where: { id: userId }, with: withTree })
|
|
2529
2555
|
```
|
|
2530
2556
|
|
|
@@ -2535,8 +2561,27 @@ relation they select, compiles each relation's `where` / `orderBy` / `limit` / `
|
|
|
2535
2561
|
into its nested entry, and leaves `<relation>Aggregate` selections out — they are not
|
|
2536
2562
|
`with:` entries. It returns `undefined` when the selection asks for no relations.
|
|
2537
2563
|
|
|
2538
|
-
`resolveSelection` additionally accepts `single`, `orderBy`, `limit`, `offset` and
|
|
2539
|
-
and returns the rows already remapped to their GraphQL transport forms.
|
|
2564
|
+
`resolveSelection` additionally accepts `single`, `orderBy`, `limit`, `offset`, `after` and
|
|
2565
|
+
`deleted`, and returns the rows already remapped to their GraphQL transport forms.
|
|
2566
|
+
|
|
2567
|
+
Both apply the build's row policies the way the generated fields do, so an override is not a
|
|
2568
|
+
way around them. `resolveSelection` scopes and soft-delete-filters the root table, applies the
|
|
2569
|
+
table's `limits` and `defaults.orderBy` to the root list, and applies all four to every nested
|
|
2570
|
+
relation. `selectionToWith` applies them to the nested relations in the tree it returns. The
|
|
2571
|
+
root query you run with it is yours to scope.
|
|
2572
|
+
|
|
2573
|
+
They find the build through the resolve info: the field's return type, the root table's type in
|
|
2574
|
+
the executing schema, or the parent type, whichever the build generated. That works for a schema
|
|
2575
|
+
assembled from `entities.types`, and for one a tool such as `mapSchema` rebuilt, because the
|
|
2576
|
+
build is found through each type's `extensions.drizzle`. Two things follow:
|
|
2577
|
+
|
|
2578
|
+
- **Pass `context`.** Scope hooks take the request context, which resolve info does not
|
|
2579
|
+
carry. When the build configures `scope`, a call without `context` throws instead of
|
|
2580
|
+
reading unscoped. Soft delete, limits and default ordering need no context.
|
|
2581
|
+
- **Pass `info`, not a parsed tree, when one database has several builds.** A parsed
|
|
2582
|
+
selection tree carries no type to find the build by. Over a database with exactly one
|
|
2583
|
+
build, that build's policies apply. With several builds, the call throws if any of them
|
|
2584
|
+
has policies, since picking one could apply the wrong policies.
|
|
2540
2585
|
|
|
2541
2586
|
Both take the naming options the build used — `typeNameMapper`, `derivedTypeNameMapper`,
|
|
2542
2587
|
`typeNamePrefix` and `typeNameSuffix`. The translation matches selections against the
|
package/dist/index.cjs
CHANGED
|
@@ -2007,6 +2007,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
|
|
|
2007
2007
|
*/
|
|
2008
2008
|
const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
2009
2009
|
/**
|
|
2010
|
+
* One alias counter per build, shared by every table's narrowed context. A statement can
|
|
2011
|
+
* combine subqueries compiled from different contexts — a relation filter on one table whose
|
|
2012
|
+
* target's scope is a filter object reaching through another relation, say — and nested
|
|
2013
|
+
* subqueries that drew the same alias from two separate counters would shadow each other.
|
|
2014
|
+
*/
|
|
2015
|
+
const aliasCounters = /* @__PURE__ */ new WeakMap();
|
|
2016
|
+
/**
|
|
2010
2017
|
* Narrows the build-scoped relation filter context to the table a resolver is filtering.
|
|
2011
2018
|
*
|
|
2012
2019
|
* Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
|
|
@@ -2020,7 +2027,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
|
2020
2027
|
* aliases only ever have to be unique *within* one statement, which a number that never
|
|
2021
2028
|
* repeats gives more surely than one that restarts.
|
|
2022
2029
|
*/
|
|
2023
|
-
const relationFilterCtx = (base, tableKey) => {
|
|
2030
|
+
const relationFilterCtx = (base, tableKey, scope) => {
|
|
2024
2031
|
if (!base) return;
|
|
2025
2032
|
let byTable = narrowedCtxCache.get(base);
|
|
2026
2033
|
if (!byTable) {
|
|
@@ -2029,13 +2036,33 @@ const relationFilterCtx = (base, tableKey) => {
|
|
|
2029
2036
|
}
|
|
2030
2037
|
let ctx = byTable.get(tableKey);
|
|
2031
2038
|
if (!ctx) {
|
|
2039
|
+
let aliases = aliasCounters.get(base);
|
|
2040
|
+
if (!aliases) {
|
|
2041
|
+
aliases = { n: 0 };
|
|
2042
|
+
aliasCounters.set(base, aliases);
|
|
2043
|
+
}
|
|
2032
2044
|
ctx = {
|
|
2033
2045
|
...base,
|
|
2034
|
-
tableKey
|
|
2046
|
+
tableKey,
|
|
2047
|
+
aliases
|
|
2035
2048
|
};
|
|
2036
2049
|
byTable.set(tableKey, ctx);
|
|
2037
2050
|
}
|
|
2038
|
-
return
|
|
2051
|
+
return scope ? {
|
|
2052
|
+
...ctx,
|
|
2053
|
+
scope
|
|
2054
|
+
} : ctx;
|
|
2055
|
+
};
|
|
2056
|
+
/**
|
|
2057
|
+
* The row-policy predicate a subquery over a relation's target applies: the target's scope
|
|
2058
|
+
* and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
|
|
2059
|
+
* relation filter or a relation ordering sees exactly the rows the relation field returns.
|
|
2060
|
+
*/
|
|
2061
|
+
const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
|
|
2062
|
+
const scope = ctx.scope;
|
|
2063
|
+
if (!scope) return;
|
|
2064
|
+
const mode = scope.relationDefault(targetTableName, (0, drizzle_orm.is)(relation, drizzle_orm.One) && relation.optional === false);
|
|
2065
|
+
return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
|
|
2039
2066
|
};
|
|
2040
2067
|
/**
|
|
2041
2068
|
* Correlates the parent row with the aliased target table using the relation's own join
|
|
@@ -2126,8 +2153,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2126
2153
|
const throughTable = relation.throughTable;
|
|
2127
2154
|
let fromClause;
|
|
2128
2155
|
let joinCondition;
|
|
2156
|
+
let junctionPolicy;
|
|
2129
2157
|
if (throughTable) {
|
|
2130
2158
|
const aliasedThrough = (0, drizzle_orm.aliasedTable)(throughTable, `dgql_rel_${aliases.n++}`);
|
|
2159
|
+
const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
|
|
2160
|
+
if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
|
|
2131
2161
|
const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
|
|
2132
2162
|
fromClause = drizzle_orm.sql`${(0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)} inner join ${(0, drizzle_orm.getTableAsAliasSQL)(aliasedThrough)} on ${junctionJoin}`;
|
|
2133
2163
|
joinCondition = correlation;
|
|
@@ -2137,6 +2167,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2137
2167
|
}
|
|
2138
2168
|
const ownWhere = relationOwnWhere(relation);
|
|
2139
2169
|
const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2170
|
+
const policy = (0, drizzle_orm.and)(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
|
|
2140
2171
|
const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
|
|
2141
2172
|
...ctx,
|
|
2142
2173
|
tableKey: targetTableName,
|
|
@@ -2144,9 +2175,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2144
2175
|
}) : void 0;
|
|
2145
2176
|
if (mode === "every") {
|
|
2146
2177
|
if (!inner) return;
|
|
2147
|
-
return drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${(0, drizzle_orm.and)(joinCondition, relationWhere, (0, drizzle_orm.not)(inner))})`;
|
|
2178
|
+
return drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${(0, drizzle_orm.and)(joinCondition, relationWhere, policy, (0, drizzle_orm.not)(inner))})`;
|
|
2148
2179
|
}
|
|
2149
|
-
const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, inner);
|
|
2180
|
+
const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, policy, inner);
|
|
2150
2181
|
return mode === "none" ? drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${condition})` : drizzle_orm.sql`exists (select 1 from ${fromClause} where ${condition})`;
|
|
2151
2182
|
};
|
|
2152
2183
|
/**
|
|
@@ -2672,12 +2703,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
|
|
|
2672
2703
|
const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
|
|
2673
2704
|
const ownWhere = relationOwnWhere(relation);
|
|
2674
2705
|
const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2706
|
+
const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
|
|
2675
2707
|
return {
|
|
2676
2708
|
fromParts: [...chain?.fromParts ?? [], (0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)],
|
|
2677
2709
|
conditions: [
|
|
2678
2710
|
...chain?.conditions ?? [],
|
|
2679
2711
|
...joinCondition ? [joinCondition] : [],
|
|
2680
|
-
...relationWhere ? [relationWhere] : []
|
|
2712
|
+
...relationWhere ? [relationWhere] : [],
|
|
2713
|
+
...policy ? [policy] : []
|
|
2681
2714
|
],
|
|
2682
2715
|
table: aliasedTarget
|
|
2683
2716
|
};
|
|
@@ -3907,10 +3940,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3907
3940
|
});
|
|
3908
3941
|
const relWhere = relationArgs?.where;
|
|
3909
3942
|
const relDeleted = relationArgs?.deleted ?? scope?.relationDefault(targetTableName, (0, drizzle_orm.is)(relEntry.relation, drizzle_orm.One) && relEntry.relation.optional === false);
|
|
3910
|
-
thisRecord.where = relWhere || scope?.has(targetTableName, relDeleted) ? { RAW: (aliasedTable) => withScope(scope, targetTableName, aliasedTable, relWhere ? extractFilters(aliasedTable, relName, relWhere, relationFilterCtx(filterCtx, targetTableName)) : void 0, relDeleted) } : void 0;
|
|
3943
|
+
thisRecord.where = relWhere || scope?.has(targetTableName, relDeleted) ? { RAW: (aliasedTable) => withScope(scope, targetTableName, aliasedTable, relWhere ? extractFilters(aliasedTable, relName, relWhere, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, relDeleted) } : void 0;
|
|
3911
3944
|
const hasPagination = offset != null || limit != null;
|
|
3912
3945
|
const pkNames = targetPkNames ?? [];
|
|
3913
|
-
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3946
|
+
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3914
3947
|
thisRecord.offset = offset;
|
|
3915
3948
|
thisRecord.limit = limit;
|
|
3916
3949
|
thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
|
|
@@ -3968,7 +4001,7 @@ const runRelationalSelect = async (opts) => {
|
|
|
3968
4001
|
tableName,
|
|
3969
4002
|
distinct,
|
|
3970
4003
|
pkNames: pkNames ?? [],
|
|
3971
|
-
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
4004
|
+
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
|
|
3972
4005
|
orderBy,
|
|
3973
4006
|
limit: single ? 1 : opts.limit,
|
|
3974
4007
|
offset
|
|
@@ -3983,8 +4016,8 @@ const runRelationalSelect = async (opts) => {
|
|
|
3983
4016
|
allRelations: filterCtx?.relationMap
|
|
3984
4017
|
}),
|
|
3985
4018
|
offset: distinctKeys ? void 0 : offset,
|
|
3986
|
-
orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
|
|
3987
|
-
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, (0, drizzle_orm.and)(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
|
|
4019
|
+
orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName, scope), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName, scope), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
|
|
4020
|
+
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, (0, drizzle_orm.and)(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
|
|
3988
4021
|
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3989
4022
|
typeNameMapper,
|
|
3990
4023
|
filterCtx,
|
|
@@ -4566,7 +4599,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
|
|
|
4566
4599
|
tableName,
|
|
4567
4600
|
table,
|
|
4568
4601
|
where,
|
|
4569
|
-
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
4602
|
+
relationCtx: relationFilterCtx(filterCtx, tableName, scope),
|
|
4570
4603
|
required: requireWhere,
|
|
4571
4604
|
deleted: hard ? "INCLUDE" : void 0
|
|
4572
4605
|
});
|
|
@@ -4972,7 +5005,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4972
5005
|
const executor = resolveExecutor(db, context);
|
|
4973
5006
|
const uniqueIds = [...new Set(parentIds)];
|
|
4974
5007
|
const scope = resolveScope(policies, context, filterCtx);
|
|
4975
|
-
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
5008
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
4976
5009
|
if (distinct) {
|
|
4977
5010
|
const keys = await selectDistinctKeys({
|
|
4978
5011
|
db: executor,
|
|
@@ -4997,10 +5030,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4997
5030
|
[PARENT_KEY]: groupCol
|
|
4998
5031
|
} : columns;
|
|
4999
5032
|
let rows;
|
|
5000
|
-
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName), whereArg, junction);
|
|
5033
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
|
|
5001
5034
|
else {
|
|
5002
5035
|
let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
|
|
5003
|
-
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
5036
|
+
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
|
|
5004
5037
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
5005
5038
|
rows = await q;
|
|
5006
5039
|
}
|
|
@@ -5680,6 +5713,7 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5680
5713
|
const normalized = {};
|
|
5681
5714
|
for (const [columnName, entry] of Object.entries(orderBy)) {
|
|
5682
5715
|
if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
|
|
5716
|
+
if (config?.exclude?.columns?.[tableName]?.includes(columnName)) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which config.exclude.columns hides. A default ordering is encoded into every row's cursor, so it can only use columns the schema exposes.`);
|
|
5683
5717
|
const direction = typeof entry === "string" ? entry : entry?.direction;
|
|
5684
5718
|
if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
|
|
5685
5719
|
const priority = typeof entry === "string" ? void 0 : entry?.priority;
|
|
@@ -6060,7 +6094,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
|
|
|
6060
6094
|
const request = parseAggregateRequest(info, target);
|
|
6061
6095
|
if (!Object.keys(request.selection).length) return {};
|
|
6062
6096
|
let query = resolveExecutor(db, context).select(request.selection).from(table);
|
|
6063
|
-
const
|
|
6097
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6098
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6064
6099
|
if (where) query = query.where(where);
|
|
6065
6100
|
const rows = await query;
|
|
6066
6101
|
return assembleAggregateRow(rows[0] ?? {}, request, target);
|
|
@@ -6137,7 +6172,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6137
6172
|
const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
|
|
6138
6173
|
const executor = resolveExecutor(db, context);
|
|
6139
6174
|
const uniqueIds = [...new Set(parentIds)];
|
|
6140
|
-
const
|
|
6175
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6176
|
+
const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
|
|
6141
6177
|
const selected = executor.select({
|
|
6142
6178
|
[GROUP_KEY]: groupCol,
|
|
6143
6179
|
...request.selection
|
|
@@ -6353,7 +6389,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6353
6389
|
...request.selection
|
|
6354
6390
|
};
|
|
6355
6391
|
let query = resolveExecutor(db, context).select(selection).from(table);
|
|
6356
|
-
const
|
|
6392
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6393
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6357
6394
|
if (where) query = query.where(where);
|
|
6358
6395
|
query = query.groupBy(...keyColumns.map(([, column]) => column));
|
|
6359
6396
|
const havingCondition = buildHavingCondition(args.having, columns);
|
|
@@ -6392,6 +6429,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6392
6429
|
};
|
|
6393
6430
|
};
|
|
6394
6431
|
//#endregion
|
|
6432
|
+
//#region src/util/builders/common/build-registry.ts
|
|
6433
|
+
/**
|
|
6434
|
+
* Keyed by the `extensions.drizzle` object of every object type the build generated, rather
|
|
6435
|
+
* than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
|
|
6436
|
+
* by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
|
|
6437
|
+
*/
|
|
6438
|
+
const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
|
|
6439
|
+
/**
|
|
6440
|
+
* What is known about the builds of one drizzle instance, for a caller with no resolve info to
|
|
6441
|
+
* look its build up by. Only a lone build is kept — with two or more there is no telling which
|
|
6442
|
+
* one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
|
|
6443
|
+
*/
|
|
6444
|
+
const buildsByDb = /* @__PURE__ */ new WeakMap();
|
|
6445
|
+
/** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
|
|
6446
|
+
const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
|
|
6447
|
+
/** Records a finished build against the object types it generated and the instance it read. */
|
|
6448
|
+
const registerBuildRuntime = (runtime, types) => {
|
|
6449
|
+
for (const type of types) {
|
|
6450
|
+
const meta = drizzleExtension(type);
|
|
6451
|
+
if (meta) runtimeByTypeMeta.set(meta, runtime);
|
|
6452
|
+
}
|
|
6453
|
+
const previous = buildsByDb.get(runtime.db);
|
|
6454
|
+
buildsByDb.set(runtime.db, previous ? {
|
|
6455
|
+
count: previous.count + 1,
|
|
6456
|
+
anyPolicies: previous.anyPolicies || hasPolicies(runtime)
|
|
6457
|
+
} : {
|
|
6458
|
+
count: 1,
|
|
6459
|
+
only: runtime,
|
|
6460
|
+
anyPolicies: hasPolicies(runtime)
|
|
6461
|
+
});
|
|
6462
|
+
};
|
|
6463
|
+
/** The build that generated `type`, when one did. */
|
|
6464
|
+
const runtimeForType = (type) => {
|
|
6465
|
+
const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
|
|
6466
|
+
return meta ? runtimeByTypeMeta.get(meta) : void 0;
|
|
6467
|
+
};
|
|
6468
|
+
/**
|
|
6469
|
+
* The build of `db` to use when no generated type identifies one: the only build, or none
|
|
6470
|
+
* when no build applies any policy. Throws when several builds exist and one of them would
|
|
6471
|
+
* change the read, since picking either could apply the wrong policies — or none.
|
|
6472
|
+
*/
|
|
6473
|
+
const runtimeForDb = (db, helper) => {
|
|
6474
|
+
const builds = buildsByDb.get(db);
|
|
6475
|
+
if (!builds) return;
|
|
6476
|
+
if (builds.count === 1) return builds.only;
|
|
6477
|
+
if (builds.anyPolicies) throw new Error(`Drizzle-GraphQL Error: ${helper} cannot tell which of the ${builds.count} schemas built over this database the selection belongs to, and at least one applies row policies (scope, softDelete, limits or defaults). Pass the resolver's \`info\` rather than a parsed selection tree, from a field whose type the build generated.`);
|
|
6478
|
+
};
|
|
6479
|
+
//#endregion
|
|
6395
6480
|
//#region src/util/builders/nested-writes.ts
|
|
6396
6481
|
const ALL_OPS = [
|
|
6397
6482
|
"create",
|
|
@@ -6884,7 +6969,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6884
6969
|
* nothing is rejected: `connect: {}` would otherwise attach every row in the table.
|
|
6885
6970
|
*/
|
|
6886
6971
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
6887
|
-
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
|
|
6972
|
+
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
6888
6973
|
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" });
|
|
6889
6974
|
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
6890
6975
|
};
|
|
@@ -7495,6 +7580,14 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7495
7580
|
}
|
|
7496
7581
|
if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
|
|
7497
7582
|
}
|
|
7583
|
+
registerBuildRuntime({
|
|
7584
|
+
db: ctx.db,
|
|
7585
|
+
filterCtx: ctx.filterCtx,
|
|
7586
|
+
policies: ctx.policies,
|
|
7587
|
+
scoped: !!ctx.tablePolicies?.scope,
|
|
7588
|
+
limits: ctx.limits,
|
|
7589
|
+
nullOrdering: adapter.nullOrdering
|
|
7590
|
+
}, Object.values(ctx.outputs));
|
|
7498
7591
|
return {
|
|
7499
7592
|
queries: ctx.queries,
|
|
7500
7593
|
mutations: ctx.mutations,
|
|
@@ -7617,7 +7710,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
|
|
|
7617
7710
|
const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
|
|
7618
7711
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7619
7712
|
await before();
|
|
7620
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7713
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7621
7714
|
const filters = scopedWhere({
|
|
7622
7715
|
scope,
|
|
7623
7716
|
tableName,
|
|
@@ -7663,7 +7756,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
|
|
|
7663
7756
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7664
7757
|
return {
|
|
7665
7758
|
set: input,
|
|
7666
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
7759
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
7667
7760
|
};
|
|
7668
7761
|
});
|
|
7669
7762
|
await executor.transaction(async (tx) => {
|
|
@@ -7706,7 +7799,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
7706
7799
|
const { where } = args;
|
|
7707
7800
|
const hard = canHardDelete && args.hard === true;
|
|
7708
7801
|
const scope = policies?.scope?.(context);
|
|
7709
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7802
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7710
7803
|
const filters = scopedWhere({
|
|
7711
7804
|
scope,
|
|
7712
7805
|
tableName,
|
|
@@ -8048,7 +8141,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8048
8141
|
excludedRef: excludedColumnRef,
|
|
8049
8142
|
withTarget: true,
|
|
8050
8143
|
serverOwned: Object.keys(contextColumns ?? {}),
|
|
8051
|
-
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
|
|
8144
|
+
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
|
|
8052
8145
|
});
|
|
8053
8146
|
const runBatch = async (executor, { values: rows, plan }) => {
|
|
8054
8147
|
let query = executor.insert(table).values(rows).returning(returning);
|
|
@@ -8130,7 +8223,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8130
8223
|
const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
|
|
8131
8224
|
const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
8132
8225
|
if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
8133
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
8226
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8134
8227
|
const filters = scopedWhere({
|
|
8135
8228
|
scope,
|
|
8136
8229
|
tableName,
|
|
@@ -8209,7 +8302,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8209
8302
|
await before();
|
|
8210
8303
|
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
8211
8304
|
const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
|
|
8212
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
8305
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8213
8306
|
const filters = scopedWhere({
|
|
8214
8307
|
scope,
|
|
8215
8308
|
tableName,
|
|
@@ -8660,7 +8753,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
8660
8753
|
return {
|
|
8661
8754
|
set: input,
|
|
8662
8755
|
ops,
|
|
8663
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
8756
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
8664
8757
|
};
|
|
8665
8758
|
});
|
|
8666
8759
|
const anyNested = entries.some((entry) => entry.ops);
|
|
@@ -8830,6 +8923,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
|
|
|
8830
8923
|
typeNameSuffix: options.typeNameSuffix
|
|
8831
8924
|
});
|
|
8832
8925
|
/**
|
|
8926
|
+
* The build whose policies the read applies. Looked up through the generated types the
|
|
8927
|
+
* resolve info reaches — the field's return type, the root table's type in the executing
|
|
8928
|
+
* schema, the parent type — so a resolver over a generated type finds its own build. Without
|
|
8929
|
+
* resolve info (a parsed tree was passed), the instance's only build is used.
|
|
8930
|
+
*/
|
|
8931
|
+
const runtimeFor = (info, db, typeName, helper) => {
|
|
8932
|
+
if (!("fieldsByTypeName" in info)) {
|
|
8933
|
+
const candidates = [
|
|
8934
|
+
info.returnType ? (0, graphql.getNamedType)(info.returnType) : void 0,
|
|
8935
|
+
info.schema?.getType(typeName),
|
|
8936
|
+
info.parentType
|
|
8937
|
+
];
|
|
8938
|
+
for (const candidate of candidates) {
|
|
8939
|
+
const runtime = runtimeForType(candidate);
|
|
8940
|
+
if (runtime && runtime.db === db) return runtime;
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
return runtimeForDb(db, helper);
|
|
8944
|
+
};
|
|
8945
|
+
/**
|
|
8946
|
+
* The request's row policies under `runtime`, bound to the context the caller passed. A build
|
|
8947
|
+
* with `scope` hooks cannot be read without that context: the hooks would be called with
|
|
8948
|
+
* nothing, and running the read unscoped is the leak the option exists to prevent.
|
|
8949
|
+
*/
|
|
8950
|
+
const scopeFor = (runtime, options, helper) => {
|
|
8951
|
+
if (!runtime?.policies?.scope) return;
|
|
8952
|
+
if (runtime.scoped && !("context" in options)) throw new Error(`Drizzle-GraphQL Error: the schema this selection belongs to configures \`scope\`, so ${helper} needs the request context to apply it. Pass the resolver's context as the \`context\` option.`);
|
|
8953
|
+
return runtime.policies.scope(options.context);
|
|
8954
|
+
};
|
|
8955
|
+
/**
|
|
8833
8956
|
* Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
|
|
8834
8957
|
* inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
|
|
8835
8958
|
* these helpers) does not pay for the walk twice.
|
|
@@ -8856,6 +8979,10 @@ const parseInfo = (info) => {
|
|
|
8856
8979
|
* const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
|
|
8857
8980
|
* ```
|
|
8858
8981
|
*
|
|
8982
|
+
* Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
|
|
8983
|
+
* `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
|
|
8984
|
+
* through the resolve info; pass `context` when it configures `scope`.
|
|
8985
|
+
*
|
|
8859
8986
|
* Returns `undefined` when the selection asks for no relations, which is the value the
|
|
8860
8987
|
* query builder wants for "no `with` clause".
|
|
8861
8988
|
*/
|
|
@@ -8865,11 +8992,16 @@ const selectionToWith = (info, options) => {
|
|
|
8865
8992
|
const { tables, relationMap, filterCtx } = contextFor(db);
|
|
8866
8993
|
if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
|
|
8867
8994
|
if (!relationMap[table]) return;
|
|
8995
|
+
const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
|
|
8996
|
+
const runtime = runtimeFor(info, db, typeName, "selectionToWith");
|
|
8868
8997
|
const parsed = parseInfo(info);
|
|
8869
|
-
const params = extractRelationsParams(relationMap, tables, table, parsed,
|
|
8998
|
+
const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
|
|
8870
8999
|
typeNameMapper,
|
|
8871
|
-
filterCtx,
|
|
8872
|
-
resolveName
|
|
9000
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
9001
|
+
resolveName,
|
|
9002
|
+
scope: scopeFor(runtime, options, "selectionToWith"),
|
|
9003
|
+
limits: runtime?.limits,
|
|
9004
|
+
defaultOrderBy: runtime?.policies?.defaultOrderBy
|
|
8873
9005
|
});
|
|
8874
9006
|
return params && Object.keys(params).length ? params : void 0;
|
|
8875
9007
|
};
|
|
@@ -8881,14 +9013,16 @@ const selectionToWith = (info, options) => {
|
|
|
8881
9013
|
* This is what an override usually wants after it has done its own work:
|
|
8882
9014
|
*
|
|
8883
9015
|
* ```ts
|
|
8884
|
-
* const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
|
|
9016
|
+
* const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
|
|
8885
9017
|
* ```
|
|
8886
9018
|
*
|
|
8887
9019
|
* Relation arguments and aggregate fields keep behaving as they do in the generated schema,
|
|
8888
|
-
* because it is the generated schema's own code path.
|
|
9020
|
+
* because it is the generated schema's own code path. So do the build's row policies: the
|
|
9021
|
+
* root table and every nested relation are scoped and soft-delete-filtered, and the limit
|
|
9022
|
+
* policy and default ordering apply. Pass `context` when the build configures `scope`.
|
|
8889
9023
|
*/
|
|
8890
9024
|
const resolveSelection = async (info, options) => {
|
|
8891
|
-
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
|
|
9025
|
+
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
|
|
8892
9026
|
const resolveName = namingRuleOf(options);
|
|
8893
9027
|
const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
|
|
8894
9028
|
const table = tables[tableName];
|
|
@@ -8898,25 +9032,34 @@ const resolveSelection = async (info, options) => {
|
|
|
8898
9032
|
const source = executor ?? db;
|
|
8899
9033
|
const queryBase = source.query?.[tableName];
|
|
8900
9034
|
if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
|
|
9035
|
+
const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
|
|
9036
|
+
const runtime = runtimeFor(info, db, typeName, "resolveSelection");
|
|
9037
|
+
const scope = scopeFor(runtime, options, "resolveSelection");
|
|
9038
|
+
const defaultOrderBy = runtime?.policies?.defaultOrderBy;
|
|
8901
9039
|
return runRelationalSelect({
|
|
8902
9040
|
queryBase,
|
|
8903
9041
|
tables,
|
|
8904
9042
|
tableName,
|
|
8905
9043
|
table,
|
|
8906
9044
|
relationMap,
|
|
8907
|
-
typeName
|
|
9045
|
+
typeName,
|
|
8908
9046
|
typeNameMapper,
|
|
8909
9047
|
resolveName,
|
|
8910
9048
|
parsedInfo,
|
|
8911
9049
|
single,
|
|
8912
9050
|
where,
|
|
8913
|
-
orderBy,
|
|
8914
|
-
limit,
|
|
9051
|
+
orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
|
|
9052
|
+
limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
|
|
8915
9053
|
offset,
|
|
8916
9054
|
after,
|
|
8917
|
-
filterCtx,
|
|
9055
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
8918
9056
|
pkNames: primaryKeyOf(tableName),
|
|
8919
|
-
db: source
|
|
9057
|
+
db: source,
|
|
9058
|
+
scope,
|
|
9059
|
+
deleted,
|
|
9060
|
+
limits: runtime?.limits,
|
|
9061
|
+
defaultOrderBy,
|
|
9062
|
+
nullOrdering: runtime?.nullOrdering
|
|
8920
9063
|
});
|
|
8921
9064
|
};
|
|
8922
9065
|
//#endregion
|