@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/dist/index.d.cts CHANGED
@@ -807,6 +807,14 @@ interface RelationFilterContext {
807
807
  * generate is an unknown filter key like any other.
808
808
  */
809
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;
810
818
  }
811
819
  /**
812
820
  * The build-scoped half of {@link RelationFilterContext}. Created once per generated schema and
@@ -924,6 +932,8 @@ type WriteHookFor = (tableName: string, operation: WriteOperation) => ResolvedWr
924
932
  type ScopeHook<TContext = any> = (context: TContext, table: any) => SQL | Record<string, any> | undefined | null;
925
933
  /** Build-time lookup: the scope configured for a table, if any. */
926
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';
927
937
  /**
928
938
  * One table's soft-delete convention, resolved against the real column at build time.
929
939
  *
@@ -961,6 +971,22 @@ type SoftDeleteInfo = {
961
971
  };
962
972
  /** Build-time lookup: the soft-delete convention of a table, if it declares one. */
963
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
+ };
964
990
  /** Produces one column's value from the GraphQL context. See `BuildSchemaConfig.contextValues`. */
965
991
  type ContextValueHook<TContext = any> = (context: TContext) => any;
966
992
  /** Build-time lookup: the context-derived columns of a table, keyed by column property name. */
@@ -2036,7 +2062,8 @@ type BuildSchemaConfig = {
2036
2062
  * what an `after`-only page is ordered by. Changing a table's default changes what its
2037
2063
  * existing cursors mean.
2038
2064
  * - Entries name column property names of that table; a direction alone is shorthand for
2039
- * `{ 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.
2040
2067
  * - Aggregates and `groupBy` take their own ordering and are unaffected.
2041
2068
  *
2042
2069
  * Off by default: a table with no entry keeps returning rows in whatever order the database
@@ -2065,6 +2092,10 @@ type BuildSchemaConfig = {
2065
2092
  * only ever narrow the scope, never widen it.
2066
2093
  * - It applies to every path that reads the table: list and single queries, aggregates and
2067
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`.
2068
2099
  * - It applies to every path that writes it: `update`, `updateMany`, `delete`, the
2069
2100
  * conflict-update half of an upsert (PostgreSQL and SQLite), and the rows a nested
2070
2101
  * `connect` / `disconnect` / `set` is allowed to reach. An out-of-scope row is simply not
@@ -2129,7 +2160,8 @@ type BuildSchemaConfig = {
2129
2160
  * - Every generated read hides marked rows: list and single queries, aggregates, `groupBy`,
2130
2161
  * relation fields on both the eager and the batched path, and relation aggregates. Each
2131
2162
  * of those fields takes a `deleted: EXCLUDE | INCLUDE | ONLY` argument (default
2132
- * `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.
2133
2165
  * - Every generated write skips them too: an `update`, a `delete`, or a nested `connect`
2134
2166
  * cannot reach a row that is already marked.
2135
2167
  * - The marker column is removed from the create and update inputs, so the only ways to set
@@ -2222,6 +2254,11 @@ type BuildSchemaConfig = {
2222
2254
  * deliberately all-or-nothing: a column you can filter on but not select is an oracle, since
2223
2255
  * `where: { keyHash: { eq: '…' } }` confirms a value without ever returning it.
2224
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
+ *
2225
2262
  * Excluding a `NOT NULL` column that has no default makes that table's inserts impossible to
2226
2263
  * satisfy — `buildSchema` warns on the console when it sees this, and still builds, since a
2227
2264
  * read-only table is a reasonable thing to want.
@@ -2628,6 +2665,11 @@ type SelectionOptions = {
2628
2665
  derivedTypeNameMapper?: DerivedTypeNameMapper;
2629
2666
  typeNamePrefix?: string;
2630
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;
2631
2673
  };
2632
2674
  /**
2633
2675
  * Translates a GraphQL selection set into the relational query builder's `with` tree — the
@@ -2647,6 +2689,10 @@ type SelectionOptions = {
2647
2689
  * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
2648
2690
  * ```
2649
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
+ *
2650
2696
  * Returns `undefined` when the selection asks for no relations, which is the value the
2651
2697
  * query builder wants for "no `with` clause".
2652
2698
  */
@@ -2668,6 +2714,11 @@ type ResolveSelectionOptions = SelectionOptions & {
2668
2714
  offset?: number;
2669
2715
  /** The generated `after` cursor argument, for keyset pagination. */
2670
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;
2671
2722
  };
2672
2723
  /**
2673
2724
  * Runs the read a generated select resolver would have run, against whichever executor is
@@ -2677,11 +2728,13 @@ type ResolveSelectionOptions = SelectionOptions & {
2677
2728
  * This is what an override usually wants after it has done its own work:
2678
2729
  *
2679
2730
  * ```ts
2680
- * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
2731
+ * const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
2681
2732
  * ```
2682
2733
  *
2683
2734
  * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
2684
- * 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`.
2685
2738
  */
2686
2739
  declare const resolveSelection: (info: GraphQLResolveInfo | ResolveTree, options: ResolveSelectionOptions) => Promise<any>;
2687
2740
  //#endregion
package/dist/index.d.ts CHANGED
@@ -807,6 +807,14 @@ interface RelationFilterContext {
807
807
  * generate is an unknown filter key like any other.
808
808
  */
809
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;
810
818
  }
811
819
  /**
812
820
  * The build-scoped half of {@link RelationFilterContext}. Created once per generated schema and
@@ -924,6 +932,8 @@ type WriteHookFor = (tableName: string, operation: WriteOperation) => ResolvedWr
924
932
  type ScopeHook<TContext = any> = (context: TContext, table: any) => SQL | Record<string, any> | undefined | null;
925
933
  /** Build-time lookup: the scope configured for a table, if any. */
926
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';
927
937
  /**
928
938
  * One table's soft-delete convention, resolved against the real column at build time.
929
939
  *
@@ -961,6 +971,22 @@ type SoftDeleteInfo = {
961
971
  };
962
972
  /** Build-time lookup: the soft-delete convention of a table, if it declares one. */
963
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
+ };
964
990
  /** Produces one column's value from the GraphQL context. See `BuildSchemaConfig.contextValues`. */
965
991
  type ContextValueHook<TContext = any> = (context: TContext) => any;
966
992
  /** Build-time lookup: the context-derived columns of a table, keyed by column property name. */
@@ -2036,7 +2062,8 @@ type BuildSchemaConfig = {
2036
2062
  * what an `after`-only page is ordered by. Changing a table's default changes what its
2037
2063
  * existing cursors mean.
2038
2064
  * - Entries name column property names of that table; a direction alone is shorthand for
2039
- * `{ 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.
2040
2067
  * - Aggregates and `groupBy` take their own ordering and are unaffected.
2041
2068
  *
2042
2069
  * Off by default: a table with no entry keeps returning rows in whatever order the database
@@ -2065,6 +2092,10 @@ type BuildSchemaConfig = {
2065
2092
  * only ever narrow the scope, never widen it.
2066
2093
  * - It applies to every path that reads the table: list and single queries, aggregates and
2067
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`.
2068
2099
  * - It applies to every path that writes it: `update`, `updateMany`, `delete`, the
2069
2100
  * conflict-update half of an upsert (PostgreSQL and SQLite), and the rows a nested
2070
2101
  * `connect` / `disconnect` / `set` is allowed to reach. An out-of-scope row is simply not
@@ -2129,7 +2160,8 @@ type BuildSchemaConfig = {
2129
2160
  * - Every generated read hides marked rows: list and single queries, aggregates, `groupBy`,
2130
2161
  * relation fields on both the eager and the batched path, and relation aggregates. Each
2131
2162
  * of those fields takes a `deleted: EXCLUDE | INCLUDE | ONLY` argument (default
2132
- * `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.
2133
2165
  * - Every generated write skips them too: an `update`, a `delete`, or a nested `connect`
2134
2166
  * cannot reach a row that is already marked.
2135
2167
  * - The marker column is removed from the create and update inputs, so the only ways to set
@@ -2222,6 +2254,11 @@ type BuildSchemaConfig = {
2222
2254
  * deliberately all-or-nothing: a column you can filter on but not select is an oracle, since
2223
2255
  * `where: { keyHash: { eq: '…' } }` confirms a value without ever returning it.
2224
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
+ *
2225
2262
  * Excluding a `NOT NULL` column that has no default makes that table's inserts impossible to
2226
2263
  * satisfy — `buildSchema` warns on the console when it sees this, and still builds, since a
2227
2264
  * read-only table is a reasonable thing to want.
@@ -2628,6 +2665,11 @@ type SelectionOptions = {
2628
2665
  derivedTypeNameMapper?: DerivedTypeNameMapper;
2629
2666
  typeNamePrefix?: string;
2630
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;
2631
2673
  };
2632
2674
  /**
2633
2675
  * Translates a GraphQL selection set into the relational query builder's `with` tree — the
@@ -2647,6 +2689,10 @@ type SelectionOptions = {
2647
2689
  * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
2648
2690
  * ```
2649
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
+ *
2650
2696
  * Returns `undefined` when the selection asks for no relations, which is the value the
2651
2697
  * query builder wants for "no `with` clause".
2652
2698
  */
@@ -2668,6 +2714,11 @@ type ResolveSelectionOptions = SelectionOptions & {
2668
2714
  offset?: number;
2669
2715
  /** The generated `after` cursor argument, for keyset pagination. */
2670
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;
2671
2722
  };
2672
2723
  /**
2673
2724
  * Runs the read a generated select resolver would have run, against whichever executor is
@@ -2677,11 +2728,13 @@ type ResolveSelectionOptions = SelectionOptions & {
2677
2728
  * This is what an override usually wants after it has done its own work:
2678
2729
  *
2679
2730
  * ```ts
2680
- * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
2731
+ * const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
2681
2732
  * ```
2682
2733
  *
2683
2734
  * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
2684
- * 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`.
2685
2738
  */
2686
2739
  declare const resolveSelection: (info: GraphQLResolveInfo | ResolveTree, options: ResolveSelectionOptions) => Promise<any>;
2687
2740
  //#endregion