@pothos/plugin-drizzle 0.17.6 → 0.18.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 (50) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dts/connection-helpers.d.ts.map +1 -1
  3. package/dts/drizzle-field-builder.d.ts.map +1 -1
  4. package/dts/types.d.ts +6 -2
  5. package/dts/types.d.ts.map +1 -1
  6. package/dts/utils/config.d.ts +3 -0
  7. package/dts/utils/config.d.ts.map +1 -1
  8. package/dts/utils/cursors.d.ts +11 -7
  9. package/dts/utils/cursors.d.ts.map +1 -1
  10. package/dts/utils/map-query.d.ts.map +1 -1
  11. package/esm/connection-helpers.d.ts.map +1 -1
  12. package/esm/connection-helpers.js +5 -4
  13. package/esm/connection-helpers.js.map +1 -1
  14. package/esm/drizzle-field-builder.d.ts.map +1 -1
  15. package/esm/drizzle-field-builder.js +5 -4
  16. package/esm/drizzle-field-builder.js.map +1 -1
  17. package/esm/types.d.ts +6 -2
  18. package/esm/types.d.ts.map +1 -1
  19. package/esm/types.js +1 -0
  20. package/esm/types.js.map +1 -1
  21. package/esm/utils/config.d.ts +3 -0
  22. package/esm/utils/config.d.ts.map +1 -1
  23. package/esm/utils/config.js +95 -17
  24. package/esm/utils/config.js.map +1 -1
  25. package/esm/utils/cursors.d.ts +11 -7
  26. package/esm/utils/cursors.d.ts.map +1 -1
  27. package/esm/utils/cursors.js +194 -113
  28. package/esm/utils/cursors.js.map +1 -1
  29. package/esm/utils/map-query.d.ts.map +1 -1
  30. package/esm/utils/map-query.js +24 -6
  31. package/esm/utils/map-query.js.map +1 -1
  32. package/lib/connection-helpers.js +5 -4
  33. package/lib/connection-helpers.js.map +1 -1
  34. package/lib/drizzle-field-builder.js +5 -4
  35. package/lib/drizzle-field-builder.js.map +1 -1
  36. package/lib/types.js +4 -0
  37. package/lib/types.js.map +1 -1
  38. package/lib/utils/config.js +95 -17
  39. package/lib/utils/config.js.map +1 -1
  40. package/lib/utils/cursors.js +196 -112
  41. package/lib/utils/cursors.js.map +1 -1
  42. package/lib/utils/map-query.js +24 -6
  43. package/lib/utils/map-query.js.map +1 -1
  44. package/package.json +4 -4
  45. package/src/connection-helpers.ts +6 -4
  46. package/src/drizzle-field-builder.ts +7 -4
  47. package/src/types.ts +9 -4
  48. package/src/utils/config.ts +117 -20
  49. package/src/utils/cursors.ts +248 -87
  50. package/src/utils/map-query.ts +41 -15
@@ -10,6 +10,7 @@ import type { DrizzleRef } from './interface-ref.js';
10
10
  import type { QueryForDrizzleConnection } from './types.js';
11
11
  import { getSchemaConfig } from './utils/config.js';
