@vantreeseba/drizzle-graphql 7.4.0 → 7.6.0

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
@@ -3,6 +3,7 @@ import { MySqlDatabase } from 'drizzle-orm/mysql-core';
3
3
  import { PgAsyncDatabase } from 'drizzle-orm/pg-core';
4
4
  import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
5
5
  import { GraphQLScalarType, GraphQLOutputType, GraphQLInputType, GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema, GraphQLInputObjectType, GraphQLObjectType, GraphQLNonNull, GraphQLList, GraphQLEnumType } from 'graphql';
6
+ import { ResolveTree } from 'graphql-parse-resolve-info';
6
7
  export { GraphQLDate, GraphQLDateTime, GraphQLJSON, GraphQLUUID } from 'graphql-scalars';
7
8
 
8
9
  /**
@@ -197,6 +198,11 @@ type OrderByArgs<TTable extends Table> = {
197
198
  };
198
199
  };
199
200
 
201
+ /** Optional mapper from table key to singular/plural name pair. Return undefined to use default naming for a table. */
202
+ type TypeNameMapper = (tableName: string) => {
203
+ singular: string;
204
+ plural: string;
205
+ } | undefined;
200
206
  /**
201
207
  * Extracts the join column info from a drizzle-orm v1 Relation object.
202
208
  * Returns the JS property name of the local column on the parent table and the
@@ -245,7 +251,7 @@ declare const drizzleExecutorKey: unique symbol;
245
251
  * each dialect builder states it: `'nulls-largest'` for PostgreSQL, `'nulls-smallest'` for
246
252
  * MySQL and SQLite.
247
253
  */
248
- declare const createRelationResolverFactory: (db: any, tables: Record<string, Table>, nullOrdering: NullOrdering, filterCtx?: RelationFilterBase, limits?: LimitPolicyFor) => RelationResolverFactory;
254
+ declare const createRelationResolverFactory: (db: any, tables: Record<string, Table>, nullOrdering: NullOrdering, filterCtx?: RelationFilterBase, limits?: LimitPolicyFor, scopes?: ScopeFor) => RelationResolverFactory;
249
255
  /** {@link BuildSchemaConfig.limits} resolved for one table. `undefined` means no policy. */
