@constructive-io/graphql-codegen 2.32.0 → 3.0.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 (270) hide show
  1. package/README.md +429 -1691
  2. package/cli/index.d.ts +5 -2
  3. package/cli/index.js +98 -581
  4. package/cli/shared.d.ts +35 -0
  5. package/cli/shared.js +106 -0
  6. package/{esm/cli → core}/codegen/barrel.d.ts +1 -1
  7. package/{cli → core}/codegen/barrel.js +1 -4
  8. package/{esm/cli → core}/codegen/index.d.ts +15 -5
  9. package/{cli → core}/codegen/index.js +44 -24
  10. package/{cli → core}/codegen/invalidation.d.ts +2 -2
  11. package/{esm/cli → core}/codegen/mutation-keys.d.ts +2 -2
  12. package/{cli → core}/codegen/orm/client-generator.js +2 -3
  13. package/{esm/cli → core}/codegen/orm/index.d.ts +9 -2
  14. package/{cli → core}/codegen/orm/index.js +3 -2
  15. package/{cli → core}/codegen/query-keys.d.ts +2 -2
  16. package/core/codegen/shared/index.d.ts +39 -0
  17. package/core/codegen/shared/index.js +118 -0
  18. package/core/config/index.d.ts +5 -0
  19. package/core/config/index.js +13 -0
  20. package/core/config/loader.d.ts +18 -0
  21. package/{cli/commands/init.js → core/config/loader.js} +7 -94
  22. package/core/config/resolver.d.ts +46 -0
  23. package/core/config/resolver.js +104 -0
  24. package/core/database/index.d.ts +43 -0
  25. package/core/database/index.js +85 -0
  26. package/core/generate.d.ts +22 -0
  27. package/core/generate.js +192 -0
  28. package/core/index.d.ts +13 -1
  29. package/core/index.js +22 -2
  30. package/{cli → core}/introspect/fetch-schema.js +58 -9
  31. package/core/introspect/source/api-schemas.d.ts +44 -0
  32. package/core/introspect/source/api-schemas.js +122 -0
  33. package/core/introspect/source/database.d.ts +32 -0
  34. package/core/introspect/source/database.js +91 -0
  35. package/core/introspect/source/index.d.ts +112 -0
  36. package/core/introspect/source/index.js +173 -0
  37. package/core/introspect/source/pgpm-module.d.ts +83 -0
  38. package/core/introspect/source/pgpm-module.js +200 -0
  39. package/core/output/index.d.ts +4 -0
  40. package/core/output/index.js +9 -0
  41. package/core/output/writer.d.ts +38 -0
  42. package/core/output/writer.js +156 -0
  43. package/{cli/commands/shared.d.ts → core/pipeline/index.d.ts} +5 -3
  44. package/{cli/commands/shared.js → core/pipeline/index.js} +4 -0
  45. package/{cli → core}/watch/orchestrator.d.ts +25 -3
  46. package/{cli → core}/watch/orchestrator.js +35 -27
  47. package/{cli → core}/watch/types.d.ts +1 -1
  48. package/esm/cli/index.d.ts +5 -2
  49. package/esm/cli/index.js +97 -547
  50. package/esm/cli/shared.d.ts +35 -0
  51. package/esm/cli/shared.js +101 -0
  52. package/{cli → esm/core}/codegen/barrel.d.ts +1 -1
  53. package/esm/{cli → core}/codegen/barrel.js +1 -4
  54. package/{cli → esm/core}/codegen/index.d.ts +15 -5
  55. package/esm/{cli → core}/codegen/index.js +44 -24
  56. package/esm/{cli → core}/codegen/invalidation.d.ts +2 -2
  57. package/{cli → esm/core}/codegen/mutation-keys.d.ts +2 -2
  58. package/esm/{cli → core}/codegen/orm/client-generator.js +2 -3
  59. package/{cli → esm/core}/codegen/orm/index.d.ts +9 -2
  60. package/esm/{cli → core}/codegen/orm/index.js +3 -2
  61. package/esm/{cli → core}/codegen/query-keys.d.ts +2 -2
  62. package/esm/core/codegen/shared/index.d.ts +39 -0
  63. package/esm/core/codegen/shared/index.js +79 -0
  64. package/esm/core/config/index.d.ts +5 -0
  65. package/esm/core/config/index.js +5 -0
  66. package/esm/core/config/loader.d.ts +18 -0
  67. package/esm/core/config/loader.js +71 -0
  68. package/esm/core/config/resolver.d.ts +46 -0
  69. package/esm/core/config/resolver.js +100 -0
  70. package/esm/core/database/index.d.ts +43 -0
  71. package/esm/core/database/index.js +48 -0
  72. package/esm/core/generate.d.ts +22 -0
  73. package/esm/core/generate.js +186 -0
  74. package/esm/core/index.d.ts +13 -1
  75. package/esm/core/index.js +20 -1
  76. package/esm/{cli → core}/introspect/fetch-schema.js +55 -9
  77. package/esm/core/introspect/source/api-schemas.d.ts +44 -0
  78. package/esm/core/introspect/source/api-schemas.js +117 -0
  79. package/esm/core/introspect/source/database.d.ts +32 -0
  80. package/esm/core/introspect/source/database.js +87 -0
  81. package/esm/core/introspect/source/index.d.ts +112 -0
  82. package/esm/core/introspect/source/index.js +154 -0
  83. package/esm/core/introspect/source/pgpm-module.d.ts +83 -0
  84. package/esm/core/introspect/source/pgpm-module.js +194 -0
  85. package/esm/core/output/index.d.ts +4 -0
  86. package/esm/core/output/index.js +4 -0
  87. package/esm/core/output/writer.d.ts +38 -0
  88. package/esm/core/output/writer.js +119 -0
  89. package/esm/{cli/commands/shared.d.ts → core/pipeline/index.d.ts} +5 -3
  90. package/esm/{cli/commands/shared.js → core/pipeline/index.js} +1 -0
  91. package/esm/{cli → core}/watch/orchestrator.d.ts +25 -3
  92. package/esm/{cli → core}/watch/orchestrator.js +35 -27
  93. package/esm/{cli → core}/watch/types.d.ts +1 -1
  94. package/esm/index.d.ts +8 -3
  95. package/esm/index.js +9 -3
  96. package/esm/types/config.d.ts +101 -138
  97. package/esm/types/config.js +8 -35
  98. package/esm/types/index.d.ts +2 -2
  99. package/esm/types/index.js +1 -1
  100. package/index.d.ts +8 -3
  101. package/index.js +18 -8
  102. package/package.json +18 -11
  103. package/types/config.d.ts +101 -138
  104. package/types/config.js +9 -38
  105. package/types/index.d.ts +2 -2
  106. package/types/index.js +3 -3
  107. package/cli/commands/generate-orm.d.ts +0 -53
  108. package/cli/commands/generate-orm.js +0 -292
  109. package/cli/commands/generate.d.ts +0 -66
  110. package/cli/commands/generate.js +0 -431
  111. package/cli/commands/index.d.ts +0 -9
  112. package/cli/commands/index.js +0 -14
  113. package/cli/commands/init.d.ts +0 -35
  114. package/cli/introspect/source/index.d.ts +0 -48
  115. package/cli/introspect/source/index.js +0 -72
  116. package/esm/cli/commands/generate-orm.d.ts +0 -53
  117. package/esm/cli/commands/generate-orm.js +0 -289
  118. package/esm/cli/commands/generate.d.ts +0 -66
  119. package/esm/cli/commands/generate.js +0 -393
  120. package/esm/cli/commands/index.d.ts +0 -9
  121. package/esm/cli/commands/index.js +0 -6
  122. package/esm/cli/commands/init.d.ts +0 -35
  123. package/esm/cli/commands/init.js +0 -158
  124. package/esm/cli/introspect/source/index.d.ts +0 -48
  125. package/esm/cli/introspect/source/index.js +0 -54
  126. /package/{cli → core}/codegen/babel-ast.d.ts +0 -0
  127. /package/{cli → core}/codegen/babel-ast.js +0 -0
  128. /package/{cli → core}/codegen/client.d.ts +0 -0
  129. /package/{cli → core}/codegen/client.js +0 -0
  130. /package/{cli → core}/codegen/custom-mutations.d.ts +0 -0
  131. /package/{cli → core}/codegen/custom-mutations.js +0 -0
  132. /package/{cli → core}/codegen/custom-queries.d.ts +0 -0
  133. /package/{cli → core}/codegen/custom-queries.js +0 -0
  134. /package/{cli → core}/codegen/gql-ast.d.ts +0 -0
  135. /package/{cli → core}/codegen/gql-ast.js +0 -0
  136. /package/{cli → core}/codegen/invalidation.js +0 -0
  137. /package/{cli → core}/codegen/mutation-keys.js +0 -0
  138. /package/{cli → core}/codegen/mutations.d.ts +0 -0
  139. /package/{cli → core}/codegen/mutations.js +0 -0
  140. /package/{cli → core}/codegen/orm/barrel.d.ts +0 -0
  141. /package/{cli → core}/codegen/orm/barrel.js +0 -0
  142. /package/{cli → core}/codegen/orm/client-generator.d.ts +0 -0
  143. /package/{cli → core}/codegen/orm/client.d.ts +0 -0
  144. /package/{cli → core}/codegen/orm/client.js +0 -0
  145. /package/{cli → core}/codegen/orm/custom-ops-generator.d.ts +0 -0
  146. /package/{cli → core}/codegen/orm/custom-ops-generator.js +0 -0
  147. /package/{cli → core}/codegen/orm/input-types-generator.d.ts +0 -0
  148. /package/{cli → core}/codegen/orm/input-types-generator.js +0 -0
  149. /package/{cli → core}/codegen/orm/model-generator.d.ts +0 -0
  150. /package/{cli → core}/codegen/orm/model-generator.js +0 -0
  151. /package/{cli → core}/codegen/orm/query-builder.d.ts +0 -0
  152. /package/{cli → core}/codegen/orm/query-builder.js +0 -0
  153. /package/{cli → core}/codegen/orm/query-builder.ts +0 -0
  154. /package/{cli → core}/codegen/orm/select-types.d.ts +0 -0
  155. /package/{cli → core}/codegen/orm/select-types.js +0 -0
  156. /package/{cli → core}/codegen/queries.d.ts +0 -0
  157. /package/{cli → core}/codegen/queries.js +0 -0
  158. /package/{cli → core}/codegen/query-keys.js +0 -0
  159. /package/{cli → core}/codegen/scalars.d.ts +0 -0
  160. /package/{cli → core}/codegen/scalars.js +0 -0
  161. /package/{cli → core}/codegen/schema-gql-ast.d.ts +0 -0
  162. /package/{cli → core}/codegen/schema-gql-ast.js +0 -0
  163. /package/{cli → core}/codegen/schema-types-generator.d.ts +0 -0
  164. /package/{cli → core}/codegen/schema-types-generator.js +0 -0
  165. /package/{cli → core}/codegen/type-resolver.d.ts +0 -0
  166. /package/{cli → core}/codegen/type-resolver.js +0 -0
  167. /package/{cli → core}/codegen/types.d.ts +0 -0
  168. /package/{cli → core}/codegen/types.js +0 -0
  169. /package/{cli → core}/codegen/utils.d.ts +0 -0
  170. /package/{cli → core}/codegen/utils.js +0 -0
  171. /package/{cli → core}/introspect/fetch-schema.d.ts +0 -0
  172. /package/{cli → core}/introspect/index.d.ts +0 -0
  173. /package/{cli → core}/introspect/index.js +0 -0
  174. /package/{cli → core}/introspect/infer-tables.d.ts +0 -0
  175. /package/{cli → core}/introspect/infer-tables.js +0 -0
  176. /package/{cli → core}/introspect/schema-query.d.ts +0 -0
  177. /package/{cli → core}/introspect/schema-query.js +0 -0
  178. /package/{cli → core}/introspect/source/endpoint.d.ts +0 -0
  179. /package/{cli → core}/introspect/source/endpoint.js +0 -0
  180. /package/{cli → core}/introspect/source/file.d.ts +0 -0
  181. /package/{cli → core}/introspect/source/file.js +0 -0
  182. /package/{cli → core}/introspect/source/types.d.ts +0 -0
  183. /package/{cli → core}/introspect/source/types.js +0 -0
  184. /package/{cli → core}/introspect/transform-schema.d.ts +0 -0
  185. /package/{cli → core}/introspect/transform-schema.js +0 -0
  186. /package/{cli → core}/introspect/transform.d.ts +0 -0
  187. /package/{cli → core}/introspect/transform.js +0 -0
  188. /package/{cli → core}/watch/cache.d.ts +0 -0
  189. /package/{cli → core}/watch/cache.js +0 -0
  190. /package/{cli → core}/watch/debounce.d.ts +0 -0
  191. /package/{cli → core}/watch/debounce.js +0 -0
  192. /package/{cli → core}/watch/hash.d.ts +0 -0
  193. /package/{cli → core}/watch/hash.js +0 -0
  194. /package/{cli → core}/watch/index.d.ts +0 -0
  195. /package/{cli → core}/watch/index.js +0 -0
  196. /package/{cli → core}/watch/poller.d.ts +0 -0
  197. /package/{cli → core}/watch/poller.js +0 -0
  198. /package/{cli → core}/watch/types.js +0 -0
  199. /package/esm/{cli → core}/codegen/babel-ast.d.ts +0 -0
  200. /package/esm/{cli → core}/codegen/babel-ast.js +0 -0
  201. /package/esm/{cli → core}/codegen/client.d.ts +0 -0
  202. /package/esm/{cli → core}/codegen/client.js +0 -0
  203. /package/esm/{cli → core}/codegen/custom-mutations.d.ts +0 -0
  204. /package/esm/{cli → core}/codegen/custom-mutations.js +0 -0
  205. /package/esm/{cli → core}/codegen/custom-queries.d.ts +0 -0
  206. /package/esm/{cli → core}/codegen/custom-queries.js +0 -0
  207. /package/esm/{cli → core}/codegen/gql-ast.d.ts +0 -0
  208. /package/esm/{cli → core}/codegen/gql-ast.js +0 -0
  209. /package/esm/{cli → core}/codegen/invalidation.js +0 -0
  210. /package/esm/{cli → core}/codegen/mutation-keys.js +0 -0
  211. /package/esm/{cli → core}/codegen/mutations.d.ts +0 -0
  212. /package/esm/{cli → core}/codegen/mutations.js +0 -0
  213. /package/esm/{cli → core}/codegen/orm/barrel.d.ts +0 -0
  214. /package/esm/{cli → core}/codegen/orm/barrel.js +0 -0
  215. /package/esm/{cli → core}/codegen/orm/client-generator.d.ts +0 -0
  216. /package/esm/{cli → core}/codegen/orm/client.d.ts +0 -0
  217. /package/esm/{cli → core}/codegen/orm/client.js +0 -0
  218. /package/esm/{cli → core}/codegen/orm/custom-ops-generator.d.ts +0 -0
  219. /package/esm/{cli → core}/codegen/orm/custom-ops-generator.js +0 -0
  220. /package/esm/{cli → core}/codegen/orm/input-types-generator.d.ts +0 -0
  221. /package/esm/{cli → core}/codegen/orm/input-types-generator.js +0 -0
  222. /package/esm/{cli → core}/codegen/orm/model-generator.d.ts +0 -0
  223. /package/esm/{cli → core}/codegen/orm/model-generator.js +0 -0
  224. /package/esm/{cli → core}/codegen/orm/query-builder.d.ts +0 -0
  225. /package/esm/{cli → core}/codegen/orm/query-builder.js +0 -0
  226. /package/esm/{cli → core}/codegen/orm/select-types.d.ts +0 -0
  227. /package/esm/{cli → core}/codegen/orm/select-types.js +0 -0
  228. /package/esm/{cli → core}/codegen/queries.d.ts +0 -0
  229. /package/esm/{cli → core}/codegen/queries.js +0 -0
  230. /package/esm/{cli → core}/codegen/query-keys.js +0 -0
  231. /package/esm/{cli → core}/codegen/scalars.d.ts +0 -0
  232. /package/esm/{cli → core}/codegen/scalars.js +0 -0
  233. /package/esm/{cli → core}/codegen/schema-gql-ast.d.ts +0 -0
  234. /package/esm/{cli → core}/codegen/schema-gql-ast.js +0 -0
  235. /package/esm/{cli → core}/codegen/schema-types-generator.d.ts +0 -0
  236. /package/esm/{cli → core}/codegen/schema-types-generator.js +0 -0
  237. /package/esm/{cli → core}/codegen/type-resolver.d.ts +0 -0
  238. /package/esm/{cli → core}/codegen/type-resolver.js +0 -0
  239. /package/esm/{cli → core}/codegen/types.d.ts +0 -0
  240. /package/esm/{cli → core}/codegen/types.js +0 -0
  241. /package/esm/{cli → core}/codegen/utils.d.ts +0 -0
  242. /package/esm/{cli → core}/codegen/utils.js +0 -0
  243. /package/esm/{cli → core}/introspect/fetch-schema.d.ts +0 -0
  244. /package/esm/{cli → core}/introspect/index.d.ts +0 -0
  245. /package/esm/{cli → core}/introspect/index.js +0 -0
  246. /package/esm/{cli → core}/introspect/infer-tables.d.ts +0 -0
  247. /package/esm/{cli → core}/introspect/infer-tables.js +0 -0
  248. /package/esm/{cli → core}/introspect/schema-query.d.ts +0 -0
  249. /package/esm/{cli → core}/introspect/schema-query.js +0 -0
  250. /package/esm/{cli → core}/introspect/source/endpoint.d.ts +0 -0
  251. /package/esm/{cli → core}/introspect/source/endpoint.js +0 -0
  252. /package/esm/{cli → core}/introspect/source/file.d.ts +0 -0
  253. /package/esm/{cli → core}/introspect/source/file.js +0 -0
  254. /package/esm/{cli → core}/introspect/source/types.d.ts +0 -0
  255. /package/esm/{cli → core}/introspect/source/types.js +0 -0
  256. /package/esm/{cli → core}/introspect/transform-schema.d.ts +0 -0
  257. /package/esm/{cli → core}/introspect/transform-schema.js +0 -0
  258. /package/esm/{cli → core}/introspect/transform.d.ts +0 -0
  259. /package/esm/{cli → core}/introspect/transform.js +0 -0
  260. /package/esm/{cli → core}/watch/cache.d.ts +0 -0
  261. /package/esm/{cli → core}/watch/cache.js +0 -0
  262. /package/esm/{cli → core}/watch/debounce.d.ts +0 -0
  263. /package/esm/{cli → core}/watch/debounce.js +0 -0
  264. /package/esm/{cli → core}/watch/hash.d.ts +0 -0
  265. /package/esm/{cli → core}/watch/hash.js +0 -0
  266. /package/esm/{cli → core}/watch/index.d.ts +0 -0
  267. /package/esm/{cli → core}/watch/index.js +0 -0
  268. /package/esm/{cli → core}/watch/poller.d.ts +0 -0
  269. /package/esm/{cli → core}/watch/poller.js +0 -0
  270. /package/esm/{cli → core}/watch/types.js +0 -0
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <a href="https://www.npmjs.com/package/@constructive-io/graphql-codegen"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=graphql%2Fcodegen%2Fpackage.json"/></a>
13
13
  </p>