12
12
  import {
13
+ type DrizzleCursorConnectionQueryOptions,
13
14
  drizzleCursorConnectionQuery,
14
15
  getCursorFormatter,
15
16
  wrapConnectionResult,
@@ -93,8 +94,8 @@ export function drizzleConnectionHelpers<
93
94
  ctx: Types['Context'],
94
95
  parent?: Parent,
95
96
  ) {
96
- const { select, cursorColumns } = getQueryArgs(args, ctx);
97
- const formatCursor = getCursorFormatter(cursorColumns, config);
97
+ const { select, cursorFields } = getQueryArgs(args, ctx);
98
+ const formatCursor = getCursorFormatter(cursorFields, config);
98
99
  return wrapConnectionResult(
99
100
  list,
100
101
  args,
@@ -122,7 +123,7 @@ export function drizzleConnectionHelpers<
122
123
  (typeof query === 'function' ? query(args, ctx) : query) ?? {};
123
124
  const table = config.relations[tableName];
124
125
 
125
- const { cursorColumns, columns, ...connectionQuery } = drizzleCursorConnectionQuery({
126
+ const { cursorFields, columns, ...connectionQuery } = drizzleCursorConnectionQuery({
126
127
  ctx,
127
128
  maxSize,
128
129
  defaultSize: typeof defaultSize === 'function' ? defaultSize(args, ctx) : defaultSize,
@@ -130,6 +131,7 @@ export function drizzleConnectionHelpers<
130
131
  orderBy:
131
132
  (typeof orderBy === 'function' ? orderBy(table) : orderBy) ??
132
133
  getSchemaConfig(builder).getPrimaryKey(tableName),
134
+ extras: (fieldQuery as { extras?: DrizzleCursorConnectionQueryOptions['extras'] }).extras,
133
135
  where,
134
136
  config,
135
137
  table,
@@ -145,7 +147,7 @@ export function drizzleConnectionHelpers<
145
147
  },
146
148
  limit: Math.abs(limit ?? connectionQuery.limit),
147
149
  },
148
- cursorColumns,
150
+ cursorFields,
149
151
  };
150
152
  };
151
153
 
@@ -51,6 +51,7 @@ import type {
51
51
  } from './types.js';
52
52
  import { getClient, getSchemaConfig } from './utils/config.js';
53
53
  import {
54
+ type DrizzleCursorConnectionQueryOptions,
54
55
  drizzleCursorConnectionQuery,
55
56
  getCursorFormatter,
56
57
  wrapConnectionResult,
@@ -236,9 +237,10 @@ export class DrizzleObjectFieldBuilder<
236
237
  orderBy?: unknown;
237
238
  where?: SQL;
238
239
  columns?: Record<string, boolean>;
240
+ extras?: DrizzleCursorConnectionQueryOptions['extras'];
239
241
  };
240
242
 
241
- const { cursorColumns, columns, ...connectionQuery } = drizzleCursorConnectionQuery({
243
+ const { cursorFields, columns, ...connectionQuery } = drizzleCursorConnectionQuery({
242
244
  ctx,
243
245
  maxSize,
244
246
  defaultSize,
@@ -246,6 +248,7 @@ export class DrizzleObjectFieldBuilder<
246
248
  orderBy:
247
249
  (typeof orderBy === 'function' ? orderBy(relatedTable.table) : orderBy) ??
248
250
  getSchemaConfig(this.builder).getPrimaryKey(relationField.targetTableName),
251
+ extras: fieldQuery.extras,
249
252
  where,
250
253
  config: schemaConfig,
251
254
  table: relatedTable,
@@ -261,7 +264,7 @@ export class DrizzleObjectFieldBuilder<
261
264
  },
262
265
  limit: Math.abs(limit ?? connectionQuery.limit),
263
266
  },
264
- cursorColumns,
267
+ cursorFields,
265
268
  };
266
269
  };
267
270
 
@@ -376,13 +379,13 @@ export class DrizzleObjectFieldBuilder<
376
379
  };
377
380
  }
378
381
 
379
- const { select, cursorColumns } = getQuery(args, context);
382
+ const { select, cursorFields } = getQuery(args, context);
380
383
 
