@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.
@@ -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 };