@vxil/sdk 0.5.2 → 0.5.3
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.ts +129 -4
- package/dist/index.js +35 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -169,6 +169,17 @@ export interface Delivery {
|
|
|
169
169
|
delivered_at?: string | null;
|
|
170
170
|
opened_at?: string | null;
|
|
171
171
|
clicked_at?: string | null;
|
|
172
|
+
/** The instant the first delivery attempt may fire (a `send_at` /
|
|
173
|
+
* `delay_seconds` schedule, a campaign quiet-hours deferral, a dead-letter
|
|
174
|
+
* replay); null = at `queued_at`. A `queued` row with no attempt 30 min past
|
|
175
|
+
* this is checked against its jobs run by a platform sweep and becomes
|
|
176
|
+
* `failed` / `last_error_code: 'never_attempted'` only when that run is gone
|
|
177
|
+
* or terminal (a run still waiting — e.g. behind the jobs concurrency cap —
|
|
178
|
+
* moves this instant forward instead) — send it again with the same
|
|
179
|
+
* Idempotency-Key. (The key itself is never on the row on the wire; a
|
|
180
|
+
* replay is the `x-vxil-idempotent-replay: true` header plus the identical
|
|
181
|
+
* `delivery_id` on the 202.) */
|
|
182
|
+
deliver_after?: string | null;
|
|
172
183
|
}
|
|
173
184
|
/** An ADDITIVE, non-fatal note on a 202 send — today only `mock_provider`
|
|
174
185
|
* (the send is recorded but no email leaves vxil). */
|
|
@@ -713,6 +724,62 @@ export interface AiJobHandle {
|
|
|
713
724
|
status: string;
|
|
714
725
|
resume_path: string;
|
|
715
726
|
}
|
|
727
|
+
/** The `data` of `job.generation.queued` (jobs.md §11): the run was accepted. */
|
|
728
|
+
export interface JobGenerationQueuedEventPayload {
|
|
729
|
+
run_id: string;
|
|
730
|
+
job_name: string;
|
|
731
|
+
/** the `202` handle's id from `POST /v1/ai/generate` (a tenant-authored
|
|
732
|
+
* `POST /v1/jobs/generation` run echoes its own `payload.generation_id`) */
|
|
733
|
+
generation_id: string | null;
|
|
734
|
+
/** your own `correlation_id` from that request (≤128 chars), or null */
|
|
735
|
+
correlation_id: string | null;
|
|
736
|
+
/** the completion mode the run was enqueued with (the ai lane enqueues `poll`) */
|
|
737
|
+
mode: 'poll' | 'webhook';
|
|
738
|
+
/** when the run is failed as expired if the provider never settles it */
|
|
739
|
+
expires_at: string;
|
|
740
|
+
}
|
|
741
|
+
/** The `data` of `job.generation.completed` / `job.generation.failed`. */
|
|
742
|
+
export interface JobGenerationSettledEventPayload {
|
|
743
|
+
run_id: string;
|
|
744
|
+
generation_id: string | null;
|
|
745
|
+
correlation_id: string | null;
|
|
746
|
+
/** `completed` on the healthy terminal, `failed` on the broken one */
|
|
747
|
+
status: 'completed' | 'failed';
|
|
748
|
+
/** the run's SETTLED error class (`provider_error`, `PollExhausted`, …);
|
|
749
|
+
* always null on `completed` */
|
|
750
|
+
error_class: string | null;
|
|
751
|
+
/** the provider hint alone (`Upstream said: gemini 400: …`); null on
|
|
752
|
+
* `completed` and whenever the failure carried none */
|
|
753
|
+
error_hint: string | null;
|
|
754
|
+
/** the failure-event vocabulary every `*.failed` carries (guide 12) */
|
|
755
|
+
level: 'info' | 'error';
|
|
756
|
+
state: 'ok' | 'broken';
|
|
757
|
+
}
|
|
758
|
+
/** The union a handler subscribed to `job.generation.` receives as `data`:
|
|
759
|
+
* narrow on `'status' in data` (queued carries none). */
|
|
760
|
+
export type JobGenerationEventPayload = JobGenerationQueuedEventPayload | JobGenerationSettledEventPayload;
|
|
761
|
+
/** The `data` of `job.succeeded` / `job.dead_lettered` (every run kind). The
|
|
762
|
+
* dead letter is the one that carries `level: 'error'` / `state: 'broken'`;
|
|
763
|
+
* `job.succeeded` carries neither. */
|
|
764
|
+
export interface JobRunEventPayload {
|
|
765
|
+
run_id: string;
|
|
766
|
+
job_name: string;
|
|
767
|
+
/** the attempt that settled the run (1-based) */
|
|
768
|
+
attempt: number;
|
|
769
|
+
level?: 'error';
|
|
770
|
+
state?: 'broken';
|
|
771
|
+
}
|
|
772
|
+
/** Event name → typed `data`, for the events that settle work you started.
|
|
773
|
+
* `VxilEventPayload<'job.generation.failed'>` names one; everything else on
|
|
774
|
+
* the catalog is `Record<string, unknown>` until typed here. */
|
|
775
|
+
export interface VxilEventPayloads {
|
|
776
|
+
'job.generation.queued': JobGenerationQueuedEventPayload;
|
|
777
|
+
'job.generation.completed': JobGenerationSettledEventPayload;
|
|
778
|
+
'job.generation.failed': JobGenerationSettledEventPayload;
|
|
779
|
+
'job.succeeded': JobRunEventPayload;
|
|
780
|
+
'job.dead_lettered': JobRunEventPayload;
|
|
781
|
+
}
|
|
782
|
+
export type VxilEventPayload<E extends string> = E extends keyof VxilEventPayloads ? VxilEventPayloads[E] : Record<string, unknown>;
|
|
716
783
|
/** A re-minted mid-stream connect token (GET /v1/ai/generations/{id}/token). */
|
|
717
784
|
export interface AiStreamToken {
|
|
718
785
|
generation_id: string;
|
|
@@ -1152,6 +1219,35 @@ export interface VxilSchemaShape {
|
|
|
1152
1219
|
Output: unknown;
|
|
1153
1220
|
}>;
|
|
1154
1221
|
}
|
|
1222
|
+
/** Per-call options of `vx.fn.<name>(input, opts)`. */
|
|
1223
|
+
export interface FnInvokeOptions {
|
|
1224
|
+
/** `true` ⇒ the ASYNC http lane (`x-vxil-async: 1`): the call resolves to the
|
|
1225
|
+
* `202 { run_id, status:'queued' }` ack at once; the run is delivered through
|
|
1226
|
+
* the jobs lane with exactly one attempt and records the function's real
|
|
1227
|
+
* status — poll `vx.jobs.run(run_id)` or listen for `job.*` events.
|
|
1228
|
+
* Server-mode only (an end-user client gets `403 server_only`). */
|
|
1229
|
+
async?: boolean;
|
|
1230
|
+
/** Sent as `Idempotency-Key`: rides the envelope as `idempotency_key` (the
|
|
1231
|
+
* function dedupes on it); on the async lane it is also the run's dedupe key,
|
|
1232
|
+
* so a repeat with the same key returns the SAME `run_id`. */
|
|
1233
|
+
idempotencyKey?: string;
|
|
1234
|
+
}
|
|
1235
|
+
/** The async lane's 202 ack. */
|
|
1236
|
+
export interface FnAsyncAck {
|
|
1237
|
+
run_id: string;
|
|
1238
|
+
status: 'queued';
|
|
1239
|
+
/** present (true) when the Idempotency-Key matched an earlier run */
|
|
1240
|
+
deduplicated?: boolean;
|
|
1241
|
+
}
|
|
1242
|
+
/** `vx.fn.<name>`: the function's Output by default, the 202 ack with `{ async: true }`. */
|
|
1243
|
+
export interface FnInvoker<I, O> {
|
|
1244
|
+
(input: I, opts?: FnInvokeOptions & {
|
|
1245
|
+
async?: false;
|
|
1246
|
+
}): Promise<O>;
|
|
1247
|
+
(input: I, opts: FnInvokeOptions & {
|
|
1248
|
+
async: true;
|
|
1249
|
+
}): Promise<FnAsyncAck>;
|
|
1250
|
+
}
|
|
1155
1251
|
/** feature client property → the feature key (in S['features']) that enables it.
|
|
1156
1252
|
* The renamed namespaces map to their real feature ids. Used by EnabledVxil to
|
|
1157
1253
|
* disable namespaces a tenant hasn't enabled. */
|
|
@@ -1540,8 +1636,12 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1540
1636
|
/** Typed function invoke (design §4.5). `vx.fn.<name>(payload)` POSTs to
|
|
1541
1637
|
* /v1/fn/<name>; the generated `VxilSchema` types Input/Output (Level-0
|
|
1542
1638
|
* opaque until a function declares a signature). A Proxy gives the
|
|
1543
|
-
* `vx.fn.<name>` accessor shape without enumerating names at runtime.
|
|
1544
|
-
|
|
1639
|
+
* `vx.fn.<name>` accessor shape without enumerating names at runtime.
|
|
1640
|
+
* `vx.fn.<name>(payload, { async: true })` takes the ASYNC lane
|
|
1641
|
+
* (`x-vxil-async: 1`): it resolves to the `202 { run_id, status:'queued' }`
|
|
1642
|
+
* ack at once and the run is delivered through the jobs lane — poll
|
|
1643
|
+
* `vx.jobs.run(run_id)` or subscribe to `job.*` events. */
|
|
1644
|
+
readonly fn: { [K in keyof S["functions"] & string]: FnInvoker<S["functions"][K]["Input"], S["functions"][K]["Output"]>; };
|
|
1545
1645
|
private call;
|
|
1546
1646
|
readonly users: {
|
|
1547
1647
|
upsert: (user: {
|
|
@@ -2148,7 +2248,13 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2148
2248
|
/** `opts.anonymous_token` is REQUIRED for a link that was requested with
|
|
2149
2249
|
* one (the guest claim above): the same guest session must present it,
|
|
2150
2250
|
* else `401 invalid_session`. `merged` is true only when the guest was
|
|
2151
|
-
* folded into an existing account (then `user_id` is that account)
|
|
2251
|
+
* folded into an existing account (then `user_id` is that account), and
|
|
2252
|
+
* `rekeyed` rides beside it: true ⇒ the guest's payments / cms / files
|
|
2253
|
+
* rows were already moved onto `user_id` when this returned; false ⇒
|
|
2254
|
+
* the move continues in the background — wait for the
|
|
2255
|
+
* `auth.user.rekeyed` event before reading owner-scoped rows as the
|
|
2256
|
+
* merged user. The event is written when the pass finishes and is
|
|
2257
|
+
* best-effort past this response (see `anonymous.link.verify`). */
|
|
2152
2258
|
verify: (token: string, opts?: {
|
|
2153
2259
|
anonymous_token?: string;
|
|
2154
2260
|
}) => Promise<{
|
|
@@ -2156,6 +2262,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2156
2262
|
session: AuthSession;
|
|
2157
2263
|
verified: boolean;
|
|
2158
2264
|
merged?: boolean;
|
|
2265
|
+
rekeyed?: boolean;
|
|
2159
2266
|
}>;
|
|
2160
2267
|
};
|
|
2161
2268
|
/** Email OTP sign-in: a 6-digit single-use code (distinct from magic-link).
|
|
@@ -2204,7 +2311,20 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2204
2311
|
* If the claimed email ALREADY has an account, the guest is MERGED
|
|
2205
2312
|
* into it: `user_id` is the existing account, `merged: true`, and a
|
|
2206
2313
|
* fresh `session.token` (same session, re-signed for the merged
|
|
2207
|
-
* identity — swap it client-side; other guest sessions are revoked).
|
|
2314
|
+
* identity — swap it client-side; other guest sessions are revoked).
|
|
2315
|
+
* `rekeyed` (present on a merge): true ⇒ the guest's payments / cms /
|
|
2316
|
+
* files rows were already moved onto `user_id` when this returned, so
|
|
2317
|
+
* the merged user's next request finds them; false ⇒ the move ran
|
|
2318
|
+
* past its in-request budget and continues in the background — wait
|
|
2319
|
+
* for the `auth.user.rekeyed { from_user_id, into_user_id, moved,
|
|
2320
|
+
* done, failed }` event before reading owner-scoped rows as `user_id`.
|
|
2321
|
+
* That event is written when the pass finishes and is BEST-EFFORT past
|
|
2322
|
+
* this response: the continuation is not re-run if the instance
|
|
2323
|
+
* serving it is evicted, and a failed event write is logged, not
|
|
2324
|
+
* retried — treat `rekeyed: false` with no event within a minute or
|
|
2325
|
+
* so as "poll your own rows", or re-run the idempotent
|
|
2326
|
+
* `cms.items.reKey` / `files.reKey` / `payments.reKeySubscriptions`
|
|
2327
|
+
* routes from the `auth.user.merged` backstop. */
|
|
2208
2328
|
verify: (input: {
|
|
2209
2329
|
token: string;
|
|
2210
2330
|
email: string;
|
|
@@ -2214,6 +2334,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2214
2334
|
email: string;
|
|
2215
2335
|
verified: boolean;
|
|
2216
2336
|
merged?: true;
|
|
2337
|
+
rekeyed?: boolean;
|
|
2217
2338
|
session?: {
|
|
2218
2339
|
token: string;
|
|
2219
2340
|
expires_at: string;
|
|
@@ -2391,6 +2512,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2391
2512
|
session: AuthSession;
|
|
2392
2513
|
verified: boolean;
|
|
2393
2514
|
linked: "created" | "existing" | "promoted" | "merged";
|
|
2515
|
+
/** present exactly when `linked === 'merged'`: true ⇒ the guest's rows
|
|
2516
|
+
* are already under `user_id`; false ⇒ wait for `auth.user.rekeyed`
|
|
2517
|
+
* (best-effort past this response — see `anonymous.link.verify`) */
|
|
2518
|
+
rekeyed?: boolean;
|
|
2394
2519
|
/** sessions taken over by auth config session.maxConcurrent (present once opted in) */
|
|
2395
2520
|
took_over?: string[];
|
|
2396
2521
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -228,7 +228,11 @@ export class Vxil {
|
|
|
228
228
|
/** Typed function invoke (design §4.5). `vx.fn.<name>(payload)` POSTs to
|
|
229
229
|
* /v1/fn/<name>; the generated `VxilSchema` types Input/Output (Level-0
|
|
230
230
|
* opaque until a function declares a signature). A Proxy gives the
|
|
231
|
-
* `vx.fn.<name>` accessor shape without enumerating names at runtime.
|
|
231
|
+
* `vx.fn.<name>` accessor shape without enumerating names at runtime.
|
|
232
|
+
* `vx.fn.<name>(payload, { async: true })` takes the ASYNC lane
|
|
233
|
+
* (`x-vxil-async: 1`): it resolves to the `202 { run_id, status:'queued' }`
|
|
234
|
+
* ack at once and the run is delivered through the jobs lane — poll
|
|
235
|
+
* `vx.jobs.run(run_id)` or subscribe to `job.*` events. */
|
|
232
236
|
fn = new Proxy({}, {
|
|
233
237
|
get: (_t, prop) => {
|
|
234
238
|
if (typeof prop !== 'string')
|
|
@@ -236,10 +240,14 @@ export class Vxil {
|
|
|
236
240
|
// /v1/fn/<name> returns the function's RAW Response (not a {data,meta}
|
|
237
241
|
// envelope), so bypass call()'s unwrap — read the body directly, like
|
|
238
242
|
// audit.export. Errors still surface as VxilError.
|
|
239
|
-
return async (payload) => {
|
|
243
|
+
return async (payload, opts) => {
|
|
240
244
|
const { response: res, text } = await this.transport.send(`${this.base}${this.path(`/v1/fn/${encodeURIComponent(prop)}`)}`, {
|
|
241
245
|
method: 'POST',
|
|
242
|
-
headers: {
|
|
246
|
+
headers: {
|
|
247
|
+
authorization: `Bearer ${this.key}`, ...this.authHeaders(), 'content-type': 'application/json',
|
|
248
|
+
...(opts?.async ? { 'x-vxil-async': '1' } : {}),
|
|
249
|
+
...(opts?.idempotencyKey ? { 'idempotency-key': opts.idempotencyKey } : {}),
|
|
250
|
+
},
|
|
243
251
|
body: JSON.stringify(payload ?? {}),
|
|
244
252
|
});
|
|
245
253
|
if (!res.ok) {
|
|
@@ -650,7 +658,13 @@ export class Vxil {
|
|
|
650
658
|
/** `opts.anonymous_token` is REQUIRED for a link that was requested with
|
|
651
659
|
* one (the guest claim above): the same guest session must present it,
|
|
652
660
|
* else `401 invalid_session`. `merged` is true only when the guest was
|
|
653
|
-
* folded into an existing account (then `user_id` is that account)
|
|
661
|
+
* folded into an existing account (then `user_id` is that account), and
|
|
662
|
+
* `rekeyed` rides beside it: true ⇒ the guest's payments / cms / files
|
|
663
|
+
* rows were already moved onto `user_id` when this returned; false ⇒
|
|
664
|
+
* the move continues in the background — wait for the
|
|
665
|
+
* `auth.user.rekeyed` event before reading owner-scoped rows as the
|
|
666
|
+
* merged user. The event is written when the pass finishes and is
|
|
667
|
+
* best-effort past this response (see `anonymous.link.verify`). */
|
|
654
668
|
verify: async (token, opts = {}) => (await this.call('POST', '/v1/auth/magic-link/verify', { token, ...(opts.anonymous_token !== undefined ? { anonymous_token: opts.anonymous_token } : {}) })).data,
|
|
655
669
|
},
|
|
656
670
|
/** Email OTP sign-in: a 6-digit single-use code (distinct from magic-link).
|
|
@@ -674,7 +688,20 @@ export class Vxil {
|
|
|
674
688
|
* If the claimed email ALREADY has an account, the guest is MERGED
|
|
675
689
|
* into it: `user_id` is the existing account, `merged: true`, and a
|
|
676
690
|
* fresh `session.token` (same session, re-signed for the merged
|
|
677
|
-
* identity — swap it client-side; other guest sessions are revoked).
|
|
691
|
+
* identity — swap it client-side; other guest sessions are revoked).
|
|
692
|
+
* `rekeyed` (present on a merge): true ⇒ the guest's payments / cms /
|
|
693
|
+
* files rows were already moved onto `user_id` when this returned, so
|
|
694
|
+
* the merged user's next request finds them; false ⇒ the move ran
|
|
695
|
+
* past its in-request budget and continues in the background — wait
|
|
696
|
+
* for the `auth.user.rekeyed { from_user_id, into_user_id, moved,
|
|
697
|
+
* done, failed }` event before reading owner-scoped rows as `user_id`.
|
|
698
|
+
* That event is written when the pass finishes and is BEST-EFFORT past
|
|
699
|
+
* this response: the continuation is not re-run if the instance
|
|
700
|
+
* serving it is evicted, and a failed event write is logged, not
|
|
701
|
+
* retried — treat `rekeyed: false` with no event within a minute or
|
|
702
|
+
* so as "poll your own rows", or re-run the idempotent
|
|
703
|
+
* `cms.items.reKey` / `files.reKey` / `payments.reKeySubscriptions`
|
|
704
|
+
* routes from the `auth.user.merged` backstop. */
|
|
678
705
|
verify: async (input) => (await this.call('POST', '/v1/auth/anonymous/link/verify', input)).data,
|
|
679
706
|
},
|
|
680
707
|
},
|
|
@@ -782,7 +809,9 @@ export class Vxil {
|
|
|
782
809
|
/** `anonymous_token`: the caller's CURRENT guest session bearer — the
|
|
783
810
|
* guest is PROMOTED to this identity (same user id, `linked: 'promoted'`)
|
|
784
811
|
* or, if the identity already has an account, MERGED into it
|
|
785
|
-
* (`linked: 'merged'`, `user_id` = that account
|
|
812
|
+
* (`linked: 'merged'`, `user_id` = that account, `rekeyed` says whether
|
|
813
|
+
* the guest's owner-scoped rows already moved — see
|
|
814
|
+
* `anonymous.link.verify`). */
|
|
786
815
|
input) => (await this.call('POST', `/v1/auth/oauth/${encodeURIComponent(provider)}/native`, input)).data,
|
|
787
816
|
},
|
|
788
817
|
};
|
package/package.json
CHANGED