@pothos/plugin-drizzle 0.18.0 → 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.
Files changed (167) hide show
  1. package/CHANGELOG.md +351 -0
  2. package/README.md +784 -221
  3. package/dts/connection-helpers.d.ts +18 -8
  4. package/dts/connection-helpers.d.ts.map +1 -1
  5. package/dts/drizzle-field-builder.d.ts +4 -11
  6. package/dts/drizzle-field-builder.d.ts.map +1 -1
  7. package/dts/global-types.d.ts.map +1 -1
  8. package/dts/index.d.ts.map +1 -1
  9. package/dts/model-loader.d.ts +18 -17
  10. package/dts/model-loader.d.ts.map +1 -1
  11. package/dts/types.d.ts +81 -20
  12. package/dts/types.d.ts.map +1 -1
  13. package/dts/utils/adapter.d.ts +39 -0
  14. package/dts/utils/adapter.d.ts.map +1 -0
  15. package/dts/utils/await-selections.d.ts +12 -0
  16. package/dts/utils/await-selections.d.ts.map +1 -0
  17. package/dts/utils/config.d.ts +23 -3
  18. package/dts/utils/config.d.ts.map +1 -1
  19. package/dts/utils/cursors.d.ts +6 -6
  20. package/dts/utils/cursors.d.ts.map +1 -1
  21. package/dts/utils/map-query.d.ts +22 -21
  22. package/dts/utils/map-query.d.ts.map +1 -1
  23. package/dts/utils/path-info.d.ts +13 -0
  24. package/dts/utils/path-info.d.ts.map +1 -0
  25. package/dts/utils/relation-filter.d.ts +48 -0
  26. package/dts/utils/relation-filter.d.ts.map +1 -0
  27. package/dts/utils/selections.d.ts +2 -21
  28. package/dts/utils/selections.d.ts.map +1 -1
  29. package/esm/connection-helpers.d.ts +18 -8
  30. package/esm/connection-helpers.d.ts.map +1 -1
  31. package/esm/connection-helpers.js +52 -19
  32. package/esm/connection-helpers.js.map +1 -1
  33. package/esm/drizzle-field-builder.d.ts +4 -11
  34. package/esm/drizzle-field-builder.d.ts.map +1 -1
  35. package/esm/drizzle-field-builder.js +138 -96
  36. package/esm/drizzle-field-builder.js.map +1 -1
  37. package/esm/field-builder.js +61 -47
  38. package/esm/field-builder.js.map +1 -1
  39. package/esm/global-types.d.ts.map +1 -1
  40. package/esm/index.d.ts.map +1 -1
  41. package/esm/index.js +37 -61
  42. package/esm/index.js.map +1 -1
  43. package/esm/model-loader.d.ts +18 -17
  44. package/esm/model-loader.d.ts.map +1 -1
  45. package/esm/model-loader.js +76 -45
  46. package/esm/model-loader.js.map +1 -1
  47. package/esm/schema-builder.js +40 -149
  48. package/esm/schema-builder.js.map +1 -1
  49. package/esm/types.d.ts +81 -20
  50. package/esm/types.d.ts.map +1 -1
  51. package/esm/types.js +1 -0
  52. package/esm/types.js.map +1 -1
  53. package/esm/utils/adapter.d.ts +39 -0
  54. package/esm/utils/adapter.d.ts.map +1 -0
  55. package/esm/utils/adapter.js +137 -0
  56. package/esm/utils/adapter.js.map +1 -0
  57. package/esm/utils/await-selections.d.ts +12 -0
  58. package/esm/utils/await-selections.d.ts.map +1 -0
  59. package/esm/utils/await-selections.js +19 -0
  60. package/esm/utils/await-selections.js.map +1 -0
  61. package/esm/utils/config.d.ts +23 -3
  62. package/esm/utils/config.d.ts.map +1 -1
  63. package/esm/utils/config.js +1 -4
  64. package/esm/utils/config.js.map +1 -1
  65. package/esm/utils/cursors.d.ts +6 -6
  66. package/esm/utils/cursors.d.ts.map +1 -1
  67. package/esm/utils/cursors.js +55 -90
  68. package/esm/utils/cursors.js.map +1 -1
  69. package/esm/utils/map-query.d.ts +22 -21
  70. package/esm/utils/map-query.d.ts.map +1 -1
  71. package/esm/utils/map-query.js +40 -336
  72. package/esm/utils/map-query.js.map +1 -1
  73. package/esm/utils/path-info.d.ts +13 -0
  74. package/esm/utils/path-info.d.ts.map +1 -0
  75. package/esm/utils/path-info.js +37 -0
  76. package/esm/utils/path-info.js.map +1 -0
  77. package/esm/utils/relation-filter.d.ts +48 -0
  78. package/esm/utils/relation-filter.d.ts.map +1 -0
  79. package/esm/utils/relation-filter.js +92 -0
  80. package/esm/utils/relation-filter.js.map +1 -0
  81. package/esm/utils/selections.d.ts +2 -21
  82. package/esm/utils/selections.d.ts.map +1 -1
  83. package/esm/utils/selections.js +1 -133
  84. package/esm/utils/selections.js.map +1 -1
  85. package/lib/connection-helpers.js +51 -18
  86. package/lib/connection-helpers.js.map +1 -1
  87. package/lib/drizzle-field-builder.js +136 -94
  88. package/lib/drizzle-field-builder.js.map +1 -1
  89. package/lib/field-builder.js +58 -44
  90. package/lib/field-builder.js.map +1 -1
  91. package/lib/index.js +36 -60
  92. package/lib/index.js.map +1 -1
  93. package/lib/model-loader.js +76 -45
  94. package/lib/model-loader.js.map +1 -1
  95. package/lib/schema-builder.js +40 -149
  96. package/lib/schema-builder.js.map +1 -1
  97. package/lib/types.js +4 -0
  98. package/lib/types.js.map +1 -1
  99. package/lib/utils/adapter.js +147 -0
  100. package/lib/utils/adapter.js.map +1 -0
  101. package/lib/utils/await-selections.js +20 -0
  102. package/lib/utils/await-selections.js.map +1 -0
  103. package/lib/utils/config.js +1 -4
  104. package/lib/utils/config.js.map +1 -1
  105. package/lib/utils/cursors.js +47 -88
  106. package/lib/utils/cursors.js.map +1 -1
  107. package/lib/utils/map-query.js +28 -341
  108. package/lib/utils/map-query.js.map +1 -1
  109. package/lib/utils/path-info.js +39 -0
  110. package/lib/utils/path-info.js.map +1 -0
  111. package/lib/utils/relation-filter.js +91 -0
  112. package/lib/utils/relation-filter.js.map +1 -0
  113. package/lib/utils/selections.js +3 -158
  114. package/lib/utils/selections.js.map +1 -1
  115. package/package.json +8 -6
  116. package/src/connection-helpers.ts +167 -53
  117. package/src/drizzle-field-builder.ts +296 -176
  118. package/src/field-builder.ts +110 -77
  119. package/src/global-types.ts +0 -32
  120. package/src/index.ts +42 -68
  121. package/src/model-loader.ts +110 -71
  122. package/src/schema-builder.ts +76 -169
  123. package/src/types.ts +176 -44
  124. package/src/utils/adapter.ts +197 -0
  125. package/src/utils/await-selections.ts +31 -0
  126. package/src/utils/config.ts +1 -5
  127. package/src/utils/cursors.ts +53 -81
  128. package/src/utils/map-query.ts +51 -708
  129. package/src/utils/path-info.ts +40 -0
  130. package/src/utils/relation-filter.ts +185 -0
  131. package/src/utils/selections.ts +2 -207
  132. package/dts/utils/deep-equal.d.ts +0 -2
  133. package/dts/utils/deep-equal.d.ts.map +0 -1
  134. package/dts/utils/drizzle-graphql.d.ts +0 -2
  135. package/dts/utils/drizzle-graphql.d.ts.map +0 -1
  136. package/dts/utils/loader-map.d.ts +0 -11
  137. package/dts/utils/loader-map.d.ts.map +0 -1
  138. package/dts/utils/usage.d.ts +0 -5
  139. package/dts/utils/usage.d.ts.map +0 -1
  140. package/esm/utils/deep-equal.d.ts +0 -2
  141. package/esm/utils/deep-equal.d.ts.map +0 -1
  142. package/esm/utils/deep-equal.js +0 -45
  143. package/esm/utils/deep-equal.js.map +0 -1
  144. package/esm/utils/drizzle-graphql.d.ts +0 -2
  145. package/esm/utils/drizzle-graphql.d.ts.map +0 -1
  146. package/esm/utils/drizzle-graphql.js +0 -232
  147. package/esm/utils/drizzle-graphql.js.map +0 -1
  148. package/esm/utils/loader-map.d.ts +0 -11
  149. package/esm/utils/loader-map.d.ts.map +0 -1
  150. package/esm/utils/loader-map.js +0 -35
  151. package/esm/utils/loader-map.js.map +0 -1
  152. package/esm/utils/usage.d.ts +0 -5
  153. package/esm/utils/usage.d.ts.map +0 -1
  154. package/esm/utils/usage.js +0 -59
  155. package/esm/utils/usage.js.map +0 -1
  156. package/lib/utils/deep-equal.js +0 -55
  157. package/lib/utils/deep-equal.js.map +0 -1
  158. package/lib/utils/drizzle-graphql.js +0 -236
  159. package/lib/utils/drizzle-graphql.js.map +0 -1
  160. package/lib/utils/loader-map.js +0 -56
  161. package/lib/utils/loader-map.js.map +0 -1
  162. package/lib/utils/usage.js +0 -83
  163. package/lib/utils/usage.js.map +0 -1
  164. package/src/utils/deep-equal.ts +0 -57
  165. package/src/utils/drizzle-graphql.ts +0 -274
  166. package/src/utils/loader-map.ts +0 -49
  167. package/src/utils/usage.ts +0 -62
