corsair 0.1.76 → 0.1.78

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.
@@ -1,152 +1,7 @@
1
- import { CorsairDatabase, CorsairPermission, CorsairDatabaseInput } from './db.js';
2
1
  import { ZodTypeAny } from 'zod';
2
+ import { CorsairDatabase, CorsairPermission, CorsairDatabaseInput } from './db.js';
3
3
  import { CorsairPluginSchema, PluginEntityClients } from './orm.js';
4
-
5
- type AllErrors = 'RATE_LIMIT_ERROR' | 'AUTH_ERROR' | 'PERMISSION_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT_ERROR' | 'SERVER_ERROR' | 'VALIDATION_ERROR' | 'NOT_FOUND_ERROR' | 'BAD_REQUEST_ERROR' | 'PARSING_ERROR' | 'DEFAULT' | (string & {});
6
- declare const BaseProviders: readonly ["airtable", "amplitude", "asana", "bitwarden", "bluesky", "box", "cal", "calendly", "cloudflare", "cursor", "discord", "dodopayments", "dropbox", "exa", "figma", "firecrawl", "fireflies", "github", "gitlab", "gmail", "googlecalendar", "googledrive", "googlesheets", "grafana", "hackernews", "hubspot", "intercom", "jira", "linear", "monday", "notion", "onedrive", "openweathermap", "oura", "outlook", "pagerduty", "posthog", "razorpay", "reddit", "resend", "sentry", "sharepoint", "slack", "spotify", "strava", "stripe", "tally", "tavily", "teams", "telegram", "todoist", "trello", "twitter", "twitterapiio", "typeform", "vapi", "xquik", "youtube", "zendesk", "zohomail", "zoom"];
7
- type AllProviders = 'airtable' | 'amplitude' | 'asana' | 'bitwarden' | 'bluesky' | 'box' | 'cal' | 'calendly' | 'cloudflare' | 'cursor' | 'discord' | 'dodopayments' | 'dropbox' | 'exa' | 'figma' | 'firecrawl' | 'fireflies' | 'github' | 'gitlab' | 'gmail' | 'googlecalendar' | 'googledrive' | 'googlesheets' | 'grafana' | 'hackernews' | 'hubspot' | 'intercom' | 'jira' | 'linear' | 'monday' | 'notion' | 'onedrive' | 'openweathermap' | 'oura' | 'outlook' | 'pagerduty' | 'posthog' | 'razorpay' | 'reddit' | 'resend' | 'sentry' | 'sharepoint' | 'slack' | 'spotify' | 'strava' | 'stripe' | 'tally' | 'tavily' | 'teams' | 'telegram' | 'todoist' | 'trello' | 'twitter' | 'twitterapiio' | 'typeform' | 'vapi' | 'xquik' | 'youtube' | 'zendesk' | 'zohomail' | 'zoom' | (string & {});
8
- type AuthTypes = 'oauth_2' | 'api_key' | 'bot_token';
9
- type PickAuth<T extends AuthTypes> = T;
10
-
11
- /**
12
- * Utility type that converts a union type to an intersection type.
13
- * This is useful for combining multiple plugin interfaces into a single client interface.
14
- * @template U - The union type to convert
15
- */
16
- type UnionToIntersection$1<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
17
- /**
18
- * Bivariance hack for function types to ensure proper type inference.
19
- * This helps TypeScript correctly infer function parameters and return types.
20
- * @template Args - The function arguments array
21
- * @template R - The function return type
22
- */
23
- type Bivariant$2<Args extends unknown[], R> = {
24
- bivarianceHack(...args: Args): R;
25
- }['bivarianceHack'];
26
-
27
- /**
28
- * Defines the default fields for each auth type at the integration and account levels.
29
- * These are the base fields that every key manager of that auth type will have.
30
- * Plugins can extend these with additional fields via `authConfig`.
31
- */
32
- declare const BASE_AUTH_FIELDS: {
33
- readonly oauth_2: {
34
- readonly integration: readonly ["client_id", "client_secret", "redirect_url"];
35
- readonly account: readonly ["access_token", "refresh_token", "expires_at", "scope", "webhook_signature"];
36
- };
37
- readonly api_key: {
38
- readonly integration: readonly [];
39
- readonly account: readonly ["api_key", "webhook_signature"];
40
- };
41
- readonly bot_token: {
42
- readonly integration: readonly [];
43
- readonly account: readonly ["bot_token", "webhook_signature"];
44
- };
45
- };
46
- /**
47
- * Type-level representation of the base auth field config.
48
- */
49
- type BaseAuthFieldConfig = typeof BASE_AUTH_FIELDS;
50
- /**
51
- * Configuration that plugins can provide to extend the base auth fields.
52
- * Each auth type can have additional integration-level and/or account-level fields.
53
- *
54
- * @example
55
- * ```ts
56
- * const gmailAuthConfig = {
57
- * oauth_2: {
58
- * integration: ["topic_id"] as const,
59
- * account: ["history_id"] as const,
60
- * },
61
- * } as const satisfies PluginAuthConfig;
62
- * ```
63
- */
64
- type PluginAuthConfig = {
65
- [K in AuthTypes]?: {
66
- integration?: readonly string[];
67
- account?: readonly string[];
68
- };
69
- };
70
- /**
71
- * Extracts extra integration fields from a plugin auth config for a given auth type.
72
- */
73
- type ExtraIntegrationFields<Config extends PluginAuthConfig | undefined, T extends AuthTypes> = Config extends PluginAuthConfig ? T extends keyof Config ? NonNullable<Config[T]> extends {
74
- integration: infer F extends readonly string[];
75
- } ? F[number] : never : never : never;
76
- /**
77
- * Extracts extra account fields from a plugin auth config for a given auth type.
78
- */
79
- type ExtraAccountFields<Config extends PluginAuthConfig | undefined, T extends AuthTypes> = Config extends PluginAuthConfig ? T extends keyof Config ? NonNullable<Config[T]> extends {
80
- account: infer F extends readonly string[];
81
- } ? F[number] : never : never : never;
82
- /**
83
- * All integration field names for a given auth type (base + extension).
84
- */
85
- type IntegrationFieldNames<T extends AuthTypes, Config extends PluginAuthConfig | undefined = undefined> = BaseAuthFieldConfig[T]['integration'][number] | ExtraIntegrationFields<Config, T>;
86
- /**
87
- * All account field names for a given auth type (base + extension).
88
- */
89
- type AccountFieldNames<T extends AuthTypes, Config extends PluginAuthConfig | undefined = undefined> = BaseAuthFieldConfig[T]['account'][number] | ExtraAccountFields<Config, T>;
90
- /**
91
- * Generates getter and setter types for a single field.
92
- * e.g., "client_id" → { get_client_id: () => Promise<string | null>, set_client_id: (value: string | null) => Promise<void> }
93
- */
94
- type FieldAccessors<Field extends string> = {
95
- [K in `get_${Field}`]: () => Promise<string | null>;
96
- } & {
97
- [K in `set_${Field}`]: (value: string | null) => Promise<void>;
98
- };
99
- /**
100
- * Generates getters and setters for all fields in a union.
101
- * Uses UnionToIntersection to merge individual field accessor types.
102
- */
103
- type AllFieldAccessors<Fields extends string> = [Fields] extends [never] ? {} : UnionToIntersection$1<FieldAccessors<Fields>>;
104
- /**
105
- * Base key manager interface with DEK operations.
106
- * All key managers (integration and account) include these.
107
- */
108
- type BaseKeyManager = {
109
- /**
110
- * Get the current DEK (decrypted using KEK)
111
- */
112
- get_dek: () => Promise<string>;
113
- /**
114
- * Issue a new DEK and re-encrypt all associated secrets
115
- * @returns The new DEK (for reference, not typically needed)
116
- */
117
- issue_new_dek: () => Promise<string>;
118
- };
119
- /**
120
- * Integration credentials returned by get_integration_credentials (OAuth2 only).
121
- */
122
- type OAuth2IntegrationCredentials = {
123
- client_id: string | null;
124
- client_secret: string | null;
125
- redirect_url: string | null;
126
- };
127
- /**
128
- * Integration-level key manager for a given auth type.
129
- * Includes base DEK operations + auto-generated getters/setters for all fields.
130
- *
131
- * @template T - The auth type
132
- * @template Config - Optional plugin auth config for extension fields
133
- */
134
- type IntegrationKeyManagerFor<T extends AuthTypes, Config extends PluginAuthConfig | undefined = undefined> = BaseKeyManager & AllFieldAccessors<IntegrationFieldNames<T, Config>>;
135
- /**
136
- * Account-level key manager for a given auth type.
137
- * Includes base DEK operations + auto-generated getters/setters for all fields.
138
- * OAuth2 account managers also include `get_integration_credentials`.
139
- *
140
- * @template T - The auth type
141
- * @template Config - Optional plugin auth config for extension fields
142
- */
143
- type AccountKeyManagerFor<T extends AuthTypes, Config extends PluginAuthConfig | undefined = undefined> = BaseKeyManager & AllFieldAccessors<AccountFieldNames<T, Config>> & (T extends 'oauth_2' ? {
144
- /**
145
- * Get the integration-level OAuth2 credentials (client_id, client_secret, redirect_url).
146
- * Useful for token refresh flows that need access to both account and integration secrets.
147
- */
148
- get_integration_credentials: () => Promise<OAuth2IntegrationCredentials>;
149
- } : {});
4
+ import { q as AllErrors, A as AuthTypes, a as AccountKeyManagerFor, P as PluginAuthConfig, I as IntegrationKeyManagerFor, f as AllProviders, H as HubConfig, n as HubConfigInput } from './types-B1We8TTP.js';
150
5
 
