@vantreeseba/drizzle-graphql 15.1.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 +17 -2
- package/dist/index.cjs +92 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +92 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -449,7 +449,7 @@ type DrizzleErrorCode =
|
|
|
449
449
|
'DRIZZLE_INVALID_DISTINCT' |
|
|
450
450
|
/** An `onConflict` named a target or update column the upsert cannot use. */
|
|
451
451
|
'DRIZZLE_INVALID_ON_CONFLICT' |
|
|
452
|
-
/** A written value could not be converted to what its column stores. */
|
|
452
|
+
/** A written value could not be converted to what its column stores, or a `limit` / `offset` was negative. */
|
|
453
453
|
'DRIZZLE_INVALID_INPUT_VALUE' |
|
|
454
454
|
/** An update's `set` gave a column that cannot hold null an explicit null. */
|
|
455
455
|
'DRIZZLE_NOT_NULL' |
|
package/dist/index.d.ts
CHANGED
|
@@ -449,7 +449,7 @@ type DrizzleErrorCode =
|
|
|
449
449
|
'DRIZZLE_INVALID_DISTINCT' |
|
|
450
450
|
/** An `onConflict` named a target or update column the upsert cannot use. */
|
|
451
451
|
'DRIZZLE_INVALID_ON_CONFLICT' |
|
|
452
|
-
/** A written value could not be converted to what its column stores. */
|
|
452
|
+
/** A written value could not be converted to what its column stores, or a `limit` / `offset` was negative. */
|
|
453
453
|
'DRIZZLE_INVALID_INPUT_VALUE' |
|
|
454
454
|
/** An update's `set` gave a column that cannot hold null an explicit null. */
|
|
455
455
|
'DRIZZLE_NOT_NULL' |
|
package/dist/index.js
CHANGED
|
@@ -1592,13 +1592,20 @@ const nullableOps = {
|
|
|
1592
1592
|
isNull,
|
|
1593
1593
|
isNotNull
|
|
1594
1594
|
};
|
|
1595
|
+
/** Operators whose operand is an on/off flag rather than a value: `false` adds no condition. */
|
|
1596
|
+
const FLAG_OPERATORS = /* @__PURE__ */ new Set([
|
|
1597
|
+
"isNull",
|
|
1598
|
+
"isNotNull",
|
|
1599
|
+
"isEmpty"
|
|
1600
|
+
]);
|
|
1595
1601
|
const extractFiltersColumn = (column, columnName, operators) => {
|
|
1596
1602
|
const { OR, AND, NOT, insensitive, ...restOperators } = operators;
|
|
1597
1603
|
const foldCase = insensitive === true;
|
|
1598
1604
|
const entries = Object.entries(restOperators);
|
|
1599
1605
|
const variants = [];
|
|
1600
1606
|
for (const [operatorName, operatorValue] of entries) {
|
|
1601
|
-
if (operatorValue === null || operatorValue ===
|
|
1607
|
+
if (operatorValue === null || operatorValue === void 0) continue;
|
|
1608
|
+
if (operatorValue === false && FLAG_OPERATORS.has(operatorName)) continue;
|
|
1602
1609
|
if (operatorName in singleValueOps) {
|
|
1603
1610
|
const singleValue = remapFromGraphQLCore(operatorValue, column, columnName);
|
|
1604
1611
|
variants.push(foldCase ? lowerComparison(column, operatorName, singleValue) : singleValueOps[operatorName](column, singleValue));
|
|
@@ -2732,6 +2739,45 @@ const cursorOrderExprs = (table, entries) => {
|
|
|
2732
2739
|
return entries.flatMap(([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls ?? void 0, cols[column]));
|
|
2733
2740
|
};
|
|
2734
2741
|
/**
|
|
2742
|
+
* Property a row carries an ordering column's full-precision text under, when the column holds
|
|
2743
|
+
* more than a JS Date can. Namespaced like {@link ROW_CURSOR_PROP}, and removed from the row
|
|
2744
|
+
* once its cursor is encoded.
|
|
2745
|
+
*/
|
|
2746
|
+
const preciseTextProp = (column) => `__drizzle_graphql_cursor_text_${column}`;
|
|
2747
|
+
/**
|
|
2748
|
+
* Whether a column reads into a JS Date (milliseconds) while the database keeps finer
|
|
2749
|
+
* fractional seconds: a PostgreSQL `timestamp` / `timestamptz` in `mode: 'date'` (precision 6
|
|
2750
|
+
* unless it says otherwise) or a MySQL `datetime` / `timestamp` in `mode: 'date'` with
|
|
2751
|
+
* `fsp` above 3. String-mode columns already arrive as the database's own text, and `time` is
|
|
2752
|
+
* always a string, so neither loses anything.
|
|
2753
|
+
*/
|
|
2754
|
+
const outgrowsJsDate = (column) => {
|
|
2755
|
+
if (is(column, PgTimestamp)) return column.precision === void 0 || column.precision > 3;
|
|
2756
|
+
if (is(column, MySqlDateTime) || is(column, MySqlTimestamp)) return (column.fsp ?? 0) > 3;
|
|
2757
|
+
return false;
|
|
2758
|
+
};
|
|
2759
|
+
/**
|
|
2760
|
+
* Extra SQL expressions a cursor-computing query selects alongside the row: for each ordering
|
|
2761
|
+
* column whose values a JS Date would truncate, the value as the database's own text. The
|
|
2762
|
+
* cursor then encodes that text rather than the Date, and `after` compares the column against
|
|
2763
|
+
* it — so two rows a microsecond apart stay distinct positions. `table` may be the aliased RQB
|
|
2764
|
+
* proxy. Empty when no ordering column needs it.
|
|
2765
|
+
*/
|
|
2766
|
+
const cursorPreciseExtras = (table, entries) => {
|
|
2767
|
+
const cols = getColumns(table);
|
|
2768
|
+
const extras = {};
|
|
2769
|
+
for (const [name] of entries) {
|
|
2770
|
+
const column = cols[name];
|
|
2771
|
+
if (column && outgrowsJsDate(column)) extras[preciseTextProp(name)] = is(column, PgTimestamp) ? sql`${column}::text` : sql`cast(${column} as char)`;
|
|
2772
|
+
}
|
|
2773
|
+
return extras;
|
|
2774
|
+
};
|
|
2775
|
+
/**
|
|
2776
|
+
* {@link cursorPreciseExtras} aliased under their property names, for a `select({...})` that
|
|
2777
|
+
* may be wrapped in a subquery (where a bare SQL expression would have no name).
|
|
2778
|
+
*/
|
|
2779
|
+
const cursorPreciseSelection = (table, entries) => Object.fromEntries(Object.entries(cursorPreciseExtras(table, entries)).map(([prop, expression]) => [prop, expression.as(prop)]));
|
|
2780
|
+
/**
|
|
2735
2781
|
* Serializes one ordering-tuple value into a JSON-safe shape. Dates and bigints don't survive
|
|
2736
2782
|
* JSON.stringify losslessly (bigint throws, Date turns into an untagged string), so they are
|
|
2737
2783
|
* tagged; decimals already arrive from the driver as strings, which are lossless as-is.
|
|
@@ -2766,7 +2812,11 @@ const cursorEncoder = (entries) => {
|
|
|
2766
2812
|
const spec = JSON.stringify(entries);
|
|
2767
2813
|
const columns = entries.map(([column]) => column);
|
|
2768
2814
|
return (row) => {
|
|
2769
|
-
const values = JSON.stringify(columns.map((column) =>
|
|
2815
|
+
const values = JSON.stringify(columns.map((column) => {
|
|
2816
|
+
const value = row[column] ?? null;
|
|
2817
|
+
const precise = row[preciseTextProp(column)];
|
|
2818
|
+
return value !== null && typeof precise === "string" ? precise : encodeCursorValue(value);
|
|
2819
|
+
}));
|
|
2770
2820
|
return Buffer.from(`{"o":${spec},"v":${values}}`, "utf8").toString("base64url");
|
|
2771
2821
|
};
|
|
2772
2822
|
};
|
|
@@ -2859,7 +2909,11 @@ const selectsCursorField = (info, table) => {
|
|
|
2859
2909
|
*/
|
|
2860
2910
|
const attachRowCursors = (rows, entries) => {
|
|
2861
2911
|
const encode = cursorEncoder(entries);
|
|
2862
|
-
|
|
2912
|
+
const preciseProps = entries.map(([column]) => preciseTextProp(column));
|
|
2913
|
+
for (const row of rows) {
|
|
2914
|
+
row[ROW_CURSOR_PROP] = encode(row);
|
|
2915
|
+
for (const prop of preciseProps) delete row[prop];
|
|
2916
|
+
}
|
|
2863
2917
|
};
|
|
2864
2918
|
/** Resolver for the `cursor` meta field: reads the value a list resolver attached, if any. */
|
|
2865
2919
|
const rowCursorResolver = (source) => source?.[ROW_CURSOR_PROP] ?? null;
|
|
@@ -3217,6 +3271,21 @@ const withDefaultOrderBy = (args, tableName, defaults) => {
|
|
|
3217
3271
|
} : args;
|
|
3218
3272
|
};
|
|
3219
3273
|
/**
|
|
3274
|
+
* Rejects a negative `limit` or `offset`. Neither is a page size or a position, and the
|
|
3275
|
+
* dialects disagree on what one means: SQLite reads `LIMIT -1` as "no limit" — which would walk
|
|
3276
|
+
* straight past `maxLimit` — while PostgreSQL and MySQL fail with a raw database error. Applied
|
|
3277
|
+
* whether or not the table has a limit policy.
|
|
3278
|
+
*/
|
|
3279
|
+
const assertPagingNotNegative = (paging, errorCtx) => {
|
|
3280
|
+
for (const name of ["limit", "offset"]) {
|
|
3281
|
+
const value = paging[name];
|
|
3282
|
+
if (value != null && value < 0) throw drizzleError(`'${name}' must not be negative, got ${value}.`, {
|
|
3283
|
+
code: "DRIZZLE_INVALID_INPUT_VALUE",
|
|
3284
|
+
...errorCtx
|
|
3285
|
+
});
|
|
3286
|
+
}
|
|
3287
|
+
};
|
|
3288
|
+
/**
|
|
3220
3289
|
* The `limit` a query actually runs with, given the policy for the table it reads.
|
|
3221
3290
|
*
|
|
3222
3291
|
* A request that passes no `limit` falls back to `defaultLimit`, or to `maxLimit` when only
|
|
@@ -3228,6 +3297,7 @@ const withDefaultOrderBy = (args, tableName, defaults) => {
|
|
|
3228
3297
|
* fail a client's query over.
|
|
3229
3298
|
*/
|
|
3230
3299
|
const applyLimitPolicy = (limit, policy, errorCtx) => {
|
|
3300
|
+
assertPagingNotNegative({ limit }, errorCtx);
|
|
3231
3301
|
if (!policy) return limit ?? void 0;
|
|
3232
3302
|
const { defaultLimit, maxLimit, clampToMax } = policy;
|
|
3233
3303
|
if (limit == null) {
|
|
@@ -3745,6 +3815,11 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3745
3815
|
thisRecord.columns = columns;
|
|
3746
3816
|
const relationArgs = is(relEntry.relation, One) ? field.args : withDefaultOrderBy(field.args ?? {}, targetTableName, defaultOrderBy);
|
|
3747
3817
|
const offset = relationArgs?.offset ?? void 0;
|
|
3818
|
+
assertPagingNotNegative({ offset }, {
|
|
3819
|
+
table: targetTableName,
|
|
3820
|
+
operation: "relation",
|
|
3821
|
+
relation: relName
|
|
3822
|
+
});
|
|
3748
3823
|
const limit = is(relEntry.relation, One) ? relationArgs?.limit ?? void 0 : applyLimitPolicy(relationArgs?.limit, limits?.(targetTableName), {
|
|
3749
3824
|
table: targetTableName,
|
|
3750
3825
|
operation: "relation",
|
|
@@ -3788,6 +3863,10 @@ const pruneNonEagerRelations = (relationMap, shouldEagerLoad) => {
|
|
|
3788
3863
|
const runRelationalSelect = async (opts) => {
|
|
3789
3864
|
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
3790
3865
|
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
3866
|
+
assertPagingNotNegative({
|
|
3867
|
+
limit: opts.limit,
|
|
3868
|
+
offset
|
|
3869
|
+
});
|
|
3791
3870
|
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
3792
3871
|
let cursorEntries;
|
|
3793
3872
|
if (!single && (after != null || cursorSelected)) {
|
|
@@ -3839,7 +3918,11 @@ const runRelationalSelect = async (opts) => {
|
|
|
3839
3918
|
const result = await queryBase.findFirst(omitUndefinedKeys(params));
|
|
3840
3919
|
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
3841
3920
|
}
|
|
3842
|
-
if (cursorEntries && cursorSelected)
|
|
3921
|
+
if (cursorEntries && cursorSelected) {
|
|
3922
|
+
for (const [column] of cursorEntries) params.columns[column] = true;
|
|
3923
|
+
const preciseProps = Object.keys(cursorPreciseExtras(table, cursorEntries));
|
|
3924
|
+
if (preciseProps.length) params.extras = Object.fromEntries(preciseProps.map((prop) => [prop, (aliasedTable) => cursorPreciseExtras(aliasedTable, cursorEntries)[prop]]));
|
|
3925
|
+
}
|
|
3843
3926
|
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
3844
3927
|
const result = await queryBase.findMany(omitUndefinedKeys(params));
|
|
3845
3928
|
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
@@ -4743,7 +4826,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4743
4826
|
const column = targetColumns[columnName];
|
|
4744
4827
|
if (column) columns[columnName] = column;
|
|
4745
4828
|
}
|
|
4746
|
-
return
|
|
4829
|
+
return cursorEntries ? {
|
|
4830
|
+
...columns,
|
|
4831
|
+
...cursorPreciseSelection(targetTable, cursorEntries)
|
|
4832
|
+
} : columns;
|
|
4747
4833
|
};
|
|
4748
4834
|
const resolve = async (parent, args, context, info) => {
|
|
4749
4835
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
@@ -4753,6 +4839,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4753
4839
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4754
4840
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
4755
4841
|
const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
|
|
4842
|
+
assertPagingNotNegative({ offset }, errorCtx);
|
|
4756
4843
|
const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
|
|
4757
4844
|
const cursorSelected = !isOne && !!info && selectsCursorField(info, targetTable);
|
|
4758
4845
|
let cursorEntries;
|