@vantreeseba/drizzle-graphql 15.0.0 → 15.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -68
- package/dist/index.cjs +204 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -4
- package/dist/index.d.ts +20 -4
- package/dist/index.js +204 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1616,13 +1616,20 @@ const nullableOps = {
|
|
|
1616
1616
|
isNull: drizzle_orm.isNull,
|
|
1617
1617
|
isNotNull: drizzle_orm.isNotNull
|
|
1618
1618
|
};
|
|
1619
|
+
/** Operators whose operand is an on/off flag rather than a value: `false` adds no condition. */
|
|
1620
|
+
const FLAG_OPERATORS = /* @__PURE__ */ new Set([
|
|
1621
|
+
"isNull",
|
|
1622
|
+
"isNotNull",
|
|
1623
|
+
"isEmpty"
|
|
1624
|
+
]);
|
|
1619
1625
|
const extractFiltersColumn = (column, columnName, operators) => {
|
|
1620
1626
|
const { OR, AND, NOT, insensitive, ...restOperators } = operators;
|
|
1621
1627
|
const foldCase = insensitive === true;
|
|
1622
1628
|
const entries = Object.entries(restOperators);
|
|
1623
1629
|
const variants = [];
|
|
1624
1630
|
for (const [operatorName, operatorValue] of entries) {
|
|
1625
|
-
if (operatorValue === null || operatorValue ===
|
|
1631
|
+
if (operatorValue === null || operatorValue === void 0) continue;
|
|
1632
|
+
if (operatorValue === false && FLAG_OPERATORS.has(operatorName)) continue;
|
|
1626
1633
|
if (operatorName in singleValueOps) {
|
|
1627
1634
|
const singleValue = remapFromGraphQLCore(operatorValue, column, columnName);
|
|
1628
1635
|
variants.push(foldCase ? lowerComparison(column, operatorName, singleValue) : singleValueOps[operatorName](column, singleValue));
|
|
@@ -1813,6 +1820,48 @@ const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
|
|
|
1813
1820
|
};
|
|
1814
1821
|
};
|
|
1815
1822
|
/**
|
|
1823
|
+
* The junction table and its two columns for a `.through()` (many-to-many) relation:
|
|
1824
|
+
* `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
|
|
1825
|
+
* with no junction; `null` for one whose junction cannot be used here — more than one column
|
|
1826
|
+
* on either side, or columns that are not on the junction table.
|
|
1827
|
+
*/
|
|
1828
|
+
const extractJunctionColumns = (relEntry) => {
|
|
1829
|
+
const rel = relEntry.relation ?? relEntry;
|
|
1830
|
+
const junction = rel?.throughTable;
|
|
1831
|
+
if (!junction) return;
|
|
1832
|
+
const sourceEntries = rel.through?.source;
|
|
1833
|
+
const targetEntries = rel.through?.target;
|
|
1834
|
+
if (sourceEntries?.length !== 1 || targetEntries?.length !== 1) return null;
|
|
1835
|
+
const junctionCols = Object.values((0, drizzle_orm.getColumns)(junction));
|
|
1836
|
+
const sourceCol = unwrapRelationColumn(sourceEntries[0]);
|
|
1837
|
+
const targetCol = unwrapRelationColumn(targetEntries[0]);
|
|
1838
|
+
if (!sourceCol || !targetCol || !junctionCols.includes(sourceCol) || !junctionCols.includes(targetCol)) return null;
|
|
1839
|
+
return {
|
|
1840
|
+
table: junction,
|
|
1841
|
+
sourceCol,
|
|
1842
|
+
targetCol
|
|
1843
|
+
};
|
|
1844
|
+
};
|
|
1845
|
+
/**
|
|
1846
|
+
* The relation's own declared `where` as a condition on a query that reads the target table,
|
|
1847
|
+
* or `undefined` when it has none. The eager path gets this from drizzle's `with:`; the batched
|
|
1848
|
+
* field resolver and `<relation>Aggregate` read the target table directly, so they have to add
|
|
1849
|
+
* it themselves or they return rows the relation hides.
|
|
1850
|
+
*
|
|
1851
|
+
* A reversed filter (inherited from the relation on the other side) is written against the
|
|
1852
|
+
* parent table, so it is applied as "the row's parent key belongs to a parent that passes it".
|
|
1853
|
+
* `parentKeyCol` is the parent's join column and `groupCol` the column in the query that holds
|
|
1854
|
+
* the parent key for each row — the target's foreign key, or the junction's source column.
|
|
1855
|
+
*/
|
|
1856
|
+
const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCol, groupCol) => {
|
|
1857
|
+
const rel = relEntry.relation ?? relEntry;
|
|
1858
|
+
const ownWhere = relationOwnWhere(rel);
|
|
1859
|
+
if (!ownWhere) return;
|
|
1860
|
+
if (!isRelationFilterReversed(rel)) return (0, drizzle_orm.relationsFilterToSQL)(targetTable, ownWhere);
|
|
1861
|
+
const parentFilter = (0, drizzle_orm.relationsFilterToSQL)(parentTable, ownWhere);
|
|
1862
|
+
return drizzle_orm.sql`${groupCol} in (select ${parentKeyCol} from ${parentTable} where ${parentFilter})`;
|
|
1863
|
+
};
|
|
1864
|
+
/**
|
|
1816
1865
|
* Whether a to-one relation can back a relation orderBy hop. `.through()` (junction)
|
|
1817
1866
|
* relations are excluded — the ordering subquery only joins the direct target, never the
|
|
1818
1867
|
* junction table. (Relation *filters* do support `.through()` — this guard is orderBy-only.)
|
|
@@ -2714,6 +2763,45 @@ const cursorOrderExprs = (table, entries) => {
|
|
|
2714
2763
|
return entries.flatMap(([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls ?? void 0, cols[column]));
|
|
2715
2764
|
};
|
|
2716
2765
|
/**
|
|
2766
|
+
* Property a row carries an ordering column's full-precision text under, when the column holds
|
|
2767
|
+
* more than a JS Date can. Namespaced like {@link ROW_CURSOR_PROP}, and removed from the row
|
|
2768
|
+
* once its cursor is encoded.
|
|
2769
|
+
*/
|
|
2770
|
+
const preciseTextProp = (column) => `__drizzle_graphql_cursor_text_${column}`;
|
|
2771
|
+
/**
|
|
2772
|
+
* Whether a column reads into a JS Date (milliseconds) while the database keeps finer
|
|
2773
|
+
* fractional seconds: a PostgreSQL `timestamp` / `timestamptz` in `mode: 'date'` (precision 6
|
|
2774
|
+
* unless it says otherwise) or a MySQL `datetime` / `timestamp` in `mode: 'date'` with
|
|
2775
|
+
* `fsp` above 3. String-mode columns already arrive as the database's own text, and `time` is
|
|
2776
|
+
* always a string, so neither loses anything.
|
|
2777
|
+
*/
|
|
2778
|
+
const outgrowsJsDate = (column) => {
|
|
2779
|
+
if ((0, drizzle_orm.is)(column, drizzle_orm_pg_core.PgTimestamp)) return column.precision === void 0 || column.precision > 3;
|
|
2780
|
+
if ((0, drizzle_orm.is)(column, drizzle_orm_mysql_core.MySqlDateTime) || (0, drizzle_orm.is)(column, drizzle_orm_mysql_core.MySqlTimestamp)) return (column.fsp ?? 0) > 3;
|
|
2781
|
+
return false;
|
|
2782
|
+
};
|
|
2783
|
+
/**
|
|
2784
|
+
* Extra SQL expressions a cursor-computing query selects alongside the row: for each ordering
|
|
2785
|
+
* column whose values a JS Date would truncate, the value as the database's own text. The
|
|
2786
|
+
* cursor then encodes that text rather than the Date, and `after` compares the column against
|
|
2787
|
+
* it — so two rows a microsecond apart stay distinct positions. `table` may be the aliased RQB
|
|
2788
|
+
* proxy. Empty when no ordering column needs it.
|
|
2789
|
+
*/
|
|
2790
|
+
const cursorPreciseExtras = (table, entries) => {
|
|
2791
|
+
const cols = (0, drizzle_orm.getColumns)(table);
|
|
2792
|
+
const extras = {};
|
|
2793
|
+
for (const [name] of entries) {
|
|
2794
|
+
const column = cols[name];
|
|
2795
|
+
if (column && outgrowsJsDate(column)) extras[preciseTextProp(name)] = (0, drizzle_orm.is)(column, drizzle_orm_pg_core.PgTimestamp) ? drizzle_orm.sql`${column}::text` : drizzle_orm.sql`cast(${column} as char)`;
|
|
2796
|
+
}
|
|
2797
|
+
return extras;
|
|
2798
|
+
};
|
|
2799
|
+
/**
|
|
2800
|
+
* {@link cursorPreciseExtras} aliased under their property names, for a `select({...})` that
|
|
2801
|
+
* may be wrapped in a subquery (where a bare SQL expression would have no name).
|
|
2802
|
+
*/
|
|
2803
|
+
const cursorPreciseSelection = (table, entries) => Object.fromEntries(Object.entries(cursorPreciseExtras(table, entries)).map(([prop, expression]) => [prop, expression.as(prop)]));
|
|
2804
|
+
/**
|
|
2717
2805
|
* Serializes one ordering-tuple value into a JSON-safe shape. Dates and bigints don't survive
|
|
2718
2806
|
* JSON.stringify losslessly (bigint throws, Date turns into an untagged string), so they are
|
|
2719
2807
|
* tagged; decimals already arrive from the driver as strings, which are lossless as-is.
|
|
@@ -2748,7 +2836,11 @@ const cursorEncoder = (entries) => {
|
|
|
2748
2836
|
const spec = JSON.stringify(entries);
|
|
2749
2837
|
const columns = entries.map(([column]) => column);
|
|
2750
2838
|
return (row) => {
|
|
2751
|
-
const values = JSON.stringify(columns.map((column) =>
|
|
2839
|
+
const values = JSON.stringify(columns.map((column) => {
|
|
2840
|
+
const value = row[column] ?? null;
|
|
2841
|
+
const precise = row[preciseTextProp(column)];
|
|
2842
|
+
return value !== null && typeof precise === "string" ? precise : encodeCursorValue(value);
|
|
2843
|
+
}));
|
|
2752
2844
|
return Buffer.from(`{"o":${spec},"v":${values}}`, "utf8").toString("base64url");
|
|
2753
2845
|
};
|
|
2754
2846
|
};
|
|
@@ -2841,7 +2933,11 @@ const selectsCursorField = (info, table) => {
|
|
|
2841
2933
|
*/
|
|
2842
2934
|
const attachRowCursors = (rows, entries) => {
|
|
2843
2935
|
const encode = cursorEncoder(entries);
|
|
2844
|
-
|
|
2936
|
+
const preciseProps = entries.map(([column]) => preciseTextProp(column));
|
|
2937
|
+
for (const row of rows) {
|
|
2938
|
+
row[ROW_CURSOR_PROP] = encode(row);
|
|
2939
|
+
for (const prop of preciseProps) delete row[prop];
|
|
2940
|
+
}
|
|
2845
2941
|
};
|
|
2846
2942
|
/** Resolver for the `cursor` meta field: reads the value a list resolver attached, if any. */
|
|
2847
2943
|
const rowCursorResolver = (source) => source?.[ROW_CURSOR_PROP] ?? null;
|
|
@@ -2976,6 +3072,11 @@ const getUniqueColumnSets = (table, getTableConfig) => {
|
|
|
2976
3072
|
//#endregion
|
|
2977
3073
|
//#region src/util/builders/common/distinct.ts
|
|
2978
3074
|
/**
|
|
3075
|
+
* Alias the parent key comes back under when the pass runs through a junction table. See
|
|
3076
|
+
* `selectDistinctKeys`' `junction` parameter.
|
|
3077
|
+
*/
|
|
3078
|
+
const DISTINCT_PARENT = "__dgql_distinct_parent";
|
|
3079
|
+
/**
|
|
2979
3080
|
* Keeps the first row of each distinct combination of the requested columns, following the
|
|
2980
3081
|
* query's own ordering, then applies `limit`/`offset` to what survives — and returns the
|
|
2981
3082
|
* surviving rows' primary key values in that order.
|
|
@@ -2988,9 +3089,14 @@ const getUniqueColumnSets = (table, getTableConfig) => {
|
|
|
2988
3089
|
* `partitionBy` prepends extra columns to the window's partition. A to-many relation field
|
|
2989
3090
|
* uses it to pass the foreign key, which makes the pass distinct *within each parent* rather
|
|
2990
3091
|
* than across the whole batch — the same meaning `distinct` has on a root list.
|
|
3092
|
+
*
|
|
3093
|
+
* `junction` is for a many-to-many relation: the target is joined to the junction `on` the
|
|
3094
|
+
* given condition, and each returned key also carries the parent it was kept for, under
|
|
3095
|
+
* {@link DISTINCT_PARENT}. A target row linked to several parents can be kept for one and
|
|
3096
|
+
* dropped for another, so a key alone cannot narrow such a batch.
|
|
2991
3097
|
*/
|
|
2992
3098
|
const selectDistinctKeys = async (params) => {
|
|
2993
|
-
const { db, table, tableName, distinct, pkNames, where, orderBy, limit, offset, partitionBy } = params;
|
|
3099
|
+
const { db, table, tableName, distinct, pkNames, where, orderBy, limit, offset, partitionBy, junction } = params;
|
|
2994
3100
|
const cols = (0, drizzle_orm.getColumns)(table);
|
|
2995
3101
|
if (!pkNames.length) throw drizzleError(`Table ${tableName} has no primary key, so 'distinct' cannot be applied to it.`, { code: "DRIZZLE_INVALID_DISTINCT" });
|
|
2996
3102
|
const requestedCols = distinct.map((name) => cols[name]).filter(Boolean);
|
|
@@ -2999,12 +3105,17 @@ const selectDistinctKeys = async (params) => {
|
|
|
2999
3105
|
const orderEntries = orderBy ? orderByEntries(orderBy) : [];
|
|
3000
3106
|
const windowOrder = [...orderEntries.flatMap(([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls, cols[column])), ...primaryKeyOrderExprs(table, pkNames)];
|
|
3001
3107
|
const rowNumber = drizzle_orm.sql`row_number() over (partition by ${drizzle_orm.sql.join(partitionCols, drizzle_orm.sql`, `)} order by ${drizzle_orm.sql.join(windowOrder, drizzle_orm.sql`, `)})`.as(DISTINCT_RN);
|
|
3002
|
-
const
|
|
3108
|
+
const selected = db.select({
|
|
3003
3109
|
...cols,
|
|
3110
|
+
...junction ? { [DISTINCT_PARENT]: junction.parentKey } : {},
|
|
3004
3111
|
[DISTINCT_RN]: rowNumber
|
|
3005
|
-
}).from(table)
|
|
3112
|
+
}).from(table);
|
|
3113
|
+
const sub = (junction ? selected.innerJoin(junction.table, junction.on) : selected).where(where).as("__dgql_distinct");
|
|
3006
3114
|
const outerOrder = [...orderEntries.flatMap(([column, direction, nulls]) => orderExpressions(sub[column], direction, nulls, cols[column])), ...pkNames.filter((name) => sub[name]).map((name) => (0, drizzle_orm.asc)(sub[name]))];
|
|
3007
|
-
let query = db.select(
|
|
3115
|
+
let query = db.select({
|
|
3116
|
+
...Object.fromEntries(pkNames.map((name) => [name, sub[name]])),
|
|
3117
|
+
...junction ? { [DISTINCT_PARENT]: sub[DISTINCT_PARENT] } : {}
|
|
3118
|
+
}).from(sub).where((0, drizzle_orm.eq)(sub[DISTINCT_RN], 1)).orderBy(...outerOrder);
|
|
3008
3119
|
if (offset) query = query.offset(offset);
|
|
3009
3120
|
if (limit != null) query = query.limit(limit);
|
|
3010
3121
|
return await query;
|
|
@@ -3184,6 +3295,21 @@ const withDefaultOrderBy = (args, tableName, defaults) => {
|
|
|
3184
3295
|
} : args;
|
|
3185
3296
|
};
|
|
3186
3297
|
/**
|
|
3298
|
+
* Rejects a negative `limit` or `offset`. Neither is a page size or a position, and the
|
|
3299
|
+
* dialects disagree on what one means: SQLite reads `LIMIT -1` as "no limit" — which would walk
|
|
3300
|
+
* straight past `maxLimit` — while PostgreSQL and MySQL fail with a raw database error. Applied
|
|
3301
|
+
* whether or not the table has a limit policy.
|
|
3302
|
+
*/
|
|
3303
|
+
const assertPagingNotNegative = (paging, errorCtx) => {
|
|
3304
|
+
for (const name of ["limit", "offset"]) {
|
|
3305
|
+
const value = paging[name];
|
|
3306
|
+
if (value != null && value < 0) throw drizzleError(`'${name}' must not be negative, got ${value}.`, {
|
|
3307
|
+
code: "DRIZZLE_INVALID_INPUT_VALUE",
|
|
3308
|
+
...errorCtx
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3312
|
+
/**
|
|
3187
3313
|
* The `limit` a query actually runs with, given the policy for the table it reads.
|
|
3188
3314
|
*
|
|
3189
3315
|
* A request that passes no `limit` falls back to `defaultLimit`, or to `maxLimit` when only
|
|
@@ -3195,6 +3321,7 @@ const withDefaultOrderBy = (args, tableName, defaults) => {
|
|
|
3195
3321
|
* fail a client's query over.
|
|
3196
3322
|
*/
|
|
3197
3323
|
const applyLimitPolicy = (limit, policy, errorCtx) => {
|
|
3324
|
+
assertPagingNotNegative({ limit }, errorCtx);
|
|
3198
3325
|
if (!policy) return limit ?? void 0;
|
|
3199
3326
|
const { defaultLimit, maxLimit, clampToMax } = policy;
|
|
3200
3327
|
if (limit == null) {
|
|
@@ -3712,6 +3839,11 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3712
3839
|
thisRecord.columns = columns;
|
|
3713
3840
|
const relationArgs = (0, drizzle_orm.is)(relEntry.relation, drizzle_orm.One) ? field.args : withDefaultOrderBy(field.args ?? {}, targetTableName, defaultOrderBy);
|
|
3714
3841
|
const offset = relationArgs?.offset ?? void 0;
|
|
3842
|
+
assertPagingNotNegative({ offset }, {
|
|
3843
|
+
table: targetTableName,
|
|
3844
|
+
operation: "relation",
|
|
3845
|
+
relation: relName
|
|
3846
|
+
});
|
|
3715
3847
|
const limit = (0, drizzle_orm.is)(relEntry.relation, drizzle_orm.One) ? relationArgs?.limit ?? void 0 : applyLimitPolicy(relationArgs?.limit, limits?.(targetTableName), {
|
|
3716
3848
|
table: targetTableName,
|
|
3717
3849
|
operation: "relation",
|
|
@@ -3755,6 +3887,10 @@ const pruneNonEagerRelations = (relationMap, shouldEagerLoad) => {
|
|
|
3755
3887
|
const runRelationalSelect = async (opts) => {
|
|
3756
3888
|
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
3757
3889
|
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
3890
|
+
assertPagingNotNegative({
|
|
3891
|
+
limit: opts.limit,
|
|
3892
|
+
offset
|
|
3893
|
+
});
|
|
3758
3894
|
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
3759
3895
|
let cursorEntries;
|
|
3760
3896
|
if (!single && (after != null || cursorSelected)) {
|
|
@@ -3806,7 +3942,11 @@ const runRelationalSelect = async (opts) => {
|
|
|
3806
3942
|
const result = await queryBase.findFirst(omitUndefinedKeys(params));
|
|
3807
3943
|
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
3808
3944
|
}
|
|
3809
|
-
if (cursorEntries && cursorSelected)
|
|
3945
|
+
if (cursorEntries && cursorSelected) {
|
|
3946
|
+
for (const [column] of cursorEntries) params.columns[column] = true;
|
|
3947
|
+
const preciseProps = Object.keys(cursorPreciseExtras(table, cursorEntries));
|
|
3948
|
+
if (preciseProps.length) params.extras = Object.fromEntries(preciseProps.map((prop) => [prop, (aliasedTable) => cursorPreciseExtras(aliasedTable, cursorEntries)[prop]]));
|
|
3949
|
+
}
|
|
3810
3950
|
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
3811
3951
|
const result = await queryBase.findMany(omitUndefinedKeys(params));
|
|
3812
3952
|
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
@@ -4625,16 +4765,17 @@ const identifyRows = (field, args) => {
|
|
|
4625
4765
|
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
4626
4766
|
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
4627
4767
|
*/
|
|
4628
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
|
|
4768
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
|
|
4629
4769
|
const cols = columns ?? (0, drizzle_orm.getColumns)(targetTable);
|
|
4630
4770
|
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
4631
4771
|
const orderClause = orderExprs.length ? drizzle_orm.sql` order by ${drizzle_orm.sql.join(orderExprs, drizzle_orm.sql`, `)}` : drizzle_orm.sql``;
|
|
4632
4772
|
const RN = "__drizzle_graphql_rn";
|
|
4633
|
-
const
|
|
4634
|
-
const
|
|
4773
|
+
const partitionCol = junction ? junction.sourceCol : foreignCol;
|
|
4774
|
+
const rowNumber = drizzle_orm.sql`row_number() over (partition by ${partitionCol}${orderClause})`.as(RN);
|
|
4775
|
+
const sub = withJunction(db.select({
|
|
4635
4776
|
...cols,
|
|
4636
4777
|
[RN]: rowNumber
|
|
4637
|
-
}).from(targetTable).where(whereCondition).as("__paginated");
|
|
4778
|
+
}).from(targetTable), junction, foreignCol).where(whereCondition).as("__paginated");
|
|
4638
4779
|
const lower = offset ?? 0;
|
|
4639
4780
|
const windowConds = [(0, drizzle_orm.gt)(sub[RN], lower)];
|
|
4640
4781
|
if (limit != null) windowConds.push((0, drizzle_orm.lte)(sub[RN], lower + limit));
|
|
@@ -4643,6 +4784,14 @@ const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereC
|
|
|
4643
4784
|
return rows;
|
|
4644
4785
|
};
|
|
4645
4786
|
/**
|
|
4787
|
+
* Alias the parent key is selected under when a relation goes through a junction table. The
|
|
4788
|
+
* key lives on the junction, not the target, so it has to be selected explicitly to group the
|
|
4789
|
+
* rows by parent — and a target row linked to several parents comes back once per parent.
|
|
4790
|
+
*/
|
|
4791
|
+
const PARENT_KEY = "__drizzle_graphql_parent";
|
|
4792
|
+
/** `from(target)`, joined to the junction table when the relation has one. */
|
|
4793
|
+
const withJunction = (query, junction, foreignCol) => junction ? query.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : query;
|
|
4794
|
+
/**
|
|
4646
4795
|
* Creates a RelationResolverFactory that generates field-level resolvers for each relation.
|
|
4647
4796
|
* Each resolver:
|
|
4648
4797
|
* 1. Returns pre-fetched data if the parent resolver already included it (eager path, zero cost).
|
|
@@ -4664,6 +4813,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4664
4813
|
const joinCols = extractRelationJoinColumns(relEntry, parentTable, targetTable);
|
|
4665
4814
|
if (!joinCols) return;
|
|
4666
4815
|
const { localColPropName, foreignCol, foreignColPropName } = joinCols;
|
|
4816
|
+
const junction = extractJunctionColumns(relEntry);
|
|
4817
|
+
const groupCol = junction ? junction.sourceCol : foreignCol;
|
|
4818
|
+
const groupKey = junction ? PARENT_KEY : foreignColPropName;
|
|
4819
|
+
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, (0, drizzle_orm.getColumns)(parentTable)[localColPropName], groupCol);
|
|
4667
4820
|
const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, isOne && relEntry.relation?.optional === false);
|
|
4668
4821
|
const limitPolicy = isOne ? void 0 : limits?.(targetTableName);
|
|
4669
4822
|
const targetPkNames = relEntry.targetPkNames ?? [];
|
|
@@ -4697,7 +4850,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4697
4850
|
const column = targetColumns[columnName];
|
|
4698
4851
|
if (column) columns[columnName] = column;
|
|
4699
4852
|
}
|
|
4700
|
-
return
|
|
4853
|
+
return cursorEntries ? {
|
|
4854
|
+
...columns,
|
|
4855
|
+
...cursorPreciseSelection(targetTable, cursorEntries)
|
|
4856
|
+
} : columns;
|
|
4701
4857
|
};
|
|
4702
4858
|
const resolve = async (parent, args, context, info) => {
|
|
4703
4859
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
@@ -4707,6 +4863,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4707
4863
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4708
4864
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
4709
4865
|
const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
|
|
4866
|
+
assertPagingNotNegative({ offset }, errorCtx);
|
|
4710
4867
|
const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
|
|
4711
4868
|
const cursorSelected = !isOne && !!info && selectsCursorField(info, targetTable);
|
|
4712
4869
|
let cursorEntries;
|
|
@@ -4741,11 +4898,12 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4741
4898
|
loaderKey: `${tableName}::${relationName}::${argsKey}`
|
|
4742
4899
|
};
|
|
4743
4900
|
});
|
|
4901
|
+
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" });
|
|
4744
4902
|
return getOrCreateLoader(context, loaderKey, async (parentIds) => {
|
|
4745
4903
|
const executor = resolveExecutor(db, context);
|
|
4746
4904
|
const uniqueIds = [...new Set(parentIds)];
|
|
4747
4905
|
const scope = resolveScope(policies, context, filterCtx);
|
|
4748
|
-
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(
|
|
4906
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
4749
4907
|
if (distinct) {
|
|
4750
4908
|
const keys = await selectDistinctKeys({
|
|
4751
4909
|
db: executor,
|
|
@@ -4755,27 +4913,41 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4755
4913
|
pkNames: targetPkNames,
|
|
4756
4914
|
where: whereCondition,
|
|
4757
4915
|
orderBy: orderByArg,
|
|
4758
|
-
partitionBy: [
|
|
4916
|
+
partitionBy: [groupCol],
|
|
4917
|
+
junction: junction ? {
|
|
4918
|
+
table: junction.table,
|
|
4919
|
+
on: (0, drizzle_orm.eq)(junction.targetCol, foreignCol),
|
|
4920
|
+
parentKey: groupCol
|
|
4921
|
+
} : void 0
|
|
4759
4922
|
});
|
|
4760
4923
|
if (!keys.length) return parentIds.map(() => isOne ? null : []);
|
|
4761
|
-
whereCondition = (0, drizzle_orm.and)(whereCondition, primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
4924
|
+
whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(groupCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
4762
4925
|
}
|
|
4926
|
+
const selection = junction ? {
|
|
4927
|
+
...columns ?? (0, drizzle_orm.getColumns)(targetTable),
|
|
4928
|
+
[PARENT_KEY]: groupCol
|
|
4929
|
+
} : columns;
|
|
4763
4930
|
let rows;
|
|
4764
|
-
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames,
|
|
4931
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName), whereArg, junction);
|
|
4765
4932
|
else {
|
|
4766
|
-
let q = (
|
|
4933
|
+
let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
|
|
4767
4934
|
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
4768
4935
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
4769
4936
|
rows = await q;
|
|
4770
4937
|
}
|
|
4771
4938
|
if (cursorEntries) attachRowCursors(rows, cursorEntries);
|
|
4939
|
+
const keyOf = (row) => {
|
|
4940
|
+
const key = String(row[groupKey]);
|
|
4941
|
+
if (junction) delete row[PARENT_KEY];
|
|
4942
|
+
return key;
|
|
4943
|
+
};
|
|
4772
4944
|
if (isOne) {
|
|
4773
|
-
const byKey = new Map(rows.map((row) => [
|
|
4945
|
+
const byKey = new Map(rows.map((row) => [keyOf(row), row]));
|
|
4774
4946
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
4775
4947
|
return parentIds.map((id) => byKey.get(String(id)) ?? null);
|
|
4776
4948
|
}
|
|
4777
4949
|
const grouped = new Map(uniqueIds.map((id) => [String(id), []]));
|
|
4778
|
-
for (const row of rows) grouped.get(
|
|
4950
|
+
for (const row of rows) grouped.get(keyOf(row))?.push(row);
|
|
4779
4951
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
4780
4952
|
return parentIds.map((id) => grouped.get(String(id)) ?? []);
|
|
4781
4953
|
}).load(localValue);
|
|
@@ -5110,7 +5282,7 @@ const generateSelectFields = (tables, tableName, relationMap, fromTableName, fro
|
|
|
5110
5282
|
});
|
|
5111
5283
|
const shell = new graphql.GraphQLObjectType({
|
|
5112
5284
|
name: typeName,
|
|
5113
|
-
description: cacheCtx.docs.describeTable?.(tableName),
|
|
5285
|
+
description: cacheCtx.docs.describeTable?.(tableName, table),
|
|
5114
5286
|
fields: () => ({
|
|
5115
5287
|
...tableFields,
|
|
5116
5288
|
...container.fields
|
|
@@ -5144,7 +5316,7 @@ const generateSelectFields = (tables, tableName, relationMap, fromTableName, fro
|
|
|
5144
5316
|
defaultName: resolveTypeName(targetTableName, typeNameMapper),
|
|
5145
5317
|
table: targetTableName
|
|
5146
5318
|
}),
|
|
5147
|
-
description: cacheCtx.docs.describeTable?.(targetTableName),
|
|
5319
|
+
description: cacheCtx.docs.describeTable?.(targetTableName, targetTable),
|
|
5148
5320
|
fields: () => ({
|
|
5149
5321
|
...targetTableFields,
|
|
5150
5322
|
...capturedTargetContainer.fields
|
|
@@ -5295,7 +5467,7 @@ const generateTableTypes = (tableName, tables, relationMap, withReturning, relat
|
|
|
5295
5467
|
defaultName: resolveTypeName(tableName, typeNameMapper),
|
|
5296
5468
|
table: tableName
|
|
5297
5469
|
}),
|
|
5298
|
-
description: cacheCtx.docs.describeTable?.(tableName),
|
|
5470
|
+
description: cacheCtx.docs.describeTable?.(tableName, tables[tableName]),
|
|
5299
5471
|
fields: {
|
|
5300
5472
|
...tableFields,
|
|
5301
5473
|
...relationFields
|
|
@@ -5862,6 +6034,10 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
5862
6034
|
return;
|
|
5863
6035
|
}
|
|
5864
6036
|
const { localColPropName, foreignCol } = joinCols;
|
|
6037
|
+
const junction = extractJunctionColumns(relEntry);
|
|
6038
|
+
if (junction === null) return;
|
|
6039
|
+
const groupCol = junction ? junction.sourceCol : foreignCol;
|
|
6040
|
+
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, (0, drizzle_orm.getColumns)(parentTable)[localColPropName], groupCol);
|
|
5865
6041
|
const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, false);
|
|
5866
6042
|
const targetTypeName = resolveTypeName(targetTableName, typeNameMapper);
|
|
5867
6043
|
const type = generateAggregateTypes(targetTable, targetTableName, targetTypeName, cacheCtx);
|
|
@@ -5892,11 +6068,12 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
5892
6068
|
const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
|
|
5893
6069
|
const executor = resolveExecutor(db, context);
|
|
5894
6070
|
const uniqueIds = [...new Set(parentIds)];
|
|
5895
|
-
const whereCondition = withScope(resolveScope(policies, context, filterCtx), targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(
|
|
5896
|
-
const
|
|
5897
|
-
[GROUP_KEY]:
|
|
6071
|
+
const whereCondition = withScope(resolveScope(policies, context, filterCtx), targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0), deleted ?? defaultDeleted);
|
|
6072
|
+
const selected = executor.select({
|
|
6073
|
+
[GROUP_KEY]: groupCol,
|
|
5898
6074
|
...request.selection
|
|
5899
|
-
}).from(targetTable)
|
|
6075
|
+
}).from(targetTable);
|
|
6076
|
+
const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(groupCol);
|
|
5900
6077
|
const byKey = new Map(rows.map((row) => [row[GROUP_KEY], row]));
|
|
5901
6078
|
return parentIds.map((id) => byKey.get(id) ?? {});
|
|
5902
6079
|
});
|