@vantreeseba/drizzle-graphql 15.1.2 → 15.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -3
- package/dist/index.cjs +180 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -4
- package/dist/index.d.ts +57 -4
- package/dist/index.js +181 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/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
|
|
@@ -1983,6 +1983,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
|
|
|
1983
1983
|
*/
|
|
1984
1984
|
const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
1985
1985
|
/**
|
|
1986
|
+
* One alias counter per build, shared by every table's narrowed context. A statement can
|
|
1987
|
+
* combine subqueries compiled from different contexts — a relation filter on one table whose
|
|
1988
|
+
* target's scope is a filter object reaching through another relation, say — and nested
|
|
1989
|
+
* subqueries that drew the same alias from two separate counters would shadow each other.
|
|
1990
|
+
*/
|
|
1991
|
+
const aliasCounters = /* @__PURE__ */ new WeakMap();
|
|
1992
|
+
/**
|
|
1986
1993
|
* Narrows the build-scoped relation filter context to the table a resolver is filtering.
|
|
1987
1994
|
*
|
|
1988
1995
|
* Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
|
|
@@ -1996,7 +2003,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
|
1996
2003
|
* aliases only ever have to be unique *within* one statement, which a number that never
|
|
1997
2004
|
* repeats gives more surely than one that restarts.
|
|
1998
2005
|
*/
|
|
1999
|
-
const relationFilterCtx = (base, tableKey) => {
|
|
2006
|
+
const relationFilterCtx = (base, tableKey, scope) => {
|
|
2000
2007
|
if (!base) return;
|
|
2001
2008
|
let byTable = narrowedCtxCache.get(base);
|
|
2002
2009
|
if (!byTable) {
|
|
@@ -2005,13 +2012,33 @@ const relationFilterCtx = (base, tableKey) => {
|
|
|
2005
2012
|
}
|
|
2006
2013
|
let ctx = byTable.get(tableKey);
|
|
2007
2014
|
if (!ctx) {
|
|
2015
|
+
let aliases = aliasCounters.get(base);
|
|
2016
|
+
if (!aliases) {
|
|
2017
|
+
aliases = { n: 0 };
|
|
2018
|
+
aliasCounters.set(base, aliases);
|
|
2019
|
+
}
|
|
2008
2020
|
ctx = {
|
|
2009
2021
|
...base,
|
|
2010
|
-
tableKey
|
|
2022
|
+
tableKey,
|
|
2023
|
+
aliases
|
|
2011
2024
|
};
|
|
2012
2025
|
byTable.set(tableKey, ctx);
|
|
2013
2026
|
}
|
|
2014
|
-
return
|
|
2027
|
+
return scope ? {
|
|
2028
|
+
...ctx,
|
|
2029
|
+
scope
|
|
2030
|
+
} : ctx;
|
|
2031
|
+
};
|
|
2032
|
+
/**
|
|
2033
|
+
* The row-policy predicate a subquery over a relation's target applies: the target's scope
|
|
2034
|
+
* and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
|
|
2035
|
+
* relation filter or a relation ordering sees exactly the rows the relation field returns.
|
|
2036
|
+
*/
|
|
2037
|
+
const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
|
|
2038
|
+
const scope = ctx.scope;
|
|
2039
|
+
if (!scope) return;
|
|
2040
|
+
const mode = scope.relationDefault(targetTableName, is(relation, One) && relation.optional === false);
|
|
2041
|
+
return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
|
|
2015
2042
|
};
|
|
2016
2043
|
/**
|
|
2017
2044
|
* Correlates the parent row with the aliased target table using the relation's own join
|
|
@@ -2102,8 +2129,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2102
2129
|
const throughTable = relation.throughTable;
|
|
2103
2130
|
let fromClause;
|
|
2104
2131
|
let joinCondition;
|
|
2132
|
+
let junctionPolicy;
|
|
2105
2133
|
if (throughTable) {
|
|
2106
2134
|
const aliasedThrough = aliasedTable(throughTable, `dgql_rel_${aliases.n++}`);
|
|
2135
|
+
const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
|
|
2136
|
+
if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
|
|
2107
2137
|
const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
|
|
2108
2138
|
fromClause = sql`${getTableAsAliasSQL(aliasedTarget)} inner join ${getTableAsAliasSQL(aliasedThrough)} on ${junctionJoin}`;
|
|
2109
2139
|
joinCondition = correlation;
|
|
@@ -2113,6 +2143,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2113
2143
|
}
|
|
2114
2144
|
const ownWhere = relationOwnWhere(relation);
|
|
2115
2145
|
const relationWhere = ownWhere ? relationsFilterToSQL(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2146
|
+
const policy = and(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
|
|
2116
2147
|
const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
|
|
2117
2148
|
...ctx,
|
|
2118
2149
|
tableKey: targetTableName,
|
|
@@ -2120,9 +2151,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2120
2151
|
}) : void 0;
|
|
2121
2152
|
if (mode === "every") {
|
|
2122
2153
|
if (!inner) return;
|
|
2123
|
-
return sql`not exists (select 1 from ${fromClause} where ${and(joinCondition, relationWhere, not(inner))})`;
|
|
2154
|
+
return sql`not exists (select 1 from ${fromClause} where ${and(joinCondition, relationWhere, policy, not(inner))})`;
|
|
2124
2155
|
}
|
|
2125
|
-
const condition = and(joinCondition, relationWhere, inner);
|
|
2156
|
+
const condition = and(joinCondition, relationWhere, policy, inner);
|
|
2126
2157
|
return mode === "none" ? sql`not exists (select 1 from ${fromClause} where ${condition})` : sql`exists (select 1 from ${fromClause} where ${condition})`;
|
|
2127
2158
|
};
|
|
2128
2159
|
/**
|
|
@@ -2648,12 +2679,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
|
|
|
2648
2679
|
const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
|
|
2649
2680
|
const ownWhere = relationOwnWhere(relation);
|
|
2650
2681
|
const relationWhere = ownWhere ? relationsFilterToSQL(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2682
|
+
const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
|
|
2651
2683
|
return {
|
|
2652
2684
|
fromParts: [...chain?.fromParts ?? [], getTableAsAliasSQL(aliasedTarget)],
|
|
2653
2685
|
conditions: [
|
|
2654
2686
|
...chain?.conditions ?? [],
|
|
2655
2687
|
...joinCondition ? [joinCondition] : [],
|
|
2656
|
-
...relationWhere ? [relationWhere] : []
|
|
2688
|
+
...relationWhere ? [relationWhere] : [],
|
|
2689
|
+
...policy ? [policy] : []
|
|
2657
2690
|
],
|
|
2658
2691
|
table: aliasedTarget
|
|
2659
2692
|
};
|
|
@@ -3883,10 +3916,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3883
3916
|
});
|
|
3884
3917
|
const relWhere = relationArgs?.where;
|
|
3885
3918
|
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;
|
|
3919
|
+
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
3920
|
const hasPagination = offset != null || limit != null;
|
|
3888
3921
|
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;
|
|
3922
|
+
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3890
3923
|
thisRecord.offset = offset;
|
|
3891
3924
|
thisRecord.limit = limit;
|
|
3892
3925
|
thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
|
|
@@ -3944,7 +3977,7 @@ const runRelationalSelect = async (opts) => {
|
|
|
3944
3977
|
tableName,
|
|
3945
3978
|
distinct,
|
|
3946
3979
|
pkNames: pkNames ?? [],
|
|
3947
|
-
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
3980
|
+
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
|
|
3948
3981
|
orderBy,
|
|
3949
3982
|
limit: single ? 1 : opts.limit,
|
|
3950
3983
|
offset
|
|
@@ -3959,8 +3992,8 @@ const runRelationalSelect = async (opts) => {
|
|
|
3959
3992
|
allRelations: filterCtx?.relationMap
|
|
3960
3993
|
}),
|
|
3961
3994
|
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,
|
|
3995
|
+
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,
|
|
3996
|
+
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
3997
|
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3965
3998
|
typeNameMapper,
|
|
3966
3999
|
filterCtx,
|
|
@@ -4542,7 +4575,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
|
|
|
4542
4575
|
tableName,
|
|
4543
4576
|
table,
|
|
4544
4577
|
where,
|
|
4545
|
-
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
4578
|
+
relationCtx: relationFilterCtx(filterCtx, tableName, scope),
|
|
4546
4579
|
required: requireWhere,
|
|
4547
4580
|
deleted: hard ? "INCLUDE" : void 0
|
|
4548
4581
|
});
|
|
@@ -4948,7 +4981,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4948
4981
|
const executor = resolveExecutor(db, context);
|
|
4949
4982
|
const uniqueIds = [...new Set(parentIds)];
|
|
4950
4983
|
const scope = resolveScope(policies, context, filterCtx);
|
|
4951
|
-
let whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
4984
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
4952
4985
|
if (distinct) {
|
|
4953
4986
|
const keys = await selectDistinctKeys({
|
|
4954
4987
|
db: executor,
|
|
@@ -4973,10 +5006,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4973
5006
|
[PARENT_KEY]: groupCol
|
|
4974
5007
|
} : columns;
|
|
4975
5008
|
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);
|
|
5009
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
|
|
4977
5010
|
else {
|
|
4978
5011
|
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) : [];
|
|
5012
|
+
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
|
|
4980
5013
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
4981
5014
|
rows = await q;
|
|
4982
5015
|
}
|
|
@@ -5656,6 +5689,7 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5656
5689
|
const normalized = {};
|
|
5657
5690
|
for (const [columnName, entry] of Object.entries(orderBy)) {
|
|
5658
5691
|
if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
|
|
5692
|
+
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
5693
|
const direction = typeof entry === "string" ? entry : entry?.direction;
|
|
5660
5694
|
if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
|
|
5661
5695
|
const priority = typeof entry === "string" ? void 0 : entry?.priority;
|
|
@@ -6036,7 +6070,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
|
|
|
6036
6070
|
const request = parseAggregateRequest(info, target);
|
|
6037
6071
|
if (!Object.keys(request.selection).length) return {};
|
|
6038
6072
|
let query = resolveExecutor(db, context).select(request.selection).from(table);
|
|
6039
|
-
const
|
|
6073
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6074
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6040
6075
|
if (where) query = query.where(where);
|
|
6041
6076
|
const rows = await query;
|
|
6042
6077
|
return assembleAggregateRow(rows[0] ?? {}, request, target);
|
|
@@ -6113,7 +6148,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6113
6148
|
const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
|
|
6114
6149
|
const executor = resolveExecutor(db, context);
|
|
6115
6150
|
const uniqueIds = [...new Set(parentIds)];
|
|
6116
|
-
const
|
|
6151
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6152
|
+
const whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
|
|
6117
6153
|
const selected = executor.select({
|
|
6118
6154
|
[GROUP_KEY]: groupCol,
|
|
6119
6155
|
...request.selection
|
|
@@ -6329,7 +6365,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6329
6365
|
...request.selection
|
|
6330
6366
|
};
|
|
6331
6367
|
let query = resolveExecutor(db, context).select(selection).from(table);
|
|
6332
|
-
const
|
|
6368
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6369
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6333
6370
|
if (where) query = query.where(where);
|
|
6334
6371
|
query = query.groupBy(...keyColumns.map(([, column]) => column));
|
|
6335
6372
|
const havingCondition = buildHavingCondition(args.having, columns);
|
|
@@ -6368,6 +6405,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6368
6405
|
};
|
|
6369
6406
|
};
|
|
6370
6407
|
//#endregion
|
|
6408
|
+
//#region src/util/builders/common/build-registry.ts
|
|
6409
|
+
/**
|
|
6410
|
+
* Keyed by the `extensions.drizzle` object of every object type the build generated, rather
|
|
6411
|
+
* than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
|
|
6412
|
+
* by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
|
|
6413
|
+
*/
|
|
6414
|
+
const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
|
|
6415
|
+
/**
|
|
6416
|
+
* What is known about the builds of one drizzle instance, for a caller with no resolve info to
|
|
6417
|
+
* look its build up by. Only a lone build is kept — with two or more there is no telling which
|
|
6418
|
+
* one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
|
|
6419
|
+
*/
|
|
6420
|
+
const buildsByDb = /* @__PURE__ */ new WeakMap();
|
|
6421
|
+
/** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
|
|
6422
|
+
const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
|
|
6423
|
+
/** Records a finished build against the object types it generated and the instance it read. */
|
|
6424
|
+
const registerBuildRuntime = (runtime, types) => {
|
|
6425
|
+
for (const type of types) {
|
|
6426
|
+
const meta = drizzleExtension(type);
|
|
6427
|
+
if (meta) runtimeByTypeMeta.set(meta, runtime);
|
|
6428
|
+
}
|
|
6429
|
+
const previous = buildsByDb.get(runtime.db);
|
|
6430
|
+
buildsByDb.set(runtime.db, previous ? {
|
|
6431
|
+
count: previous.count + 1,
|
|
6432
|
+
anyPolicies: previous.anyPolicies || hasPolicies(runtime)
|
|
6433
|
+
} : {
|
|
6434
|
+
count: 1,
|
|
6435
|
+
only: runtime,
|
|
6436
|
+
anyPolicies: hasPolicies(runtime)
|
|
6437
|
+
});
|
|
6438
|
+
};
|
|
6439
|
+
/** The build that generated `type`, when one did. */
|
|
6440
|
+
const runtimeForType = (type) => {
|
|
6441
|
+
const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
|
|
6442
|
+
return meta ? runtimeByTypeMeta.get(meta) : void 0;
|
|
6443
|
+
};
|
|
6444
|
+
/**
|
|
6445
|
+
* The build of `db` to use when no generated type identifies one: the only build, or none
|
|
6446
|
+
* when no build applies any policy. Throws when several builds exist and one of them would
|
|
6447
|
+
* change the read, since picking either could apply the wrong policies — or none.
|
|
6448
|
+
*/
|
|
6449
|
+
const runtimeForDb = (db, helper) => {
|
|
6450
|
+
const builds = buildsByDb.get(db);
|
|
6451
|
+
if (!builds) return;
|
|
6452
|
+
if (builds.count === 1) return builds.only;
|
|
6453
|
+
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.`);
|
|
6454
|
+
};
|
|
6455
|
+
//#endregion
|
|
6371
6456
|
//#region src/util/builders/nested-writes.ts
|
|
6372
6457
|
const ALL_OPS = [
|
|
6373
6458
|
"create",
|
|
@@ -6860,7 +6945,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6860
6945
|
* nothing is rejected: `connect: {}` would otherwise attach every row in the table.
|
|
6861
6946
|
*/
|
|
6862
6947
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
6863
|
-
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
|
|
6948
|
+
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
6864
6949
|
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
6950
|
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
6866
6951
|
};
|
|
@@ -7471,6 +7556,14 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7471
7556
|
}
|
|
7472
7557
|
if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
|
|
7473
7558
|
}
|
|
7559
|
+
registerBuildRuntime({
|
|
7560
|
+
db: ctx.db,
|
|
7561
|
+
filterCtx: ctx.filterCtx,
|
|
7562
|
+
policies: ctx.policies,
|
|
7563
|
+
scoped: !!ctx.tablePolicies?.scope,
|
|
7564
|
+
limits: ctx.limits,
|
|
7565
|
+
nullOrdering: adapter.nullOrdering
|
|
7566
|
+
}, Object.values(ctx.outputs));
|
|
7474
7567
|
return {
|
|
7475
7568
|
queries: ctx.queries,
|
|
7476
7569
|
mutations: ctx.mutations,
|
|
@@ -7593,7 +7686,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
|
|
|
7593
7686
|
const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
|
|
7594
7687
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7595
7688
|
await before();
|
|
7596
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7689
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7597
7690
|
const filters = scopedWhere({
|
|
7598
7691
|
scope,
|
|
7599
7692
|
tableName,
|
|
@@ -7639,7 +7732,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
|
|
|
7639
7732
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7640
7733
|
return {
|
|
7641
7734
|
set: input,
|
|
7642
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
7735
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
7643
7736
|
};
|
|
7644
7737
|
});
|
|
7645
7738
|
await executor.transaction(async (tx) => {
|
|
@@ -7682,7 +7775,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
7682
7775
|
const { where } = args;
|
|
7683
7776
|
const hard = canHardDelete && args.hard === true;
|
|
7684
7777
|
const scope = policies?.scope?.(context);
|
|
7685
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7778
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7686
7779
|
const filters = scopedWhere({
|
|
7687
7780
|
scope,
|
|
7688
7781
|
tableName,
|
|
@@ -8024,7 +8117,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8024
8117
|
excludedRef: excludedColumnRef,
|
|
8025
8118
|
withTarget: true,
|
|
8026
8119
|
serverOwned: Object.keys(contextColumns ?? {}),
|
|
8027
|
-
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
|
|
8120
|
+
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
|
|
8028
8121
|
});
|
|
8029
8122
|
const runBatch = async (executor, { values: rows, plan }) => {
|
|
8030
8123
|
let query = executor.insert(table).values(rows).returning(returning);
|
|
@@ -8106,7 +8199,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8106
8199
|
const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
|
|
8107
8200
|
const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
8108
8201
|
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);
|
|
8202
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8110
8203
|
const filters = scopedWhere({
|
|
8111
8204
|
scope,
|
|
8112
8205
|
tableName,
|
|
@@ -8185,7 +8278,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8185
8278
|
await before();
|
|
8186
8279
|
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
8187
8280
|
const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
|
|
8188
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
8281
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8189
8282
|
const filters = scopedWhere({
|
|
8190
8283
|
scope,
|
|
8191
8284
|
tableName,
|
|
@@ -8636,7 +8729,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
8636
8729
|
return {
|
|
8637
8730
|
set: input,
|
|
8638
8731
|
ops,
|
|
8639
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
8732
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
8640
8733
|
};
|
|
8641
8734
|
});
|
|
8642
8735
|
const anyNested = entries.some((entry) => entry.ops);
|
|
@@ -8806,6 +8899,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
|
|
|
8806
8899
|
typeNameSuffix: options.typeNameSuffix
|
|
8807
8900
|
});
|
|
8808
8901
|
/**
|
|
8902
|
+
* The build whose policies the read applies. Looked up through the generated types the
|
|
8903
|
+
* resolve info reaches — the field's return type, the root table's type in the executing
|
|
8904
|
+
* schema, the parent type — so a resolver over a generated type finds its own build. Without
|
|
8905
|
+
* resolve info (a parsed tree was passed), the instance's only build is used.
|
|
8906
|
+
*/
|
|
8907
|
+
const runtimeFor = (info, db, typeName, helper) => {
|
|
8908
|
+
if (!("fieldsByTypeName" in info)) {
|
|
8909
|
+
const candidates = [
|
|
8910
|
+
info.returnType ? getNamedType(info.returnType) : void 0,
|
|
8911
|
+
info.schema?.getType(typeName),
|
|
8912
|
+
info.parentType
|
|
8913
|
+
];
|
|
8914
|
+
for (const candidate of candidates) {
|
|
8915
|
+
const runtime = runtimeForType(candidate);
|
|
8916
|
+
if (runtime && runtime.db === db) return runtime;
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
return runtimeForDb(db, helper);
|
|
8920
|
+
};
|
|
8921
|
+
/**
|
|
8922
|
+
* The request's row policies under `runtime`, bound to the context the caller passed. A build
|
|
8923
|
+
* with `scope` hooks cannot be read without that context: the hooks would be called with
|
|
8924
|
+
* nothing, and running the read unscoped is the leak the option exists to prevent.
|
|
8925
|
+
*/
|
|
8926
|
+
const scopeFor = (runtime, options, helper) => {
|
|
8927
|
+
if (!runtime?.policies?.scope) return;
|
|
8928
|
+
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.`);
|
|
8929
|
+
return runtime.policies.scope(options.context);
|
|
8930
|
+
};
|
|
8931
|
+
/**
|
|
8809
8932
|
* Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
|
|
8810
8933
|
* inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
|
|
8811
8934
|
* these helpers) does not pay for the walk twice.
|
|
@@ -8832,6 +8955,10 @@ const parseInfo = (info) => {
|
|
|
8832
8955
|
* const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
|
|
8833
8956
|
* ```
|
|
8834
8957
|
*
|
|
8958
|
+
* Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
|
|
8959
|
+
* `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
|
|
8960
|
+
* through the resolve info; pass `context` when it configures `scope`.
|
|
8961
|
+
*
|
|
8835
8962
|
* Returns `undefined` when the selection asks for no relations, which is the value the
|
|
8836
8963
|
* query builder wants for "no `with` clause".
|
|
8837
8964
|
*/
|
|
@@ -8841,11 +8968,16 @@ const selectionToWith = (info, options) => {
|
|
|
8841
8968
|
const { tables, relationMap, filterCtx } = contextFor(db);
|
|
8842
8969
|
if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
|
|
8843
8970
|
if (!relationMap[table]) return;
|
|
8971
|
+
const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
|
|
8972
|
+
const runtime = runtimeFor(info, db, typeName, "selectionToWith");
|
|
8844
8973
|
const parsed = parseInfo(info);
|
|
8845
|
-
const params = extractRelationsParams(relationMap, tables, table, parsed,
|
|
8974
|
+
const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
|
|
8846
8975
|
typeNameMapper,
|
|
8847
|
-
filterCtx,
|
|
8848
|
-
resolveName
|
|
8976
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
8977
|
+
resolveName,
|
|
8978
|
+
scope: scopeFor(runtime, options, "selectionToWith"),
|
|
8979
|
+
limits: runtime?.limits,
|
|
8980
|
+
defaultOrderBy: runtime?.policies?.defaultOrderBy
|
|
8849
8981
|
});
|
|
8850
8982
|
return params && Object.keys(params).length ? params : void 0;
|
|
8851
8983
|
};
|
|
@@ -8857,14 +8989,16 @@ const selectionToWith = (info, options) => {
|
|
|
8857
8989
|
* This is what an override usually wants after it has done its own work:
|
|
8858
8990
|
*
|
|
8859
8991
|
* ```ts
|
|
8860
|
-
* const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
|
|
8992
|
+
* const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
|
|
8861
8993
|
* ```
|
|
8862
8994
|
*
|
|
8863
8995
|
* 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.
|
|
8996
|
+
* because it is the generated schema's own code path. So do the build's row policies: the
|
|
8997
|
+
* root table and every nested relation are scoped and soft-delete-filtered, and the limit
|
|
8998
|
+
* policy and default ordering apply. Pass `context` when the build configures `scope`.
|
|
8865
8999
|
*/
|
|
8866
9000
|
const resolveSelection = async (info, options) => {
|
|
8867
|
-
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
|
|
9001
|
+
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
|
|
8868
9002
|
const resolveName = namingRuleOf(options);
|
|
8869
9003
|
const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
|
|
8870
9004
|
const table = tables[tableName];
|
|
@@ -8874,25 +9008,34 @@ const resolveSelection = async (info, options) => {
|
|
|
8874
9008
|
const source = executor ?? db;
|
|
8875
9009
|
const queryBase = source.query?.[tableName];
|
|
8876
9010
|
if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
|
|
9011
|
+
const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
|
|
9012
|
+
const runtime = runtimeFor(info, db, typeName, "resolveSelection");
|
|
9013
|
+
const scope = scopeFor(runtime, options, "resolveSelection");
|
|
9014
|
+
const defaultOrderBy = runtime?.policies?.defaultOrderBy;
|
|
8877
9015
|
return runRelationalSelect({
|
|
8878
9016
|
queryBase,
|
|
8879
9017
|
tables,
|
|
8880
9018
|
tableName,
|
|
8881
9019
|
table,
|
|
8882
9020
|
relationMap,
|
|
8883
|
-
typeName
|
|
9021
|
+
typeName,
|
|
8884
9022
|
typeNameMapper,
|
|
8885
9023
|
resolveName,
|
|
8886
9024
|
parsedInfo,
|
|
8887
9025
|
single,
|
|
8888
9026
|
where,
|
|
8889
|
-
orderBy,
|
|
8890
|
-
limit,
|
|
9027
|
+
orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
|
|
9028
|
+
limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
|
|
8891
9029
|
offset,
|
|
8892
9030
|
after,
|
|
8893
|
-
filterCtx,
|
|
9031
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
8894
9032
|
pkNames: primaryKeyOf(tableName),
|
|
8895
|
-
db: source
|
|
9033
|
+
db: source,
|
|
9034
|
+
scope,
|
|
9035
|
+
deleted,
|
|
9036
|
+
limits: runtime?.limits,
|
|
9037
|
+
defaultOrderBy,
|
|
9038
|
+
nullOrdering: runtime?.nullOrdering
|
|
8896
9039
|
});
|
|
8897
9040
|
};
|
|
8898
9041
|
//#endregion
|