250
256
  type ResolvedLimitPolicy = {
251
257
  defaultLimit: number | undefined;
@@ -301,6 +307,20 @@ interface RelationFilterContext {
301
307
  */
302
308
  type RelationFilterBase = Pick<RelationFilterContext, 'tables' | 'relationMap'>;
303
309
  declare const extractFilters: <TTable extends Table>(table: TTable, tableName: string, filters: Filters<TTable>, relationCtx?: RelationFilterContext) => SQL | undefined;
310
+ /**
311
+ * A table's row-level scope: the predicate every generated read, update and delete on that
312
+ * table is narrowed by. See `BuildSchemaConfig.scope`.
313
+ *
314
+ * `table` is the table the predicate must be built against. On the relational-query path
315
+ * drizzle hands its callbacks an *aliased* proxy (`d0`, `d1`, …), so a predicate built from
316
+ * the imported schema table would reference a name that isn't in the query — always build
317
+ * from the argument. Returning a filter object instead (the same shape as the field's
318
+ * `where`) sidesteps aliasing entirely, and is the only form that can reach through a
319
+ * relation — which is what ownership through a join table needs.
320
+ */
321
+ type ScopeHook<TContext = any> = (context: TContext, table: any) => SQL | Record<string, any> | undefined | null;
322
+ /** Build-time lookup: the scope configured for a table, if any. */
323
+ type ScopeFor = (tableName: string) => ScopeHook | undefined;
304
324
  /**
305
325
  * Where a dialect's default sort places NULLs relative to non-NULL values:
306
326
  * - `nulls-largest` — PostgreSQL: NULLs sort as the largest values (last in ASC, first in DESC).
@@ -815,6 +835,34 @@ type LimitsConfig = TableLimitPolicy & {
815
835
  /** Per-table overrides, keyed by the table's key in the Drizzle schema. */
816
836
  tables?: Record<string, TableLimitPolicy>;
817
837
  };
838
+ /**
839
+ * What a {@link RowScope} may return: a Drizzle `SQL` expression, a filter object in the same
840
+ * shape as the generated `where` argument, or `undefined`/`null` for "no restriction".
841
+ *
842
+ * The filter-object form is compiled with the same code that compiles a client `where`, so it
843
+ * supports relation filters — `{ author: { orgId: { eq: 1 } } }` scopes a table through a join
844
+ * rather than a column it owns.
845
+ */
846
+ type RowScopeFilter = Record<string, any> | undefined | null;
847
+ /**
848
+ * A per-table row scope: given the GraphQL context, the predicate every read and write of that
849
+ * table is confined to.
850
+ *
851
+ * `table` is the table instance the current statement runs against. For a relational read that
852
+ * is Drizzle's *aliased* copy of the table, so build the predicate from this argument rather
853
+ * than from the imported table object — `eq(table.orgId, ctx.orgId)` is always correct, while
854
+ * `eq(users.orgId, ctx.orgId)` refers to the wrong alias inside a nested read.
855
+ *
856
+ * See {@link BuildSchemaConfig.scope}.
857
+ */
858
+ type RowScope<TContext = any> = (context: TContext, table: any) => RowScopeFilter | object;
859
+ /** {@link BuildSchemaConfig.scope} — a row scope per table, keyed by the Drizzle schema key. */
860
+ type ScopeConfig<TContext = any> = Record<string, RowScope<TContext>>;
861
+ /**
862
+ * {@link BuildSchemaConfig.contextValues} — columns whose value the server derives from the
863
+ * request context, keyed by Drizzle schema key and then by column property name.
864
+ */
865
+ type ContextValuesConfig<TContext = any> = Record<string, Record<string, (context: TContext) => any>>;
818
866
  /**
819
867
  * {@link BuildSchemaConfig.exclude} — tables and columns to keep out of the generated schema.
820
868
  *
@@ -1043,6 +1091,66 @@ type BuildSchemaConfig = {
1043
1091
  * agree.
1044
1092
  */
1045
1093
  limits?: LimitsConfig;
1094
+ /**
1095
+ * Row-level scoping: a predicate, derived from the GraphQL context, that every read and
1096
+ * write of a table is confined to. This is the multi-tenancy / ownership knob — the one
1097
+ * rule a wrapper cannot enforce from outside, because a nested relation field never passes
1098
+ * through a root resolver.
1099
+ *
1100
+ * ```ts
1101
+ * import { and, eq } from 'drizzle-orm';
1102
+ *
1103
+ * buildSchema(db, {
1104
+ * scope: {
1105
+ * posts: (ctx, table) => eq(table.orgId, ctx.orgId),
1106
+ * // A filter object works too, and can scope through a relation:
1107
+ * comments: (ctx) => ({ post: { orgId: { eq: ctx.orgId } } }),
1108
+ * },
1109
+ * });
1110
+ * ```
1111
+ *
1112
+ * - The predicate is ANDed on **last**, after the client's `where`, so a client filter can
1113
+ * only ever narrow the scope, never widen it.
1114
+ * - It applies to every path that reads the table: list and single queries, aggregates and
1115
+ * `groupBy`, relation fields (batched and eager), relation aggregates, and cursor pages.
1116
+ * - It applies to every path that writes it: `update`, `updateMany`, `delete`, the
1117
+ * conflict-update half of an upsert (PostgreSQL and SQLite), and the rows a nested
1118
+ * `connect` / `disconnect` / `set` is allowed to reach. An out-of-scope row is simply not
1119
+ * matched — a scoped `delete` reports zero rows rather than refusing.
1120
+ * - It cannot apply to a plain `insert`: there is no existing row to filter. Use
1121
+ * {@link BuildSchemaConfig.contextValues} to stamp the owning column on the way in.
1122
+ * - `table` is the table the current statement runs against, which for a relational read is
1123
+ * Drizzle's aliased copy — always build the predicate from that argument.
1124
+ * - Returning `undefined` means "no restriction for this context", so a hook can let an
1125
+ * admin through. A table with no entry is unscoped.
1126
+ * - MySQL's `ON DUPLICATE KEY UPDATE` takes no predicate, so a MySQL upsert cannot be
1127
+ * scoped on the conflict path; scope the table's `update` and rely on `contextValues`
1128
+ * for the insert half.
1129
+ */
1130
+ scope?: ScopeConfig;
1131
+ /**
1132
+ * Columns whose value the server derives from the request context rather than accepting
1133
+ * from the client — the write-side half of {@link BuildSchemaConfig.scope}.
1134
+ *
1135
+ * ```ts
1136
+ * buildSchema(db, {
1137
+ * contextValues: {
1138
+ * posts: {
1139
+ * orgId: (ctx) => ctx.orgId,
1140
+ * authorId: (ctx) => ctx.userId,
1141
+ * },
1142
+ * },
1143
+ * });
1144
+ * ```
1145
+ *
1146
+ * - The column is removed from the generated insert and update inputs, so a client cannot
1147
+ * send it at all — the schema itself documents that it is server-owned.
1148
+ * - Every insert stamps it, including the rows created by a nested `create`.
1149
+ * - Updates never write it: an update cannot hand a row to another owner.
1150
+ * - Keyed by the Drizzle schema key, then by the column's property name (not its database
1151
+ * name), the same keying {@link BuildSchemaConfig.exclude} uses.
1152
+ */
1153
+ contextValues?: ContextValuesConfig;
1046
1154
  /**
1047
1155
  * Tables and columns to leave out of the generated schema entirely.
1048
1156
  *
@@ -1315,6 +1423,110 @@ declare const singularizeMapper: (tableName: string) => {
1315
1423
  plural: string;
1316
1424
  };
1317
1425
 
1426
+ /**
1427
+ * The identity a generated field publishes about itself, under `extensions.drizzle`.
1428
+ *
1429
+ * Every field this library generates carries one, so a wrapper — `graphql-middleware`,
1430
+ * envelop, `graphql-shield`, an audit log, a cache invalidator — can ask the schema what a
1431
+ * field is instead of reverse-engineering its name. Names are configurable (`prefixes`,
1432
+ * `suffixes`, `typeNameMapper`), so name parsing is ambiguous by construction; this is not.
1433
+ *
1434
+ * This shape is stable API: fields are only ever added to it, never removed or repurposed.
1435
+ */
1436
+ /** What family of field this is. */
1437
+ type DrizzleFieldKind = 'query' | 'mutation' | 'relation' | 'aggregate' | 'type';
1438
+ /**
1439
+ * What the field does. Arity is carried separately by `single`, so `select` covers both
1440
+ * the list query and its `…Single` sibling rather than being spelled twice.
1441
+ */
1442
+ type DrizzleFieldOperation = 'select' | 'insert' | 'update' | 'updateMany' | 'upsert' | 'delete' | 'aggregate' | 'groupBy' | 'relation' | 'relationAggregate';
1443
+ /** The argument holding the rows a field is about, when it has one. */
1444
+ type DrizzleTargetArg = 'where' | 'values' | 'updates';
1445
+ /** `extensions.drizzle` on a generated field. */
1446
+ type DrizzleFieldExtension = {
1447
+ /** The Drizzle schema key of the table the field reads or writes. */
1448
+ table: string;
1449
+ kind: Exclude<DrizzleFieldKind, 'type'>;
1450
+ operation: DrizzleFieldOperation;
1451
+ /** Whether the field resolves to one row (or one aggregate) rather than a list. */
1452
+ single: boolean;
1453
+ /**
1454
+ * Which argument names the rows: `where` for select/update/delete/aggregate, `values`
1455
+ * for insert and upsert, `updates` for the batch update's per-entry `{ where, set }`.
1456
+ * Absent when the field takes no such argument.
1457
+ */
1458
+ targetArg?: DrizzleTargetArg;
1459
+ /**
1460
+ * Primary-key property names of `table`, in schema order. Empty when the table declares
1461
+ * no primary key. Carried here so {@link identifyRows} works from the field alone.
1462
+ */
1463
+ primaryKey: readonly string[];
1464
+ /** For `kind: 'relation'` and `operation: 'relationAggregate'`: the relation's name. */
1465
+ relation?: string;
1466
+ /** For `kind: 'relation'` and `operation: 'relationAggregate'`: the table it hangs off. */
1467
+ parentTable?: string;
1468
+ };
1469
+ /** `extensions.drizzle` on a generated object type. */
1470
+ type DrizzleTypeExtension = {
1471
+ /** The Drizzle schema key this type was generated from. */
1472
+ table: string;
1473
+ kind: 'type';
1474
+ /** Primary-key property names of `table`, in schema order. Empty when there is none. */
1475
+ primaryKey: readonly string[];
1476
+ };
1477
+ type DrizzleExtension = DrizzleFieldExtension | DrizzleTypeExtension;
1478
+ /** Anything carrying GraphQL `extensions` — a field config, a field, or a named type. */
1479
+ type WithExtensions = {
1480
+ extensions?: {
1481
+ drizzle?: unknown;
1482
+ } | null;
1483
+ } | null | undefined;
1484
+ /**
1485
+ * Reads the `drizzle` extension off a field or type, typed. Returns `undefined` for
1486
+ * anything this library did not generate, which is the check a wrapper wants before it
1487
+ * treats a field as one of ours.
1488
+ */
1489
+ declare const drizzleExtension: (target: WithExtensions) => DrizzleExtension | undefined;
1490
+ /** Narrows to the field form — object types carry `kind: 'type'` and no operation. */
1491
+ declare const isDrizzleFieldExtension: (value: DrizzleExtension | undefined) => value is DrizzleFieldExtension;
1492
+ /** The rows a field's arguments name. */
1493
+ type IdentifiedRows = {
1494
+ /** The Drizzle schema key of the table being read or written. */
1495
+ table: string;
1496
+ /** Primary-key property names, in schema order. */
1497
+ primaryKey: readonly string[];
1498
+ /** One entry per identified row: the primary-key values, keyed by property name. */
1499
+ rows: Record<string, unknown>[];
1500
+ /**
1501
+ * Whether `rows` is exactly the set the operation affects.
1502
+ *
1503
+ * `false` means the arguments do not pin the rows down — an insert whose key the
1504
+ * database generates, a `where` that filters on something other than the primary key,
1505
+ * a plural update with no filter at all, or a table with no primary key. `rows` is then
1506
+ * a best-effort subset-free superset: every entry in it is a row the key values name,
1507
+ * but other filters may narrow the real set further, and it may be empty.
1508
+ */
1509
+ complete: boolean;
1510
+ };
1511
+ /**
1512
+ * The rows a generated field's arguments are about, as primary-key values.
1513
+ *
1514
+ * The row identity of a mutation lives in a different argument for every operation —
1515
+ * `where` for update and delete, `values` for insert and upsert, a per-entry `where` inside
1516
+ * `updates` for the batch update — and each of those has both a one-row and a many-row
1517
+ * spelling. This reads whichever one applies:
1518
+ *
1519
+ * ```ts
1520
+ * const field = info.parentType.getFields()[info.fieldName];
1521
+ * const target = identifyRows(field, args);
1522
+ * if (target?.complete) await invalidate(target.table, target.rows);
1523
+ * ```
1524
+ *
1525
+ * Returns `undefined` for a field this library did not generate, or an object type. Check
1526
+ * `complete` before treating `rows` as the affected set — see {@link IdentifiedRows}.
1527
+ */
1528
+ declare const identifyRows: (field: WithExtensions, args: Record<string, unknown> | undefined) => IdentifiedRows | undefined;
1529
+
1318
1530
  /**
1319
1531
  * A 64-bit integer. Always transported as a decimal string, in both directions, so no value
1320
1532
  * is ever silently rounded by JSON's double-precision numbers. `graphql-scalars`' own
@@ -1330,6 +1542,75 @@ declare const GraphQLBigIntString: GraphQLScalarType<string, string>;
1330
1542
  */
1331
1543
  declare const GraphQLDecimalString: GraphQLScalarType<string, string>;
1332
1544
 
1545
+ /** Options shared by {@link selectionToWith} and {@link resolveSelection}. */
1546
+ type SelectionOptions = {
1547
+ /** The drizzle instance the schema was built from. */
1548
+ db: AnyDrizzleDB<any>;
1549
+ /** The Drizzle schema key of the table the selection is rooted at. */
1550
+ table: string;
1551
+ /**
1552
+ * The `typeNameMapper` the schema was built with, when one was configured. The translation
1553
+ * matches selections against GraphQL type names, so a build that renames its types has to
1554
+ * pass the same mapper here or nothing will match.
1555
+ */
1556
+ typeNameMapper?: TypeNameMapper;
1557
+ };
1558
+ /**
1559
+ * Translates a GraphQL selection set into the relational query builder's `with` tree — the
1560
+ * same translation the generated resolvers use for their own queries.
1561
+ *
1562
+ * A resolver that replaces a generated one has to return a row shaped like the type it
1563
+ * replaced, and the field resolvers underneath only fill in relations the parent did not
1564
+ * already provide. Reproducing that by hand means resolving fragments against
1565
+ * `info.fragments`, intersecting selection names against the relations config (so scalars
1566
+ * and consumer-added fields drop out instead of becoming invalid `with` keys), following
1567
+ * aliases, compiling each relation's `where` / `orderBy` / `limit` / `offset` arguments into
1568
+ * its nested entry, and leaving `<relation>Aggregate` selections out — they are not `with`
1569
+ * entries at all. This does all of it.
1570
+ *
1571
+ * ```ts
1572
+ * const withTree = selectionToWith(info, { db, table: 'Users' });
1573
+ * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
1574
+ * ```
1575
+ *
1576
+ * Returns `undefined` when the selection asks for no relations, which is the value the
1577
+ * query builder wants for "no `with` clause".
1578
+ */
1579
+ declare const selectionToWith: (info: GraphQLResolveInfo | ResolveTree, options: SelectionOptions) => Record<string, any> | undefined;
1580
+ /** Options for {@link resolveSelection} — the selection options plus the read itself. */
1581
+ type ResolveSelectionOptions = SelectionOptions & {
1582
+ /**
1583
+ * The transaction (or other drizzle executor) to read on. Defaults to `db`, so an override
1584
+ * that has opened its own transaction passes it here to see its own uncommitted writes.
1585
+ */
1586
+ executor?: any;
1587
+ /** Whether to return one row or a list. A single read ignores `limit`. */
1588
+ single?: boolean;
1589
+ /** The generated `where` argument, in the schema's filter DSL — compiled the same way. */
1590
+ where?: any;
1591
+ /** The generated `orderBy` argument. */
1592
+ orderBy?: any;
1593
+ limit?: number;
1594
+ offset?: number;
1595
+ /** The generated `after` cursor argument, for keyset pagination. */
1596
+ after?: string;
1597
+ };
1598
+ /**
1599
+ * Runs the read a generated select resolver would have run, against whichever executor is
1600
+ * given — the whole body, not just the `with` tree: selected columns, relation arguments,
1601
+ * eager loading, cursor pagination, and the transport remapping of the result.
1602
+ *
1603
+ * This is what an override usually wants after it has done its own work:
1604
+ *
1605
+ * ```ts
1606
+ * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
1607
+ * ```
1608
+ *
1609
+ * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
1610
+ * because it is the generated schema's own code path.
1611
+ */
1612
+ declare const resolveSelection: (info: GraphQLResolveInfo | ResolveTree, options: ResolveSelectionOptions) => Promise<any>;
1613
+
1333
1614
  declare const buildSchema: <TDbClient extends AnyDrizzleDB<any>>(db: TDbClient, config?: BuildSchemaConfig) => GeneratedData<TDbClient>;
1334
1615
 
1335
- export { type AggregateResolver, type AnyDrizzleDB, type BuildSchemaConfig, type ColumnDeprecator, type ColumnDescriber, type ColumnDocInfo, type ColumnTypeMapper, type ColumnTypeMapperInfo, type ComplexityConfig, type DeleteResolver, type DeleteSingleResolver, type EnumNameInfo, type EnumNameMapper, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type FeatureSwitch, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, GraphQLBigIntString, GraphQLDecimalString, type InsertArrResolver, type InsertResolver, type LimitsConfig, type MutationReturnlessResult, type MutationsCore, type QueriesCore, type RelationDescriber, type RelationResolverFactory, type ScalarOverride, type ScalarOverridesConfig, type SchemaDocs, type SchemaExclusions, type SchemaFeatures, type SelectResolver, type SelectSingleResolver, type TableDescriber, type TableLimitPolicy, type TableNamedRelations, type UpdateManyArgs, type UpdateManyEntry, type UpdateManyResolver, type UpdateResolver, type UpdateSingleResolver, type UpsertArgs, type UpsertArrResolver, type UpsertConflictArgs, type UpsertResolver, buildSchema, createRelationResolverFactory, defaultErrorMapper, drizzleExecutorKey, extractFilters, extractOrderBy, extractRelationJoinColumns, singularizeMapper };
1616
+ export { type AggregateResolver, type AnyDrizzleDB, type BuildSchemaConfig, type ColumnDeprecator, type ColumnDescriber, type ColumnDocInfo, type ColumnTypeMapper, type ColumnTypeMapperInfo, type ComplexityConfig, type ContextValuesConfig, type DeleteResolver, type DeleteSingleResolver, type DrizzleExtension, type DrizzleFieldExtension, type DrizzleFieldKind, type DrizzleFieldOperation, type DrizzleTargetArg, type DrizzleTypeExtension, type EnumNameInfo, type EnumNameMapper, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type FeatureSwitch, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, GraphQLBigIntString, GraphQLDecimalString, type IdentifiedRows, type InsertArrResolver, type InsertResolver, type LimitsConfig, type MutationReturnlessResult, type MutationsCore, type QueriesCore, type RelationDescriber, type RelationResolverFactory, type ResolveSelectionOptions, type RowScope, type RowScopeFilter, type ScalarOverride, type ScalarOverridesConfig, type SchemaDocs, type SchemaExclusions, type SchemaFeatures, type ScopeConfig, type SelectResolver, type SelectSingleResolver, type SelectionOptions, type TableDescriber, type TableLimitPolicy, type TableNamedRelations, type UpdateManyArgs, type UpdateManyEntry, type UpdateManyResolver, type UpdateResolver, type UpdateSingleResolver, type UpsertArgs, type UpsertArrResolver, type UpsertConflictArgs, type UpsertResolver, buildSchema, createRelationResolverFactory, defaultErrorMapper, drizzleExecutorKey, drizzleExtension, extractFilters, extractOrderBy, extractRelationJoinColumns, identifyRows, isDrizzleFieldExtension, resolveSelection, selectionToWith, singularizeMapper };