@nominalso/vibe-bridge 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +61 -0
- package/README.md +255 -13
- package/dist/index.cjs +287 -12
- package/dist/index.d.cts +199 -10
- package/dist/index.d.ts +199 -10
- package/dist/index.js +287 -12
- package/docs/connect-lifecycle.md +56 -0
- package/docs/data-fetching.md +66 -0
- package/docs/file-upload.md +48 -0
- package/docs/getting-started.md +53 -0
- package/llms.txt +20 -0
- package/package.json +5 -12
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var version = "0.0
|
|
1
|
+
var version = "0.1.0";
|
|
2
2
|
|
|
3
3
|
type AccountingAccountDimensionValues = {
|
|
4
4
|
account_id: string;
|
|
@@ -3424,14 +3424,48 @@ interface RequestRegistry {
|
|
|
3424
3424
|
}
|
|
3425
3425
|
|
|
3426
3426
|
interface VibeAppBridgeOptions {
|
|
3427
|
+
/**
|
|
3428
|
+
* Origin of the Nominal app embedding this iframe (e.g.
|
|
3429
|
+
* `https://app.nominal.so` or `http://localhost:3000`). Must match the host
|
|
3430
|
+
* origin **exactly** — messages from any other origin are ignored, and the
|
|
3431
|
+
* bridge only `postMessage`s to this origin. Drive it from an env var so the
|
|
3432
|
+
* same code works locally and in production.
|
|
3433
|
+
*/
|
|
3427
3434
|
parentOrigin: string;
|
|
3435
|
+
/** Per-request timeout in milliseconds before a call rejects. Default `10000`. */
|
|
3428
3436
|
requestTimeout?: number;
|
|
3429
3437
|
}
|
|
3430
3438
|
interface UploadOptions {
|
|
3439
|
+
/** Domain entity the file attaches to, e.g. `'JOURNAL_ENTRY'`. */
|
|
3431
3440
|
entityType: string;
|
|
3441
|
+
/** Id of the entity the file attaches to, when applicable. */
|
|
3432
3442
|
entityId?: string | number;
|
|
3443
|
+
/** Called with incremental progress (`0`–`100`) while the upload streams. */
|
|
3433
3444
|
onProgress?: (p: RequestRegistry['UPLOAD_FILE']['progress']) => void;
|
|
3434
3445
|
}
|
|
3446
|
+
/**
|
|
3447
|
+
* Iframe-side bridge to the Nominal host. One instance per app: construct it,
|
|
3448
|
+
* `await connect()` once, then call data methods, `upload()`, and subroute
|
|
3449
|
+
* helpers. Tear down with `destroy()` on unmount.
|
|
3450
|
+
*
|
|
3451
|
+
* Every data method delegates to {@link VibeAppBridge.request}; the ~46 named
|
|
3452
|
+
* methods (e.g. {@link VibeAppBridge.getChartOfAccounts}) are the typed,
|
|
3453
|
+
* discoverable surface, and `request('<OP>', payload)` is the escape hatch for
|
|
3454
|
+
* any operation in {@link RequestRegistry}.
|
|
3455
|
+
*
|
|
3456
|
+
* @example
|
|
3457
|
+
* ```ts
|
|
3458
|
+
* import { VibeAppBridge, type ContextPayload } from '@nominalso/vibe-bridge'
|
|
3459
|
+
*
|
|
3460
|
+
* const bridge = new VibeAppBridge({ parentOrigin: import.meta.env.VITE_PARENT_ORIGIN })
|
|
3461
|
+
*
|
|
3462
|
+
* const ctx: ContextPayload = await bridge.connect()
|
|
3463
|
+
* const accounts = await bridge.getChartOfAccounts({ path: { subsidiary_id: ctx.subsidiaryId } })
|
|
3464
|
+
*
|
|
3465
|
+
* // cleanup
|
|
3466
|
+
* bridge.destroy()
|
|
3467
|
+
* ```
|
|
3468
|
+
*/
|
|
3435
3469
|
declare class VibeAppBridge {
|
|
3436
3470
|
private readonly parentOrigin;
|
|
3437
3471
|
private readonly requestTimeout;
|
|
@@ -3452,27 +3486,170 @@ declare class VibeAppBridge {
|
|
|
3452
3486
|
private readonly pushHandlers;
|
|
3453
3487
|
constructor({ parentOrigin, requestTimeout }: VibeAppBridgeOptions);
|
|
3454
3488
|
/**
|
|
3455
|
-
* Connects to the host and returns the tenant/user
|
|
3456
|
-
* Call once on app init
|
|
3489
|
+
* Connects to the host and returns the tenant/user {@link ContextPayload}.
|
|
3490
|
+
* **Call this once on app init and await it before any other method** — data
|
|
3491
|
+
* methods, `upload()`, and subroute reporting all require an established
|
|
3492
|
+
* connection. Concurrent calls return the same promise.
|
|
3457
3493
|
*
|
|
3458
3494
|
* Actively polls the host every 500ms until a response arrives, which
|
|
3459
3495
|
* handles the race condition where the host's initial context push arrives
|
|
3460
|
-
* before this listener is ready.
|
|
3496
|
+
* before this listener is ready. Rejects with `Bridge connect timed out`
|
|
3497
|
+
* after 10 seconds if no context arrives (usually a `parentOrigin` mismatch
|
|
3498
|
+
* or the host hasn't mounted).
|
|
3461
3499
|
*
|
|
3462
3500
|
* After connecting, if the context includes an initial subroute (e.g. from
|
|
3463
3501
|
* a deep link), the bridge navigates to it and starts auto-tracking
|
|
3464
3502
|
* internal navigation via the history API.
|
|
3503
|
+
*
|
|
3504
|
+
* @returns The tenant/user/subsidiary context for this app session.
|
|
3505
|
+
* @throws If no context is received within 10 seconds.
|
|
3506
|
+
* @example
|
|
3507
|
+
* ```ts
|
|
3508
|
+
* const ctx = await bridge.connect()
|
|
3509
|
+
* // ctx.tenant, ctx.subsidiaryId, ctx.subsidiaries, ctx.user, ctx.lastClosedPeriodSlug
|
|
3510
|
+
* ```
|
|
3465
3511
|
*/
|
|
3466
3512
|
connect(): Promise<ContextPayload>;
|
|
3513
|
+
/**
|
|
3514
|
+
* Fetch the flat chart of accounts for a subsidiary.
|
|
3515
|
+
*
|
|
3516
|
+
* @example
|
|
3517
|
+
* ```ts
|
|
3518
|
+
* const ctx = await bridge.connect()
|
|
3519
|
+
* const accounts = await bridge.getChartOfAccounts({
|
|
3520
|
+
* path: { subsidiary_id: ctx.subsidiaryId },
|
|
3521
|
+
* })
|
|
3522
|
+
* ```
|
|
3523
|
+
*/
|
|
3467
3524
|
getChartOfAccounts(payload: RequestRegistry['GET_CHART_OF_ACCOUNTS']['payload']): Promise<RequestRegistry['GET_CHART_OF_ACCOUNTS']['data']>;
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3525
|
+
/** Fetch the chart of accounts as a hierarchical tree for a subsidiary. */
|
|
3526
|
+
getCoaTree(payload: RequestRegistry['GET_COA_TREE']['payload']): Promise<RequestRegistry['GET_COA_TREE']['data']>;
|
|
3527
|
+
/** Fetch a simplified flat chart of accounts for a subsidiary. */
|
|
3528
|
+
getCoaFlatSimple(payload: RequestRegistry['GET_COA_FLAT_SIMPLE']['payload']): Promise<RequestRegistry['GET_COA_FLAT_SIMPLE']['data']>;
|
|
3529
|
+
/** Fetch the flat chart of accounts grouped by account group. */
|
|
3530
|
+
getCoaGrouped(payload: RequestRegistry['GET_COA_GROUPED']['payload']): Promise<RequestRegistry['GET_COA_GROUPED']['data']>;
|
|
3531
|
+
/** Fetch a single chart-of-accounts account by id within a subsidiary. */
|
|
3532
|
+
getCoaAccount(payload: RequestRegistry['GET_COA_ACCOUNT']['payload']): Promise<RequestRegistry['GET_COA_ACCOUNT']['data']>;
|
|
3533
|
+
/** List the currencies available to a subsidiary's chart of accounts. */
|
|
3534
|
+
getSubsidiaryAvailableCurrencies(payload: RequestRegistry['GET_SUBSIDIARY_AVAILABLE_CURRENCIES']['payload']): Promise<RequestRegistry['GET_SUBSIDIARY_AVAILABLE_CURRENCIES']['data']>;
|
|
3535
|
+
/** Fetch accounts at the tenant level. */
|
|
3536
|
+
getAccounts(payload: RequestRegistry['GET_ACCOUNTS']['payload']): Promise<RequestRegistry['GET_ACCOUNTS']['data']>;
|
|
3537
|
+
/** Fetch a single account by id. */
|
|
3538
|
+
getAccount(payload: RequestRegistry['GET_ACCOUNT']['payload']): Promise<RequestRegistry['GET_ACCOUNT']['data']>;
|
|
3539
|
+
/** Fetch currency conversion rates for a currency. */
|
|
3540
|
+
getConversionRates(payload: RequestRegistry['GET_CONVERSION_RATES']['payload']): Promise<RequestRegistry['GET_CONVERSION_RATES']['data']>;
|
|
3541
|
+
/** Fetch the effective consolidation exchange rate. */
|
|
3542
|
+
getEffectiveExchangeRate(payload: RequestRegistry['GET_EFFECTIVE_EXCHANGE_RATE']['payload']): Promise<RequestRegistry['GET_EFFECTIVE_EXCHANGE_RATE']['data']>;
|
|
3543
|
+
/** Fetch the exchange-rate period covering a given input date. */
|
|
3544
|
+
getExchangeRateByDate(payload: RequestRegistry['GET_EXCHANGE_RATE_BY_DATE']['payload']): Promise<RequestRegistry['GET_EXCHANGE_RATE_BY_DATE']['data']>;
|
|
3545
|
+
/** List accounting dimensions. */
|
|
3546
|
+
getDimensions(payload: RequestRegistry['GET_DIMENSIONS']['payload']): Promise<RequestRegistry['GET_DIMENSIONS']['data']>;
|
|
3547
|
+
/** List values for accounting dimensions. */
|
|
3548
|
+
getDimensionValues(payload: RequestRegistry['GET_DIMENSION_VALUES']['payload']): Promise<RequestRegistry['GET_DIMENSION_VALUES']['data']>;
|
|
3549
|
+
/** List dimension values in hierarchical form. */
|
|
3550
|
+
getDimensionValuesHierarchical(payload: RequestRegistry['GET_DIMENSION_VALUES_HIERARCHICAL']['payload']): Promise<RequestRegistry['GET_DIMENSION_VALUES_HIERARCHICAL']['data']>;
|
|
3551
|
+
/** List account assignments for a dimension/account pair. */
|
|
3552
|
+
getDimensionAccountAssignments(payload: RequestRegistry['GET_DIMENSION_ACCOUNT_ASSIGNMENTS']['payload']): Promise<RequestRegistry['GET_DIMENSION_ACCOUNT_ASSIGNMENTS']['data']>;
|
|
3553
|
+
/**
|
|
3554
|
+
* Fetch journal entries for a subsidiary, optionally filtered by date range.
|
|
3555
|
+
*
|
|
3556
|
+
* @example
|
|
3557
|
+
* ```ts
|
|
3558
|
+
* const entries = await bridge.getJournalEntries({
|
|
3559
|
+
* path: { subsidiary_id: ctx.subsidiaryId },
|
|
3560
|
+
* query: { from_date: '2026-01-01', to_date: '2026-03-31' },
|
|
3561
|
+
* })
|
|
3562
|
+
* ```
|
|
3563
|
+
*/
|
|
3471
3564
|
getJournalEntries(payload: RequestRegistry['GET_JOURNAL_ENTRIES']['payload']): Promise<RequestRegistry['GET_JOURNAL_ENTRIES']['data']>;
|
|
3565
|
+
/** Fetch journal entry lines for a subsidiary. */
|
|
3472
3566
|
getJournalLines(payload: RequestRegistry['GET_JOURNAL_LINES']['payload']): Promise<RequestRegistry['GET_JOURNAL_LINES']['data']>;
|
|
3567
|
+
/** Fetch a single journal entry by id within a subsidiary. */
|
|
3568
|
+
getJournalEntry(payload: RequestRegistry['GET_JOURNAL_ENTRY']['payload']): Promise<RequestRegistry['GET_JOURNAL_ENTRY']['data']>;
|
|
3569
|
+
/** List accounting period instances. */
|
|
3570
|
+
getPeriods(payload: RequestRegistry['GET_PERIODS']['payload']): Promise<RequestRegistry['GET_PERIODS']['data']>;
|
|
3571
|
+
/** Fetch a single period instance by id. */
|
|
3572
|
+
getPeriodInstance(payload: RequestRegistry['GET_PERIOD_INSTANCE']['payload']): Promise<RequestRegistry['GET_PERIOD_INSTANCE']['data']>;
|
|
3573
|
+
/** Fetch a period instance by its display slug (e.g. `"mar-2026"`). */
|
|
3574
|
+
getPeriodInstanceBySlug(payload: RequestRegistry['GET_PERIOD_INSTANCE_BY_SLUG']['payload']): Promise<RequestRegistry['GET_PERIOD_INSTANCE_BY_SLUG']['data']>;
|
|
3575
|
+
/** Fetch the close-progress breakdown for a period (by slug). */
|
|
3576
|
+
getPeriodProgressBreakdown(payload: RequestRegistry['GET_PERIOD_PROGRESS_BREAKDOWN']['payload']): Promise<RequestRegistry['GET_PERIOD_PROGRESS_BREAKDOWN']['data']>;
|
|
3577
|
+
/** List activity definitions. */
|
|
3578
|
+
getActivityDefinitions(payload: RequestRegistry['GET_ACTIVITY_DEFINITIONS']['payload']): Promise<RequestRegistry['GET_ACTIVITY_DEFINITIONS']['data']>;
|
|
3579
|
+
/** Fetch a single activity definition by id. */
|
|
3580
|
+
getActivityDefinition(payload: RequestRegistry['GET_ACTIVITY_DEFINITION']['payload']): Promise<RequestRegistry['GET_ACTIVITY_DEFINITION']['data']>;
|
|
3581
|
+
/** List activity instances. */
|
|
3582
|
+
getActivityInstances(payload: RequestRegistry['GET_ACTIVITY_INSTANCES']['payload']): Promise<RequestRegistry['GET_ACTIVITY_INSTANCES']['data']>;
|
|
3583
|
+
/** Fetch a single activity instance by id. */
|
|
3584
|
+
getActivityInstance(payload: RequestRegistry['GET_ACTIVITY_INSTANCE']['payload']): Promise<RequestRegistry['GET_ACTIVITY_INSTANCE']['data']>;
|
|
3585
|
+
/** Fetch an activity instance for a given period + activity definition. */
|
|
3586
|
+
getActivityInstanceByPeriod(payload: RequestRegistry['GET_ACTIVITY_INSTANCE_BY_PERIOD']['payload']): Promise<RequestRegistry['GET_ACTIVITY_INSTANCE_BY_PERIOD']['data']>;
|
|
3587
|
+
/** List task instances for an activity instance. */
|
|
3588
|
+
getActivityInstanceTasks(payload: RequestRegistry['GET_ACTIVITY_INSTANCE_TASKS']['payload']): Promise<RequestRegistry['GET_ACTIVITY_INSTANCE_TASKS']['data']>;
|
|
3589
|
+
/** List task instances for an activity in a given period. */
|
|
3590
|
+
getActivityPeriodTasks(payload: RequestRegistry['GET_ACTIVITY_PERIOD_TASKS']['payload']): Promise<RequestRegistry['GET_ACTIVITY_PERIOD_TASKS']['data']>;
|
|
3591
|
+
/** List task definitions. */
|
|
3592
|
+
getTaskDefinitions(payload: RequestRegistry['GET_TASK_DEFINITIONS']['payload']): Promise<RequestRegistry['GET_TASK_DEFINITIONS']['data']>;
|
|
3593
|
+
/** Fetch a single task definition by id. */
|
|
3594
|
+
getTaskDefinition(payload: RequestRegistry['GET_TASK_DEFINITION']['payload']): Promise<RequestRegistry['GET_TASK_DEFINITION']['data']>;
|
|
3595
|
+
/** Create a task definition. */
|
|
3596
|
+
createTaskDefinition(payload: RequestRegistry['CREATE_TASK_DEFINITION']['payload']): Promise<RequestRegistry['CREATE_TASK_DEFINITION']['data']>;
|
|
3597
|
+
/** Update a task definition by id. */
|
|
3598
|
+
updateTaskDefinition(payload: RequestRegistry['UPDATE_TASK_DEFINITION']['payload']): Promise<RequestRegistry['UPDATE_TASK_DEFINITION']['data']>;
|
|
3599
|
+
/** Query task definitions by filter. */
|
|
3600
|
+
getTaskDefinitionsByFilter(payload: RequestRegistry['GET_TASK_DEFINITIONS_BY_FILTER']['payload']): Promise<RequestRegistry['GET_TASK_DEFINITIONS_BY_FILTER']['data']>;
|
|
3601
|
+
/** List task instances. */
|
|
3602
|
+
getTaskInstances(payload: RequestRegistry['GET_TASK_INSTANCES']['payload']): Promise<RequestRegistry['GET_TASK_INSTANCES']['data']>;
|
|
3603
|
+
/** Fetch a single task instance by id. */
|
|
3604
|
+
getTaskInstance(payload: RequestRegistry['GET_TASK_INSTANCE']['payload']): Promise<RequestRegistry['GET_TASK_INSTANCE']['data']>;
|
|
3605
|
+
/**
|
|
3606
|
+
* Submit a Close-Management task output. This is the **only write path** back
|
|
3607
|
+
* into Nominal — Vibe Apps never write Nominal data directly.
|
|
3608
|
+
*
|
|
3609
|
+
* @example
|
|
3610
|
+
* ```ts
|
|
3611
|
+
* // `body` carries the task-specific output shape.
|
|
3612
|
+
* await bridge.postTaskOutput({
|
|
3613
|
+
* path: { task_instance_id: 'task-1' },
|
|
3614
|
+
* body: {},
|
|
3615
|
+
* })
|
|
3616
|
+
* ```
|
|
3617
|
+
*/
|
|
3618
|
+
postTaskOutput(payload: RequestRegistry['POST_TASK_OUTPUT']['payload']): Promise<RequestRegistry['POST_TASK_OUTPUT']['data']>;
|
|
3619
|
+
/** Query task instances by filter. */
|
|
3620
|
+
getTaskInstancesByFilter(payload: RequestRegistry['GET_TASK_INSTANCES_BY_FILTER']['payload']): Promise<RequestRegistry['GET_TASK_INSTANCES_BY_FILTER']['data']>;
|
|
3621
|
+
/** List audit-trail events. */
|
|
3622
|
+
getAuditEvents(payload: RequestRegistry['GET_AUDIT_EVENTS']['payload']): Promise<RequestRegistry['GET_AUDIT_EVENTS']['data']>;
|
|
3623
|
+
/** List audit-trail events for a specific entity. */
|
|
3624
|
+
getEntityAuditEvents(payload: RequestRegistry['GET_ENTITY_AUDIT_EVENTS']['payload']): Promise<RequestRegistry['GET_ENTITY_AUDIT_EVENTS']['data']>;
|
|
3625
|
+
/** List fiscal calendars for a subsidiary. */
|
|
3626
|
+
getFiscalCalendars(payload: RequestRegistry['GET_FISCAL_CALENDARS']['payload']): Promise<RequestRegistry['GET_FISCAL_CALENDARS']['data']>;
|
|
3627
|
+
/** Fetch a single fiscal calendar by id. */
|
|
3628
|
+
getFiscalCalendar(payload: RequestRegistry['GET_FISCAL_CALENDAR']['payload']): Promise<RequestRegistry['GET_FISCAL_CALENDAR']['data']>;
|
|
3629
|
+
/** List subsidiaries for the tenant. */
|
|
3630
|
+
getSubsidiaries(payload: RequestRegistry['GET_SUBSIDIARIES']['payload']): Promise<RequestRegistry['GET_SUBSIDIARIES']['data']>;
|
|
3631
|
+
/** Fetch a single subsidiary by id. */
|
|
3632
|
+
getSubsidiary(payload: RequestRegistry['GET_SUBSIDIARY']['payload']): Promise<RequestRegistry['GET_SUBSIDIARY']['data']>;
|
|
3633
|
+
/** List parent currencies for a subsidiary. */
|
|
3634
|
+
getSubsidiaryParentCurrencies(payload: RequestRegistry['GET_SUBSIDIARY_PARENT_CURRENCIES']['payload']): Promise<RequestRegistry['GET_SUBSIDIARY_PARENT_CURRENCIES']['data']>;
|
|
3635
|
+
/** List users for the tenant. */
|
|
3636
|
+
getTenantUsers(payload: RequestRegistry['GET_TENANT_USERS']['payload']): Promise<RequestRegistry['GET_TENANT_USERS']['data']>;
|
|
3473
3637
|
/**
|
|
3474
|
-
* Uploads a file through the host. Converts the File to an ArrayBuffer
|
|
3475
|
-
* before sending — sandboxed iframes cannot pass File handles across
|
|
3638
|
+
* Uploads a file through the host. Converts the `File` to an `ArrayBuffer`
|
|
3639
|
+
* before sending — sandboxed iframes cannot pass `File` handles across
|
|
3640
|
+
* windows. Resolves once Nominal has stored the file.
|
|
3641
|
+
*
|
|
3642
|
+
* @example
|
|
3643
|
+
* ```ts
|
|
3644
|
+
* const input = document.querySelector<HTMLInputElement>('#file')!
|
|
3645
|
+
* const file = input.files![0]
|
|
3646
|
+
* const result = await bridge.upload(file, {
|
|
3647
|
+
* entityType: 'JOURNAL_ENTRY',
|
|
3648
|
+
* entityId: '123',
|
|
3649
|
+
* onProgress: (p) => console.log(`${p.progress}%`),
|
|
3650
|
+
* })
|
|
3651
|
+
* // result.attachmentId, result.name
|
|
3652
|
+
* ```
|
|
3476
3653
|
*/
|
|
3477
3654
|
upload(file: File, options: UploadOptions): Promise<RequestRegistry['UPLOAD_FILE']['data']>;
|
|
3478
3655
|
/**
|
|
@@ -3503,10 +3680,22 @@ declare class VibeAppBridge {
|
|
|
3503
3680
|
private withSubrouteSuppressed;
|
|
3504
3681
|
private reportCurrentSubroute;
|
|
3505
3682
|
private sendCommand;
|
|
3683
|
+
/**
|
|
3684
|
+
* Low-level typed request for **any** operation in {@link RequestRegistry}.
|
|
3685
|
+
* The named methods (e.g. {@link VibeAppBridge.getChartOfAccounts}) wrap this;
|
|
3686
|
+
* use it directly for operations without a named method, or to pass an
|
|
3687
|
+
* `onProgress` callback. `type` autocompletes to every operation name and
|
|
3688
|
+
* narrows `payload`/return to that operation's types.
|
|
3689
|
+
*
|
|
3690
|
+
* @example
|
|
3691
|
+
* ```ts
|
|
3692
|
+
* const accounts = await bridge.request('GET_ACCOUNTS', { query: { account_ids: ['acc-1'] } })
|
|
3693
|
+
* ```
|
|
3694
|
+
*/
|
|
3506
3695
|
request<K extends keyof RequestRegistry>(type: K, payload: RequestRegistry[K]['payload'], onProgress?: (payload: unknown) => void): Promise<RequestRegistry[K]['data']>;
|
|
3507
3696
|
private handleMessage;
|
|
3508
3697
|
private handleResponse;
|
|
3509
3698
|
private handleProgress;
|
|
3510
3699
|
}
|
|
3511
3700
|
|
|
3512
|
-
export { version as BRIDGE_VERSION, VibeAppBridge, type VibeAppBridgeOptions };
|
|
3701
|
+
export { version as BRIDGE_VERSION, type BridgeSubsidiary, type ContextPayload, type RequestRegistry, type UploadOptions, type UploadProgress, type UploadResponse, VibeAppBridge, type VibeAppBridgeOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// package.json
|
|
2
|
-
var version = "0.0
|
|
2
|
+
var version = "0.1.0";
|
|
3
3
|
|
|
4
4
|
// ../protocol-types/dist/index.js
|
|
5
5
|
var PROTOCOL_ID = "nominal-vibe-bridge";
|
|
@@ -62,16 +62,28 @@ var VibeAppBridge = class {
|
|
|
62
62
|
handler(msg.payload);
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
-
* Connects to the host and returns the tenant/user
|
|
66
|
-
* Call once on app init
|
|
65
|
+
* Connects to the host and returns the tenant/user {@link ContextPayload}.
|
|
66
|
+
* **Call this once on app init and await it before any other method** — data
|
|
67
|
+
* methods, `upload()`, and subroute reporting all require an established
|
|
68
|
+
* connection. Concurrent calls return the same promise.
|
|
67
69
|
*
|
|
68
70
|
* Actively polls the host every 500ms until a response arrives, which
|
|
69
71
|
* handles the race condition where the host's initial context push arrives
|
|
70
|
-
* before this listener is ready.
|
|
72
|
+
* before this listener is ready. Rejects with `Bridge connect timed out`
|
|
73
|
+
* after 10 seconds if no context arrives (usually a `parentOrigin` mismatch
|
|
74
|
+
* or the host hasn't mounted).
|
|
71
75
|
*
|
|
72
76
|
* After connecting, if the context includes an initial subroute (e.g. from
|
|
73
77
|
* a deep link), the bridge navigates to it and starts auto-tracking
|
|
74
78
|
* internal navigation via the history API.
|
|
79
|
+
*
|
|
80
|
+
* @returns The tenant/user/subsidiary context for this app session.
|
|
81
|
+
* @throws If no context is received within 10 seconds.
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const ctx = await bridge.connect()
|
|
85
|
+
* // ctx.tenant, ctx.subsidiaryId, ctx.subsidiaries, ctx.user, ctx.lastClosedPeriodSlug
|
|
86
|
+
* ```
|
|
75
87
|
*/
|
|
76
88
|
connect() {
|
|
77
89
|
if (this.context) return Promise.resolve(this.context);
|
|
@@ -119,27 +131,278 @@ var VibeAppBridge = class {
|
|
|
119
131
|
});
|
|
120
132
|
return this.connectPromise;
|
|
121
133
|
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Operations — Accounting: chart of accounts
|
|
136
|
+
//
|
|
137
|
+
// Each method is a typed wrapper over `request('<OP>', payload)`. The payload
|
|
138
|
+
// and return types come straight from the Nominal API (`RequestRegistry`).
|
|
139
|
+
// For any operation without a named method, call `request('<OP>', payload)`.
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
/**
|
|
142
|
+
* Fetch the flat chart of accounts for a subsidiary.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* const ctx = await bridge.connect()
|
|
147
|
+
* const accounts = await bridge.getChartOfAccounts({
|
|
148
|
+
* path: { subsidiary_id: ctx.subsidiaryId },
|
|
149
|
+
* })
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
122
152
|
getChartOfAccounts(payload) {
|
|
123
153
|
return this.request("GET_CHART_OF_ACCOUNTS", payload);
|
|
124
154
|
}
|
|
125
|
-
|
|
126
|
-
|
|
155
|
+
/** Fetch the chart of accounts as a hierarchical tree for a subsidiary. */
|
|
156
|
+
getCoaTree(payload) {
|
|
157
|
+
return this.request("GET_COA_TREE", payload);
|
|
127
158
|
}
|
|
128
|
-
|
|
129
|
-
|
|
159
|
+
/** Fetch a simplified flat chart of accounts for a subsidiary. */
|
|
160
|
+
getCoaFlatSimple(payload) {
|
|
161
|
+
return this.request("GET_COA_FLAT_SIMPLE", payload);
|
|
130
162
|
}
|
|
131
|
-
|
|
132
|
-
|
|
163
|
+
/** Fetch the flat chart of accounts grouped by account group. */
|
|
164
|
+
getCoaGrouped(payload) {
|
|
165
|
+
return this.request("GET_COA_GROUPED", payload);
|
|
166
|
+
}
|
|
167
|
+
/** Fetch a single chart-of-accounts account by id within a subsidiary. */
|
|
168
|
+
getCoaAccount(payload) {
|
|
169
|
+
return this.request("GET_COA_ACCOUNT", payload);
|
|
170
|
+
}
|
|
171
|
+
/** List the currencies available to a subsidiary's chart of accounts. */
|
|
172
|
+
getSubsidiaryAvailableCurrencies(payload) {
|
|
173
|
+
return this.request("GET_SUBSIDIARY_AVAILABLE_CURRENCIES", payload);
|
|
174
|
+
}
|
|
175
|
+
/** Fetch accounts at the tenant level. */
|
|
176
|
+
getAccounts(payload) {
|
|
177
|
+
return this.request("GET_ACCOUNTS", payload);
|
|
178
|
+
}
|
|
179
|
+
/** Fetch a single account by id. */
|
|
180
|
+
getAccount(payload) {
|
|
181
|
+
return this.request("GET_ACCOUNT", payload);
|
|
182
|
+
}
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Operations — Accounting: exchange rates
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
/** Fetch currency conversion rates for a currency. */
|
|
187
|
+
getConversionRates(payload) {
|
|
188
|
+
return this.request("GET_CONVERSION_RATES", payload);
|
|
189
|
+
}
|
|
190
|
+
/** Fetch the effective consolidation exchange rate. */
|
|
191
|
+
getEffectiveExchangeRate(payload) {
|
|
192
|
+
return this.request("GET_EFFECTIVE_EXCHANGE_RATE", payload);
|
|
193
|
+
}
|
|
194
|
+
/** Fetch the exchange-rate period covering a given input date. */
|
|
195
|
+
getExchangeRateByDate(payload) {
|
|
196
|
+
return this.request("GET_EXCHANGE_RATE_BY_DATE", payload);
|
|
133
197
|
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Operations — Accounting: dimensions
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
/** List accounting dimensions. */
|
|
202
|
+
getDimensions(payload) {
|
|
203
|
+
return this.request("GET_DIMENSIONS", payload);
|
|
204
|
+
}
|
|
205
|
+
/** List values for accounting dimensions. */
|
|
206
|
+
getDimensionValues(payload) {
|
|
207
|
+
return this.request("GET_DIMENSION_VALUES", payload);
|
|
208
|
+
}
|
|
209
|
+
/** List dimension values in hierarchical form. */
|
|
210
|
+
getDimensionValuesHierarchical(payload) {
|
|
211
|
+
return this.request("GET_DIMENSION_VALUES_HIERARCHICAL", payload);
|
|
212
|
+
}
|
|
213
|
+
/** List account assignments for a dimension/account pair. */
|
|
214
|
+
getDimensionAccountAssignments(payload) {
|
|
215
|
+
return this.request("GET_DIMENSION_ACCOUNT_ASSIGNMENTS", payload);
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Operations — Accounting: journal entries
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
/**
|
|
221
|
+
* Fetch journal entries for a subsidiary, optionally filtered by date range.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```ts
|
|
225
|
+
* const entries = await bridge.getJournalEntries({
|
|
226
|
+
* path: { subsidiary_id: ctx.subsidiaryId },
|
|
227
|
+
* query: { from_date: '2026-01-01', to_date: '2026-03-31' },
|
|
228
|
+
* })
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
134
231
|
getJournalEntries(payload) {
|
|
135
232
|
return this.request("GET_JOURNAL_ENTRIES", payload);
|
|
136
233
|
}
|
|
234
|
+
/** Fetch journal entry lines for a subsidiary. */
|
|
137
235
|
getJournalLines(payload) {
|
|
138
236
|
return this.request("GET_JOURNAL_LINES", payload);
|
|
139
237
|
}
|
|
238
|
+
/** Fetch a single journal entry by id within a subsidiary. */
|
|
239
|
+
getJournalEntry(payload) {
|
|
240
|
+
return this.request("GET_JOURNAL_ENTRY", payload);
|
|
241
|
+
}
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Operations — Activity: period instances
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
/** List accounting period instances. */
|
|
246
|
+
getPeriods(payload) {
|
|
247
|
+
return this.request("GET_PERIODS", payload);
|
|
248
|
+
}
|
|
249
|
+
/** Fetch a single period instance by id. */
|
|
250
|
+
getPeriodInstance(payload) {
|
|
251
|
+
return this.request("GET_PERIOD_INSTANCE", payload);
|
|
252
|
+
}
|
|
253
|
+
/** Fetch a period instance by its display slug (e.g. `"mar-2026"`). */
|
|
254
|
+
getPeriodInstanceBySlug(payload) {
|
|
255
|
+
return this.request("GET_PERIOD_INSTANCE_BY_SLUG", payload);
|
|
256
|
+
}
|
|
257
|
+
/** Fetch the close-progress breakdown for a period (by slug). */
|
|
258
|
+
getPeriodProgressBreakdown(payload) {
|
|
259
|
+
return this.request("GET_PERIOD_PROGRESS_BREAKDOWN", payload);
|
|
260
|
+
}
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// Operations — Activity: activity definitions
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
/** List activity definitions. */
|
|
265
|
+
getActivityDefinitions(payload) {
|
|
266
|
+
return this.request("GET_ACTIVITY_DEFINITIONS", payload);
|
|
267
|
+
}
|
|
268
|
+
/** Fetch a single activity definition by id. */
|
|
269
|
+
getActivityDefinition(payload) {
|
|
270
|
+
return this.request("GET_ACTIVITY_DEFINITION", payload);
|
|
271
|
+
}
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Operations — Activity: activity instances
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
/** List activity instances. */
|
|
276
|
+
getActivityInstances(payload) {
|
|
277
|
+
return this.request("GET_ACTIVITY_INSTANCES", payload);
|
|
278
|
+
}
|
|
279
|
+
/** Fetch a single activity instance by id. */
|
|
280
|
+
getActivityInstance(payload) {
|
|
281
|
+
return this.request("GET_ACTIVITY_INSTANCE", payload);
|
|
282
|
+
}
|
|
283
|
+
/** Fetch an activity instance for a given period + activity definition. */
|
|
284
|
+
getActivityInstanceByPeriod(payload) {
|
|
285
|
+
return this.request("GET_ACTIVITY_INSTANCE_BY_PERIOD", payload);
|
|
286
|
+
}
|
|
287
|
+
/** List task instances for an activity instance. */
|
|
288
|
+
getActivityInstanceTasks(payload) {
|
|
289
|
+
return this.request("GET_ACTIVITY_INSTANCE_TASKS", payload);
|
|
290
|
+
}
|
|
291
|
+
/** List task instances for an activity in a given period. */
|
|
292
|
+
getActivityPeriodTasks(payload) {
|
|
293
|
+
return this.request("GET_ACTIVITY_PERIOD_TASKS", payload);
|
|
294
|
+
}
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
// Operations — Activity: task definitions
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
/** List task definitions. */
|
|
299
|
+
getTaskDefinitions(payload) {
|
|
300
|
+
return this.request("GET_TASK_DEFINITIONS", payload);
|
|
301
|
+
}
|
|
302
|
+
/** Fetch a single task definition by id. */
|
|
303
|
+
getTaskDefinition(payload) {
|
|
304
|
+
return this.request("GET_TASK_DEFINITION", payload);
|
|
305
|
+
}
|
|
306
|
+
/** Create a task definition. */
|
|
307
|
+
createTaskDefinition(payload) {
|
|
308
|
+
return this.request("CREATE_TASK_DEFINITION", payload);
|
|
309
|
+
}
|
|
310
|
+
/** Update a task definition by id. */
|
|
311
|
+
updateTaskDefinition(payload) {
|
|
312
|
+
return this.request("UPDATE_TASK_DEFINITION", payload);
|
|
313
|
+
}
|
|
314
|
+
/** Query task definitions by filter. */
|
|
315
|
+
getTaskDefinitionsByFilter(payload) {
|
|
316
|
+
return this.request("GET_TASK_DEFINITIONS_BY_FILTER", payload);
|
|
317
|
+
}
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// Operations — Activity: task instances
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
/** List task instances. */
|
|
322
|
+
getTaskInstances(payload) {
|
|
323
|
+
return this.request("GET_TASK_INSTANCES", payload);
|
|
324
|
+
}
|
|
325
|
+
/** Fetch a single task instance by id. */
|
|
326
|
+
getTaskInstance(payload) {
|
|
327
|
+
return this.request("GET_TASK_INSTANCE", payload);
|
|
328
|
+
}
|
|
140
329
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
330
|
+
* Submit a Close-Management task output. This is the **only write path** back
|
|
331
|
+
* into Nominal — Vibe Apps never write Nominal data directly.
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```ts
|
|
335
|
+
* // `body` carries the task-specific output shape.
|
|
336
|
+
* await bridge.postTaskOutput({
|
|
337
|
+
* path: { task_instance_id: 'task-1' },
|
|
338
|
+
* body: {},
|
|
339
|
+
* })
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
postTaskOutput(payload) {
|
|
343
|
+
return this.request("POST_TASK_OUTPUT", payload);
|
|
344
|
+
}
|
|
345
|
+
/** Query task instances by filter. */
|
|
346
|
+
getTaskInstancesByFilter(payload) {
|
|
347
|
+
return this.request("GET_TASK_INSTANCES_BY_FILTER", payload);
|
|
348
|
+
}
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// Operations — Audit trail
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
/** List audit-trail events. */
|
|
353
|
+
getAuditEvents(payload) {
|
|
354
|
+
return this.request("GET_AUDIT_EVENTS", payload);
|
|
355
|
+
}
|
|
356
|
+
/** List audit-trail events for a specific entity. */
|
|
357
|
+
getEntityAuditEvents(payload) {
|
|
358
|
+
return this.request("GET_ENTITY_AUDIT_EVENTS", payload);
|
|
359
|
+
}
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
// Operations — Period manager: fiscal calendars
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
/** List fiscal calendars for a subsidiary. */
|
|
364
|
+
getFiscalCalendars(payload) {
|
|
365
|
+
return this.request("GET_FISCAL_CALENDARS", payload);
|
|
366
|
+
}
|
|
367
|
+
/** Fetch a single fiscal calendar by id. */
|
|
368
|
+
getFiscalCalendar(payload) {
|
|
369
|
+
return this.request("GET_FISCAL_CALENDAR", payload);
|
|
370
|
+
}
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
// Operations — Tenancy
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
/** List subsidiaries for the tenant. */
|
|
375
|
+
getSubsidiaries(payload) {
|
|
376
|
+
return this.request("GET_SUBSIDIARIES", payload);
|
|
377
|
+
}
|
|
378
|
+
/** Fetch a single subsidiary by id. */
|
|
379
|
+
getSubsidiary(payload) {
|
|
380
|
+
return this.request("GET_SUBSIDIARY", payload);
|
|
381
|
+
}
|
|
382
|
+
/** List parent currencies for a subsidiary. */
|
|
383
|
+
getSubsidiaryParentCurrencies(payload) {
|
|
384
|
+
return this.request("GET_SUBSIDIARY_PARENT_CURRENCIES", payload);
|
|
385
|
+
}
|
|
386
|
+
/** List users for the tenant. */
|
|
387
|
+
getTenantUsers(payload) {
|
|
388
|
+
return this.request("GET_TENANT_USERS", payload);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Uploads a file through the host. Converts the `File` to an `ArrayBuffer`
|
|
392
|
+
* before sending — sandboxed iframes cannot pass `File` handles across
|
|
393
|
+
* windows. Resolves once Nominal has stored the file.
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```ts
|
|
397
|
+
* const input = document.querySelector<HTMLInputElement>('#file')!
|
|
398
|
+
* const file = input.files![0]
|
|
399
|
+
* const result = await bridge.upload(file, {
|
|
400
|
+
* entityType: 'JOURNAL_ENTRY',
|
|
401
|
+
* entityId: '123',
|
|
402
|
+
* onProgress: (p) => console.log(`${p.progress}%`),
|
|
403
|
+
* })
|
|
404
|
+
* // result.attachmentId, result.name
|
|
405
|
+
* ```
|
|
143
406
|
*/
|
|
144
407
|
async upload(file, options) {
|
|
145
408
|
const buffer = await file.arrayBuffer();
|
|
@@ -253,6 +516,18 @@ var VibeAppBridge = class {
|
|
|
253
516
|
this.parentOrigin
|
|
254
517
|
);
|
|
255
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Low-level typed request for **any** operation in {@link RequestRegistry}.
|
|
521
|
+
* The named methods (e.g. {@link VibeAppBridge.getChartOfAccounts}) wrap this;
|
|
522
|
+
* use it directly for operations without a named method, or to pass an
|
|
523
|
+
* `onProgress` callback. `type` autocompletes to every operation name and
|
|
524
|
+
* narrows `payload`/return to that operation's types.
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```ts
|
|
528
|
+
* const accounts = await bridge.request('GET_ACCOUNTS', { query: { account_ids: ['acc-1'] } })
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
256
531
|
request(type, payload, onProgress) {
|
|
257
532
|
return new Promise((resolve, reject) => {
|
|
258
533
|
const requestId = crypto.randomUUID();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Connection lifecycle
|
|
2
|
+
|
|
3
|
+
## Always connect first
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const ctx = await bridge.connect()
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Call `connect()` **once** on app init and `await` it before any data method, `upload()`, or subroute call. Concurrent calls return the same promise, so it's safe to call from multiple init paths.
|
|
10
|
+
|
|
11
|
+
## What `connect()` returns
|
|
12
|
+
|
|
13
|
+
A `ContextPayload`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
interface ContextPayload {
|
|
17
|
+
tenant: string
|
|
18
|
+
subsidiaryId: number
|
|
19
|
+
subsidiaries: { id: number; displayName: string }[]
|
|
20
|
+
user: { id: string; displayName: string }
|
|
21
|
+
subroute?: string // initial deep-link path, if any
|
|
22
|
+
lastClosedPeriodSlug?: string // e.g. "mar-2026"
|
|
23
|
+
hostVersion: string // version of @nominalso/vibe-host running in the host
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
The host pushes context to the iframe on load, but the iframe's listener may not be ready when that push arrives. `connect()` solves the race:
|
|
30
|
+
|
|
31
|
+
1. The bridge starts a 500 ms polling loop, sending `GET_CONTEXT` requests to the host.
|
|
32
|
+
2. The host responds as soon as its listener is mounted, **or** proactively pushes `CONTEXT_PUSH` once it has the iframe's window.
|
|
33
|
+
3. Whichever arrives first resolves `connect()`. Polling stops.
|
|
34
|
+
4. The bridge begins auto-tracking SPA navigation (see below).
|
|
35
|
+
5. If nothing arrives within **10 seconds**, `connect()` rejects with `Bridge connect timed out`.
|
|
36
|
+
|
|
37
|
+
A timeout almost always means a `parentOrigin` mismatch or the host never mounted.
|
|
38
|
+
|
|
39
|
+
## Initial deep link
|
|
40
|
+
|
|
41
|
+
If `ctx.subroute` is present (e.g. the user opened a bookmarked deep link), the bridge navigates to it via `history.replaceState` right after connecting, so your router lands on the correct view.
|
|
42
|
+
|
|
43
|
+
## Subroute sync (usually automatic)
|
|
44
|
+
|
|
45
|
+
After connecting, the bridge monkey-patches `history.pushState`/`replaceState` to auto-report internal navigation to the host, keeping the Nominal browser URL in sync. For standard SPA routers you need no code.
|
|
46
|
+
|
|
47
|
+
- **Non-standard / hash routers:** call `bridge.reportSubroute('/assets/123', { replace })` manually.
|
|
48
|
+
- **React to host back/forward:** `const off = bridge.onSubrouteRequest((subroute) => router.navigate(subroute))`. Returns an unsubscribe function. Without one, the SDK uses `history.pushState` + a `popstate` event, which most routers pick up.
|
|
49
|
+
|
|
50
|
+
## Teardown
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
bridge.destroy()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Removes listeners, rejects pending requests with `Bridge destroyed`, and restores the original history methods. Call it on unmount.
|