@pothos/plugin-drizzle 0.18.1 → 0.19.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/CHANGELOG.md +345 -0
- package/README.md +784 -221
- package/dts/connection-helpers.d.ts +18 -8
- package/dts/connection-helpers.d.ts.map +1 -1
- package/dts/drizzle-field-builder.d.ts +4 -11
- package/dts/drizzle-field-builder.d.ts.map +1 -1
- package/dts/global-types.d.ts.map +1 -1
- package/dts/index.d.ts.map +1 -1
- package/dts/model-loader.d.ts +18 -17
- package/dts/model-loader.d.ts.map +1 -1
- package/dts/types.d.ts +80 -20
- package/dts/types.d.ts.map +1 -1
- package/dts/utils/adapter.d.ts +39 -0
- package/dts/utils/adapter.d.ts.map +1 -0
- package/dts/utils/await-selections.d.ts +12 -0
- package/dts/utils/await-selections.d.ts.map +1 -0
- package/dts/utils/config.d.ts +23 -3
- package/dts/utils/config.d.ts.map +1 -1
- package/dts/utils/cursors.d.ts +6 -6
- package/dts/utils/cursors.d.ts.map +1 -1
- package/dts/utils/map-query.d.ts +22 -21
- package/dts/utils/map-query.d.ts.map +1 -1
- package/dts/utils/path-info.d.ts +13 -0
- package/dts/utils/path-info.d.ts.map +1 -0
- package/dts/utils/relation-filter.d.ts +48 -0
- package/dts/utils/relation-filter.d.ts.map +1 -0
- package/dts/utils/selections.d.ts +2 -21
- package/dts/utils/selections.d.ts.map +1 -1
- package/esm/connection-helpers.d.ts +18 -8
- package/esm/connection-helpers.d.ts.map +1 -1
- package/esm/connection-helpers.js +52 -19
- package/esm/connection-helpers.js.map +1 -1
- package/esm/drizzle-field-builder.d.ts +4 -11
- package/esm/drizzle-field-builder.d.ts.map +1 -1
- package/esm/drizzle-field-builder.js +138 -96
- package/esm/drizzle-field-builder.js.map +1 -1
- package/esm/field-builder.js +61 -47
- package/esm/field-builder.js.map +1 -1
- package/esm/global-types.d.ts.map +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +37 -61
- package/esm/index.js.map +1 -1
- package/esm/model-loader.d.ts +18 -17
- package/esm/model-loader.d.ts.map +1 -1
- package/esm/model-loader.js +76 -45
- package/esm/model-loader.js.map +1 -1
- package/esm/schema-builder.js +40 -149
- package/esm/schema-builder.js.map +1 -1
- package/esm/types.d.ts +80 -20
- package/esm/types.d.ts.map +1 -1
- package/esm/types.js.map +1 -1
- package/esm/utils/adapter.d.ts +39 -0
- package/esm/utils/adapter.d.ts.map +1 -0
- package/esm/utils/adapter.js +137 -0
- package/esm/utils/adapter.js.map +1 -0
- package/esm/utils/await-selections.d.ts +12 -0
- package/esm/utils/await-selections.d.ts.map +1 -0
- package/esm/utils/await-selections.js +19 -0
- package/esm/utils/await-selections.js.map +1 -0
- package/esm/utils/config.d.ts +23 -3
- package/esm/utils/config.d.ts.map +1 -1
- package/esm/utils/config.js +1 -4
- package/esm/utils/config.js.map +1 -1
- package/esm/utils/cursors.d.ts +6 -6
- package/esm/utils/cursors.d.ts.map +1 -1
- package/esm/utils/cursors.js +55 -90
- package/esm/utils/cursors.js.map +1 -1
- package/esm/utils/map-query.d.ts +22 -21
- package/esm/utils/map-query.d.ts.map +1 -1
- package/esm/utils/map-query.js +40 -354
- package/esm/utils/map-query.js.map +1 -1
- package/esm/utils/path-info.d.ts +13 -0
- package/esm/utils/path-info.d.ts.map +1 -0
- package/esm/utils/path-info.js +37 -0
- package/esm/utils/path-info.js.map +1 -0
- package/esm/utils/relation-filter.d.ts +48 -0
- package/esm/utils/relation-filter.d.ts.map +1 -0
- package/esm/utils/relation-filter.js +92 -0
- package/esm/utils/relation-filter.js.map +1 -0
- package/esm/utils/selections.d.ts +2 -21
- package/esm/utils/selections.d.ts.map +1 -1
- package/esm/utils/selections.js +1 -133
- package/esm/utils/selections.js.map +1 -1
- package/lib/connection-helpers.js +51 -18
- package/lib/connection-helpers.js.map +1 -1
- package/lib/drizzle-field-builder.js +136 -94
- package/lib/drizzle-field-builder.js.map +1 -1
- package/lib/field-builder.js +58 -44
- package/lib/field-builder.js.map +1 -1
- package/lib/index.js +36 -60
- package/lib/index.js.map +1 -1
- package/lib/model-loader.js +76 -45
- package/lib/model-loader.js.map +1 -1
- package/lib/schema-builder.js +40 -149
- package/lib/schema-builder.js.map +1 -1
- package/lib/types.js.map +1 -1
- package/lib/utils/adapter.js +147 -0
- package/lib/utils/adapter.js.map +1 -0
- package/lib/utils/await-selections.js +20 -0
- package/lib/utils/await-selections.js.map +1 -0
- package/lib/utils/config.js +1 -4
- package/lib/utils/config.js.map +1 -1
- package/lib/utils/cursors.js +47 -88
- package/lib/utils/cursors.js.map +1 -1
- package/lib/utils/map-query.js +28 -359
- package/lib/utils/map-query.js.map +1 -1
- package/lib/utils/path-info.js +39 -0
- package/lib/utils/path-info.js.map +1 -0
- package/lib/utils/relation-filter.js +91 -0
- package/lib/utils/relation-filter.js.map +1 -0
- package/lib/utils/selections.js +3 -158
- package/lib/utils/selections.js.map +1 -1
- package/package.json +8 -6
- package/src/connection-helpers.ts +167 -53
- package/src/drizzle-field-builder.ts +296 -176
- package/src/field-builder.ts +110 -77
- package/src/global-types.ts +0 -32
- package/src/index.ts +42 -68
- package/src/model-loader.ts +110 -71
- package/src/schema-builder.ts +76 -169
- package/src/types.ts +175 -44
- package/src/utils/adapter.ts +197 -0
- package/src/utils/await-selections.ts +31 -0
- package/src/utils/config.ts +1 -5
- package/src/utils/cursors.ts +53 -81
- package/src/utils/map-query.ts +51 -734
- package/src/utils/path-info.ts +40 -0
- package/src/utils/relation-filter.ts +185 -0
- package/src/utils/selections.ts +2 -207
- package/dts/utils/deep-equal.d.ts +0 -2
- package/dts/utils/deep-equal.d.ts.map +0 -1
- package/dts/utils/drizzle-graphql.d.ts +0 -2
- package/dts/utils/drizzle-graphql.d.ts.map +0 -1
- package/dts/utils/loader-map.d.ts +0 -11
- package/dts/utils/loader-map.d.ts.map +0 -1
- package/dts/utils/usage.d.ts +0 -5
- package/dts/utils/usage.d.ts.map +0 -1
- package/esm/utils/deep-equal.d.ts +0 -2
- package/esm/utils/deep-equal.d.ts.map +0 -1
- package/esm/utils/deep-equal.js +0 -45
- package/esm/utils/deep-equal.js.map +0 -1
- package/esm/utils/drizzle-graphql.d.ts +0 -2
- package/esm/utils/drizzle-graphql.d.ts.map +0 -1
- package/esm/utils/drizzle-graphql.js +0 -232
- package/esm/utils/drizzle-graphql.js.map +0 -1
- package/esm/utils/loader-map.d.ts +0 -11
- package/esm/utils/loader-map.d.ts.map +0 -1
- package/esm/utils/loader-map.js +0 -35
- package/esm/utils/loader-map.js.map +0 -1
- package/esm/utils/usage.d.ts +0 -5
- package/esm/utils/usage.d.ts.map +0 -1
- package/esm/utils/usage.js +0 -59
- package/esm/utils/usage.js.map +0 -1
- package/lib/utils/deep-equal.js +0 -55
- package/lib/utils/deep-equal.js.map +0 -1
- package/lib/utils/drizzle-graphql.js +0 -236
- package/lib/utils/drizzle-graphql.js.map +0 -1
- package/lib/utils/loader-map.js +0 -56
- package/lib/utils/loader-map.js.map +0 -1
- package/lib/utils/usage.js +0 -83
- package/lib/utils/usage.js.map +0 -1
- package/src/utils/deep-equal.ts +0 -57
- package/src/utils/drizzle-graphql.ts +0 -274
- package/src/utils/loader-map.ts +0 -49
- package/src/utils/usage.ts +0 -62
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ArgumentRef,
|
|
3
|
+
type CheckAsyncSelection,
|
|
3
4
|
type DistributeOmit,
|
|
4
5
|
type FieldKind,
|
|
5
6
|
type FieldMap,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
type InterfaceRef,
|
|
15
16
|
type InterfaceTypeOptions,
|
|
16
17
|
type ListResolveValue,
|
|
18
|
+
type MaybeAsyncSelection,
|
|
17
19
|
type MaybePromise,
|
|
18
20
|
type Merge,
|
|
19
21
|
type Normalize,
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
type TypeParam,
|
|
27
29
|
typeBrandKey,
|
|
28
30
|
} from '@pothos/core';
|
|
31
|
+
import type { IndirectInclude, IndirectPathSegment, PathSegment } from '@pothos/selection-mapper';
|
|
29
32
|
import type {
|
|
30
33
|
AnyRelations,
|
|
31
34
|
BuildQueryResult,
|
|
@@ -41,9 +44,14 @@ import type {
|
|
|
41
44
|
import type { FieldNode, GraphQLResolveInfo } from 'graphql';
|
|
42
45
|
import type { DrizzleObjectFieldBuilder } from './drizzle-field-builder.js';
|
|
43
46
|
import type { DrizzleRef } from './interface-ref.js';
|
|
44
|
-
import type { IndirectInclude } from './utils/map-query.js';
|
|
45
47
|
import type { SelectionMap } from './utils/selections.js';
|
|
46
48
|
|
|
49
|
+
/**
|
|
50
|
+
* A segment of a `path` given to `queryFromInfo` or `nestedSelection`: a field name, or
|
|
51
|
+
* `{ name, type }` to pin the type the field must be selected under (a fragment on it).
|
|
52
|
+
*/
|
|
53
|
+
export type { IndirectInclude, IndirectPathSegment, PathSegment };
|
|
54
|
+
|
|
47
55
|
export interface FieldPathInfo {
|
|
48
56
|
field: string;
|
|
49
57
|
alias: string;
|
|
@@ -60,8 +68,24 @@ export type DrizzleClient<TCountSource = Table | SQL | SQLWrapper> = {
|
|
|
60
68
|
readonly _: {
|
|
61
69
|
readonly relations: AnyRelations;
|
|
62
70
|
};
|
|
63
|
-
query:
|
|
71
|
+
query: Record<
|
|
72
|
+
string,
|
|
73
|
+
{
|
|
74
|
+
// Each table accepts its own schema-specific config. The loader supplies that config.
|
|
75
|
+
findMany: (config: never) => PromiseLike<Record<string, unknown>[]>;
|
|
76
|
+
}
|
|
77
|
+
>;
|
|
64
78
|
$count: (source: TCountSource, filter?: SQL) => SQL<number>;
|
|
79
|
+
/** Core SQL construction used for relation predicates and counts; it does not execute a query. */
|
|
80
|
+
select: (fields: Record<string, SQL>) => {
|
|
81
|
+
from(table: Extract<TCountSource, Table>): {
|
|
82
|
+
innerJoin(
|
|
83
|
+
table: Extract<TCountSource, Table>,
|
|
84
|
+
on: SQL,
|
|
85
|
+
): { where: (filter?: SQL) => SQLWrapper };
|
|
86
|
+
where: (filter?: SQL) => SQLWrapper;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
65
89
|
};
|
|
66
90
|
|
|
67
91
|
type GetTableConfigFn<TTable = Table> = (table: TTable) => {
|
|
@@ -85,6 +109,12 @@ type DrizzlePluginBaseOptions = {
|
|
|
85
109
|
maxConnectionSize?: number;
|
|
86
110
|
defaultConnectionSize?: number;
|
|
87
111
|
skipDeferredFragments?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* When `true` (the default), the `totalCount` of a `relatedConnection` applies the `where`
|
|
114
|
+
* returned by the field's `query`, so it counts the same rows the connection paginates. Set to
|
|
115
|
+
* `false` to count every related row regardless of the filter.
|
|
116
|
+
*/
|
|
117
|
+
filterConnectionTotalCount?: boolean;
|
|
88
118
|
};
|
|
89
119
|
|
|
90
120
|
export type DrizzlePluginOptions<Types extends SchemaTypes> = DrizzlePluginBaseOptions &
|
|
@@ -305,7 +335,7 @@ export type DrizzleObjectFieldOptions<
|
|
|
305
335
|
Record<string, unknown> &
|
|
306
336
|
// biome-ignore lint/suspicious/noExplicitAny: this is fine
|
|
307
337
|
Select extends (...args: any[]) => infer R
|
|
308
|
-
? R & { columns: {} }
|
|
338
|
+
? Awaited<R> & { columns: {} }
|
|
309
339
|
: Select & { columns: {} }
|
|
310
340
|
> &
|
|
311
341
|
ParentShape,
|
|
@@ -329,21 +359,98 @@ export type DrizzleObjectFieldOptions<
|
|
|
329
359
|
Args,
|
|
330
360
|
ResolveReturnShape
|
|
331
361
|
> & {
|
|
362
|
+
/**
|
|
363
|
+
* What the field needs from its parent row. With `AsyncSelections: true` the function may be
|
|
364
|
+
* async, and `nestedSelection` is then a promise when a selection beneath it is async, and
|
|
365
|
+
* must be awaited.
|
|
366
|
+
*/
|
|
332
367
|
select?: Select &
|
|
368
|
+
CheckAsyncSelection<Types, Select> &
|
|
333
369
|
(
|
|
334
370
|
| DBQueryConfig<'one', Types['DrizzleRelations'], ExtractTable<Types, ParentShape>>
|
|
335
371
|
| ((
|
|
336
372
|
args: InputShapeFromFields<Args>,
|
|
337
373
|
ctx: Types['Context'],
|
|
338
|
-
nestedSelection:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
) => DBQueryConfig<'one', Types['DrizzleRelations'], ExtractTable<Types, ParentShape>>)
|
|
374
|
+
nestedSelection: NestedSelectionFn<Types, Type, Args>,
|
|
375
|
+
) => MaybeAsyncSelection<
|
|
376
|
+
Types,
|
|
377
|
+
DBQueryConfig<'one', Types['DrizzleRelations'], ExtractTable<Types, ParentShape>>
|
|
378
|
+
>)
|
|
344
379
|
);
|
|
345
380
|
};
|
|
346
381
|
|
|
382
|
+
/** The table a field's type param names: a drizzle ref, or a list of one. */
|
|
383
|
+
export type TableForTypeParam<Types extends SchemaTypes, Type> = Type extends [infer Item]
|
|
384
|
+
? TableForTypeParam<Types, Item>
|
|
385
|
+
: // biome-ignore lint/suspicious/noExplicitAny: matching against any ref
|
|
386
|
+
Type extends DrizzleRef<any, infer Table>
|
|
387
|
+
? Table & keyof Types['DrizzleRelations']
|
|
388
|
+
: never;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* The query config for the table a field's type param names (a `many` config for a list field),
|
|
392
|
+
* or never when the type has no table.
|
|
393
|
+
*/
|
|
394
|
+
export type QueryForTypeParam<Types extends SchemaTypes, Type> =
|
|
395
|
+
TableForTypeParam<Types, Type> extends infer Table
|
|
396
|
+
? [Table] extends [never]
|
|
397
|
+
? never
|
|
398
|
+
: DBQueryConfig<
|
|
399
|
+
Type extends [unknown] ? 'many' : 'one',
|
|
400
|
+
Types['DrizzleRelations'],
|
|
401
|
+
Types['DrizzleRelations'][Table & keyof Types['DrizzleRelations']]
|
|
402
|
+
>
|
|
403
|
+
: never;
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* What `nestedSelection` returns: the query config for the field's table, keeping the keys of
|
|
407
|
+
* the given selection as they were given (so `columns` in it still narrow the parent shape).
|
|
408
|
+
* With no selection, or `true`, it is the config itself. A field whose type has no table keeps
|
|
409
|
+
* the selection it was given.
|
|
410
|
+
*/
|
|
411
|
+
export type NestedSelectionResult<Types extends SchemaTypes, Type, Selection> = [
|
|
412
|
+
QueryForTypeParam<Types, Type>,
|
|
413
|
+
] extends [never]
|
|
414
|
+
? Selection
|
|
415
|
+
: Selection extends boolean
|
|
416
|
+
? QueryForTypeParam<Types, Type>
|
|
417
|
+
: Normalize<Omit<QueryForTypeParam<Types, Type>, keyof Selection> & Selection>;
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* The callback a field's `select` function plans the selection beneath the field with: `path`
|
|
421
|
+
* walks a field nested under the field's type, `type` names the type the selection is read as.
|
|
422
|
+
* It also carries the `PathInfo` of the field being planned. The selection is typed by the
|
|
423
|
+
* field's table when it has one, so `columns: { title: true }` keeps its literal `true`.
|
|
424
|
+
*/
|
|
425
|
+
export type NestedSelectionFn<Types extends SchemaTypes, Type, Args extends InputFieldMap = {}> = (<
|
|
426
|
+
Selection extends
|
|
427
|
+
| boolean
|
|
428
|
+
| ([QueryForTypeParam<Types, Type>] extends [never]
|
|
429
|
+
? {}
|
|
430
|
+
: QueryForTypeParam<Types, Type>) = true,
|
|
431
|
+
>(
|
|
432
|
+
selection?: NestedSelectionArg<Types, Selection, Args>,
|
|
433
|
+
path?: PathSegment[],
|
|
434
|
+
type?: string,
|
|
435
|
+
) => NestedSelectionResult<Types, Type, Selection>) &
|
|
436
|
+
PathInfo;
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* The selection given to `nestedSelection`: the query config itself, or a callback building it
|
|
440
|
+
* from the field's arguments, the context, and the field's `PathInfo`. A schema with
|
|
441
|
+
* `AsyncSelections: true` may also give a promise of the config, or an async callback; the result
|
|
442
|
+
* is typed by the query either way, and is a promise at runtime only when a promise or an async
|
|
443
|
+
* callback was given, or a selection beneath it is async, and must then be awaited.
|
|
444
|
+
*/
|
|
445
|
+
export type NestedSelectionArg<Types extends SchemaTypes, Selection, Args extends InputFieldMap> =
|
|
446
|
+
| Selection
|
|
447
|
+
| (true extends Types['AsyncSelections'] ? PromiseLike<Selection> : never)
|
|
448
|
+
| ((
|
|
449
|
+
args: InputShapeFromFields<Args>,
|
|
450
|
+
ctx: Types['Context'],
|
|
451
|
+
pathInfo: PathInfo,
|
|
452
|
+
) => MaybeAsyncSelection<Types, Selection>);
|
|
453
|
+
|
|
347
454
|
export type DrizzleFieldSelection =
|
|
348
455
|
| DBQueryConfig<'one'>
|
|
349
456
|
| ((
|
|
@@ -353,13 +460,17 @@ export type DrizzleFieldSelection =
|
|
|
353
460
|
selection:
|
|
354
461
|
| SelectionMap
|
|
355
462
|
| boolean
|
|
356
|
-
| ((
|
|
357
|
-
|
|
463
|
+
| ((
|
|
464
|
+
args: object,
|
|
465
|
+
context: object,
|
|
466
|
+
pathInfo: PathInfo,
|
|
467
|
+
) => MaybePromise<DBQueryConfig<'one'>>),
|
|
468
|
+
path?: IndirectInclude | PathSegment[],
|
|
358
469
|
type?: string,
|
|
359
470
|
) => DBQueryConfig<'one'> | boolean,
|
|
360
471
|
resolveSelection: (path: string[]) => FieldNode | null,
|
|
361
472
|
pathInfo: PathInfo,
|
|
362
|
-
) => SelectionMap);
|
|
473
|
+
) => MaybePromise<SelectionMap | false | null | undefined>);
|
|
363
474
|
|
|
364
475
|
export type ExtractTable<Types extends SchemaTypes, Shape> = Shape extends {
|
|
365
476
|
[drizzleTableName]?: keyof Types['DrizzleRelations'];
|
|
@@ -460,7 +571,50 @@ export type RelatedCountOptions<
|
|
|
460
571
|
PothosSchemaTypes.ObjectFieldOptions<Types, Shape, 'Int', false, Args, number>,
|
|
461
572
|
'type' | InferredFieldOptionKeys
|
|
462
573
|
> & {
|
|
463
|
-
where?:
|
|
574
|
+
where?:
|
|
575
|
+
| Where
|
|
576
|
+
| ((
|
|
577
|
+
args: InputShapeFromFields<Args>,
|
|
578
|
+
context: Types['Context'],
|
|
579
|
+
) => MaybeAsyncSelection<Types, Where>);
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* The options of `t.relatedField`: the ordinary object field options (description, deprecation,
|
|
584
|
+
* extensions, and what other plugins add), with a `select` that plans a query on the relation.
|
|
585
|
+
*/
|
|
586
|
+
export type RelatedSelectionFieldOptions<
|
|
587
|
+
Types extends SchemaTypes,
|
|
588
|
+
TableConfig extends TableRelationalConfig,
|
|
589
|
+
Type extends TypeParam<Types>,
|
|
590
|
+
Nullable extends boolean,
|
|
591
|
+
Args extends InputFieldMap,
|
|
592
|
+
Select,
|
|
593
|
+
ShapeWithSelection,
|
|
594
|
+
> = Omit<
|
|
595
|
+
PothosSchemaTypes.ObjectFieldOptions<Types, ShapeWithSelection, Type, Nullable, Args, unknown>,
|
|
596
|
+
InferredFieldOptionKeys
|
|
597
|
+
> & {
|
|
598
|
+
/**
|
|
599
|
+
* The query planned on the parent row for this field: `buildFilter` filters the related table
|
|
600
|
+
* to the parent's rows, `nestedQuery` plans the field's own selection beneath a query. With
|
|
601
|
+
* `AsyncSelections: true` the function may be async.
|
|
602
|
+
*/
|
|
603
|
+
select: ((
|
|
604
|
+
buildFilter: (parentTable: TableConfig['table']) => SQL,
|
|
605
|
+
args: InputShapeFromFields<Args>,
|
|
606
|
+
ctx: Types['Context'],
|
|
607
|
+
nestedQuery: (
|
|
608
|
+
query: DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>,
|
|
609
|
+
) => DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>,
|
|
610
|
+
) => MaybeAsyncSelection<Types, Select>) &
|
|
611
|
+
CheckAsyncSelection<Types, Select>;
|
|
612
|
+
resolve: (
|
|
613
|
+
parent: ShapeWithSelection,
|
|
614
|
+
args: InputShapeFromFields<Args>,
|
|
615
|
+
ctx: Types['Context'],
|
|
616
|
+
info: GraphQLResolveInfo,
|
|
617
|
+
) => MaybePromise<ShapeFromTypeParam<Types, Type, Nullable>>;
|
|
464
618
|
};
|
|
465
619
|
|
|
466
620
|
export type TypesForRelation<Types extends SchemaTypes, Rel extends Relation> = BuildQueryResult<
|
|
@@ -500,7 +654,7 @@ export type QueryForField<
|
|
|
500
654
|
args: InputShapeFromFields<Args>,
|
|
501
655
|
context: Types['Context'],
|
|
502
656
|
pathInfo: PathInfo,
|
|
503
|
-
) => QueryConfig)
|
|
657
|
+
) => MaybeAsyncSelection<Types, QueryConfig>)
|
|
504
658
|
: never;
|
|
505
659
|
|
|
506
660
|
export type QueryForDrizzleField<
|
|
@@ -523,7 +677,13 @@ export type QueryForRelatedConnection<
|
|
|
523
677
|
> & {
|
|
524
678
|
orderBy?: ConnectionOrderBy<Table> | ((table: Table) => ConnectionOrderBy<Table>);
|
|
525
679
|
} extends infer QueryConfig
|
|
526
|
-
?
|
|
680
|
+
?
|
|
681
|
+
| QueryConfig
|
|
682
|
+
| ((
|
|
683
|
+
args: Args,
|
|
684
|
+
context: Types['Context'],
|
|
685
|
+
pathInfo: PathInfo,
|
|
686
|
+
) => MaybeAsyncSelection<Types, QueryConfig>)
|
|
527
687
|
: never;
|
|
528
688
|
|
|
529
689
|
export type QueryForDrizzleConnection<
|
|
@@ -655,18 +815,6 @@ export type RelatedConnectionOptions<
|
|
|
655
815
|
(InputShapeFromFields<Args> &
|
|
656
816
|
PothosSchemaTypes.DefaultConnectionArguments extends infer ConnectionArgs
|
|
657
817
|
? {
|
|
658
|
-
resolve?: (
|
|
659
|
-
parent: Shape,
|
|
660
|
-
args: ConnectionArgs,
|
|
661
|
-
context: Types['Context'],
|
|
662
|
-
info: GraphQLResolveInfo,
|
|
663
|
-
) => MaybePromise<
|
|
664
|
-
ShapeFromTypeParam<
|
|
665
|
-
Types,
|
|
666
|
-
[ObjectRef<Types, TypesForRelation<Types, Table['relations'][Field]>>],
|
|
667
|
-
Nullable
|
|
668
|
-
>
|
|
669
|
-
>;
|
|
670
818
|
query?: QueryForRelatedConnection<Types, NodeTable, ConnectionArgs>;
|
|
671
819
|
// biome-ignore lint/suspicious/noExplicitAny: this is fine
|
|
672
820
|
type?: Type & DrizzleRef<any, Table['relations'][Field]['targetTableName']>;
|
|
@@ -723,22 +871,5 @@ export type DrizzleConnectionShape<
|
|
|
723
871
|
|
|
724
872
|
export type WithBrand<T> = T & { [typeBrandKey]: string };
|
|
725
873
|
|
|
726
|
-
export interface AddGraphQLInputTypeOptions<Types extends SchemaTypes, Shape extends {}>
|
|
727
|
-
extends Omit<
|
|
728
|
-
PothosSchemaTypes.InputObjectTypeOptions<
|
|
729
|
-
Types,
|
|
730
|
-
InputFieldsFromShape<Types, Shape, 'InputObject'>
|
|
731
|
-
>,
|
|
732
|
-
'fields'
|
|
733
|
-
> {
|
|
734
|
-
name?: string;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
export interface DrizzleGraphQLInputExtensions {
|
|
738
|
-
table: string;
|
|
739
|
-
tableConfig: TableRelationalConfig;
|
|
740
|
-
inputType: 'insert' | 'filters' | 'orderBy' | 'update';
|
|
741
|
-
}
|
|
742
|
-
|
|
743
874
|
export { DrizzleInterfaceRef, type DrizzleRef } from './interface-ref.js';
|
|
744
875
|
export { DrizzleObjectRef } from './object-ref.js';
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { createContextCache, PothosValidationError } from '@pothos/core';
|
|
2
|
+
import {
|
|
3
|
+
type Node,
|
|
4
|
+
NodeAdapter,
|
|
5
|
+
type Plan,
|
|
6
|
+
type PlayedPlan,
|
|
7
|
+
type QueryVisitor,
|
|
8
|
+
type SelectFn,
|
|
9
|
+
} from '@pothos/selection-mapper';
|
|
10
|
+
import type { TableRelationalConfig } from 'drizzle-orm';
|
|
11
|
+
import type { GraphQLField, GraphQLNamedType } from 'graphql';
|
|
12
|
+
import type { DrizzleFieldSelection, PathInfo } from '../types.js';
|
|
13
|
+
import type { PothosDrizzleSchemaConfig } from './config.js';
|
|
14
|
+
import { pathInfoFor } from './path-info.js';
|
|
15
|
+
import { omitUndefinedKeys, type SelectionMap } from './selections.js';
|
|
16
|
+
|
|
17
|
+
export type DrizzleNode = Node<TableRelationalConfig>;
|
|
18
|
+
export type DrizzlePlan = Plan<TableRelationalConfig, SelectionMap>;
|
|
19
|
+
export type DrizzlePlayedPlan = PlayedPlan<TableRelationalConfig, SelectionMap>;
|
|
20
|
+
type DrizzleVisitor = QueryVisitor<TableRelationalConfig, SelectionMap>;
|
|
21
|
+
|
|
22
|
+
/** A map without `columns`: every column. Shared and never mutated. */
|
|
23
|
+
const ALL: SelectionMap = Object.freeze({});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How drizzle selections (`DBQueryConfig`: `columns`, `with`, `extras` and query arguments) read
|
|
27
|
+
* onto the shared query tree, and how a node is written back. A node whose columns are `null`
|
|
28
|
+
* selects every column; a named-column node always adds the table's primary key so rows can be
|
|
29
|
+
* matched back to their parent.
|
|
30
|
+
*
|
|
31
|
+
* The merge, compare and conflict rules are `NodeAdapter`'s: this is the schema side, the key
|
|
32
|
+
* loop and `toQuery`. The schema config every one of them needs is the adapter's own state.
|
|
33
|
+
*/
|
|
34
|
+
export class DrizzleAdapter extends NodeAdapter<TableRelationalConfig, SelectionMap> {
|
|
35
|
+
override skipDeferredFragments: boolean;
|
|
36
|
+
|
|
37
|
+
private readonly fieldSelections = new WeakMap<
|
|
38
|
+
GraphQLField<unknown, unknown>,
|
|
39
|
+
SelectFn<SelectionMap>
|
|
40
|
+
>();
|
|
41
|
+
|
|
42
|
+
constructor(private readonly config: PothosDrizzleSchemaConfig) {
|
|
43
|
+
super();
|
|
44
|
+
this.skipDeferredFragments = config.skipDeferredFragments;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Set by drizzleObject/drizzleInterface and propagated to implementing types by onTypeConfig.
|
|
48
|
+
modelFor(type: GraphQLNamedType) {
|
|
49
|
+
return type.extensions?.pothosDrizzleTable as TableRelationalConfig | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
typeSelection(type: GraphQLNamedType) {
|
|
53
|
+
// schema-builder stores `{ columns: {}, ...select }`, or `true` for every column.
|
|
54
|
+
const selection = type.extensions?.pothosDrizzleSelect as SelectionMap | true | undefined;
|
|
55
|
+
|
|
56
|
+
return selection === true ? ALL : selection || undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fieldSelection(field: GraphQLField<unknown, unknown>) {
|
|
60
|
+
const selection = field.extensions?.pothosDrizzleSelect as DrizzleFieldSelection | undefined;
|
|
61
|
+
if (typeof selection !== 'function') {
|
|
62
|
+
return selection || undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let compiled = this.fieldSelections.get(field);
|
|
66
|
+
if (!compiled) {
|
|
67
|
+
compiled = (args, context, nested, selected, position) => {
|
|
68
|
+
// Keep the public metadata at the adapter boundary. Static query objects do not need
|
|
69
|
+
// either path array; callbacks that inspect metadata build them once per invocation.
|
|
70
|
+
let resolvedPath: PathInfo | undefined;
|
|
71
|
+
const pathInfo: PathInfo = {
|
|
72
|
+
get path() {
|
|
73
|
+
resolvedPath ??= pathInfoFor(position)!;
|
|
74
|
+
return resolvedPath.path;
|
|
75
|
+
},
|
|
76
|
+
get segments() {
|
|
77
|
+
resolvedPath ??= pathInfoFor(position)!;
|
|
78
|
+
return resolvedPath.segments;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
return selection(
|
|
82
|
+
args,
|
|
83
|
+
context,
|
|
84
|
+
(query, path, type) =>
|
|
85
|
+
nested(
|
|
86
|
+
// The public API also accepts false, which the walker treats as an empty seed.
|
|
87
|
+
(typeof query === 'function' ? query(args, context, pathInfo) : query) as Parameters<
|
|
88
|
+
typeof nested
|
|
89
|
+
>[0],
|
|
90
|
+
path,
|
|
91
|
+
type,
|
|
92
|
+
),
|
|
93
|
+
selected,
|
|
94
|
+
pathInfo,
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
this.fieldSelections.set(field, compiled);
|
|
98
|
+
}
|
|
99
|
+
return compiled;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
visitQuery(
|
|
103
|
+
{ columns, with: withSelection, extras, ...args }: SelectionMap,
|
|
104
|
+
model: TableRelationalConfig,
|
|
105
|
+
visit: DrizzleVisitor,
|
|
106
|
+
) {
|
|
107
|
+
this.readRelations(withSelection, model, visit);
|
|
108
|
+
visit.args(omitUndefinedKeys(args));
|
|
109
|
+
|
|
110
|
+
for (const key of Object.keys(extras ?? {})) {
|
|
111
|
+
visit.computed(key, extras![key]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!columns) {
|
|
115
|
+
// No `columns` means every column, which a node never goes back from.
|
|
116
|
+
visit.allColumns();
|
|
117
|
+
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const key of Object.keys(columns)) {
|
|
122
|
+
if (columns[key]) {
|
|
123
|
+
visit.column(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Drizzle's `extras` are functions, compared by identity: the inherited deep-equal would let
|
|
130
|
+
* two different functions computing the same shape merge.
|
|
131
|
+
*/
|
|
132
|
+
override computedConflicts(computed: ReadonlyMap<string, unknown>, name: string, value: unknown) {
|
|
133
|
+
return computed.has(name) && computed.get(name) !== value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
toQuery(node: DrizzleNode): SelectionMap {
|
|
137
|
+
// `columns` stays present (as undefined) for an every-column node: it is what callers
|
|
138
|
+
// spreading or comparing the query have always seen.
|
|
139
|
+
const query: SelectionMap & {
|
|
140
|
+
with: Record<string, unknown>;
|
|
141
|
+
extras: Record<string, unknown>;
|
|
142
|
+
} = { ...node.args, columns: undefined, with: {}, extras: {} };
|
|
143
|
+
|
|
144
|
+
if (node.columns) {
|
|
145
|
+
query.columns = {};
|
|
146
|
+
|
|
147
|
+
for (const column of node.columns) {
|
|
148
|
+
query.columns[column] = true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const column of this.config.getPrimaryKey(node.model.name)) {
|
|
152
|
+
query.columns[this.config.columnToTsName(column)] = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const [key, value] of node.computed) {
|
|
157
|
+
query.extras[key] = value as never;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
for (const [key, child] of node.relations) {
|
|
161
|
+
query.with[key] = this.toQuery(child);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return query;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The keys of a `with` map, resolved to the tables they target. */
|
|
168
|
+
private readRelations(
|
|
169
|
+
withSelection: Record<string, unknown> | undefined,
|
|
170
|
+
model: TableRelationalConfig,
|
|
171
|
+
visit: DrizzleVisitor,
|
|
172
|
+
) {
|
|
173
|
+
for (const key of Object.keys(withSelection ?? {})) {
|
|
174
|
+
const value = withSelection![key];
|
|
175
|
+
|
|
176
|
+
if (!value) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const relationConfig = model.relations[key];
|
|
181
|
+
|
|
182
|
+
if (!relationConfig) {
|
|
183
|
+
throw new PothosValidationError(`Relation ${key} does not exist on ${model.name}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
visit.relation(
|
|
187
|
+
key,
|
|
188
|
+
this.config.relations[relationConfig.targetTableName],
|
|
189
|
+
value === true ? ALL : (value as SelectionMap),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const drizzleAdapter = createContextCache(
|
|
196
|
+
(config: PothosDrizzleSchemaConfig) => new DrizzleAdapter(config),
|
|
197
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isThenable, PothosValidationError } from '@pothos/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The guard behind `awaitSelections`. A query is only built asynchronously when a selection
|
|
5
|
+
* beneath the field is async, which the call site cannot see: what it plans is the rest of the
|
|
6
|
+
* document. Rather than hand back a promise where the declared type says there is none — which
|
|
7
|
+
* spreads into a drizzle call as one unusable key, with no type error — the promise is refused
|
|
8
|
+
* unless the caller asked for it.
|
|
9
|
+
*
|
|
10
|
+
* The refused promise is left handled, so a plan that also rejects does not report an unhandled
|
|
11
|
+
* rejection on the way out.
|
|
12
|
+
*/
|
|
13
|
+
export function checkAwaitSelections<T>(
|
|
14
|
+
value: T,
|
|
15
|
+
awaitSelections: boolean | undefined,
|
|
16
|
+
caller: string,
|
|
17
|
+
subject: string,
|
|
18
|
+
): T {
|
|
19
|
+
if (awaitSelections || !isThenable(value)) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
value.then(
|
|
24
|
+
() => {},
|
|
25
|
+
() => {},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
throw new PothosValidationError(
|
|
29
|
+
`${caller} could not build the query for ${subject} synchronously, because a selection beneath it is async. Pass awaitSelections: true and await the result.`,
|
|
30
|
+
);
|
|
31
|
+
}
|
package/src/utils/config.ts
CHANGED
|
@@ -173,11 +173,7 @@ export const drizzleClientCache = createContextCache(
|
|
|
173
173
|
? createContextCache((ctx) => clientConfig(ctx))
|
|
174
174
|
: (_ctx: object) => clientConfig;
|
|
175
175
|
|
|
176
|
-
return createContextCache(
|
|
177
|
-
const client = getClient(context);
|
|
178
|
-
|
|
179
|
-
return client;
|
|
180
|
-
});
|
|
176
|
+
return createContextCache(getClient);
|
|
181
177
|
},
|
|
182
178
|
);
|
|
183
179
|
|