14
14
 
15
- CLI-based GraphQL SDK generator for PostGraphile endpoints. Generate type-safe React Query hooks or a Prisma-like ORM client from your GraphQL schema.
15
+ GraphQL SDK generator for Constructive databases. Generate type-safe React Query hooks or a Prisma-like ORM client from your GraphQL schema.
16
16
 
17
17
  ## Features
18
18
 
@@ -24,29 +24,21 @@ CLI-based GraphQL SDK generator for PostGraphile endpoints. Generate type-safe R
24
24
  - **Advanced Type Inference**: Const generics for narrowed return types based on select clauses
25
25
  - **Relation Support**: Typed nested selects for belongsTo, hasMany, and manyToMany relations
26
26
  - **Error Handling**: Discriminated unions with `.unwrap()`, `.unwrapOr()`, `.unwrapOrElse()` methods
27
- - **AST-Based Generation**: Uses `ts-morph` for reliable code generation
27
+ - **AST-Based Generation**: Uses Babel for reliable code generation
28
28
  - **Configurable**: Filter tables, queries, and mutations with glob patterns
29
29
  - **Type-Safe**: Full TypeScript support with generated interfaces
30
30
 
31
31
  ## Table of Contents
32
32
 
33
33
  - [Installation](#installation)
34
- - [Quick Start](#quick-start)
35
- - [CLI Commands](#cli-commands)
36
- - [Configuration](#configuration)
34
+ - [Programmatic API](#programmatic-api)
37
35
  - [React Query Hooks](#react-query-hooks)
38
36
  - [ORM Client](#orm-client)
39
- - [Basic Usage](#basic-usage)
40
- - [Select & Type Inference](#select--type-inference)
41
- - [Relations](#relations)
42
- - [Filtering & Ordering](#filtering--ordering)
43
- - [Pagination](#pagination)
44
- - [Error Handling](#error-handling)
45
- - [Custom Operations](#custom-operations)
37
+ - [Configuration](#configuration)
38
+ - [CLI Commands](#cli-commands)
46
39
  - [Architecture](#architecture)
47
40
  - [Generated Types](#generated-types)
48
41
  - [Development](#development)
49
- - [Roadmap](#roadmap)
50
42
 
51
43
  ## Installation
52
44
 
@@ -54,217 +46,195 @@ CLI-based GraphQL SDK generator for PostGraphile endpoints. Generate type-safe R
54
46
  pnpm add @constructive-io/graphql-codegen
55
47
  ```
56
48
 
57
- ## Quick Start
49
+ ## Programmatic API
58
50
 
59
- ### 1. Initialize Config (Optional)
51
+ The primary way to use this package is through the programmatic API. Import the `generate` function and call it with your configuration.
60
52
 
61
- ```bash
62
- npx graphql-sdk init
63
- ```
64
-
65
- Creates a `graphql-sdk.config.ts` file:
53
+ ### Generate from Endpoint
66
54
 
67
55
  ```typescript
68
- import { defineConfig } from '@constructive-io/graphql-codegen';
56
+ import { generate } from '@constructive-io/graphql-codegen';
69
57
 
70
- export default defineConfig({
58
+ // Generate React Query hooks from a GraphQL endpoint
59
+ await generate({
71
60
  endpoint: 'https://api.example.com/graphql',
72
- output: './generated/graphql',
73
- headers: {
74
- Authorization: 'Bearer <token>',
75
- },
61
+ output: './generated',
62
+ headers: { Authorization: 'Bearer <token>' },
63
+ reactQuery: true,
76
64
  });
77
- ```
78
-
79
- ### 2. Generate SDK
80
65
 
81
- ```bash
82
- # Generate React Query hooks
83
- npx graphql-sdk generate -e https://api.example.com/graphql -o ./generated/hooks
66
+ // Generate ORM client from a GraphQL endpoint
67
+ await generate({
68
+ endpoint: 'https://api.example.com/graphql',
69
+ output: './generated',
70
+ headers: { Authorization: 'Bearer <token>' },
71
+ orm: true,
72
+ });
84
73
 
85
- # Generate ORM client
86
- npx graphql-sdk generate-orm -e https://api.example.com/graphql -o ./generated/orm
74
+ // Generate both React Query hooks and ORM client
75
+ await generate({
76
+ endpoint: 'https://api.example.com/graphql',
77
+ output: './generated',
78
+ reactQuery: true,
79
+ orm: true,
80
+ });
87
81
  ```
88
82
 
89
- ### 3. Use the Generated Code
90
-
91
- ```typescript
92
- // ORM Client
93
- import { createClient } from './generated/orm';
94
-
95
- const db = createClient({ endpoint: 'https://api.example.com/graphql' });
83
+ ### Generate from Database
96
84
 
97
- const users = await db.user.findMany({
98
- select: { id: true, username: true },
99
- first: 10,
100
- }).execute();
85
+ Connect directly to a PostgreSQL database to generate code:
101
86
 
102
- // React Query Hooks
103
- import { useCarsQuery } from './generated/hooks';
104
-
105
- function CarList() {
106
- const { data } = useCarsQuery({ first: 10 });
107
- return <ul>{data?.cars.nodes.map(car => <li key={car.id}>{car.name}</li>)}</ul>;
108
- }
109
- ```
110
-
111
- ## CLI Commands
87
+ ```typescript
88
+ import { generate } from '@constructive-io/graphql-codegen';
112
89
 
113
- ### `graphql-sdk generate`
90
+ // Generate from database with explicit schemas
91
+ await generate({
92
+ db: {
93
+ schemas: ['public', 'app_public'],
94
+ },
95
+ output: './generated',
96
+ reactQuery: true,
97
+ });
114
98
 
115
- Generate React Query hooks from a PostGraphile endpoint.
99
+ // Generate from database using API names for automatic schema discovery
100
+ await generate({
101
+ db: {
102
+ apiNames: ['my_api'],
103
+ },
104
+ output: './generated',
105
+ orm: true,
106
+ });
116
107
 
117
- ```bash
118
- Options:
119
- -e, --endpoint <url> GraphQL endpoint URL (overrides config)
120
- -t, --target <name> Target name in config file
121
- -o, --output <dir> Output directory (default: ./generated/graphql)
122
- -c, --config <path> Path to config file
123
- -a, --authorization <token> Authorization header value
124
- --dry-run Preview without writing files
125
- --skip-custom-operations Only generate table CRUD hooks
126
- -v, --verbose Show detailed output
108
+ // Generate with explicit database config (overrides environment variables)
109
+ await generate({
110
+ db: {
111
+ config: {
112
+ host: 'localhost',
113
+ port: 5432,
114
+ database: 'mydb',
115
+ user: 'postgres',
116
+ },
117
+ schemas: ['public'],
118
+ },
119
+ output: './generated',
120
+ reactQuery: true,
121
+ });
127
122
  ```
128
123
 
129
- ### `graphql-sdk generate-orm`
124
+ ### Generate from PGPM Module
130
125
 
131
- Generate Prisma-like ORM client from a PostGraphile endpoint.
126
+ Generate code from a PGPM module path:
132
127
 
133
- ```bash
134
- Options:
135
- -e, --endpoint <url> GraphQL endpoint URL
136
- -t, --target <name> Target name in config file
137
- -o, --output <dir> Output directory (default: ./generated/orm)
138
- -c, --config <path> Path to config file
139
- -a, --authorization <token> Authorization header value
140
- --skip-custom-operations Only generate table models
141
- --dry-run Preview without writing files
142
- -v, --verbose Show detailed output
143
- ```
128
+ ```typescript
129
+ import { generate } from '@constructive-io/graphql-codegen';
144
130
 
145
- ### `graphql-sdk init`
131
+ // Generate from a PGPM module directory
132
+ await generate({
133
+ db: {
134
+ pgpm: { modulePath: './packages/my-module' },
135
+ schemas: ['public'],
136
+ },
137
+ output: './generated',
138
+ reactQuery: true,
139
+ });
146
140
 
147
- Create a configuration file.
141
+ // Generate from a PGPM workspace with module name
142
+ await generate({
143
+ db: {
144
+ pgpm: {
145
+ workspacePath: '/path/to/workspace',
146
+ moduleName: 'my-module',
147
+ },
148
+ schemas: ['public'],
149
+ },
150
+ output: './generated',
151
+ orm: true,
152
+ });
148
153
 
149
- ```bash
150
- Options:
151
- -f, --format <format> Config format: ts, js, json (default: ts)
152
- -o, --output <path> Output path for config file
154
+ // Keep the ephemeral database after generation (for debugging)
155
+ await generate({
156
+ db: {
157
+ pgpm: { modulePath: './packages/my-module' },
158
+ schemas: ['public'],
159
+ keepDb: true,
160
+ },
161
+ output: './generated',
162
+ reactQuery: true,
163
+ });
153
164
  ```
154
165
 
155
- ### `graphql-sdk introspect`
156
-
157
- Inspect schema without generating code.
158
-
159
- ```bash
160
- Options:
161
- -e, --endpoint <url> GraphQL endpoint URL
162
- --json Output as JSON
163
- -v, --verbose Show detailed output
164
- ```
166
+ ### Configuration Options
165
167
 
166
- ## Configuration
168
+ The `generate` function accepts a configuration object with the following options:
167
169
 
168
170
  ```typescript
169
171
  interface GraphQLSDKConfigTarget {
170
- // Required (choose one)
171
- endpoint?: string;
172
- schema?: string;
172
+ // Source (choose one)
173
+ endpoint?: string; // GraphQL endpoint URL
174
+ schemaFile?: string; // Path to GraphQL schema file (.graphql)
175
+ db?: DbConfig; // Database configuration (see below)
173
176
 
174
177
  // Output
175
- output?: string; // default: './generated/graphql'
178
+ output?: string; // Output directory (default: './generated/graphql')
176
179
 
177
180
  // Authentication
178
- headers?: Record<string, string>;
181
+ headers?: Record<string, string>; // HTTP headers for endpoint requests
182
+
183
+ // Generator flags
184
+ reactQuery?: boolean; // Generate React Query hooks (output: {output}/hooks)
185
+ orm?: boolean; // Generate ORM client (output: {output}/orm)
179
186
 
180
187
  // Table filtering (for CRUD operations from _meta)
181
188
  tables?: {
182
- include?: string[]; // default: ['*']
183
- exclude?: string[]; // default: []
189
+ include?: string[]; // Glob patterns (default: ['*'])
190
+ exclude?: string[]; // Glob patterns (default: [])
184
191
  };
185
192
 
186
193
  // Query filtering (for ALL queries from __schema)
187
194
  queries?: {
188
- include?: string[]; // default: ['*']
189
- exclude?: string[]; // default: ['_meta', 'query']
195
+ include?: string[]; // Glob patterns (default: ['*'])
196
+ exclude?: string[]; // Glob patterns (default: ['_meta', 'query'])
190
197
  };
191
198
 
192
199
  // Mutation filtering (for ALL mutations from __schema)
193
200
  mutations?: {
194
- include?: string[]; // default: ['*']
195
- exclude?: string[]; // default: []
201
+ include?: string[]; // Glob patterns (default: ['*'])
202
+ exclude?: string[]; // Glob patterns (default: [])
196
203
  };
197
204
 
198
205
  // Code generation options
199
206
  codegen?: {
200
- maxFieldDepth?: number; // default: 2
201
- skipQueryField?: boolean; // default: true
207
+ maxFieldDepth?: number; // Max depth for nested fields (default: 2)
208
+ skipQueryField?: boolean; // Skip 'query' field (default: true)
202
209
  };
203
210
 
204
- // ORM-specific config
205
- orm?: {
206
- output?: string; // default: './generated/orm'
207
- useSharedTypes?: boolean; // default: true
211
+ // Query key generation
212
+ queryKeys?: {
213
+ generateScopedKeys?: boolean; // Generate scope-aware keys (default: true)
214
+ generateMutationKeys?: boolean; // Generate mutation keys (default: true)
215
+ generateCascadeHelpers?: boolean; // Generate invalidation helpers (default: true)
216
+ relationships?: Record<string, { parent: string; foreignKey: string }>;
208
217
  };
209
218
  }
210
219
 
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
- });
239
- ```
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
220
+ // Database configuration for direct database introspection or PGPM module
221
+ interface DbConfig {
222
+ // PostgreSQL connection config (falls back to PGHOST, PGPORT, etc. env vars)
223
+ config?: Partial<PgConfig>;
246
224
 
247
- ### Glob Patterns
248
-
249
- Filter patterns support wildcards:
250
-
251
- - `*` - matches any string
252
- - `?` - matches single character
225
+ // PGPM module configuration for ephemeral database creation
226
+ pgpm?: {
227
+ modulePath?: string; // Path to PGPM module directory
228
+ workspacePath?: string; // Path to PGPM workspace (with moduleName)
229
+ moduleName?: string; // Module name within workspace
230
+ };
253
231
 
254
- Examples:
232
+ // Schema selection (choose one)
233
+ schemas?: string[]; // Explicit PostgreSQL schema names
234
+ apiNames?: string[]; // API names for automatic schema discovery
255
235
 
256
- ```typescript
257
- {
258
- tables: {
259
- include: ['User', 'Product', 'Order*'],
260
- exclude: ['*_archive', 'temp_*'],
261
- },
262
- queries: {
263
- exclude: ['_meta', 'query', '*Debug*'],
264
- },
265
- mutations: {
266
- include: ['create*', 'update*', 'delete*', 'login', 'register', 'logout'],
267
- },
236
+ // Debugging
237
+ keepDb?: boolean; // Keep ephemeral database after generation
268
238
  }
269
239
  ```
270
240
 
@@ -279,138 +249,74 @@ generated/hooks/
279
249
  ├── index.ts # Main barrel export (configure, hooks, types)
280
250
  ├── client.ts # configure() and execute() functions
281
251
  ├── types.ts # Entity interfaces, filter types, enums
282
- ├── hooks.ts # All hooks re-exported
283
252
  ├── queries/
284
253
  │ ├── index.ts # Query hooks barrel
285
254
  │ ├── useCarsQuery.ts # Table list query (findMany)
286
255
  │ ├── useCarQuery.ts # Table single item query (findOne)
287
- │ ├── useCurrentUserQuery.ts # Custom query
288
256
  │ └── ...
289
257
  └── mutations/
290
258
  ├── index.ts # Mutation hooks barrel
291
259
  ├── useCreateCarMutation.ts
292
260
  ├── useUpdateCarMutation.ts
293
261
  ├── useDeleteCarMutation.ts
294
- ├── useLoginMutation.ts # Custom mutation
295
262
  └── ...
296
263
  ```
297
264
 
298
265
  ### Setup & Configuration
299
266
 
300
- #### 1. Configure the Client
301
-
302
267
  Configure the GraphQL client once at your app's entry point:
303
268
 
304
269
  ```tsx
305
- // App.tsx or main.tsx
306
270
  import { configure } from './generated/hooks';
307
271
 
308
- // Basic configuration
309
- configure({
310
- endpoint: 'https://api.example.com/graphql',
311
- });
312
-
313
- // With authentication
314
272
  configure({
315
273
  endpoint: 'https://api.example.com/graphql',
316
274
  headers: {
317
275
  Authorization: 'Bearer <token>',
318
- 'X-Custom-Header': 'value',
319
276
  },
320
277
  });
321
278
  ```
322
279
 
323
- #### 2. Update Headers at Runtime
324
-
325
- ```tsx
326
- import { configure } from './generated/hooks';
327
-
328
- // After login, update the authorization header
329
- function handleLoginSuccess(token: string) {
330
- configure({
331
- endpoint: 'https://api.example.com/graphql',
332
- headers: {
333
- Authorization: `Bearer ${token}`,
334
- },
335
- });
336
- }
337
- ```
338
-
339
280
  ### Table Query Hooks
340
281
 
341
282
  For each table, two query hooks are generated:
342
283
 
343
- #### List Query (`use{Table}sQuery`)
344
-
345
- Fetches multiple records with pagination, filtering, and ordering:
346
-
347
284
  ```tsx
348
- import { useCarsQuery } from './generated/hooks';
285
+ import { useCarsQuery, useCarQuery } from './generated/hooks';
349
286
 
287
+ // List query with filtering, pagination, and ordering
350
288
  function CarList() {
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
- );
289
+ const { data, isLoading, isError, error } = useCarsQuery({
290
+ first: 10,
291
+ filter: {
292
+ brand: { equalTo: 'Tesla' },
293
+ price: { greaterThan: 50000 },
294
+ },
295
+ orderBy: ['CREATED_AT_DESC'],
296
+ });
370
297
 
371
298
  if (isLoading) return <div>Loading...</div>;
372
299
  if (isError) return <div>Error: {error.message}</div>;
373
300
 
374
301
  return (
375
- <div>
376
- <p>Total: {data?.cars.totalCount}</p>
377
- <ul>
378
- {data?.cars.nodes.map((car) => (
379
- <li key={car.id}>
380
- {car.brand} - ${car.price}
381
- </li>
382
- ))}
383
- </ul>
384
-
385
- {/* Pagination info */}
386
- {data?.cars.pageInfo.hasNextPage && (
387
- <button onClick={() => refetch()}>Load More</button>
388
- )}
389
- </div>
302
+ <ul>
303
+ {data?.cars.nodes.map((car) => (
304
+ <li key={car.id}>{car.brand} - ${car.price}</li>
305
+ ))}
306
+ </ul>
390
307
  );
391
308
  }
392
- ```
393
-
394
- #### Single Item Query (`use{Table}Query`)
395
-
396
- Fetches a single record by ID:
397
-
398
- ```tsx
399
- import { useCarQuery } from './generated/hooks';
400
309
 
310
+ // Single item query by ID
401
311
  function CarDetails({ carId }: { carId: string }) {
402
- const { data, isLoading, isError } = useCarQuery({
403
- id: carId,
404
- });
312
+ const { data, isLoading } = useCarQuery({ id: carId });
405
313
 
406
314
  if (isLoading) return <div>Loading...</div>;
407
- if (isError) return <div>Car not found</div>;
408
315
 
409
316
  return (
410
317
  <div>
411
318
  <h1>{data?.car?.brand}</h1>
412
319
  <p>Price: ${data?.car?.price}</p>
413
- <p>Created: {data?.car?.createdAt}</p>
414
320
  </div>
415
321
  );
416
322
  }
@@ -420,1472 +326,421 @@ function CarDetails({ carId }: { carId: string }) {
420
326
 
421
327
  For each table, three mutation hooks are generated:
422
328
 
423
- #### Create Mutation (`useCreate{Table}Mutation`)
424
-
425
329
  ```tsx
426
- import { useCreateCarMutation } from './generated/hooks';
330
+ import {
331
+ useCreateCarMutation,
332
+ useUpdateCarMutation,
333
+ useDeleteCarMutation,
334
+ } from './generated/hooks';
427
335
 
428
- function CreateCarForm() {
336
+ function CarForm() {
429
337
  const createCar = useCreateCarMutation({
430
338
  onSuccess: (data) => {
431
339
  console.log('Created car:', data.createCar.car.id);
432
- // Invalidate queries, redirect, show toast, etc.
433
- },
434
- onError: (error) => {
435
- console.error('Failed to create car:', error);
436
- },
437
- });
438
-
439
- const handleSubmit = (formData: { brand: string; price: number }) => {
440
- createCar.mutate({
441
- input: {
442
- car: {
443
- brand: formData.brand,
444
- price: formData.price,
445
- },
446
- },
447
- });
448
- };
449
-
450
- return (
451
- <form
452
- onSubmit={(e) => {
453
- e.preventDefault();
454
- handleSubmit({ brand: 'Tesla', price: 80000 });
455
- }}
456
- >
457
- {/* form fields */}
458
- <button type="submit" disabled={createCar.isPending}>
459
- {createCar.isPending ? 'Creating...' : 'Create Car'}
460
- </button>
461
- {createCar.isError && <p>Error: {createCar.error.message}</p>}
462
- </form>
463
- );
464
- }
465
- ```
466
-
467
- #### Update Mutation (`useUpdate{Table}Mutation`)
468
-
469
- ```tsx
470
- import { useUpdateCarMutation } from './generated/hooks';
471
-
472
- function EditCarForm({
473
- carId,
474
- currentBrand,
475
- }: {
476
- carId: string;
477
- currentBrand: string;
478
- }) {
479
- const updateCar = useUpdateCarMutation({
480
- onSuccess: (data) => {
481
- console.log('Updated car:', data.updateCar.car.brand);
482
- },
483
- });
484
-
485
- const handleUpdate = (newBrand: string) => {
486
- updateCar.mutate({
487
- input: {
488
- id: carId,
489
- patch: {
490
- brand: newBrand,
491
- },
492
- },
493
- });
494
- };
495
-
496
- return (
497
- <button
498
- onClick={() => handleUpdate('Updated Brand')}
499
- disabled={updateCar.isPending}
500
- >
501
- Update
502
- </button>
503
- );
504
- }
505
- ```
506
-
507
- #### Delete Mutation (`useDelete{Table}Mutation`)
508
-
509
- ```tsx
510
- import { useDeleteCarMutation } from './generated/hooks';
511
-
512
- function DeleteCarButton({ carId }: { carId: string }) {
513
- const deleteCar = useDeleteCarMutation({
514
- onSuccess: () => {
515
- console.log('Car deleted');
516
- // Navigate away, refetch list, etc.
517
340
  },
518
341
  });
519
342
 
520
343
  return (
521
344
  <button
522
- onClick={() => deleteCar.mutate({ input: { id: carId } })}
523
- disabled={deleteCar.isPending}
345
+ onClick={() =>
346
+ createCar.mutate({
347
+ input: { car: { brand: 'Tesla', price: 80000 } },
348
+ })
349
+ }
350
+ disabled={createCar.isPending}
524
351
  >
525
- {deleteCar.isPending ? 'Deleting...' : 'Delete'}
352
+ Create
526
353
  </button>
527
354
  );
528
355
  }
529
356
  ```
530
357
 
531
- ### Custom Query Hooks
358
+ ### Custom Query and Mutation Hooks
532
359
 
533
- Custom queries from your schema (like `currentUser`, `nodeById`, etc.) get their own hooks:
360
+ Custom queries and mutations from your schema get their own hooks:
534
361
 
535
362
  ```tsx
536
- import { useCurrentUserQuery, useNodeByIdQuery } from './generated/hooks';
363
+ import { useCurrentUserQuery, useLoginMutation } from './generated/hooks';
537
364
 
538
- // Simple custom query
539
365
  function UserProfile() {
540
- const { data, isLoading } = useCurrentUserQuery();
541
-
542
- if (isLoading) return <div>Loading...</div>;
543
- if (!data?.currentUser) return <div>Not logged in</div>;
544
-
545
- return (
546
- <div>
547
- <h1>Welcome, {data.currentUser.username}</h1>
548
- <p>Email: {data.currentUser.email}</p>
549
- </div>
550
- );
551
- }
552
-
553
- // Custom query with arguments
554
- function NodeViewer({ nodeId }: { nodeId: string }) {
555
- const { data } = useNodeByIdQuery({
556
- id: nodeId,
557
- });
558
-
559
- return <pre>{JSON.stringify(data?.node, null, 2)}</pre>;
366
+ const { data } = useCurrentUserQuery();
367
+ return <h1>Welcome, {data?.currentUser?.username}</h1>;
560
368
  }
561
- ```
562
-
563
- ### Custom Mutation Hooks
564
-
565
- Custom mutations (like `login`, `register`, `logout`) get dedicated hooks:
566
369
 
567
- ```tsx
568
- import {
569
- useLoginMutation,
570
- useRegisterMutation,
571
- useLogoutMutation,
572
- useForgotPasswordMutation,
573
- } from './generated/hooks';
574
-
575
- // Login
576
370
  function LoginForm() {
577
371
  const login = useLoginMutation({
578
372
  onSuccess: (data) => {
579
373
  const token = data.login.apiToken?.accessToken;
580
- if (token) {
581
- localStorage.setItem('token', token);
582
- // Reconfigure client with new token
583
- configure({
584
- endpoint: 'https://api.example.com/graphql',
585
- headers: { Authorization: `Bearer ${token}` },
586
- });
587
- }
588
- },
589
- onError: (error) => {
590
- alert('Login failed: ' + error.message);
591
- },
592
- });
593
-
594
- const handleLogin = (email: string, password: string) => {
595
- login.mutate({
596
- input: { email, password },
597
- });
598
- };
599
-
600
- return (
601
- <form
602
- onSubmit={(e) => {
603
- e.preventDefault();
604
- handleLogin('user@example.com', 'password');
605
- }}
606
- >
607
- {/* email and password inputs */}
608
- <button disabled={login.isPending}>
609
- {login.isPending ? 'Logging in...' : 'Login'}
610
- </button>
611
- </form>
612
- );
613
- }
614
-
615
- // Register
616
- function RegisterForm() {
617
- const register = useRegisterMutation({
618
- onSuccess: () => {
619
- alert('Registration successful! Please check your email.');
620
- },
621
- });
622
-
623
- const handleRegister = (data: {
624
- email: string;
625
- password: string;
626
- username: string;
627
- }) => {
628
- register.mutate({
629
- input: {
630
- email: data.email,
631
- password: data.password,
632
- username: data.username,
633
- },
634
- });
635
- };
636
-
637
- return (
638
- <button
639
- onClick={() =>
640
- handleRegister({
641
- email: 'new@example.com',
642
- password: 'secret',
643
- username: 'newuser',
644
- })
645
- }
646
- >
647
- Register
648
- </button>
649
- );
650
- }
651
-
652
- // Logout
653
- function LogoutButton() {
654
- const logout = useLogoutMutation({
655
- onSuccess: () => {
656
- localStorage.removeItem('token');
657
- window.location.href = '/login';
658
- },
659
- });
660
-
661
- return <button onClick={() => logout.mutate({ input: {} })}>Logout</button>;
662
- }
663
-
664
- // Forgot Password
665
- function ForgotPasswordForm() {
666
- const forgotPassword = useForgotPasswordMutation({
667
- onSuccess: () => {
668
- alert('Password reset email sent!');
374
+ if (token) localStorage.setItem('token', token);
669
375
  },
670
376
  });
671
377
 
672
378
  return (
673
- <button
674
- onClick={() =>
675
- forgotPassword.mutate({ input: { email: 'user@example.com' } })
676
- }
677
- >
678
- Reset Password
379
+ <button onClick={() => login.mutate({ input: { email: 'user@example.com', password: 'secret' } })}>
380
+ Login
679
381
  </button>
680
382
  );
681
383
  }
682
384
  ```
683
385
 
684
- ### Filtering
386
+ ### Centralized Query Keys
685
387
 
686
- All filter types from your PostGraphile schema are available:
388
+ The codegen generates a centralized query key factory for type-safe cache management:
687
389
 
688
390
  ```tsx
689
- // String filters
690
- useCarsQuery({
691
- filter: {
692
- brand: {
693
- equalTo: 'Tesla',
694
- notEqualTo: 'Ford',
695
- in: ['Tesla', 'BMW', 'Mercedes'],
696
- notIn: ['Unknown'],
697
- contains: 'es', // LIKE '%es%'
698
- startsWith: 'Tes', // LIKE 'Tes%'
699
- endsWith: 'la', // LIKE '%la'
700
- includesInsensitive: 'TESLA', // Case-insensitive
701
- },
702
- },
703
- });
704
-
705
- // Number filters
706
- useProductsQuery({
707
- filter: {
708
- price: {
709
- equalTo: 100,
710
- greaterThan: 50,
711
- greaterThanOrEqualTo: 50,
712
- lessThan: 200,
713
- lessThanOrEqualTo: 200,
714
- },
715
- },
716
- });
391
+ import { userKeys, invalidate } from './generated/hooks';
392
+ import { useQueryClient } from '@tanstack/react-query';
717
393
 
718
- // Boolean filters
719
- useUsersQuery({
720
- filter: {
721
- isActive: { equalTo: true },
722
- isAdmin: { equalTo: false },
723
- },
724
- });
394
+ // Query key structure
395
+ userKeys.all; // ['user']
396
+ userKeys.lists(); // ['user', 'list']
397
+ userKeys.list({ first: 10 }); // ['user', 'list', { first: 10 }]
398
+ userKeys.details(); // ['user', 'detail']
399
+ userKeys.detail('user-123'); // ['user', 'detail', 'user-123']
725
400
 
726
- // Date/DateTime filters
727
- useOrdersQuery({
728
- filter: {
729
- createdAt: {
730
- greaterThan: '2024-01-01T00:00:00Z',
731
- lessThan: '2024-12-31T23:59:59Z',
732
- },
733
- },
734
- });
735
-
736
- // Null checks
737
- useUsersQuery({
738
- filter: {
739
- deletedAt: { isNull: true }, // Only non-deleted
740
- },
741
- });
742
-
743
- // Logical operators
744
- useUsersQuery({
745
- filter: {
746
- // AND (implicit)
747
- isActive: { equalTo: true },
748
- role: { equalTo: 'ADMIN' },
749
- },
750
- });
751
-
752
- useUsersQuery({
753
- filter: {
754
- // OR
755
- or: [{ role: { equalTo: 'ADMIN' } }, { role: { equalTo: 'MODERATOR' } }],
756
- },
757
- });
758
-
759
- useUsersQuery({
760
- filter: {
761
- // Complex: active AND (admin OR moderator)
762
- and: [
763
- { isActive: { equalTo: true } },
764
- {
765
- or: [
766
- { role: { equalTo: 'ADMIN' } },
767
- { role: { equalTo: 'MODERATOR' } },
768
- ],
769
- },
770
- ],
771
- },
772
- });
773
-
774
- useUsersQuery({
775
- filter: {
776
- // NOT
777
- not: { status: { equalTo: 'DELETED' } },
778
- },
779
- });
780
- ```
781
-
782
- ### Ordering
783
-
784
- ```tsx
785
- // Single order
786
- useCarsQuery({
787
- orderBy: ['CREATED_AT_DESC'],
788
- });
789
-
790
- // Multiple orders (fallback)
791
- useCarsQuery({
792
- orderBy: ['BRAND_ASC', 'CREATED_AT_DESC'],
793
- });
794
-
795
- // Available OrderBy values per table:
796
- // - PRIMARY_KEY_ASC / PRIMARY_KEY_DESC
797
- // - NATURAL
798
- // - {FIELD_NAME}_ASC / {FIELD_NAME}_DESC
799
- ```
800
-
801
- ### Pagination
802
-
803
- ```tsx
804
- // First N records
805
- useCarsQuery({ first: 10 });
806
-
807
- // Last N records
808
- useCarsQuery({ last: 10 });
809
-
810
- // Offset pagination
811
- useCarsQuery({ first: 10, offset: 20 }); // Skip 20, take 10
812
-
813
- // Cursor-based pagination
814
- function PaginatedList() {
815
- const [cursor, setCursor] = useState<string | null>(null);
816
-
817
- const { data } = useCarsQuery({
818
- first: 10,
819
- after: cursor,
820
- });
821
-
822
- return (
823
- <div>
824
- {data?.cars.nodes.map((car) => (
825
- <div key={car.id}>{car.brand}</div>
826
- ))}
827
-
828
- {data?.cars.pageInfo.hasNextPage && (
829
- <button onClick={() => setCursor(data.cars.pageInfo.endCursor)}>
830
- Load More
831
- </button>
832
- )}
833
- </div>
834
- );
835
- }
836
-
837
- // PageInfo structure
838
- // {
839
- // hasNextPage: boolean;
840
- // hasPreviousPage: boolean;
841
- // startCursor: string | null;
842
- // endCursor: string | null;
843
- // }
844
- ```
845
-
846
- ### React Query Options
847
-
848
- All hooks accept standard React Query options:
849
-
850
- ```tsx
851
- // Query hooks
852
- useCarsQuery(
853
- { first: 10 }, // Variables
854
- {
855
- // React Query options
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
862
- retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
863
- placeholderData: previousData, // Show previous data while loading
864
- select: (data) => data.cars.nodes, // Transform data
865
- }
866
- );
867
-
868
- // Mutation hooks
869
- useCreateCarMutation({
870
- onSuccess: (data, variables, context) => {
871
- console.log('Created:', data);
872
- queryClient.invalidateQueries({ queryKey: ['cars'] });
873
- },
874
- onError: (error, variables, context) => {
875
- console.error('Error:', error);
876
- },
877
- onSettled: (data, error, variables, context) => {
878
- console.log('Mutation completed');
879
- },
880
- onMutate: async (variables) => {
881
- // Optimistic update
882
- await queryClient.cancelQueries({ queryKey: ['cars'] });
883
- const previousCars = queryClient.getQueryData(['cars']);
884
- queryClient.setQueryData(['cars'], (old) => ({
885
- ...old,
886
- cars: {
887
- ...old.cars,
888
- nodes: [...old.cars.nodes, { id: 'temp', ...variables.input.car }],
889
- },
890
- }));
891
- return { previousCars };
892
- },
893
- });
894
- ```
895
-
896
- ### Cache Invalidation
897
-
898
- ```tsx
899
- import { useQueryClient } from '@tanstack/react-query';
900
- import { useCreateCarMutation, useCarsQuery } from './generated/hooks';
901
-
902
- function CreateCarWithInvalidation() {
903
- const queryClient = useQueryClient();
904
-
905
- const createCar = useCreateCarMutation({
906
- onSuccess: () => {
907
- // Invalidate all car queries to refetch
908
- queryClient.invalidateQueries({ queryKey: ['cars'] });
909
-
910
- // Or invalidate specific queries
911
- queryClient.invalidateQueries({ queryKey: ['cars', { first: 10 }] });
912
- },
913
- });
914
-
915
- // ...
916
- }
917
- ```
918
-
919
- ### Centralized Query Keys
920
-
921
- The codegen generates a centralized query key factory following the [lukemorales query-key-factory](https://tanstack.com/query/docs/framework/react/community/lukemorales-query-key-factory) pattern. This provides type-safe cache management with autocomplete support.
922
-
923
- #### Generated Files
924
-
925
- | File | Purpose |
926
- | ------------------ | ------------------------------------------------------- |
927
- | `query-keys.ts` | Query key factories for all entities |
928
- | `mutation-keys.ts` | Mutation key factories for tracking in-flight mutations |
929
- | `invalidation.ts` | Type-safe cache invalidation helpers |
930
-
931
- #### Using Query Keys
932
-
933
- ```tsx
934
- import { userKeys, invalidate } from './generated/hooks';
935
- import { useQueryClient } from '@tanstack/react-query';
936
-
937
- // Query key structure
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']
943
-
944
- // Granular cache invalidation
945
- const queryClient = useQueryClient();
946
-
947
- // Invalidate ALL user queries
948
- queryClient.invalidateQueries({ queryKey: userKeys.all });
949
-
950
- // Invalidate only list queries
951
- queryClient.invalidateQueries({ queryKey: userKeys.lists() });
952
-
953
- // Invalidate a specific user
954
- queryClient.invalidateQueries({ queryKey: userKeys.detail(userId) });
955
- ```
956
-
957
- #### Invalidation Helpers
958
-
959
- Type-safe invalidation utilities:
960
-
961
- ```tsx
962
- import { invalidate, remove } from './generated/hooks';
963
-
964
- // Invalidate queries (triggers refetch)
965
- invalidate.user.all(queryClient);
966
- invalidate.user.lists(queryClient);
967
- invalidate.user.detail(queryClient, userId);
968
-
969
- // Remove from cache (for delete operations)
970
- remove.user(queryClient, userId);
971
- ```
972
-
973
- #### Mutation Key Tracking
974
-
975
- Track in-flight mutations with `useIsMutating`:
976
-
977
- ```tsx
978
- import { useIsMutating } from '@tanstack/react-query';
979
- import { userMutationKeys } from './generated/hooks';
980
-
981
- function UserList() {
982
- // Check if any user mutations are in progress
983
- const isMutating = useIsMutating({ mutationKey: userMutationKeys.all });
984
-
985
- // Check if a specific user is being deleted
986
- const isDeleting = useIsMutating({
987
- mutationKey: userMutationKeys.delete(userId),
988
- });
989
-
990
- return (
991
- <div>
992
- {isMutating > 0 && <Spinner />}
993
- <button disabled={isDeleting > 0}>Delete</button>
994
- </div>
995
- );
996
- }
997
- ```
998
-
999
- #### Optimistic Updates with Query Keys
1000
-
1001
- ```tsx
1002
- import { useCreateUserMutation, userKeys } from './generated/hooks';
1003
-
1004
- const createUser = useCreateUserMutation({
1005
- onMutate: async (newUser) => {
1006
- // Cancel outgoing refetches
1007
- await queryClient.cancelQueries({ queryKey: userKeys.lists() });
1008
-
1009
- // Snapshot previous value
1010
- const previous = queryClient.getQueryData(userKeys.list());
1011
-
1012
- // Optimistically update cache
1013
- queryClient.setQueryData(userKeys.list(), (old) => ({
1014
- ...old,
1015
- users: {
1016
- ...old.users,
1017
- nodes: [...old.users.nodes, { id: 'temp', ...newUser.input.user }],
1018
- },
1019
- }));
1020
-
1021
- return { previous };
1022
- },
1023
- onError: (err, variables, context) => {
1024
- // Rollback on error
1025
- queryClient.setQueryData(userKeys.list(), context.previous);
1026
- },
1027
- onSettled: () => {
1028
- // Refetch after mutation
1029
- queryClient.invalidateQueries({ queryKey: userKeys.lists() });
1030
- },
1031
- });
1032
- ```
1033
-
1034
- #### Configuration
1035
-
1036
- Query key generation is enabled by default. Configure in your config file:
1037
-
1038
- ```typescript
1039
- // graphql-sdk.config.ts
1040
- export default defineConfig({
1041
- endpoint: 'https://api.example.com/graphql',
1042
-
1043
- queryKeys: {
1044
- // Generate scope-aware keys (default: true)
1045
- generateScopedKeys: true,
1046
-
1047
- // Generate mutation keys (default: true)
1048
- generateMutationKeys: true,
1049
-
1050
- // Generate invalidation helpers (default: true)
1051
- generateCascadeHelpers: true,
1052
-
1053
- // Define entity relationships for cascade invalidation
1054
- relationships: {
1055
- table: { parent: 'database', foreignKey: 'databaseId' },
1056
- field: { parent: 'table', foreignKey: 'tableId' },
1057
- },
1058
- },
1059
- });
1060
- ```
1061
-
1062
- For detailed documentation on query key factory design and implementation, see [docs/QUERY-KEY-FACTORY.md](./docs/QUERY-KEY-FACTORY.md).
1063
-
1064
- ### Prefetching
1065
-
1066
- ```tsx
1067
- import { useQueryClient } from '@tanstack/react-query';
1068
-
1069
- function CarListItem({ car }: { car: Car }) {
1070
- const queryClient = useQueryClient();
1071
-
1072
- // Prefetch details on hover
1073
- const handleHover = () => {
1074
- queryClient.prefetchQuery({
1075
- queryKey: ['car', { id: car.id }],
1076
- queryFn: () => execute(carQuery, { id: car.id }),
1077
- });
1078
- };
1079
-
1080
- return (
1081
- <Link to={`/cars/${car.id}`} onMouseEnter={handleHover}>
1082
- {car.brand}
1083
- </Link>
1084
- );
1085
- }
1086
- ```
1087
-
1088
- ### Type Exports
1089
-
1090
- All generated types are exported for use in your application:
1091
-
1092
- ```tsx
1093
- import type {
1094
- // Entity types
1095
- Car,
1096
- User,
1097
- Product,
1098
- Order,
1099
-
1100
- // Filter types
1101
- CarFilter,
1102
- UserFilter,
1103
- StringFilter,
1104
- IntFilter,
1105
- UUIDFilter,
1106
- DatetimeFilter,
1107
-
1108
- // OrderBy types
1109
- CarsOrderBy,
1110
- UsersOrderBy,
1111
-
1112
- // Input types
1113
- CreateCarInput,
1114
- UpdateCarInput,
1115
- CarPatch,
1116
- LoginInput,
1117
-
1118
- // Payload types
1119
- LoginPayload,
1120
- CreateCarPayload,
1121
- } from './generated/hooks';
1122
-
1123
- // Use in your components
1124
- interface CarListProps {
1125
- filter?: CarFilter;
1126
- orderBy?: CarsOrderBy[];
1127
- }
1128
-
1129
- function CarList({ filter, orderBy }: CarListProps) {
1130
- const { data } = useCarsQuery({ filter, orderBy, first: 10 });
1131
- // ...
1132
- }
1133
- ```
1134
-
1135
- ### Error Handling
1136
-
1137
- ```tsx
1138
- function CarList() {
1139
- const { data, isLoading, isError, error, failureCount } = useCarsQuery({
1140
- first: 10,
1141
- });
1142
-
1143
- if (isLoading) {
1144
- return <div>Loading...</div>;
1145
- }
1146
-
1147
- if (isError) {
1148
- // error is typed as Error
1149
- return (
1150
- <div>
1151
- <p>Error: {error.message}</p>
1152
- <p>Failed {failureCount} times</p>
1153
- <button onClick={() => refetch()}>Retry</button>
1154
- </div>
1155
- );
1156
- }
1157
-
1158
- return <div>{/* render data */}</div>;
1159
- }
1160
-
1161
- // Global error handling
1162
- const queryClient = new QueryClient({
1163
- defaultOptions: {
1164
- queries: {
1165
- onError: (error) => {
1166
- console.error('Query error:', error);
1167
- // Show toast, log to monitoring, etc.
1168
- },
1169
- },
1170
- mutations: {
1171
- onError: (error) => {
1172
- console.error('Mutation error:', error);
1173
- },
1174
- },
1175
- },
1176
- });
1177
- ```
1178
-
1179
- ### Generated Types Reference
1180
-
1181
- ```typescript
1182
- // Query hook return type
1183
- type UseQueryResult<TData> = {
1184
- data: TData | undefined;
1185
- error: Error | null;
1186
- isLoading: boolean;
1187
- isFetching: boolean;
1188
- isError: boolean;
1189
- isSuccess: boolean;
1190
- refetch: () => Promise<QueryObserverResult<TData>>;
1191
- // ... more React Query properties
1192
- };
1193
-
1194
- // Mutation hook return type
1195
- type UseMutationResult<TData, TVariables> = {
1196
- data: TData | undefined;
1197
- error: Error | null;
1198
- isLoading: boolean; // deprecated, use isPending
1199
- isPending: boolean;
1200
- isError: boolean;
1201
- isSuccess: boolean;
1202
- mutate: (variables: TVariables) => void;
1203
- mutateAsync: (variables: TVariables) => Promise<TData>;
1204
- reset: () => void;
1205
- // ... more React Query properties
1206
- };
1207
-
1208
- // Connection result (for list queries)
1209
- interface CarsConnection {
1210
- nodes: Car[];
1211
- totalCount: number;
1212
- pageInfo: PageInfo;
1213
- }
1214
-
1215
- interface PageInfo {
1216
- hasNextPage: boolean;
1217
- hasPreviousPage: boolean;
1218
- startCursor: string | null;
1219
- endCursor: string | null;
1220
- }
1221
- ```
1222
-
1223
- ---
401
+ // Invalidation helpers
402
+ const queryClient = useQueryClient();
403
+ invalidate.user.all(queryClient);
404
+ invalidate.user.lists(queryClient);
405
+ invalidate.user.detail(queryClient, userId);
406
+ ```
1224
407
 
1225
408
  ## ORM Client
1226
409
 
1227
- The ORM client provides a Prisma-like fluent API for GraphQL operations without React dependencies.
1228
-
1229
- ### Generated Output Structure
1230
-
1231
- ```
1232
- generated/orm/
1233
- ├── index.ts # createClient() factory + re-exports
1234
- ├── client.ts # OrmClient class (GraphQL executor)
1235
- ├── query-builder.ts # QueryBuilder with execute(), unwrap(), etc.
1236
- ├── select-types.ts # Type utilities for select inference
1237
- ├── input-types.ts # All generated types (entities, filters, inputs, etc.)
1238
- ├── types.ts # Re-exports from input-types
1239
- ├── models/
1240
- │ ├── index.ts # Barrel export for all models
1241
- │ ├── user.ts # UserModel class
1242
- │ ├── product.ts # ProductModel class
1243
- │ ├── order.ts # OrderModel class
1244
- │ └── ...
1245
- ├── query/
1246
- │ └── index.ts # Custom query operations (currentUser, etc.)
1247
- └── mutation/
1248
- └── index.ts # Custom mutation operations (login, register, etc.)
1249
- ```
1250
-
1251
- ### Basic Usage
1252
-
1253
- ```typescript
1254
- import { createClient } from './generated/orm';
1255
-
1256
- // Create client instance
1257
- const db = createClient({
1258
- endpoint: 'https://api.example.com/graphql',
1259
- headers: { Authorization: 'Bearer <token>' },
1260
- });
1261
-
1262
- // Query users
1263
- const result = await db.user
1264
- .findMany({
1265
- select: { id: true, username: true, email: true },
1266
- first: 20,
1267
- })
1268
- .execute();
1269
-
1270
- if (result.ok) {
1271
- console.log(result.data.users.nodes);
1272
- } else {
1273
- console.error(result.errors);
1274
- }
1275
-
1276
- // Find first matching user
1277
- const user = await db.user
1278
- .findFirst({
1279
- select: { id: true, username: true },
1280
- where: { username: { equalTo: 'john' } },
1281
- })
1282
- .execute();
1283
-
1284
- // Create a user
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();
1291
-
1292
- // Update a user
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();
1300
-
1301
- // Delete a user
1302
- const deleted = await db.user
1303
- .delete({
1304
- where: { id: 'user-id' },
1305
- })
1306
- .execute();
1307
- ```
1308
-
1309
- ### Select & Type Inference
1310
-
1311
- The ORM uses **const generics** to infer return types based on your select clause. Only the fields you select will be in the return type.
1312
-
1313
- ```typescript
1314
- // Select specific fields - return type is narrowed
1315
- const users = await db.user
1316
- .findMany({
1317
- select: { id: true, username: true }, // Only id and username
1318
- })
1319
- .unwrap();
1320
-
1321
- // TypeScript knows the exact shape:
1322
- // users.users.nodes[0] is { id: string; username: string | null }
1323
-
1324
- // If you try to access a field you didn't select, TypeScript will error:
1325
- // users.users.nodes[0].email // Error: Property 'email' does not exist
1326
-
1327
- // Without select, you get the full entity type
1328
- const allFields = await db.user.findMany({}).unwrap();
1329
- // allFields.users.nodes[0] has all User fields
1330
- ```
1331
-
1332
- ### Relations
1333
-
1334
- Relations are fully typed in Select types. The ORM supports all PostGraphile relation types:
1335
-
1336
- #### BelongsTo Relations (Single Entity)
1337
-
1338
- ```typescript
1339
- // Order.customer is a belongsTo relation to User
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();
1352
-
1353
- // TypeScript knows:
1354
- // orders.orders.nodes[0].customer is { id: string; username: string | null; displayName: string | null }
1355
- ```
1356
-
1357
- #### HasMany Relations (Connection/Collection)
1358
-
1359
- ```typescript
1360
- // Order.orderItems is a hasMany relation to OrderItem
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();
1375
-
1376
- // orders.orders.nodes[0].orderItems is a connection:
1377
- // { nodes: Array<{ id: string; quantity: number | null; price: number | null }>, totalCount: number, pageInfo: PageInfo }
1378
- ```
1379
-
1380
- #### ManyToMany Relations
1381
-
1382
- ```typescript
1383
- // Order.productsByOrderItemOrderIdAndProductId is a manyToMany through OrderItem
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();
1395
- ```
1396
-
1397
- #### Deeply Nested Relations
1398
-
1399
- ```typescript
1400
- // Multiple levels of nesting
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
- },
1413
- },
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();
1431
- ```
1432
-
1433
- ### Filtering & Ordering
1434
-
1435
- #### Filter Types
1436
-
1437
- Each entity has a generated Filter type with field-specific operators:
1438
-
1439
- ```typescript
1440
- // String filters
1441
- where: {
1442
- username: {
1443
- equalTo: 'john',
1444
- notEqualTo: 'jane',
1445
- in: ['john', 'jane', 'bob'],
1446
- notIn: ['admin'],
1447
- contains: 'oh', // LIKE '%oh%'
1448
- startsWith: 'j', // LIKE 'j%'
1449
- endsWith: 'n', // LIKE '%n'
1450
- includesInsensitive: 'OH', // Case-insensitive
1451
- }
1452
- }
1453
-
1454
- // Number filters (Int, Float, BigInt, BigFloat)
1455
- where: {
1456
- price: {
1457
- equalTo: 100,
1458
- greaterThan: 50,
1459
- greaterThanOrEqualTo: 50,
1460
- lessThan: 200,
1461
- lessThanOrEqualTo: 200,
1462
- in: [100, 200, 300],
1463
- }
1464
- }
1465
-
1466
- // Boolean filters
1467
- where: {
1468
- isActive: { equalTo: true }
1469
- }
1470
-
1471
- // UUID filters
1472
- where: {
1473
- id: {
1474
- equalTo: 'uuid-string',
1475
- in: ['uuid-1', 'uuid-2'],
1476
- }
1477
- }
1478
-
1479
- // DateTime filters
1480
- where: {
1481
- createdAt: {
1482
- greaterThan: '2024-01-01T00:00:00Z',
1483
- lessThan: '2024-12-31T23:59:59Z',
1484
- }
1485
- }
1486
-
1487
- // JSON filters
1488
- where: {
1489
- metadata: {
1490
- contains: { key: 'value' },
1491
- containsKey: 'key',
1492
- containsAllKeys: ['key1', 'key2'],
1493
- }
1494
- }
410
+ The ORM client provides a Prisma-like fluent API for GraphQL operations without React dependencies.
1495
411
 
1496
- // Null checks (all filters)
1497
- where: {
1498
- deletedAt: { isNull: true }
1499
- }
412
+ ### Generated Output Structure
413
+
414
+ ```
415
+ generated/orm/
416
+ ├── index.ts # createClient() factory + re-exports
417
+ ├── client.ts # OrmClient class (GraphQL executor)
418
+ ├── query-builder.ts # QueryBuilder with execute(), unwrap(), etc.
419
+ ├── select-types.ts # Type utilities for select inference
420
+ ├── input-types.ts # All generated types
421
+ ├── models/
422
+ │ ├── index.ts # Barrel export for all models
423
+ │ ├── user.ts # UserModel class
424
+ │ └── ...
425
+ ├── query/
426
+ │ └── index.ts # Custom query operations
427
+ └── mutation/
428
+ └── index.ts # Custom mutation operations
1500
429
  ```
1501
430
 
1502
- #### Logical Operators
431
+ ### Basic Usage
1503
432
 
1504
433
  ```typescript
1505
- // AND (implicit - all conditions must match)
1506
- where: {
1507
- isActive: { equalTo: true },
1508
- username: { startsWith: 'j' }
1509
- }
1510
-
1511
- // AND (explicit)
1512
- where: {
1513
- and: [
1514
- { isActive: { equalTo: true } },
1515
- { username: { startsWith: 'j' } }
1516
- ]
1517
- }
434
+ import { createClient } from './generated/orm';
1518
435
 
1519
- // OR
1520
- where: {
1521
- or: [
1522
- { status: { equalTo: 'ACTIVE' } },
1523
- { status: { equalTo: 'PENDING' } }
1524
- ]
1525
- }
436
+ const db = createClient({
437
+ endpoint: 'https://api.example.com/graphql',
438
+ headers: { Authorization: 'Bearer <token>' },
439
+ });
1526
440
 
1527
- // NOT
1528
- where: {
1529
- not: { status: { equalTo: 'DELETED' } }
1530
- }
441
+ // Query users
442
+ const result = await db.user
443
+ .findMany({
444
+ select: { id: true, username: true, email: true },
445
+ first: 20,
446
+ })
447
+ .execute();
1531
448
 
1532
- // Complex combinations
1533
- where: {
1534
- and: [
1535
- { isActive: { equalTo: true } },
1536
- {
1537
- or: [
1538
- { role: { equalTo: 'ADMIN' } },
1539
- { role: { equalTo: 'MODERATOR' } }
1540
- ]
1541
- }
1542
- ]
449
+ if (result.ok) {
450
+ console.log(result.data.users.nodes);
451
+ } else {
452
+ console.error(result.errors);
1543
453
  }
1544
- ```
1545
454
 
1546
- #### Ordering
455
+ // Create a user
456
+ const newUser = await db.user
457
+ .create({
458
+ data: { username: 'john', email: 'john@example.com' },
459
+ select: { id: true, username: true },
460
+ })
461
+ .execute();
1547
462
 
1548
- ```typescript
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
- ],
463
+ // Update a user
464
+ const updated = await db.user
465
+ .update({
466
+ where: { id: 'user-id' },
467
+ data: { displayName: 'John Doe' },
468
+ select: { id: true, displayName: true },
1556
469
  })
1557
- .unwrap();
470
+ .execute();
1558
471
 
1559
- // Available OrderBy values (generated per entity):
1560
- // - PRIMARY_KEY_ASC / PRIMARY_KEY_DESC
1561
- // - NATURAL
1562
- // - {FIELD_NAME}_ASC / {FIELD_NAME}_DESC
472
+ // Delete a user
473
+ const deleted = await db.user
474
+ .delete({ where: { id: 'user-id' } })
475
+ .execute();
1563
476
  ```
1564
477
 
1565
- ### Pagination
478
+ ### Select & Type Inference
1566
479
 
1567
- The ORM supports cursor-based and offset pagination:
480
+ The ORM uses const generics to infer return types based on your select clause:
1568
481
 
1569
482
  ```typescript
1570
- // First N records
1571
- const first10 = await db.user
483
+ const users = await db.user
1572
484
  .findMany({
1573
- select: { id: true },
1574
- first: 10,
485
+ select: { id: true, username: true },
1575
486
  })
1576
487
  .unwrap();
1577
488
 
1578
- // Last N records
1579
- const last10 = await db.user
1580
- .findMany({
1581
- select: { id: true },
1582
- last: 10,
1583
- })
1584
- .unwrap();
489
+ // TypeScript knows the exact shape:
490
+ // users.users.nodes[0] is { id: string; username: string | null }
1585
491
 
1586
- // Cursor-based pagination (after/before)
1587
- const page1 = await db.user
1588
- .findMany({
1589
- select: { id: true },
1590
- first: 10,
1591
- })
1592
- .unwrap();
492
+ // Accessing unselected fields is a compile error:
493
+ // users.users.nodes[0].email // Error: Property 'email' does not exist
494
+ ```
495
+
496
+ ### Relations
1593
497
 
1594
- const endCursor = page1.users.pageInfo.endCursor;
498
+ Relations are fully typed in Select types:
1595
499
 
1596
- const page2 = await db.user
500
+ ```typescript
501
+ // BelongsTo relation
502
+ const orders = await db.order
1597
503
  .findMany({
1598
- select: { id: true },
1599
- first: 10,
1600
- after: endCursor, // Get records after this cursor
504
+ select: {
505
+ id: true,
506
+ customer: {
507
+ select: { id: true, username: true },
508
+ },
509
+ },
1601
510
  })
1602
511
  .unwrap();
1603
512
 
1604
- // Offset pagination
1605
- const page3 = await db.user
513
+ // HasMany relation with pagination
514
+ const users = await db.user
1606
515
  .findMany({
1607
- select: { id: true },
1608
- first: 10,
1609
- offset: 20, // Skip first 20 records
516
+ select: {
517
+ id: true,
518
+ orders: {
519
+ select: { id: true, total: true },
520
+ first: 10,
521
+ orderBy: ['CREATED_AT_DESC'],
522
+ },
523
+ },
1610
524
  })
1611
525
  .unwrap();
1612
-
1613
- // PageInfo structure
1614
- // {
1615
- // hasNextPage: boolean;
1616
- // hasPreviousPage: boolean;
1617
- // startCursor: string | null;
1618
- // endCursor: string | null;
1619
- // }
1620
-
1621
- // Total count is always included
1622
- console.log(page1.users.totalCount); // Total matching records
1623
526
  ```
1624
527
 
1625
528
  ### Error Handling
1626
529
 
1627
530
  The ORM provides multiple ways to handle errors:
1628
531
 
1629
- #### Discriminated Union (Recommended)
1630
-
1631
532
  ```typescript
1632
- const result = await db.user
1633
- .findMany({
1634
- select: { id: true },
1635
- })
1636
- .execute();
533
+ // Discriminated union (recommended)
534
+ const result = await db.user.findMany({ select: { id: true } }).execute();
1637
535
 
1638
536
  if (result.ok) {
1639
- // TypeScript knows result.data is non-null
1640
537
  console.log(result.data.users.nodes);
1641
- // result.errors is undefined in this branch
1642
538
  } else {
1643
- // TypeScript knows result.errors is non-null
1644
- console.error(result.errors[0].message);
1645
- // result.data is null in this branch
539
+ console.error(result.errors);
1646
540
  }
1647
- ```
1648
-
1649
- #### `.unwrap()` - Throw on Error
1650
-
1651
- ```typescript
1652
- import { GraphQLRequestError } from './generated/orm';
1653
541
 
542
+ // .unwrap() - throws on error
1654
543
  try {
1655
- // Throws GraphQLRequestError if query fails
1656
- const data = await db.user
1657
- .findMany({
1658
- select: { id: true },
1659
- })
1660
- .unwrap();
1661
-
1662
- console.log(data.users.nodes);
544
+ const data = await db.user.findMany({ select: { id: true } }).unwrap();
1663
545
  } catch (error) {
1664
546
  if (error instanceof GraphQLRequestError) {
1665
547
  console.error('GraphQL errors:', error.errors);
1666
- console.error('Message:', error.message);
1667
548
  }
1668
549
  }
1669
- ```
1670
-
1671
- #### `.unwrapOr()` - Default Value on Error
1672
550
 
1673
- ```typescript
1674
- // Returns default value if query fails (no throwing)
551
+ // .unwrapOr() - returns default on error
1675
552
  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
- });
1686
-
1687
- // Always returns data (either real or default)
1688
- console.log(data.users.nodes);
1689
- ```
1690
-
1691
- #### `.unwrapOrElse()` - Callback on Error
553
+ .findMany({ select: { id: true } })
554
+ .unwrapOr({ users: { nodes: [], totalCount: 0, pageInfo: { hasNextPage: false, hasPreviousPage: false } } });
1692
555
 
1693
- ```typescript
1694
- // Call a function to handle errors and return fallback
556
+ // .unwrapOrElse() - callback on error
1695
557
  const data = await db.user
1696
- .findMany({
1697
- select: { id: true },
1698
- })
558
+ .findMany({ select: { id: true } })
1699
559
  .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
- };
560
+ console.error('Query failed:', errors);
561
+ return { users: { nodes: [], totalCount: 0, pageInfo: { hasNextPage: false, hasPreviousPage: false } } };
1711
562
  });
1712
563
  ```
1713
564
 
1714
- #### Error Types
565
+ ### Custom Operations
566
+
567
+ Custom queries and mutations are available on `db.query` and `db.mutation`:
1715
568
 
1716
569
  ```typescript
1717
- interface GraphQLError {
1718
- message: string;
1719
- locations?: { line: number; column: number }[];
1720
- path?: (string | number)[];
1721
- extensions?: Record<string, unknown>;
1722
- }
570
+ // Custom query
571
+ const currentUser = await db.query
572
+ .currentUser({ select: { id: true, username: true } })
573
+ .unwrap();
1723
574
 
1724
- class GraphQLRequestError extends Error {
1725
- readonly errors: GraphQLError[];
1726
- readonly data: unknown; // Partial data if available
1727
- }
575
+ // Custom mutation
576
+ const login = await db.mutation
577
+ .login(
578
+ { input: { email: 'user@example.com', password: 'secret' } },
579
+ { select: { apiToken: { select: { accessToken: true } } } }
580
+ )
581
+ .unwrap();
1728
582
 
1729
- type QueryResult<T> =
1730
- | { ok: true; data: T; errors: undefined }
1731
- | { ok: false; data: null; errors: GraphQLError[] };
583
+ console.log(login.login.apiToken?.accessToken);
1732
584
  ```
1733
585
 
1734
- ### Custom Operations
586
+ ## Configuration
1735
587
 
1736
- Custom queries and mutations (like `login`, `currentUser`, etc.) are available on `db.query` and `db.mutation`:
588
+ ### Config File
1737
589
 
1738
- #### Custom Queries
590
+ Create a `graphql-codegen.config.ts` file:
1739
591
 
1740
592
  ```typescript
1741
- // Query with select
1742
- const currentUser = await db.query
1743
- .currentUser({
1744
- select: { id: true, username: true, email: true },
1745
- })
1746
- .unwrap();
1747
-
1748
- // Query without select (returns full type)
1749
- const me = await db.query.currentUser({}).unwrap();
593
+ import type { GraphQLSDKConfig } from '@constructive-io/graphql-codegen';
1750
594
 
1751
- // Query with arguments
1752
- const node = await db.query
1753
- .nodeById(
1754
- {
1755
- id: 'some-node-id',
1756
- },
1757
- {
1758
- select: { id: true },
1759
- }
1760
- )
1761
- .unwrap();
595
+ export default {
596
+ endpoint: 'https://api.example.com/graphql',
597
+ output: './generated/graphql',
598
+ headers: {
599
+ Authorization: 'Bearer <token>',
600
+ },
601
+ reactQuery: true,
602
+ orm: true,
603
+ } satisfies GraphQLSDKConfig;
1762
604
  ```
1763
605
 
1764
- #### Custom Mutations
606
+ ### Multi-target Configuration
607
+
608
+ For multiple schema sources, export a record of named configs:
1765
609
 
1766
610
  ```typescript
1767
- // Login mutation with typed select
1768
- const login = await db.mutation
1769
- .login(
1770
- {
1771
- input: {
1772
- email: 'user@example.com',
1773
- password: 'secret123',
1774
- },
611
+ import type { GraphQLSDKMultiConfig } from '@constructive-io/graphql-codegen';
612
+
613
+ export default {
614
+ public: {
615
+ endpoint: 'https://api.example.com/graphql',
616
+ output: './generated/public',
617
+ headers: { Authorization: 'Bearer <token>' },
618
+ reactQuery: true,
619
+ },
620
+ admin: {
621
+ schemaFile: './admin.schema.graphql',
622
+ output: './generated/admin',
623
+ orm: true,
624
+ },
625
+ database: {
626
+ db: {
627
+ pgpm: { modulePath: './packages/my-module' },
628
+ schemas: ['public'],
1775
629
  },
1776
- {
1777
- select: {
1778
- clientMutationId: true,
1779
- apiToken: {
1780
- select: {
1781
- accessToken: true,
1782
- accessTokenExpiresAt: true,
1783
- },
1784
- },
1785
- },
1786
- }
1787
- )
1788
- .unwrap();
630
+ output: './generated/db',
631
+ reactQuery: true,
632
+ orm: true,
633
+ },
634
+ } satisfies GraphQLSDKMultiConfig;
635
+ ```
1789
636
 
1790
- console.log(login.login.apiToken?.accessToken);
637
+ Run all targets with `graphql-codegen` or a specific target with `graphql-codegen --target public`.
1791
638
 
1792
- // Register mutation
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();
639
+ ### Glob Patterns
1802
640
 
1803
- // Logout mutation
1804
- await db.mutation
1805
- .logout({
1806
- input: { clientMutationId: 'optional-id' },
1807
- })
1808
- .execute();
641
+ Filter patterns support wildcards:
642
+
643
+ ```typescript
644
+ {
645
+ tables: {
646
+ include: ['User', 'Product', 'Order*'],
647
+ exclude: ['*_archive', 'temp_*'],
648
+ },
649
+ queries: {
650
+ exclude: ['_meta', 'query', '*Debug*'],
651
+ },
652
+ mutations: {
653
+ include: ['create*', 'update*', 'delete*', 'login', 'register', 'logout'],
654
+ },
655
+ }
1809
656
  ```
1810
657
 
1811
- ### Query Builder API
658
+ ## CLI Commands
659
+
660
+ The CLI provides a convenient way to run code generation from the command line.
1812
661
 
1813
- Every operation returns a `QueryBuilder` that can be inspected before execution:
662
+ ### `graphql-sdk generate`
1814
663
 
1815
- ```typescript
1816
- const query = db.user.findMany({
1817
- select: { id: true, username: true },
1818
- where: { isActive: { equalTo: true } },
1819
- first: 10,
1820
- });
664
+ Generate React Query hooks and/or ORM client from various sources.
1821
665
 
1822
- // Inspect the generated GraphQL
1823
- console.log(query.toGraphQL());
1824
- // query UserQuery($where: UserFilter, $first: Int) {
1825
- // users(filter: $where, first: $first) {
1826
- // nodes { id username }
1827
- // totalCount
1828
- // pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1829
- // }
1830
- // }
1831
-
1832
- // Get variables
1833
- console.log(query.getVariables());
1834
- // { where: { isActive: { equalTo: true } }, first: 10 }
1835
-
1836
- // Execute when ready
1837
- const result = await query.execute();
1838
- // Or: const data = await query.unwrap();
666
+ ```bash
667
+ Source Options (choose one):
668
+ -c, --config <path> Path to config file (graphql-sdk.config.ts)
669
+ -e, --endpoint <url> GraphQL endpoint URL
670
+ -s, --schema-file <path> Path to GraphQL schema file (.graphql)
671
+ --pgpm-module-path <path> Path to PGPM module directory
672
+ --pgpm-workspace-path <path> Path to PGPM workspace (requires --pgpm-module-name)
673
+ --pgpm-module-name <name> PGPM module name in workspace
674
+
675
+ Database Options (for pgpm modes):
676
+ --schemas <list> Comma-separated list of PostgreSQL schemas to introspect
677
+ --api-names <list> Comma-separated API names for automatic schema discovery
678
+ (mutually exclusive with --schemas)
679
+
680
+ Generator Options:
681
+ --react-query Generate React Query hooks
682
+ --orm Generate ORM client
683
+ -t, --target <name> Target name in config file
684
+ -o, --output <dir> Output directory
685
+ -a, --authorization <token> Authorization header value
686
+ --skip-custom-operations Only generate table CRUD operations
687
+ --dry-run Preview without writing files
688
+ --keep-db Keep ephemeral database after generation (pgpm modes)
689
+ -v, --verbose Show detailed output
690
+
691
+ Watch Mode Options:
692
+ -w, --watch Watch for schema changes and regenerate
693
+ --poll-interval <ms> Polling interval in milliseconds (default: 5000)
694
+ --debounce <ms> Debounce delay in milliseconds (default: 500)
695
+ --touch <path> Touch file after regeneration
696
+ --no-clear Don't clear console on regeneration
1839
697
  ```
1840
698
 
1841
- ### Client Configuration
699
+ Examples:
1842
700
 
1843
- ```typescript
1844
- import { createClient } from './generated/orm';
701
+ ```bash
702
+ # Generate React Query hooks from an endpoint
703
+ npx graphql-sdk generate --endpoint https://api.example.com/graphql --output ./generated --react-query
1845
704
 
1846
- // Basic configuration
1847
- const db = createClient({
1848
- endpoint: 'https://api.example.com/graphql',
1849
- });
705
+ # Generate ORM client from an endpoint
706
+ npx graphql-sdk generate --endpoint https://api.example.com/graphql --output ./generated --orm
1850
707
 
1851
- // With authentication
1852
- const db = createClient({
1853
- endpoint: 'https://api.example.com/graphql',
1854
- headers: {
1855
- Authorization: 'Bearer <token>',
1856
- 'X-Custom-Header': 'value',
1857
- },
1858
- });
708
+ # Generate both React Query hooks and ORM client
709
+ npx graphql-sdk generate --endpoint https://api.example.com/graphql --output ./generated --react-query --orm
1859
710
 
1860
- // Update headers at runtime
1861
- db.setHeaders({
1862
- Authorization: 'Bearer <new-token>',
1863
- });
711
+ # Generate from a PGPM module
712
+ npx graphql-sdk generate --pgpm-module-path ./packages/my-module --schemas public --react-query
1864
713
 
1865
- // Get current endpoint
1866
- console.log(db.getEndpoint());
714
+ # Generate using apiNames for automatic schema discovery
715
+ npx graphql-sdk generate --pgpm-module-path ./packages/my-module --api-names my_api --react-query --orm
1867
716
  ```
1868
717
 
1869
- ---
718
+ ### `graphql-sdk init`
1870
719
 
1871
- ## Architecture
720
+ Create a configuration file.
1872
721
 
1873
- ### How It Works
722
+ ```bash
723
+ Options:
724
+ -f, --format <format> Config format: ts, js, json (default: ts)
725
+ -o, --output <path> Output path for config file
726
+ ```
727
+
728
+ ### `graphql-sdk introspect`
729
+
730
+ Inspect schema without generating code.
1874
731
 
1875
- 1. **Fetch `_meta`**: Gets table metadata from PostGraphile's `_meta` query including:
1876
- - Table names and fields
1877
- - Relations (belongsTo, hasMany, manyToMany)
1878
- - Constraints (primary key, foreign key, unique)
1879
- - Inflection rules (query names, type names)
732
+ ```bash
733
+ Options:
734
+ -e, --endpoint <url> GraphQL endpoint URL
735
+ --json Output as JSON
736
+ -v, --verbose Show detailed output
737
+ ```
1880
738
 
1881
- 2. **Fetch `__schema`**: Gets full schema introspection for ALL operations:
1882
- - All queries (including custom ones like `currentUser`)
1883
- - All mutations (including custom ones like `login`, `register`)
1884
- - All types (entities, inputs, enums, scalars)
739
+ ## Architecture
1885
740
 
1886
- 3. **Filter Operations**: Removes table CRUD from custom operations to avoid duplicates
741
+ ### How It Works
1887
742
 
1888
- 4. **Generate Code**: Creates type-safe code using AST-based generation (`ts-morph`)
743
+ The codegen fetches `_meta` for table metadata (names, fields, relations, constraints) and `__schema` for full schema introspection (all queries, mutations, types). It then filters operations to avoid duplicates and generates type-safe code using Babel AST.
1889
744
 
1890
745
  ### Code Generation Pipeline
1891
746
 
@@ -1924,68 +779,16 @@ PostGraphile Endpoint
1924
779
  └───────────────────┘
1925
780
  ```
1926
781
 
1927
- ### Key Concepts
1928
-
1929
- #### Type Inference with Const Generics
782
+ ### Type Inference with Const Generics
1930
783
 
1931
784
  The ORM uses TypeScript const generics to infer return types:
1932
785
 
1933
786
  ```typescript
1934
- // Model method signature
1935
787
  findMany<const S extends UserSelect>(
1936
788
  args?: FindManyArgs<S, UserFilter, UsersOrderBy>
1937
789
  ): QueryBuilder<{ users: ConnectionResult<InferSelectResult<User, S>> }>
1938
-
1939
- // InferSelectResult maps select object to result type
1940
- type InferSelectResult<TEntity, TSelect> = {
1941
- [K in keyof TSelect & keyof TEntity as TSelect[K] extends false | undefined
1942
- ? never
1943
- : K]: TSelect[K] extends true
1944
- ? TEntity[K]
1945
- : TSelect[K] extends { select: infer NestedSelect }
1946
- ? /* handle nested select */
1947
- : TEntity[K];
1948
- };
1949
- ```
1950
-
1951
- #### Select Types with Relations
1952
-
1953
- Select types include relation fields with proper typing:
1954
-
1955
- ```typescript
1956
- export type OrderSelect = {
1957
- // Scalar fields
1958
- id?: boolean;
1959
- orderNumber?: boolean;
1960
- status?: boolean;
1961
-
1962
- // BelongsTo relation
1963
- customer?: boolean | { select?: UserSelect };
1964
-
1965
- // HasMany relation
1966
- orderItems?:
1967
- | boolean
1968
- | {
1969
- select?: OrderItemSelect;
1970
- first?: number;
1971
- filter?: OrderItemFilter;
1972
- orderBy?: OrderItemsOrderBy[];
1973
- };
1974
-
1975
- // ManyToMany relation
1976
- productsByOrderItemOrderIdAndProductId?:
1977
- | boolean
1978
- | {
1979
- select?: ProductSelect;
1980
- first?: number;
1981
- filter?: ProductFilter;
1982
- orderBy?: ProductsOrderBy[];
1983
- };
1984
- };
1985
790
  ```
1986
791
 
1987
- ---
1988
-
1989
792
  ## Generated Types
1990
793
 
1991
794
  ### Entity Types
@@ -2024,7 +827,6 @@ export interface StringFilter {
2024
827
  contains?: string;
2025
828
  startsWith?: string;
2026
829
  endsWith?: string;
2027
- // ... more operators
2028
830
  }
2029
831
  ```
2030
832
 
@@ -2060,35 +862,8 @@ export interface UpdateUserInput {
2060
862
  id: string;
2061
863
  patch: UserPatch;
2062
864
  }
2063
-
2064
- export interface UserPatch {
2065
- username?: string | null;
2066
- email?: string | null;
2067
- displayName?: string | null;
2068
- }
2069
- ```
2070
-
2071
- ### Payload Types (Custom Operations)
2072
-
2073
- ```typescript
2074
- export interface LoginPayload {
2075
- clientMutationId?: string | null;
2076
- apiToken?: ApiToken | null;
2077
- }
2078
-
2079
- export interface ApiToken {
2080
- accessToken: string;
2081
- accessTokenExpiresAt?: string | null;
2082
- }
2083
-
2084
- export type LoginPayloadSelect = {
2085
- clientMutationId?: boolean;
2086
- apiToken?: boolean | { select?: ApiTokenSelect };
2087
- };
2088
865
  ```
2089
866
 
2090
- ---
2091
-
2092
867
  ## Development
2093
868
 
2094
869
  ```bash
@@ -2101,27 +876,6 @@ pnpm build
2101
876
  # Run in watch mode
2102
877
  pnpm dev
2103
878
 
2104
- # Test React Query hooks generation
2105
- node bin/graphql-sdk.js generate \
2106
- -e http://public-0e394519.localhost:3000/graphql \
2107
- -o ./output-rq \
2108
- --verbose
2109
-
2110
- # Test ORM client generation
2111
- node bin/graphql-sdk.js generate-orm \
2112
- -e http://public-0e394519.localhost:3000/graphql \
2113
- -o ./output-orm \
2114
- --verbose
2115
-
2116
- # Type check generated output
2117
- npx tsc --noEmit output-orm/*.ts output-orm/**/*.ts \
2118
- --skipLibCheck --target ES2022 --module ESNext \
2119
- --moduleResolution bundler --strict
2120
-
2121
- # Run example tests
2122
- npx tsx examples/test-orm.ts
2123
- npx tsx examples/type-inference-test.ts
2124
-
2125
879
  # Type check
2126
880
  pnpm lint:types
2127
881
 
@@ -2129,22 +883,6 @@ pnpm lint:types
2129
883
  pnpm test
2130
884
  ```
2131
885
 
2132
- ---
2133
-
2134
- ## Roadmap
2135
-
2136
- - [x] **Relations**: Typed nested select with relation loading
2137
- - [x] **Type Inference**: Const generics for narrowed return types
2138
- - [x] **Error Handling**: Discriminated unions with unwrap methods
2139
- - [ ] **Aggregations**: Count, sum, avg operations
2140
- - [ ] **Batch Operations**: Bulk create/update/delete
2141
- - [ ] **Transactions**: Transaction support where available
2142
- - [ ] **Subscriptions**: Real-time subscription support
2143
- - [ ] **Custom Scalars**: Better handling of PostGraphile custom types
2144
- - [ ] **Query Caching**: Optional caching layer for ORM client
2145
- - [ ] **Middleware**: Request/response interceptors
2146
- - [ ] **Connection Pooling**: For high-throughput scenarios
2147
-
2148
886
  ## Requirements
2149
887
 
2150
888
  - Node.js >= 18