@@ -1,9 +1,14 @@
1
1
  import {
2
2
  decodeBase64,
3
+ decodeCursorChunk,
3
4
  encodeBase64,
5
+ encodeCursorChunk,
6
+ encodeCursorTuple,
7
+ getConnectionPageSize,
4
8
  type MaybePromise,
5
9
  PothosValidationError,
6
10
  type SchemaTypes,
11
+ validateConnectionArguments,
7
12
  } from '@pothos/core';
8
13
  import {
9
14
  asc,
@@ -20,35 +25,17 @@ import {
20
25
  type Table,
21
26
  type TableRelationalConfig,
22
27
  } from 'drizzle-orm';
23
- import type { GraphQLResolveInfo } from 'graphql';
24
28
  import type { ConnectionOrderBy, QueryForDrizzleConnection } from '../types.js';
29
+ import type { DrizzlePlan } from './adapter.js';
25
30
  import type { PothosDrizzleSchemaConfig } from './config.js';
26
- import { queryFromInfo } from './map-query.js';
31
+ import { queryFromPlan } from './map-query.js';
27
32
  import { omitUndefinedKeys, type SelectionMap } from './selections.js';
28
33
 
29
- const DEFAULT_MAX_SIZE = 100;
30
- const DEFAULT_SIZE = 20;
31
-
32
- export function formatCursorChunk(value: unknown) {
33
- if (value == null) {
34
- return null;
35
- }
36
-
37
- if (value instanceof Date) {
38
- return `D:${String(Number(value))}`;
39
- }
40
-
41
- switch (typeof value) {
42
- case 'number':
43
- return `N:${value}`;
44
- case 'string':
45
- return `S:${value}`;
46
- case 'bigint':
47
- return `I:${value}`;
48
- default:
49
- throw new PothosValidationError(`Unsupported cursor type ${typeof value}`);
50
- }
51
- }
34
+ // The tagging is `@pothos/core`'s `encodeCursorChunk`, shared with the prisma plugins so the
35
+ // three can't drift apart again. A nullish ordering value is a position this plugin compares
36
+ // against with `is null`, and it now writes as a `Z:` chunk: it used to interpolate as the string
37
+ // `null`, which is not a chunk, so the cursor came back out as "Invalid cursor" on the next page.
38
+ export const formatCursorChunk = encodeCursorChunk;
52
39
 
53
40
  export function formatDrizzleCursor(
54
41
  record: Record<string, unknown>,
@@ -80,7 +67,6 @@ export function getIDSerializer(fields: Column[], config: PothosDrizzleSchemaCon
80
67
 
81
68
  return (value: Record<string, unknown>) => {
82
69
  if (fields.length > 1) {
83
- fields.map((field) => field.keyAsName);
84
70
  return `${JSON.stringify(fields.map((col) => value[config.columnToTsName(col)]))}`;
85
71
  }
86
72
 
@@ -109,9 +95,7 @@ export function getColumnSerializer(
109
95
  if (fields.length > 1) {
110
96
  // each value carries its own type tag, the same ones a single value gets.
111
97
  // Plain JSON would turn a Date into a string and refuse a bigint outright.
112
- return `T:${JSON.stringify(
113
- fields.map((field) => formatCursorChunk(value[cursorFieldKey(field, config)])),
114
- )}`;
98
+ return encodeCursorTuple(fields.map((field) => value[cursorFieldKey(field, config)]));
115
99
  }
116
100
 
117
101
  return formatCursorChunk(value[cursorFieldKey(fields[0], config)]);
@@ -156,28 +140,9 @@ export function parseSerializedDrizzleColumn(value: unknown): unknown {
156
140
  }
157
141
 
158
142
  try {
159
- const [, type, rawValue] = value.match(/^(S|N|D|J|I|T):(.*)/) as [string, string, string];
160
-
161
- switch (type) {
162
- case 'S':
163
- return rawValue;
164
- case 'N':
165
- return Number.parseInt(rawValue, 10);
166
- case 'D':
167
- return new Date(Number.parseInt(rawValue, 10));
168
- case 'J':
169
- // compound values from before each one carried a tag: whatever JSON
170
- // preserved is the best that can be recovered
171
- return JSON.parse(rawValue) as unknown;
172
- case 'T':
173
- return (JSON.parse(rawValue) as (string | null)[]).map((chunk) =>
174
- chunk === null ? null : parseSerializedDrizzleColumn(chunk),
175
- );
176
- case 'I':
177
- return BigInt(rawValue);
178
- default:
179
- throw new PothosValidationError(`Invalid cursor type ${type}`);
180
- }
143
+ // `@pothos/core`'s `decodeCursorChunk` reads every tag, including the `J:` a compound cursor
144
+ // used before each part carried one -- see `getCursorParser` for what happens to that array.
145
+ return decodeCursorChunk(value);
181
146
  } catch {
182
147
  throw new PothosValidationError(`Invalid serialized data: ${value}`);
183
148
  }
@@ -202,7 +167,9 @@ export function parseSerializedIDColumn(id: string, field: Column): unknown {
202
167
  }
203
168
 
204
169
  if (field.dataType === 'object date') {
205
- return new Date(id);
170
+ // `formatIDChunk` writes the epoch milliseconds; `new Date` of that string is an Invalid
171
+ // Date, since it is not a date format `Date` parses.
172
+ return new Date(Number(id));
206
173
  }
207
174
 
208
175
  throw new PothosValidationError(`Unsupported ID type ${field.dataType}`);
@@ -215,14 +182,16 @@ export function parseSerializedIDColumn(id: string, field: Column): unknown {
215
182
  }
216
183
  }
217
184
 
218
- export function getIDParser(fields: readonly Column[]) {
185
+ // Keyed by the typescript name, as `getIDSerializer` reads by: the record is a row of the table,
186
+ // and every consumer (the node ref's `parseId`, the model loader) looks columns up that way.
187
+ export function getIDParser(fields: readonly Column[], config: PothosDrizzleSchemaConfig) {
219
188
  if (fields.length === 0) {
220
189
  throw new PothosValidationError('Column parser must have at least one field');
221
190
  }
222
191
 
223
192
  return (value: string) => {
224
193
  if (fields.length === 1) {
225
- return { [fields[0].name]: parseSerializedIDColumn(value, fields[0]) };
194
+ return { [config.columnToTsName(fields[0])]: parseSerializedIDColumn(value, fields[0]) };
226
195
  }
227
196
 
228
197
  try {
@@ -243,7 +212,7 @@ export function getIDParser(fields: readonly Column[]) {
243
212
  const record: Record<string, unknown> = {};
244
213
 
245
214
  fields.forEach((field, i) => {
246
- record[field.name] = parsed[i];
215
+ record[config.columnToTsName(field)] = parsed[i];
247
216
  });
248
217
 
249
218
  return record;
@@ -307,6 +276,10 @@ export function getCursorParser(keys: readonly string[]) {
307
276
  // the columns that came before it. Those still describe a position, just a
308
277
  // less precise one, so the page is keyed off the prefix the cursor covers
309
278
  // rather than rejected. Cursors returned by that page carry every column.
279
+ //
280
+ // The array is a `T:` chunk's values, each still its own type. A cursor issued before this
281
+ // release decodes from a `J:` chunk instead, whose parts arrive as whatever plain JSON
282
+ // preserved -- see the deprecated `J:` case in `@pothos/core`'s `decodeCursorChunk`.
310
283
  const values = Array.isArray(parsed) ? parsed : [parsed];
311
284
 
312
285
  if (values.length === 0) {
@@ -553,8 +526,8 @@ function keysetFilter(entries: OrderByEntry[], cursor: string, paging: 'after' |
553
526
  export function drizzleCursorConnectionQuery({
554
527
  args,
555
528
  ctx,
556
- maxSize = DEFAULT_MAX_SIZE,
557
- defaultSize = DEFAULT_SIZE,
529
+ maxSize,
530
+ defaultSize,
558
531
  orderBy,
559
532
  extras,
560
533
  where,
@@ -562,13 +535,7 @@ export function drizzleCursorConnectionQuery({
562
535
  table,
563
536
  }: DrizzleCursorConnectionQueryOptions) {
564
537
  const { before, after, first, last } = args;
565
- if (first != null && first < 0) {
566
- throw new PothosValidationError('Argument "first" must be a non-negative integer');
567
- }
568
-
569
- if (last != null && last < 0) {
570
- throw new PothosValidationError('Argument "last" must be a non-negative integer');
571
- }
538
+ validateConnectionArguments(args);
572
539
 
573
540
  if (first != null && last != null) {
574
541
  throw new PothosValidationError(
@@ -580,8 +547,13 @@ export function drizzleCursorConnectionQuery({
580
547
  const defaultSizeForConnection =
581
548
  typeof defaultSize === 'function' ? defaultSize(args, ctx) : defaultSize;
582
549
 
583
- const limit = Math.min(first ?? last ?? defaultSizeForConnection, maxSizeForConnection) + 1;
584
- const inverted = !first && !!last;
550
+ const { limit } = getConnectionPageSize({
551
+ args,
552
+ defaultSize: defaultSizeForConnection,
553
+ maxSize: maxSizeForConnection,
554
+ });
555
+ // `last: 0` asks for the last zero rows, so it pages backwards like any other `last`.
556
+ const inverted = first == null && last != null;
585
557
 
586
558
  const parsedOrderBy = parseOrderBy(config, table, orderBy, inverted, extras);
587
559
 
@@ -624,8 +596,11 @@ export function wrapConnectionResult<T extends {}>(
624
596
  totalCount?: number | (() => MaybePromise<number>) | null,
625
597
  ) {
626
598
  const gotFullResults = results.length === Math.abs(limit);
627
- const hasNextPage = args.before ? true : args.last ? false : gotFullResults;
628
- const hasPreviousPage = args.after ? true : !args.first && !!args.last ? gotFullResults : false;
599
+ // `first`/`last` are compared against null rather than by truthiness, so `last: 0` reports the
600
+ // `pageInfo` of a backward page of zero rows rather than of a forward one.
601
+ const backward = args.first == null && args.last != null;
602
+ const hasNextPage = args.before ? true : args.last != null ? false : gotFullResults;
603
+ const hasPreviousPage = args.after ? true : backward ? gotFullResults : false;
629
604
  const nodes = gotFullResults ? results.slice(0, -1) : results;
630
605
 
631
606
  const connection = {
@@ -658,7 +633,7 @@ export function wrapConnectionResult<T extends {}>(
658
633
  },
659
634
  );
660
635
 
661
- if (args.last && !args.first) {
636
+ if (backward) {
662
637
  edges.reverse();
663
638
  }
664
639
 
@@ -671,8 +646,8 @@ export function wrapConnectionResult<T extends {}>(
671
646
 
672
647
  export async function resolveDrizzleCursorConnection<T extends {}>(
673
648
  tableName: string,
674
- info: GraphQLResolveInfo,
675
- typeName: string,
649
+ // The settled plan of the connection's rows; the builder handed to `resolve` merges into it.
650
+ plan: DrizzlePlan | undefined,
676
651
  config: PothosDrizzleSchemaConfig,
677
652
  options: Omit<DrizzleCursorConnectionQueryOptions, 'orderBy' | 'config' | 'table'> & {
678
653
  totalCount?: () => MaybePromise<number>;
@@ -697,10 +672,9 @@ export async function resolveDrizzleCursorConnection<T extends {}>(
697
672
  });
698
673
  formatter = getCursorFormatter(cursorFields, config);
699
674
 
700
- query = queryFromInfo({
701
- context: options.ctx,
702
- info,
703
- select: omitUndefinedKeys({
675
+ query = queryFromPlan(
676
+ plan,
677
+ omitUndefinedKeys({
704
678
  ...connectionQuery,
705
679
  extras: q.extras,
706
680
  columns: {
@@ -714,11 +688,7 @@ export async function resolveDrizzleCursorConnection<T extends {}>(
714
688
  }
715
689
  : q.where || connectionQuery.where,
716
690
  }) as never,
717
- paths: [['nodes'], ['edges', 'node']],
718
- typeName,
719
- config,
720
- // withUsageCheck: !!this.builder.options.prisma?.onUnusedQuery,
721
- });
691
+ );
722
692
 
723
693
  return query;
724
694
  });
@@ -743,10 +713,12 @@ export async function resolveDrizzleCursorConnection<T extends {}>(
743
713
  };
744
714
  }
745
715
 
716
+ const { limit } = query;
717
+
746
718
  return wrapConnectionResult(
747
719
  results,
748
720
  options.args,
749
- query.limit as number,
721
+ limit as number,
750
722
  formatter!,
751
723
  undefined,
752
724
  parent,