151
6
  /**
152
7
  * Bivariance hack for function types to ensure proper type inference.
@@ -307,6 +162,29 @@ type ManagementOk = {
307
162
  ok: true;
308
163
  };
309
164
  type PermissionRecord = CorsairPermission;
165
+ type CreateConnectLinkInput = {
166
+ plugin: string;
167
+ tenantId?: string;
168
+ };
169
+ type ConnectLink = {
170
+ connectUrl: string;
171
+ state: string;
172
+ };
173
+ type ResolvedConnectLink = {
174
+ plugin: string;
175
+ tenantId: string;
176
+ providerName: string;
177
+ oauthUrl: string;
178
+ state: string;
179
+ };
180
+ type OAuthCallbackInput = {
181
+ code: string;
182
+ state: string;
183
+ };
184
+ type OAuthCallbackResult = {
185
+ plugin: string;
186
+ tenantId: string;
187
+ };
310
188
 
311
189
  type CorsairManageNamespace = {
312
190
  ok: () => ManagementOk;
@@ -328,168 +206,215 @@ type CorsairManageNamespace = {
328
206
  get: (id: string) => Promise<PermissionRecord>;
329
207
  getByToken: (token: string) => Promise<PermissionRecord>;
330
208
  };
209
+ connect: {
210
+ createLink: (input: CreateConnectLinkInput) => Promise<ConnectLink>;
211
+ resolve: (state: string) => Promise<ResolvedConnectLink>;
212
+ oauthCallback: (input: OAuthCallbackInput) => Promise<OAuthCallbackResult>;
213
+ };
331
214
  };
332
215
 
333
216
  /**
334
- * Raw incoming webhook data for matching (before full parsing).
335
- * Used by matcher functions to determine if a webhook should be handled.
217
+ * The `corsair.permissions` namespace available at the root of every corsair instance.
218
+ * Provides methods for querying and transitioning permission records.
219
+ *
220
+ * Status transitions exposed here are intentionally limited to safe, non-escalating states.
221
+ * Setting a record to 'approved' (which grants execution) is deliberately excluded —
222
+ * that must happen through the out-of-band review flow.
336
223
  */
