@constructive-io/graphql-codegen 2.24.0 → 2.26.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 (66) hide show
  1. package/README.md +403 -279
  2. package/cli/codegen/babel-ast.d.ts +7 -0
  3. package/cli/codegen/babel-ast.js +15 -0
  4. package/cli/codegen/barrel.js +43 -14
  5. package/cli/codegen/custom-mutations.js +4 -4
  6. package/cli/codegen/custom-queries.js +12 -22
  7. package/cli/codegen/gql-ast.js +22 -1
  8. package/cli/codegen/index.js +1 -0
  9. package/cli/codegen/mutations.d.ts +2 -0
  10. package/cli/codegen/mutations.js +26 -13
  11. package/cli/codegen/orm/client-generator.js +475 -136
  12. package/cli/codegen/orm/custom-ops-generator.js +8 -3
  13. package/cli/codegen/orm/input-types-generator.js +22 -0
  14. package/cli/codegen/orm/model-generator.js +18 -5
  15. package/cli/codegen/orm/select-types.d.ts +33 -0
  16. package/cli/codegen/queries.d.ts +1 -1
  17. package/cli/codegen/queries.js +112 -35
  18. package/cli/codegen/utils.d.ts +6 -0
  19. package/cli/codegen/utils.js +19 -0
  20. package/cli/commands/generate-orm.d.ts +14 -0
  21. package/cli/commands/generate-orm.js +160 -44
  22. package/cli/commands/generate.d.ts +22 -0
  23. package/cli/commands/generate.js +195 -55
  24. package/cli/commands/init.js +29 -9
  25. package/cli/index.js +133 -28
  26. package/cli/watch/orchestrator.d.ts +4 -0
  27. package/cli/watch/orchestrator.js +4 -0
  28. package/esm/cli/codegen/babel-ast.d.ts +7 -0
  29. package/esm/cli/codegen/babel-ast.js +14 -0
  30. package/esm/cli/codegen/barrel.js +44 -15
  31. package/esm/cli/codegen/custom-mutations.js +5 -5
  32. package/esm/cli/codegen/custom-queries.js +13 -23
  33. package/esm/cli/codegen/gql-ast.js +23 -2
  34. package/esm/cli/codegen/index.js +1 -0
  35. package/esm/cli/codegen/mutations.d.ts +2 -0
  36. package/esm/cli/codegen/mutations.js +27 -14
  37. package/esm/cli/codegen/orm/client-generator.js +475 -136
  38. package/esm/cli/codegen/orm/custom-ops-generator.js +8 -3
  39. package/esm/cli/codegen/orm/input-types-generator.js +22 -0
  40. package/esm/cli/codegen/orm/model-generator.js +18 -5
  41. package/esm/cli/codegen/orm/select-types.d.ts +33 -0
  42. package/esm/cli/codegen/queries.d.ts +1 -1
  43. package/esm/cli/codegen/queries.js +114 -37
  44. package/esm/cli/codegen/utils.d.ts +6 -0
  45. package/esm/cli/codegen/utils.js +18 -0
  46. package/esm/cli/commands/generate-orm.d.ts +14 -0
  47. package/esm/cli/commands/generate-orm.js +161 -45
  48. package/esm/cli/commands/generate.d.ts +22 -0
  49. package/esm/cli/commands/generate.js +195 -56
  50. package/esm/cli/commands/init.js +29 -9
  51. package/esm/cli/index.js +134 -29
  52. package/esm/cli/watch/orchestrator.d.ts +4 -0
  53. package/esm/cli/watch/orchestrator.js +5 -1
  54. package/esm/types/config.d.ts +39 -2
  55. package/esm/types/config.js +88 -4
  56. package/esm/types/index.d.ts +2 -2
  57. package/esm/types/index.js +1 -1
  58. package/package.json +10 -7
  59. package/types/config.d.ts +39 -2
  60. package/types/config.js +91 -4
  61. package/types/index.d.ts +2 -2
  62. package/types/index.js +2 -1
  63. package/cli/codegen/orm/query-builder.d.ts +0 -161
  64. package/cli/codegen/orm/query-builder.js +0 -366
  65. package/esm/cli/codegen/orm/query-builder.d.ts +0 -161
  66. package/esm/cli/codegen/orm/query-builder.js +0 -353
package/README.md CHANGED
@@ -117,6 +117,7 @@ Generate React Query hooks from a PostGraphile endpoint.
117
117
  ```bash
118
118
  Options:
119
119
  -e, --endpoint <url> GraphQL endpoint URL (overrides config)
120
+ -t, --target <name> Target name in config file
120
121
  -o, --output <dir> Output directory (default: ./generated/graphql)
121
122
  -c, --config <path> Path to config file
122
123
  -a, --authorization <token> Authorization header value
@@ -132,6 +133,7 @@ Generate Prisma-like ORM client from a PostGraphile endpoint.
132
133
  ```bash
133
134
  Options:
134
135
  -e, --endpoint <url> GraphQL endpoint URL
136
+ -t, --target <name> Target name in config file
135
137
  -o, --output <dir> Output directory (default: ./generated/orm)
136
138
  -c, --config <path> Path to config file
137
139
  -a, --authorization <token> Authorization header value
@@ -164,9 +166,10 @@ Options:
164
166
  ## Configuration
165
167
 
