@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.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
|