@pothos/plugin-drizzle 0.18.1 → 0.19.1

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 (165) hide show
  1. package/CHANGELOG.md +357 -0
  2. package/README.md +1199 -563
  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 +82 -22
  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 +82 -22
  50. package/esm/types.d.ts.map +1 -1
  51. package/esm/types.js.map +1 -1
  52. package/esm/utils/adapter.d.ts +39 -0
  53. package/esm/utils/adapter.d.ts.map +1 -0
  54. package/esm/utils/adapter.js +137 -0
  55. package/esm/utils/adapter.js.map +1 -0
  56. package/esm/utils/await-selections.d.ts +12 -0
  57. package/esm/utils/await-selections.d.ts.map +1 -0
  58. package/esm/utils/await-selections.js +19 -0
  59. package/esm/utils/await-selections.js.map +1 -0
  60. package/esm/utils/config.d.ts +23 -3
  61. package/esm/utils/config.d.ts.map +1 -1
  62. package/esm/utils/config.js +1 -4
  63. package/esm/utils/config.js.map +1 -1
  64. package/esm/utils/cursors.d.ts +6 -6
  65. package/esm/utils/cursors.d.ts.map +1 -1
  66. package/esm/utils/cursors.js +75 -92
  67. package/esm/utils/cursors.js.map +1 -1
  68. package/esm/utils/map-query.d.ts +22 -21
  69. package/esm/utils/map-query.d.ts.map +1 -1
  70. package/esm/utils/map-query.js +40 -354
  71. package/esm/utils/map-query.js.map +1 -1
  72. package/esm/utils/path-info.d.ts +13 -0
  73. package/esm/utils/path-info.d.ts.map +1 -0
  74. package/esm/utils/path-info.js +37 -0
  75. package/esm/utils/path-info.js.map +1 -0
  76. package/esm/utils/relation-filter.d.ts +48 -0
  77. package/esm/utils/relation-filter.d.ts.map +1 -0
  78. package/esm/utils/relation-filter.js +92 -0
  79. package/esm/utils/relation-filter.js.map +1 -0
  80. package/esm/utils/selections.d.ts +2 -21
  81. package/esm/utils/selections.d.ts.map +1 -1
  82. package/esm/utils/selections.js +1 -133
  83. package/esm/utils/selections.js.map +1 -1
  84. package/lib/connection-helpers.js +51 -18
  85. package/lib/connection-helpers.js.map +1 -1
  86. package/lib/drizzle-field-builder.js +136 -94
  87. package/lib/drizzle-field-builder.js.map +1 -1
  88. package/lib/field-builder.js +58 -44
  89. package/lib/field-builder.js.map +1 -1
  90. package/lib/index.js +36 -60
  91. package/lib/index.js.map +1 -1
  92. package/lib/model-loader.js +76 -45
  93. package/lib/model-loader.js.map +1 -1
  94. package/lib/schema-builder.js +40 -149
  95. package/lib/schema-builder.js.map +1 -1
  96. package/lib/types.js.map +1 -1
  97. package/lib/utils/adapter.js +147 -0
  98. package/lib/utils/adapter.js.map +1 -0
  99. package/lib/utils/await-selections.js +20 -0
  100. package/lib/utils/await-selections.js.map +1 -0
  101. package/lib/utils/config.js +1 -4
  102. package/lib/utils/config.js.map +1 -1
  103. package/lib/utils/cursors.js +67 -90
  104. package/lib/utils/cursors.js.map +1 -1
  105. package/lib/utils/map-query.js +28 -359
  106. package/lib/utils/map-query.js.map +1 -1
  107. package/lib/utils/path-info.js +39 -0
  108. package/lib/utils/path-info.js.map +1 -0
  109. package/lib/utils/relation-filter.js +91 -0
  110. package/lib/utils/relation-filter.js.map +1 -0
  111. package/lib/utils/selections.js +3 -158
  112. package/lib/utils/selections.js.map +1 -1
  113. package/package.json +10 -8
  114. package/src/connection-helpers.ts +168 -54
  115. package/src/drizzle-field-builder.ts +296 -176
  116. package/src/field-builder.ts +110 -77
  117. package/src/global-types.ts +0 -32
  118. package/src/index.ts +42 -68
  119. package/src/model-loader.ts +110 -71
  120. package/src/schema-builder.ts +76 -169
  121. package/src/types.ts +178 -46
  122. package/src/utils/adapter.ts +197 -0
  123. package/src/utils/await-selections.ts +31 -0
  124. package/src/utils/config.ts +1 -5
  125. package/src/utils/cursors.ts +84 -83
  126. package/src/utils/map-query.ts +51 -734
  127. package/src/utils/path-info.ts +40 -0
  128. package/src/utils/relation-filter.ts +185 -0
  129. package/src/utils/selections.ts +2 -207
  130. package/dts/utils/deep-equal.d.ts +0 -2
  131. package/dts/utils/deep-equal.d.ts.map +0 -1
  132. package/dts/utils/drizzle-graphql.d.ts +0 -2
  133. package/dts/utils/drizzle-graphql.d.ts.map +0 -1
  134. package/dts/utils/loader-map.d.ts +0 -11
  135. package/dts/utils/loader-map.d.ts.map +0 -1
  136. package/dts/utils/usage.d.ts +0 -5
  137. package/dts/utils/usage.d.ts.map +0 -1
  138. package/esm/utils/deep-equal.d.ts +0 -2
  139. package/esm/utils/deep-equal.d.ts.map +0 -1
  140. package/esm/utils/deep-equal.js +0 -45
  141. package/esm/utils/deep-equal.js.map +0 -1
  142. package/esm/utils/drizzle-graphql.d.ts +0 -2
  143. package/esm/utils/drizzle-graphql.d.ts.map +0 -1
  144. package/esm/utils/drizzle-graphql.js +0 -232
  145. package/esm/utils/drizzle-graphql.js.map +0 -1
  146. package/esm/utils/loader-map.d.ts +0 -11
  147. package/esm/utils/loader-map.d.ts.map +0 -1
  148. package/esm/utils/loader-map.js +0 -35
  149. package/esm/utils/loader-map.js.map +0 -1
  150. package/esm/utils/usage.d.ts +0 -5
  151. package/esm/utils/usage.d.ts.map +0 -1
  152. package/esm/utils/usage.js +0 -59
  153. package/esm/utils/usage.js.map +0 -1
  154. package/lib/utils/deep-equal.js +0 -55
  155. package/lib/utils/deep-equal.js.map +0 -1
  156. package/lib/utils/drizzle-graphql.js +0 -236
  157. package/lib/utils/drizzle-graphql.js.map +0 -1
  158. package/lib/utils/loader-map.js +0 -56
  159. package/lib/utils/loader-map.js.map +0 -1
  160. package/lib/utils/usage.js +0 -83
  161. package/lib/utils/usage.js.map +0 -1
  162. package/src/utils/deep-equal.ts +0 -57
  163. package/src/utils/drizzle-graphql.ts +0 -274
  164. package/src/utils/loader-map.ts +0 -49
  165. 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>,