166
168
  ```typescript
167
- interface GraphQLSDKConfig {
168
- // Required
169
- endpoint: string;
169
+ interface GraphQLSDKConfigTarget {
170
+ // Required (choose one)
171
+ endpoint?: string;
172
+ schema?: string;
170
173
 
171
174
  // Output
172
175
  output?: string; // default: './generated/graphql'
@@ -194,25 +197,62 @@ interface GraphQLSDKConfig {
194
197
 
195
198
  // Code generation options
196
199
  codegen?: {
197
- maxFieldDepth?: number; // default: 2
198
- skipQueryField?: boolean; // default: true
200
+ maxFieldDepth?: number; // default: 2
201
+ skipQueryField?: boolean; // default: true
199
202
  };
200
203
 
201
204
  // ORM-specific config
202
205
  orm?: {
203
- output?: string; // default: './generated/orm'
204
- useSharedTypes?: boolean; // default: true
206
+ output?: string; // default: './generated/orm'
207
+ useSharedTypes?: boolean; // default: true
205
208
  };
206
209
  }
210
+
211
+ interface GraphQLSDKMultiConfig {
212
+ defaults?: GraphQLSDKConfigTarget;
213
+ targets: Record<string, GraphQLSDKConfigTarget>;
214
+ }
215
+
216
+ type GraphQLSDKConfig = GraphQLSDKConfigTarget | GraphQLSDKMultiConfig;
217
+ ```
218
+
219
+ ### Multi-target Configuration
220
+
221
+ Configure multiple schema sources and outputs in one file:
222
+
223
+ ```typescript
224
+ export default defineConfig({
225
+ defaults: {
226
+ headers: { Authorization: 'Bearer <token>' },
227
+ },
228
+ targets: {
229
+ public: {
230
+ endpoint: 'https://api.example.com/graphql',
231
+ output: './generated/public',
232
+ },
233
+ admin: {
234
+ schema: './admin.schema.graphql',
235
+ output: './generated/admin',
236
+ },
237
+ },
238
+ });
207
239
  ```
208
240
 
241
+ CLI behavior:
242
+
243
+ - `graphql-codegen generate` runs all targets
244
+ - `graphql-codegen generate --target admin` runs a single target
245
+ - `--output` requires `--target` when multiple targets exist
246
+
209
247
  ### Glob Patterns
210
248
 
211
249
  Filter patterns support wildcards:
250
+
212
251
  - `*` - matches any string
213
252
  - `?` - matches single character
214
253
 
215
254
  Examples:
255
+
216
256
  ```typescript
217
257
  {
218
258
  tables: {
@@ -308,30 +348,25 @@ Fetches multiple records with pagination, filtering, and ordering:
308
348
  import { useCarsQuery } from './generated/hooks';
309
349
 
310
350
  function CarList() {
311
- const {
312
- data,
313
- isLoading,
314
- isError,
315
- error,
316
- refetch,
317
- isFetching,
318
- } = useCarsQuery({
319
- // Pagination
320
- first: 10, // First N records
321
- // last: 10, // Last N records
322
- // after: 'cursor', // Cursor-based pagination
323
- // before: 'cursor',
324
- // offset: 20, // Offset pagination
325
-
326
- // Filtering
327
- filter: {
328
- brand: { equalTo: 'Tesla' },
329
- price: { greaterThan: 50000 },
330
- },
331
-
332
- // Ordering
333
- orderBy: ['CREATED_AT_DESC', 'NAME_ASC'],
334
- });
351
+ const { data, isLoading, isError, error, refetch, isFetching } = useCarsQuery(
352
+ {
353
+ // Pagination
354
+ first: 10, // First N records
355
+ // last: 10, // Last N records
356
+ // after: 'cursor', // Cursor-based pagination
357
+ // before: 'cursor',
358
+ // offset: 20, // Offset pagination
359
+
360
+ // Filtering
361
+ filter: {
362
+ brand: { equalTo: 'Tesla' },
363
+ price: { greaterThan: 50000 },
364
+ },
365
+
366
+ // Ordering
367
+ orderBy: ['CREATED_AT_DESC', 'NAME_ASC'],
368
+ }
369
+ );
335
370
 
336
371
  if (isLoading) return <div>Loading...</div>;
337
372
  if (isError) return <div>Error: {error.message}</div>;
@@ -340,11 +375,13 @@ function CarList() {
340
375
  <div>
341
376
  <p>Total: {data?.cars.totalCount}</p>
342
377
  <ul>
343
- {data?.cars.nodes.map(car => (
344
- <li key={car.id}>{car.brand} - ${car.price}</li>
378
+ {data?.cars.nodes.map((car) => (
379
+ <li key={car.id}>
380
+ {car.brand} - ${car.price}
381
+ </li>
345
382
  ))}
346
383
  </ul>
347
-
384
+
348
385
  {/* Pagination info */}
349
386
  {data?.cars.pageInfo.hasNextPage && (
350
387
  <button onClick={() => refetch()}>Load More</button>
@@ -411,7 +448,12 @@ function CreateCarForm() {
411
448
  };
412
449
 
413
450
  return (
414
- <form onSubmit={(e) => { e.preventDefault(); handleSubmit({ brand: 'Tesla', price: 80000 }); }}>
451
+ <form
452
+ onSubmit={(e) => {
453
+ e.preventDefault();
454
+ handleSubmit({ brand: 'Tesla', price: 80000 });
455
+ }}
456
+ >
415
457
  {/* form fields */}
416
458
  <button type="submit" disabled={createCar.isPending}>
417
459
  {createCar.isPending ? 'Creating...' : 'Create Car'}
@@ -427,7 +469,13 @@ function CreateCarForm() {
427
469
  ```tsx
428
470
  import { useUpdateCarMutation } from './generated/hooks';
