@usewind/node 0.1.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/DESIGN.md +404 -0
- package/README.md +65 -0
- package/dist/index.cjs +1035 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +609 -0
- package/dist/index.d.ts +609 -0
- package/dist/index.js +1010 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types — hand-derived from `apps/docs/public/openapi.yaml` (Wind API 0.1.0).
|
|
3
|
+
* Keep in lock-step with that file; it is the source of truth.
|
|
4
|
+
*
|
|
5
|
+
* Money is always integer micro-cents, USD. `100_000_000` mc = $1.00 (1¢ = `1_000_000` mc). Fields end `_mc`.
|
|
6
|
+
*/
|
|
7
|
+
/** `POST /oauth/token` — 200 body, both grant types. */
|
|
8
|
+
interface TokenResponse {
|
|
9
|
+
access_token: string;
|
|
10
|
+
expires_in: number;
|
|
11
|
+
refresh_token: string;
|
|
12
|
+
connection_id: string;
|
|
13
|
+
allocation_mc?: number;
|
|
14
|
+
}
|
|
15
|
+
interface ChatMessage {
|
|
16
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
17
|
+
content: unknown;
|
|
18
|
+
[k: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
/** Present on every successful `POST /v1/ai/*` response. */
|
|
21
|
+
interface WindBlock {
|
|
22
|
+
feature: string | null;
|
|
23
|
+
hold_amount_mc: number;
|
|
24
|
+
provider_cost_mc: number;
|
|
25
|
+
wind_fee_mc: number;
|
|
26
|
+
dev_margin_mc: number;
|
|
27
|
+
user_charge_mc: number;
|
|
28
|
+
allocation_remaining_mc: number;
|
|
29
|
+
}
|
|
30
|
+
interface ChatUsage {
|
|
31
|
+
prompt_tokens: number;
|
|
32
|
+
completion_tokens: number;
|
|
33
|
+
total_tokens: number;
|
|
34
|
+
}
|
|
35
|
+
/** `POST /v1/ai/{model}` — 200 body. */
|
|
36
|
+
interface ChatResult {
|
|
37
|
+
id: string;
|
|
38
|
+
choices: unknown[];
|
|
39
|
+
usage: ChatUsage;
|
|
40
|
+
wind: WindBlock;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Arguments to `.chat()` / `.chatStream()`. No `model` field — model is bound
|
|
44
|
+
* once via `wind.connection(userId).model(id)`, not passed per call. (Feature
|
|
45
|
+
* calls resolve their model server-side from the feature's `default_model`.)
|
|
46
|
+
*/
|
|
47
|
+
interface ChatParams {
|
|
48
|
+
messages: ChatMessage[];
|
|
49
|
+
/** Optional — hold cap resolves request → feature → app default → model ceiling. */
|
|
50
|
+
max_tokens?: number;
|
|
51
|
+
temperature?: number;
|
|
52
|
+
/** Prefer `.chatStream()` over setting this directly. */
|
|
53
|
+
stream?: boolean;
|
|
54
|
+
/** Defaults to `crypto.randomUUID()`; the same value is reused for every internal retry. */
|
|
55
|
+
idempotencyKey?: string;
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}
|
|
58
|
+
interface ChatStreamChunk {
|
|
59
|
+
/** Raw provider SSE `data:` payload, JSON-parsed. */
|
|
60
|
+
delta: unknown;
|
|
61
|
+
/** The final chunk of a stream carries the settled `wind` block; `null` on all others. */
|
|
62
|
+
wind: WindBlock | null;
|
|
63
|
+
}
|
|
64
|
+
/** 'frozen' — Wind's own risk layer, independent of anything you or the user configured; clears when the user reconnects. */
|
|
65
|
+
type ConnectionStatus = 'active' | 'exhausted' | 'revoked' | 'frozen';
|
|
66
|
+
interface Connection {
|
|
67
|
+
id: string;
|
|
68
|
+
app_id: string;
|
|
69
|
+
user_account_id: string;
|
|
70
|
+
/** Optional lifetime ceiling — null means no fixed cap; spend is bounded instead by per-call/velocity/revocation/risk controls. */
|
|
71
|
+
reserved_allocation_mc: number | null;
|
|
72
|
+
consumed_mc: number;
|
|
73
|
+
held_mc: number;
|
|
74
|
+
velocity_limit_mc: number | null;
|
|
75
|
+
status: ConnectionStatus;
|
|
76
|
+
created_at: string;
|
|
77
|
+
}
|
|
78
|
+
interface RevokeResult {
|
|
79
|
+
id: string;
|
|
80
|
+
status: 'revoked';
|
|
81
|
+
revoked_at: string;
|
|
82
|
+
released_mc: number;
|
|
83
|
+
}
|
|
84
|
+
interface AllocationRequestParams {
|
|
85
|
+
targetAllocationMc: number;
|
|
86
|
+
reason?: string;
|
|
87
|
+
}
|
|
88
|
+
interface AllocationRequest {
|
|
89
|
+
id: string;
|
|
90
|
+
connection_id: string;
|
|
91
|
+
status: 'pending' | 'approved' | 'denied' | 'superseded';
|
|
92
|
+
current_allocation_mc: number;
|
|
93
|
+
target_allocation_mc: number;
|
|
94
|
+
approval_url: string;
|
|
95
|
+
}
|
|
96
|
+
interface Account {
|
|
97
|
+
wac_id: string;
|
|
98
|
+
email: string;
|
|
99
|
+
sandbox: boolean;
|
|
100
|
+
balance_available_mc: number;
|
|
101
|
+
balance_reserved_mc: number;
|
|
102
|
+
}
|
|
103
|
+
interface Feature {
|
|
104
|
+
slug: string;
|
|
105
|
+
display_name: string;
|
|
106
|
+
description: string;
|
|
107
|
+
max_cost_per_call_mc?: number | null;
|
|
108
|
+
max_tokens?: number | null;
|
|
109
|
+
rate_limit?: number | null;
|
|
110
|
+
default_model?: string | null;
|
|
111
|
+
/** Overrides App.dev_margin_rate for calls tagged with this feature; null falls back to the app-wide rate. */
|
|
112
|
+
dev_margin_rate?: number | null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The per-user credential the application persists. Carries everything needed to
|
|
116
|
+
* make billed calls for one user, and nothing else. The app stores and returns
|
|
117
|
+
* it whole — never field by field (that is how rotation bugs happen).
|
|
118
|
+
*/
|
|
119
|
+
interface Grant {
|
|
120
|
+
connectionId: string;
|
|
121
|
+
refreshToken: string;
|
|
122
|
+
accessToken?: string;
|
|
123
|
+
accessTokenExpiresAt?: number;
|
|
124
|
+
}
|
|
125
|
+
interface WebhookEvent {
|
|
126
|
+
id: string;
|
|
127
|
+
type: string;
|
|
128
|
+
created_at: string;
|
|
129
|
+
data: Record<string, unknown>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Configuration resolution and validation. Explicit args beat environment
|
|
134
|
+
* variables; env is only consulted for the three credential fields.
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The one persistence abstraction the SDK relies on for correctness.
|
|
139
|
+
*
|
|
140
|
+
* Refresh tokens rotate on every use, so `set` is called both after first
|
|
141
|
+
* connect and after every refresh, and the SDK **awaits it before using the new
|
|
142
|
+
* tokens** — a throw from `set` aborts the operation rather than stranding a
|
|
143
|
+
* rotated refresh token. Implement `set` so it commits in the same transaction
|
|
144
|
+
* as whatever work triggered the refresh.
|
|
145
|
+
*
|
|
146
|
+
* `delete` is called when a connection is revoked (terminal).
|
|
147
|
+
*
|
|
148
|
+
* Any Map-shaped store works; for production back it with your database.
|
|
149
|
+
*/
|
|
150
|
+
interface GrantStore {
|
|
151
|
+
get(userId: string): Promise<Grant | null> | Grant | null;
|
|
152
|
+
set(userId: string, grant: Grant): Promise<void> | void;
|
|
153
|
+
delete(userId: string): Promise<void> | void;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* An in-memory `GrantStore` backed by a `Map`. **Dev/test only** — grants
|
|
157
|
+
* vanish on restart and are not shared across instances, so a multi-instance
|
|
158
|
+
* or restart-prone deployment will spuriously "disconnect" users. Back
|
|
159
|
+
* production with your database instead.
|
|
160
|
+
*/
|
|
161
|
+
declare function memoryGrantStore(): GrantStore;
|
|
162
|
+
interface WindConfig {
|
|
163
|
+
clientId: string;
|
|
164
|
+
clientSecret: string;
|
|
165
|
+
/** Must exactly match a registered redirect URI and the callback route's URL. */
|
|
166
|
+
redirectUri: string;
|
|
167
|
+
baseUrl?: string;
|
|
168
|
+
accountsUrl?: string;
|
|
169
|
+
webhookSecret?: string;
|
|
170
|
+
/** Injectable for tests; defaults to the global `fetch`. */
|
|
171
|
+
fetch?: typeof fetch;
|
|
172
|
+
/** Injectable for tests; defaults to `Date.now`. */
|
|
173
|
+
clock?: () => number;
|
|
174
|
+
/**
|
|
175
|
+
* Where per-user grants are persisted. Required for every user-scoped path
|
|
176
|
+
* (`wind.routes()`, `wind.handler()`, `wind.connection()`); those throw
|
|
177
|
+
* `WindConfigError` without it. Not needed for client-credentials-only use
|
|
178
|
+
* (`wind.connections.*`, `wind.account()`, `wind.raw`).
|
|
179
|
+
*/
|
|
180
|
+
grantStore?: GrantStore;
|
|
181
|
+
/**
|
|
182
|
+
* Optional notification — fires once, after a user first connects. Runs after
|
|
183
|
+
* `grantStore.set` has committed. Not correctness-critical: a throw here is
|
|
184
|
+
* caught and ignored. Use it for audit logs, analytics, welcome emails.
|
|
185
|
+
*/
|
|
186
|
+
onConnected?: (userId: string, grant: Grant) => Promise<void> | void;
|
|
187
|
+
/**
|
|
188
|
+
* Optional notification — fires after every refresh-token rotation, once
|
|
189
|
+
* `grantStore.set` has committed. Errors are caught and ignored.
|
|
190
|
+
*/
|
|
191
|
+
onGrantRotated?: (userId: string, grant: Grant) => Promise<void> | void;
|
|
192
|
+
}
|
|
193
|
+
interface ResolvedConfig extends Required<Omit<WindConfig, 'webhookSecret' | 'grantStore' | 'onConnected' | 'onGrantRotated'>> {
|
|
194
|
+
webhookSecret?: string;
|
|
195
|
+
grantStore?: GrantStore;
|
|
196
|
+
onConnected?: WindConfig['onConnected'];
|
|
197
|
+
onGrantRotated?: WindConfig['onGrantRotated'];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* RawClient — one method per openapi.yaml path, no ergonomics. The escape hatch
|
|
202
|
+
* behind `wind.raw`, and the transport every higher-level module builds on.
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
interface RawRequest {
|
|
206
|
+
method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
207
|
+
path: string;
|
|
208
|
+
/** `application/json` body; omit for GET. */
|
|
209
|
+
body?: unknown;
|
|
210
|
+
headers?: Record<string, string>;
|
|
211
|
+
/** `Authorization: Bearer` value — mutually exclusive with `basic`. */
|
|
212
|
+
bearer?: string;
|
|
213
|
+
/** Use HTTP Basic client credentials for `Authorization`. */
|
|
214
|
+
basic?: boolean;
|
|
215
|
+
signal?: AbortSignal;
|
|
216
|
+
}
|
|
217
|
+
interface RawResponse<T = unknown> {
|
|
218
|
+
status: number;
|
|
219
|
+
headers: Headers;
|
|
220
|
+
body: T;
|
|
221
|
+
requestId: string | null;
|
|
222
|
+
}
|
|
223
|
+
declare class RawClient {
|
|
224
|
+
private readonly config;
|
|
225
|
+
constructor(config: ResolvedConfig);
|
|
226
|
+
/** Low-level: perform one request, parse JSON, never throw on non-2xx (returns the response). */
|
|
227
|
+
request<T = unknown>(req: RawRequest): Promise<RawResponse<T>>;
|
|
228
|
+
/** `POST /oauth/token` — `grant_type=authorization_code`. */
|
|
229
|
+
exchangeCode(args: {
|
|
230
|
+
code: string;
|
|
231
|
+
redirectUri: string;
|
|
232
|
+
codeVerifier: string;
|
|
233
|
+
}): Promise<TokenResponse>;
|
|
234
|
+
/** `POST /oauth/token` — `grant_type=refresh_token`. */
|
|
235
|
+
refresh(refreshToken: string): Promise<TokenResponse>;
|
|
236
|
+
private postToken;
|
|
237
|
+
/** `POST /v1/ai/{model}`. Returns the raw response so callers can branch on status/headers. */
|
|
238
|
+
inference(args: {
|
|
239
|
+
model: string;
|
|
240
|
+
accessToken: string;
|
|
241
|
+
idempotencyKey: string;
|
|
242
|
+
feature?: string;
|
|
243
|
+
body: unknown;
|
|
244
|
+
signal?: AbortSignal;
|
|
245
|
+
}): Promise<RawResponse>;
|
|
246
|
+
/** `POST /v1/ai/features/{slug}`. */
|
|
247
|
+
inferenceFeature(args: {
|
|
248
|
+
slug: string;
|
|
249
|
+
accessToken: string;
|
|
250
|
+
idempotencyKey: string;
|
|
251
|
+
body: unknown;
|
|
252
|
+
signal?: AbortSignal;
|
|
253
|
+
}): Promise<RawResponse>;
|
|
254
|
+
listConnections(): Promise<Connection[]>;
|
|
255
|
+
getConnection(id: string): Promise<Connection>;
|
|
256
|
+
revokeConnection(id: string): Promise<RevokeResult>;
|
|
257
|
+
createAllocationRequest(id: string, body: {
|
|
258
|
+
target_allocation_mc: number;
|
|
259
|
+
reason?: string;
|
|
260
|
+
}): Promise<AllocationRequest>;
|
|
261
|
+
getAccount(): Promise<Account>;
|
|
262
|
+
listFeatures(appId: string): Promise<Feature[]>;
|
|
263
|
+
registerFeature(appId: string, feature: Feature): Promise<Feature>;
|
|
264
|
+
updateFeature(appId: string, slug: string, patch: Partial<Feature>): Promise<Feature>;
|
|
265
|
+
private unwrap;
|
|
266
|
+
get<T = unknown>(path: string, init?: Omit<RawRequest, 'method' | 'path' | 'body'>): Promise<RawResponse<T>>;
|
|
267
|
+
post<T = unknown>(path: string, init?: Omit<RawRequest, 'method' | 'path'>): Promise<RawResponse<T>>;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The connect flow, framework-neutral. Adapters (Express, fetch-handler) call
|
|
272
|
+
* into `OAuthFlow`; they own request/response plumbing, this owns the protocol.
|
|
273
|
+
*/
|
|
274
|
+
|
|
275
|
+
/** The short-lived value stashed between `/wind/start` and `/wind/callback`. */
|
|
276
|
+
interface ConnectTransaction {
|
|
277
|
+
verifier: string;
|
|
278
|
+
state: string;
|
|
279
|
+
createdAt: number;
|
|
280
|
+
}
|
|
281
|
+
/** Pluggable storage for the transaction. Default is a signed cookie (see adapters). */
|
|
282
|
+
interface TransactionStore {
|
|
283
|
+
put(id: string, tx: ConnectTransaction): Promise<void> | void;
|
|
284
|
+
take(id: string): Promise<ConnectTransaction | null> | ConnectTransaction | null;
|
|
285
|
+
}
|
|
286
|
+
interface RoutesOptions {
|
|
287
|
+
startPath?: string;
|
|
288
|
+
callbackPath?: string;
|
|
289
|
+
/** REQUIRED — Wind is not your login. Identify the signed-in user of your app. */
|
|
290
|
+
currentUser: (req: unknown) => string | null | undefined | Promise<string | null | undefined>;
|
|
291
|
+
/** Override the config's `grantStore` for these routes. Falls back to config. */
|
|
292
|
+
grantStore?: GrantStore;
|
|
293
|
+
/** Optional post-connect notification (see `WindConfig.onConnected`). */
|
|
294
|
+
onConnected?: (userId: string, grant: Grant) => Promise<void> | void;
|
|
295
|
+
/** Pre-fill the consent screen's spend cap (micro-cents). */
|
|
296
|
+
requestedAllocationMc?: number;
|
|
297
|
+
/** Feature slugs → the consent "what for" line. */
|
|
298
|
+
features?: string[];
|
|
299
|
+
session?: 'cookie' | TransactionStore;
|
|
300
|
+
successRedirect?: string;
|
|
301
|
+
onError?: (err: unknown, req: unknown, res: unknown) => void;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Access-token cache + atomic refresh-token rotation.
|
|
306
|
+
*
|
|
307
|
+
* The one rule: after a refresh, the rotated grant is written via
|
|
308
|
+
* `grantStore.set(userId, next)` and **awaited before** the caller may use the
|
|
309
|
+
* new access token. A crash between "got new refresh token" and "persisted it"
|
|
310
|
+
* kills the connection, so the SDK never proceeds past an unpersisted rotation.
|
|
311
|
+
* The optional `onGrantRotated` hook fires afterward and its errors are ignored.
|
|
312
|
+
*/
|
|
313
|
+
|
|
314
|
+
declare class TokenManager {
|
|
315
|
+
private readonly config;
|
|
316
|
+
private readonly raw;
|
|
317
|
+
constructor(config: ResolvedConfig, raw: RawClient);
|
|
318
|
+
/** True if the grant's cached access token is present and outside the skew window. */
|
|
319
|
+
isFresh(grant: Grant): boolean;
|
|
320
|
+
/**
|
|
321
|
+
* Return a grant guaranteed to have a usable access token. If a refresh was
|
|
322
|
+
* needed, `grantStore.set(userId, next)` has already been awaited before this
|
|
323
|
+
* resolves (then `onGrantRotated` fired). Refresh failure (`invalid_grant`)
|
|
324
|
+
* throws `ConnectionRevokedError` — the caller then clears the store entry.
|
|
325
|
+
*/
|
|
326
|
+
ensureAccessToken(userId: string, grant: Grant): Promise<Grant>;
|
|
327
|
+
/** Force a refresh regardless of cache state (used on a 401 retry). */
|
|
328
|
+
forceRefresh(userId: string, grant: Grant): Promise<Grant>;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Billed-call surface: `wind.connection(userId).model('gpt-5').chat(params)`
|
|
333
|
+
* or `wind.connection(userId).feature('summarize').chat(params)`.
|
|
334
|
+
*
|
|
335
|
+
* Model and feature are DELIBERATELY mutually exclusive, no chaining — each
|
|
336
|
+
* hits a different route (`POST /v1/ai/{model}` vs `POST /v1/ai/features/{slug}`)
|
|
337
|
+
* with a different model-resolution rule, and picking one is an explicit,
|
|
338
|
+
* unambiguous choice at the call site rather than something that falls out of
|
|
339
|
+
* which optional param you happened to pass. The one combination this can't
|
|
340
|
+
* express — a specific model AND a feature tag on the same call (`X-Wind-Feature`
|
|
341
|
+
* on `POST /v1/ai/{model}`) — is still reachable via `wind.raw.inference(...)`;
|
|
342
|
+
* "wrap, don't hide" means the ergonomic surface enforcing a simpler mental
|
|
343
|
+
* model doesn't have to cost the escape hatch anything.
|
|
344
|
+
*/
|
|
345
|
+
|
|
346
|
+
interface Chattable {
|
|
347
|
+
/** Refresh-and-retry, idempotency key, and error mapping are all handled internally. */
|
|
348
|
+
chat(params: ChatParams): Promise<ChatResult>;
|
|
349
|
+
/** Streaming variant — async iterable; the last chunk carries the settled `wind` block. */
|
|
350
|
+
chatStream(params: ChatParams): AsyncIterable<ChatStreamChunk>;
|
|
351
|
+
}
|
|
352
|
+
/** `POST /v1/ai/{model}` — no feature tag; see the mutual-exclusivity note above. */
|
|
353
|
+
declare class ModelScope implements Chattable {
|
|
354
|
+
private readonly ctx;
|
|
355
|
+
private readonly userId;
|
|
356
|
+
private readonly model;
|
|
357
|
+
constructor(ctx: ScopeContext, userId: string, model: string);
|
|
358
|
+
chat(params: ChatParams): Promise<ChatResult>;
|
|
359
|
+
chatStream(params: ChatParams): AsyncIterable<ChatStreamChunk>;
|
|
360
|
+
}
|
|
361
|
+
/** `POST /v1/ai/features/{slug}` — model resolved server-side from the feature's `default_model`. */
|
|
362
|
+
declare class FeatureScope implements Chattable {
|
|
363
|
+
private readonly ctx;
|
|
364
|
+
private readonly userId;
|
|
365
|
+
private readonly feature;
|
|
366
|
+
constructor(ctx: ScopeContext, userId: string, feature: string);
|
|
367
|
+
chat(params: ChatParams): Promise<ChatResult>;
|
|
368
|
+
chatStream(params: ChatParams): AsyncIterable<ChatStreamChunk>;
|
|
369
|
+
}
|
|
370
|
+
declare class ConnectionScope {
|
|
371
|
+
private readonly ctx;
|
|
372
|
+
private readonly userId;
|
|
373
|
+
constructor(ctx: ScopeContext, userId: string);
|
|
374
|
+
/** `wind.connection(userId).model('gpt-5').chat(...)` — hits `POST /v1/ai/{model}`. */
|
|
375
|
+
model(id: string): ModelScope;
|
|
376
|
+
/** `wind.connection(userId).feature('summarize').chat(...)` — hits `POST /v1/ai/features/{slug}`. */
|
|
377
|
+
feature(slug: string): FeatureScope;
|
|
378
|
+
/** `GET /v1/connections/{id}` for this user's connection. Named `.get()`, not `.connection()` — avoids `wind.connection(userId).connection()`. */
|
|
379
|
+
get(): Promise<Connection>;
|
|
380
|
+
/** `POST /v1/connections/{id}/revoke`. */
|
|
381
|
+
revoke(): Promise<RevokeResult>;
|
|
382
|
+
/** `POST /v1/connections/{id}/allocation-requests` — returns `{ approvalUrl }`. */
|
|
383
|
+
requestIncrease(params: AllocationRequestParams): Promise<AllocationRequest>;
|
|
384
|
+
}
|
|
385
|
+
/** Shared dependencies handed to every scope. */
|
|
386
|
+
interface ScopeContext {
|
|
387
|
+
config: ResolvedConfig;
|
|
388
|
+
raw: RawClient;
|
|
389
|
+
tokens: TokenManager;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Express adapter. `wind.routes(opts)` returns a middleware that owns
|
|
394
|
+
* `startPath` + `callbackPath`; every other request falls through to `next()`.
|
|
395
|
+
*
|
|
396
|
+
* Express is detected structurally (duck-typed req/res) and never imported, so
|
|
397
|
+
* this package has no Express dependency.
|
|
398
|
+
*/
|
|
399
|
+
|
|
400
|
+
/** Minimal shape this adapter relies on — a subset of Express req/res. */
|
|
401
|
+
interface ExpressLikeRequest {
|
|
402
|
+
method?: string;
|
|
403
|
+
url?: string;
|
|
404
|
+
path?: string;
|
|
405
|
+
query?: Record<string, unknown>;
|
|
406
|
+
headers: Record<string, string | string[] | undefined>;
|
|
407
|
+
}
|
|
408
|
+
interface ExpressLikeResponse {
|
|
409
|
+
statusCode: number;
|
|
410
|
+
setHeader(name: string, value: string): void;
|
|
411
|
+
redirect(url: string): void;
|
|
412
|
+
status(code: number): ExpressLikeResponse;
|
|
413
|
+
send(body?: unknown): void;
|
|
414
|
+
}
|
|
415
|
+
type NextFn = (err?: unknown) => void;
|
|
416
|
+
type ExpressMiddleware = (req: ExpressLikeRequest, res: ExpressLikeResponse, next: NextFn) => void;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Web-standard adapter. `wind.handler(opts)` returns `(Request) => Promise<Response>`
|
|
420
|
+
* covering both `startPath` and `callbackPath`. Mount under a catch-all route:
|
|
421
|
+
*
|
|
422
|
+
* // Next.js — app/wind/[...wind]/route.ts
|
|
423
|
+
* export const GET = wind.handler({ currentUser: … })
|
|
424
|
+
*
|
|
425
|
+
* Also works for Hono, Bun.serve, Deno, and Remix.
|
|
426
|
+
*/
|
|
427
|
+
|
|
428
|
+
type FetchHandler = (req: Request) => Promise<Response>;
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Error hierarchy for `@usewind/node`.
|
|
432
|
+
*
|
|
433
|
+
* This module is fully implemented (the skeleton stubs live elsewhere). The
|
|
434
|
+
* mapping mirrors `apps/docs/src/content/docs/errors-and-types.mdx`.
|
|
435
|
+
*/
|
|
436
|
+
interface WindErrorEnvelope {
|
|
437
|
+
error?: {
|
|
438
|
+
code?: string;
|
|
439
|
+
message?: string;
|
|
440
|
+
type?: string;
|
|
441
|
+
connection_id?: string | null;
|
|
442
|
+
increase_url?: string | null;
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
interface WindErrorInit {
|
|
446
|
+
status?: number;
|
|
447
|
+
code?: string;
|
|
448
|
+
type?: string;
|
|
449
|
+
connectionId?: string | null;
|
|
450
|
+
requestId?: string | null;
|
|
451
|
+
raw?: unknown;
|
|
452
|
+
cause?: unknown;
|
|
453
|
+
}
|
|
454
|
+
/** Base class for everything this SDK throws. */
|
|
455
|
+
declare class WindError extends Error {
|
|
456
|
+
readonly status?: number;
|
|
457
|
+
readonly code?: string;
|
|
458
|
+
readonly type?: string;
|
|
459
|
+
readonly connectionId?: string | null;
|
|
460
|
+
readonly requestId?: string | null;
|
|
461
|
+
readonly raw?: unknown;
|
|
462
|
+
constructor(message: string, init?: WindErrorInit);
|
|
463
|
+
/**
|
|
464
|
+
* Build the right subclass from an HTTP response. Matches on the envelope's
|
|
465
|
+
* `error.code` first, then falls back to `status`.
|
|
466
|
+
*/
|
|
467
|
+
static fromResponse(status: number, body: WindErrorEnvelope | undefined, extra?: {
|
|
468
|
+
requestId?: string | null;
|
|
469
|
+
retryAfterMs?: number;
|
|
470
|
+
}): WindError;
|
|
471
|
+
}
|
|
472
|
+
/** Missing or invalid configuration. Thrown at `configure()` or first use. */
|
|
473
|
+
declare class WindConfigError extends WindError {
|
|
474
|
+
}
|
|
475
|
+
/** `grantStore.get(userId)` returned null — the user has no Wind connection yet. */
|
|
476
|
+
declare class WindNotConnectedError extends WindError {
|
|
477
|
+
}
|
|
478
|
+
/** Webhook HMAC mismatch, malformed signature header, or stale timestamp. */
|
|
479
|
+
declare class WindSignatureError extends WindError {
|
|
480
|
+
}
|
|
481
|
+
/** Marks an unimplemented skeleton path. Remove as bodies land. */
|
|
482
|
+
declare class WindNotImplementedError extends WindError {
|
|
483
|
+
constructor(what: string);
|
|
484
|
+
}
|
|
485
|
+
/** Any non-2xx not classified below. */
|
|
486
|
+
declare class WindApiError extends WindError {
|
|
487
|
+
}
|
|
488
|
+
/** 402 `allocation_exhausted` — connection spent its reserved cap. */
|
|
489
|
+
declare class AllocationExhaustedError extends WindApiError {
|
|
490
|
+
readonly increaseUrl: string | null;
|
|
491
|
+
constructor(message: string, init?: WindErrorInit & {
|
|
492
|
+
increaseUrl?: string | null;
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
/** 402 `feature_cost_exceeded` — one call blew a per-feature guardrail. Not billed. */
|
|
496
|
+
declare class FeatureCostExceededError extends WindApiError {
|
|
497
|
+
}
|
|
498
|
+
/** 400 `feature_model_not_configured` — the feature has no `default_model` set. Config bug, not runtime-retryable. */
|
|
499
|
+
declare class FeatureModelNotConfiguredError extends WindApiError {
|
|
500
|
+
}
|
|
501
|
+
/** 403 `connection_revoked` (or refresh `invalid_grant`). Terminal — clear stored grant, prompt reconnect. */
|
|
502
|
+
declare class ConnectionRevokedError extends WindApiError {
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* 403 `connection_frozen` — Wind's own platform risk layer, not user
|
|
506
|
+
* revocation, froze this connection on an abnormal spend pattern. Unlike
|
|
507
|
+
* `ConnectionRevokedError`, do NOT delete the stored grant — the same
|
|
508
|
+
* connection reactivates once the user goes through the connect flow again.
|
|
509
|
+
*/
|
|
510
|
+
declare class ConnectionFrozenError extends WindApiError {
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* 403 `app_suspended` — the app itself is switched off, by Wind or by you.
|
|
514
|
+
* Every connection is affected, so stop calling and surface it; don't clear
|
|
515
|
+
* any grants — they work again once the app is unsuspended.
|
|
516
|
+
*/
|
|
517
|
+
declare class AppSuspendedError extends WindApiError {
|
|
518
|
+
}
|
|
519
|
+
/** 403 `model_not_allowed` — model is outside the app's allowlist. Integration bug. */
|
|
520
|
+
declare class ModelNotAllowedError extends WindApiError {
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* 429 — a spend or request limit, not an error in the call. `code` says which:
|
|
524
|
+
* `rate_limited` (Feature.rate_limit), `daily_limit_reached` (the connection's
|
|
525
|
+
* 24h limit), `app_spend_limited` (the app's daily limit on its tier) or
|
|
526
|
+
* `sandbox_live_cost_limited` (the daily live-model allowance in sandbox).
|
|
527
|
+
* The SDK does not auto-sleep; back off using `retryAfterMs`.
|
|
528
|
+
*/
|
|
529
|
+
declare class RateLimitedError extends WindApiError {
|
|
530
|
+
readonly retryAfterMs: number | null;
|
|
531
|
+
constructor(message: string, init?: WindErrorInit & {
|
|
532
|
+
retryAfterMs?: number;
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
/** 429 `risk_throttled` — Wind's platform risk layer, not the per-feature rate limit. Same shape as `RateLimitedError`, distinct so callers can tell the two apart. */
|
|
536
|
+
declare class RiskThrottledError extends WindApiError {
|
|
537
|
+
readonly retryAfterMs: number | null;
|
|
538
|
+
constructor(message: string, init?: WindErrorInit & {
|
|
539
|
+
retryAfterMs?: number;
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
/** 502 `provider_error` — upstream provider failed. Retried internally with the same idempotency key; not billed. */
|
|
543
|
+
declare class ProviderError extends WindApiError {
|
|
544
|
+
}
|
|
545
|
+
/** 401 `token_expired` — internal; surfaces only if a refresh also fails. */
|
|
546
|
+
declare class TokenExpiredError extends WindApiError {
|
|
547
|
+
}
|
|
548
|
+
/** 409 `idempotency_key_reused` — the same Idempotency-Key was sent with a different request body. Integration bug (or a genuine UUID collision, practically never). */
|
|
549
|
+
declare class IdempotencyKeyReusedError extends WindApiError {
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* @usewind/node — server SDK for Connect with Wind.
|
|
554
|
+
*
|
|
555
|
+
* Two ways in:
|
|
556
|
+
* import { wind } from '@usewind/node' // lazily reads WIND_CLIENT_* from env
|
|
557
|
+
* import { createWind } from '@usewind/node' // explicit instance (tests, multi-tenant)
|
|
558
|
+
*
|
|
559
|
+
* See DESIGN.md for the full surface.
|
|
560
|
+
*/
|
|
561
|
+
|
|
562
|
+
declare class Wind {
|
|
563
|
+
#private;
|
|
564
|
+
constructor(config?: Partial<WindConfig>);
|
|
565
|
+
/** Set or override configuration. Call once at boot. Resets memoised internals. */
|
|
566
|
+
configure(config: Partial<WindConfig>): this;
|
|
567
|
+
/** The low-level HTTP client — one method per openapi path. Escape hatch. */
|
|
568
|
+
get raw(): RawClient;
|
|
569
|
+
/** Express middleware owning `startPath` + `callbackPath`. */
|
|
570
|
+
routes(opts: RoutesOptions): ExpressMiddleware;
|
|
571
|
+
/** Web-standard `(Request) => Response` handler for the same two routes. */
|
|
572
|
+
handler(opts: RoutesOptions): FetchHandler;
|
|
573
|
+
/**
|
|
574
|
+
* Scope subsequent calls to one user of your app. Loads their grant via
|
|
575
|
+
* `grantStore.get`. Singular, one user's connection — distinct from the
|
|
576
|
+
* plural `wind.connections` below (client-credentials management across
|
|
577
|
+
* every connection).
|
|
578
|
+
*/
|
|
579
|
+
connection(userId: string): ConnectionScope;
|
|
580
|
+
readonly connections: {
|
|
581
|
+
list: () => Promise<Connection[]>;
|
|
582
|
+
get: (id: string) => Promise<Connection>;
|
|
583
|
+
revoke: (id: string) => Promise<RevokeResult>;
|
|
584
|
+
requestIncrease: (id: string, p: {
|
|
585
|
+
targetAllocationMc: number;
|
|
586
|
+
reason?: string;
|
|
587
|
+
}) => Promise<AllocationRequest>;
|
|
588
|
+
};
|
|
589
|
+
account(): Promise<Account>;
|
|
590
|
+
readonly features: {
|
|
591
|
+
list: (appId: string) => Promise<Feature[]>;
|
|
592
|
+
register: (appId: string, feature: Feature) => Promise<Feature>;
|
|
593
|
+
update: (appId: string, slug: string, patch: Partial<Feature>) => Promise<Feature>;
|
|
594
|
+
};
|
|
595
|
+
/** Verify a webhook's `X-Wind-Signature` and return the typed event. Needs the raw body. */
|
|
596
|
+
verify(input: {
|
|
597
|
+
payload: string | Buffer;
|
|
598
|
+
signature: string;
|
|
599
|
+
}): WebhookEvent;
|
|
600
|
+
}
|
|
601
|
+
/** An explicit, independent instance. Use for tests or multi-tenant servers. */
|
|
602
|
+
declare function createWind(config?: Partial<WindConfig>): Wind;
|
|
603
|
+
/**
|
|
604
|
+
* The default instance. Lazily reads `WIND_CLIENT_ID` / `WIND_CLIENT_SECRET` /
|
|
605
|
+
* `WIND_REDIRECT_URI` on first use; `wind.configure({ … })` overrides.
|
|
606
|
+
*/
|
|
607
|
+
declare const wind: Wind;
|
|
608
|
+
|
|
609
|
+
export { type Account, AllocationExhaustedError, type AllocationRequest, type AllocationRequestParams, AppSuspendedError, type ChatMessage, type ChatParams, type ChatResult, type ChatStreamChunk, type ChatUsage, type Chattable, type ConnectTransaction, type Connection, ConnectionFrozenError, ConnectionRevokedError, ConnectionScope, type ConnectionStatus, type Feature, FeatureCostExceededError, FeatureModelNotConfiguredError, FeatureScope, type Grant, type GrantStore, IdempotencyKeyReusedError, ModelNotAllowedError, ProviderError, RateLimitedError, type RevokeResult, RiskThrottledError, type RoutesOptions, TokenExpiredError, type TokenResponse, type TransactionStore, type WebhookEvent, Wind, WindApiError, type WindBlock, type WindConfig, WindConfigError, WindError, type WindErrorEnvelope, type WindErrorInit, WindNotConnectedError, WindNotImplementedError, WindSignatureError, createWind, memoryGrantStore, wind };
|