@vantreeseba/drizzle-graphql 15.1.2 → 15.1.4
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 +54 -4
- package/dist/index.cjs +275 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -4
- package/dist/index.d.ts +62 -4
- package/dist/index.js +276 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Column, One, SQL, Table, aliasedTable, and, arrayContains, arrayOverlap
|
|
|
2
2
|
import { MySqlAsyncDatabase, MySqlDate, MySqlDateString, MySqlDateTime, MySqlDateTimeString, MySqlInt, MySqlSerial, MySqlTable, MySqlTimestamp, MySqlTimestampString, getTableConfig } from "drizzle-orm/mysql-core";
|
|
3
3
|
import { PgAsyncDatabase, PgDate, PgDateString, PgInteger, PgSerial, PgTable, PgTimestamp, PgTimestampString, PgUUID, getTableConfig as getTableConfig$1 } from "drizzle-orm/pg-core";
|
|
4
4
|
import { SQLiteAsyncDatabase, SQLiteInteger, SQLiteTable, SQLiteTimestamp, getTableConfig as getTableConfig$2 } from "drizzle-orm/sqlite-core";
|
|
5
|
-
import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, Kind, getArgumentValues } from "graphql";
|
|
5
|
+
import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, Kind, getArgumentValues, getNamedType } from "graphql";
|
|
6
6
|
import { GraphQLDateTime, GraphQLJSON, GraphQLUUID } from "graphql-scalars";
|
|
7
7
|
import pluralize from "pluralize";
|
|
8
8
|
//#region src/util/builders/common/errors.ts
|
|
@@ -1778,6 +1778,11 @@ const warnUnresolvedJoin = (relEntry, tableName, relationName, omitted) => {
|
|
|
1778
1778
|
* Returns the JS property name of the local column on the parent table and the
|
|
1779
1779
|
* Column object for the foreign column on the target table, or undefined if the
|
|
1780
1780
|
* relation internals are not accessible.
|
|
1781
|
+
*
|
|
1782
|
+
* Only the relation's *first* column pair is reported. A relation joined on several columns
|
|
1783
|
+
* (`from: [a, b], to: [x, y]`) is not described fully by it, so anything that reads rows by
|
|
1784
|
+
* this pair alone mixes up parents that share the first column. Read paths use
|
|
1785
|
+
* {@link extractRelationJoinColumnPairs}, which returns every pair.
|
|
1781
1786
|
*/
|
|
1782
1787
|
const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
|
|
1783
1788
|
const rel = relEntry.relation ?? relEntry;
|
|
@@ -1796,6 +1801,48 @@ const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
|
|
|
1796
1801
|
};
|
|
1797
1802
|
};
|
|
1798
1803
|
/**
|
|
1804
|
+
* Every column pair the relation joins on, in declaration order — one for an ordinary relation,
|
|
1805
|
+
* several for one declared `from: [a, b], to: [x, y]`. A row of the target belongs to a parent
|
|
1806
|
+
* only when *all* pairs match, so a read path has to correlate on every one of them.
|
|
1807
|
+
*
|
|
1808
|
+
* `undefined` when the relation's columns cannot be matched to the two tables, or when its two
|
|
1809
|
+
* sides list a different number of columns.
|
|
1810
|
+
*/
|
|
1811
|
+
const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
|
|
1812
|
+
const rel = relEntry.relation ?? relEntry;
|
|
1813
|
+
const sourceCols = relationColumns(rel, "source");
|
|
1814
|
+
const targetCols = relationColumns(rel, "target");
|
|
1815
|
+
if (!sourceCols?.length || sourceCols.length !== targetCols?.length) return;
|
|
1816
|
+
const parentEntries = Object.entries(getColumns(parentTable));
|
|
1817
|
+
const targetEntries = Object.entries(getColumns(targetTable));
|
|
1818
|
+
const pairs = [];
|
|
1819
|
+
for (let i = 0; i < sourceCols.length; i++) {
|
|
1820
|
+
const localCol = sourceCols[i];
|
|
1821
|
+
const foreignCol = targetCols[i];
|
|
1822
|
+
const localColPropName = parentEntries.find(([, c]) => c === localCol)?.[0];
|
|
1823
|
+
const foreignColPropName = targetEntries.find(([, c]) => c === foreignCol)?.[0];
|
|
1824
|
+
if (!localColPropName || !foreignColPropName) return;
|
|
1825
|
+
pairs.push({
|
|
1826
|
+
localColPropName,
|
|
1827
|
+
localCol,
|
|
1828
|
+
foreignColPropName,
|
|
1829
|
+
foreignCol
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
return pairs;
|
|
1833
|
+
};
|
|
1834
|
+
/**
|
|
1835
|
+
* A string identifying one combination of join-column values, for grouping a batch's rows by
|
|
1836
|
+
* parent. Values are compared by their string form, as a single-column key always has been.
|
|
1837
|
+
*/
|
|
1838
|
+
const joinKeyOf = (values) => values.length === 1 ? String(values[0]) : JSON.stringify(values.map((value) => String(value)));
|
|
1839
|
+
/**
|
|
1840
|
+
* The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
|
|
1841
|
+
* of per-parent equalities for several — `(a = 1 AND b = 1) OR (a = 1 AND b = 2)` — which
|
|
1842
|
+
* every dialect accepts, unlike a row-value `IN` list.
|
|
1843
|
+
*/
|
|
1844
|
+
const parentKeyCondition = (cols, keys) => cols.length === 1 ? inArray(cols[0], keys.map((key) => key[0])) : or(...keys.map((key) => and(...cols.map((col, i) => eq(col, key[i])))));
|
|
1845
|
+
/**
|
|
1799
1846
|
* The junction table and its two columns for a `.through()` (many-to-many) relation:
|
|
1800
1847
|
* `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
|
|
1801
1848
|
* with no junction; `null` for one whose junction cannot be used here — more than one column
|
|
@@ -1826,16 +1873,18 @@ const extractJunctionColumns = (relEntry) => {
|
|
|
1826
1873
|
*
|
|
1827
1874
|
* A reversed filter (inherited from the relation on the other side) is written against the
|
|
1828
1875
|
* parent table, so it is applied as "the row's parent key belongs to a parent that passes it".
|
|
1829
|
-
* `
|
|
1830
|
-
* the parent key for each row — the target's foreign key, or the junction's source
|
|
1876
|
+
* `parentKeyCols` are the parent's join columns and `groupCols` the columns in the query that
|
|
1877
|
+
* hold the parent key for each row — the target's foreign key, or the junction's source
|
|
1878
|
+
* column — in the same order. A key of several columns is compared as a row value.
|
|
1831
1879
|
*/
|
|
1832
|
-
const relationOwnCondition = (relEntry, parentTable, targetTable,
|
|
1880
|
+
const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCols, groupCols) => {
|
|
1833
1881
|
const rel = relEntry.relation ?? relEntry;
|
|
1834
1882
|
const ownWhere = relationOwnWhere(rel);
|
|
1835
1883
|
if (!ownWhere) return;
|
|
1836
1884
|
if (!isRelationFilterReversed(rel)) return relationsFilterToSQL(targetTable, ownWhere);
|
|
1837
1885
|
const parentFilter = relationsFilterToSQL(parentTable, ownWhere);
|
|
1838
|
-
return sql`${
|
|
1886
|
+
if (groupCols.length === 1) return sql`${groupCols[0]} in (select ${parentKeyCols[0]} from ${parentTable} where ${parentFilter})`;
|
|
1887
|
+
return sql`(${sql.join([...groupCols], sql`, `)}) in (select ${sql.join([...parentKeyCols], sql`, `)} from ${parentTable} where ${parentFilter})`;
|
|
1839
1888
|
};
|
|
1840
1889
|
/**
|
|
1841
1890
|
* Whether a to-one relation can back a relation orderBy hop. `.through()` (junction)
|
|
@@ -1983,6 +2032,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
|
|
|
1983
2032
|
*/
|
|
1984
2033
|
const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
1985
2034
|
/**
|
|
2035
|
+
* One alias counter per build, shared by every table's narrowed context. A statement can
|
|
2036
|
+
* combine subqueries compiled from different contexts — a relation filter on one table whose
|
|
2037
|
+
* target's scope is a filter object reaching through another relation, say — and nested
|
|
2038
|
+
* subqueries that drew the same alias from two separate counters would shadow each other.
|
|
2039
|
+
*/
|
|
2040
|
+
const aliasCounters = /* @__PURE__ */ new WeakMap();
|
|
2041
|
+
/**
|
|
1986
2042
|
* Narrows the build-scoped relation filter context to the table a resolver is filtering.
|
|
1987
2043
|
*
|
|
1988
2044
|
* Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
|
|
@@ -1996,7 +2052,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
|
1996
2052
|
* aliases only ever have to be unique *within* one statement, which a number that never
|
|
1997
2053
|
* repeats gives more surely than one that restarts.
|
|
1998
2054
|
*/
|
|
1999
|
-
const relationFilterCtx = (base, tableKey) => {
|
|
2055
|
+
const relationFilterCtx = (base, tableKey, scope) => {
|
|
2000
2056
|
if (!base) return;
|
|
2001
2057
|
let byTable = narrowedCtxCache.get(base);
|
|
2002
2058
|
if (!byTable) {
|
|
@@ -2005,13 +2061,33 @@ const relationFilterCtx = (base, tableKey) => {
|
|
|
2005
2061
|
}
|
|
2006
2062
|
let ctx = byTable.get(tableKey);
|
|
2007
2063
|
if (!ctx) {
|
|
2064
|
+
let aliases = aliasCounters.get(base);
|
|
2065
|
+
if (!aliases) {
|
|
2066
|
+
aliases = { n: 0 };
|
|
2067
|
+
aliasCounters.set(base, aliases);
|
|
2068
|
+
}
|
|
2008
2069
|
ctx = {
|
|
2009
2070
|
...base,
|
|
2010
|
-
tableKey
|
|
2071
|
+
tableKey,
|
|
2072
|
+
aliases
|
|
2011
2073
|
};
|
|
2012
2074
|
byTable.set(tableKey, ctx);
|
|
2013
2075
|
}
|
|
2014
|
-
return
|
|
2076
|
+
return scope ? {
|
|
2077
|
+
...ctx,
|
|
2078
|
+
scope
|
|
2079
|
+
} : ctx;
|
|
2080
|
+
};
|
|
2081
|
+
/**
|
|
2082
|
+
* The row-policy predicate a subquery over a relation's target applies: the target's scope
|
|
2083
|
+
* and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
|
|
2084
|
+
* relation filter or a relation ordering sees exactly the rows the relation field returns.
|
|
2085
|
+
*/
|
|
2086
|
+
const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
|
|
2087
|
+
const scope = ctx.scope;
|
|
2088
|
+
if (!scope) return;
|
|
2089
|
+
const mode = scope.relationDefault(targetTableName, is(relation, One) && relation.optional === false);
|
|
2090
|
+
return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
|
|
2015
2091
|
};
|
|
2016
2092
|
/**
|
|
2017
2093
|
* Correlates the parent row with the aliased target table using the relation's own join
|
|
@@ -2102,8 +2178,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2102
2178
|
const throughTable = relation.throughTable;
|
|
2103
2179
|
let fromClause;
|
|
2104
2180
|
let joinCondition;
|
|
2181
|
+
let junctionPolicy;
|
|
2105
2182
|
if (throughTable) {
|
|
2106
2183
|
const aliasedThrough = aliasedTable(throughTable, `dgql_rel_${aliases.n++}`);
|
|
2184
|
+
const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
|
|
2185
|
+
if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
|
|
2107
2186
|
const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
|
|
2108
2187
|
fromClause = sql`${getTableAsAliasSQL(aliasedTarget)} inner join ${getTableAsAliasSQL(aliasedThrough)} on ${junctionJoin}`;
|
|
2109
2188
|
joinCondition = correlation;
|
|
@@ -2113,6 +2192,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2113
2192
|
}
|
|
2114
2193
|
const ownWhere = relationOwnWhere(relation);
|
|
2115
2194
|
const relationWhere = ownWhere ? relationsFilterToSQL(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2195
|
+
const policy = and(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
|
|
2116
2196
|
const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
|
|
2117
2197
|
...ctx,
|
|
2118
2198
|
tableKey: targetTableName,
|
|
@@ -2120,9 +2200,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2120
2200
|
}) : void 0;
|
|
2121
2201
|
if (mode === "every") {
|
|
2122
2202
|
if (!inner) return;
|
|
2123
|
-
return sql`not exists (select 1 from ${fromClause} where ${and(joinCondition, relationWhere, not(inner))})`;
|
|
2203
|
+
return sql`not exists (select 1 from ${fromClause} where ${and(joinCondition, relationWhere, policy, not(inner))})`;
|
|
2124
2204
|
}
|
|
2125
|
-
const condition = and(joinCondition, relationWhere, inner);
|
|
2205
|
+
const condition = and(joinCondition, relationWhere, policy, inner);
|
|
2126
2206
|
return mode === "none" ? sql`not exists (select 1 from ${fromClause} where ${condition})` : sql`exists (select 1 from ${fromClause} where ${condition})`;
|
|
2127
2207
|
};
|
|
2128
2208
|
/**
|
|
@@ -2648,12 +2728,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
|
|
|
2648
2728
|
const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
|
|
2649
2729
|
const ownWhere = relationOwnWhere(relation);
|
|
2650
2730
|
const relationWhere = ownWhere ? relationsFilterToSQL(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2731
|
+
const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
|
|
2651
2732
|
return {
|
|
2652
2733
|
fromParts: [...chain?.fromParts ?? [], getTableAsAliasSQL(aliasedTarget)],
|
|
2653
2734
|
conditions: [
|
|
2654
2735
|
...chain?.conditions ?? [],
|
|
2655
2736
|
...joinCondition ? [joinCondition] : [],
|
|
2656
|
-
...relationWhere ? [relationWhere] : []
|
|
2737
|
+
...relationWhere ? [relationWhere] : [],
|
|
2738
|
+
...policy ? [policy] : []
|
|
2657
2739
|
],
|
|
2658
2740
|
table: aliasedTarget
|
|
2659
2741
|
};
|
|
@@ -3788,8 +3870,7 @@ const relationJoinColumns = (tree, table, selectionCtx) => {
|
|
|
3788
3870
|
const relEntry = fieldData.name.endsWith(AGGREGATE_FIELD_SUFFIX) ? relations[fieldData.name.slice(0, -9)] : relations[fieldData.name];
|
|
3789
3871
|
const targetTable = relEntry ? selectionCtx.tables[relEntry.targetTableName] : void 0;
|
|
3790
3872
|
if (!relEntry || !targetTable) continue;
|
|
3791
|
-
const
|
|
3792
|
-
if (joinCols) needed.push(joinCols.localColPropName);
|
|
3873
|
+
for (const pair of extractRelationJoinColumnPairs(relEntry, table, targetTable) ?? []) needed.push(pair.localColPropName);
|
|
3793
3874
|
}
|
|
3794
3875
|
return needed;
|
|
3795
3876
|
};
|
|
@@ -3883,10 +3964,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3883
3964
|
});
|
|
3884
3965
|
const relWhere = relationArgs?.where;
|
|
3885
3966
|
const relDeleted = relationArgs?.deleted ?? scope?.relationDefault(targetTableName, is(relEntry.relation, One) && relEntry.relation.optional === false);
|
|
3886
|
-
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;
|
|
3967
|
+
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;
|
|
3887
3968
|
const hasPagination = offset != null || limit != null;
|
|
3888
3969
|
const pkNames = targetPkNames ?? [];
|
|
3889
|
-
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3970
|
+
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3890
3971
|
thisRecord.offset = offset;
|
|
3891
3972
|
thisRecord.limit = limit;
|
|
3892
3973
|
thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
|
|
@@ -3944,7 +4025,7 @@ const runRelationalSelect = async (opts) => {
|
|
|
3944
4025
|
tableName,
|
|
3945
4026
|
distinct,
|
|
3946
4027
|
pkNames: pkNames ?? [],
|
|
3947
|
-
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
4028
|
+
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
|
|
3948
4029
|
orderBy,
|
|
3949
4030
|
limit: single ? 1 : opts.limit,
|
|
3950
4031
|
offset
|
|
@@ -3959,8 +4040,8 @@ const runRelationalSelect = async (opts) => {
|
|
|
3959
4040
|
allRelations: filterCtx?.relationMap
|
|
3960
4041
|
}),
|
|
3961
4042
|
offset: distinctKeys ? void 0 : offset,
|
|
3962
|
-
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,
|
|
3963
|
-
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, 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,
|
|
4043
|
+
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,
|
|
4044
|
+
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, 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,
|
|
3964
4045
|
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3965
4046
|
typeNameMapper,
|
|
3966
4047
|
filterCtx,
|
|
@@ -4542,7 +4623,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
|
|
|
4542
4623
|
tableName,
|
|
4543
4624
|
table,
|
|
4544
4625
|
where,
|
|
4545
|
-
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
4626
|
+
relationCtx: relationFilterCtx(filterCtx, tableName, scope),
|
|
4546
4627
|
required: requireWhere,
|
|
4547
4628
|
deleted: hard ? "INCLUDE" : void 0
|
|
4548
4629
|
});
|
|
@@ -4810,13 +4891,12 @@ const identifyRows = (field, args) => {
|
|
|
4810
4891
|
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
4811
4892
|
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
4812
4893
|
*/
|
|
4813
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
|
|
4894
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, partitionCols, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
|
|
4814
4895
|
const cols = columns ?? getColumns(targetTable);
|
|
4815
4896
|
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
4816
4897
|
const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
|
|
4817
4898
|
const RN = "__drizzle_graphql_rn";
|
|
4818
|
-
const
|
|
4819
|
-
const rowNumber = sql`row_number() over (partition by ${partitionCol}${orderClause})`.as(RN);
|
|
4899
|
+
const rowNumber = sql`row_number() over (partition by ${sql.join([...partitionCols], sql`, `)}${orderClause})`.as(RN);
|
|
4820
4900
|
const sub = withJunction(db.select({
|
|
4821
4901
|
...cols,
|
|
4822
4902
|
[RN]: rowNumber
|
|
@@ -4855,13 +4935,14 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4855
4935
|
const targetTableName = relEntry.targetTableName;
|
|
4856
4936
|
const targetTable = tables[targetTableName];
|
|
4857
4937
|
if (!parentTable || !targetTable) return;
|
|
4858
|
-
const
|
|
4859
|
-
if (!
|
|
4860
|
-
const
|
|
4938
|
+
const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
|
|
4939
|
+
if (!joinPairs) return;
|
|
4940
|
+
const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
|
|
4941
|
+
const foreignCol = joinPairs[0].foreignCol;
|
|
4861
4942
|
const junction = extractJunctionColumns(relEntry);
|
|
4862
|
-
const
|
|
4863
|
-
const
|
|
4864
|
-
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable,
|
|
4943
|
+
const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
|
|
4944
|
+
const groupKeys = junction ? [PARENT_KEY] : joinPairs.map((pair) => pair.foreignColPropName);
|
|
4945
|
+
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
|
|
4865
4946
|
const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, isOne && relEntry.relation?.optional === false);
|
|
4866
4947
|
const limitPolicy = isOne ? void 0 : limits?.(targetTableName);
|
|
4867
4948
|
const targetPkNames = relEntry.targetPkNames ?? [];
|
|
@@ -4890,7 +4971,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4890
4971
|
allRelations: filterCtx?.relationMap
|
|
4891
4972
|
});
|
|
4892
4973
|
const targetColumns = getColumns(targetTable);
|
|
4893
|
-
columns[foreignColPropName] = foreignCol;
|
|
4974
|
+
for (const pair of joinPairs) columns[pair.foreignColPropName] = pair.foreignCol;
|
|
4894
4975
|
for (const [columnName] of cursorEntries ?? []) {
|
|
4895
4976
|
const column = targetColumns[columnName];
|
|
4896
4977
|
if (column) columns[columnName] = column;
|
|
@@ -4902,8 +4983,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4902
4983
|
};
|
|
4903
4984
|
const resolve = async (parent, args, context, info) => {
|
|
4904
4985
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
4905
|
-
const
|
|
4906
|
-
if (
|
|
4986
|
+
const localValues = localColPropNames.map((name) => parent[name]);
|
|
4987
|
+
if (localValues.some((value) => value == null)) return isOne ? null : [];
|
|
4907
4988
|
const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
|
|
4908
4989
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4909
4990
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
@@ -4944,11 +5025,11 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4944
5025
|
};
|
|
4945
5026
|
});
|
|
4946
5027
|
if (junction === null) throw drizzleError(`Relation '${tableName}.${relationName}' goes through a junction with a composite key, which can only be loaded together with its parent. Leave it to eager loading (eagerLoadRelations).`, { code: "DRIZZLE_UNSUPPORTED_RELATION" });
|
|
4947
|
-
return getOrCreateLoader(context, loaderKey, async (
|
|
5028
|
+
return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
4948
5029
|
const executor = resolveExecutor(db, context);
|
|
4949
|
-
const
|
|
5030
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
4950
5031
|
const scope = resolveScope(policies, context, filterCtx);
|
|
4951
|
-
let whereCondition = withScope(scope, targetTableName, targetTable, and(
|
|
5032
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
4952
5033
|
if (distinct) {
|
|
4953
5034
|
const keys = await selectDistinctKeys({
|
|
4954
5035
|
db: executor,
|
|
@@ -4958,44 +5039,44 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4958
5039
|
pkNames: targetPkNames,
|
|
4959
5040
|
where: whereCondition,
|
|
4960
5041
|
orderBy: orderByArg,
|
|
4961
|
-
partitionBy:
|
|
5042
|
+
partitionBy: groupCols,
|
|
4962
5043
|
junction: junction ? {
|
|
4963
5044
|
table: junction.table,
|
|
4964
5045
|
on: eq(junction.targetCol, foreignCol),
|
|
4965
|
-
parentKey:
|
|
5046
|
+
parentKey: junction.sourceCol
|
|
4966
5047
|
} : void 0
|
|
4967
5048
|
});
|
|
4968
|
-
if (!keys.length) return
|
|
4969
|
-
whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(
|
|
5049
|
+
if (!keys.length) return parentKeys.map(() => isOne ? null : []);
|
|
5050
|
+
whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
4970
5051
|
}
|
|
4971
5052
|
const selection = junction ? {
|
|
4972
5053
|
...columns ?? getColumns(targetTable),
|
|
4973
|
-
[PARENT_KEY]:
|
|
5054
|
+
[PARENT_KEY]: junction.sourceCol
|
|
4974
5055
|
} : columns;
|
|
4975
5056
|
let rows;
|
|
4976
|
-
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName), whereArg, junction);
|
|
5057
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, groupCols, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
|
|
4977
5058
|
else {
|
|
4978
5059
|
let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
|
|
4979
|
-
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
5060
|
+
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
|
|
4980
5061
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
4981
5062
|
rows = await q;
|
|
4982
5063
|
}
|
|
4983
5064
|
if (cursorEntries) attachRowCursors(rows, cursorEntries);
|
|
4984
5065
|
const keyOf = (row) => {
|
|
4985
|
-
const key =
|
|
5066
|
+
const key = joinKeyOf(groupKeys.map((name) => row[name]));
|
|
4986
5067
|
if (junction) delete row[PARENT_KEY];
|
|
4987
5068
|
return key;
|
|
4988
5069
|
};
|
|
4989
5070
|
if (isOne) {
|
|
4990
5071
|
const byKey = new Map(rows.map((row) => [keyOf(row), row]));
|
|
4991
5072
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
4992
|
-
return
|
|
5073
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
|
|
4993
5074
|
}
|
|
4994
|
-
const grouped = new Map(
|
|
5075
|
+
const grouped = new Map(uniqueKeys.map((key) => [joinKeyOf(key), []]));
|
|
4995
5076
|
for (const row of rows) grouped.get(keyOf(row))?.push(row);
|
|
4996
5077
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
4997
|
-
return
|
|
4998
|
-
}).load(
|
|
5078
|
+
return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
|
|
5079
|
+
}).load(localValues);
|
|
4999
5080
|
};
|
|
5000
5081
|
return async (parent, args, context, info) => {
|
|
5001
5082
|
try {
|
|
@@ -5656,6 +5737,7 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5656
5737
|
const normalized = {};
|
|
5657
5738
|
for (const [columnName, entry] of Object.entries(orderBy)) {
|
|
5658
5739
|
if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
|
|
5740
|
+
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.`);
|
|
5659
5741
|
const direction = typeof entry === "string" ? entry : entry?.direction;
|
|
5660
5742
|
if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
|
|
5661
5743
|
const priority = typeof entry === "string" ? void 0 : entry?.priority;
|
|
@@ -6036,7 +6118,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
|
|
|
6036
6118
|
const request = parseAggregateRequest(info, target);
|
|
6037
6119
|
if (!Object.keys(request.selection).length) return {};
|
|
6038
6120
|
let query = resolveExecutor(db, context).select(request.selection).from(table);
|
|
6039
|
-
const
|
|
6121
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6122
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6040
6123
|
if (where) query = query.where(where);
|
|
6041
6124
|
const rows = await query;
|
|
6042
6125
|
return assembleAggregateRow(rows[0] ?? {}, request, target);
|
|
@@ -6069,8 +6152,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6069
6152
|
const targetTableName = relEntry.targetTableName;
|
|
6070
6153
|
const targetTable = tables[targetTableName];
|
|
6071
6154
|
if (!parentTable || !targetTable) return;
|
|
6072
|
-
const
|
|
6073
|
-
if (!
|
|
6155
|
+
const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
|
|
6156
|
+
if (!joinPairs) {
|
|
6074
6157
|
const key = `${tableName}.${relationName}`;
|
|
6075
6158
|
if (!warned.has(key)) {
|
|
6076
6159
|
warned.add(key);
|
|
@@ -6078,11 +6161,13 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6078
6161
|
}
|
|
6079
6162
|
return;
|
|
6080
6163
|
}
|
|
6081
|
-
const
|
|
6164
|
+
const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
|
|
6165
|
+
const foreignCol = joinPairs[0].foreignCol;
|
|
6082
6166
|
const junction = extractJunctionColumns(relEntry);
|
|
6083
6167
|
if (junction === null) return;
|
|
6084
|
-
const
|
|
6085
|
-
const
|
|
6168
|
+
const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
|
|
6169
|
+
const groupAliases = groupCols.map((_, i) => `${GROUP_KEY}${i}`);
|
|
6170
|
+
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
|
|
6086
6171
|
const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, false);
|
|
6087
6172
|
const targetTypeName = resolveTypeName(targetTableName, typeNameMapper);
|
|
6088
6173
|
const type = generateAggregateTypes(targetTable, targetTableName, targetTypeName, cacheCtx);
|
|
@@ -6106,23 +6191,25 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6106
6191
|
};
|
|
6107
6192
|
});
|
|
6108
6193
|
if (!Object.keys(request.selection).length) return {};
|
|
6109
|
-
const
|
|
6110
|
-
if (
|
|
6194
|
+
const localValues = localColPropNames.map((name) => parent[name]);
|
|
6195
|
+
if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
|
|
6111
6196
|
const whereArg = args?.where;
|
|
6112
6197
|
const deleted = args?.deleted;
|
|
6113
|
-
const loader = getOrCreateLoader(context, loaderKey, async (
|
|
6198
|
+
const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
6114
6199
|
const executor = resolveExecutor(db, context);
|
|
6115
|
-
const
|
|
6116
|
-
const
|
|
6200
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
6201
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6202
|
+
const whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
|
|
6203
|
+
const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
|
|
6117
6204
|
const selected = executor.select({
|
|
6118
|
-
|
|
6205
|
+
...groupSelection,
|
|
6119
6206
|
...request.selection
|
|
6120
6207
|
}).from(targetTable);
|
|
6121
|
-
const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(
|
|
6122
|
-
const byKey = new Map(rows.map((row) => [row[
|
|
6123
|
-
return
|
|
6208
|
+
const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
|
|
6209
|
+
const byKey = new Map(rows.map((row) => [joinKeyOf(groupAliases.map((alias) => row[alias])), row]));
|
|
6210
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? {});
|
|
6124
6211
|
});
|
|
6125
|
-
return assembleAggregateRow(await loader.load(
|
|
6212
|
+
return assembleAggregateRow(await loader.load(localValues), request, target);
|
|
6126
6213
|
} catch (e) {
|
|
6127
6214
|
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
6128
6215
|
}
|
|
@@ -6329,7 +6416,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6329
6416
|
...request.selection
|
|
6330
6417
|
};
|
|
6331
6418
|
let query = resolveExecutor(db, context).select(selection).from(table);
|
|
6332
|
-
const
|
|
6419
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6420
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6333
6421
|
if (where) query = query.where(where);
|
|
6334
6422
|
query = query.groupBy(...keyColumns.map(([, column]) => column));
|
|
6335
6423
|
const havingCondition = buildHavingCondition(args.having, columns);
|
|
@@ -6368,6 +6456,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6368
6456
|
};
|
|
6369
6457
|
};
|
|
6370
6458
|
//#endregion
|
|
6459
|
+
//#region src/util/builders/common/build-registry.ts
|
|
6460
|
+
/**
|
|
6461
|
+
* Keyed by the `extensions.drizzle` object of every object type the build generated, rather
|
|
6462
|
+
* than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
|
|
6463
|
+
* by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
|
|
6464
|
+
*/
|
|
6465
|
+
const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
|
|
6466
|
+
/**
|
|
6467
|
+
* What is known about the builds of one drizzle instance, for a caller with no resolve info to
|
|
6468
|
+
* look its build up by. Only a lone build is kept — with two or more there is no telling which
|
|
6469
|
+
* one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
|
|
6470
|
+
*/
|
|
6471
|
+
const buildsByDb = /* @__PURE__ */ new WeakMap();
|
|
6472
|
+
/** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
|
|
6473
|
+
const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
|
|
6474
|
+
/** Records a finished build against the object types it generated and the instance it read. */
|
|
6475
|
+
const registerBuildRuntime = (runtime, types) => {
|
|
6476
|
+
for (const type of types) {
|
|
6477
|
+
const meta = drizzleExtension(type);
|
|
6478
|
+
if (meta) runtimeByTypeMeta.set(meta, runtime);
|
|
6479
|
+
}
|
|
6480
|
+
const previous = buildsByDb.get(runtime.db);
|
|
6481
|
+
buildsByDb.set(runtime.db, previous ? {
|
|
6482
|
+
count: previous.count + 1,
|
|
6483
|
+
anyPolicies: previous.anyPolicies || hasPolicies(runtime)
|
|
6484
|
+
} : {
|
|
6485
|
+
count: 1,
|
|
6486
|
+
only: runtime,
|
|
6487
|
+
anyPolicies: hasPolicies(runtime)
|
|
6488
|
+
});
|
|
6489
|
+
};
|
|
6490
|
+
/** The build that generated `type`, when one did. */
|
|
6491
|
+
const runtimeForType = (type) => {
|
|
6492
|
+
const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
|
|
6493
|
+
return meta ? runtimeByTypeMeta.get(meta) : void 0;
|
|
6494
|
+
};
|
|
6495
|
+
/**
|
|
6496
|
+
* The build of `db` to use when no generated type identifies one: the only build, or none
|
|
6497
|
+
* when no build applies any policy. Throws when several builds exist and one of them would
|
|
6498
|
+
* change the read, since picking either could apply the wrong policies — or none.
|
|
6499
|
+
*/
|
|
6500
|
+
const runtimeForDb = (db, helper) => {
|
|
6501
|
+
const builds = buildsByDb.get(db);
|
|
6502
|
+
if (!builds) return;
|
|
6503
|
+
if (builds.count === 1) return builds.only;
|
|
6504
|
+
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.`);
|
|
6505
|
+
};
|
|
6506
|
+
//#endregion
|
|
6371
6507
|
//#region src/util/builders/nested-writes.ts
|
|
6372
6508
|
const ALL_OPS = [
|
|
6373
6509
|
"create",
|
|
@@ -6860,7 +6996,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6860
6996
|
* nothing is rejected: `connect: {}` would otherwise attach every row in the table.
|
|
6861
6997
|
*/
|
|
6862
6998
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
6863
|
-
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
|
|
6999
|
+
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
6864
7000
|
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" });
|
|
6865
7001
|
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
6866
7002
|
};
|
|
@@ -7471,6 +7607,14 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7471
7607
|
}
|
|
7472
7608
|
if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
|
|
7473
7609
|
}
|
|
7610
|
+
registerBuildRuntime({
|
|
7611
|
+
db: ctx.db,
|
|
7612
|
+
filterCtx: ctx.filterCtx,
|
|
7613
|
+
policies: ctx.policies,
|
|
7614
|
+
scoped: !!ctx.tablePolicies?.scope,
|
|
7615
|
+
limits: ctx.limits,
|
|
7616
|
+
nullOrdering: adapter.nullOrdering
|
|
7617
|
+
}, Object.values(ctx.outputs));
|
|
7474
7618
|
return {
|
|
7475
7619
|
queries: ctx.queries,
|
|
7476
7620
|
mutations: ctx.mutations,
|
|
@@ -7593,7 +7737,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
|
|
|
7593
7737
|
const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
|
|
7594
7738
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7595
7739
|
await before();
|
|
7596
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7740
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7597
7741
|
const filters = scopedWhere({
|
|
7598
7742
|
scope,
|
|
7599
7743
|
tableName,
|
|
@@ -7639,7 +7783,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
|
|
|
7639
7783
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7640
7784
|
return {
|
|
7641
7785
|
set: input,
|
|
7642
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
7786
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
7643
7787
|
};
|
|
7644
7788
|
});
|
|
7645
7789
|
await executor.transaction(async (tx) => {
|
|
@@ -7682,7 +7826,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
7682
7826
|
const { where } = args;
|
|
7683
7827
|
const hard = canHardDelete && args.hard === true;
|
|
7684
7828
|
const scope = policies?.scope?.(context);
|
|
7685
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7829
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7686
7830
|
const filters = scopedWhere({
|
|
7687
7831
|
scope,
|
|
7688
7832
|
tableName,
|
|
@@ -8024,7 +8168,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8024
8168
|
excludedRef: excludedColumnRef,
|
|
8025
8169
|
withTarget: true,
|
|
8026
8170
|
serverOwned: Object.keys(contextColumns ?? {}),
|
|
8027
|
-
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
|
|
8171
|
+
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
|
|
8028
8172
|
});
|
|
8029
8173
|
const runBatch = async (executor, { values: rows, plan }) => {
|
|
8030
8174
|
let query = executor.insert(table).values(rows).returning(returning);
|
|
@@ -8106,7 +8250,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8106
8250
|
const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
|
|
8107
8251
|
const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
8108
8252
|
if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
8109
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
8253
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8110
8254
|
const filters = scopedWhere({
|
|
8111
8255
|
scope,
|
|
8112
8256
|
tableName,
|
|
@@ -8185,7 +8329,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8185
8329
|
await before();
|
|
8186
8330
|
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
8187
8331
|
const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
|
|
8188
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
8332
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8189
8333
|
const filters = scopedWhere({
|
|
8190
8334
|
scope,
|
|
8191
8335
|
tableName,
|
|
@@ -8636,7 +8780,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
8636
8780
|
return {
|
|
8637
8781
|
set: input,
|
|
8638
8782
|
ops,
|
|
8639
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
8783
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
8640
8784
|
};
|
|
8641
8785
|
});
|
|
8642
8786
|
const anyNested = entries.some((entry) => entry.ops);
|
|
@@ -8806,6 +8950,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
|
|
|
8806
8950
|
typeNameSuffix: options.typeNameSuffix
|
|
8807
8951
|
});
|
|
8808
8952
|
/**
|
|
8953
|
+
* The build whose policies the read applies. Looked up through the generated types the
|
|
8954
|
+
* resolve info reaches — the field's return type, the root table's type in the executing
|
|
8955
|
+
* schema, the parent type — so a resolver over a generated type finds its own build. Without
|
|
8956
|
+
* resolve info (a parsed tree was passed), the instance's only build is used.
|
|
8957
|
+
*/
|
|
8958
|
+
const runtimeFor = (info, db, typeName, helper) => {
|
|
8959
|
+
if (!("fieldsByTypeName" in info)) {
|
|
8960
|
+
const candidates = [
|
|
8961
|
+
info.returnType ? getNamedType(info.returnType) : void 0,
|
|
8962
|
+
info.schema?.getType(typeName),
|
|
8963
|
+
info.parentType
|
|
8964
|
+
];
|
|
8965
|
+
for (const candidate of candidates) {
|
|
8966
|
+
const runtime = runtimeForType(candidate);
|
|
8967
|
+
if (runtime && runtime.db === db) return runtime;
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
return runtimeForDb(db, helper);
|
|
8971
|
+
};
|
|
8972
|
+
/**
|
|
8973
|
+
* The request's row policies under `runtime`, bound to the context the caller passed. A build
|
|
8974
|
+
* with `scope` hooks cannot be read without that context: the hooks would be called with
|
|
8975
|
+
* nothing, and running the read unscoped is the leak the option exists to prevent.
|
|
8976
|
+
*/
|
|
8977
|
+
const scopeFor = (runtime, options, helper) => {
|
|
8978
|
+
if (!runtime?.policies?.scope) return;
|
|
8979
|
+
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.`);
|
|
8980
|
+
return runtime.policies.scope(options.context);
|
|
8981
|
+
};
|
|
8982
|
+
/**
|
|
8809
8983
|
* Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
|
|
8810
8984
|
* inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
|
|
8811
8985
|
* these helpers) does not pay for the walk twice.
|
|
@@ -8832,6 +9006,10 @@ const parseInfo = (info) => {
|
|
|
8832
9006
|
* const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
|
|
8833
9007
|
* ```
|
|
8834
9008
|
*
|
|
9009
|
+
* Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
|
|
9010
|
+
* `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
|
|
9011
|
+
* through the resolve info; pass `context` when it configures `scope`.
|
|
9012
|
+
*
|
|
8835
9013
|
* Returns `undefined` when the selection asks for no relations, which is the value the
|
|
8836
9014
|
* query builder wants for "no `with` clause".
|
|
8837
9015
|
*/
|
|
@@ -8841,11 +9019,16 @@ const selectionToWith = (info, options) => {
|
|
|
8841
9019
|
const { tables, relationMap, filterCtx } = contextFor(db);
|
|
8842
9020
|
if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
|
|
8843
9021
|
if (!relationMap[table]) return;
|
|
9022
|
+
const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
|
|
9023
|
+
const runtime = runtimeFor(info, db, typeName, "selectionToWith");
|
|
8844
9024
|
const parsed = parseInfo(info);
|
|
8845
|
-
const params = extractRelationsParams(relationMap, tables, table, parsed,
|
|
9025
|
+
const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
|
|
8846
9026
|
typeNameMapper,
|
|
8847
|
-
filterCtx,
|
|
8848
|
-
resolveName
|
|
9027
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
9028
|
+
resolveName,
|
|
9029
|
+
scope: scopeFor(runtime, options, "selectionToWith"),
|
|
9030
|
+
limits: runtime?.limits,
|
|
9031
|
+
defaultOrderBy: runtime?.policies?.defaultOrderBy
|
|
8849
9032
|
});
|
|
8850
9033
|
return params && Object.keys(params).length ? params : void 0;
|
|
8851
9034
|
};
|
|
@@ -8857,14 +9040,16 @@ const selectionToWith = (info, options) => {
|
|
|
8857
9040
|
* This is what an override usually wants after it has done its own work:
|
|
8858
9041
|
*
|
|
8859
9042
|
* ```ts
|
|
8860
|
-
* const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
|
|
9043
|
+
* const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
|
|
8861
9044
|
* ```
|
|
8862
9045
|
*
|
|
8863
9046
|
* Relation arguments and aggregate fields keep behaving as they do in the generated schema,
|
|
8864
|
-
* because it is the generated schema's own code path.
|
|
9047
|
+
* because it is the generated schema's own code path. So do the build's row policies: the
|
|
9048
|
+
* root table and every nested relation are scoped and soft-delete-filtered, and the limit
|
|
9049
|
+
* policy and default ordering apply. Pass `context` when the build configures `scope`.
|
|
8865
9050
|
*/
|
|
8866
9051
|
const resolveSelection = async (info, options) => {
|
|
8867
|
-
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
|
|
9052
|
+
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
|
|
8868
9053
|
const resolveName = namingRuleOf(options);
|
|
8869
9054
|
const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
|
|
8870
9055
|
const table = tables[tableName];
|
|
@@ -8874,25 +9059,34 @@ const resolveSelection = async (info, options) => {
|
|
|
8874
9059
|
const source = executor ?? db;
|
|
8875
9060
|
const queryBase = source.query?.[tableName];
|
|
8876
9061
|
if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
|
|
9062
|
+
const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
|
|
9063
|
+
const runtime = runtimeFor(info, db, typeName, "resolveSelection");
|
|
9064
|
+
const scope = scopeFor(runtime, options, "resolveSelection");
|
|
9065
|
+
const defaultOrderBy = runtime?.policies?.defaultOrderBy;
|
|
8877
9066
|
return runRelationalSelect({
|
|
8878
9067
|
queryBase,
|
|
8879
9068
|
tables,
|
|
8880
9069
|
tableName,
|
|
8881
9070
|
table,
|
|
8882
9071
|
relationMap,
|
|
8883
|
-
typeName
|
|
9072
|
+
typeName,
|
|
8884
9073
|
typeNameMapper,
|
|
8885
9074
|
resolveName,
|
|
8886
9075
|
parsedInfo,
|
|
8887
9076
|
single,
|
|
8888
9077
|
where,
|
|
8889
|
-
orderBy,
|
|
8890
|
-
limit,
|
|
9078
|
+
orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
|
|
9079
|
+
limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
|
|
8891
9080
|
offset,
|
|
8892
9081
|
after,
|
|
8893
|
-
filterCtx,
|
|
9082
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
8894
9083
|
pkNames: primaryKeyOf(tableName),
|
|
8895
|
-
db: source
|
|
9084
|
+
db: source,
|
|
9085
|
+
scope,
|
|
9086
|
+
deleted,
|
|
9087
|
+
limits: runtime?.limits,
|
|
9088
|
+
defaultOrderBy,
|
|
9089
|
+
nullOrdering: runtime?.nullOrdering
|
|
8896
9090
|
});
|
|
8897
9091
|
};
|
|
8898
9092
|
//#endregion
|