@vxil/sdk 0.5.1 → 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 +181 -5
- package/dist/index.js +52 -7
- 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). */
|
|
@@ -399,6 +410,12 @@ export interface PaymentsWebhookEvent {
|
|
|
399
410
|
event_id: string;
|
|
400
411
|
provider: string;
|
|
401
412
|
provider_evt_id: string;
|
|
413
|
+
/** the NORMALISED charge id the receiver stamped at delivery time — the same
|
|
414
|
+
* string `payments.charge.*` events carry as `provider_charge_id`. `null`
|
|
415
|
+
* when the delivery carries no charge (subscription events, failures) or
|
|
416
|
+
* was received before the column existed. Filter on it with
|
|
417
|
+
* `webhookEvents.list({ provider_charge_id })`. */
|
|
418
|
+
provider_charge_id?: string | null;
|
|
402
419
|
event_type: string;
|
|
403
420
|
/** received | processed | error | sig_failed | parse_failed | reprocessed |
|
|
404
421
|
* ignored (a well-formed provider type we deliberately do not fold) |
|
|
@@ -640,6 +657,8 @@ export interface AiChatMessage {
|
|
|
640
657
|
/** A synchronous generation result (POST /v1/ai/generate without stream). */
|
|
641
658
|
export interface AiGeneration {
|
|
642
659
|
generation_id: string;
|
|
660
|
+
/** your own `correlation_id` echoed back (null when you sent none). */
|
|
661
|
+
correlation_id?: string | null;
|
|
643
662
|
text: string;
|
|
644
663
|
usage: AiUsage;
|
|
645
664
|
finish: string;
|
|
@@ -655,6 +674,8 @@ export interface AiGeneration {
|
|
|
655
674
|
/** A streamed generation handle: open the realtime channel for token frames. */
|
|
656
675
|
export interface AiStreamHandle {
|
|
657
676
|
generation_id: string;
|
|
677
|
+
/** your own `correlation_id` echoed back (null when you sent none). */
|
|
678
|
+
correlation_id?: string | null;
|
|
658
679
|
channel: string;
|
|
659
680
|
token?: string;
|
|
660
681
|
ttl_seconds?: number;
|
|
@@ -696,9 +717,69 @@ export interface AiTokenExpiringEvent {
|
|
|
696
717
|
export interface AiJobHandle {
|
|
697
718
|
generation_id: string;
|
|
698
719
|
run_id: string;
|
|
720
|
+
/** your own `correlation_id` echoed back (null when you sent none). The
|
|
721
|
+
* `job.generation.completed|failed` events carry BOTH `generation_id` and
|
|
722
|
+
* `correlation_id` next to `run_id`, so no run→record link is needed. */
|
|
723
|
+
correlation_id?: string | null;
|
|
699
724
|
status: string;
|
|
700
725
|
resume_path: string;
|
|
701
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>;
|
|
702
783
|
/** A re-minted mid-stream connect token (GET /v1/ai/generations/{id}/token). */
|
|
703
784
|
export interface AiStreamToken {
|
|
704
785
|
generation_id: string;
|
|
@@ -1138,6 +1219,35 @@ export interface VxilSchemaShape {
|
|
|
1138
1219
|
Output: unknown;
|
|
1139
1220
|
}>;
|
|
1140
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
|
+
}
|
|
1141
1251
|
/** feature client property → the feature key (in S['features']) that enables it.
|
|
1142
1252
|
* The renamed namespaces map to their real feature ids. Used by EnabledVxil to
|
|
1143
1253
|
* disable namespaces a tenant hasn't enabled. */
|
|
@@ -1526,8 +1636,12 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1526
1636
|
/** Typed function invoke (design §4.5). `vx.fn.<name>(payload)` POSTs to
|
|
1527
1637
|
* /v1/fn/<name>; the generated `VxilSchema` types Input/Output (Level-0
|
|
1528
1638
|
* opaque until a function declares a signature). A Proxy gives the
|
|
1529
|
-
* `vx.fn.<name>` accessor shape without enumerating names at runtime.
|
|
1530
|
-
|
|
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"]>; };
|
|
1531
1645
|
private call;
|
|
1532
1646
|
readonly users: {
|
|
1533
1647
|
upsert: (user: {
|
|
@@ -2110,20 +2224,45 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2110
2224
|
* `otp.testRecipients` (store-review / CI accounts): no mail is sent and
|
|
2111
2225
|
* the full sign-in link comes back instead. `captcha_token` is required
|
|
2112
2226
|
* when the tenant configured `security.captchaSecretRef`; `locale`
|
|
2113
|
-
* chooses the mail's language.
|
|
2227
|
+
* chooses the mail's language. `redirect_url` is an https page in your
|
|
2228
|
+
* app — or `http://localhost[:port]` / `http://127.0.0.1[:port]` for local
|
|
2229
|
+
* development (plain http on any other host is refused).
|
|
2230
|
+
*
|
|
2231
|
+
* GUEST CLAIM: pass the guest's session bearer as `anonymous_token` and
|
|
2232
|
+
* the link claims `email` FOR THAT GUEST — `verify` (which must present
|
|
2233
|
+
* the same `anonymous_token`) then keeps the guest's `user_id` (every
|
|
2234
|
+
* owner-scoped row survives) or, when the address already has an
|
|
2235
|
+
* account, merges the guest into it (`merged: true`, `user_id` = the
|
|
2236
|
+
* existing account; swap tokens). Needs `anonymous.enabled`; a non-guest
|
|
2237
|
+
* bearer is `409 not_anonymous`. */
|
|
2114
2238
|
request: (input: {
|
|
2115
2239
|
email: string;
|
|
2116
2240
|
redirect_url: string;
|
|
2117
2241
|
locale?: string;
|
|
2118
2242
|
captcha_token?: string;
|
|
2243
|
+
anonymous_token?: string;
|
|
2119
2244
|
}) => Promise<{
|
|
2120
2245
|
sent: true;
|
|
2121
2246
|
test_link?: string;
|
|
2122
2247
|
}>;
|
|
2123
|
-
|
|
2248
|
+
/** `opts.anonymous_token` is REQUIRED for a link that was requested with
|
|
2249
|
+
* one (the guest claim above): the same guest session must present it,
|
|
2250
|
+
* else `401 invalid_session`. `merged` is true only when the guest was
|
|
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`). */
|
|
2258
|
+
verify: (token: string, opts?: {
|
|
2259
|
+
anonymous_token?: string;
|
|
2260
|
+
}) => Promise<{
|
|
2124
2261
|
user_id: string;
|
|
2125
2262
|
session: AuthSession;
|
|
2126
2263
|
verified: boolean;
|
|
2264
|
+
merged?: boolean;
|
|
2265
|
+
rekeyed?: boolean;
|
|
2127
2266
|
}>;
|
|
2128
2267
|
};
|
|
2129
2268
|
/** Email OTP sign-in: a 6-digit single-use code (distinct from magic-link).
|
|
@@ -2172,7 +2311,20 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2172
2311
|
* If the claimed email ALREADY has an account, the guest is MERGED
|
|
2173
2312
|
* into it: `user_id` is the existing account, `merged: true`, and a
|
|
2174
2313
|
* fresh `session.token` (same session, re-signed for the merged
|
|
2175
|
-
* 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. */
|
|
2176
2328
|
verify: (input: {
|
|
2177
2329
|
token: string;
|
|
2178
2330
|
email: string;
|
|
@@ -2182,6 +2334,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2182
2334
|
email: string;
|
|
2183
2335
|
verified: boolean;
|
|
2184
2336
|
merged?: true;
|
|
2337
|
+
rekeyed?: boolean;
|
|
2185
2338
|
session?: {
|
|
2186
2339
|
token: string;
|
|
2187
2340
|
expires_at: string;
|
|
@@ -2359,6 +2512,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2359
2512
|
session: AuthSession;
|
|
2360
2513
|
verified: boolean;
|
|
2361
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;
|
|
2362
2519
|
/** sessions taken over by auth config session.maxConcurrent (present once opted in) */
|
|
2363
2520
|
took_over?: string[];
|
|
2364
2521
|
}>;
|
|
@@ -3757,6 +3914,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3757
3914
|
ttl?: "5m" | "1h";
|
|
3758
3915
|
};
|
|
3759
3916
|
user_id?: string;
|
|
3917
|
+
/** Your own opaque handle (1..128 chars) for this generation — stored on
|
|
3918
|
+
* the row, echoed on the answer and the replay read, and (job mode)
|
|
3919
|
+
* carried on the `job.generation.*` events with `generation_id`. */
|
|
3920
|
+
correlation_id?: string;
|
|
3760
3921
|
}) => Promise<AiGeneration>;
|
|
3761
3922
|
/** Streamed generation: returns the channel + connect token immediately; open
|
|
3762
3923
|
* the realtime channel for AiStreamFrame frames (token/title, then a
|
|
@@ -3786,6 +3947,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3786
3947
|
* {{prompt}}); overrides the built-in instruction (stream-only). */
|
|
3787
3948
|
title_template?: string;
|
|
3788
3949
|
user_id?: string;
|
|
3950
|
+
/** Your own opaque handle (1..128 chars) for this generation — stored on
|
|
3951
|
+
* the row, echoed on the answer and the replay read, and (job mode)
|
|
3952
|
+
* carried on the `job.generation.*` events with `generation_id`. */
|
|
3953
|
+
correlation_id?: string;
|
|
3789
3954
|
}) => Promise<AiStreamHandle>;
|
|
3790
3955
|
/** Job-routed async generation (long/vision/batch): 202 + a jobs run drives
|
|
3791
3956
|
* the provider call; the settled answer lands in the replay buffer at
|
|
@@ -3834,6 +3999,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3834
3999
|
ttl?: "5m" | "1h";
|
|
3835
4000
|
};
|
|
3836
4001
|
user_id?: string;
|
|
4002
|
+
/** Your own opaque handle (1..128 chars) for this generation — stored on
|
|
4003
|
+
* the row, echoed on the answer and the replay read, and (job mode)
|
|
4004
|
+
* carried on the `job.generation.*` events with `generation_id`. */
|
|
4005
|
+
correlation_id?: string;
|
|
3837
4006
|
}) => Promise<AiJobHandle>;
|
|
3838
4007
|
/** Re-mint a fresh connect token for a LIVE stream (a generation that
|
|
3839
4008
|
* outlives the ≤300s realtime token TTL); reconnect with `?since=<seq>`. */
|
|
@@ -4399,6 +4568,13 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
4399
4568
|
outcome?: "received" | "processed" | "error" | "sig_failed" | "parse_failed" | "reprocessed" | "ignored" | "unowned" | "rejected_environment";
|
|
4400
4569
|
/** the provider-reported environment axis (not the API key's label) */
|
|
4401
4570
|
environment?: "production" | "sandbox";
|
|
4571
|
+
/** exact match on the normalised charge id (= `payments.charge.*`'s
|
|
4572
|
+
* `provider_charge_id`) — the delivery that recorded a charge, and any
|
|
4573
|
+
* refund/dispute delivery against it, in ≤ a few rows; then ONE
|
|
4574
|
+
* detail `get` reads the verified payload. */
|
|
4575
|
+
provider_charge_id?: string;
|
|
4576
|
+
/** exact match on the provider's own event id. */
|
|
4577
|
+
provider_evt_id?: string;
|
|
4402
4578
|
since?: string;
|
|
4403
4579
|
cursor?: string;
|
|
4404
4580
|
limit?: number;
|
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) {
|
|
@@ -635,9 +643,29 @@ export class Vxil {
|
|
|
635
643
|
* `otp.testRecipients` (store-review / CI accounts): no mail is sent and
|
|
636
644
|
* the full sign-in link comes back instead. `captcha_token` is required
|
|
637
645
|
* when the tenant configured `security.captchaSecretRef`; `locale`
|
|
638
|
-
* chooses the mail's language.
|
|
646
|
+
* chooses the mail's language. `redirect_url` is an https page in your
|
|
647
|
+
* app — or `http://localhost[:port]` / `http://127.0.0.1[:port]` for local
|
|
648
|
+
* development (plain http on any other host is refused).
|
|
649
|
+
*
|
|
650
|
+
* GUEST CLAIM: pass the guest's session bearer as `anonymous_token` and
|
|
651
|
+
* the link claims `email` FOR THAT GUEST — `verify` (which must present
|
|
652
|
+
* the same `anonymous_token`) then keeps the guest's `user_id` (every
|
|
653
|
+
* owner-scoped row survives) or, when the address already has an
|
|
654
|
+
* account, merges the guest into it (`merged: true`, `user_id` = the
|
|
655
|
+
* existing account; swap tokens). Needs `anonymous.enabled`; a non-guest
|
|
656
|
+
* bearer is `409 not_anonymous`. */
|
|
639
657
|
request: async (input) => (await this.call('POST', '/v1/auth/magic-link/request', input)).data,
|
|
640
|
-
|
|
658
|
+
/** `opts.anonymous_token` is REQUIRED for a link that was requested with
|
|
659
|
+
* one (the guest claim above): the same guest session must present it,
|
|
660
|
+
* else `401 invalid_session`. `merged` is true only when the guest was
|
|
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`). */
|
|
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,
|
|
641
669
|
},
|
|
642
670
|
/** Email OTP sign-in: a 6-digit single-use code (distinct from magic-link).
|
|
643
671
|
* Server-side guessing budget (config otp.maxAttempts), single active code,
|
|
@@ -660,7 +688,20 @@ export class Vxil {
|
|
|
660
688
|
* If the claimed email ALREADY has an account, the guest is MERGED
|
|
661
689
|
* into it: `user_id` is the existing account, `merged: true`, and a
|
|
662
690
|
* fresh `session.token` (same session, re-signed for the merged
|
|
663
|
-
* 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. */
|
|
664
705
|
verify: async (input) => (await this.call('POST', '/v1/auth/anonymous/link/verify', input)).data,
|
|
665
706
|
},
|
|
666
707
|
},
|
|
@@ -768,7 +809,9 @@ export class Vxil {
|
|
|
768
809
|
/** `anonymous_token`: the caller's CURRENT guest session bearer — the
|
|
769
810
|
* guest is PROMOTED to this identity (same user id, `linked: 'promoted'`)
|
|
770
811
|
* or, if the identity already has an account, MERGED into it
|
|
771
|
-
* (`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`). */
|
|
772
815
|
input) => (await this.call('POST', `/v1/auth/oauth/${encodeURIComponent(provider)}/native`, input)).data,
|
|
773
816
|
},
|
|
774
817
|
};
|
|
@@ -1821,6 +1864,8 @@ export class Vxil {
|
|
|
1821
1864
|
event_type: q?.event_type || undefined,
|
|
1822
1865
|
outcome: q?.outcome || undefined,
|
|
1823
1866
|
environment: q?.environment || undefined,
|
|
1867
|
+
provider_charge_id: q?.provider_charge_id || undefined,
|
|
1868
|
+
provider_evt_id: q?.provider_evt_id || undefined,
|
|
1824
1869
|
since: q?.since || undefined,
|
|
1825
1870
|
cursor: q?.cursor || undefined,
|
|
1826
1871
|
limit: q?.limit || undefined,
|
package/package.json
CHANGED