@vantreeseba/drizzle-graphql 15.1.1 → 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/dist/index.d.cts CHANGED
@@ -385,7 +385,9 @@ type IdentifiedRows = {
385
385
  *
386
386
  * `false` means the arguments do not pin the rows down — an insert whose key the
387
387
  * database generates, a `where` that filters on something other than the primary key,
388
- * a plural update with no filter at all, or a table with no primary key. `rows` is then
388
+ * a plural update with no filter at all, a table with no primary key, or an upsert whose
389
+ * `onConflict` conflicts on another unique key (the row updated is the one holding that
390
+ * key) or can skip a conflicting row (`action: NOTHING`, a `where` guard). `rows` is then
389
391
  * a best-effort subset-free superset: every entry in it is a row the key values name,
390
392
  * but other filters may narrow the real set further, and it may be empty.
391
393
  */
@@ -805,6 +807,14 @@ interface RelationFilterContext {
805
807
  * generate is an unknown filter key like any other.
806
808
  */
807
809
  uniqueKeys?: Record<string, UniqueKeyMap>;
810
+ /**
811
+ * The row policies of the request being compiled. A relation filter (and an ordering through
812
+ * a relation) reads the target table in a subquery, so it applies the target's `scope` and
813
+ * `softDelete` here — with the mode the relation field itself would default to — or a filter
814
+ * could match, and a sort could rank by, a row the relation hides. Absent for a scope's own
815
+ * filter object, which is the server's predicate and is compiled unscoped.
816
+ */
817
+ scope?: ScopeResolver;
808
818
  }
809
819
  /**
810
820
  * The build-scoped half of {@link RelationFilterContext}. Created once per generated schema and
@@ -922,6 +932,8 @@ type WriteHookFor = (tableName: string, operation: WriteOperation) => ResolvedWr
922
932
  type ScopeHook<TContext = any> = (context: TContext, table: any) => SQL | Record<string, any> | undefined | null;
923
933
  /** Build-time lookup: the scope configured for a table, if any. */
924
934
  type ScopeFor = (tableName: string) => ScopeHook | undefined;
935
+ /** What a read over a soft-deleting table does with rows that are marked deleted. */
936
+ type DeletedMode = 'EXCLUDE' | 'INCLUDE' | 'ONLY';
925
937
  /**
926
938
  * One table's soft-delete convention, resolved against the real column at build time.
927
939
  *
@@ -959,6 +971,22 @@ type SoftDeleteInfo = {
959
971
  };
960
972
  /** Build-time lookup: the soft-delete convention of a table, if it declares one. */
961
973
  type SoftDeleteFor = (tableName: string) => SoftDeleteInfo | undefined;
974
+ /**
975
+ * The row policies of one request, bound to its context: the table's scope and its
976
+ * soft-delete convention, which both narrow the same `where`. `has` answers "does this table
977
+ * restrict anything at all", cheaply and without evaluating a hook; `on` compiles the
978
+ * predicate against a given table.
979
+ */
980
+ type ScopeResolver = {
981
+ has: (tableName: string, mode?: DeletedMode) => boolean;
982
+ on: (tableName: string, table: Table, mode?: DeletedMode) => SQL | undefined;
983
+ /**
984
+ * The mode a relation field reading `tableName` defaults to — see
985
+ * {@link relationDeletedDefault}. Carried here because the eager (`with:`) path has the
986
+ * resolver but not the build-time soft-delete lookup.
987
+ */
988
+ relationDefault: (tableName: string, requiredToOne: boolean) => DeletedMode | undefined;
989
+ };
962
990
  /** Produces one column's value from the GraphQL context. See `BuildSchemaConfig.contextValues`. */
963
991
  type ContextValueHook<TContext = any> = (context: TContext) => any;
964
992
  /** Build-time lookup: the context-derived columns of a table, keyed by column property name. */
@@ -1178,6 +1206,9 @@ type UpdateManyArgs<TTable extends Table> = {
1178
1206
  * `target` and `where` exist on PostgreSQL and SQLite only: MySQL's
1179
1207
  * `ON DUPLICATE KEY UPDATE` fires on whichever unique key was violated and takes no
1180
1208
  * predicate, so neither field is generated there.
1209
+ *
1210
+ * `target` may name a partial unique index; its predicate is repeated in the statement's
1211
+ * `ON CONFLICT (…) WHERE …`, which is what lets the database match it.
1181
1212
  */
1182
1213
  type UpsertConflictArgs<TTable extends Table> = {
1183
1214
  action?: 'UPDATE' | 'NOTHING';
@@ -1604,7 +1635,9 @@ type SchemaFeatures = {
1604
1635
  * have — the guarantee is in the input type.
1605
1636
  *
1606
1637
  * Single-column constraints get no field (`eq` on the column already says it), and a
1607
- * constraint whose field name is taken by a column or a relation is skipped. The fields live
1638
+ * constraint whose field name is taken by a column or a relation is skipped. So is a partial
1639
+ * unique index (`uniqueIndex().on(…).where(…)`): it is unique only among the rows its
1640
+ * predicate matches, so its columns do not name one row of the table. The fields live
1608
1641
  * on `<Type>Filters`, so they are accepted wherever a filter is: the `Single` reads and
1609
1642
  * writes they are meant for, but also list queries, relation filters and `onConflict.where`.
1610
1643
  *
@@ -2029,7 +2062,8 @@ type BuildSchemaConfig = {
2029
2062
  * what an `after`-only page is ordered by. Changing a table's default changes what its
2030
2063
  * existing cursors mean.
2031
2064
  * - Entries name column property names of that table; a direction alone is shorthand for
2032
- * `{ direction }`. Unknown table or column names throw at build time.
2065
+ * `{ direction }`. Unknown table or column names throw at build time, and so does a
2066
+ * column hidden by `exclude.columns`: cursors carry the ordering values in readable form.
2033
2067
  * - Aggregates and `groupBy` take their own ordering and are unaffected.
2034
2068
  *
2035
2069
  * Off by default: a table with no entry keeps returning rows in whatever order the database
@@ -2058,6 +2092,10 @@ type BuildSchemaConfig = {
2058
2092
  * only ever narrow the scope, never widen it.
2059
2093
  * - It applies to every path that reads the table: list and single queries, aggregates and
2060
2094
  * `groupBy`, relation fields (batched and eager), relation aggregates, and cursor pages.
2095
+ * It also applies wherever another table's request reads this one in a subquery: a
2096
+ * relation filter (`some` / `none` / `every`, to-one, `.through()` — the junction's scope
2097
+ * included) can only match in-scope rows, and an `orderBy` through a to-one relation sorts
2098
+ * an out-of-scope target as `NULL`.
2061
2099
  * - It applies to every path that writes it: `update`, `updateMany`, `delete`, the
2062
2100
  * conflict-update half of an upsert (PostgreSQL and SQLite), and the rows a nested
2063
2101
  * `connect` / `disconnect` / `set` is allowed to reach. An out-of-scope row is simply not
@@ -2091,7 +2129,8 @@ type BuildSchemaConfig = {
2091
2129
  * - The column is removed from the generated insert and update inputs, so a client cannot
2092
2130
  * send it at all — the schema itself documents that it is server-owned.
2093
2131
  * - Every insert stamps it, including the rows created by a nested `create`.
2094
- * - Updates never write it: an update cannot hand a row to another owner.
2132
+ * - Updates never write it: an update cannot hand a row to another owner. That includes
2133
+ * the conflict branch of an upsert, whose `onConflict.update` does not offer it.
2095
2134
  * - Keyed by the Drizzle schema key, then by the column's property name (not its database
2096
2135
  * name), the same keying {@link BuildSchemaConfig.exclude} uses.
2097
2136
  */
@@ -2121,7 +2160,8 @@ type BuildSchemaConfig = {
2121
2160
  * - Every generated read hides marked rows: list and single queries, aggregates, `groupBy`,
2122
2161
  * relation fields on both the eager and the batched path, and relation aggregates. Each
2123
2162
  * of those fields takes a `deleted: EXCLUDE | INCLUDE | ONLY` argument (default
2124
- * `EXCLUDE`); `ONLY` is a trash view.
2163
+ * `EXCLUDE`); `ONLY` is a trash view. Relation filters and an `orderBy` through a to-one
2164
+ * relation see the rows the relation field would show, with the same default mode.
2125
2165
  * - Every generated write skips them too: an `update`, a `delete`, or a nested `connect`
2126
2166
  * cannot reach a row that is already marked.
2127
2167
  * - The marker column is removed from the create and update inputs, so the only ways to set
@@ -2214,6 +2254,11 @@ type BuildSchemaConfig = {
2214
2254
  * deliberately all-or-nothing: a column you can filter on but not select is an oracle, since
2215
2255
  * `where: { keyHash: { eq: '…' } }` confirms a value without ever returning it.
2216
2256
  *
2257
+ * One value stays readable: a row's `cursor` is base64url JSON of its ordering values, and the
2258
+ * primary key is always among them, so excluding the primary key does not hide it from a
2259
+ * client that decodes a cursor. Wrap cursors in your own opaque token if it must stay secret.
2260
+ * A `defaults` ordering on an excluded column throws at build time for the same reason.
2261
+ *
2217
2262
  * Excluding a `NOT NULL` column that has no default makes that table's inserts impossible to
2218
2263
  * satisfy — `buildSchema` warns on the console when it sees this, and still builds, since a
2219
2264
  * read-only table is a reasonable thing to want.
@@ -2620,6 +2665,11 @@ type SelectionOptions = {
2620
2665
  derivedTypeNameMapper?: DerivedTypeNameMapper;
2621
2666
  typeNamePrefix?: string;
2622
2667
  typeNameSuffix?: string;
2668
+ /**
2669
+ * The resolver's GraphQL context. The build's `scope` hooks are called with it, so a build
2670
+ * that configures `scope` requires it — the helpers throw rather than read unscoped.
2671
+ */
2672
+ context?: any;
2623
2673
  };
2624
2674
  /**
2625
2675
  * Translates a GraphQL selection set into the relational query builder's `with` tree — the
@@ -2639,6 +2689,10 @@ type SelectionOptions = {
2639
2689
  * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
2640
2690
  * ```
2641
2691
  *
2692
+ * Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
2693
+ * `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
2694
+ * through the resolve info; pass `context` when it configures `scope`.
2695
+ *
2642
2696
  * Returns `undefined` when the selection asks for no relations, which is the value the
2643
2697
  * query builder wants for "no `with` clause".
2644
2698
  */
@@ -2660,6 +2714,11 @@ type ResolveSelectionOptions = SelectionOptions & {
2660
2714
  offset?: number;
2661
2715
  /** The generated `after` cursor argument, for keyset pagination. */
2662
2716
  after?: string;
2717
+ /**
2718
+ * The generated `deleted` argument, for a soft-deleting table. Defaults to `EXCLUDE`, as the
2719
+ * generated field does.
2720
+ */
2721
+ deleted?: DeletedMode;
2663
2722
  };
2664
2723
  /**
2665
2724
  * Runs the read a generated select resolver would have run, against whichever executor is
@@ -2669,11 +2728,13 @@ type ResolveSelectionOptions = SelectionOptions & {
2669
2728
  * This is what an override usually wants after it has done its own work:
2670
2729
  *
2671
2730
  * ```ts
2672
- * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
2731
+ * const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
2673
2732
  * ```
2674
2733
  *
2675
2734
  * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
2676
- * because it is the generated schema's own code path.
2735
+ * because it is the generated schema's own code path. So do the build's row policies: the
2736
+ * root table and every nested relation are scoped and soft-delete-filtered, and the limit
2737
+ * policy and default ordering apply. Pass `context` when the build configures `scope`.
2677
2738
  */
2678
2739
  declare const resolveSelection: (info: GraphQLResolveInfo | ResolveTree, options: ResolveSelectionOptions) => Promise<any>;
2679
2740
  //#endregion
package/dist/index.d.ts CHANGED
@@ -385,7 +385,9 @@ type IdentifiedRows = {
385
385
  *
386
386
  * `false` means the arguments do not pin the rows down — an insert whose key the
387
387
  * database generates, a `where` that filters on something other than the primary key,
388
- * a plural update with no filter at all, or a table with no primary key. `rows` is then
388
+ * a plural update with no filter at all, a table with no primary key, or an upsert whose
389
+ * `onConflict` conflicts on another unique key (the row updated is the one holding that
390
+ * key) or can skip a conflicting row (`action: NOTHING`, a `where` guard). `rows` is then
389
391
  * a best-effort subset-free superset: every entry in it is a row the key values name,
390
392
  * but other filters may narrow the real set further, and it may be empty.
391
393
  */
@@ -805,6 +807,14 @@ interface RelationFilterContext {
805
807
  * generate is an unknown filter key like any other.
806
808
  */
807
809
  uniqueKeys?: Record<string, UniqueKeyMap>;
810
+ /**
811
+ * The row policies of the request being compiled. A relation filter (and an ordering through
812
+ * a relation) reads the target table in a subquery, so it applies the target's `scope` and
813
+ * `softDelete` here — with the mode the relation field itself would default to — or a filter
814
+ * could match, and a sort could rank by, a row the relation hides. Absent for a scope's own
815
+ * filter object, which is the server's predicate and is compiled unscoped.
816
+ */
817
+ scope?: ScopeResolver;
808
818
  }
809
819
  /**
810
820
  * The build-scoped half of {@link RelationFilterContext}. Created once per generated schema and
@@ -922,6 +932,8 @@ type WriteHookFor = (tableName: string, operation: WriteOperation) => ResolvedWr
922
932
  type ScopeHook<TContext = any> = (context: TContext, table: any) => SQL | Record<string, any> | undefined | null;
923
933
  /** Build-time lookup: the scope configured for a table, if any. */
924
934
  type ScopeFor = (tableName: string) => ScopeHook | undefined;
935
+ /** What a read over a soft-deleting table does with rows that are marked deleted. */
936
+ type DeletedMode = 'EXCLUDE' | 'INCLUDE' | 'ONLY';
925
937
  /**
926
938
  * One table's soft-delete convention, resolved against the real column at build time.
927
939
  *
@@ -959,6 +971,22 @@ type SoftDeleteInfo = {
959
971
  };
960
972
  /** Build-time lookup: the soft-delete convention of a table, if it declares one. */
961
973
  type SoftDeleteFor = (tableName: string) => SoftDeleteInfo | undefined;
974
+ /**
975
+ * The row policies of one request, bound to its context: the table's scope and its
976
+ * soft-delete convention, which both narrow the same `where`. `has` answers "does this table
977
+ * restrict anything at all", cheaply and without evaluating a hook; `on` compiles the
978
+ * predicate against a given table.
979
+ */
980
+ type ScopeResolver = {
981
+ has: (tableName: string, mode?: DeletedMode) => boolean;
982
+ on: (tableName: string, table: Table, mode?: DeletedMode) => SQL | undefined;
983
+ /**
984
+ * The mode a relation field reading `tableName` defaults to — see
985
+ * {@link relationDeletedDefault}. Carried here because the eager (`with:`) path has the
986
+ * resolver but not the build-time soft-delete lookup.
987
+ */
988
+ relationDefault: (tableName: string, requiredToOne: boolean) => DeletedMode | undefined;
989
+ };
962
990
  /** Produces one column's value from the GraphQL context. See `BuildSchemaConfig.contextValues`. */
963
991
  type ContextValueHook<TContext = any> = (context: TContext) => any;
964
992
  /** Build-time lookup: the context-derived columns of a table, keyed by column property name. */
@@ -1178,6 +1206,9 @@ type UpdateManyArgs<TTable extends Table> = {
1178
1206
  * `target` and `where` exist on PostgreSQL and SQLite only: MySQL's
1179
1207
  * `ON DUPLICATE KEY UPDATE` fires on whichever unique key was violated and takes no
1180
1208
  * predicate, so neither field is generated there.
1209
+ *
1210
+ * `target` may name a partial unique index; its predicate is repeated in the statement's
1211
+ * `ON CONFLICT (…) WHERE …`, which is what lets the database match it.
1181
1212
  */
1182
1213
  type UpsertConflictArgs<TTable extends Table> = {
1183
1214
  action?: 'UPDATE' | 'NOTHING';
@@ -1604,7 +1635,9 @@ type SchemaFeatures = {
1604
1635
  * have — the guarantee is in the input type.
1605
1636
  *
1606
1637
  * Single-column constraints get no field (`eq` on the column already says it), and a
1607
- * constraint whose field name is taken by a column or a relation is skipped. The fields live
1638
+ * constraint whose field name is taken by a column or a relation is skipped. So is a partial
1639
+ * unique index (`uniqueIndex().on(…).where(…)`): it is unique only among the rows its
1640
+ * predicate matches, so its columns do not name one row of the table. The fields live
1608
1641
  * on `<Type>Filters`, so they are accepted wherever a filter is: the `Single` reads and
1609
1642
  * writes they are meant for, but also list queries, relation filters and `onConflict.where`.
1610
1643
  *
@@ -2029,7 +2062,8 @@ type BuildSchemaConfig = {
2029
2062
  * what an `after`-only page is ordered by. Changing a table's default changes what its
2030
2063
  * existing cursors mean.
2031
2064
  * - Entries name column property names of that table; a direction alone is shorthand for
2032
- * `{ direction }`. Unknown table or column names throw at build time.
2065
+ * `{ direction }`. Unknown table or column names throw at build time, and so does a
2066
+ * column hidden by `exclude.columns`: cursors carry the ordering values in readable form.
2033
2067
  * - Aggregates and `groupBy` take their own ordering and are unaffected.
2034
2068
  *
2035
2069
  * Off by default: a table with no entry keeps returning rows in whatever order the database
@@ -2058,6 +2092,10 @@ type BuildSchemaConfig = {
2058
2092
  * only ever narrow the scope, never widen it.
2059
2093
  * - It applies to every path that reads the table: list and single queries, aggregates and
2060
2094
  * `groupBy`, relation fields (batched and eager), relation aggregates, and cursor pages.
2095
+ * It also applies wherever another table's request reads this one in a subquery: a
2096
+ * relation filter (`some` / `none` / `every`, to-one, `.through()` — the junction's scope
2097
+ * included) can only match in-scope rows, and an `orderBy` through a to-one relation sorts
2098
+ * an out-of-scope target as `NULL`.
2061
2099
  * - It applies to every path that writes it: `update`, `updateMany`, `delete`, the
2062
2100
  * conflict-update half of an upsert (PostgreSQL and SQLite), and the rows a nested
2063
2101
  * `connect` / `disconnect` / `set` is allowed to reach. An out-of-scope row is simply not
@@ -2091,7 +2129,8 @@ type BuildSchemaConfig = {
2091
2129
  * - The column is removed from the generated insert and update inputs, so a client cannot
2092
2130
  * send it at all — the schema itself documents that it is server-owned.
2093
2131
  * - Every insert stamps it, including the rows created by a nested `create`.
2094
- * - Updates never write it: an update cannot hand a row to another owner.
2132
+ * - Updates never write it: an update cannot hand a row to another owner. That includes
2133
+ * the conflict branch of an upsert, whose `onConflict.update` does not offer it.
2095
2134
  * - Keyed by the Drizzle schema key, then by the column's property name (not its database
2096
2135
  * name), the same keying {@link BuildSchemaConfig.exclude} uses.
2097
2136
  */
@@ -2121,7 +2160,8 @@ type BuildSchemaConfig = {
2121
2160
  * - Every generated read hides marked rows: list and single queries, aggregates, `groupBy`,
2122
2161
  * relation fields on both the eager and the batched path, and relation aggregates. Each
2123
2162
  * of those fields takes a `deleted: EXCLUDE | INCLUDE | ONLY` argument (default
2124
- * `EXCLUDE`); `ONLY` is a trash view.
2163
+ * `EXCLUDE`); `ONLY` is a trash view. Relation filters and an `orderBy` through a to-one
2164
+ * relation see the rows the relation field would show, with the same default mode.
2125
2165
  * - Every generated write skips them too: an `update`, a `delete`, or a nested `connect`
2126
2166
  * cannot reach a row that is already marked.
2127
2167
  * - The marker column is removed from the create and update inputs, so the only ways to set
@@ -2214,6 +2254,11 @@ type BuildSchemaConfig = {
2214
2254
  * deliberately all-or-nothing: a column you can filter on but not select is an oracle, since
2215
2255
  * `where: { keyHash: { eq: '…' } }` confirms a value without ever returning it.
2216
2256
  *
2257
+ * One value stays readable: a row's `cursor` is base64url JSON of its ordering values, and the
2258
+ * primary key is always among them, so excluding the primary key does not hide it from a
2259
+ * client that decodes a cursor. Wrap cursors in your own opaque token if it must stay secret.
2260
+ * A `defaults` ordering on an excluded column throws at build time for the same reason.
2261
+ *
2217
2262
  * Excluding a `NOT NULL` column that has no default makes that table's inserts impossible to
2218
2263
  * satisfy — `buildSchema` warns on the console when it sees this, and still builds, since a
2219
2264
  * read-only table is a reasonable thing to want.
@@ -2620,6 +2665,11 @@ type SelectionOptions = {
2620
2665
  derivedTypeNameMapper?: DerivedTypeNameMapper;
2621
2666
  typeNamePrefix?: string;
2622
2667
  typeNameSuffix?: string;
2668
+ /**
2669
+ * The resolver's GraphQL context. The build's `scope` hooks are called with it, so a build
2670
+ * that configures `scope` requires it — the helpers throw rather than read unscoped.
2671
+ */
2672
+ context?: any;
2623
2673
  };
2624
2674
  /**
2625
2675
  * Translates a GraphQL selection set into the relational query builder's `with` tree — the
@@ -2639,6 +2689,10 @@ type SelectionOptions = {
2639
2689
  * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
2640
2690
  * ```
2641
2691
  *
2692
+ * Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
2693
+ * `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
2694
+ * through the resolve info; pass `context` when it configures `scope`.
2695
+ *
2642
2696
  * Returns `undefined` when the selection asks for no relations, which is the value the
2643
2697
  * query builder wants for "no `with` clause".
2644
2698
  */
@@ -2660,6 +2714,11 @@ type ResolveSelectionOptions = SelectionOptions & {
2660
2714
  offset?: number;
2661
2715
  /** The generated `after` cursor argument, for keyset pagination. */
2662
2716
  after?: string;
2717
+ /**
2718
+ * The generated `deleted` argument, for a soft-deleting table. Defaults to `EXCLUDE`, as the
2719
+ * generated field does.
2720
+ */
2721
+ deleted?: DeletedMode;
2663
2722
  };
2664
2723
  /**
2665
2724
  * Runs the read a generated select resolver would have run, against whichever executor is
@@ -2669,11 +2728,13 @@ type ResolveSelectionOptions = SelectionOptions & {
2669
2728
  * This is what an override usually wants after it has done its own work:
2670
2729
  *
2671
2730
  * ```ts
2672
- * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
2731
+ * const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
2673
2732
  * ```
2674
2733
  *
2675
2734
  * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
2676
- * because it is the generated schema's own code path.
2735
+ * because it is the generated schema's own code path. So do the build's row policies: the
2736
+ * root table and every nested relation are scoped and soft-delete-filtered, and the limit
2737
+ * policy and default ordering apply. Pass `context` when the build configures `scope`.
2677
2738
  */
2678
2739
  declare const resolveSelection: (info: GraphQLResolveInfo | ResolveTree, options: ResolveSelectionOptions) => Promise<any>;
2679
2740
  //#endregion