@foir/sdk 0.7.0 → 0.9.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/CHANGELOG.md +857 -4
- package/README.md +46 -15
- package/dist/auth/auth.d.ts +104 -1
- package/dist/auth/hosted.d.ts +22 -0
- package/dist/auth/index.d.ts +13 -7
- package/dist/auth/operations.d.ts +85 -64
- package/dist/auth/ops.d.ts +99 -0
- package/dist/auth.js +786 -106
- package/dist/chunk-IGNLWT3H.js +35 -0
- package/dist/chunk-OHYVKTGB.js +45 -0
- package/dist/chunk-TAL5H6VA.js +39 -0
- package/dist/client.js +25 -31
- package/dist/codegen/block-registry.d.ts +13 -19
- package/dist/codegen/block-registry.js +3 -31
- package/dist/codegen/block-scaffold.d.ts +12 -21
- package/dist/codegen/block-scaffold.js +3 -41
- package/dist/codegen/index.d.ts +9 -4
- package/dist/codegen/index.js +7 -11
- package/dist/codegen/item-registry.d.ts +28 -0
- package/dist/codegen/item-registry.js +7 -0
- package/dist/codegen/item-scaffold.d.ts +29 -0
- package/dist/codegen/item-scaffold.js +7 -0
- package/dist/codegen/preset.d.ts +14 -7
- package/dist/codegen/shared.d.ts +20 -9
- package/dist/content/content-renderer.d.ts +90 -24
- package/dist/content/index.d.ts +13 -7
- package/dist/content/richtext.d.ts +66 -18
- package/dist/content.js +227 -80
- package/dist/data/builtins.d.ts +369 -92
- package/dist/data/client.d.ts +97 -25
- package/dist/data/document.d.ts +251 -0
- package/dist/data/index.d.ts +38 -19
- package/dist/data/manifest.d.ts +290 -8
- package/dist/data/schema.d.ts +78 -8
- package/dist/data/selection.d.ts +231 -37
- package/dist/data/subjects.d.ts +221 -0
- package/dist/data/subscription-document.d.ts +75 -0
- package/dist/data/subscriptions.d.ts +181 -0
- package/dist/data/transport.d.ts +255 -37
- package/dist/data/ws-transport.d.ts +197 -0
- package/dist/data.js +1419 -45
- package/dist/graphql/cdn-route.d.ts +12 -28
- package/dist/media/index.d.ts +6 -2
- package/dist/media/media.d.ts +2 -2
- package/dist/media/screen-capture.d.ts +81 -0
- package/dist/media.js +97 -0
- package/dist/next.js +663 -182
- package/dist/push.js +700 -90
- package/dist/react/FoirFeedback.d.ts +62 -0
- package/dist/react/feedback-notes.d.ts +101 -0
- package/dist/react/index.d.ts +7 -1
- package/dist/react.js +1272 -194
- package/dist/realtime.js +1 -1
- package/dist/server/events-client.d.ts +279 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server.js +298 -0
- package/dist/transport/foir-http.d.ts +23 -6
- package/dist/transport/persisted-query.d.ts +68 -0
- package/dist/transport/retry.d.ts +1 -1
- package/package.json +13 -1
- package/dist/chunk-ADO5FJNM.js +0 -30
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graphql-ws transport — `@foir/sdk/data`'s second wire, beside the HTTP
|
|
3
|
+
* one.
|
|
4
|
+
*
|
|
5
|
+
* ---------------------------------------------------------------------------
|
|
6
|
+
* Why the protocol is implemented here rather than imported
|
|
7
|
+
* ---------------------------------------------------------------------------
|
|
8
|
+
*
|
|
9
|
+
* `graphql-ws` (the npm package) would do this, and `packages/shell` uses it.
|
|
10
|
+
* It is not used here for three reasons that are specific to this subpath:
|
|
11
|
+
*
|
|
12
|
+
* 1. `@foir/sdk/data` has exactly one runtime dependency and a build-time
|
|
13
|
+
* assertion that nothing in its import graph reaches `graphql` or a Node
|
|
14
|
+
* built-in. A second runtime dependency on this subpath is a real cost to
|
|
15
|
+
* every storefront that installs it.
|
|
16
|
+
* 2. The protocol surface actually used is small and closed: `connection_init`
|
|
17
|
+
* / `connection_ack`, `subscribe`, `next` / `error` / `complete`, `ping` /
|
|
18
|
+
* `pong`. The server (`api-public/internal/ws`) implements exactly that
|
|
19
|
+
* set and nothing else, so there is no negotiation to get wrong.
|
|
20
|
+
* 3. Reconnect-with-resubscribe has to be OURS regardless. A generic client
|
|
21
|
+
* reconnects the socket; what a caller needs is their subscriptions still
|
|
22
|
+
* running afterwards, which means replaying the subscribe frames — and
|
|
23
|
+
* that bookkeeping is the part worth testing, not the framing.
|
|
24
|
+
*
|
|
25
|
+
* The wire is the standard `graphql-transport-ws` subprotocol either way, so a
|
|
26
|
+
* consumer who prefers the package can still talk to the same endpoint.
|
|
27
|
+
*
|
|
28
|
+
* ---------------------------------------------------------------------------
|
|
29
|
+
* Credentials
|
|
30
|
+
* ---------------------------------------------------------------------------
|
|
31
|
+
*
|
|
32
|
+
* A WebSocket handshake carries no request headers a browser will let you set,
|
|
33
|
+
* so graphql-ws puts credentials in the `connection_init` payload. This client
|
|
34
|
+
* builds that payload from `buildFoirHeaders` — the same function the HTTP
|
|
35
|
+
* transport builds its headers from — and drops only `Content-Type`, which
|
|
36
|
+
* means nothing on a socket. The server reads `x-api-key` and `Authorization`
|
|
37
|
+
* from those params under exactly those names, so the two surfaces authenticate
|
|
38
|
+
* with one builder rather than two spellings that can drift.
|
|
39
|
+
*
|
|
40
|
+
* A credential the header builder does not know about goes in
|
|
41
|
+
* `config.connectionParams`, which is a FUNCTION and is called on every
|
|
42
|
+
* connect — so a short-lived credential is re-resolved on reconnect instead of
|
|
43
|
+
* being replayed after it expired.
|
|
44
|
+
*/
|
|
45
|
+
import { FoirRequestError } from '../transport/foir-http';
|
|
46
|
+
import { type FoirClientConfig } from './transport';
|
|
47
|
+
/**
|
|
48
|
+
* The slice of `WebSocket` this transport uses.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately the event-handler PROPERTIES rather than `addEventListener`,
|
|
51
|
+
* because that is the intersection of the browser's `WebSocket`, Node's global
|
|
52
|
+
* one, and the `ws` package — so a consumer can pass any of the three without
|
|
53
|
+
* an adapter.
|
|
54
|
+
*/
|
|
55
|
+
export interface FoirWebSocketLike {
|
|
56
|
+
readyState: number;
|
|
57
|
+
send(data: string): void;
|
|
58
|
+
close(code?: number, reason?: string): void;
|
|
59
|
+
onopen: ((event: unknown) => void) | null;
|
|
60
|
+
onmessage: ((event: {
|
|
61
|
+
data: unknown;
|
|
62
|
+
}) => void) | null;
|
|
63
|
+
onerror: ((event: unknown) => void) | null;
|
|
64
|
+
onclose: ((event: unknown) => void) | null;
|
|
65
|
+
}
|
|
66
|
+
/** Constructs a socket. Matches `new WebSocket(url, protocols)`. */
|
|
67
|
+
export type FoirWebSocketFactory = (url: string, protocols: string | string[]) => FoirWebSocketLike;
|
|
68
|
+
/** How the transport behaves when the socket drops. */
|
|
69
|
+
export interface FoirReconnectPolicy {
|
|
70
|
+
/**
|
|
71
|
+
* Attempts before giving up and reporting the failure to every live
|
|
72
|
+
* subscription. `Infinity` retries forever, which is the right default for a
|
|
73
|
+
* long-lived stream in a browser tab.
|
|
74
|
+
*/
|
|
75
|
+
maxAttempts?: number;
|
|
76
|
+
/** Delay before the first retry. Doubles, capped at `maxDelayMs`. */
|
|
77
|
+
initialDelayMs?: number;
|
|
78
|
+
maxDelayMs?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Random fraction of the delay added on each attempt, so a server restart
|
|
81
|
+
* does not bring every client back simultaneously. 0 disables it, which is
|
|
82
|
+
* what tests want and production does not.
|
|
83
|
+
*/
|
|
84
|
+
jitter?: number;
|
|
85
|
+
}
|
|
86
|
+
/** A live subscription. Idempotent teardown. */
|
|
87
|
+
export interface FoirSubscription {
|
|
88
|
+
unsubscribe(): void;
|
|
89
|
+
}
|
|
90
|
+
export interface FoirSubscriptionHandlers<T> {
|
|
91
|
+
/** One event. Called for every `next` frame the server pushes. */
|
|
92
|
+
onData: (value: T) => void;
|
|
93
|
+
/**
|
|
94
|
+
* A terminal error for THIS subscription: the server refused it, or the
|
|
95
|
+
* transport gave up reconnecting. Not called for a drop the transport
|
|
96
|
+
* recovers from — that is what `onReconnect` reports.
|
|
97
|
+
*/
|
|
98
|
+
onError?: (error: FoirRequestError) => void;
|
|
99
|
+
/** The server ended the stream (an execution reached a terminal state). */
|
|
100
|
+
onComplete?: () => void;
|
|
101
|
+
/**
|
|
102
|
+
* The socket dropped and this subscription was re-established.
|
|
103
|
+
*
|
|
104
|
+
* Worth surfacing rather than hiding, because a subscription is not a
|
|
105
|
+
* durable log: events published while the socket was down were not buffered
|
|
106
|
+
* anywhere and are gone. A consumer that needs them refetches here. (The
|
|
107
|
+
* `events` subscription is the exception — it is cursor-resumable — and it is
|
|
108
|
+
* not one of the fields this client addresses.)
|
|
109
|
+
*/
|
|
110
|
+
onReconnect?: () => void;
|
|
111
|
+
}
|
|
112
|
+
/** What to send in one `subscribe` frame. */
|
|
113
|
+
export interface FoirSubscribeRequest {
|
|
114
|
+
query: string;
|
|
115
|
+
variables?: Record<string, unknown>;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Derive the WebSocket endpoint from the HTTP one.
|
|
119
|
+
*
|
|
120
|
+
* `apiUrl` is conventionally `https://api.foir.dev` or
|
|
121
|
+
* `https://api.foir.dev/graphql` — people paste whichever their env holds, and
|
|
122
|
+
* the HTTP transport already strips a trailing `/graphql` for the same reason.
|
|
123
|
+
* The socket lives at `/graphql/ws`, so this normalises to the origin and
|
|
124
|
+
* appends it.
|
|
125
|
+
*/
|
|
126
|
+
export declare function foirSocketEndpoint(apiUrl: string): string;
|
|
127
|
+
/**
|
|
128
|
+
* The socket, its subscriptions, and the reconnect loop.
|
|
129
|
+
*
|
|
130
|
+
* One instance per client. It is lazy — nothing connects until the first
|
|
131
|
+
* `subscribe` — and it closes itself when the last subscription is dropped, so
|
|
132
|
+
* a client that is constructed and only used for queries never opens a socket.
|
|
133
|
+
*/
|
|
134
|
+
export declare class FoirSocketTransport {
|
|
135
|
+
private readonly config;
|
|
136
|
+
private readonly policy;
|
|
137
|
+
private readonly registrations;
|
|
138
|
+
private socket;
|
|
139
|
+
private acknowledged;
|
|
140
|
+
private connecting;
|
|
141
|
+
private attempt;
|
|
142
|
+
private nextId;
|
|
143
|
+
private disposed;
|
|
144
|
+
private keepAlive;
|
|
145
|
+
private retryTimer;
|
|
146
|
+
constructor(config: FoirClientConfig);
|
|
147
|
+
/**
|
|
148
|
+
* Start a subscription.
|
|
149
|
+
*
|
|
150
|
+
* Returns synchronously so a caller can hold the handle immediately, while
|
|
151
|
+
* the connect and the handshake proceed in the background. A subscription
|
|
152
|
+
* registered before the socket is acknowledged is sent the moment it is —
|
|
153
|
+
* which is the same path a reconnect takes, so the ordinary case and the
|
|
154
|
+
* recovery case exercise one piece of code.
|
|
155
|
+
*/
|
|
156
|
+
subscribe<T>(request: FoirSubscribeRequest, handlers: FoirSubscriptionHandlers<T>): FoirSubscription;
|
|
157
|
+
/** Close the socket and drop every subscription. */
|
|
158
|
+
dispose(): void;
|
|
159
|
+
/** Live subscription count. Exposed for tests and diagnostics. */
|
|
160
|
+
get size(): number;
|
|
161
|
+
private ensureConnected;
|
|
162
|
+
private sendConnectionInit;
|
|
163
|
+
private handleClose;
|
|
164
|
+
private scheduleReconnect;
|
|
165
|
+
private teardownSocket;
|
|
166
|
+
private receive;
|
|
167
|
+
/**
|
|
168
|
+
* Send every registration the current socket has not yet been told about.
|
|
169
|
+
*
|
|
170
|
+
* This IS the resubscribe path. A reconnect clears `sent` on every
|
|
171
|
+
* registration and lands here, so a subscription re-established after a drop
|
|
172
|
+
* takes the same code as one opened for the first time — there is no second
|
|
173
|
+
* implementation that can be correct in one case and wrong in the other.
|
|
174
|
+
*/
|
|
175
|
+
private flush;
|
|
176
|
+
private cancel;
|
|
177
|
+
/**
|
|
178
|
+
* Close the socket once nothing is using it.
|
|
179
|
+
*
|
|
180
|
+
* A socket held open with no subscriptions is a connection the server has to
|
|
181
|
+
* keep, a keepalive timer that keeps a serverless invocation alive, and
|
|
182
|
+
* nothing in return.
|
|
183
|
+
*/
|
|
184
|
+
private closeIfIdle;
|
|
185
|
+
private failAll;
|
|
186
|
+
/**
|
|
187
|
+
* Client-side keepalive.
|
|
188
|
+
*
|
|
189
|
+
* The server pings on its own timer and this client answers, which is enough
|
|
190
|
+
* for the server to know the client is alive. This is the other direction:
|
|
191
|
+
* an idle subscription can sit for hours with no events, and a proxy that
|
|
192
|
+
* times out idle sockets will drop it. A periodic `ping` keeps it warm, and
|
|
193
|
+
* the pong that comes back proves the path is still there.
|
|
194
|
+
*/
|
|
195
|
+
private startKeepAlive;
|
|
196
|
+
private send;
|
|
197
|
+
}
|