381
384
  return wrapConnectionResult(
382
385
  parentRecord[name] as readonly {}[],
383
386
  args,
384
387
  select.limit,
385
- getCursorFormatter(cursorColumns, schemaConfig),
388
+ getCursorFormatter(cursorFields, schemaConfig),
386
389
  undefined,
387
390
  parent,
388
391
  countValue,
package/src/types.ts CHANGED
@@ -68,6 +68,9 @@ type GetTableConfigFn<TTable = Table> = (table: TTable) => {
68
68
  primaryKeys: {
69
69
  readonly columns: Column[];
70
70
  }[];
71
+ uniqueConstraints?: {
72
+ readonly columns: Column[];
73
+ }[];
71
74
  readonly columns: Column[];
72
75
  };
73
76
 
@@ -677,11 +680,12 @@ export type RelatedConnectionOptions<
677
680
  export type ConnectionOrderBy<T extends TableRelationalConfig> =
678
681
  | Column
679
682
  | Column[]
683
+ // keys name a column, or an extra the same query selects. `string & {}` keeps
684
+ // the column names in autocomplete while letting extra names through
680
685
  | {
681
- [K in T['table']['_'] extends { columns: infer Columns } ? keyof Columns : never]?:
682
- | 'asc'
683
- | 'desc'
684
- | undefined;
686
+ [K in
687
+ | (T['table']['_'] extends { columns: infer Columns } ? keyof Columns : never)
688
+ | (string & {})]?: 'asc' | 'desc' | undefined;
685
689
  };
686
690
 
687
691
  export type ShapeFromConnection<T> = T extends { shape: unknown } ? T['shape'] : never;
@@ -736,4 +740,5 @@ export interface DrizzleGraphQLInputExtensions {
736
740
  inputType: 'insert' | 'filters' | 'orderBy' | 'update';
737
741
  }
738
742
 
743
+ export { DrizzleInterfaceRef, type DrizzleRef } from './interface-ref.js';
739
744
  export { DrizzleObjectRef } from './object-ref.js';
@@ -5,7 +5,10 @@ import type { DrizzleClient } from '../types.js';
5
5
  export interface PothosDrizzleSchemaConfig {
6
6
  skipDeferredFragments: boolean;
7
7
  relations: AnyRelations;
8
+ findPrimaryKey: (tableName: string) => Column[] | null;
9
+ findTieBreaker: (tableName: string) => Column[] | null;
8
10
  getPrimaryKey: (tableName: string) => Column[];
11
+ getUniqueConstraints: (tableName: string) => Column[][];
9
12
  columnToTsName: (column: Column) => string;
10
13
  }
11
14
  const configCache = createContextCache(
@@ -27,6 +30,113 @@ const configCache = createContextCache(
27
30
  }
28
31
  });
29
32
 