429
471
 
430
- function EditCarForm({ carId, currentBrand }: { carId: string; currentBrand: string }) {
472
+ function EditCarForm({
473
+ carId,
474
+ currentBrand,
475
+ }: {
476
+ carId: string;
477
+ currentBrand: string;
478
+ }) {
431
479
  const updateCar = useUpdateCarMutation({
432
480
  onSuccess: (data) => {
433
481
  console.log('Updated car:', data.updateCar.car.brand);
@@ -446,7 +494,7 @@ function EditCarForm({ carId, currentBrand }: { carId: string; currentBrand: str
446
494
  };
447
495
 
448
496
  return (
449
- <button
497
+ <button
450
498
  onClick={() => handleUpdate('Updated Brand')}
451
499
  disabled={updateCar.isPending}
452
500
  >
@@ -470,7 +518,7 @@ function DeleteCarButton({ carId }: { carId: string }) {
470
518
  });
471
519
 
472
520
  return (
473
- <button
521
+ <button
474
522
  onClick={() => deleteCar.mutate({ input: { id: carId } })}
475
523
  disabled={deleteCar.isPending}
476
524
  >
@@ -517,9 +565,9 @@ function NodeViewer({ nodeId }: { nodeId: string }) {
517
565
  Custom mutations (like `login`, `register`, `logout`) get dedicated hooks:
518
566
 
519
567
  ```tsx
520
- import {
521
- useLoginMutation,
522
- useRegisterMutation,
568
+ import {
569
+ useLoginMutation,
570
+ useRegisterMutation,
523
571
  useLogoutMutation,
524
572
  useForgotPasswordMutation,
525
573
  } from './generated/hooks';
@@ -550,7 +598,12 @@ function LoginForm() {
550
598
  };
551
599
 
552
600
  return (
553
- <form onSubmit={(e) => { e.preventDefault(); handleLogin('user@example.com', 'password'); }}>
601
+ <form
602
+ onSubmit={(e) => {
603
+ e.preventDefault();
604
+ handleLogin('user@example.com', 'password');
605
+ }}
606
+ >
554
607
  {/* email and password inputs */}
555
608
  <button disabled={login.isPending}>
556
609
  {login.isPending ? 'Logging in...' : 'Login'}
@@ -567,7 +620,11 @@ function RegisterForm() {
567
620
  },
568
621
  });
569
622
 
570
- const handleRegister = (data: { email: string; password: string; username: string }) => {
623
+ const handleRegister = (data: {
624
+ email: string;
625
+ password: string;
626
+ username: string;
627
+ }) => {
571
628
  register.mutate({
572
629
  input: {
573
630
  email: data.email,
@@ -578,7 +635,15 @@ function RegisterForm() {
578
635
  };
579
636
 
580
637
  return (
581
- <button onClick={() => handleRegister({ email: 'new@example.com', password: 'secret', username: 'newuser' })}>
638
+ <button
639
+ onClick={() =>
640
+ handleRegister({
641
+ email: 'new@example.com',
642
+ password: 'secret',
643
+ username: 'newuser',
644
+ })
645
+ }
646
+ >
582
647
  Register
583
648
  </button>
584
649
  );
@@ -593,11 +658,7 @@ function LogoutButton() {
593
658
  },
594
659
  });
595
660
 
596
- return (
597
- <button onClick={() => logout.mutate({ input: {} })}>
598
- Logout
599
- </button>
600
- );
661
+ return <button onClick={() => logout.mutate({ input: {} })}>Logout</button>;
601
662
  }
602
663
 
603
664
  // Forgot Password
@@ -609,7 +670,11 @@ function ForgotPasswordForm() {
609
670
  });
610
671
 
611
672
  return (
612
- <button onClick={() => forgotPassword.mutate({ input: { email: 'user@example.com' } })}>
673
+ <button
674
+ onClick={() =>
675
+ forgotPassword.mutate({ input: { email: 'user@example.com' } })
676
+ }
677
+ >
613
678
  Reset Password
614
679
  </button>
615
680
  );
@@ -629,10 +694,10 @@ useCarsQuery({
629
694
  notEqualTo: 'Ford',
630
695
  in: ['Tesla', 'BMW', 'Mercedes'],
631
696
  notIn: ['Unknown'],
632
- contains: 'es', // LIKE '%es%'
633
- startsWith: 'Tes', // LIKE 'Tes%'
634
- endsWith: 'la', // LIKE '%la'
635
- includesInsensitive: 'TESLA', // Case-insensitive
697
+ contains: 'es', // LIKE '%es%'
698
+ startsWith: 'Tes', // LIKE 'Tes%'
699
+ endsWith: 'la', // LIKE '%la'
700
+ includesInsensitive: 'TESLA', // Case-insensitive
636
701
  },
637
702
  },
638
703
  });
@@ -671,7 +736,7 @@ useOrdersQuery({
671
736
  // Null checks
672
737
  useUsersQuery({
673
738
  filter: {
674
- deletedAt: { isNull: true }, // Only non-deleted
739
+ deletedAt: { isNull: true }, // Only non-deleted
675
740
  },
676
741
  });
677
742
 
@@ -687,10 +752,7 @@ useUsersQuery({
687
752
  useUsersQuery({
688
753
  filter: {
689
754
  // OR
690
- or: [
691
- { role: { equalTo: 'ADMIN' } },
692
- { role: { equalTo: 'MODERATOR' } },
693
- ],
755
+ or: [{ role: { equalTo: 'ADMIN' } }, { role: { equalTo: 'MODERATOR' } }],
694
756
  },
695
757
  });
696
758
 
@@ -746,12 +808,12 @@ useCarsQuery({ first: 10 });
746
808
  useCarsQuery({ last: 10 });
747
809
 
748
810
  // Offset pagination
749
- useCarsQuery({ first: 10, offset: 20 }); // Skip 20, take 10
811
+ useCarsQuery({ first: 10, offset: 20 }); // Skip 20, take 10
750
812
 
751
813
  // Cursor-based pagination
752
814
  function PaginatedList() {
753
815
  const [cursor, setCursor] = useState<string | null>(null);
754
-
816
+
755
817
  const { data } = useCarsQuery({
756
818
  first: 10,
757
819
  after: cursor,
@@ -759,8 +821,10 @@ function PaginatedList() {
759
821
 
760
822
  return (
761
823
  <div>
762
- {data?.cars.nodes.map(car => <div key={car.id}>{car.brand}</div>)}
763
-
824
+ {data?.cars.nodes.map((car) => (
825
+ <div key={car.id}>{car.brand}</div>
826
+ ))}
827
+
764
828
  {data?.cars.pageInfo.hasNextPage && (
765
829
  <button onClick={() => setCursor(data.cars.pageInfo.endCursor)}>
766
830
  Load More
@@ -786,18 +850,18 @@ All hooks accept standard React Query options:
786
850
  ```tsx
787
851
  // Query hooks
788
852
  useCarsQuery(
789
- { first: 10 }, // Variables
853
+ { first: 10 }, // Variables
790
854
  {
791
855
  // React Query options
792
- enabled: isAuthenticated, // Conditional fetching
793
- refetchInterval: 30000, // Poll every 30s
794
- refetchOnWindowFocus: true, // Refetch on tab focus
795
- staleTime: 5 * 60 * 1000, // Consider fresh for 5 min
796
- gcTime: 30 * 60 * 1000, // Keep in cache for 30 min
797
- retry: 3, // Retry failed requests
856
+ enabled: isAuthenticated, // Conditional fetching
857
+ refetchInterval: 30000, // Poll every 30s
858
+ refetchOnWindowFocus: true, // Refetch on tab focus
859
+ staleTime: 5 * 60 * 1000, // Consider fresh for 5 min
860
+ gcTime: 30 * 60 * 1000, // Keep in cache for 30 min
861
+ retry: 3, // Retry failed requests
798
862
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
799
- placeholderData: previousData, // Show previous data while loading
800
- select: (data) => data.cars.nodes, // Transform data
863
+ placeholderData: previousData, // Show previous data while loading
864
+ select: (data) => data.cars.nodes, // Transform data
801
865
  }
802
866
  );
803
867
 
@@ -858,11 +922,11 @@ The codegen generates a centralized query key factory following the [lukemorales
858
922
 
859
923
  #### Generated Files
860
924
 
861
- | File | Purpose |
862
- |------|---------|
863
- | `query-keys.ts` | Query key factories for all entities |
925
+ | File | Purpose |
926
+ | ------------------ | ------------------------------------------------------- |
927
+ | `query-keys.ts` | Query key factories for all entities |
864
928
  | `mutation-keys.ts` | Mutation key factories for tracking in-flight mutations |
865
- | `invalidation.ts` | Type-safe cache invalidation helpers |
929
+ | `invalidation.ts` | Type-safe cache invalidation helpers |
866
930
 
867
931
  #### Using Query Keys
868
932
 
@@ -871,11 +935,11 @@ import { userKeys, invalidate } from './generated/hooks';
871
935
  import { useQueryClient } from '@tanstack/react-query';
872
936
 
873
937
  // Query key structure
874
- userKeys.all // ['user']
875
- userKeys.lists() // ['user', 'list']
876
- userKeys.list({ first: 10 }) // ['user', 'list', { first: 10 }]
877
- userKeys.details() // ['user', 'detail']
878
- userKeys.detail('user-123') // ['user', 'detail', 'user-123']
938
+ userKeys.all; // ['user']
939
+ userKeys.lists(); // ['user', 'list']
940
+ userKeys.list({ first: 10 }); // ['user', 'list', { first: 10 }]
941
+ userKeys.details(); // ['user', 'detail']
942
+ userKeys.detail('user-123'); // ['user', 'detail', 'user-123']
879
943
 
880
944
  // Granular cache invalidation
881
945
  const queryClient = useQueryClient();
@@ -920,7 +984,7 @@ function UserList() {
920
984
 
921
985
  // Check if a specific user is being deleted
922
986
  const isDeleting = useIsMutating({
923
- mutationKey: userMutationKeys.delete(userId)
987
+ mutationKey: userMutationKeys.delete(userId),
924
988
  });
925
989
 
926
990
  return (
@@ -950,7 +1014,7 @@ const createUser = useCreateUserMutation({
950
1014
  ...old,
951
1015
  users: {
952
1016
  ...old.users,
953
- nodes: [...old.users.nodes, { id: 'temp', ...newUser.input.user }]
1017
+ nodes: [...old.users.nodes, { id: 'temp', ...newUser.input.user }],
954
1018
  },
955
1019
  }));
956
1020
 
@@ -1032,7 +1096,7 @@ import type {
1032
1096
  User,
1033
1097
  Product,
1034
1098
  Order,
1035
-
1099
+
1036
1100
  // Filter types
1037
1101
  CarFilter,
1038
1102
  UserFilter,
@@ -1040,17 +1104,17 @@ import type {
1040
1104
  IntFilter,
1041
1105
  UUIDFilter,
1042
1106
  DatetimeFilter,
1043
-
1107
+
1044
1108
  // OrderBy types
1045
1109
  CarsOrderBy,
1046
1110
  UsersOrderBy,
1047
-
1111
+
1048
1112
  // Input types
1049
1113
  CreateCarInput,
1050
1114
  UpdateCarInput,
1051
1115
  CarPatch,
1052
1116
  LoginInput,
1053
-
1117
+
1054
1118
  // Payload types
1055
1119
  LoginPayload,
1056
1120
  CreateCarPayload,
@@ -1131,7 +1195,7 @@ type UseQueryResult<TData> = {
1131
1195
  type UseMutationResult<TData, TVariables> = {
1132
1196
  data: TData | undefined;
1133
1197
  error: Error | null;
1134
- isLoading: boolean; // deprecated, use isPending
1198
+ isLoading: boolean; // deprecated, use isPending
1135
1199
  isPending: boolean;
1136
1200
  isError: boolean;
1137
1201
  isSuccess: boolean;
@@ -1196,10 +1260,12 @@ const db = createClient({
1196
1260
  });
1197
1261
 
1198
1262
  // Query users
1199
- const result = await db.user.findMany({
1200
- select: { id: true, username: true, email: true },
1201
- first: 20,
1202
- }).execute();
1263
+ const result = await db.user
1264
+ .findMany({
1265
+ select: { id: true, username: true, email: true },
1266
+ first: 20,
1267
+ })
1268
+ .execute();
1203
1269
 
1204
1270
  if (result.ok) {
1205
1271
  console.log(result.data.users.nodes);
@@ -1208,28 +1274,36 @@ if (result.ok) {
1208
1274
  }
1209
1275
 
1210
1276
  // Find first matching user
1211
- const user = await db.user.findFirst({
1212
- select: { id: true, username: true },
1213
- where: { username: { equalTo: 'john' } },
1214
- }).execute();
1277
+ const user = await db.user
1278
+ .findFirst({
1279
+ select: { id: true, username: true },
1280
+ where: { username: { equalTo: 'john' } },
1281
+ })
1282
+ .execute();
1215
1283
 
1216
1284
  // Create a user
1217
- const newUser = await db.user.create({
1218
- data: { username: 'john', email: 'john@example.com' },
1219
- select: { id: true, username: true },
1220
- }).execute();
1285
+ const newUser = await db.user
1286
+ .create({
1287
+ data: { username: 'john', email: 'john@example.com' },
1288
+ select: { id: true, username: true },
1289
+ })
1290
+ .execute();
1221
1291
 
1222
1292
  // Update a user
1223
- const updated = await db.user.update({
1224
- where: { id: 'user-id' },
1225
- data: { displayName: 'John Doe' },
1226
- select: { id: true, displayName: true },
1227
- }).execute();
1293
+ const updated = await db.user
1294
+ .update({
1295
+ where: { id: 'user-id' },
1296
+ data: { displayName: 'John Doe' },
1297
+ select: { id: true, displayName: true },
1298
+ })
1299
+ .execute();
1228
1300
 
1229
1301
  // Delete a user
1230
- const deleted = await db.user.delete({
1231
- where: { id: 'user-id' },
1232
- }).execute();
1302
+ const deleted = await db.user
1303
+ .delete({
1304
+ where: { id: 'user-id' },
1305
+ })
1306
+ .execute();
1233
1307
  ```
1234
1308
 
1235
1309
  ### Select & Type Inference
@@ -1238,9 +1312,11 @@ The ORM uses **const generics** to infer return types based on your select claus
1238
1312
 
1239
1313
  ```typescript
1240
1314
  // Select specific fields - return type is narrowed
1241
- const users = await db.user.findMany({
1242
- select: { id: true, username: true } // Only id and username
1243
- }).unwrap();
1315
+ const users = await db.user
1316
+ .findMany({
1317
+ select: { id: true, username: true }, // Only id and username
1318
+ })
1319
+ .unwrap();
1244
1320
 
1245
1321
  // TypeScript knows the exact shape:
1246
1322
  // users.users.nodes[0] is { id: string; username: string | null }
@@ -1261,16 +1337,18 @@ Relations are fully typed in Select types. The ORM supports all PostGraphile rel
1261
1337
 
1262
1338
  ```typescript
1263
1339
  // Order.customer is a belongsTo relation to User
1264
- const orders = await db.order.findMany({
1265
- select: {
1266
- id: true,
1267
- orderNumber: true,
1268
- // Nested select for belongsTo relation
1269
- customer: {
1270
- select: { id: true, username: true, displayName: true }
1271
- }
1272
- }
1273
- }).unwrap();
1340
+ const orders = await db.order
1341
+ .findMany({
1342
+ select: {
1343
+ id: true,
1344
+ orderNumber: true,
1345
+ // Nested select for belongsTo relation
1346
+ customer: {
1347
+ select: { id: true, username: true, displayName: true },
1348
+ },
1349
+ },
1350
+ })
1351
+ .unwrap();
1274
1352
 
1275
1353
  // TypeScript knows:
1276
1354
  // orders.orders.nodes[0].customer is { id: string; username: string | null; displayName: string | null }
@@ -1280,18 +1358,20 @@ const orders = await db.order.findMany({
1280
1358
 
1281
1359
  ```typescript
1282
1360
  // Order.orderItems is a hasMany relation to OrderItem
1283
- const orders = await db.order.findMany({
1284
- select: {
1285
- id: true,
1286
- // HasMany with pagination and filtering
1287
- orderItems: {
1288
- select: { id: true, quantity: true, price: true },
1289
- first: 10, // Pagination
1290
- filter: { quantity: { greaterThan: 0 } }, // Filtering
1291
- orderBy: ['QUANTITY_DESC'] // Ordering
1292
- }
1293
- }
1294
- }).unwrap();
1361
+ const orders = await db.order
1362
+ .findMany({
1363
+ select: {
1364
+ id: true,
1365
+ // HasMany with pagination and filtering
1366
+ orderItems: {
1367
+ select: { id: true, quantity: true, price: true },
1368
+ first: 10, // Pagination
1369
+ filter: { quantity: { greaterThan: 0 } }, // Filtering
1370
+ orderBy: ['QUANTITY_DESC'], // Ordering
1371
+ },
1372
+ },
1373
+ })
1374
+ .unwrap();
1295
1375
 
1296
1376
  // orders.orders.nodes[0].orderItems is a connection:
1297
1377
  // { nodes: Array<{ id: string; quantity: number | null; price: number | null }>, totalCount: number, pageInfo: PageInfo }
@@ -1301,49 +1381,53 @@ const orders = await db.order.findMany({
1301
1381
 
1302
1382
  ```typescript
1303
1383
  // Order.productsByOrderItemOrderIdAndProductId is a manyToMany through OrderItem
1304
- const orders = await db.order.findMany({
1305
- select: {
1306
- id: true,
1307
- productsByOrderItemOrderIdAndProductId: {
1308
- select: { id: true, name: true, price: true },
1309
- first: 5
1310
- }
1311
- }
1312
- }).unwrap();
1384
+ const orders = await db.order
1385
+ .findMany({
1386
+ select: {
1387
+ id: true,
1388
+ productsByOrderItemOrderIdAndProductId: {
1389
+ select: { id: true, name: true, price: true },
1390
+ first: 5,
1391
+ },
1392
+ },
1393
+ })
1394
+ .unwrap();
1313
1395
  ```
1314
1396
 
1315
1397
  #### Deeply Nested Relations
1316
1398
 
1317
1399
  ```typescript
1318
1400
  // Multiple levels of nesting
1319
- const products = await db.product.findMany({
1320
- select: {
1321
- id: true,
1322
- name: true,
1323
- // BelongsTo: Product -> User (seller)
1324
- seller: {
1325
- select: {
1326
- id: true,
1327
- username: true,
1328
- // Even deeper nesting if needed
1329
- }
1330
- },
1331
- // BelongsTo: Product -> Category
1332
- category: {
1333
- select: { id: true, name: true }
1334
- },
1335
- // HasMany: Product -> Review
1336
- reviews: {
1337
- select: {
1338
- id: true,
1339
- rating: true,
1340
- comment: true
1401
+ const products = await db.product
1402
+ .findMany({
1403
+ select: {
1404
+ id: true,
1405
+ name: true,
1406
+ // BelongsTo: Product -> User (seller)
1407
+ seller: {
1408
+ select: {
1409
+ id: true,
1410
+ username: true,
1411
+ // Even deeper nesting if needed
1412
+ },
1341
1413
  },
1342
- first: 5,
1343
- orderBy: ['CREATED_AT_DESC']
1344
- }
1345
- }
1346
- }).unwrap();
1414
+ // BelongsTo: Product -> Category
1415
+ category: {
1416
+ select: { id: true, name: true },
1417
+ },
1418
+ // HasMany: Product -> Review
1419
+ reviews: {
1420
+ select: {
1421
+ id: true,
1422
+ rating: true,
1423
+ comment: true,
1424
+ },
1425
+ first: 5,
1426
+ orderBy: ['CREATED_AT_DESC'],
1427
+ },
1428
+ },
1429
+ })
1430
+ .unwrap();
1347
1431
  ```
1348
1432
 
1349
1433
  ### Filtering & Ordering
@@ -1462,13 +1546,15 @@ where: {
1462
1546
  #### Ordering
1463
1547
 
1464
1548
  ```typescript
1465
- const users = await db.user.findMany({
1466
- select: { id: true, username: true, createdAt: true },
1467
- orderBy: [
1468
- 'CREATED_AT_DESC', // Newest first
1469
- 'USERNAME_ASC', // Then alphabetical
1470
- ]
1471
- }).unwrap();
1549
+ const users = await db.user
1550
+ .findMany({
1551
+ select: { id: true, username: true, createdAt: true },
1552
+ orderBy: [
1553
+ 'CREATED_AT_DESC', // Newest first
1554
+ 'USERNAME_ASC', // Then alphabetical
1555
+ ],
1556
+ })
1557
+ .unwrap();
1472
1558
 
1473
1559
  // Available OrderBy values (generated per entity):
1474
1560
  // - PRIMARY_KEY_ASC / PRIMARY_KEY_DESC
@@ -1482,37 +1568,47 @@ The ORM supports cursor-based and offset pagination:
1482
1568
 
1483
1569
  ```typescript
1484
1570
  // First N records
1485
- const first10 = await db.user.findMany({
1486
- select: { id: true },
1487
- first: 10
1488
- }).unwrap();
1571
+ const first10 = await db.user
1572
+ .findMany({
1573
+ select: { id: true },
1574
+ first: 10,
1575
+ })
1576
+ .unwrap();
1489
1577
 
1490
1578
  // Last N records
1491
- const last10 = await db.user.findMany({
1492
- select: { id: true },
1493
- last: 10
1494
- }).unwrap();
1579
+ const last10 = await db.user
1580
+ .findMany({
1581
+ select: { id: true },
1582
+ last: 10,
1583
+ })
1584
+ .unwrap();
1495
1585
 
1496
1586
  // Cursor-based pagination (after/before)
1497
- const page1 = await db.user.findMany({
1498
- select: { id: true },
1499
- first: 10
1500
- }).unwrap();
1587
+ const page1 = await db.user
1588
+ .findMany({
1589
+ select: { id: true },
1590
+ first: 10,
1591
+ })
1592
+ .unwrap();
1501
1593
 
1502
1594
  const endCursor = page1.users.pageInfo.endCursor;
1503
1595
 
1504
- const page2 = await db.user.findMany({
1505
- select: { id: true },
1506
- first: 10,
1507
- after: endCursor // Get records after this cursor
1508
- }).unwrap();
1596
+ const page2 = await db.user
1597
+ .findMany({
1598
+ select: { id: true },
1599
+ first: 10,
1600
+ after: endCursor, // Get records after this cursor
1601
+ })
1602
+ .unwrap();
1509
1603
 
1510
1604
  // Offset pagination
1511
- const page3 = await db.user.findMany({
1512
- select: { id: true },
1513
- first: 10,
1514
- offset: 20 // Skip first 20 records
1515
- }).unwrap();
1605
+ const page3 = await db.user
1606
+ .findMany({
1607
+ select: { id: true },
1608
+ first: 10,
1609
+ offset: 20, // Skip first 20 records
1610
+ })
1611
+ .unwrap();
1516
1612
 
1517
1613
  // PageInfo structure
1518
1614
  // {
@@ -1523,7 +1619,7 @@ const page3 = await db.user.findMany({
1523
1619
  // }
1524
1620
 
1525
1621
  // Total count is always included
1526
- console.log(page1.users.totalCount); // Total matching records
1622
+ console.log(page1.users.totalCount); // Total matching records
1527
1623
  ```
1528
1624
 
1529
1625
  ### Error Handling
@@ -1533,9 +1629,11 @@ The ORM provides multiple ways to handle errors:
1533
1629
  #### Discriminated Union (Recommended)
1534
1630
 
1535
1631
  ```typescript
1536
- const result = await db.user.findMany({
1537
- select: { id: true }
1538
- }).execute();
1632
+ const result = await db.user
1633
+ .findMany({
1634
+ select: { id: true },
1635
+ })
1636
+ .execute();
1539
1637
 
1540
1638
  if (result.ok) {
1541
1639
  // TypeScript knows result.data is non-null
@@ -1555,10 +1653,12 @@ import { GraphQLRequestError } from './generated/orm';
1555
1653
 
1556
1654
  try {
1557
1655
  // Throws GraphQLRequestError if query fails
1558
- const data = await db.user.findMany({
1559
- select: { id: true }
1560
- }).unwrap();
1561
-
1656
+ const data = await db.user
1657
+ .findMany({
1658
+ select: { id: true },
1659
+ })
1660
+ .unwrap();
1661
+
1562
1662
  console.log(data.users.nodes);
1563
1663
  } catch (error) {
1564
1664
  if (error instanceof GraphQLRequestError) {
@@ -1572,15 +1672,17 @@ try {
1572
1672
 
1573
1673
  ```typescript
1574
1674
  // Returns default value if query fails (no throwing)
1575
- const data = await db.user.findMany({
1576
- select: { id: true }
1577
- }).unwrapOr({
1578
- users: {
1579
- nodes: [],
1580
- totalCount: 0,
1581
- pageInfo: { hasNextPage: false, hasPreviousPage: false }
1582
- }
1583
- });
1675
+ const data = await db.user
1676
+ .findMany({
1677
+ select: { id: true },
1678
+ })
1679
+ .unwrapOr({
1680
+ users: {
1681
+ nodes: [],
1682
+ totalCount: 0,
1683
+ pageInfo: { hasNextPage: false, hasPreviousPage: false },
1684
+ },
1685
+ });
1584
1686
 
1585
1687
  // Always returns data (either real or default)
1586
1688
  console.log(data.users.nodes);
@@ -1590,21 +1692,23 @@ console.log(data.users.nodes);
1590
1692
 
1591
1693
  ```typescript
1592
1694
  // Call a function to handle errors and return fallback
1593
- const data = await db.user.findMany({
1594
- select: { id: true }
1595
- }).unwrapOrElse((errors) => {
1596
- // Log errors, send to monitoring, etc.
1597
- console.error('Query failed:', errors.map(e => e.message).join(', '));
1598
-
1599
- // Return fallback data
1600
- return {
1601
- users: {
1602
- nodes: [],
1603
- totalCount: 0,
1604
- pageInfo: { hasNextPage: false, hasPreviousPage: false }
1605
- }
1606
- };
1607
- });
1695
+ const data = await db.user
1696
+ .findMany({
1697
+ select: { id: true },
1698
+ })
1699
+ .unwrapOrElse((errors) => {
1700
+ // Log errors, send to monitoring, etc.
1701
+ console.error('Query failed:', errors.map((e) => e.message).join(', '));
1702
+
1703
+ // Return fallback data
1704
+ return {
1705
+ users: {
1706
+ nodes: [],
1707
+ totalCount: 0,
1708
+ pageInfo: { hasNextPage: false, hasPreviousPage: false },
1709
+ },
1710
+ };
1711
+ });
1608
1712
  ```
1609
1713
 
1610
1714
  #### Error Types
@@ -1619,7 +1723,7 @@ interface GraphQLError {
1619
1723
 
1620
1724
  class GraphQLRequestError extends Error {
1621
1725
  readonly errors: GraphQLError[];
1622
- readonly data: unknown; // Partial data if available
1726
+ readonly data: unknown; // Partial data if available
1623
1727
  }
1624
1728
 
1625
1729
  type QueryResult<T> =
@@ -1635,57 +1739,73 @@ Custom queries and mutations (like `login`, `currentUser`, etc.) are available o
1635
1739
 
1636
1740
  ```typescript
1637
1741
  // Query with select
1638
- const currentUser = await db.query.currentUser({
1639
- select: { id: true, username: true, email: true }
1640
- }).unwrap();
1742
+ const currentUser = await db.query
1743
+ .currentUser({
1744
+ select: { id: true, username: true, email: true },
1745
+ })
1746
+ .unwrap();
1641
1747
 
1642
1748
  // Query without select (returns full type)
1643
1749
  const me = await db.query.currentUser({}).unwrap();
1644
1750
 
1645
1751
  // Query with arguments
1646
- const node = await db.query.nodeById({
1647
- id: 'some-node-id'
1648
- }, {
1649
- select: { id: true }
1650
- }).unwrap();
1752
+ const node = await db.query
1753
+ .nodeById(
1754
+ {
1755
+ id: 'some-node-id',
1756
+ },
1757
+ {
1758
+ select: { id: true },
1759
+ }
1760
+ )
1761
+ .unwrap();
1651
1762
  ```
1652
1763
 
1653
1764
  #### Custom Mutations
1654
1765
 
1655
1766
  ```typescript
1656
1767
  // Login mutation with typed select
1657
- const login = await db.mutation.login({
1658
- input: {
1659
- email: 'user@example.com',
1660
- password: 'secret123'
1661
- }
1662
- }, {
1663
- select: {
1664
- clientMutationId: true,
1665
- apiToken: {
1768
+ const login = await db.mutation
1769
+ .login(
1770
+ {
1771
+ input: {
1772
+ email: 'user@example.com',
1773
+ password: 'secret123',
1774
+ },
1775
+ },
1776
+ {
1666
1777
  select: {
1667
- accessToken: true,
1668
- accessTokenExpiresAt: true
1669
- }
1778
+ clientMutationId: true,
1779
+ apiToken: {
1780
+ select: {
1781
+ accessToken: true,
1782
+ accessTokenExpiresAt: true,
1783
+ },
1784
+ },
1785
+ },
1670
1786
  }
1671
- }
1672
- }).unwrap();
1787
+ )
1788
+ .unwrap();
1673
1789
 
1674
1790
  console.log(login.login.apiToken?.accessToken);
1675
1791
 
1676
1792
  // Register mutation
1677
- const register = await db.mutation.register({
1678
- input: {
1679
- email: 'new@example.com',
1680
- password: 'secret123',
1681
- username: 'newuser'
1682
- }
1683
- }).unwrap();
1793
+ const register = await db.mutation
1794
+ .register({
1795
+ input: {
1796
+ email: 'new@example.com',
1797
+ password: 'secret123',
1798
+ username: 'newuser',
1799
+ },
1800
+ })
1801
+ .unwrap();
1684
1802
 
1685
1803
  // Logout mutation
1686
- await db.mutation.logout({
1687
- input: { clientMutationId: 'optional-id' }
1688
- }).execute();
1804
+ await db.mutation
1805
+ .logout({
1806
+ input: { clientMutationId: 'optional-id' },
1807
+ })
1808
+ .execute();
1689
1809
  ```
1690
1810
 
1691
1811
  ### Query Builder API
@@ -1696,7 +1816,7 @@ Every operation returns a `QueryBuilder` that can be inspected before execution:
1696
1816
  const query = db.user.findMany({
1697
1817
  select: { id: true, username: true },
1698
1818
  where: { isActive: { equalTo: true } },
1699
- first: 10
1819
+ first: 10,
1700
1820
  });
1701
1821
 
1702
1822
  // Inspect the generated GraphQL
@@ -1838,25 +1958,29 @@ export type OrderSelect = {
1838
1958
  id?: boolean;
1839
1959
  orderNumber?: boolean;
1840
1960
  status?: boolean;
1841
-
1961
+
1842
1962
  // BelongsTo relation
1843
1963
  customer?: boolean | { select?: UserSelect };
1844
-
1964
+
1845
1965
  // HasMany relation
1846
- orderItems?: boolean | {
1847
- select?: OrderItemSelect;
1848
- first?: number;
1849
- filter?: OrderItemFilter;
1850
- orderBy?: OrderItemsOrderBy[];
1851
- };
1852
-
1966
+ orderItems?:
1967
+ | boolean
1968
+ | {
1969
+ select?: OrderItemSelect;
1970
+ first?: number;
1971
+ filter?: OrderItemFilter;
1972
+ orderBy?: OrderItemsOrderBy[];
1973
+ };
1974
+
1853
1975
  // ManyToMany relation
1854
- productsByOrderItemOrderIdAndProductId?: boolean | {
1855
- select?: ProductSelect;
1856
- first?: number;
1857
- filter?: ProductFilter;
1858
- orderBy?: ProductsOrderBy[];
1859
- };
1976
+ productsByOrderItemOrderIdAndProductId?:
1977
+ | boolean
1978
+ | {
1979
+ select?: ProductSelect;
1980
+ first?: number;
1981
+ filter?: ProductFilter;
1982
+ orderBy?: ProductsOrderBy[];
1983
+ };
1860
1984
  };
1861
1985
  ```
1862
1986