fareql 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bruce0X
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,208 @@
1
- # Temporary Holding Version
1
+ # FareQL
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/fareql)](https://www.npmjs.com/package/fareql)
4
+ [![ci](https://github.com/Bruce039/fareql/actions/workflows/ci.yml/badge.svg)](https://github.com/Bruce039/fareql/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/fareql)](https://github.com/Bruce039/fareql/blob/main/LICENSE)
6
+
7
+ Charge for GraphQL operations over [MPP](https://mpp.dev) (HTTP 402), priced by what each operation selects.
8
+
9
+ A GraphQL API is one endpoint, so a flat price per route doesn't fit it: `{ hello }` and `{ users(first: 100) { edges { node { email } } } }` hit the same URL. FareQL works out the fare of every operation from your schema and charges it through [`mppx`](https://github.com/wevm/mppx) with Tempo, Stripe or any other MPP method.
10
+
11
+ - **Schema-driven prices.** `@price(amount: "0.001")` on fields and types, multiplied through lists, Relay connections and `@listSize`.
12
+ - **One Challenge per operation**, bound to the exact document and variables. A Credential paid for a cheap query can't be replayed on an expensive one; mppx enforces this through the Challenge's HMAC.
13
+ - **Charged after validation, right before execution.** Persisted queries and depth limits are already applied when FareQL charges, so nobody pays for an operation the server then refuses.
14
+ - **Fails closed.** If FareQL can't read or price an operation, the operation doesn't run.
15
+ - **Ready for GraphQL clients.** The 402 keeps mppx's `WWW-Authenticate` Challenge and adds a GraphQL `errors` entry with the price and its breakdown.
16
+ - Works with **GraphQL Yoga**, **Apollo Server 4/5** and **any Fetch-API handler**, on graphql 16 and 17.
17
+
18
+ ```bash
19
+ npm install fareql mppx graphql
20
+ ```
21
+
22
+ ## How it works
23
+
24
+ 1. You mark what costs money in your schema: `@price(amount: "0.001")`.
25
+ 2. A client sends a query. FareQL prices it from what it selects. Free operations run as usual.
26
+ 3. A priced operation gets `402 Payment Required` with an MPP Challenge for that exact operation, plus the price and its breakdown.
27
+ 4. The client (an app, the `mppx` CLI, an AI agent) pays, for example in stablecoins on Tempo, and resends the query with the Credential. FareQL verifies it through mppx, runs the operation, and returns the data with a `Payment-Receipt`.
28
+
29
+ Payments go straight to your own address. There is no account, API key or subscription for the client to set up.
30
+
31
+ ## GraphQL Yoga
32
+
33
+ ```ts
34
+ import { createSchema, createYoga } from 'graphql-yoga'
35
+ import { Mppx, tempo } from 'mppx/server'
36
+ import { fareDirectiveTypeDefs } from 'fareql'
37
+ import { useFareql } from 'fareql/yoga'
38
+
39
+ const mppx = Mppx.create({
40
+ methods: [tempo.charge({ currency: '0x…' /* TIP-20 token */, recipient: '0x…' })],
41
+ })
42
+
43
+ const yoga = createYoga({
44
+ schema: createSchema({
45
+ typeDefs: /* GraphQL */ `
46
+ ${fareDirectiveTypeDefs}
47
+ type Query {
48
+ hello: String
49
+ weather(city: String!): Forecast @price(amount: "0.001")
50
+ search(query: String!, first: Int = 5): [Hit!]! @price(amount: "0.0005")
51
+ }
52
+ type Forecast { temperature: Float }
53
+ type Hit { title: String, snippet: String @price(amount: "0.0002") }
54
+ `,
55
+ resolvers,
56
+ }),
57
+ plugins: [useFareql({ charge: (o) => mppx.charge(o), pricing: { maxPrice: '1' } })],
58
+ })
59
+ ```
60
+
61
+ `{ hello }` is free. `{ search(query: "mpp", first: 2) { snippet } }` costs `2 × (0.0005 + 0.0002) = 0.0014`.
62
+
63
+ Register `useFareql` after plugins that change what runs (APQ, persisted operations, validation rules). It hooks into execution, so it always prices the final document. A backstop before each response charges results that never reached execution, so a response cache (`useResponseCache`) or a plugin that short-circuits cannot serve a priced result for free.
64
+
65
+ Yoga-only options: `getRequest(context)` supplies the HTTP request for transports that have none (graphql-ws); without it, priced operations over such transports are refused. `maxSubscriptionEvents` (default 100) is how many events one payment buys on a priced subscription; the stream then ends with `PAYMENT_EXHAUSTED`.
66
+
67
+ ## Apollo Server
68
+
69
+ ```ts
70
+ import { ApolloServer } from '@apollo/server'
71
+ import { fareqlApolloPlugin } from 'fareql/apollo'
72
+
73
+ const server = new ApolloServer({
74
+ typeDefs,
75
+ resolvers,
76
+ plugins: [fareqlApolloPlugin({ charge: (o) => mppx.charge(o) })],
77
+ })
78
+ ```
79
+
80
+ The plugin charges in `didResolveOperation`, after Apollo has parsed, validated and resolved the operation, APQ included, and before Apollo's response cache can answer. In an HTTP batch only one priced operation can be paid. Operations you run in-process with `server.executeOperation()` have no HTTP request and are not charged.
81
+
82
+ ## Any Fetch handler (Hono, Next.js, graphql-http, Cloudflare Workers)
83
+
84
+ ```ts
85
+ import { withPayment } from 'fareql'
86
+
87
+ export const POST = withPayment((req) => handler(req), { schema, charge: (o) => mppx.charge(o) })
88
+ ```
89
+
90
+ `withPayment` reads the request itself, so it is strict about anything the wrapped handler could read differently:
91
+
92
+ - It accepts GET and `application/json` POST by default. Enable `application/graphql`, `application/graphql+json`, form-urlencoded or multipart uploads with `encodings: [...]`, and only if your handler accepts them, so a client is never charged for a body the handler then refuses.
93
+ - It refuses the following with 4xx before anything is charged:
94
+ - URL fragments (`#`), duplicate or unknown parameters, and GraphQL parameters in the URL of a POST;
95
+ - non-object `variables`, and `query` sent together with `documentId`;
96
+ - bodies on other methods, and mutations over GET.
97
+ - `maxBodyBytes` (default 1 MB), `maxQueryLength` (100 000), `maxTokens` (15 000) and `maxBatchSize` (10) bound the work.
98
+ - `validationRules` adds your server's extra rules (depth limits and so on), so operations they reject are refused before payment.
99
+ - It speaks APQ: a hash it hasn't seen gets `PersistedQueryNotFound`, the client resends the full query, and that query is priced. For trusted documents (`documentId`), pass `resolveDocument`.
100
+ - Batches are refused unless you set `batching: true`; then the whole batch is charged in one payment, and `maxPrice` applies to the total.
101
+
102
+ For full control, `createPaymentGate(options)(request)` returns `free`, `payment-required` (with the 402 `response`), `paid` (with `withReceipt`) or `rejected` (with the response to send).
103
+
104
+ ## Pricing rules
105
+
106
+ | Rule | Behaviour |
107
+ |---|---|
108
+ | Field `@price` | Charged once per resolved instance of the field |
109
+ | Type `@price` | Applies to every field returning that type, unless the field has its own |
110
+ | Field selected several times | Charged once per response key, as the executor resolves it (fragments merged, repeated spreads ignored) |
111
+ | Lists | Children × list size, read from `first`, `last`, `limit`, `take`, `top`, `pageSize` or `perPage` (also one level inside an input object, e.g. `page: { limit }`); otherwise `defaultListSize` (10). Capped at `maxListSize` (100). `null` or an unbounded number counts as `maxListSize`; a negative size is refused (`INVALID_LIST_SIZE`) |
112
+ | Nested lists | `[[Cell]]`: each inner level multiplies by `defaultListSize` |
113
+ | Connections and page wrappers | A non-list field given a size (`users(first: 50)`) passes it to the lists it selects (`edges`, `nodes`, `items`…). Restrict with `connections: { sizedFields }` or turn off with `false` |
114
+ | `@listSize` | As in the IBM GraphQL Cost Directives draft: `slicingArguments`, `sizedFields`, `assumedSize`, `requireOneSlicingArgument` (default true) |
115
+ | Interfaces and unions | Each possible runtime type is priced and the most expensive one is charged. Branches are never added together. `@price` on an interface field or interface type applies to the types implementing it |
116
+ | Code-first schemas | `extensions: { price: '0.001' }` or `extensions: { directives: { price: { amount: '0.001' } } }` work like the directive |
117
+ | Fragments, `@skip`, `@include` | Followed and evaluated with the request variables |
118
+ | `__typename`, introspection | Free |
119
+ | `maxPrice` | Operations above it get `PRICE_LIMIT_EXCEEDED` before any Challenge is issued |
120
+ | `maxFieldVisits`, `maxDepth` | Operations too complex to price get `TOO_COMPLEX` (defaults 10 000 selections, 100 levels) |
121
+ | No price anywhere | A schema without any `@price` (lost directives, an introspection copy) is refused with `NO_PRICES` instead of making everything free |
122
+
123
+ Other options: `decimals` (default 6, which must match your payment method's token), `defaultFieldPrice`, `listSizeArguments`, `connections` (`false` or `{ sizedFields }`), and `fieldPrice(ctx)` to price fields in code. `priceOperation()` is exported for quotes and tests. The breakdown lists at most 100 items (`breakdownTruncated: true` when there are more).
124
+
125
+ ## What clients get
126
+
127
+ ```http
128
+ HTTP/1.1 402 Payment Required
129
+ WWW-Authenticate: Payment id="…", realm="api.example.com", method="tempo", intent="charge", request="…"
130
+ Cache-Control: no-store
131
+ Content-Type: application/json; charset=utf-8
132
+
133
+ {
134
+ "errors": [{
135
+ "message": "Payment required: this operation costs 0.0014",
136
+ "extensions": {
137
+ "code": "PAYMENT_REQUIRED",
138
+ "price": {
139
+ "amount": "0.0014",
140
+ "decimals": 6,
141
+ "breakdown": [
142
+ { "path": "search", "unitAmount": "0.0005", "count": "2", "amount": "0.001" },
143
+ { "path": "search.snippet", "unitAmount": "0.0002", "count": "2", "amount": "0.0004" }
144
+ ]
145
+ }
146
+ }
147
+ }]
148
+ }
149
+ ```
150
+
151
+ Clients need nothing from FareQL, only an MPP client. With the mppx CLI and a Tempo testnet wallet:
152
+
153
+ ```bash
154
+ npx mppx account create && npx mppx account fund
155
+ npx mppx https://api.example.com/graphql -X POST -H 'content-type: application/json' \
156
+ -d '{"query":"{ search(query: \"mpp\", first: 2) { snippet } }"}'
157
+ ```
158
+
159
+ In code, `mppx/client` wraps `fetch` so 402s are paid and retried automatically, and MPP-aware agents do the same. The paid response carries `Payment-Receipt` and `Cache-Control: private` (or `private, no-store`), so no shared cache or CDN stores paid content. Set `exposePrice: false` to leave the price out of every error body.
160
+
161
+ ## Security model
162
+
163
+ - **Binding.** Each Challenge's `scope` is a hash of the printed document, the operation name and the declared variables. mppx keeps it in the HMAC-protected Challenge and refuses a Credential whose scope differs. A payment for one operation never unlocks another.
164
+ - **Price what runs.** Yoga and Apollo are charged after the server's own parsing, validation and persisted-document lookup. `withPayment` refuses requests a handler could parse differently.
165
+ - **Fail closed.** Unreadable, unpriceable, over-limit or ambiguous requests are answered with an error and never executed. Batches can't hold more than one paid operation per Credential.
166
+ - **Bounded work.** Pricing is linear in the document, like graphql-js field collection. Token, length, depth, selection, body and batch limits keep it cheap.
167
+ - **Tested by attack.** [`test/attacks-*.test.ts`](https://github.com/Bruce039/fareql/tree/main/test) hold the red-team suites: pricing tricks, parser differentials, cache and plugin-order bypasses, scope collisions and DoS. Each test asserts the secure behaviour.
168
+
169
+ ## Shared options
170
+
171
+ | Option | |
172
+ |---|---|
173
+ | `charge` | `(o) => mppx.charge(o)`, or `(o) => mppx.tempo.charge(o)` when several methods are configured. Receives `{ amount, scope, description }` |
174
+ | `pricing` | See [Pricing rules](#pricing-rules) |
175
+ | `describe(op, price)` | Challenge description. Default: `GraphQL <operation name>` |
176
+ | `exposePrice` | Include the price breakdown in 402 bodies. Default: `true` |
177
+
178
+ ## Notes
179
+
180
+ - Payment settles before execution, so a resolver error after payment is not refunded. Keep priced resolvers reliable, or price them lower.
181
+ - Subscriptions are charged once, when they start, for up to `maxSubscriptionEvents` events. Metered streams over MPP sessions are planned.
182
+ - Set `maxListSize` to your server's real page cap. A larger requested size is clamped, so a server that returns more items than `maxListSize` is under-charged.
183
+ - With Yoga, priced operations can't share a batch, because one request carries one Credential. Free operations in a batch work as usual.
184
+ - In production, give `tempo.charge()` a shared `store` so a Credential can't be replayed across instances. This is an mppx setting.
185
+
186
+ ## Examples
187
+
188
+ - [`examples/yoga-tempo`](https://github.com/Bruce039/fareql/blob/main/examples/yoga-tempo/server.ts): GraphQL Yoga on Tempo's Moderato testnet
189
+ - [`examples/apollo-tempo`](https://github.com/Bruce039/fareql/blob/main/examples/apollo-tempo/server.ts): Apollo Server with a priced Relay-style connection
190
+ - [`examples/playground`](https://github.com/Bruce039/fareql/blob/main/examples/playground/server.ts): a local page that walks through quote → Challenge → Credential → Receipt. It uses a settlement-free demo method by default (`npx tsx examples/playground/server.ts`, then open http://localhost:4000), and real Moderato Challenges when `RECIPIENT` and `MPP_SECRET_KEY` are set
191
+
192
+ ```bash
193
+ MPP_SECRET_KEY=$(openssl rand -hex 32) RECIPIENT=0x… npx tsx examples/yoga-tempo/server.ts
194
+ npx mppx account create && npx mppx account fund
195
+ npx mppx http://localhost:4000/graphql -X POST -H 'content-type: application/json' \
196
+ -d '{"query":"{ search(query: \"tempo\", first: 3) { title snippet } }"}'
197
+ ```
198
+
199
+ ## Development
200
+
201
+ ```bash
202
+ npm test # vitest: pricing, gate, Yoga, Apollo, and real mppx client ↔ server loops
203
+ npm run build && node scripts/smoke.mjs # the built package over real HTTP in plain Node
204
+ ```
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,19 @@
1
+ import { type ApolloServerPlugin, type BaseContext, type HTTPGraphQLRequest } from '@apollo/server';
2
+ import { type FareOptions } from './core.js';
3
+ export type FareqlApolloOptions = FareOptions & {
4
+ /**
5
+ * URL of the GraphQL endpoint, used to build the Fetch `Request` mppx sees. Apollo does not
6
+ * expose the path. Default: `http://<Host header>/graphql<search>`.
7
+ */
8
+ url?: string | ((http: HTTPGraphQLRequest) => string);
9
+ };
10
+ /**
11
+ * Apollo Server 4/5 plugin. Prices each operation after Apollo has parsed, validated and
12
+ * resolved it (persisted queries included), before execution. Priced operations answer with the
13
+ * MPP Challenge (402 + `WWW-Authenticate`); paid ones carry `Payment-Receipt`.
14
+ *
15
+ * In an HTTP batch only the first priced operation can be paid (one request carries one
16
+ * Credential); further priced operations in the same batch are refused.
17
+ * Operations run in-process with `server.executeOperation()` have no HTTP request and are not charged.
18
+ */
19
+ export declare function fareqlApolloPlugin<TContext extends BaseContext = BaseContext>(options: FareqlApolloOptions): ApolloServerPlugin<TContext>;
package/dist/apollo.js ADDED
@@ -0,0 +1,100 @@
1
+ import { HeaderMap } from '@apollo/server';
2
+ import { GraphQLError } from 'graphql';
3
+ import { chargeOperations, paymentError, pricingErrorBody, receiptHeaders } from './core.js';
4
+ import { PricingError, priceOperation } from './price.js';
5
+ function toFetchRequest(http, url) {
6
+ const headers = new Headers();
7
+ for (const [key, value] of http.headers)
8
+ headers.set(key, value);
9
+ const href = typeof url === 'function'
10
+ ? url(http)
11
+ : url
12
+ ? `${url}${http.search}`
13
+ : `http://${http.headers.get('host') ?? 'localhost'}/graphql${http.search}`;
14
+ try {
15
+ return new Request(href, { method: http.method, headers });
16
+ }
17
+ catch {
18
+ // A Host header that is not a valid authority: the Challenge realm falls back to localhost
19
+ // (configure `realm` in Mppx.create to make it independent of the Host header).
20
+ return new Request(`http://localhost/graphql${http.search.startsWith('?') ? http.search : ''}`, { method: http.method, headers });
21
+ }
22
+ }
23
+ const fail = (message, extensions, status, headers) => {
24
+ // Typed loosely: GraphQL Yoga augments GraphQLErrorExtensions with its own `http` shape.
25
+ const ext = { ...extensions, http: { status, headers: headers ?? new HeaderMap() } };
26
+ return new GraphQLError(message, { extensions: ext });
27
+ };
28
+ /**
29
+ * Apollo Server 4/5 plugin. Prices each operation after Apollo has parsed, validated and
30
+ * resolved it (persisted queries included), before execution. Priced operations answer with the
31
+ * MPP Challenge (402 + `WWW-Authenticate`); paid ones carry `Payment-Receipt`.
32
+ *
33
+ * In an HTTP batch only the first priced operation can be paid (one request carries one
34
+ * Credential); further priced operations in the same batch are refused.
35
+ * Operations run in-process with `server.executeOperation()` have no HTTP request and are not charged.
36
+ */
37
+ export function fareqlApolloPlugin(options) {
38
+ const exposePrice = options.exposePrice ?? true;
39
+ // Pieces of one HTTP batch share the same headers object; count the priced ones per request.
40
+ const pricedPieces = new WeakMap();
41
+ return {
42
+ async requestDidStart() {
43
+ let receipt;
44
+ return {
45
+ async didResolveOperation(rc) {
46
+ const http = rc.request.http;
47
+ if (!http)
48
+ return;
49
+ const input = { document: rc.document, operationName: rc.operationName ?? rc.request.operationName, variables: rc.request.variables };
50
+ let units;
51
+ try {
52
+ units = priceOperation({ schema: rc.schema, ...input }, options.pricing).units;
53
+ }
54
+ catch (e) {
55
+ if (e instanceof PricingError)
56
+ throw fail(e.message, pricingErrorBody(e, exposePrice).extensions, 400);
57
+ throw e;
58
+ }
59
+ if (units === 0n)
60
+ return;
61
+ // Counted synchronously, before any await, so parallel batch pieces cannot race past it.
62
+ const n = (pricedPieces.get(http.headers) ?? 0) + 1;
63
+ pricedPieces.set(http.headers, n);
64
+ if (n > 1) {
65
+ throw fail('Priced operations cannot be batched; send them one per request', { code: 'BATCHING_NOT_SUPPORTED' }, 400);
66
+ }
67
+ const outcome = await chargeOperations(toFetchRequest(http, options.url), rc.schema, [input], options);
68
+ switch (outcome.status) {
69
+ case 'free':
70
+ return;
71
+ case 'rejected': {
72
+ const body = pricingErrorBody(outcome.error, exposePrice);
73
+ throw fail(body.message, body.extensions, 400);
74
+ }
75
+ case 'payment-required': {
76
+ const headers = new HeaderMap();
77
+ outcome.challenge.headers.forEach((value, key) => {
78
+ if (key !== 'content-type' && key !== 'content-length')
79
+ headers.set(key, value);
80
+ });
81
+ const body = paymentError(outcome.challenge.status, outcome.price, exposePrice);
82
+ throw fail(body.message, body.extensions, outcome.challenge.status, headers);
83
+ }
84
+ case 'paid':
85
+ receipt = receiptHeaders(outcome.withReceipt);
86
+ }
87
+ },
88
+ async willSendResponse(rc) {
89
+ if (!receipt)
90
+ return;
91
+ receipt.forEach((value, key) => rc.response.http.headers.set(key, value));
92
+ // Paid content belongs to the payer: never let a shared cache or CDN store it.
93
+ const cc = rc.response.http.headers.get('cache-control') ?? '';
94
+ if (!/\b(private|no-store)\b/i.test(cc))
95
+ rc.response.http.headers.set('cache-control', 'private, no-store');
96
+ },
97
+ };
98
+ },
99
+ };
100
+ }
package/dist/core.d.ts ADDED
@@ -0,0 +1,119 @@
1
+ import { type DocumentNode, type GraphQLError, type GraphQLSchema, type OperationTypeNode, type ValidationRule } from 'graphql';
2
+ import { type OperationPrice, type PricingOptions, PricingError } from './price.js';
3
+ /** Options passed to the charge factory; maps 1:1 onto `mppx.charge({ ... })`. */
4
+ export type ChargeOptions = {
5
+ /** Price in token units, e.g. "0.0014". */
6
+ amount: string;
7
+ /** Binds the Challenge to this exact operation, so its Credential cannot pay for another one. */
8
+ scope: string;
9
+ description?: string;
10
+ };
11
+ /** What an mppx route handler returns (`await mppx.charge(options)(request)`). */
12
+ export type ChargeResult = {
13
+ status: 402;
14
+ challenge: Response;
15
+ } | {
16
+ status: 200;
17
+ withReceipt: (response: Response) => Response;
18
+ };
19
+ /**
20
+ * Creates a charge handler for one priced operation. With mppx this is
21
+ * `(o) => mppx.charge(o)`, or `(o) => mppx.tempo.charge(o)` with several methods.
22
+ */
23
+ export type ChargeFactory = (options: ChargeOptions) => (request: Request) => Promise<ChargeResult>;
24
+ export type OperationInput = {
25
+ document: DocumentNode;
26
+ operationName?: string | null;
27
+ variables?: Record<string, unknown> | null;
28
+ };
29
+ export type OperationInfo = {
30
+ operationName: string | undefined;
31
+ operationType: OperationTypeNode;
32
+ };
33
+ export type FareOptions = {
34
+ charge: ChargeFactory;
35
+ pricing?: PricingOptions;
36
+ /** Challenge description. Default: `GraphQL <operation name or type>`. */
37
+ describe?: (operation: OperationInfo, price: OperationPrice) => string | undefined;
38
+ /** Include the price and its breakdown in 402 bodies. Default: true. */
39
+ exposePrice?: boolean;
40
+ };
41
+ export type ChargeOutcome =
42
+ /** Nothing to charge. Execute normally. */
43
+ {
44
+ status: 'free';
45
+ price: OperationPrice;
46
+ }
47
+ /** Send `challenge` (an MPP 402) to the client. */
48
+ | {
49
+ status: 'payment-required';
50
+ price: OperationPrice;
51
+ scope: string;
52
+ challenge: Response;
53
+ }
54
+ /** Paid. Execute, then pass the response through `withReceipt`. */
55
+ | {
56
+ status: 'paid';
57
+ price: OperationPrice;
58
+ scope: string;
59
+ withReceipt: (response: Response) => Response;
60
+ }
61
+ /** The operation could not be priced; it must not run. */
62
+ | {
63
+ status: 'rejected';
64
+ error: PricingError;
65
+ };
66
+ declare function sha256Hex(text: string): Promise<string>;
67
+ export { sha256Hex };
68
+ /**
69
+ * Scope binding a Challenge to a set of operations: `graphql:<sha256 of document, operation name and variables>`.
70
+ * mppx stores it in the Challenge's HMAC-protected metadata and refuses Credentials whose scope differs.
71
+ */
72
+ export declare function operationScope(operations: readonly OperationInput[]): Promise<string>;
73
+ /**
74
+ * Prices one or more operations and charges for them through mppx. Every adapter (Fetch, Yoga,
75
+ * Apollo) goes through here. The price is recomputed from the retried request, so a Credential
76
+ * for a cheaper operation never pays for a more expensive one.
77
+ */
78
+ export declare function chargeOperations(request: Request, schema: GraphQLSchema, operations: readonly OperationInput[], options: FareOptions): Promise<ChargeOutcome>;
79
+ export type GraphQLErrorBody = {
80
+ message: string;
81
+ extensions: Record<string, unknown>;
82
+ };
83
+ export declare function priceExtension(price: OperationPrice): {
84
+ breakdownTruncated?: boolean | undefined;
85
+ amount: string;
86
+ decimals: number;
87
+ breakdown: import("./price.js").PriceBreakdownItem[];
88
+ };
89
+ export declare function paymentError(status: number, price: OperationPrice, exposePrice: boolean): GraphQLErrorBody;
90
+ export declare function pricingErrorBody(error: PricingError, exposePrice?: boolean): GraphQLErrorBody;
91
+ /**
92
+ * Turns mppx's 402 into a response GraphQL clients understand: same status and headers
93
+ * (WWW-Authenticate carries the Challenge), and a body with both the problem details mppx
94
+ * wrote and a GraphQL `errors` array. HTML paywalls are passed through untouched.
95
+ */
96
+ export declare function graphqlChallenge(challenge: Response, price: OperationPrice, exposePrice: boolean): Promise<Response>;
97
+ export declare function jsonResponse(status: number, errors: readonly GraphQLErrorBody[], headers?: HeadersInit): Response;
98
+ /** Copies the headers mppx adds for a receipt onto an existing response. */
99
+ export declare function receiptHeaders(withReceipt: (response: Response) => Response): Headers;
100
+ export type ParsedDocument = {
101
+ document: DocumentNode;
102
+ errors?: undefined;
103
+ } | {
104
+ document?: undefined;
105
+ errors: readonly GraphQLError[];
106
+ };
107
+ export type DocumentLimits = {
108
+ /** Longest query text accepted, in characters. Default: 100000. */
109
+ maxQueryLength?: number;
110
+ /** Most lexical tokens a document may have (graphql-js `maxTokens`). Default: 15000. */
111
+ maxTokens?: number;
112
+ /** Extra validation rules your server applies, so their failures are refused before payment. */
113
+ validationRules?: readonly ValidationRule[];
114
+ };
115
+ /** Small LRU of parse + validate results. Only short queries are cached, so memory stays bounded. */
116
+ export declare function createDocumentCache(limits?: DocumentLimits, max?: number): {
117
+ get(schema: GraphQLSchema, query: string): ParsedDocument;
118
+ clear(): void;
119
+ };