33
+ const buildTableConfig = (tableName: string) => {
34
+ const table = relations[tableName].table as Table;
35
+ const tableConfig = builder.options.drizzle.getTableConfig(table);
36
+ const tableColumns = Object.values(getColumns(table));
37
+
38
+ // The pg dialect builds primaryKeys and uniqueConstraints from
39
+ // ExtraConfigColumns rather than the table's own columns, so the objects
40
+ // it hands back are not the ones columnNameMappings knows about, and do
41
+ // not carry notNull. Look each one back up by name.
42
+ const toTableColumn = (column: Column) =>
43
+ tableColumns.find((candidate) => candidate.name === column.name) ?? column;
44
+
45
+ return {
46
+ columns: tableConfig.columns.map(toTableColumn),
47
+ primaryKeys: tableConfig.primaryKeys.map((key) => key.columns.map(toTableColumn)),
48
+ uniqueConstraints: (tableConfig.uniqueConstraints ?? []).map((constraint) =>
49
+ constraint.columns.map(toTableColumn),
50
+ ),
51
+ };
52
+ };
53
+
54
+ const tableConfigs = new Map<string, ReturnType<typeof buildTableConfig>>();
55
+
56
+ // drizzle rebuilds this from the table's config callback on every call
57
+ const getTableConfig = (tableName: string) => {
58
+ let tableConfig = tableConfigs.get(tableName);
59
+
60
+ if (!tableConfig) {
61
+ tableConfig = buildTableConfig(tableName);
62
+ tableConfigs.set(tableName, tableConfig);
63
+ }
64
+
65
+ return tableConfig;
66
+ };
67
+
68
+ // Every set of columns the database guarantees is unique. Unique indexes
69
+ // are not included: missing one only costs a redundant order column, while
70
+ // wrongly reporting a set as unique would break pagination.
71
+ const getUniqueConstraints = (tableName: string) => {
72
+ const tableConfig = getTableConfig(tableName);
73
+
74
+ return [
75
+ ...tableConfig.columns
76
+ .filter((column) => column.primary || column.isUnique)
77
+ .map((column) => [column]),
78
+ ...tableConfig.primaryKeys,
79
+ ...tableConfig.uniqueConstraints,
80
+ ].filter((columns) => columns.length > 0);
81
+ };
82
+
83
+ // What a cursor can rely on to break ties. Primary key columns are
84
+ // non-nullable whatever the drizzle schema says, because SQL requires it,
85
+ // so a composite key declared without notNull() on each column still
86
+ // counts. A column that is only unique has to say so itself: comparing a
87
+ // cursor against null matches nothing, and rows sharing a null tie anyway.
88
+ const findTieBreaker = (tableName: string) => {
89
+ const tableConfig = getTableConfig(tableName);
90
+
91
+ const primaryKey = tableConfig.columns.find((column) => column.primary);
92
+
93
+ if (primaryKey) {
94
+ return [primaryKey];
95
+ }
96
+
97
+ const compositeKey = tableConfig.primaryKeys.find((columns) => columns.length > 0);
98
+
99
+ if (compositeKey) {
100
+ return compositeKey;
101
+ }
102
+
103
+ const uniqueColumn = tableConfig.columns.find((column) => column.isUnique && column.notNull);
104
+
105
+ if (uniqueColumn) {
106
+ return [uniqueColumn];
107
+ }
108
+
109
+ const uniqueConstraint = tableConfig.uniqueConstraints.find(
110
+ (columns) => columns.length > 0 && columns.every((column) => column.notNull),
111
+ );
112
+
113
+ return uniqueConstraint ?? null;
114
+ };
115
+
116
+ const findPrimaryKey = (tableName: string) => {
117
+ const tableConfig = getTableConfig(tableName);
118
+
119
+ const primaryKey = tableConfig.columns.find((column) => column.primary);
120
+
121
+ if (primaryKey) {
122
+ return [primaryKey];
123
+ }
124
+
125
+ const primaryKeys = tableConfig.primaryKeys.find((columns) => columns.length > 0);
126
+
127
+ if (primaryKeys) {
128
+ return primaryKeys;
129
+ }
130
+
131
+ const uniqueColumn = tableConfig.columns.find((column) => column.isUnique);
132
+
133
+ if (uniqueColumn) {
134
+ return [uniqueColumn];
135
+ }
136
+
137
+ return null;
138
+ };
139
+
30
140
  return {
31
141
  skipDeferredFragments: builder.options.drizzle.skipDeferredFragments ?? true,
32
142
  columnToTsName: (column) => {
@@ -38,30 +148,17 @@ const configCache = createContextCache(
38
148
 
39
149
  return tsName;
40
150
  },
151
+ findPrimaryKey,
152
+ findTieBreaker,
153
+ getUniqueConstraints,
41
154
  getPrimaryKey: (tableName) => {
42
- const tableConfig = builder.options.drizzle.getTableConfig(
43
- relations[tableName].table as Table,
44
- );
45
-
46
- const primaryKey = tableConfig.columns.find((column) => column.primary);
47
-
48
- if (primaryKey) {
49
- return [primaryKey];
50
- }
51
-
52
- const primaryKeys = tableConfig.primaryKeys.find((key) => key.columns.length > 0);
53
-
54
- if (primaryKeys) {
55
- return primaryKeys.columns;
56
- }
57
-
58
- const uniqueColumn = tableConfig.columns.find((column) => column.isUnique);
155
+ const primaryKey = findPrimaryKey(tableName);
59
156
 
60
- if (uniqueColumn) {
61
- return [uniqueColumn];
157
+ if (!primaryKey) {
158
+ throw new Error(`Could not find primary key for table ${tableName}`);
62
159
  }
63
160
 
64
- throw new Error(`Could not find primary key for table ${tableName}`);
161
+ return primaryKey;
65
162
  },
66
163
  relations,
67
164
  };