@@ -73,6 +60,33 @@ export function formatIDChunk(value: unknown) {
73
60
  }
74
61
  }
75
62
 
63
+ function formatCompoundIDValue(value: unknown) {
64
+ if (typeof value === 'bigint') {
65
+ return value.toString();
66
+ }
67
+
68
+ if (value instanceof Date) {
69
+ return Number(value);
70
+ }
71
+
72
+ return value;
73
+ }
74
+
75
+ function parseCompoundIDValue(value: unknown, field: Column) {
76
+ if (
77
+ field.dataType.startsWith('bigint') &&
78
+ (typeof value === 'string' || typeof value === 'number')
79
+ ) {
80
+ return BigInt(value);
81
+ }
82
+
83
+ if (field.dataType === 'object date' && typeof value === 'number') {
84
+ return new Date(value);
85
+ }
86
+
87
+ return value;
88
+ }
89
+
76
90
  export function getIDSerializer(fields: Column[], config: PothosDrizzleSchemaConfig) {
77
91
  if (fields.length === 0) {
78
92
  throw new PothosValidationError('Column serializer must have at least one field');
@@ -80,8 +94,9 @@ export function getIDSerializer(fields: Column[], config: PothosDrizzleSchemaCon
80
94
 
81
95
  return (value: Record<string, unknown>) => {
82
96
  if (fields.length > 1) {
83
- fields.map((field) => field.keyAsName);
84
- return `${JSON.stringify(fields.map((col) => value[config.columnToTsName(col)]))}`;
97
+ return `${JSON.stringify(
98
+ fields.map((col) => formatCompoundIDValue(value[config.columnToTsName(col)])),
99
+ )}`;
85
100
  }
86
101
 
87
102
  return `${formatIDChunk(value[config.columnToTsName(fields[0])])}`;
@@ -109,9 +124,7 @@ export function getColumnSerializer(
109
124
  if (fields.length > 1) {
110
125
  // each value carries its own type tag, the same ones a single value gets.
111
126
  // 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
- )}`;
127
+ return encodeCursorTuple(fields.map((field) => value[cursorFieldKey(field, config)]));
115
128
  }
116
129
 
117
130
  return formatCursorChunk(value[cursorFieldKey(fields[0], config)]);
@@ -156,28 +169,9 @@ export function parseSerializedDrizzleColumn(value: unknown): unknown {
156
169
  }
157
170
 
158
171
  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
- }
172
+ // `@pothos/core`'s `decodeCursorChunk` reads every tag, including the `J:` a compound cursor
173
+ // used before each part carried one -- see `getCursorParser` for what happens to that array.
174
+ return decodeCursorChunk(value);
181
175
  } catch {
182
176
  throw new PothosValidationError(`Invalid serialized data: ${value}`);
183
177
  }
@@ -190,7 +184,7 @@ export function parseSerializedIDColumn(id: string, field: Column): unknown {
190
184
 
191
185
  try {
192
186
  if (field.dataType.startsWith('number')) {
193
- return Number.parseInt(id, 10);
187
+ return Number(id);
194
188
  }
195
189
 
196
190
  if (field.dataType.startsWith('bigint')) {
@@ -202,7 +196,9 @@ export function parseSerializedIDColumn(id: string, field: Column): unknown {
202
196
  }
203
197
 
204
198
  if (field.dataType === 'object date') {
205
- return new Date(id);
199
+ // `formatIDChunk` writes the epoch milliseconds; `new Date` of that string is an Invalid
200
+ // Date, since it is not a date format `Date` parses.
201
+ return new Date(Number(id));
206
202
  }
207
203
 
208
204
  throw new PothosValidationError(`Unsupported ID type ${field.dataType}`);
@@ -215,14 +211,16 @@ export function parseSerializedIDColumn(id: string, field: Column): unknown {
215
211
  }
216
212
  }
217
213
 
218
- export function getIDParser(fields: readonly Column[]) {
214
+ // Keyed by the typescript name, as `getIDSerializer` reads by: the record is a row of the table,
215
+ // and every consumer (the node ref's `parseId`, the model loader) looks columns up that way.
216
+ export function getIDParser(fields: readonly Column[], config: PothosDrizzleSchemaConfig) {
219
217
  if (fields.length === 0) {
220
218
  throw new PothosValidationError('Column parser must have at least one field');
221
219
  }
222
220
 
223
221
  return (value: string) => {
224
222
  if (fields.length === 1) {
225
- return { [fields[0].name]: parseSerializedIDColumn(value, fields[0]) };
223
+ return { [config.columnToTsName(fields[0])]: parseSerializedIDColumn(value, fields[0]) };
226
224
  }
227
225
 
228
226
  try {
@@ -243,7 +241,7 @@ export function getIDParser(fields: readonly Column[]) {
243
241
  const record: Record<string, unknown> = {};
244
242
 
245
243
  fields.forEach((field, i) => {
246
- record[field.name] = parsed[i];
244
+ record[config.columnToTsName(field)] = parseCompoundIDValue(parsed[i], field);
247
245
  });
248
246
 
249
247
  return record;
@@ -307,6 +305,10 @@ export function getCursorParser(keys: readonly string[]) {
307
305
  // the columns that came before it. Those still describe a position, just a
308
306
  // less precise one, so the page is keyed off the prefix the cursor covers
309
307
  // rather than rejected. Cursors returned by that page carry every column.
308
+ //
309
+ // The array is a `T:` chunk's values, each still its own type. A cursor issued before this
310
+ // release decodes from a `J:` chunk instead, whose parts arrive as whatever plain JSON
311
+ // preserved -- see the deprecated `J:` case in `@pothos/core`'s `decodeCursorChunk`.
310
312
  const values = Array.isArray(parsed) ? parsed : [parsed];
311
313
 
312
314
  if (values.length === 0) {
@@ -553,8 +555,8 @@ function keysetFilter(entries: OrderByEntry[], cursor: string, paging: 'after' |
553
555
  export function drizzleCursorConnectionQuery({
554
556
  args,
555
557
  ctx,
556
- maxSize = DEFAULT_MAX_SIZE,
557
- defaultSize = DEFAULT_SIZE,
558
+ maxSize,
559
+ defaultSize,
558
560
  orderBy,
559
561
  extras,
560
562
  where,
@@ -562,13 +564,7 @@ export function drizzleCursorConnectionQuery({
562
564
  table,
563
565
  }: DrizzleCursorConnectionQueryOptions) {
564
566
  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
- }
567
+ validateConnectionArguments(args);
572
568
 
573
569
  if (first != null && last != null) {
574
570
  throw new PothosValidationError(
@@ -580,8 +576,13 @@ export function drizzleCursorConnectionQuery({
580
576
  const defaultSizeForConnection =
581
577
  typeof defaultSize === 'function' ? defaultSize(args, ctx) : defaultSize;
582
578
 
583
- const limit = Math.min(first ?? last ?? defaultSizeForConnection, maxSizeForConnection) + 1;
584
- const inverted = !first && !!last;
579
+ const { limit } = getConnectionPageSize({
580
+ args,
581
+ defaultSize: defaultSizeForConnection,
582
+ maxSize: maxSizeForConnection,
583
+ });
584
+ // `last: 0` asks for the last zero rows, so it pages backwards like any other `last`.
585
+ const inverted = first == null && last != null;
585
586
 
586
587
  const parsedOrderBy = parseOrderBy(config, table, orderBy, inverted, extras);
587
588
 
@@ -624,8 +625,11 @@ export function wrapConnectionResult<T extends {}>(
624
625
  totalCount?: number | (() => MaybePromise<number>) | null,
625
626
  ) {
626
627
  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;
628
+ // `first`/`last` are compared against null rather than by truthiness, so `last: 0` reports the
629
+ // `pageInfo` of a backward page of zero rows rather than of a forward one.
630
+ const backward = args.first == null && args.last != null;
631
+ const hasNextPage = args.before ? true : args.last != null ? false : gotFullResults;
632
+ const hasPreviousPage = args.after ? true : backward ? gotFullResults : false;
629
633
  const nodes = gotFullResults ? results.slice(0, -1) : results;
630
634
 
631
635
  const connection = {
@@ -658,7 +662,7 @@ export function wrapConnectionResult<T extends {}>(
658
662
  },
659
663
  );
660
664
 
661
- if (args.last && !args.first) {
665
+ if (backward) {
662
666
  edges.reverse();
663
667
  }
664
668
 
@@ -671,8 +675,8 @@ export function wrapConnectionResult<T extends {}>(
671
675
 
672
676
  export async function resolveDrizzleCursorConnection<T extends {}>(
673
677
  tableName: string,
674
- info: GraphQLResolveInfo,
675
- typeName: string,
678
+ // The settled plan of the connection's rows; the builder handed to `resolve` merges into it.
679
+ plan: DrizzlePlan | undefined,
676
680
  config: PothosDrizzleSchemaConfig,
677
681
  options: Omit<DrizzleCursorConnectionQueryOptions, 'orderBy' | 'config' | 'table'> & {
678
682
  totalCount?: () => MaybePromise<number>;
@@ -697,10 +701,9 @@ export async function resolveDrizzleCursorConnection<T extends {}>(
697
701
  });
698
702
  formatter = getCursorFormatter(cursorFields, config);
699
703
 
700
- query = queryFromInfo({
701
- context: options.ctx,
702
- info,
703
- select: omitUndefinedKeys({
704
+ query = queryFromPlan(
705
+ plan,
706
+ omitUndefinedKeys({
704
707
  ...connectionQuery,
705
708
  extras: q.extras,
706
709
  columns: {
@@ -714,11 +717,7 @@ export async function resolveDrizzleCursorConnection<T extends {}>(
714
717
  }
715
718
  : q.where || connectionQuery.where,
716
719
  }) as never,
717
- paths: [['nodes'], ['edges', 'node']],
718
- typeName,
719
- config,
720
- // withUsageCheck: !!this.builder.options.prisma?.onUnusedQuery,
721
- });
720
+ );
722
721
 
723
722
  return query;
724
723
  });
@@ -743,10 +742,12 @@ export async function resolveDrizzleCursorConnection<T extends {}>(
743
742
  };
744
743
  }
745
744
 
745
+ const { limit } = query;
746
+
746
747
  return wrapConnectionResult(
747
748
  results,
748
749
  options.args,
749
- query.limit as number,
750
+ limit as number,
750
751
  formatter!,
751
752
  undefined,
752
753
  parent,