@nominalso/vibe-host 0.0.1 → 0.2.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/dist/index.d.cts CHANGED
@@ -3201,15 +3201,6 @@ interface GetContextPayload {
3201
3201
  bridgeVersion: string;
3202
3202
  }
3203
3203
 
3204
- /**
3205
- * All operations the iframe can invoke on the host, each with a typed request
3206
- * payload and a typed response. Adding a new operation here is all that's
3207
- * required — message union types and handler types below are derived
3208
- * automatically.
3209
- *
3210
- * Operations that also emit intermediate progress events should include a
3211
- * `progress` field with the progress payload type.
3212
- */
3213
3204
  interface UploadPayload {
3214
3205
  buffer: ArrayBuffer;
3215
3206
  fileName: string;
@@ -3220,11 +3211,57 @@ interface UploadPayload {
3220
3211
  interface UploadResponse {
3221
3212
  attachmentId: string;
3222
3213
  name: string;
3214
+ /** Public/presigned URL of the stored file, when the host returns one. */
3215
+ url?: string;
3223
3216
  }
3224
3217
  interface UploadProgress {
3225
3218
  attachmentId: string;
3226
3219
  progress: number;
3227
3220
  }
3221
+ /** How the host should apply a navigation. Defaults to `'push'`. */
3222
+ type NavigateAction = 'push' | 'replace' | 'refresh';
3223
+ /**
3224
+ * Tells the host to navigate. Provide **either** a raw `path` (relative to the
3225
+ * app's tenant/subsidiary base) **or** a named `route` resolved host-side.
3226
+ */
3227
+ interface NavigatePayload {
3228
+ /** Raw path relative to the app's base, e.g. `'/journal-entries/123'`. */
3229
+ path?: string;
3230
+ /** Named route key the host resolves, e.g. `'CHART_OF_ACCOUNTS'`. */
3231
+ route?: string;
3232
+ /** Values for route placeholders, e.g. `{ id: '123' }`. */
3233
+ routeParams?: Record<string, string>;
3234
+ /** Navigation method. Defaults to `'push'`. */
3235
+ action?: NavigateAction;
3236
+ }
3237
+ /** Collapse/expand the host's side navigation. */
3238
+ interface SetSideNavPayload {
3239
+ collapsed: boolean;
3240
+ }
3241
+ /** Switch the host to a different subsidiary. */
3242
+ interface SwitchSubsidiaryPayload {
3243
+ subsidiaryId: number;
3244
+ }
3245
+ /** Targets for a host cache invalidation. */
3246
+ interface InvalidateCachePayload {
3247
+ /** API path prefixes to invalidate, e.g. `['/accounting/accounts']`. */
3248
+ paths?: string[];
3249
+ /** Cache tag keys to invalidate, e.g. `['REPORTS', 'SUBSIDIARIES']`. */
3250
+ tags?: string[];
3251
+ }
3252
+ /** Result of a host cache invalidation. */
3253
+ interface InvalidateResult {
3254
+ invalidated: boolean;
3255
+ }
3256
+
3257
+ /**
3258
+ * All operations the iframe can invoke on the host, each with a typed request
3259
+ * payload and a typed response. Adding a new operation here is all that's
3260
+ * required — message union types and handler types are derived automatically.
3261
+ *
3262
+ * Operations that also emit intermediate progress events should include a
3263
+ * `progress` field with the progress payload type.
3264
+ */
3228
3265
  interface RequestRegistry {
3229
3266
  GET_CHART_OF_ACCOUNTS: {
3230
3267
  payload: Omit<GetCoaFlatApiAccountingSubsidiarySubsidiaryIdCoaAccountGetData, 'url'>;
@@ -3410,6 +3447,10 @@ interface RequestRegistry {
3410
3447
  payload: Omit<GetTenantUsersApiTenancyTenantTenantIdOrNameUsersGetData, 'url'>;
3411
3448
  data: GetTenantUsersApiTenancyTenantTenantIdOrNameUsersGetResponse;
3412
3449
  };
3450
+ INVALIDATE_CACHE: {
3451
+ payload: InvalidateCachePayload;
3452
+ data: InvalidateResult;
3453
+ };
3413
3454
  GET_CONTEXT: {
3414
3455
  payload: GetContextPayload;
3415
3456
  data: ContextPayload;
@@ -3434,6 +3475,53 @@ type RequestHandlers = {
3434
3475
  } ? (payload: RequestRegistry[K]['payload'], sendProgress: (p: P) => void) => Promise<RequestRegistry[K]['data']> : (payload: RequestRegistry[K]['payload']) => Promise<RequestRegistry[K]['data']>;
3435
3476
  };
3436
3477
 
3478
+ /**
3479
+ * An error that crossed the bridge, carrying a machine-readable {@link code}
3480
+ * alongside the human-readable message.
3481
+ *
3482
+ * The host attaches a `code` to failed responses (e.g. `'RATE_LIMITED'`,
3483
+ * `'FILE_TOO_LARGE'`, `'REQUEST_FAILED'`); the bridge rehydrates it into a
3484
+ * `BridgeError` so app `catch` blocks can branch on `error.code` instead of
3485
+ * string-matching `error.message`. A failed Nominal API call rehydrates as the
3486
+ * {@link HttpBridgeError} subtype, which adds the HTTP `status`.
3487
+ *
3488
+ * @example
3489
+ * ```ts
3490
+ * try {
3491
+ * await bridge.getAccounts({})
3492
+ * } catch (err) {
3493
+ * if (err instanceof BridgeError && err.code === 'RATE_LIMITED') {
3494
+ * // back off and retry
3495
+ * }
3496
+ * }
3497
+ * ```
3498
+ */
3499
+ declare class BridgeError extends Error {
3500
+ readonly code: string;
3501
+ constructor(code: string, message: string);
3502
+ }
3503
+ /**
3504
+ * A {@link BridgeError} for a failed Nominal API call. Its `code` is always
3505
+ * `'REQUEST_FAILED'`; the HTTP {@link status} is carried as a structured field
3506
+ * so callers can branch on it (e.g. `err.status === 404`) instead of parsing
3507
+ * the code string.
3508
+ *
3509
+ * @example
3510
+ * ```ts
3511
+ * try {
3512
+ * await bridge.getAccount({ path: { account_id: 'missing' } })
3513
+ * } catch (err) {
3514
+ * if (err instanceof HttpBridgeError && err.status === 404) {
3515
+ * // handle not-found
3516
+ * }
3517
+ * }
3518
+ * ```
3519
+ */
3520
+ declare class HttpBridgeError extends BridgeError {
3521
+ readonly status: number;
3522
+ constructor(status: number, message: string);
3523
+ }
3524
+
3437
3525
  interface VibeApiClientOptions {
3438
3526
  baseUrl: string;
3439
3527
  /**
@@ -3446,7 +3534,24 @@ interface VibeApiClientOptions {
3446
3534
 
3447
3535
  type HostHandlers = Omit<RequestHandlers, 'GET_CONTEXT'>;
3448
3536
  type HostContext = Omit<ContextPayload, 'hostVersion'>;
3537
+ /**
3538
+ * Notified after each request resolves — except the `GET_CONTEXT` connect poll,
3539
+ * which is exempt. Used by nom-ui for SWR busting (SSOT §5j).
3540
+ */
3541
+ interface RequestCompleteInfo {
3542
+ /** The operation type, e.g. `'POST_TASK_OUTPUT'`. */
3543
+ type: string;
3544
+ /** Whether the operation succeeded. */
3545
+ ok: boolean;
3546
+ /** The request payload that was handled. */
3547
+ payload: unknown;
3548
+ }
3449
3549
  interface VibeAppHostOptions {
3550
+ /**
3551
+ * Iframe origins allowed to talk to this host. Messages from any other origin
3552
+ * are ignored, and context/pushes are only sent to these origins. Must match
3553
+ * the Vibe App's origin exactly (e.g. `'https://my-vibe-app.lovable.app'`).
3554
+ */
3450
3555
  trustedOrigins: string[];
3451
3556
  /** Returns the current context to send to the iframe on connect. */
3452
3557
  getContext: () => HostContext;
@@ -3455,21 +3560,98 @@ interface VibeAppHostOptions {
3455
3560
  * Used to sync the browser URL with the iframe's internal subroute.
3456
3561
  */
3457
3562
  appBasePath: string;
3563
+ /**
3564
+ * Points the built-in handler map at the Nominal API (e.g. nom-ui's proxy
3565
+ * route). The host provides handlers for every protocol operation out of the
3566
+ * box; this just tells them where to send requests. Defaults to
3567
+ * `{ baseUrl: '', stripApiPrefix: false }`.
3568
+ */
3458
3569
  clientConfig?: VibeApiClientOptions;
3570
+ /** Fire-and-forget. Resolve a path / named route and push to the host router. */
3571
+ onNavigate?: (payload: NavigatePayload) => void;
3572
+ /** Fire-and-forget. Collapse/expand the host side nav. */
3573
+ onSetSideNav?: (payload: SetSideNavPayload) => void;
3574
+ /** Fire-and-forget. Switch subsidiary (host owns validation + navigation). */
3575
+ onSwitchSubsidiary?: (payload: SwitchSubsidiaryPayload) => void;
3576
+ /** Awaited. Invalidate host cache by tags/paths. Defaults to a no-op. */
3577
+ onInvalidateCache?: (payload: InvalidateCachePayload) => Promise<InvalidateResult>;
3578
+ /**
3579
+ * Awaited. Real file upload — replaces the built-in stub (DP2). When omitted,
3580
+ * `UPLOAD_FILE` requests reject with a `'NOT_WIRED'` coded error.
3581
+ */
3582
+ onUpload?: (payload: UploadPayload, sendProgress: (p: UploadProgress) => void) => Promise<UploadResponse>;
3583
+ /** Optional. Notified after each request resolves (except the `GET_CONTEXT` connect poll) — used by nom-ui for SWR busting (SSOT §5j). */
3584
+ onRequestComplete?: (info: RequestCompleteInfo) => void;
3585
+ /** Requests/min before a request trips `RATE_LIMITED` (DP5). Default 60. */
3586
+ rateLimit?: number;
3459
3587
  }
3588
+
3589
+ /**
3590
+ * Host-side counterpart to `VibeAppBridge`. Construct it with the trusted
3591
+ * iframe origin(s), a `getContext` callback, the app's base path, and a
3592
+ * `clientConfig` pointing at the Nominal API; then call {@link VibeAppHost.mount}
3593
+ * to start listening. The handler map for all protocol API operations is built
3594
+ * automatically — you do not pass those handlers. Non-API side effects
3595
+ * (navigation, side nav, subsidiary switch, cache invalidation, upload) are
3596
+ * supplied via the optional injected callbacks.
3597
+ *
3598
+ * @example
3599
+ * ```tsx
3600
+ * // In a React (nom-ui) component:
3601
+ * const host = new VibeAppHost({
3602
+ * trustedOrigins: ['https://my-vibe-app.lovable.app'],
3603
+ * appBasePath: `/${tenant}/${subsidiary}/apps/${slug}`,
3604
+ * getContext: () => ({ tenant, subsidiaryId, subsidiaries, user, lastClosedPeriodSlug }),
3605
+ * clientConfig: { baseUrl: '/api/proxy', stripApiPrefix: true },
3606
+ * onNavigate: (p) => router.push(resolve(p)),
3607
+ * onUpload: (p, sendProgress) => uploadToS3(p, sendProgress),
3608
+ * })
3609
+ *
3610
+ * useEffect(() => {
3611
+ * const iframe = iframeRef.current
3612
+ * if (!iframe) return
3613
+ * const unmount = host.mount() // listen before the iframe loads
3614
+ * const onLoad = () => iframe.contentWindow && host.pushContextTo(iframe.contentWindow)
3615
+ * iframe.addEventListener('load', onLoad)
3616
+ * return () => {
3617
+ * iframe.removeEventListener('load', onLoad)
3618
+ * unmount()
3619
+ * }
3620
+ * }, [host])
3621
+ * ```
3622
+ */
3460
3623
  declare class VibeAppHost {
3461
3624
  private readonly trustedOrigins;
3462
3625
  private readonly handlers;
3626
+ private readonly commandHandlers;
3463
3627
  private readonly getContext;
3464
3628
  private readonly appBasePath;
3629
+ private readonly onRequestComplete?;
3630
+ private readonly rateLimiter;
3465
3631
  private readonly boundHandleMessage;
3466
3632
  private readonly boundHandlePopState;
3467
3633
  private iframeWindow;
3634
+ /**
3635
+ * Source windows we've already logged a successful handshake for. The bridge
3636
+ * polls `GET_CONTEXT` on an interval until it connects, so without this the
3637
+ * "Vibe App connected" line repeats on every poll (SSOT §13 F3).
3638
+ */
3639
+ private readonly handshakeLoggedWindows;
3468
3640
  private readonly kindHandlers;
3469
3641
  private dispatchCommand;
3470
- private readonly commandHandlers;
3642
+ /**
3643
+ * Commands: `REPORT_SUBROUTE` is handled internally (transport + traversal
3644
+ * guard, item `l`); navigation/UI side effects are injected by the consumer,
3645
+ * falling back to warn-and-ignore (DP4).
3646
+ */
3647
+ private buildCommandHandlers;
3648
+ /**
3649
+ * Requests: the built-in API map (hey-api), plus the injected/awaited side
3650
+ * effects with safe defaults — unwired `UPLOAD_FILE` rejects with a coded
3651
+ * error; unwired `INVALIDATE_CACHE` resolves to a no-op (DP4).
3652
+ */
3471
3653
  private initializeHandlers;
3472
- constructor({ trustedOrigins, getContext, appBasePath, clientConfig }: VibeAppHostOptions);
3654
+ constructor(opts: VibeAppHostOptions);
3473
3655
  /**
3474
3656
  * Starts listening for bridge messages and sets up browser back/forward sync.
3475
3657
  * Returns a cleanup function to call on unmount.
@@ -3494,4 +3676,4 @@ declare class VibeAppHost {
3494
3676
  private respond;
3495
3677
  }
3496
3678
 
3497
- export { type HostContext, type HostHandlers, type RequestHandlers, VibeAppHost, type VibeAppHostOptions };
3679
+ export { BridgeError, type ContextPayload, type HostContext, type HostHandlers, HttpBridgeError, type InvalidateCachePayload, type InvalidateResult, type NavigateAction, type NavigatePayload, type RequestCompleteInfo, type RequestHandlers, type SetSideNavPayload, type SwitchSubsidiaryPayload, type UploadPayload, type UploadProgress, type UploadResponse, type VibeApiClientOptions, VibeAppHost, type VibeAppHostOptions };
package/dist/index.d.ts CHANGED
@@ -3201,15 +3201,6 @@ interface GetContextPayload {
3201
3201
  bridgeVersion: string;
3202
3202
  }
3203
3203
 
3204
- /**
3205
- * All operations the iframe can invoke on the host, each with a typed request
3206
- * payload and a typed response. Adding a new operation here is all that's
3207
- * required — message union types and handler types below are derived
3208
- * automatically.
3209
- *
3210
- * Operations that also emit intermediate progress events should include a
3211
- * `progress` field with the progress payload type.
3212
- */
3213
3204
  interface UploadPayload {
3214
3205
  buffer: ArrayBuffer;
3215
3206
  fileName: string;
@@ -3220,11 +3211,57 @@ interface UploadPayload {
3220
3211
  interface UploadResponse {
3221
3212
  attachmentId: string;
3222
3213
  name: string;
3214
+ /** Public/presigned URL of the stored file, when the host returns one. */
3215
+ url?: string;
3223
3216
  }
3224
3217
  interface UploadProgress {
3225
3218
  attachmentId: string;
3226
3219
  progress: number;
3227
3220
  }
3221
+ /** How the host should apply a navigation. Defaults to `'push'`. */
3222
+ type NavigateAction = 'push' | 'replace' | 'refresh';
3223
+ /**
3224
+ * Tells the host to navigate. Provide **either** a raw `path` (relative to the
3225
+ * app's tenant/subsidiary base) **or** a named `route` resolved host-side.
3226
+ */
3227
+ interface NavigatePayload {
3228
+ /** Raw path relative to the app's base, e.g. `'/journal-entries/123'`. */
3229
+ path?: string;
3230
+ /** Named route key the host resolves, e.g. `'CHART_OF_ACCOUNTS'`. */
3231
+ route?: string;
3232
+ /** Values for route placeholders, e.g. `{ id: '123' }`. */
3233
+ routeParams?: Record<string, string>;
3234
+ /** Navigation method. Defaults to `'push'`. */
3235
+ action?: NavigateAction;
3236
+ }
3237
+ /** Collapse/expand the host's side navigation. */
3238
+ interface SetSideNavPayload {
3239
+ collapsed: boolean;
3240
+ }
3241
+ /** Switch the host to a different subsidiary. */
3242
+ interface SwitchSubsidiaryPayload {
3243
+ subsidiaryId: number;
3244
+ }
3245
+ /** Targets for a host cache invalidation. */
3246
+ interface InvalidateCachePayload {
3247
+ /** API path prefixes to invalidate, e.g. `['/accounting/accounts']`. */
3248
+ paths?: string[];
3249
+ /** Cache tag keys to invalidate, e.g. `['REPORTS', 'SUBSIDIARIES']`. */
3250
+ tags?: string[];
3251
+ }
3252
+ /** Result of a host cache invalidation. */
3253
+ interface InvalidateResult {
3254
+ invalidated: boolean;
3255
+ }
3256
+
3257
+ /**
3258
+ * All operations the iframe can invoke on the host, each with a typed request
3259
+ * payload and a typed response. Adding a new operation here is all that's
3260
+ * required — message union types and handler types are derived automatically.
3261
+ *
3262
+ * Operations that also emit intermediate progress events should include a
3263
+ * `progress` field with the progress payload type.
3264
+ */
3228
3265
  interface RequestRegistry {
3229
3266
  GET_CHART_OF_ACCOUNTS: {
3230
3267
  payload: Omit<GetCoaFlatApiAccountingSubsidiarySubsidiaryIdCoaAccountGetData, 'url'>;
@@ -3410,6 +3447,10 @@ interface RequestRegistry {
3410
3447
  payload: Omit<GetTenantUsersApiTenancyTenantTenantIdOrNameUsersGetData, 'url'>;
3411
3448
  data: GetTenantUsersApiTenancyTenantTenantIdOrNameUsersGetResponse;
3412
3449
  };
3450
+ INVALIDATE_CACHE: {
3451
+ payload: InvalidateCachePayload;
3452
+ data: InvalidateResult;
3453
+ };
3413
3454
  GET_CONTEXT: {
3414
3455
  payload: GetContextPayload;
3415
3456
  data: ContextPayload;
@@ -3434,6 +3475,53 @@ type RequestHandlers = {
3434
3475
  } ? (payload: RequestRegistry[K]['payload'], sendProgress: (p: P) => void) => Promise<RequestRegistry[K]['data']> : (payload: RequestRegistry[K]['payload']) => Promise<RequestRegistry[K]['data']>;
3435
3476
  };
3436
3477
 
3478
+ /**
3479
+ * An error that crossed the bridge, carrying a machine-readable {@link code}
3480
+ * alongside the human-readable message.
3481
+ *
3482
+ * The host attaches a `code` to failed responses (e.g. `'RATE_LIMITED'`,
3483
+ * `'FILE_TOO_LARGE'`, `'REQUEST_FAILED'`); the bridge rehydrates it into a
3484
+ * `BridgeError` so app `catch` blocks can branch on `error.code` instead of
3485
+ * string-matching `error.message`. A failed Nominal API call rehydrates as the
3486
+ * {@link HttpBridgeError} subtype, which adds the HTTP `status`.
3487
+ *
3488
+ * @example
3489
+ * ```ts
3490
+ * try {
3491
+ * await bridge.getAccounts({})
3492
+ * } catch (err) {
3493
+ * if (err instanceof BridgeError && err.code === 'RATE_LIMITED') {
3494
+ * // back off and retry
3495
+ * }
3496
+ * }
3497
+ * ```
3498
+ */
3499
+ declare class BridgeError extends Error {
3500
+ readonly code: string;
3501
+ constructor(code: string, message: string);
3502
+ }
3503
+ /**
3504
+ * A {@link BridgeError} for a failed Nominal API call. Its `code` is always
3505
+ * `'REQUEST_FAILED'`; the HTTP {@link status} is carried as a structured field
3506
+ * so callers can branch on it (e.g. `err.status === 404`) instead of parsing
3507
+ * the code string.
3508
+ *
3509
+ * @example
3510
+ * ```ts
3511
+ * try {
3512
+ * await bridge.getAccount({ path: { account_id: 'missing' } })
3513
+ * } catch (err) {
3514
+ * if (err instanceof HttpBridgeError && err.status === 404) {
3515
+ * // handle not-found
3516
+ * }
3517
+ * }
3518
+ * ```
3519
+ */
3520
+ declare class HttpBridgeError extends BridgeError {
3521
+ readonly status: number;
3522
+ constructor(status: number, message: string);
3523
+ }
3524
+
3437
3525
  interface VibeApiClientOptions {
3438
3526
  baseUrl: string;
3439
3527
  /**
@@ -3446,7 +3534,24 @@ interface VibeApiClientOptions {
3446
3534
 
3447
3535
  type HostHandlers = Omit<RequestHandlers, 'GET_CONTEXT'>;
3448
3536
  type HostContext = Omit<ContextPayload, 'hostVersion'>;
3537
+ /**
3538
+ * Notified after each request resolves — except the `GET_CONTEXT` connect poll,
3539
+ * which is exempt. Used by nom-ui for SWR busting (SSOT §5j).
3540
+ */
3541
+ interface RequestCompleteInfo {
3542
+ /** The operation type, e.g. `'POST_TASK_OUTPUT'`. */
3543
+ type: string;
3544
+ /** Whether the operation succeeded. */
3545
+ ok: boolean;
3546
+ /** The request payload that was handled. */
3547
+ payload: unknown;
3548
+ }
3449
3549
  interface VibeAppHostOptions {
3550
+ /**
3551
+ * Iframe origins allowed to talk to this host. Messages from any other origin
3552
+ * are ignored, and context/pushes are only sent to these origins. Must match
3553
+ * the Vibe App's origin exactly (e.g. `'https://my-vibe-app.lovable.app'`).
3554
+ */
3450
3555
  trustedOrigins: string[];
3451
3556
  /** Returns the current context to send to the iframe on connect. */
3452
3557
  getContext: () => HostContext;
@@ -3455,21 +3560,98 @@ interface VibeAppHostOptions {
3455
3560
  * Used to sync the browser URL with the iframe's internal subroute.
3456
3561
  */
3457
3562
  appBasePath: string;
3563
+ /**
3564
+ * Points the built-in handler map at the Nominal API (e.g. nom-ui's proxy
3565
+ * route). The host provides handlers for every protocol operation out of the
3566
+ * box; this just tells them where to send requests. Defaults to
3567
+ * `{ baseUrl: '', stripApiPrefix: false }`.
3568
+ */
3458
3569
  clientConfig?: VibeApiClientOptions;
3570
+ /** Fire-and-forget. Resolve a path / named route and push to the host router. */
3571
+ onNavigate?: (payload: NavigatePayload) => void;
3572
+ /** Fire-and-forget. Collapse/expand the host side nav. */
3573
+ onSetSideNav?: (payload: SetSideNavPayload) => void;
3574
+ /** Fire-and-forget. Switch subsidiary (host owns validation + navigation). */
3575
+ onSwitchSubsidiary?: (payload: SwitchSubsidiaryPayload) => void;
3576
+ /** Awaited. Invalidate host cache by tags/paths. Defaults to a no-op. */
3577
+ onInvalidateCache?: (payload: InvalidateCachePayload) => Promise<InvalidateResult>;
3578
+ /**
3579
+ * Awaited. Real file upload — replaces the built-in stub (DP2). When omitted,
3580
+ * `UPLOAD_FILE` requests reject with a `'NOT_WIRED'` coded error.
3581
+ */
3582
+ onUpload?: (payload: UploadPayload, sendProgress: (p: UploadProgress) => void) => Promise<UploadResponse>;
3583
+ /** Optional. Notified after each request resolves (except the `GET_CONTEXT` connect poll) — used by nom-ui for SWR busting (SSOT §5j). */
3584
+ onRequestComplete?: (info: RequestCompleteInfo) => void;
3585
+ /** Requests/min before a request trips `RATE_LIMITED` (DP5). Default 60. */
3586
+ rateLimit?: number;
3459
3587
  }
3588
+
3589
+ /**
3590
+ * Host-side counterpart to `VibeAppBridge`. Construct it with the trusted
3591
+ * iframe origin(s), a `getContext` callback, the app's base path, and a
3592
+ * `clientConfig` pointing at the Nominal API; then call {@link VibeAppHost.mount}
3593
+ * to start listening. The handler map for all protocol API operations is built
3594
+ * automatically — you do not pass those handlers. Non-API side effects
3595
+ * (navigation, side nav, subsidiary switch, cache invalidation, upload) are
3596
+ * supplied via the optional injected callbacks.
3597
+ *
3598
+ * @example
3599
+ * ```tsx
3600
+ * // In a React (nom-ui) component:
3601
+ * const host = new VibeAppHost({
3602
+ * trustedOrigins: ['https://my-vibe-app.lovable.app'],
3603
+ * appBasePath: `/${tenant}/${subsidiary}/apps/${slug}`,
3604
+ * getContext: () => ({ tenant, subsidiaryId, subsidiaries, user, lastClosedPeriodSlug }),
3605
+ * clientConfig: { baseUrl: '/api/proxy', stripApiPrefix: true },
3606
+ * onNavigate: (p) => router.push(resolve(p)),
3607
+ * onUpload: (p, sendProgress) => uploadToS3(p, sendProgress),
3608
+ * })
3609
+ *
3610
+ * useEffect(() => {
3611
+ * const iframe = iframeRef.current
3612
+ * if (!iframe) return
3613
+ * const unmount = host.mount() // listen before the iframe loads
3614
+ * const onLoad = () => iframe.contentWindow && host.pushContextTo(iframe.contentWindow)
3615
+ * iframe.addEventListener('load', onLoad)
3616
+ * return () => {
3617
+ * iframe.removeEventListener('load', onLoad)
3618
+ * unmount()
3619
+ * }
3620
+ * }, [host])
3621
+ * ```
3622
+ */
3460
3623
  declare class VibeAppHost {
3461
3624
  private readonly trustedOrigins;
3462
3625
  private readonly handlers;
3626
+ private readonly commandHandlers;
3463
3627
  private readonly getContext;
3464
3628
  private readonly appBasePath;
3629
+ private readonly onRequestComplete?;
3630
+ private readonly rateLimiter;
3465
3631
  private readonly boundHandleMessage;
3466
3632
  private readonly boundHandlePopState;
3467
3633
  private iframeWindow;
3634
+ /**
3635
+ * Source windows we've already logged a successful handshake for. The bridge
3636
+ * polls `GET_CONTEXT` on an interval until it connects, so without this the
3637
+ * "Vibe App connected" line repeats on every poll (SSOT §13 F3).
3638
+ */
3639
+ private readonly handshakeLoggedWindows;
3468
3640
  private readonly kindHandlers;
3469
3641
  private dispatchCommand;
3470
- private readonly commandHandlers;
3642
+ /**
3643
+ * Commands: `REPORT_SUBROUTE` is handled internally (transport + traversal
3644
+ * guard, item `l`); navigation/UI side effects are injected by the consumer,
3645
+ * falling back to warn-and-ignore (DP4).
3646
+ */
3647
+ private buildCommandHandlers;
3648
+ /**
3649
+ * Requests: the built-in API map (hey-api), plus the injected/awaited side
3650
+ * effects with safe defaults — unwired `UPLOAD_FILE` rejects with a coded
3651
+ * error; unwired `INVALIDATE_CACHE` resolves to a no-op (DP4).
3652
+ */
3471
3653
  private initializeHandlers;
3472
- constructor({ trustedOrigins, getContext, appBasePath, clientConfig }: VibeAppHostOptions);
3654
+ constructor(opts: VibeAppHostOptions);
3473
3655
  /**
3474
3656
  * Starts listening for bridge messages and sets up browser back/forward sync.
3475
3657
  * Returns a cleanup function to call on unmount.
@@ -3494,4 +3676,4 @@ declare class VibeAppHost {
3494
3676
  private respond;
3495
3677
  }
3496
3678
 
3497
- export { type HostContext, type HostHandlers, type RequestHandlers, VibeAppHost, type VibeAppHostOptions };
3679
+ export { BridgeError, type ContextPayload, type HostContext, type HostHandlers, HttpBridgeError, type InvalidateCachePayload, type InvalidateResult, type NavigateAction, type NavigatePayload, type RequestCompleteInfo, type RequestHandlers, type SetSideNavPayload, type SwitchSubsidiaryPayload, type UploadPayload, type UploadProgress, type UploadResponse, type VibeApiClientOptions, VibeAppHost, type VibeAppHostOptions };