337
- type RawWebhookRequest = {
338
- /** HTTP headers from the webhook request */
339
- headers: Record<string, string | string[] | undefined>;
340
- /** Raw request body (string or already parsed object) */
341
- body: unknown;
224
+ type CorsairPermissionsNamespace = {
225
+ /**
226
+ * Fetches a single permission record by its ID.
227
+ * Returns undefined if no record exists or if no database is configured.
228
+ */
229
+ find_by_permission_id(id: string): Promise<CorsairPermission | undefined>;
230
+ /**
231
+ * Fetches a single permission record by its token.
232
+ * The token is the public-facing handle embedded in review URLs.
233
+ * Returns undefined if no record exists or if no database is configured.
234
+ */
235
+ find_by_token(token: string): Promise<CorsairPermission | undefined>;
236
+ /**
237
+ * Marks the permission as 'executing'. Call this when executePermission picks up
238
+ * an approved record and is about to run the endpoint.
239
+ */
240
+ set_executing(id: string): Promise<void>;
241
+ /**
242
+ * Marks the permission as 'completed'. Call this after the endpoint has finished
243
+ * executing successfully.
244
+ */
245
+ set_completed(id: string): Promise<void>;
342
246
  };
343
- /**
344
- * Raw incoming webhook request data after initial processing.
345
- * Contains the parsed payload, headers, and optional raw body string.
346
- * @template TPayload - The type of the parsed webhook payload
347
- */
348
- type WebhookRequest<TPayload = unknown> = {
349
- /** Parsed payload from the webhook request body */
350
- payload: TPayload;
351
- /** HTTP headers from the webhook request */
352
- headers: Record<string, string | string[] | undefined>;
353
- /** Raw request body string (for signature verification) */
354
- rawBody?: string;
247
+ type EnforcePermissionOptions = {
248
+ pluginId: string;
249
+ endpointPath: string;
250
+ /** unknown: caller-supplied args vary per endpoint not statically knowable here */
251
+ args: unknown;
252
+ mode: PermissionMode;
253
+ override?: PermissionPolicy;
254
+ riskLevel: EndpointRiskLevel;
255
+ meta?: EndpointMetaEntry;
256
+ /** Required to create an approval record. Without a DB, 'require_approval' falls back to deny. */
257
+ db?: CorsairDatabase;
258
+ timeoutMs?: number;
259
+ /** Tenant ID for multi-tenant instances. Stored on the record so executePermission can scope correctly. Defaults to 'default'. */
260
+ tenantId?: string;
261
+ /**
262
+ * Controls whether the call blocks until the user approves or denies.
263
+ * - `'synchronous'` → polls the DB every 500 ms; returns 'allow' on approval, 'blocked' on denial/timeout.
264
+ * - `'asynchronous'` → returns 'blocked' immediately after creating the pending record.
265
+ * - A no-arg function → called per-request, return value selects the mode dynamically.
266
+ * Defaults to `'asynchronous'`.
267
+ */
268
+ approvalMode?: 'synchronous' | 'asynchronous' | (() => 'synchronous' | 'asynchronous');
269
+ };
270
+ type EnforcePermissionResult = {
271
+ result: 'allow' | 'blocked';
272
+ /** Why the call was blocked. Only present when result === 'blocked'. */
273
+ reason?: 'denied' | 'policy' | 'timeout' | 'pending';
274
+ /** Permission record ID. Present when a pending approval record exists. */
275
+ id?: string;
276
+ /** Permission token (the value embedded in review URLs). Present when a pending approval record exists. */
277
+ token?: string;
278
+ /** ISO8601 expiry for pending approval records. Present when token is present. */
279
+ expiresAt?: string;
280
+ /**
281
+ * Called by the endpoint binding layer after the endpoint executes successfully.
282
+ * Marks the permission record as 'completed' (single-use approval consumed).
283
+ * Only present when an 'approved' record was found and the call is allowed through.
284
+ */
285
+ onComplete?: () => Promise<void>;
355
286
  };
287
+
356
288
  /**
357
- * Response from a webhook handler that can include acknowledgment data.
358
- * @template TData - The type of data to return in the response
289
+ * Extracts typed entity clients from a plugin schema.
290
+ * Each entity type becomes a `PluginEntityClient<DataSchema>`.
291
+ * Entities are nested under `db` to separate them from API endpoints.
359
292
  */
360
- type WebhookResponse<TData = unknown> = {
361
- /** Whether the webhook was processed successfully */
362
- success: boolean;
363
- /** The entity relevant to the webhook (note that this is corsair_entities.id) */
364
- corsairEntityId?: string;
365
- /** Return this object to the sender. Defaults to empty. Usually only necessary for webhook confirmation / challenge. */
366
- returnToSender?: Record<string, string>;
367
- /** Optional data to return in the HTTP response */
368
- data?: TData;
369
- /** Optional error message if processing failed */
370
- error?: string;
371
- /** HTTP status code to return (defaults to 200 on success, 500 on error) */
372
- statusCode?: number;
373
- /** HTTP response headers to set on the outgoing response. Used for header-based handshakes (e.g. Asana X-Hook-Secret). */
374
- responseHeaders?: Record<string, string>;
375
- };
293
+ type InferPluginEntities<Schema extends CorsairPluginSchema<Record<string, ZodTypeAny>> | undefined> = Schema extends CorsairPluginSchema<infer Entities> ? {
294
+ db: PluginEntityClients<Entities>;
295
+ } : {};
376
296
  /**
377
- * A webhook matcher function that synchronously determines if a raw webhook
378
- * request should be handled by this webhook.
379
- * @param request - The raw webhook request data
380
- * @returns True if this webhook should handle the request
297
+ * Utility type that converts a union to an intersection type.
381
298
  */
382
- type CorsairWebhookMatcher = (request: RawWebhookRequest) => boolean;
299
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
383
300
  /**
384
- * Bivariance hack for webhook function types to ensure proper type inference.
385
- * @template Args - The function arguments
386
- * @template R - The function return type
301
+ * Extracts the authType from plugin options.
302
+ * @template Options - The plugin options type
303
+ * @template DefaultAuthType - Optional default auth type to use when authType is optional or not present
304
+ *
305
+ * Priority:
306
+ * 1. If authType is a specific single AuthType (not a union), use that
307
+ * 2. If DefaultAuthType parameter is provided, use that as the fallback
308
+ * 3. Otherwise use the non-nullable union from authType
309
+ * 4. If authType is not in Options at all, fall back to DefaultAuthType
387
310
  */
388
- type Bivariant<Args extends unknown[], R> = {
389
- bivarianceHack(...args: Args): R;
390
- }['bivarianceHack'];
311
+ type ExtractAuthType<Options, DefaultAuthType extends AuthTypes | undefined = undefined> = 'authType' extends keyof Options ? Options['authType'] extends AuthTypes ? Options['authType'] : DefaultAuthType extends AuthTypes ? DefaultAuthType : NonNullable<Options['authType']> extends AuthTypes ? NonNullable<Options['authType']> : never : DefaultAuthType extends AuthTypes ? DefaultAuthType : never;
391
312
  /**
392
- * A webhook handler function definition that processes incoming webhooks.
393
- * Takes context + webhook request, returns a webhook response.
394
- * @template Ctx - The context type passed to the handler
395
- * @template TPayload - The type of the webhook payload
396
- * @template TResponseData - The type of data returned in the response
313
+ * Extracts Options type from plugin's options property.
397
314
  */
398
- type CorsairWebhookHandler<Ctx extends CorsairContext = CorsairContext, TPayload = unknown, TResponseData = unknown> = Bivariant<[
399
- ctx: Ctx,
400
- request: WebhookRequest<TPayload>
401
- ], Promise<WebhookResponse<TResponseData>>>;
315
+ type InferPluginOptions<P> = P extends {
316
+ options?: infer O;
317
+ } ? O : never;
402
318
  /**
403
- * A complete webhook definition with both matcher and handler.
404
- * The matcher synchronously determines if this webhook handles the incoming request.
405
- * The handler processes the webhook after matching.
406
- * @template Ctx - The context type passed to the handler
407
- * @template TPayload - The type of the webhook payload
408
- * @template TResponseData - The type of data returned in the response
319
+ * Extracts DefaultAuthType from plugin's __defaultAuthType property.
320
+ * Uses NonNullable<D> because the __defaultAuthType property is optional,
321
+ * which causes TypeScript to infer D as `AuthType | undefined`.
409
322
  */
410
- type CorsairWebhook<Ctx extends CorsairContext = CorsairContext, TPayload = unknown, TResponseData = unknown> = {
411
- /** Synchronously determines if this webhook handles the incoming request */
412
- match: CorsairWebhookMatcher;
413
- /** Handles the webhook request after matching */
414
- handler: CorsairWebhookHandler<Ctx, TPayload, TResponseData>;
415
- };
323
+ type InferDefaultAuthType<P> = P extends {
324
+ __defaultAuthType?: infer D;
325
+ } ? NonNullable<D> extends AuthTypes ? NonNullable<D> : undefined : undefined;
416
326
  /**
417
- * A tree of webhooks that can be nested arbitrarily deep.
418
- * Similar to EndpointTree but for webhook handlers.
419
- *
420
- * @example
421
- * ```ts
422
- * // Flat structure
423
- * webhooks: {
424
- * issueCreated: { match: (req) => ..., handler: async (ctx, req) => ... },
425
- * issueClosed: { match: (req) => ..., handler: async (ctx, req) => ... },
426
- * }
427
- *
428
- * // Nested structure
429
- * webhooks: {
430
- * issues: {
431
- * created: { match: (req) => ..., handler: async (ctx, req) => ... },
432
- * updated: { match: (req) => ..., handler: async (ctx, req) => ... },
433
- * },
434
- * pull_requests: {
435
- * opened: { match: (req) => ..., handler: async (ctx, req) => ... },
436
- * },
437
- * }
438
- * ```
327
+ * Extracts the AuthConfig from a plugin's authConfig property.
439
328
  */
440
- type WebhookTree = {
441
- [key: string]: CorsairWebhook | WebhookTree;
442
- };
329
+ type InferAuthConfig<P> = P extends {
330
+ authConfig?: infer C;
331
+ } ? C extends PluginAuthConfig ? C : undefined : undefined;
443
332
  /**
444
- * A bound webhook - the user-facing API after context is applied.
445
- * Contains both the matcher (unchanged) and the bound handler.
446
- * @template TPayload - The type of the webhook payload
447
- * @template TResponseData - The type of data returned in the response
333
+ * Infers the complete namespace for a single plugin, including API endpoints,
334
+ * database entities, webhooks, and account-level keys.
448
335
  */
449
- type BoundWebhook<TPayload = unknown, TResponseData = unknown> = {
450
- /** Synchronously determines if this webhook handles the incoming request */
451
- match: CorsairWebhookMatcher;
452
- /** Handles the webhook request (context already applied) */
453
- handler: (request: WebhookRequest<TPayload>) => Promise<WebhookResponse<TResponseData>>;
454
- };
336
+ type InferPluginNamespace<P extends CorsairPlugin> = P extends CorsairPlugin<infer Id, infer Schema, infer Endpoints, infer Webhooks> ? {
337
+ [K in Id]: (Endpoints extends EndpointTree ? {
338
+ api: BindEndpoints<Endpoints>;
339
+ } : {}) & InferPluginEntities<Schema> & (Webhooks extends WebhookTree ? {
340
+ webhooks: BindWebhooks<Webhooks>;
341
+ /**
342
+ * Synchronously checks if an incoming webhook request is intended for this plugin.
343
+ * Only present if the plugin defines a `pluginWebhookMatcher`.
344
+ * Use this as a first-level filter before checking individual webhook matchers.
345
+ */
346
+ pluginWebhookMatcher?: (request: RawWebhookRequest) => boolean;
347
+ /**
348
+ * Extracts the external tenant lookup key for this plugin's webhook.
349
+ * Only present if the plugin defines `pluginTenantWebhookMatcher`.
350
+ */
351
+ pluginTenantWebhookMatcher?: CorsairWebhookTenantMatcher;
352
+ } : {}) & (ExtractAuthType<InferPluginOptions<P>, InferDefaultAuthType<P>> extends AuthTypes ? {
353
+ keys: AccountKeyManagerFor<ExtractAuthType<InferPluginOptions<P>, InferDefaultAuthType<P>>, InferAuthConfig<P>>;
354
+ } : {});
355
+ } : never;
455
356
  /**
456
- * A tree of bound webhooks (context already applied).
457
- * This is what the end user interacts with after client initialization.
357
+ * Infers the integration-level key manager for a single plugin.
458
358
  */
459
- type BoundWebhookTree = {
460
- [key: string]: BoundWebhook<any, any> | BoundWebhookTree;
461
- };
359
+ type InferIntegrationKeys<P extends CorsairPlugin> = P extends CorsairPlugin<infer Id> ? ExtractAuthType<InferPluginOptions<P>, InferDefaultAuthType<P>> extends AuthTypes ? {
360
+ [K in Id]: IntegrationKeyManagerFor<ExtractAuthType<InferPluginOptions<P>, InferDefaultAuthType<P>>, InferAuthConfig<P>>;
361
+ } : never : never;
462
362
  /**
463
- * Recursively transforms webhook definitions to their bound (context-free) signatures.
464
- * Handles both flat and nested webhook structures.
465
- * @template T - The webhook tree to bind
363
+ * Combines all integration-level keys into a single interface.
466
364
  */
467
- type BindWebhooks<T extends WebhookTree> = {
468
- [K in keyof T]: T[K] extends CorsairWebhook<any, infer P, infer R> ? BoundWebhook<P, R> : T[K] extends WebhookTree ? BindWebhooks<T[K]> : never;
365
+ type InferAllIntegrationKeys<Plugins extends readonly CorsairPlugin[]> = UnionToIntersection<InferIntegrationKeys<Plugins[number]>>;
366
+ /**
367
+ * Combines all plugin namespaces into a single client interface.
368
+ */
369
+ type InferPluginNamespaces<Plugins extends readonly CorsairPlugin[]> = UnionToIntersection<InferPluginNamespace<Plugins[number]>>;
370
+ /**
371
+ * The main Corsair client type that provides access to all plugin APIs, entities, webhooks, and keys.
372
+ */
373
+ type CorsairClient<Plugins extends readonly CorsairPlugin[]> = InferPluginNamespaces<Plugins>;
374
+ /**
375
+ * Multi-tenant wrapper that provides a `withTenant` method to scope operations to a specific tenant.
376
+ * Also includes integration-level `keys` for managing shared secrets (OAuth2 client credentials, etc.)
377
+ */
378
+ type CorsairTenantWrapper<Plugins extends readonly CorsairPlugin[]> = {
379
+ withTenant: (tenantId: string) => CorsairClient<Plugins>;
380
+ /**
381
+ * Integration-level key managers for each plugin.
382
+ * Used to manage secrets shared across all tenants (e.g., OAuth2 client_id, client_secret).
383
+ */
384
+ keys: InferAllIntegrationKeys<Plugins>;
385
+ /**
386
+ * Permission management namespace. Use this to query and transition permission records.
387
+ * Available at the root regardless of multi-tenancy setting.
388
+ */
389
+ permissions: CorsairPermissionsNamespace;
390
+ /**
391
+ * Management control plane namespace — in-process equivalent of the HTTP
392
+ * `managementHandler`. Lists tenants, plugins, connection status, and
393
+ * permission records without going through HTTP.
394
+ */
395
+ manage: CorsairManageNamespace;
469
396
  };
470
397
  /**
471
- * Derives all dot-notation webhook paths from a WebhookTree as a string literal union.
472
- * Used to provide compile-time validation for webhook schema registry keys.
473
- * Passing an invalid path to any config that accepts WebhookPathsOf<T> is a type error.
474
- *
475
- * Design note: Same recursive constraint relaxation as EndpointPathsOf — see that type
476
- * for a full explanation of why we use `extends object` rather than `extends WebhookTree`
477
- * on the recursive call. We also check `{ match: any; handler: any }` before `object`
478
- * because webhook leaves are objects themselves.
479
- *
480
- * @example
481
- * Given: `{ messages: { message: { match: fn, handler: fn } }, channels: { created: { match: fn, handler: fn } } }`
482
- * Result: `'messages.message' | 'channels.created'`
483
- *
484
- * @template T - The webhook tree to extract paths from (unconstrained to allow recursion through as-const types)
485
- * @template Prefix - Internal accumulator for the current path prefix (do not supply manually)
398
+ * Single-tenant client that includes both plugin APIs and integration-level keys.
486
399
  */
487
- type WebhookPathsOf<T, Prefix extends string = ''> = {
488
- [K in keyof T & string]: T[K] extends {
489
- match: any;
490
- handler: any;
491
- } ? Prefix extends '' ? K : `${Prefix}.${K}` : T[K] extends object ? WebhookPathsOf<T[K], Prefix extends '' ? K : `${Prefix}.${K}`> : never;
492
- }[keyof T & string];
400
+ type CorsairSingleTenantClient<Plugins extends readonly CorsairPlugin[]> = CorsairClient<Plugins> & {
401
+ /**
402
+ * Integration-level key managers for each plugin.
403
+ * Used to manage secrets shared across all tenants (e.g., OAuth2 client_id, client_secret).
404
+ */
405
+ keys: InferAllIntegrationKeys<Plugins>;
406
+ /**
407
+ * Permission management namespace. Use this to query and transition permission records.
408
+ * Available at the root regardless of multi-tenancy setting.
409
+ */
410
+ permissions: CorsairPermissionsNamespace;
411
+ /**
412
+ * Management control plane namespace — in-process equivalent of the HTTP
413
+ * `managementHandler`. Lists tenants, plugins, connection status, and
414
+ * permission records without going through HTTP.
415
+ */
416
+ manage: CorsairManageNamespace;
417
+ };
493
418
 
494
419
  /**
495
420
  * Risk level classification for plugin endpoints.
@@ -725,6 +650,8 @@ type ExtractAllAuthTypes<Options> = 'authType' extends keyof Options ? NonNullab
725
650
  * return await ctx.keys.get_access_token();
726
651
  * } else if (ctx.authType === 'bot_token') {
727
652
  * return await ctx.keys.get_bot_token();
653
+ * } else if (ctx.authType === 'managed') {
654
+ * return await ctx.keys.get_access_token();
728
655
  * }
729
656
  * return '';
730
657
  * }
@@ -737,6 +664,10 @@ type KeyBuilderContext<Options extends Record<string, unknown>, AuthConfig exten
737
664
  options: Options;
738
665
  /** Account-level key manager - type narrows based on authType check */
739
666
  keys: AccountKeyManagerFor<A, AuthConfig>;
667
+ /** Tenant ID for this request (always set; defaults to 'default' in single-tenant) */
668
+ tenantId: string;
669
+ /** Hub config when createCorsair({ hub: ... }) is configured */
670
+ hub?: HubConfig;
740
671
  } : never : never;
741
672
  /**
742
673
  * Type for the keyBuilder callback function that retrieves the authentication key.
@@ -780,6 +711,17 @@ type CorsairPlugin<Id extends AllProviders = AllProviders, Schema extends Corsai
780
711
  * should handle an incoming request. Acts as a first-level filter.
781
712
  */
782
713
  pluginWebhookMatcher?: CorsairWebhookMatcher;
714
+ /**
715
+ * Extracts the external identifier used to resolve a tenant for this webhook
716
+ * (for example Slack `team_id`, GitHub `installation.id`). Return null when
717
+ * the request cannot be mapped to a tenant.
718
+ */
719
+ pluginTenantWebhookMatcher?: CorsairWebhookTenantMatcher;
720
+ /**
721
+ * Resolves the external id to store on the account after OAuth so incoming
722
+ * webhooks can be routed to the correct tenant. Return null when unavailable.
723
+ */
724
+ oauthWebhookTenantLinkResolver?: CorsairOAuthWebhookTenantLinkResolver;
783
725
  /** Plugin-specific error handlers */
784
726
  errorHandlers?: CorsairErrorHandler;
785
727
  /**
@@ -1005,251 +947,206 @@ type CorsairIntegration<Plugins extends readonly CorsairPlugin[]> = {
1005
947
  state: string;
1006
948
  }) => string;
1007
949
  };
950
+ /** Corsair Hub configuration for hosted OAuth connect flows. */
951
+ hub?: HubConfigInput;
1008
952
  };
1009
953
 
954
+ type TokenResponse = {
955
+ access_token?: string;
956
+ refresh_token?: string;
957
+ expires_in?: number;
958
+ token_type?: string;
959
+ [key: string]: unknown;
960
+ };
1010
961
  /**
1011
- * The `corsair.permissions` namespace available at the root of every corsair instance.
1012
- * Provides methods for querying and transitioning permission records.
1013
- *
1014
- * Status transitions exposed here are intentionally limited to safe, non-escalating states.
1015
- * Setting a record to 'approved' (which grants execution) is deliberately excluded —
1016
- * that must happen through the out-of-band review flow.
962
+ * Exchanges an OAuth authorization code for access/refresh tokens.
963
+ * Supports both 'body' (default) and 'basic' token auth methods.
1017
964
  */
1018
- type CorsairPermissionsNamespace = {
1019
- /**
1020
- * Fetches a single permission record by its ID.
1021
- * Returns undefined if no record exists or if no database is configured.
1022
- */
1023
- find_by_permission_id(id: string): Promise<CorsairPermission | undefined>;
1024
- /**
1025
- * Fetches a single permission record by its token.
1026
- * The token is the public-facing handle embedded in review URLs.
1027
- * Returns undefined if no record exists or if no database is configured.
1028
- */
1029
- find_by_token(token: string): Promise<CorsairPermission | undefined>;
1030
- /**
1031
- * Marks the permission as 'executing'. Call this when executePermission picks up
1032
- * an approved record and is about to run the endpoint.
1033
- */
1034
- set_executing(id: string): Promise<void>;
1035
- /**
1036
- * Marks the permission as 'completed'. Call this after the endpoint has finished
1037
- * executing successfully.
1038
- */
1039
- set_completed(id: string): Promise<void>;
1040
- };
1041
- type EnforcePermissionOptions = {
1042
- pluginId: string;
1043
- endpointPath: string;
1044
- /** unknown: caller-supplied args vary per endpoint — not statically knowable here */
1045
- args: unknown;
1046
- mode: PermissionMode;
1047
- override?: PermissionPolicy;
1048
- riskLevel: EndpointRiskLevel;
1049
- meta?: EndpointMetaEntry;
1050
- /** Required to create an approval record. Without a DB, 'require_approval' falls back to deny. */
1051
- db?: CorsairDatabase;
1052
- timeoutMs?: number;
1053
- /** Tenant ID for multi-tenant instances. Stored on the record so executePermission can scope correctly. Defaults to 'default'. */
1054
- tenantId?: string;
1055
- /**
1056
- * Controls whether the call blocks until the user approves or denies.
1057
- * - `'synchronous'` → polls the DB every 500 ms; returns 'allow' on approval, 'blocked' on denial/timeout.
1058
- * - `'asynchronous'` → returns 'blocked' immediately after creating the pending record.
1059
- * - A no-arg function → called per-request, return value selects the mode dynamically.
1060
- * Defaults to `'asynchronous'`.
1061
- */
1062
- approvalMode?: 'synchronous' | 'asynchronous' | (() => 'synchronous' | 'asynchronous');
1063
- };
1064
- type EnforcePermissionResult = {
1065
- result: 'allow' | 'blocked';
1066
- /** Why the call was blocked. Only present when result === 'blocked'. */
1067
- reason?: 'denied' | 'policy' | 'timeout' | 'pending';
1068
- /** Permission record ID. Present when a pending approval record exists. */
1069
- id?: string;
1070
- /** Permission token (the value embedded in review URLs). Present when a pending approval record exists. */
1071
- token?: string;
1072
- /**
1073
- * Called by the endpoint binding layer after the endpoint executes successfully.
1074
- * Marks the permission record as 'completed' (single-use approval consumed).
1075
- * Only present when an 'approved' record was found and the call is allowed through.
1076
- */
1077
- onComplete?: () => Promise<void>;
1078
- };
965
+ declare function exchangeCodeForTokens(code: string, clientId: string, clientSecret: string, oauthConfig: OAuthConfig, redirectUri: string): Promise<TokenResponse>;
1079
966
 
1080
967
  /**
1081
- * Extracts typed entity clients from a plugin schema.
1082
- * Each entity type becomes a `PluginEntityClient<DataSchema>`.
1083
- * Entities are nested under `db` to separate them from API endpoints.
968
+ * Raw incoming webhook data for matching (before full parsing).
969
+ * Used by matcher functions to determine if a webhook should be handled.
1084
970
  */
1085
- type InferPluginEntities<Schema extends CorsairPluginSchema<Record<string, ZodTypeAny>> | undefined> = Schema extends CorsairPluginSchema<infer Entities> ? {
1086
- db: PluginEntityClients<Entities>;
1087
- } : {};
971
+ type RawWebhookRequest = {
972
+ /** HTTP headers from the webhook request */
973
+ headers: Record<string, string | string[] | undefined>;
974
+ /** Raw request body (string or already parsed object) */
975
+ body: unknown;
976
+ /** Query string parameters when available (e.g. Microsoft Graph validationToken). */
977
+ query?: Record<string, string | string[] | undefined>;
978
+ };
1088
979
  /**
1089
- * Utility type that converts a union to an intersection type.
980
+ * Raw incoming webhook request data after initial processing.
981
+ * Contains the parsed payload, headers, and optional raw body string.
982
+ * @template TPayload - The type of the parsed webhook payload
1090
983
  */
1091
- type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
984
+ type WebhookRequest<TPayload = unknown> = {
985
+ /** Parsed payload from the webhook request body */
986
+ payload: TPayload;
987
+ /** HTTP headers from the webhook request */
988
+ headers: Record<string, string | string[] | undefined>;
989
+ /** Raw request body string (for signature verification) */
990
+ rawBody?: string;
991
+ /** Query string parameters when available. */
992
+ query?: Record<string, string | string[] | undefined>;
993
+ };
1092
994
  /**
1093
- * Extracts the authType from plugin options.
1094
- * @template Options - The plugin options type
1095
- * @template DefaultAuthType - Optional default auth type to use when authType is optional or not present
1096
- *
1097
- * Priority:
1098
- * 1. If authType is a specific single AuthType (not a union), use that
1099
- * 2. If DefaultAuthType parameter is provided, use that as the fallback
1100
- * 3. Otherwise use the non-nullable union from authType
1101
- * 4. If authType is not in Options at all, fall back to DefaultAuthType
995
+ * Response from a webhook handler that can include acknowledgment data.
996
+ * @template TData - The type of data to return in the response
1102
997
  */
1103
- type ExtractAuthType<Options, DefaultAuthType extends AuthTypes | undefined = undefined> = 'authType' extends keyof Options ? Options['authType'] extends AuthTypes ? Options['authType'] : DefaultAuthType extends AuthTypes ? DefaultAuthType : NonNullable<Options['authType']> extends AuthTypes ? NonNullable<Options['authType']> : never : DefaultAuthType extends AuthTypes ? DefaultAuthType : never;
998
+ type WebhookResponse<TData = unknown> = {
999
+ /** Whether the webhook was processed successfully */
1000
+ success: boolean;
1001
+ /** The entity relevant to the webhook (note that this is corsair_entities.id) */
1002
+ corsairEntityId?: string;
1003
+ /** Return this object to the sender. Defaults to empty. Usually only necessary for webhook confirmation / challenge. */
1004
+ returnToSender?: Record<string, string>;
1005
+ /** Optional data to return in the HTTP response */
1006
+ data?: TData;
1007
+ /** Optional error message if processing failed */
1008
+ error?: string;
1009
+ /** HTTP status code to return (defaults to 200 on success, 500 on error) */
1010
+ statusCode?: number;
1011
+ /** HTTP response headers to set on the outgoing response. Used for header-based handshakes (e.g. Asana X-Hook-Secret). */
1012
+ responseHeaders?: Record<string, string>;
1013
+ };
1104
1014
  /**
1105
- * Extracts Options type from plugin's options property.
1015
+ * A webhook matcher function that synchronously determines if a raw webhook
1016
+ * request should be handled by this webhook.
1017
+ * @param request - The raw webhook request data
1018
+ * @returns True if this webhook should handle the request
1106
1019
  */
1107
- type InferPluginOptions<P> = P extends {
1108
- options?: infer O;
1109
- } ? O : never;
1020
+ type CorsairWebhookMatcher = (request: RawWebhookRequest) => boolean;
1110
1021
  /**
1111
- * Extracts DefaultAuthType from plugin's __defaultAuthType property.
1112
- * Uses NonNullable<D> because the __defaultAuthType property is optional,
1113
- * which causes TypeScript to infer D as `AuthType | undefined`.
1022
+ * Identifies which external credential key should be used to resolve a tenant
1023
+ * for an incoming webhook. The `linkType` names the field stored on
1024
+ * `corsair_accounts` (for example `team_id`, `installation_id`).
1114
1025
  */
1115
- type InferDefaultAuthType<P> = P extends {
1116
- __defaultAuthType?: infer D;
1117
- } ? NonNullable<D> extends AuthTypes ? NonNullable<D> : undefined : undefined;
1026
+ type WebhookTenantMatch = {
1027
+ linkType: string;
1028
+ externalId: string;
1029
+ };
1118
1030
  /**
1119
- * Extracts the AuthConfig from a plugin's authConfig property.
1031
+ * Extracts the tenant lookup key from a webhook after the plugin has been
1032
+ * identified. Return null when the payload does not contain a resolvable id
1033
+ * (for example URL verification challenges).
1120
1034
  */
1121
- type InferAuthConfig<P> = P extends {
1122
- authConfig?: infer C;
1123
- } ? C extends PluginAuthConfig ? C : undefined : undefined;
1035
+ type CorsairWebhookTenantMatcher = (request: RawWebhookRequest) => WebhookTenantMatch | null;
1124
1036
  /**
1125
- * Infers the complete namespace for a single plugin, including API endpoints,
1126
- * database entities, webhooks, and account-level keys.
1037
+ * Resolves the webhook tenant link field after OAuth completes.
1038
+ * Return null when the provider does not expose a stable external id.
1127
1039
  */
1128
- type InferPluginNamespace<P extends CorsairPlugin> = P extends CorsairPlugin<infer Id, infer Schema, infer Endpoints, infer Webhooks> ? {
1129
- [K in Id]: (Endpoints extends EndpointTree ? {
1130
- api: BindEndpoints<Endpoints>;
1131
- } : {}) & InferPluginEntities<Schema> & (Webhooks extends WebhookTree ? {
1132
- webhooks: BindWebhooks<Webhooks>;
1133
- /**
1134
- * Synchronously checks if an incoming webhook request is intended for this plugin.
1135
- * Only present if the plugin defines a `pluginWebhookMatcher`.
1136
- * Use this as a first-level filter before checking individual webhook matchers.
1137
- */
1138
- pluginWebhookMatcher?: (request: RawWebhookRequest) => boolean;
1139
- } : {}) & (ExtractAuthType<InferPluginOptions<P>, InferDefaultAuthType<P>> extends AuthTypes ? {
1140
- keys: AccountKeyManagerFor<ExtractAuthType<InferPluginOptions<P>, InferDefaultAuthType<P>>, InferAuthConfig<P>>;
1141
- } : {});
1142
- } : never;
1040
+ type CorsairOAuthWebhookTenantLinkResolver = (tokens: TokenResponse) => WebhookTenantMatch | null | Promise<WebhookTenantMatch | null>;
1143
1041
  /**
1144
- * Infers the integration-level key manager for a single plugin.
1042
+ * Bivariance hack for webhook function types to ensure proper type inference.
1043
+ * @template Args - The function arguments
1044
+ * @template R - The function return type
1145
1045
  */
1146
- type InferIntegrationKeys<P extends CorsairPlugin> = P extends CorsairPlugin<infer Id> ? ExtractAuthType<InferPluginOptions<P>, InferDefaultAuthType<P>> extends AuthTypes ? {
1147
- [K in Id]: IntegrationKeyManagerFor<ExtractAuthType<InferPluginOptions<P>, InferDefaultAuthType<P>>, InferAuthConfig<P>>;
1148
- } : never : never;
1046
+ type Bivariant<Args extends unknown[], R> = {
1047
+ bivarianceHack(...args: Args): R;
1048
+ }['bivarianceHack'];
1149
1049
  /**
1150
- * Combines all integration-level keys into a single interface.
1050
+ * A webhook handler function definition that processes incoming webhooks.
1051
+ * Takes context + webhook request, returns a webhook response.
1052
+ * @template Ctx - The context type passed to the handler
1053
+ * @template TPayload - The type of the webhook payload
1054
+ * @template TResponseData - The type of data returned in the response
1151
1055
  */
1152
- type InferAllIntegrationKeys<Plugins extends readonly CorsairPlugin[]> = UnionToIntersection<InferIntegrationKeys<Plugins[number]>>;
1056
+ type CorsairWebhookHandler<Ctx extends CorsairContext = CorsairContext, TPayload = unknown, TResponseData = unknown> = Bivariant<[
1057
+ ctx: Ctx,
1058
+ request: WebhookRequest<TPayload>
1059
+ ], Promise<WebhookResponse<TResponseData>>>;
1153
1060
  /**
1154
- * Combines all plugin namespaces into a single client interface.
1061
+ * A complete webhook definition with both matcher and handler.
1062
+ * The matcher synchronously determines if this webhook handles the incoming request.
1063
+ * The handler processes the webhook after matching.
1064
+ * @template Ctx - The context type passed to the handler
1065
+ * @template TPayload - The type of the webhook payload
1066
+ * @template TResponseData - The type of data returned in the response
1155
1067
  */
1156
- type InferPluginNamespaces<Plugins extends readonly CorsairPlugin[]> = UnionToIntersection<InferPluginNamespace<Plugins[number]>>;
1068
+ type CorsairWebhook<Ctx extends CorsairContext = CorsairContext, TPayload = unknown, TResponseData = unknown> = {
1069
+ /** Synchronously determines if this webhook handles the incoming request */
1070
+ match: CorsairWebhookMatcher;
1071
+ /** Handles the webhook request after matching */
1072
+ handler: CorsairWebhookHandler<Ctx, TPayload, TResponseData>;
1073
+ };
1157
1074
  /**
1158
- * The main Corsair client type that provides access to all plugin APIs, entities, webhooks, and keys.
1075
+ * A tree of webhooks that can be nested arbitrarily deep.
1076
+ * Similar to EndpointTree but for webhook handlers.
1077
+ *
1078
+ * @example
1079
+ * ```ts
1080
+ * // Flat structure
1081
+ * webhooks: {
1082
+ * issueCreated: { match: (req) => ..., handler: async (ctx, req) => ... },
1083
+ * issueClosed: { match: (req) => ..., handler: async (ctx, req) => ... },
1084
+ * }
1085
+ *
1086
+ * // Nested structure
1087
+ * webhooks: {
1088
+ * issues: {
1089
+ * created: { match: (req) => ..., handler: async (ctx, req) => ... },
1090
+ * updated: { match: (req) => ..., handler: async (ctx, req) => ... },
1091
+ * },
1092
+ * pull_requests: {
1093
+ * opened: { match: (req) => ..., handler: async (ctx, req) => ... },
1094
+ * },
1095
+ * }
1096
+ * ```
1159
1097
  */
1160
- type CorsairClient<Plugins extends readonly CorsairPlugin[]> = InferPluginNamespaces<Plugins>;
1098
+ type WebhookTree = {
1099
+ [key: string]: CorsairWebhook | WebhookTree;
1100
+ };
1161
1101
  /**
1162
- * Multi-tenant wrapper that provides a `withTenant` method to scope operations to a specific tenant.
1163
- * Also includes integration-level `keys` for managing shared secrets (OAuth2 client credentials, etc.)
1102
+ * A bound webhook - the user-facing API after context is applied.
1103
+ * Contains both the matcher (unchanged) and the bound handler.
1104
+ * @template TPayload - The type of the webhook payload
1105
+ * @template TResponseData - The type of data returned in the response
1164
1106
  */
1165
- type CorsairTenantWrapper<Plugins extends readonly CorsairPlugin[]> = {
1166
- withTenant: (tenantId: string) => CorsairClient<Plugins>;
1167
- /**
1168
- * Integration-level key managers for each plugin.
1169
- * Used to manage secrets shared across all tenants (e.g., OAuth2 client_id, client_secret).
1170
- */
1171
- keys: InferAllIntegrationKeys<Plugins>;
1172
- /**
1173
- * Permission management namespace. Use this to query and transition permission records.
1174
- * Available at the root regardless of multi-tenancy setting.
1175
- */
1176
- permissions: CorsairPermissionsNamespace;
1177
- /**
1178
- * Management control plane namespace — in-process equivalent of the HTTP
1179
- * `managementHandler`. Lists tenants, plugins, connection status, and
1180
- * permission records without going through HTTP.
1181
- */
1182
- manage: CorsairManageNamespace;
1107
+ type BoundWebhook<TPayload = unknown, TResponseData = unknown> = {
1108
+ /** Synchronously determines if this webhook handles the incoming request */
1109
+ match: CorsairWebhookMatcher;
1110
+ /** Handles the webhook request (context already applied) */
1111
+ handler: (request: WebhookRequest<TPayload>) => Promise<WebhookResponse<TResponseData>>;
1183
1112
  };
1184
1113
  /**
1185
- * Single-tenant client that includes both plugin APIs and integration-level keys.
1114
+ * A tree of bound webhooks (context already applied).
1115
+ * This is what the end user interacts with after client initialization.
1186
1116
  */
1187
- type CorsairSingleTenantClient<Plugins extends readonly CorsairPlugin[]> = CorsairClient<Plugins> & {
1188
- /**
1189
- * Integration-level key managers for each plugin.
1190
- * Used to manage secrets shared across all tenants (e.g., OAuth2 client_id, client_secret).
1191
- */
1192
- keys: InferAllIntegrationKeys<Plugins>;
1193
- /**
1194
- * Permission management namespace. Use this to query and transition permission records.
1195
- * Available at the root regardless of multi-tenancy setting.
1196
- */
1197
- permissions: CorsairPermissionsNamespace;
1198
- /**
1199
- * Management control plane namespace — in-process equivalent of the HTTP
1200
- * `managementHandler`. Lists tenants, plugins, connection status, and
1201
- * permission records without going through HTTP.
1202
- */
1203
- manage: CorsairManageNamespace;
1204
- };
1205
-
1206
- declare const CORSAIR_INTERNAL: unique symbol;
1207
- type CorsairInternalConfig = {
1208
- plugins: readonly CorsairPlugin[];
1209
- database: CorsairDatabase | undefined;
1210
- kek: string;
1211
- multiTenancy: boolean;
1212
- approval?: {
1213
- timeout: string;
1214
- onTimeout: 'deny' | 'approve';
1215
- mode?: 'synchronous' | 'asynchronous' | (() => 'synchronous' | 'asynchronous');
1216
- /** Called when a permission is blocked in async mode. Return the message surfaced to the LLM. */
1217
- formatAsyncMessage?: (opts: {
1218
- token: string;
1219
- id: string;
1220
- plugin: string;
1221
- endpoint: string;
1222
- args: unknown;
1223
- }) => string;
1224
- };
1225
- connect?: {
1226
- baseUrl: string;
1227
- redirectUri: string;
1228
- onAuthMissing?: (opts: {
1229
- plugin: string;
1230
- connectUrl: string;
1231
- state: string;
1232
- }) => string;
1233
- };
1117
+ type BoundWebhookTree = {
1118
+ [key: string]: BoundWebhook<any, any> | BoundWebhookTree;
1234
1119
  };
1235
1120
  /**
1236
- * Creates a Corsair integration with multi-tenancy enabled.
1237
- * Returns a wrapper with a `withTenant()` method to scope operations to specific tenants,
1238
- * and a `keys` property for integration-level key management.
1239
- * @param config - Configuration with plugins, database, and multiTenancy: true
1240
- * @returns A tenant wrapper with `withTenant(tenantId)` method and integration-level `keys`
1121
+ * Recursively transforms webhook definitions to their bound (context-free) signatures.
1122
+ * Handles both flat and nested webhook structures.
1123
+ * @template T - The webhook tree to bind
1241
1124
  */
1242
- declare function createCorsair<const Plugins extends readonly CorsairPlugin[]>(config: CorsairIntegration<Plugins> & {
1243
- multiTenancy: true;
1244
- }): CorsairTenantWrapper<Plugins>;
1125
+ type BindWebhooks<T extends WebhookTree> = {
1126
+ [K in keyof T]: T[K] extends CorsairWebhook<any, infer P, infer R> ? BoundWebhook<P, R> : T[K] extends WebhookTree ? BindWebhooks<T[K]> : never;
1127
+ };
1245
1128
  /**
1246
- * Creates a Corsair integration without multi-tenancy.
1247
- * Returns a direct client instance with both plugin APIs and integration-level keys.
1248
- * @param config - Configuration with plugins and optional database
1249
- * @returns A Corsair client instance with plugin APIs and integration-level `keys`
1129
+ * Derives all dot-notation webhook paths from a WebhookTree as a string literal union.
1130
+ * Used to provide compile-time validation for webhook schema registry keys.
1131
+ * Passing an invalid path to any config that accepts WebhookPathsOf<T> is a type error.
1132
+ *
1133
+ * Design note: Same recursive constraint relaxation as EndpointPathsOf — see that type
1134
+ * for a full explanation of why we use `extends object` rather than `extends WebhookTree`
1135
+ * on the recursive call. We also check `{ match: any; handler: any }` before `object`
1136
+ * because webhook leaves are objects themselves.
1137
+ *
1138
+ * @example
1139
+ * Given: `{ messages: { message: { match: fn, handler: fn } }, channels: { created: { match: fn, handler: fn } } }`
1140
+ * Result: `'messages.message' | 'channels.created'`
1141
+ *
1142
+ * @template T - The webhook tree to extract paths from (unconstrained to allow recursion through as-const types)
1143
+ * @template Prefix - Internal accumulator for the current path prefix (do not supply manually)
1250
1144
  */
1251
- declare function createCorsair<const Plugins extends readonly CorsairPlugin[]>(config: CorsairIntegration<Plugins> & {
1252
- multiTenancy?: false | undefined;
1253
- }): CorsairSingleTenantClient<Plugins>;
1145
+ type WebhookPathsOf<T, Prefix extends string = ''> = {
1146
+ [K in keyof T & string]: T[K] extends {
1147
+ match: any;
1148
+ handler: any;
1149
+ } ? Prefix extends '' ? K : `${Prefix}.${K}` : T[K] extends object ? WebhookPathsOf<T[K], Prefix extends '' ? K : `${Prefix}.${K}`> : never;
1150
+ }[keyof T & string];
1254
1151
 
1255
- export { type RequiredPluginWebhookSchemas as $, type AuthTypes as A, type BaseAuthFieldConfig as B, type CorsairPlugin as C, type ErrorMatcher as D, type EndpointRiskLevel as E, type RetryStrategy as F, type CorsairPermissionsNamespace as G, type EnforcePermissionOptions as H, type IntegrationKeyManagerFor as I, type EnforcePermissionResult as J, type BeforeHookResult as K, type CorsairIntegration as L, type CorsairKeyBuilder as M, type CorsairKeyBuilderBase as N, type OAuthConfig as O, type PluginAuthConfig as P, type CorsairPluginContext as Q, type RetryStrategies as R, type EndpointHooks as S, type EndpointMetaEntry as T, type KeyBuilderContext as U, type PermissionMode as V, type PermissionPolicy as W, type PluginEndpointMeta as X, type PluginPermissionsConfig as Y, type RequiredPluginEndpointMeta as Z, type RequiredPluginEndpointSchemas as _, type AccountKeyManagerFor as a, type WebhookHooks as a0, type Bivariant$2 as a1, type UnionToIntersection$1 as a2, type BindWebhooks as a3, type BoundWebhook as a4, type BoundWebhookTree as a5, type CorsairWebhook as a6, type CorsairWebhookHandler as a7, type CorsairWebhookMatcher as a8, type RawWebhookRequest as a9, type WebhookPathsOf as aa, type WebhookRequest as ab, type WebhookResponse as ac, type WebhookTree as ad, type ManagementOk as ae, type Tenant as af, type CreateTenantInput as ag, type PluginInfo as ah, type ConnectionStatus as ai, type PermissionRecord as aj, type CorsairManageNamespace as ak, type PluginConnectionState as al, CORSAIR_INTERNAL as b, type CorsairInternalConfig as c, createCorsair as d, type AccountFieldNames as e, type BaseKeyManager as f, type IntegrationFieldNames as g, type OAuth2IntegrationCredentials as h, BASE_AUTH_FIELDS as i, type CorsairClient as j, type CorsairSingleTenantClient as k, type CorsairTenantWrapper as l, type AllProviders as m, BaseProviders as n, type PickAuth as o, type BindEndpoints as p, type BoundEndpointFn as q, type BoundEndpointTree as r, type CorsairContext as s, type CorsairEndpoint as t, type EndpointPathsOf as u, type EndpointTree as v, type CorsairErrorHandler as w, type ErrorContext as x, type ErrorHandler as y, type ErrorHandlerAndMatchFunction as z };
1152
+ export { type WebhookResponse as $, type PermissionPolicy as A, type BindEndpoints as B, type CorsairPlugin as C, type PluginEndpointMeta as D, type EndpointPathsOf as E, type PluginPermissionsConfig as F, type RequiredPluginEndpointMeta as G, type RequiredPluginEndpointSchemas as H, type RequiredPluginWebhookSchemas as I, type WebhookHooks as J, type KeyBuilderContext as K, type BindWebhooks as L, type BoundWebhook as M, type BoundWebhookTree as N, type OAuthConfig as O, type PermissionMode as P, type CorsairOAuthWebhookTenantLinkResolver as Q, type RetryStrategies as R, type CorsairWebhook as S, type TokenResponse as T, type CorsairWebhookHandler as U, type CorsairWebhookMatcher as V, type WebhookTenantMatch as W, type CorsairWebhookTenantMatcher as X, type RawWebhookRequest as Y, type WebhookPathsOf as Z, type WebhookRequest as _, type CorsairIntegration as a, type WebhookTree as a0, type ManagementOk as a1, type Tenant as a2, type CreateTenantInput as a3, type PluginInfo as a4, type ConnectionStatus as a5, type PermissionRecord as a6, type CreateConnectLinkInput as a7, type ConnectLink as a8, type ResolvedConnectLink as a9, type OAuthCallbackInput as aa, type OAuthCallbackResult as ab, type CorsairManageNamespace as ac, type PluginConnectionState as ad, type CorsairTenantWrapper as b, type CorsairSingleTenantClient as c, type CorsairClient as d, exchangeCodeForTokens as e, type BoundEndpointFn as f, type BoundEndpointTree as g, type CorsairContext as h, type CorsairEndpoint as i, type EndpointTree as j, type CorsairErrorHandler as k, type ErrorContext as l, type ErrorHandler as m, type ErrorHandlerAndMatchFunction as n, type ErrorMatcher as o, type RetryStrategy as p, type CorsairPermissionsNamespace as q, type EnforcePermissionOptions as r, type EnforcePermissionResult as s, type BeforeHookResult as t, type CorsairKeyBuilder as u, type CorsairKeyBuilderBase as v, type CorsairPluginContext as w, type EndpointHooks as x, type EndpointMetaEntry as y, type EndpointRiskLevel as z };