@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.cjs
CHANGED
|
@@ -1802,6 +1802,11 @@ const warnUnresolvedJoin = (relEntry, tableName, relationName, omitted) => {
|
|
|
1802
1802
|
* Returns the JS property name of the local column on the parent table and the
|
|
1803
1803
|
* Column object for the foreign column on the target table, or undefined if the
|
|
1804
1804
|
* relation internals are not accessible.
|
|
1805
|
+
*
|
|
1806
|
+
* Only the relation's *first* column pair is reported. A relation joined on several columns
|
|
1807
|
+
* (`from: [a, b], to: [x, y]`) is not described fully by it, so anything that reads rows by
|
|
1808
|
+
* this pair alone mixes up parents that share the first column. Read paths use
|
|
1809
|
+
* {@link extractRelationJoinColumnPairs}, which returns every pair.
|
|
1805
1810
|
*/
|
|
1806
1811
|
const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
|
|
1807
1812
|
const rel = relEntry.relation ?? relEntry;
|
|
@@ -1820,6 +1825,48 @@ const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
|
|
|
1820
1825
|
};
|
|
1821
1826
|
};
|
|
1822
1827
|
/**
|
|
1828
|
+
* Every column pair the relation joins on, in declaration order — one for an ordinary relation,
|
|
1829
|
+
* several for one declared `from: [a, b], to: [x, y]`. A row of the target belongs to a parent
|
|
1830
|
+
* only when *all* pairs match, so a read path has to correlate on every one of them.
|
|
1831
|
+
*
|
|
1832
|
+
* `undefined` when the relation's columns cannot be matched to the two tables, or when its two
|
|
1833
|
+
* sides list a different number of columns.
|
|
1834
|
+
*/
|
|
1835
|
+
const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
|
|
1836
|
+
const rel = relEntry.relation ?? relEntry;
|
|
1837
|
+
const sourceCols = relationColumns(rel, "source");
|
|
1838
|
+
const targetCols = relationColumns(rel, "target");
|
|
1839
|
+
if (!sourceCols?.length || sourceCols.length !== targetCols?.length) return;
|
|
1840
|
+
const parentEntries = Object.entries((0, drizzle_orm.getColumns)(parentTable));
|
|
1841
|
+
const targetEntries = Object.entries((0, drizzle_orm.getColumns)(targetTable));
|
|
1842
|
+
const pairs = [];
|
|
1843
|
+
for (let i = 0; i < sourceCols.length; i++) {
|
|
1844
|
+
const localCol = sourceCols[i];
|
|
1845
|
+
const foreignCol = targetCols[i];
|
|
1846
|
+
const localColPropName = parentEntries.find(([, c]) => c === localCol)?.[0];
|
|
1847
|
+
const foreignColPropName = targetEntries.find(([, c]) => c === foreignCol)?.[0];
|
|
1848
|
+
if (!localColPropName || !foreignColPropName) return;
|
|
1849
|
+
pairs.push({
|
|
1850
|
+
localColPropName,
|
|
1851
|
+
localCol,
|
|
1852
|
+
foreignColPropName,
|
|
1853
|
+
foreignCol
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
return pairs;
|
|
1857
|
+
};
|
|
1858
|
+
/**
|
|
1859
|
+
* A string identifying one combination of join-column values, for grouping a batch's rows by
|
|
1860
|
+
* parent. Values are compared by their string form, as a single-column key always has been.
|
|
1861
|
+
*/
|
|
1862
|
+
const joinKeyOf = (values) => values.length === 1 ? String(values[0]) : JSON.stringify(values.map((value) => String(value)));
|
|
1863
|
+
/**
|
|
1864
|
+
* The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
|
|
1865
|
+
* of per-parent equalities for several — `(a = 1 AND b = 1) OR (a = 1 AND b = 2)` — which
|
|
1866
|
+
* every dialect accepts, unlike a row-value `IN` list.
|
|
1867
|
+
*/
|
|
1868
|
+
const parentKeyCondition = (cols, keys) => cols.length === 1 ? (0, drizzle_orm.inArray)(cols[0], keys.map((key) => key[0])) : (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)(...cols.map((col, i) => (0, drizzle_orm.eq)(col, key[i])))));
|
|
1869
|
+
/**
|
|
1823
1870
|
* The junction table and its two columns for a `.through()` (many-to-many) relation:
|
|
1824
1871
|
* `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
|
|
1825
1872
|
* with no junction; `null` for one whose junction cannot be used here — more than one column
|
|
@@ -1850,16 +1897,18 @@ const extractJunctionColumns = (relEntry) => {
|
|
|
1850
1897
|
*
|
|
1851
1898
|
* A reversed filter (inherited from the relation on the other side) is written against the
|
|
1852
1899
|
* parent table, so it is applied as "the row's parent key belongs to a parent that passes it".
|
|
1853
|
-
* `
|
|
1854
|
-
* the parent key for each row — the target's foreign key, or the junction's source
|
|
1900
|
+
* `parentKeyCols` are the parent's join columns and `groupCols` the columns in the query that
|
|
1901
|
+
* hold the parent key for each row — the target's foreign key, or the junction's source
|
|
1902
|
+
* column — in the same order. A key of several columns is compared as a row value.
|
|
1855
1903
|
*/
|
|
1856
|
-
const relationOwnCondition = (relEntry, parentTable, targetTable,
|
|
1904
|
+
const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCols, groupCols) => {
|
|
1857
1905
|
const rel = relEntry.relation ?? relEntry;
|
|
1858
1906
|
const ownWhere = relationOwnWhere(rel);
|
|
1859
1907
|
if (!ownWhere) return;
|
|
1860
1908
|
if (!isRelationFilterReversed(rel)) return (0, drizzle_orm.relationsFilterToSQL)(targetTable, ownWhere);
|
|
1861
1909
|
const parentFilter = (0, drizzle_orm.relationsFilterToSQL)(parentTable, ownWhere);
|
|
1862
|
-
return drizzle_orm.sql`${
|
|
1910
|
+
if (groupCols.length === 1) return drizzle_orm.sql`${groupCols[0]} in (select ${parentKeyCols[0]} from ${parentTable} where ${parentFilter})`;
|
|
1911
|
+
return drizzle_orm.sql`(${drizzle_orm.sql.join([...groupCols], drizzle_orm.sql`, `)}) in (select ${drizzle_orm.sql.join([...parentKeyCols], drizzle_orm.sql`, `)} from ${parentTable} where ${parentFilter})`;
|
|
1863
1912
|
};
|
|
1864
1913
|
/**
|
|
1865
1914
|
* Whether a to-one relation can back a relation orderBy hop. `.through()` (junction)
|
|
@@ -2007,6 +2056,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
|
|
|
2007
2056
|
*/
|
|
2008
2057
|
const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
2009
2058
|
/**
|
|
2059
|
+
* One alias counter per build, shared by every table's narrowed context. A statement can
|
|
2060
|
+
* combine subqueries compiled from different contexts — a relation filter on one table whose
|
|
2061
|
+
* target's scope is a filter object reaching through another relation, say — and nested
|
|
2062
|
+
* subqueries that drew the same alias from two separate counters would shadow each other.
|
|
2063
|
+
*/
|
|
2064
|
+
const aliasCounters = /* @__PURE__ */ new WeakMap();
|
|
2065
|
+
/**
|
|
2010
2066
|
* Narrows the build-scoped relation filter context to the table a resolver is filtering.
|
|
2011
2067
|
*
|
|
2012
2068
|
* Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
|
|
@@ -2020,7 +2076,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
|
2020
2076
|
* aliases only ever have to be unique *within* one statement, which a number that never
|
|
2021
2077
|
* repeats gives more surely than one that restarts.
|
|
2022
2078
|
*/
|
|
2023
|
-
const relationFilterCtx = (base, tableKey) => {
|
|
2079
|
+
const relationFilterCtx = (base, tableKey, scope) => {
|
|
2024
2080
|
if (!base) return;
|
|
2025
2081
|
let byTable = narrowedCtxCache.get(base);
|
|
2026
2082
|
if (!byTable) {
|
|
@@ -2029,13 +2085,33 @@ const relationFilterCtx = (base, tableKey) => {
|
|
|
2029
2085
|
}
|
|
2030
2086
|
let ctx = byTable.get(tableKey);
|
|
2031
2087
|
if (!ctx) {
|
|
2088
|
+
let aliases = aliasCounters.get(base);
|
|
2089
|
+
if (!aliases) {
|
|
2090
|
+
aliases = { n: 0 };
|
|
2091
|
+
aliasCounters.set(base, aliases);
|
|
2092
|
+
}
|
|
2032
2093
|
ctx = {
|
|
2033
2094
|
...base,
|
|
2034
|
-
tableKey
|
|
2095
|
+
tableKey,
|
|
2096
|
+
aliases
|
|
2035
2097
|
};
|
|
2036
2098
|
byTable.set(tableKey, ctx);
|
|
2037
2099
|
}
|
|
2038
|
-
return
|
|
2100
|
+
return scope ? {
|
|
2101
|
+
...ctx,
|
|
2102
|
+
scope
|
|
2103
|
+
} : ctx;
|
|
2104
|
+
};
|
|
2105
|
+
/**
|
|
2106
|
+
* The row-policy predicate a subquery over a relation's target applies: the target's scope
|
|
2107
|
+
* and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
|
|
2108
|
+
* relation filter or a relation ordering sees exactly the rows the relation field returns.
|
|
2109
|
+
*/
|
|
2110
|
+
const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
|
|
2111
|
+
const scope = ctx.scope;
|
|
2112
|
+
if (!scope) return;
|
|
2113
|
+
const mode = scope.relationDefault(targetTableName, (0, drizzle_orm.is)(relation, drizzle_orm.One) && relation.optional === false);
|
|
2114
|
+
return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
|
|
2039
2115
|
};
|
|
2040
2116
|
/**
|
|
2041
2117
|
* Correlates the parent row with the aliased target table using the relation's own join
|
|
@@ -2126,8 +2202,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2126
2202
|
const throughTable = relation.throughTable;
|
|
2127
2203
|
let fromClause;
|
|
2128
2204
|
let joinCondition;
|
|
2205
|
+
let junctionPolicy;
|
|
2129
2206
|
if (throughTable) {
|
|
2130
2207
|
const aliasedThrough = (0, drizzle_orm.aliasedTable)(throughTable, `dgql_rel_${aliases.n++}`);
|
|
2208
|
+
const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
|
|
2209
|
+
if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
|
|
2131
2210
|
const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
|
|
2132
2211
|
fromClause = drizzle_orm.sql`${(0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)} inner join ${(0, drizzle_orm.getTableAsAliasSQL)(aliasedThrough)} on ${junctionJoin}`;
|
|
2133
2212
|
joinCondition = correlation;
|
|
@@ -2137,6 +2216,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2137
2216
|
}
|
|
2138
2217
|
const ownWhere = relationOwnWhere(relation);
|
|
2139
2218
|
const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2219
|
+
const policy = (0, drizzle_orm.and)(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
|
|
2140
2220
|
const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
|
|
2141
2221
|
...ctx,
|
|
2142
2222
|
tableKey: targetTableName,
|
|
@@ -2144,9 +2224,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2144
2224
|
}) : void 0;
|
|
2145
2225
|
if (mode === "every") {
|
|
2146
2226
|
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))})`;
|
|
2227
|
+
return drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${(0, drizzle_orm.and)(joinCondition, relationWhere, policy, (0, drizzle_orm.not)(inner))})`;
|
|
2148
2228
|
}
|
|
2149
|
-
const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, inner);
|
|
2229
|
+
const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, policy, inner);
|
|
2150
2230
|
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
2231
|
};
|
|
2152
2232
|
/**
|
|
@@ -2672,12 +2752,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
|
|
|
2672
2752
|
const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
|
|
2673
2753
|
const ownWhere = relationOwnWhere(relation);
|
|
2674
2754
|
const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2755
|
+
const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
|
|
2675
2756
|
return {
|
|
2676
2757
|
fromParts: [...chain?.fromParts ?? [], (0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)],
|
|
2677
2758
|
conditions: [
|
|
2678
2759
|
...chain?.conditions ?? [],
|
|
2679
2760
|
...joinCondition ? [joinCondition] : [],
|
|
2680
|
-
...relationWhere ? [relationWhere] : []
|
|
2761
|
+
...relationWhere ? [relationWhere] : [],
|
|
2762
|
+
...policy ? [policy] : []
|
|
2681
2763
|
],
|
|
2682
2764
|
table: aliasedTarget
|
|
2683
2765
|
};
|
|
@@ -3812,8 +3894,7 @@ const relationJoinColumns = (tree, table, selectionCtx) => {
|
|
|
3812
3894
|
const relEntry = fieldData.name.endsWith(AGGREGATE_FIELD_SUFFIX) ? relations[fieldData.name.slice(0, -9)] : relations[fieldData.name];
|
|
3813
3895
|
const targetTable = relEntry ? selectionCtx.tables[relEntry.targetTableName] : void 0;
|
|
3814
3896
|
if (!relEntry || !targetTable) continue;
|
|
3815
|
-
const
|
|
3816
|
-
if (joinCols) needed.push(joinCols.localColPropName);
|
|
3897
|
+
for (const pair of extractRelationJoinColumnPairs(relEntry, table, targetTable) ?? []) needed.push(pair.localColPropName);
|
|
3817
3898
|
}
|
|
3818
3899
|
return needed;
|
|
3819
3900
|
};
|
|
@@ -3907,10 +3988,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3907
3988
|
});
|
|
3908
3989
|
const relWhere = relationArgs?.where;
|
|
3909
3990
|
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;
|
|
3991
|
+
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
3992
|
const hasPagination = offset != null || limit != null;
|
|
3912
3993
|
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;
|
|
3994
|
+
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3914
3995
|
thisRecord.offset = offset;
|
|
3915
3996
|
thisRecord.limit = limit;
|
|
3916
3997
|
thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
|
|
@@ -3968,7 +4049,7 @@ const runRelationalSelect = async (opts) => {
|
|
|
3968
4049
|
tableName,
|
|
3969
4050
|
distinct,
|
|
3970
4051
|
pkNames: pkNames ?? [],
|
|
3971
|
-
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
4052
|
+
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
|
|
3972
4053
|
orderBy,
|
|
3973
4054
|
limit: single ? 1 : opts.limit,
|
|
3974
4055
|
offset
|
|
@@ -3983,8 +4064,8 @@ const runRelationalSelect = async (opts) => {
|
|
|
3983
4064
|
allRelations: filterCtx?.relationMap
|
|
3984
4065
|
}),
|
|
3985
4066
|
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,
|
|
4067
|
+
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,
|
|
4068
|
+
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
4069
|
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3989
4070
|
typeNameMapper,
|
|
3990
4071
|
filterCtx,
|
|
@@ -4566,7 +4647,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
|
|
|
4566
4647
|
tableName,
|
|
4567
4648
|
table,
|
|
4568
4649
|
where,
|
|
4569
|
-
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
4650
|
+
relationCtx: relationFilterCtx(filterCtx, tableName, scope),
|
|
4570
4651
|
required: requireWhere,
|
|
4571
4652
|
deleted: hard ? "INCLUDE" : void 0
|
|
4572
4653
|
});
|
|
@@ -4834,13 +4915,12 @@ const identifyRows = (field, args) => {
|
|
|
4834
4915
|
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
4835
4916
|
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
4836
4917
|
*/
|
|
4837
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
|
|
4918
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, partitionCols, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
|
|
4838
4919
|
const cols = columns ?? (0, drizzle_orm.getColumns)(targetTable);
|
|
4839
4920
|
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
4840
4921
|
const orderClause = orderExprs.length ? drizzle_orm.sql` order by ${drizzle_orm.sql.join(orderExprs, drizzle_orm.sql`, `)}` : drizzle_orm.sql``;
|
|
4841
4922
|
const RN = "__drizzle_graphql_rn";
|
|
4842
|
-
const
|
|
4843
|
-
const rowNumber = drizzle_orm.sql`row_number() over (partition by ${partitionCol}${orderClause})`.as(RN);
|
|
4923
|
+
const rowNumber = drizzle_orm.sql`row_number() over (partition by ${drizzle_orm.sql.join([...partitionCols], drizzle_orm.sql`, `)}${orderClause})`.as(RN);
|
|
4844
4924
|
const sub = withJunction(db.select({
|
|
4845
4925
|
...cols,
|
|
4846
4926
|
[RN]: rowNumber
|
|
@@ -4879,13 +4959,14 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4879
4959
|
const targetTableName = relEntry.targetTableName;
|
|
4880
4960
|
const targetTable = tables[targetTableName];
|
|
4881
4961
|
if (!parentTable || !targetTable) return;
|
|
4882
|
-
const
|
|
4883
|
-
if (!
|
|
4884
|
-
const
|
|
4962
|
+
const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
|
|
4963
|
+
if (!joinPairs) return;
|
|
4964
|
+
const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
|
|
4965
|
+
const foreignCol = joinPairs[0].foreignCol;
|
|
4885
4966
|
const junction = extractJunctionColumns(relEntry);
|
|
4886
|
-
const
|
|
4887
|
-
const
|
|
4888
|
-
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, (
|
|
4967
|
+
const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
|
|
4968
|
+
const groupKeys = junction ? [PARENT_KEY] : joinPairs.map((pair) => pair.foreignColPropName);
|
|
4969
|
+
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
|
|
4889
4970
|
const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, isOne && relEntry.relation?.optional === false);
|
|
4890
4971
|
const limitPolicy = isOne ? void 0 : limits?.(targetTableName);
|
|
4891
4972
|
const targetPkNames = relEntry.targetPkNames ?? [];
|
|
@@ -4914,7 +4995,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4914
4995
|
allRelations: filterCtx?.relationMap
|
|
4915
4996
|
});
|
|
4916
4997
|
const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
|
|
4917
|
-
columns[foreignColPropName] = foreignCol;
|
|
4998
|
+
for (const pair of joinPairs) columns[pair.foreignColPropName] = pair.foreignCol;
|
|
4918
4999
|
for (const [columnName] of cursorEntries ?? []) {
|
|
4919
5000
|
const column = targetColumns[columnName];
|
|
4920
5001
|
if (column) columns[columnName] = column;
|
|
@@ -4926,8 +5007,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4926
5007
|
};
|
|
4927
5008
|
const resolve = async (parent, args, context, info) => {
|
|
4928
5009
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
4929
|
-
const
|
|
4930
|
-
if (
|
|
5010
|
+
const localValues = localColPropNames.map((name) => parent[name]);
|
|
5011
|
+
if (localValues.some((value) => value == null)) return isOne ? null : [];
|
|
4931
5012
|
const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
|
|
4932
5013
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4933
5014
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
@@ -4968,11 +5049,11 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4968
5049
|
};
|
|
4969
5050
|
});
|
|
4970
5051
|
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" });
|
|
4971
|
-
return getOrCreateLoader(context, loaderKey, async (
|
|
5052
|
+
return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
4972
5053
|
const executor = resolveExecutor(db, context);
|
|
4973
|
-
const
|
|
5054
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
4974
5055
|
const scope = resolveScope(policies, context, filterCtx);
|
|
4975
|
-
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((
|
|
5056
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.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);
|
|
4976
5057
|
if (distinct) {
|
|
4977
5058
|
const keys = await selectDistinctKeys({
|
|
4978
5059
|
db: executor,
|
|
@@ -4982,44 +5063,44 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4982
5063
|
pkNames: targetPkNames,
|
|
4983
5064
|
where: whereCondition,
|
|
4984
5065
|
orderBy: orderByArg,
|
|
4985
|
-
partitionBy:
|
|
5066
|
+
partitionBy: groupCols,
|
|
4986
5067
|
junction: junction ? {
|
|
4987
5068
|
table: junction.table,
|
|
4988
5069
|
on: (0, drizzle_orm.eq)(junction.targetCol, foreignCol),
|
|
4989
|
-
parentKey:
|
|
5070
|
+
parentKey: junction.sourceCol
|
|
4990
5071
|
} : void 0
|
|
4991
5072
|
});
|
|
4992
|
-
if (!keys.length) return
|
|
4993
|
-
whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(
|
|
5073
|
+
if (!keys.length) return parentKeys.map(() => isOne ? null : []);
|
|
5074
|
+
whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
4994
5075
|
}
|
|
4995
5076
|
const selection = junction ? {
|
|
4996
5077
|
...columns ?? (0, drizzle_orm.getColumns)(targetTable),
|
|
4997
|
-
[PARENT_KEY]:
|
|
5078
|
+
[PARENT_KEY]: junction.sourceCol
|
|
4998
5079
|
} : columns;
|
|
4999
5080
|
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);
|
|
5081
|
+
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);
|
|
5001
5082
|
else {
|
|
5002
5083
|
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) : [];
|
|
5084
|
+
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
|
|
5004
5085
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
5005
5086
|
rows = await q;
|
|
5006
5087
|
}
|
|
5007
5088
|
if (cursorEntries) attachRowCursors(rows, cursorEntries);
|
|
5008
5089
|
const keyOf = (row) => {
|
|
5009
|
-
const key =
|
|
5090
|
+
const key = joinKeyOf(groupKeys.map((name) => row[name]));
|
|
5010
5091
|
if (junction) delete row[PARENT_KEY];
|
|
5011
5092
|
return key;
|
|
5012
5093
|
};
|
|
5013
5094
|
if (isOne) {
|
|
5014
5095
|
const byKey = new Map(rows.map((row) => [keyOf(row), row]));
|
|
5015
5096
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
5016
|
-
return
|
|
5097
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
|
|
5017
5098
|
}
|
|
5018
|
-
const grouped = new Map(
|
|
5099
|
+
const grouped = new Map(uniqueKeys.map((key) => [joinKeyOf(key), []]));
|
|
5019
5100
|
for (const row of rows) grouped.get(keyOf(row))?.push(row);
|
|
5020
5101
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
5021
|
-
return
|
|
5022
|
-
}).load(
|
|
5102
|
+
return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
|
|
5103
|
+
}).load(localValues);
|
|
5023
5104
|
};
|
|
5024
5105
|
return async (parent, args, context, info) => {
|
|
5025
5106
|
try {
|
|
@@ -5680,6 +5761,7 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5680
5761
|
const normalized = {};
|
|
5681
5762
|
for (const [columnName, entry] of Object.entries(orderBy)) {
|
|
5682
5763
|
if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
|
|
5764
|
+
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
5765
|
const direction = typeof entry === "string" ? entry : entry?.direction;
|
|
5684
5766
|
if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
|
|
5685
5767
|
const priority = typeof entry === "string" ? void 0 : entry?.priority;
|
|
@@ -6060,7 +6142,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
|
|
|
6060
6142
|
const request = parseAggregateRequest(info, target);
|
|
6061
6143
|
if (!Object.keys(request.selection).length) return {};
|
|
6062
6144
|
let query = resolveExecutor(db, context).select(request.selection).from(table);
|
|
6063
|
-
const
|
|
6145
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6146
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6064
6147
|
if (where) query = query.where(where);
|
|
6065
6148
|
const rows = await query;
|
|
6066
6149
|
return assembleAggregateRow(rows[0] ?? {}, request, target);
|
|
@@ -6093,8 +6176,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6093
6176
|
const targetTableName = relEntry.targetTableName;
|
|
6094
6177
|
const targetTable = tables[targetTableName];
|
|
6095
6178
|
if (!parentTable || !targetTable) return;
|
|
6096
|
-
const
|
|
6097
|
-
if (!
|
|
6179
|
+
const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
|
|
6180
|
+
if (!joinPairs) {
|
|
6098
6181
|
const key = `${tableName}.${relationName}`;
|
|
6099
6182
|
if (!warned.has(key)) {
|
|
6100
6183
|
warned.add(key);
|
|
@@ -6102,11 +6185,13 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6102
6185
|
}
|
|
6103
6186
|
return;
|
|
6104
6187
|
}
|
|
6105
|
-
const
|
|
6188
|
+
const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
|
|
6189
|
+
const foreignCol = joinPairs[0].foreignCol;
|
|
6106
6190
|
const junction = extractJunctionColumns(relEntry);
|
|
6107
6191
|
if (junction === null) return;
|
|
6108
|
-
const
|
|
6109
|
-
const
|
|
6192
|
+
const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
|
|
6193
|
+
const groupAliases = groupCols.map((_, i) => `${GROUP_KEY}${i}`);
|
|
6194
|
+
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
|
|
6110
6195
|
const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, false);
|
|
6111
6196
|
const targetTypeName = resolveTypeName(targetTableName, typeNameMapper);
|
|
6112
6197
|
const type = generateAggregateTypes(targetTable, targetTableName, targetTypeName, cacheCtx);
|
|
@@ -6130,23 +6215,25 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6130
6215
|
};
|
|
6131
6216
|
});
|
|
6132
6217
|
if (!Object.keys(request.selection).length) return {};
|
|
6133
|
-
const
|
|
6134
|
-
if (
|
|
6218
|
+
const localValues = localColPropNames.map((name) => parent[name]);
|
|
6219
|
+
if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
|
|
6135
6220
|
const whereArg = args?.where;
|
|
6136
6221
|
const deleted = args?.deleted;
|
|
6137
|
-
const loader = getOrCreateLoader(context, loaderKey, async (
|
|
6222
|
+
const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
6138
6223
|
const executor = resolveExecutor(db, context);
|
|
6139
|
-
const
|
|
6140
|
-
const
|
|
6224
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
6225
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6226
|
+
const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
|
|
6227
|
+
const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
|
|
6141
6228
|
const selected = executor.select({
|
|
6142
|
-
|
|
6229
|
+
...groupSelection,
|
|
6143
6230
|
...request.selection
|
|
6144
6231
|
}).from(targetTable);
|
|
6145
|
-
const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(
|
|
6146
|
-
const byKey = new Map(rows.map((row) => [row[
|
|
6147
|
-
return
|
|
6232
|
+
const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
|
|
6233
|
+
const byKey = new Map(rows.map((row) => [joinKeyOf(groupAliases.map((alias) => row[alias])), row]));
|
|
6234
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? {});
|
|
6148
6235
|
});
|
|
6149
|
-
return assembleAggregateRow(await loader.load(
|
|
6236
|
+
return assembleAggregateRow(await loader.load(localValues), request, target);
|
|
6150
6237
|
} catch (e) {
|
|
6151
6238
|
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
6152
6239
|
}
|
|
@@ -6353,7 +6440,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6353
6440
|
...request.selection
|
|
6354
6441
|
};
|
|
6355
6442
|
let query = resolveExecutor(db, context).select(selection).from(table);
|
|
6356
|
-
const
|
|
6443
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6444
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6357
6445
|
if (where) query = query.where(where);
|
|
6358
6446
|
query = query.groupBy(...keyColumns.map(([, column]) => column));
|
|
6359
6447
|
const havingCondition = buildHavingCondition(args.having, columns);
|
|
@@ -6392,6 +6480,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6392
6480
|
};
|
|
6393
6481
|
};
|
|
6394
6482
|
//#endregion
|
|
6483
|
+
//#region src/util/builders/common/build-registry.ts
|
|
6484
|
+
/**
|
|
6485
|
+
* Keyed by the `extensions.drizzle` object of every object type the build generated, rather
|
|
6486
|
+
* than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
|
|
6487
|
+
* by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
|
|
6488
|
+
*/
|
|
6489
|
+
const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
|
|
6490
|
+
/**
|
|
6491
|
+
* What is known about the builds of one drizzle instance, for a caller with no resolve info to
|
|
6492
|
+
* look its build up by. Only a lone build is kept — with two or more there is no telling which
|
|
6493
|
+
* one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
|
|
6494
|
+
*/
|
|
6495
|
+
const buildsByDb = /* @__PURE__ */ new WeakMap();
|
|
6496
|
+
/** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
|
|
6497
|
+
const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
|
|
6498
|
+
/** Records a finished build against the object types it generated and the instance it read. */
|
|
6499
|
+
const registerBuildRuntime = (runtime, types) => {
|
|
6500
|
+
for (const type of types) {
|
|
6501
|
+
const meta = drizzleExtension(type);
|
|
6502
|
+
if (meta) runtimeByTypeMeta.set(meta, runtime);
|
|
6503
|
+
}
|
|
6504
|
+
const previous = buildsByDb.get(runtime.db);
|
|
6505
|
+
buildsByDb.set(runtime.db, previous ? {
|
|
6506
|
+
count: previous.count + 1,
|
|
6507
|
+
anyPolicies: previous.anyPolicies || hasPolicies(runtime)
|
|
6508
|
+
} : {
|
|
6509
|
+
count: 1,
|
|
6510
|
+
only: runtime,
|
|
6511
|
+
anyPolicies: hasPolicies(runtime)
|
|
6512
|
+
});
|
|
6513
|
+
};
|
|
6514
|
+
/** The build that generated `type`, when one did. */
|
|
6515
|
+
const runtimeForType = (type) => {
|
|
6516
|
+
const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
|
|
6517
|
+
return meta ? runtimeByTypeMeta.get(meta) : void 0;
|
|
6518
|
+
};
|
|
6519
|
+
/**
|
|
6520
|
+
* The build of `db` to use when no generated type identifies one: the only build, or none
|
|
6521
|
+
* when no build applies any policy. Throws when several builds exist and one of them would
|
|
6522
|
+
* change the read, since picking either could apply the wrong policies — or none.
|
|
6523
|
+
*/
|
|
6524
|
+
const runtimeForDb = (db, helper) => {
|
|
6525
|
+
const builds = buildsByDb.get(db);
|
|
6526
|
+
if (!builds) return;
|
|
6527
|
+
if (builds.count === 1) return builds.only;
|
|
6528
|
+
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.`);
|
|
6529
|
+
};
|
|
6530
|
+
//#endregion
|
|
6395
6531
|
//#region src/util/builders/nested-writes.ts
|
|
6396
6532
|
const ALL_OPS = [
|
|
6397
6533
|
"create",
|
|
@@ -6884,7 +7020,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6884
7020
|
* nothing is rejected: `connect: {}` would otherwise attach every row in the table.
|
|
6885
7021
|
*/
|
|
6886
7022
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
6887
|
-
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
|
|
7023
|
+
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
6888
7024
|
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
7025
|
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
6890
7026
|
};
|
|
@@ -7495,6 +7631,14 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7495
7631
|
}
|
|
7496
7632
|
if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
|
|
7497
7633
|
}
|
|
7634
|
+
registerBuildRuntime({
|
|
7635
|
+
db: ctx.db,
|
|
7636
|
+
filterCtx: ctx.filterCtx,
|
|
7637
|
+
policies: ctx.policies,
|
|
7638
|
+
scoped: !!ctx.tablePolicies?.scope,
|
|
7639
|
+
limits: ctx.limits,
|
|
7640
|
+
nullOrdering: adapter.nullOrdering
|
|
7641
|
+
}, Object.values(ctx.outputs));
|
|
7498
7642
|
return {
|
|
7499
7643
|
queries: ctx.queries,
|
|
7500
7644
|
mutations: ctx.mutations,
|
|
@@ -7617,7 +7761,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
|
|
|
7617
7761
|
const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
|
|
7618
7762
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7619
7763
|
await before();
|
|
7620
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7764
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7621
7765
|
const filters = scopedWhere({
|
|
7622
7766
|
scope,
|
|
7623
7767
|
tableName,
|
|
@@ -7663,7 +7807,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
|
|
|
7663
7807
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7664
7808
|
return {
|
|
7665
7809
|
set: input,
|
|
7666
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
7810
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
7667
7811
|
};
|
|
7668
7812
|
});
|
|
7669
7813
|
await executor.transaction(async (tx) => {
|
|
@@ -7706,7 +7850,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
7706
7850
|
const { where } = args;
|
|
7707
7851
|
const hard = canHardDelete && args.hard === true;
|
|
7708
7852
|
const scope = policies?.scope?.(context);
|
|
7709
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7853
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7710
7854
|
const filters = scopedWhere({
|
|
7711
7855
|
scope,
|
|
7712
7856
|
tableName,
|
|
@@ -8048,7 +8192,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8048
8192
|
excludedRef: excludedColumnRef,
|
|
8049
8193
|
withTarget: true,
|
|
8050
8194
|
serverOwned: Object.keys(contextColumns ?? {}),
|
|
8051
|
-
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
|
|
8195
|
+
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
|
|
8052
8196
|
});
|
|
8053
8197
|
const runBatch = async (executor, { values: rows, plan }) => {
|
|
8054
8198
|
let query = executor.insert(table).values(rows).returning(returning);
|
|
@@ -8130,7 +8274,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8130
8274
|
const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
|
|
8131
8275
|
const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
8132
8276
|
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);
|
|
8277
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8134
8278
|
const filters = scopedWhere({
|
|
8135
8279
|
scope,
|
|
8136
8280
|
tableName,
|
|
@@ -8209,7 +8353,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8209
8353
|
await before();
|
|
8210
8354
|
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
8211
8355
|
const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
|
|
8212
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
8356
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8213
8357
|
const filters = scopedWhere({
|
|
8214
8358
|
scope,
|
|
8215
8359
|
tableName,
|
|
@@ -8660,7 +8804,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
8660
8804
|
return {
|
|
8661
8805
|
set: input,
|
|
8662
8806
|
ops,
|
|
8663
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
8807
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
8664
8808
|
};
|
|
8665
8809
|
});
|
|
8666
8810
|
const anyNested = entries.some((entry) => entry.ops);
|
|
@@ -8830,6 +8974,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
|
|
|
8830
8974
|
typeNameSuffix: options.typeNameSuffix
|
|
8831
8975
|
});
|
|
8832
8976
|
/**
|
|
8977
|
+
* The build whose policies the read applies. Looked up through the generated types the
|
|
8978
|
+
* resolve info reaches — the field's return type, the root table's type in the executing
|
|
8979
|
+
* schema, the parent type — so a resolver over a generated type finds its own build. Without
|
|
8980
|
+
* resolve info (a parsed tree was passed), the instance's only build is used.
|
|
8981
|
+
*/
|
|
8982
|
+
const runtimeFor = (info, db, typeName, helper) => {
|
|
8983
|
+
if (!("fieldsByTypeName" in info)) {
|
|
8984
|
+
const candidates = [
|
|
8985
|
+
info.returnType ? (0, graphql.getNamedType)(info.returnType) : void 0,
|
|
8986
|
+
info.schema?.getType(typeName),
|
|
8987
|
+
info.parentType
|
|
8988
|
+
];
|
|
8989
|
+
for (const candidate of candidates) {
|
|
8990
|
+
const runtime = runtimeForType(candidate);
|
|
8991
|
+
if (runtime && runtime.db === db) return runtime;
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
return runtimeForDb(db, helper);
|
|
8995
|
+
};
|
|
8996
|
+
/**
|
|
8997
|
+
* The request's row policies under `runtime`, bound to the context the caller passed. A build
|
|
8998
|
+
* with `scope` hooks cannot be read without that context: the hooks would be called with
|
|
8999
|
+
* nothing, and running the read unscoped is the leak the option exists to prevent.
|
|
9000
|
+
*/
|
|
9001
|
+
const scopeFor = (runtime, options, helper) => {
|
|
9002
|
+
if (!runtime?.policies?.scope) return;
|
|
9003
|
+
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.`);
|
|
9004
|
+
return runtime.policies.scope(options.context);
|
|
9005
|
+
};
|
|
9006
|
+
/**
|
|
8833
9007
|
* Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
|
|
8834
9008
|
* inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
|
|
8835
9009
|
* these helpers) does not pay for the walk twice.
|
|
@@ -8856,6 +9030,10 @@ const parseInfo = (info) => {
|
|
|
8856
9030
|
* const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
|
|
8857
9031
|
* ```
|
|
8858
9032
|
*
|
|
9033
|
+
* Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
|
|
9034
|
+
* `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
|
|
9035
|
+
* through the resolve info; pass `context` when it configures `scope`.
|
|
9036
|
+
*
|
|
8859
9037
|
* Returns `undefined` when the selection asks for no relations, which is the value the
|
|
8860
9038
|
* query builder wants for "no `with` clause".
|
|
8861
9039
|
*/
|
|
@@ -8865,11 +9043,16 @@ const selectionToWith = (info, options) => {
|
|
|
8865
9043
|
const { tables, relationMap, filterCtx } = contextFor(db);
|
|
8866
9044
|
if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
|
|
8867
9045
|
if (!relationMap[table]) return;
|
|
9046
|
+
const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
|
|
9047
|
+
const runtime = runtimeFor(info, db, typeName, "selectionToWith");
|
|
8868
9048
|
const parsed = parseInfo(info);
|
|
8869
|
-
const params = extractRelationsParams(relationMap, tables, table, parsed,
|
|
9049
|
+
const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
|
|
8870
9050
|
typeNameMapper,
|
|
8871
|
-
filterCtx,
|
|
8872
|
-
resolveName
|
|
9051
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
9052
|
+
resolveName,
|
|
9053
|
+
scope: scopeFor(runtime, options, "selectionToWith"),
|
|
9054
|
+
limits: runtime?.limits,
|
|
9055
|
+
defaultOrderBy: runtime?.policies?.defaultOrderBy
|
|
8873
9056
|
});
|
|
8874
9057
|
return params && Object.keys(params).length ? params : void 0;
|
|
8875
9058
|
};
|
|
@@ -8881,14 +9064,16 @@ const selectionToWith = (info, options) => {
|
|
|
8881
9064
|
* This is what an override usually wants after it has done its own work:
|
|
8882
9065
|
*
|
|
8883
9066
|
* ```ts
|
|
8884
|
-
* const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
|
|
9067
|
+
* const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
|
|
8885
9068
|
* ```
|
|
8886
9069
|
*
|
|
8887
9070
|
* 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.
|
|
9071
|
+
* because it is the generated schema's own code path. So do the build's row policies: the
|
|
9072
|
+
* root table and every nested relation are scoped and soft-delete-filtered, and the limit
|
|
9073
|
+
* policy and default ordering apply. Pass `context` when the build configures `scope`.
|
|
8889
9074
|
*/
|
|
8890
9075
|
const resolveSelection = async (info, options) => {
|
|
8891
|
-
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
|
|
9076
|
+
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
|
|
8892
9077
|
const resolveName = namingRuleOf(options);
|
|
8893
9078
|
const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
|
|
8894
9079
|
const table = tables[tableName];
|
|
@@ -8898,25 +9083,34 @@ const resolveSelection = async (info, options) => {
|
|
|
8898
9083
|
const source = executor ?? db;
|
|
8899
9084
|
const queryBase = source.query?.[tableName];
|
|
8900
9085
|
if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
|
|
9086
|
+
const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
|
|
9087
|
+
const runtime = runtimeFor(info, db, typeName, "resolveSelection");
|
|
9088
|
+
const scope = scopeFor(runtime, options, "resolveSelection");
|
|
9089
|
+
const defaultOrderBy = runtime?.policies?.defaultOrderBy;
|
|
8901
9090
|
return runRelationalSelect({
|
|
8902
9091
|
queryBase,
|
|
8903
9092
|
tables,
|
|
8904
9093
|
tableName,
|
|
8905
9094
|
table,
|
|
8906
9095
|
relationMap,
|
|
8907
|
-
typeName
|
|
9096
|
+
typeName,
|
|
8908
9097
|
typeNameMapper,
|
|
8909
9098
|
resolveName,
|
|
8910
9099
|
parsedInfo,
|
|
8911
9100
|
single,
|
|
8912
9101
|
where,
|
|
8913
|
-
orderBy,
|
|
8914
|
-
limit,
|
|
9102
|
+
orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
|
|
9103
|
+
limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
|
|
8915
9104
|
offset,
|
|
8916
9105
|
after,
|
|
8917
|
-
filterCtx,
|
|
9106
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
8918
9107
|
pkNames: primaryKeyOf(tableName),
|
|
8919
|
-
db: source
|
|
9108
|
+
db: source,
|
|
9109
|
+
scope,
|
|
9110
|
+
deleted,
|
|
9111
|
+
limits: runtime?.limits,
|
|
9112
|
+
defaultOrderBy,
|
|
9113
|
+
nullOrdering: runtime?.nullOrdering
|
|
8920
9114
|
});
|
|
8921
9115
|
};
|
|
8922
9116
|
//#endregion
|