postgraphile 5.0.0-rc.8 → 5.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.
package/README.md CHANGED
@@ -1,22 +1,298 @@
1
1
  # PostGraphile
2
2
 
3
+ > **High-performance GraphQL APIs, honed exactly to your needs, with minimal
4
+ > effort expended**
5
+
3
6
  [![GitHub Sponsors](https://img.shields.io/github/sponsors/benjie?color=ff69b4&label=github%20sponsors)](https://github.com/sponsors/benjie)
4
- [![Patreon sponsor button](https://img.shields.io/badge/sponsor-via%20Patreon-orange.svg)](https://patreon.com/benjie)
5
7
  [![Discord chat room](https://img.shields.io/discord/489127045289476126.svg)](http://discord.gg/graphile)
6
- [![Follow](https://img.shields.io/badge/twitter-@GraphileHQ-blue.svg)](https://twitter.com/GraphileHQ)
7
-
8
- _**Instant lightning-fast GraphQL API backed primarily by your PostgreSQL
9
- database. Highly customisable and extensible thanks to incredibly powerful
10
- plugin system.**_
8
+ [![Follow](https://img.shields.io/badge/BSky-@Graphile.org-006aff.svg)](https://bsky.app/profile/graphile.org)
9
+ [![Follow](https://img.shields.io/badge/Mastodon-@Graphile.fosstodon.org-6364ff.svg)](https://fosstodon.org/@graphile)
11
10
 
12
11
  **Documentation**: https://postgraphile.org/postgraphile/next/
13
12
 
14
- An incredibly low-effort way to build a well structured and high-performance
15
- GraphQL API backed primarily by a PostgreSQL database. Our main focusses are
16
- performance, automatic best-practices and customisability/extensibility. Use
17
- this if you have a PostgreSQL database and you want to use it as the primary
18
- "source of truth" for an auto-generated GraphQL API (which you can still make
19
- significant changes to).
13
+ ## About
14
+
15
+ **Focus on only writing the code that brings value.**
16
+
17
+ If you use Postgres as your primary datastore, PostGraphile helps you build
18
+ best-practices, well-structured, frontend-focused, and high-performance GraphQL
19
+ APIs, honed exactly to your needs, with minimal effort expended.
20
+
21
+ ## How it works
22
+
23
+ Rather than spending months hand-writing and maintaining an entire schema,
24
+ PostGraphile handles the repetitive parts so you can focus on the code that
25
+ really matters.
26
+
27
+ Out of the box, PostGraphile analyzes your PostgreSQL database and builds a
28
+ complete, consistent GraphQL schema informed by its tables, relations,
29
+ functions, indexes, permissions, and your configuration. Into this living
30
+ baseline that evolves with your database, you seamlessly compose customizations
31
+ and extensions to sculpt the perfect GraphQL API for your client's needs with
32
+ minimal effort:
33
+
34
+ - Seamlessly extend the schema with your own custom types and fields that can
35
+ execute SQL or Node.js code (see "Extensible" below).
36
+ - Use database permissions to govern which parts to expose; it's quick,
37
+ ergonomic, granular, and it improves your security posture.
38
+ - Use our powerful plugin and preset system to apply your general preferences to
39
+ the generated GraphQL schema.
40
+ - Use simple "tags" to fine-tune individual database entities: renaming them,
41
+ choosing when/how to expose them, changing their
42
+ type/presentation/nullability, indicating abstract types (interfaces/unions),
43
+ introducing additional relations, and more.
44
+ - Use our "inflection" system to overhaul naming across the generated schema (we
45
+ recommend
46
+ [`@graphile/simplify-inflection`](https://www.npmjs.com/package/@graphile/simplify-inflection)
47
+ if your database schema meets the requirements).
48
+ - Use third party plugins to add new features and capabilities.
49
+ - And much more!
50
+
51
+ ## Execution efficiency
52
+
53
+ Backed by the cutting edge [Gra*fast*](https://grafast.org) planning and
54
+ execution engine for GraphQL, PostGraphile has outstanding performance,
55
+ typically out-performing hand-written GraphQL schemas using traditional
56
+ GraphQL.js resolvers.
57
+
58
+ ## Consistency
59
+
60
+ PostGraphile's autogeneration of the common parts of your API helps ensure
61
+ consistency, whilst its plugin and preset system allows you to tweak every facet
62
+ of your schema to your heart's content.
63
+
64
+ ## No lock-in
65
+
66
+ If you ever feel the need to leave PostGraphile, you can
67
+ [export your schema as executable code](https://postgraphile.org/postgraphile/5/exporting-schema),
68
+ and take over maintenance yourself — all without losing the performance
69
+ advantages of our fully-planned execution.
70
+
71
+ ## Extensible
72
+
73
+ GraphQL is frontend-focused, so in most cases your API should be shaped by
74
+ client needs rather than being a simple 1-to-1 map of your database.
75
+ PostGraphile helps you achieve this with minimal effort.
76
+
77
+ Many of your business domain objects will naturally align across the frontend,
78
+ backend, and database; for these, a few tags or helper functions are typically
79
+ sufficient to accommodate minor differences.
80
+
81
+ For domains that don't fit that model, PostGraphile is built with extensibility
82
+ and composability at its heart. You can seamlessly adjust the schema to fit,
83
+ without sacrificing the productivity and performance gains you get from the rest
84
+ of the system.
85
+
86
+ Almost every feature in PostGraphile, from introspection through to type
87
+ generation and on to adding pagination arguments, is implemented via plugins.
88
+ PostGraphile's plugin API is incredibly powerful and flexible; it's designed for
89
+ you to use and even has helper factories to give more ergonomic APIs for common
90
+ needs.
91
+
92
+ Let's take the example of a checkout process. Your database only exposes the
93
+ underlying `products`, `prices`, `cart_items`, and so on, but your frontend
94
+ needs to know `subtotal`, `tax`, etc. Prices may vary depending on promotional
95
+ discounts or other business rules. It should not be up to the client to
96
+ implement these details client-side, instead your GraphQL schema should expose
97
+ them as helpful strongly typed and well documented fields.
98
+
99
+ In PostGraphile, you can handle this in the database:
100
+
101
+ ```sql
102
+ -- Hide the "prices" table from the GraphQL schema.
103
+ comment on table prices is '@behavior -*';
104
+
105
+ -- Create the `Product.unitPrice` field to get the current price of an item
106
+ create function products_unit_price(p products) returns money as $$
107
+ select unit_price
108
+ from prices
109
+ where product_id = p.product_id
110
+ and now() >= valid_from
111
+ and now() < valid_until;
112
+ $$ language sql stable;
113
+
114
+ -- Add documentation for this field
115
+ comment on function products_unit_price is
116
+ 'The unit price at the current time, reflecting promotional discounts.';
117
+ ```
118
+
119
+ This will automatically add a `Product.unitPrice` field that finds the current
120
+ unit price of the given product according to the time validity. (Here we assume
121
+ the time periods will not overlap, but if they do then you can add
122
+ `order by unit_price asc limit 1` to the query.)
123
+
124
+ If your business logic is more complex, you could instead achieve this via a
125
+ schema extension; this also allows for more advanced logic such as querying an
126
+ external service to determine shipping costs.
127
+
128
+ Let's imagine we didn't add the above database function, and instead want to
129
+ implement the logic in TypeScript, along with adding cart "summary" logic
130
+ (subtotal, shipping, tax, total).
131
+
132
+ ```ts
133
+ import { extendSchema } from "postgraphile/utils";
134
+ import { constant, context, get } from "postgraphile/grafast";
135
+ import { batchSummarizeCart } from "./businessLogic/cart";
136
+
137
+ export default extendSchema((build) => {
138
+ const {
139
+ pgExecutor,
140
+ pgResources: { cartItems, products, prices },
141
+ } = build;
142
+ return {
143
+ typeDefs: /* GraphQL */ `
144
+ extend type Product {
145
+ "The unit price at the current time, reflecting promotional discounts."
146
+ unitPrice: Money!
147
+ }
148
+
149
+ extend type Cart {
150
+ summary: CartSummary
151
+ }
152
+
153
+ type CartSummary {
154
+ subtotal: Money!
155
+ shipping: Money!
156
+ tax: Money!
157
+ total: Money!
158
+ }
159
+ `,
160
+ plans: {
161
+ Product: {
162
+ unitPrice($item) {
163
+ // Find the relevant price
164
+ const productId = $item.get("product_id");
165
+ const $prices = prices.find({ productId: $productId });
166
+ $prices.where(sql`now() >= valid_from and now() < valid_until`);
167
+
168
+ // There's exactly one row, fetch it and return the unit price
169
+ return $prices.single().get("unit_price");
170
+ },
171
+ },
172
+ Cart: {
173
+ summary($cart) {
174
+ const $cartId = $cart.get("id");
175
+ return loadOne($cartId, batchSummarizeCart);
176
+ },
177
+ },
178
+ // CartSummary doesn't need plan resolvers, it can use the defaults.
179
+ },
180
+ };
181
+ });
182
+ ```
183
+
184
+ With `extendSchema()` you can add custom types and fields that invoke arbitrary
185
+ Node.js business logic, integrating with any datasource Node.js can communicate
186
+ with. This can also be used to maintain backwards compatibility if and when you
187
+ make breaking changes to your database schema. And that's just one of our plugin
188
+ helpers, there's so much more you can do to make the schema your own!
189
+
190
+ <details>
191
+ <summary>
192
+ Click to see example business logic for this schema
193
+ </summary>
194
+
195
+ Your business logic can be implemented in any way that you want and can do
196
+ anything Node.js can do; typically we assume you're using DataLoader-style
197
+ callbacks (you can actually use your DataLoader callbacks with
198
+ `loadOne`/`loadMany` directly!) to enable batched loading, but the `loadOne()`
199
+ and `loadMany()` steps you'd use to call them can
200
+ [have many advantages over DataLoader](https://grafast.org/grafast/standard-steps/loadMany#enhancements-over-dataloader).
201
+
202
+ ```ts
203
+ // businessLogic/cart.ts
204
+
205
+ import { context } from "postgraphile/grafast";
206
+
207
+ export const batchSummarizeCart = {
208
+ // Plan to get access to the GraphQL context in our loader
209
+ shared: () => context(),
210
+
211
+ // `cartIds` is the batch of Cart identifiers, `shared` is the runtime GraphQL
212
+ // context (shared by everything in the batch)
213
+ async load(cartIds, { shared }) {
214
+ const carts = await batchGetCartInfo(shared, cartIds);
215
+ const cartsWithShipping = await batchCalculateShippingCosts(carts);
216
+ const cartsWithTax = await batchCalculateTax(cartsWithShipping);
217
+ return cartIds.map((cartId) => {
218
+ const cartInfo = cartsWithTax.find((c) => c.cart_id === cartId);
219
+ const { subtotal, shipping, tax } = cartInfo;
220
+ const total = subtotal + shipping + tax;
221
+ return { subtotal, shipping, tax, total };
222
+ });
223
+ },
224
+ };
225
+
226
+ /** Cart info from the database */
227
+ interface CartInfo {
228
+ cart_id: number;
229
+ shipping_address: Address;
230
+ subtotal: number;
231
+ mass: number;
232
+ }
233
+ async function batchGetCartInfo(shared, cartIds) {
234
+ // Single DB fetch across all carts; you could use you ORM instead if you want.
235
+ const result = await shared.withPgClient(shared.pgSettings, (db) =>
236
+ db.query<CartInfo>({
237
+ text: `
238
+ select
239
+ carts.id as cart_id,
240
+ to_json(carts.shipping_address) as shipping_address,
241
+ sum(cart_items.quantity * prices.unit_amount) as subtotal,
242
+ sum(cart_items.quantity * products.mass) as mass
243
+ from carts
244
+ inner join cart_items
245
+ on (cart_items.cart_id = carts.id)
246
+ inner join prices
247
+ on (
248
+ prices.product_id = cart_items.product_id
249
+ and now() >= valid_from
250
+ and now() < valid_to
251
+ )
252
+ where carts.id = any($1::int[])
253
+ group by carts.id
254
+ `,
255
+ values: [cartIds],
256
+ }),
257
+ );
258
+ return result.rows;
259
+ }
260
+
261
+ interface CartInfoWithShipping extends CartInfo {
262
+ shipping: number;
263
+ }
264
+
265
+ async function batchCalculateShippingCosts(carts: CartInfo[]) {
266
+ // TODO: given the mass and address for each cart, determine its shipping cost
267
+ return carts.map((cart) => ({ ...cart, shipping: 500 }));
268
+ }
269
+
270
+ interface CartInfoWithShippingAndTax extends CartInfoWithShipping {
271
+ shipping: number;
272
+ }
273
+ async function batchCalculateTax(carts: CartInfoWithShipping[]) {
274
+ // TODO: update with the correct taxes for the region
275
+ const TAX_PERCENTAGE = 20;
276
+ return carts.map((cart) => ({
277
+ ...cart,
278
+ tax: ((cart.subtotal + cart.shipping) * TAX_PERCENTAGE) / 100,
279
+ }));
280
+ }
281
+ ```
282
+
283
+ </details>
284
+
285
+ ## Summary
286
+
287
+ If your backend uses PostgreSQL as its primary datastore, and you use a
288
+ conventional relational schema, PostGraphile is the best way to get your project
289
+ up and running in record time; and, thanks to its incredibly efficient execution
290
+ that eliminates under- and over-fetching on the backend, it helps you reach
291
+ significant scale with minimal resources and minimal complexity.
292
+
293
+ Stop building boilerplate, iterate faster, and start shipping today with
294
+ PostGraphile;
295
+ [click here to get started](https://postgraphile.org/postgraphile/5).
20
296
 
21
297
  <!-- SPONSORS_BEGIN -->
22
298
 
@@ -31,9 +307,9 @@ via sponsorship.
31
307
  And please give some love to our featured sponsors 🤩:
32
308
 
33
309
  <table><tr>
34
- <td align="center"><a href="https://www.the-guild.dev/"><img src="https://graphile.org/images/sponsors/theguild.png" width="90" height="90" alt="The Guild" /><br />The Guild</a> *</td>
35
310
  <td align="center"><a href="https://gosteelhead.com/"><img src="https://graphile.org/images/sponsors/steelhead.svg" width="90" height="90" alt="Steelhead" /><br />Steelhead</a> *</td>
36
311
  <td align="center"><a href="https://outbankapp.com/"><img src="https://graphile.org/images/sponsors/outbank.png" width="90" height="90" alt="Outbank" /><br />Outbank</a></td>
312
+ <td align="center"><a href="https://constructive.io/"><img src="https://graphile.org/images/sponsors/constructive.svg" width="90" height="90" alt="Constructive" /><br />Constructive</a></td>
37
313
  </tr></table>
38
314
 
39
315
  <em>\* Sponsors the entire Graphile suite</em>
package/dist/cli-run.js CHANGED
File without changes
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version = "5.0.0-rc.8";
1
+ export declare const version = "5.0.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,eAAe,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,UAAU,CAAC"}
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
4
  // This file is autogenerated by /scripts/postversion.mjs
5
- exports.version = "5.0.0-rc.8";
5
+ exports.version = "5.0.0";
6
6
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":";;;AAAA,yDAAyD;AAC5C,QAAA,OAAO,GAAG,YAAY,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":";;;AAAA,yDAAyD;AAC5C,QAAA,OAAO,GAAG,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgraphile",
3
- "version": "5.0.0-rc.8",
3
+ "version": "5.0.0",
4
4
  "description": "Automatic, high performance, and highly customizable GraphQL API for PostgreSQL",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
@@ -222,16 +222,6 @@
222
222
  }
223
223
  },
224
224
  "bin": "./dist/cli-run.js",
225
- "scripts": {
226
- "test": "yarn test:jest && UPDATE_SNAPSHOTS=\"\" yarn test:schema-exports && UPDATE_SNAPSHOTS=\"\" yarn test:operations-exports",
227
- "test:jest": "jest",
228
- "test:schema-exports": "node scripts/test-schema-exports.mjs",
229
- "test:operations-exports": "yarn test:operations-exports:typeDefs",
230
- "//test:operations-exports": "yarn test:operations-exports:typeDefs && yarn test:operations-exports:graphql-js",
231
- "test:operations-exports:typeDefs": "EXPORT_SCHEMA=typeDefs jest __tests__/queries __tests__/mutations",
232
- "test:operations-exports:graphql-js": "EXPORT_SCHEMA=graphql-js jest __tests__/queries __tests__/mutations",
233
- "prepack": "tsc -b tsconfig.build.json"
234
- },
235
225
  "repository": {
236
226
  "type": "git",
237
227
  "url": "git+https://github.com/graphile/crystal.git"
@@ -257,40 +247,40 @@
257
247
  },
258
248
  "homepage": "https://graphile.org/graphile-engine/",
259
249
  "dependencies": {
260
- "@dataplan/json": "^1.0.0-rc.5",
261
- "@dataplan/pg": "^1.0.0-rc.6",
262
- "@graphile/lru": "^5.0.0-rc.4",
250
+ "@dataplan/json": "^1.0.0",
251
+ "@dataplan/pg": "^1.0.0",
252
+ "@graphile/lru": "^5.0.0",
263
253
  "@types/node": "^22.19.1",
264
254
  "@types/pg": "^8.15.6",
265
255
  "debug": "^4.4.3",
266
- "grafast": "^1.0.0-rc.8",
267
- "grafserv": "^1.0.0-rc.6",
268
- "graphile-build": "^5.0.0-rc.5",
269
- "graphile-build-pg": "^5.0.0-rc.6",
270
- "graphile-config": "^1.0.0-rc.5",
271
- "graphile-utils": "^5.0.0-rc.7",
256
+ "grafast": "^1.0.0",
257
+ "grafserv": "^1.0.0",
258
+ "graphile-build": "^5.0.0",
259
+ "graphile-build-pg": "^5.0.0",
260
+ "graphile-config": "^1.0.0",
261
+ "graphile-utils": "^5.0.0",
272
262
  "graphql": "^16.9.0",
273
263
  "iterall": "^1.3.0",
274
264
  "jsonwebtoken": "^9.0.2",
275
265
  "pg": "^8.16.3",
276
- "pg-sql2": "^5.0.0-rc.4",
277
- "tamedevil": "^0.1.0-rc.5",
266
+ "pg-sql2": "^5.0.0",
267
+ "tamedevil": "^0.1.0",
278
268
  "tslib": "^2.8.1",
279
269
  "ws": "^8.18.3"
280
270
  },
281
271
  "peerDependencies": {
282
- "@dataplan/json": "^1.0.0-rc.5",
283
- "@dataplan/pg": "^1.0.0-rc.6",
272
+ "@dataplan/json": "^1.0.0",
273
+ "@dataplan/pg": "^1.0.0",
284
274
  "@envelop/core": "^5.0.0",
285
- "grafast": "^1.0.0-rc.8",
286
- "grafserv": "^1.0.0-rc.6",
287
- "graphile-build": "^5.0.0-rc.5",
288
- "graphile-build-pg": "^5.0.0-rc.6",
289
- "graphile-config": "^1.0.0-rc.5",
275
+ "grafast": "^1.0.0",
276
+ "grafserv": "^1.0.0",
277
+ "graphile-build": "^5.0.0",
278
+ "graphile-build-pg": "^5.0.0",
279
+ "graphile-config": "^1.0.0",
290
280
  "graphql": "^16.9.0",
291
281
  "pg": "^8.7.1",
292
- "pg-sql2": "^5.0.0-rc.4",
293
- "tamedevil": "^0.1.0-rc.5"
282
+ "pg-sql2": "^5.0.0",
283
+ "tamedevil": "^0.1.0"
294
284
  },
295
285
  "peerDependenciesMeta": {
296
286
  "@envelop/core": {
@@ -302,20 +292,6 @@
302
292
  },
303
293
  "files": [
304
294
  "dist",
305
- "fwd",
306
- "index.js"
307
- ],
308
- "devDependencies": {
309
- "@types/debug": "^4.1.12",
310
- "@types/express": "^4.17.25",
311
- "@types/jest": "^30.0.0",
312
- "@types/jsonwebtoken": "^9.0.10",
313
- "@types/nodemon": "^3.1.1",
314
- "graphile": "^5.0.0-rc.5",
315
- "graphile-export": "^1.0.0-rc.5",
316
- "jest": "^30.2.0",
317
- "nodemon": "^3.1.11",
318
- "ts-node": "^10.9.2",
319
- "typescript": "^5.9.3"
320
- }
295
+ "fwd"
296
+ ]
321
297
  }