@vxil/sdk 0.18.0 → 0.19.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +267 -18
  2. package/dist/index.js +140 -14
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1646,9 +1646,11 @@ export type VxilEventPayload<E extends string> = E extends keyof VxilEventPayloa
1646
1646
  export type FunctionEnvelopeTrigger = 'http' | 'cms-hook' | 'auth-hook' | 'queue' | 'cron' | 'webhook';
1647
1647
  /** The audiences a function's scoped callback tokens are minted for — one per
1648
1648
  * feature its declared scopes reach. `control-plane` carries `users:*` /
1649
- * `usage:read` only. There is deliberately no `functions` audience: a function
1650
- * cannot call another function. */
1651
- export type FunctionCallbackAudience = 'cms' | 'payments' | 'notifications' | 'comments' | 'files' | 'ai' | 'rag' | 'vector-search' | 'activity-feed' | 'orgs' | 'auth' | 'jobs' | 'realtime' | 'rate-limits' | 'control-plane';
1649
+ * `usage:read` / `audit:read` only; `webhooks` carries `webhooks:read` only (read the inbound
1650
+ * sources and received events — e.g. `GET /v1/webhooks/events?after=`). There
1651
+ * is deliberately no `functions` audience: a function cannot call another
1652
+ * function. */
1653
+ export type FunctionCallbackAudience = 'cms' | 'payments' | 'notifications' | 'comments' | 'files' | 'ai' | 'rag' | 'vector-search' | 'activity-feed' | 'orgs' | 'auth' | 'jobs' | 'realtime' | 'rate-limits' | 'control-plane' | 'webhooks';
1652
1654
  /** One short-lived scoped token per audience your scopes imply
1653
1655
  * (`env.scoped_jwts.cms`, `env.scoped_jwts['control-plane']`); an audience your
1654
1656
  * scopes do not reach is absent. Send it as `Authorization: Bearer …` to
@@ -1710,6 +1712,43 @@ export interface WebhookTriggerPayload<D = Record<string, unknown>> {
1710
1712
  truncated: true;
1711
1713
  } | null;
1712
1714
  }
1715
+ /** The verification parameters of an inbound `hmac` source (`webhooks.sources.create`). */
1716
+ export interface WebhookHmacVerify {
1717
+ /** the header carrying the signature (case-insensitive) */
1718
+ header: string;
1719
+ algorithm?: 'sha256' | 'sha1';
1720
+ encoding?: 'hex' | 'base64';
1721
+ /** stripped before comparing (e.g. `sha256=`); required on the request when set */
1722
+ prefix?: string;
1723
+ /** a header carrying the signing timestamp (unix seconds or ISO 8601): the
1724
+ * signed message becomes `<timestamp><timestamp_separator><body>` */
1725
+ timestamp_header?: string;
1726
+ /** default '.'; '' to concatenate */
1727
+ timestamp_separator?: string;
1728
+ /** default 300, 1..3600 */
1729
+ tolerance_seconds?: number;
1730
+ event_id_header?: string;
1731
+ event_type_header?: string;
1732
+ }
1733
+ /** `payload.data` of a `webhook` invocation fired by an inbound source whose
1734
+ * `target_function` is this function (`payload.event === 'inbound_webhook.received'`). */
1735
+ export interface InboundWebhookTriggerData {
1736
+ vxil_event_id: string;
1737
+ source_id: string;
1738
+ provider: string;
1739
+ provider_event_id: string | null;
1740
+ event_type: string | null;
1741
+ sig_verified: boolean;
1742
+ /** the provider's JSON body; null when it was larger than 48 000 bytes
1743
+ * (`payload_omitted: true`) — read it with
1744
+ * `GET /v1/webhooks/events?event_id=<vxil_event_id>` (scope webhooks:read) */
1745
+ payload: Record<string, unknown> | unknown[] | null;
1746
+ payload_omitted?: true;
1747
+ /** true on a `POST /v1/webhooks/sources/:id/test` probe */
1748
+ test?: true;
1749
+ /** true on a `POST /v1/webhooks/events/:id/replay` */
1750
+ replayed?: true;
1751
+ }
1713
1752
  /** `POST /v1/fn/:name`: `payload` is the JSON request body (the query
1714
1753
  * parameters on a GET). */
1715
1754
  export interface HttpFunctionEnvelope<P = unknown> extends FunctionEnvelopeBase {
@@ -2847,6 +2886,11 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2847
2886
  readonly fn: { [K in keyof S["functions"] & string]: FnInvoker<S["functions"][K]["Input"], S["functions"][K]["Output"]>; };
2848
2887
  private call;
2849
2888
  readonly users: {
2889
+ /** Create or replace a tenant user by id. Upserting a deleted id brings it
2890
+ * back; upserting an ERASED id re-creates it as a new, empty user
2891
+ * (`erased_at` cleared — the erase already removed the old data). While
2892
+ * the erased user's files are still being erased the call fails with 409
2893
+ * `user_erase_pending`; retry later. `bulk` is all-or-nothing. */
2850
2894
  upsert: (user: {
2851
2895
  id: string;
2852
2896
  email?: string;
@@ -3215,11 +3259,23 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3215
3259
  brief?: boolean;
3216
3260
  }) => Promise<VxilPlan>;
3217
3261
  };
3262
+ /** The project audit log. Needs `audit:read` (the narrow read-only scope —
3263
+ * declare it on a function to read the log from its callback token) or
3264
+ * `features:read`. Server-only: a public / thin-client key cannot read it. */
3218
3265
  readonly audit: {
3266
+ /**
3267
+ * The audit trail, newest first. `subject` narrows to the events about ONE
3268
+ * thing: a user id (`payload.user_id` / `payload.id`), a cms record
3269
+ * (`payload.item_id` — the record's history), a verified end user
3270
+ * (`payload.end_user_id` — what that person changed through a thin-client
3271
+ * key or a function acting for them, and their payments events), or a
3272
+ * principal (`actor`). Server keys only.
3273
+ */
3219
3274
  list: (q?: {
3220
3275
  since?: string;
3221
3276
  cursor?: string;
3222
3277
  limit?: number;
3278
+ subject?: string;
3223
3279
  }) => Promise<Array<{
3224
3280
  id: string;
3225
3281
  event: string;
@@ -3237,6 +3293,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3237
3293
  until?: string;
3238
3294
  after_id?: string;
3239
3295
  limit?: number;
3296
+ /** the same subject filter as `list` */
3297
+ subject?: string;
3240
3298
  }) => Promise<{
3241
3299
  events: Array<{
3242
3300
  id: string;
@@ -3521,7 +3579,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3521
3579
  state: string;
3522
3580
  }>;
3523
3581
  /** Clone a terminal run into a fresh queued run. A generation run answers
3524
- * `409 not_replayable` — submit the generation again instead. */
3582
+ * `409 not_replayable` — submit the generation again instead; a redacted
3583
+ * or erased run answers `409 run_redacted` (its payload is gone). */
3525
3584
  replay: (runId: string) => Promise<{
3526
3585
  run_id: string;
3527
3586
  replayed_from: string;
@@ -3544,6 +3603,30 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3544
3603
  moved: number;
3545
3604
  done: boolean;
3546
3605
  }>;
3606
+ /** SERVER-ONLY (403 server_only in end-user mode). Redact FINISHED runs
3607
+ * that hold personal data: the payload becomes `{ erased: true }`, the
3608
+ * result, progress, callback body and error text are cleared, a
3609
+ * generation's provider request body/headers are dropped and its credit
3610
+ * hold's user id is pseudonymized (unless its settle is still owed). The
3611
+ * row itself stays (state, timings, job_name). The same fields a GDPR
3612
+ * erase clears on the runs it can link to a user — use this for runs
3613
+ * nothing links to one (a plain queue run, an async function call).
3614
+ * `{ run_ids }` (≤500, one call; open runs are left alone and counted in
3615
+ * `skipped_open`) or `{ user_id }` (the generation runs whose credit hold
3616
+ * names that user — ONE page of ≤500 per call, call again until `done`).
3617
+ * Idempotent. Audited as `jobs.runs.redacted` (counts only; none when a
3618
+ * call redacted nothing). NOT cleared: concurrency_key, debounce_key,
3619
+ * idempotency_key and target_url — keep user ids out of those (hash them).
3620
+ * A redacted run does not replay. Scope jobs:write. */
3621
+ redact: (input: {
3622
+ run_ids: string[];
3623
+ } | {
3624
+ user_id: string;
3625
+ }) => Promise<{
3626
+ redacted: number;
3627
+ skipped_open: number;
3628
+ done: boolean;
3629
+ }>;
3547
3630
  /**
3548
3631
  * Suspend the RUNNING run until an event (call from the executing
3549
3632
  * handler, then return 200 — the suspension wins). `state: 'resumed'` =
@@ -3734,8 +3817,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3734
3817
  /** On success the guest keeps its user id (`user_id` unchanged).
3735
3818
  * If the claimed email ALREADY has an account, the guest is MERGED
3736
3819
  * into it: `user_id` is the existing account, `merged: true`, and a
3737
- * fresh `session.token` (same session, re-signed for the merged
3738
- * identity — swap it client-side; other guest sessions are revoked).
3820
+ * fresh `session.token` (the same session carried over to the merged
3821
+ * identity under a new session id — swap it client-side: the old
3822
+ * guest token is revoked, the refresh token keeps working; other
3823
+ * guest sessions are revoked).
3739
3824
  * `rekeyed` (present on a merge): true ⇒ the guest's payments / cms /
3740
3825
  * files rows were already moved onto `user_id` when this returned, so
3741
3826
  * the merged user's next request finds them; false ⇒ the move ran
@@ -3967,15 +4052,34 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3967
4052
  verified: true;
3968
4053
  }>;
3969
4054
  };
3970
- /** Social sign-in. The web `start`/`callback` flows are browser redirects
3971
- * (not JSON calls): send the browser to
3972
- * `GET /v1/auth/oauth/{provider}/start?redirect_uri=…` (server-side, with
3973
- * your key) and hand the returned `code`+`state` to `…/callback`; `native`
4055
+ /** Social sign-in. The web flow: `startUrl` fetches the provider authorize
4056
+ * URL with your key (a browser cannot follow the header-carrying 302 of
4057
+ * `GET /v1/auth/oauth/{provider}/start` itself), you navigate the browser
4058
+ * there, and hand the returned `code`+`state` to `…/callback`; `native`
3974
4059
  * is the mobile / broker token-exchange the SDK wraps. `oidc` is the
3975
4060
  * tenant's generic OIDC / SSO issuer (auth config `providers.oidc` —
3976
4061
  * Okta / Entra / Auth0 / any OpenID Connect IdP, or a SAML broker that
3977
4062
  * speaks OIDC); it rides the same three routes. */
3978
4063
  oauth: {
4064
+ /** Start the web sign-in from a browser SPA: answers the provider
4065
+ * `authorize_url` (PKCE challenge, one-shot `state`, and for `oidc` a
4066
+ * nonce bound to the flow — all held server-side) for you to navigate
4067
+ * to: `location.assign((await vx.auth.oauth.startUrl('oidc',
4068
+ * { redirect_uri })).authorize_url)`. The provider returns to
4069
+ * `redirect_uri` with `?code&state`, which your page sends to
4070
+ * `GET /v1/auth/oauth/{provider}/callback` for the session. The same
4071
+ * rules as the redirect form apply: `redirect_uri` must match the auth
4072
+ * config `security.allowedRedirectOrigins` when set (else
4073
+ * `422 redirect_not_allowed`), and the state expires after
4074
+ * `expires_in` seconds (600). `anonymous_token` (a guest bearer)
4075
+ * promotes or merges that guest at the callback. */
4076
+ startUrl: (provider: "google" | "apple" | "github" | "facebook" | "mock" | "oidc", input: {
4077
+ redirect_uri: string;
4078
+ anonymous_token?: string;
4079
+ }) => Promise<{
4080
+ authorize_url: string;
4081
+ expires_in: number;
4082
+ }>;
3979
4083
  /** Native social sign-in: exchange a provider `id_token`/`access_token`
3980
4084
  * for a vxil session (`linked` marks whether the user was created or
3981
4085
  * matched to an existing identity). */
@@ -4126,6 +4230,20 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4126
4230
  * (end-user reads and writes are `403 server_only`, even with an
4127
4231
  * owner_field). Server keys are never affected. */
4128
4232
  end_user_access?: CmsEndUserAccess;
4233
+ /** Role slugs one of which a VERIFIED end user must hold to WRITE this
4234
+ * collection (create / update / $inc / delete / publish / transaction
4235
+ * step / batch / filtered delete, and an end-user delete's cascade into
4236
+ * it) — otherwise `403 role_required`. Reads are unaffected; server keys
4237
+ * are never affected. Omitted/`[]` = ungated. ≤16 entries, each
4238
+ * `^[a-z0-9][a-z0-9_-]{0,31}$` (the `orgs` role alphabet). */
4239
+ write_roles?: string[];
4240
+ /** Retention (guide ch. 4): live items created more than this many days
4241
+ * ago (1..3650) are soft-deleted by the nightly platform sweep — like a
4242
+ * normal delete (no restore; purged at least 30 days later, unique values freed), with ONE
4243
+ * `cms.collection.retention_swept` audit event per collection per night
4244
+ * instead of per-item delete events. A collection another collection
4245
+ * references with an `on_delete` rule is skipped. Omit/null = keep. */
4246
+ retain_days?: number | null;
4129
4247
  fields?: Array<{
4130
4248
  field: string;
4131
4249
  type: "string" | "text" | "int" | "float" | "bool" | "datetime" | "json" | "relation" | "file";
@@ -4175,6 +4293,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4175
4293
  actions?: CmsActionDef[];
4176
4294
  /** present when not the default 'readwrite' */
4177
4295
  end_user_access?: CmsEndUserAccess;
4296
+ /** present when the collection is role-gated for end-user writes */
4297
+ write_roles?: string[];
4298
+ /** present when retention is set */
4299
+ retain_days?: number;
4178
4300
  }>;
4179
4301
  list: () => Promise<Array<{
4180
4302
  collection: string;
@@ -4183,6 +4305,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4183
4305
  owner_field?: string | null;
4184
4306
  /** the collection's end-user access mode (absent from an older server = 'readwrite') */
4185
4307
  end_user_access?: CmsEndUserAccess;
4308
+ /** the roles an end user needs to write it (null / absent = ungated) */
4309
+ write_roles?: string[] | null;
4310
+ /** retention in days (null = keep until deleted; absent from an older server) */
4311
+ retain_days?: number | null;
4186
4312
  }>>;
4187
4313
  addField: (collection: string, field: Record<string, unknown>) => Promise<void>;
4188
4314
  /** Set (or clear, with `null`/`[]`) a field's READ-ROLE gate (guide ch. 4) —
@@ -4225,6 +4351,25 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4225
4351
  collection: string;
4226
4352
  end_user_access: CmsEndUserAccess;
4227
4353
  }>;
4354
+ /** Set (or clear, with `[]` / `null`) the collection's END-USER WRITE
4355
+ * ROLES (guide ch. 9): a verified end user must hold one of `roles` (the
4356
+ * session's verified role claims — orgs roles) to write the collection,
4357
+ * otherwise every write door answers `403 role_required`. Reads are
4358
+ * unaffected; server keys are never affected. Audited; takes effect on
4359
+ * the next write. */
4360
+ setWriteRoles: (collection: string, roles: string[] | null) => Promise<{
4361
+ collection: string;
4362
+ write_roles: string[];
4363
+ }>;
4364
+ /** Set (1..3650 days) or clear (`null`) the collection's RETENTION (guide
4365
+ * ch. 4): the nightly platform sweep soft-deletes live items created more
4366
+ * than `days` ago — bounded per project per night, no restore, purged at
4367
+ * least 30 days later like any delete, one `cms.collection.retention_swept` audit event
4368
+ * per collection per sweep. Audited (`cms.collection.retain_days.set`). */
4369
+ setRetention: (collection: string, days: number | null) => Promise<{
4370
+ collection: string;
4371
+ retain_days: number | null;
4372
+ }>;
4228
4373
  /** Re-project the collection's index slots after an `index_slot` move
4229
4374
  * (guide ch. 4). Slots are projected on WRITE only, so until this runs,
4230
4375
  * stored rows keep their OLD projection: the new slot is NULL and the
@@ -4276,6 +4421,29 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4276
4421
  version: number;
4277
4422
  data: Record<string, unknown>;
4278
4423
  }>;
4424
+ /** Upsert by a declared `unique` field (guide ch. 4): creates the item, or
4425
+ * — when a live item already holds `data[field]` — merges `patch` into
4426
+ * THAT item instead (one transaction; update hooks, guards, version bump
4427
+ * and a `cms.item.updated` audit, exactly like `patch`; null clears a
4428
+ * field). `created` tells which happened (201 vs 200). Concurrent upserts
4429
+ * of one key serialize, so they never create a duplicate. */
4430
+ upsert: (collection: string, input: {
4431
+ data: Record<string, unknown>;
4432
+ onConflict: {
4433
+ field: string;
4434
+ patch: Record<string, unknown>;
4435
+ };
4436
+ status?: "draft" | "published";
4437
+ lock?: string;
4438
+ guard?: CmsGuard;
4439
+ guards?: CmsGuardTerm[];
4440
+ }) => Promise<{
4441
+ item_id: string;
4442
+ status: string;
4443
+ version: number;
4444
+ data: Record<string, unknown>;
4445
+ created: boolean;
4446
+ }>;
4279
4447
  /** `expand` inlines relation/file fields into `data` (guide ch. 4): the
4280
4448
  * full item envelope, a `{ object_id, $ref: 'files' }` stub for a file,
4281
4449
  * the bare id on a cycle/depth cut, `null` for an invisible target. */
@@ -4284,7 +4452,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4284
4452
  }) => Promise<Record<string, unknown>>;
4285
4453
  /**
4286
4454
  * The bounded query DSL. filter ops: $eq $ne $gt $gte $lt $lte $in
4287
- * $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
4455
+ * ($ne is NULL-safe: a row whose field is absent or null counts as not
4456
+ * equal; on a dotted join term the row still needs a readable target,
4457
+ * so a null or dangling relation is excluded) $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
4288
4458
  * fields) $arrayContains/$anyOf (json/relation array containment, indexed);
4289
4459
  * range/sort needs slot-indexed fields. This is the
4290
4460
  * DELIBERATELY-UNTYPED escape hatch: it accepts everything the server
@@ -4674,6 +4844,32 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4674
4844
  };
4675
4845
  warnings?: string[];
4676
4846
  }>;
4847
+ /**
4848
+ * Log records of ONE function, one per invocation (outcome, wall/cpu ms,
4849
+ * console lines, exceptions), oldest first. Pass the returned `next_since`
4850
+ * back as `since` to read only newer records (a tail); without it, the most
4851
+ * recent records of the last 24 hours. Kept 14 days. Needs
4852
+ * `functions:read`. (GET /v1/functions/:name/logs)
4853
+ */
4854
+ logs: (name: string, opts?: {
4855
+ since?: string;
4856
+ }) => Promise<{
4857
+ lines: Array<{
4858
+ ts: string;
4859
+ outcome: string;
4860
+ wall_ms: number | null;
4861
+ cpu_ms: number | null;
4862
+ logs: Array<{
4863
+ level: string;
4864
+ message: string;
4865
+ }>;
4866
+ exceptions: Array<{
4867
+ name: string;
4868
+ message: string;
4869
+ }>;
4870
+ }>;
4871
+ next_since: string | null;
4872
+ }>;
4677
4873
  };
4678
4874
  /** The MCP aggregation surface (the `mcp` feature). */
4679
4875
  readonly mcp: {
@@ -4969,20 +5165,38 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4969
5165
  last_error_msg?: string | null;
4970
5166
  }>;
4971
5167
  sources: {
4972
- /** Register an inbound source; the receiver URL is returned ONCE. */
5168
+ /** Register an inbound source; the receiver URL is returned ONCE.
5169
+ *
5170
+ * Destination: `forward_url` (your endpoint) OR `target_function` (a
5171
+ * deployed function that declares a bare `trigger: { kind: 'webhook' }`
5172
+ * binding — it receives `payload.event === 'inbound_webhook.received'`
5173
+ * with the inbound envelope as `payload.data`), never both; neither =
5174
+ * store only (read with `events.list`).
5175
+ *
5176
+ * `provider: 'hmac'` is the parameterized preset for any provider that
5177
+ * signs an HMAC of the raw body (Shopify, Intercom, Linear, Zendesk, …):
5178
+ * `verify` says which header, sha256|sha1, hex|base64, an optional prefix
5179
+ * and an optional timestamp header + tolerance. Like stripe/paddle/slack it
5180
+ * fails closed (401) until the source's signing secret is set. */
4973
5181
  create: (input: {
4974
- provider: "stripe" | "paddle" | "github" | "slack" | "revenuecat" | "generic";
5182
+ provider: "stripe" | "paddle" | "github" | "slack" | "revenuecat" | "generic" | "hmac";
4975
5183
  name: string;
4976
5184
  forward_url?: string;
5185
+ target_function?: string;
5186
+ verify?: WebhookHmacVerify;
4977
5187
  }) => Promise<{
4978
5188
  source_id: string;
4979
5189
  receiver_url_path: string;
4980
5190
  provider: string;
5191
+ target_function?: string | null;
4981
5192
  }>;
4982
5193
  list: () => Promise<Array<{
4983
5194
  source_id: string;
4984
5195
  provider: string;
4985
5196
  name: string;
5197
+ forward_url?: string | null;
5198
+ target_function?: string | null;
5199
+ verify?: WebhookHmacVerify | null;
4986
5200
  }>>;
4987
5201
  delete: (sourceId: string) => Promise<void>;
4988
5202
  /** Send a sample envelope to the source's forward_url through the real
@@ -4997,20 +5211,38 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4997
5211
  }>;
4998
5212
  };
4999
5213
  events: {
5214
+ /** Received events. Newest first, paged with `cursor`; or — with `after`
5215
+ * (your watermark, an event id) — OLDEST first from just after it, the
5216
+ * drain shape: store `next_after` and pass it back, `has_more` says
5217
+ * another page waits. The `after` drain answers only events received
5218
+ * at least 10 s ago (ids are not strictly arrival-ordered, so a fresher
5219
+ * event could otherwise commit below a stored watermark and be
5220
+ * skipped); `cursor` and `event_id` reads are not lagged. `event_id`
5221
+ * reads exactly one event. `cursor` and `after` are exclusive. */
5000
5222
  list: (q?: {
5001
5223
  source_id?: string;
5002
5224
  status?: string;
5003
5225
  cursor?: string;
5004
5226
  limit?: number;
5227
+ after?: string;
5228
+ event_id?: string;
5005
5229
  }) => Promise<{
5006
5230
  events: Array<{
5007
5231
  event_id: string;
5232
+ source_id: string;
5233
+ provider: string;
5234
+ provider_event_id: string | null;
5008
5235
  event_type: string | null;
5009
5236
  payload: Record<string, unknown>;
5010
5237
  sig_verified: boolean;
5011
5238
  status: string;
5239
+ received_at: string;
5012
5240
  }>;
5013
5241
  next_cursor: string | null;
5242
+ /** `after` pages only */
5243
+ next_after?: string | null;
5244
+ /** `after` pages only */
5245
+ has_more?: boolean;
5014
5246
  }>;
5015
5247
  replay: (eventId: string) => Promise<void>;
5016
5248
  /** The Svix message-attempt view: delivery state/attempts/DLQ flag for
@@ -5231,8 +5463,14 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5231
5463
  * matching its TokenProvider type *structurally* (no import — a static
5232
5464
  * import would break the single-file served sdk.mjs) that re-mints via
5233
5465
  * POST /v1/realtime/tokens with `defaults` merged over the channel the
5234
- * client asks for. Server-side (Node) use only — it needs the API key;
5235
- * browsers must fetch tokens from YOUR backend instead. */
5466
+ * client asks for. Two safe ways to use it:
5467
+ * - on a server, with a server key (`user_id` names the subject); or
5468
+ * - in a browser / mobile app, with a PUBLIC `end_user_required` key and
5469
+ * the signed-in user's session (`endUserToken` on the client): the mint
5470
+ * runs in end-user mode and the token's subject is FORCED to the
5471
+ * verified user (any `user_id` you pass is overridden), so no
5472
+ * backend of your own is needed.
5473
+ * Never ship a server key to a browser. */
5236
5474
  tokenProvider: (defaults: {
5237
5475
  user_id: string;
5238
5476
  ttl_seconds?: number;
@@ -5808,6 +6046,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5808
6046
  model?: string;
5809
6047
  user_id?: string;
5810
6048
  input?: Record<string, unknown>;
6049
+ /** Drop chunks whose cosine `similarity` to the query is below this
6050
+ * (-1..1) before grounding; overrides rag config `retrieval.minSimilarity`. */
6051
+ min_similarity?: number;
5811
6052
  }) => Promise<RagAnswer>;
5812
6053
  /** Streamed grounded answer: citations + the channel handle up front, tokens
5813
6054
  * over the realtime channel. */
@@ -5822,13 +6063,18 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5822
6063
  model?: string;
5823
6064
  user_id?: string;
5824
6065
  input?: Record<string, unknown>;
6066
+ /** Drop chunks whose cosine `similarity` to the query is below this
6067
+ * (-1..1) before grounding; overrides rag config `retrieval.minSimilarity`. */
6068
+ min_similarity?: number;
5825
6069
  }) => Promise<RagStreamHandle>;
5826
6070
  /** Retrieval-only grounding preview (guide ch. 6, rag): the exact chunks `answer`
5827
6071
  * would ground on, with rerank + metadata boosts applied — no generation,
5828
6072
  * no token spend. `boosts`/`rerank`/`min_score` override the rag config.
5829
6073
  * `min_score` floors the EFFECTIVE `score`, which is a RANK value (about
5830
- * 0.016–0.033 at the top): a floor above ~0.033 drops every hit. Judge
5831
- * relevance by each hit's `similarity` (cosine, -1..1) instead. */
6074
+ * 0.016–0.033 at the top): a floor above ~0.033 drops every hit. For a
6075
+ * relevance threshold use `min_similarity`, which floors each hit's cosine
6076
+ * `similarity` (-1..1; overrides config `retrieval.minSimilarity`; a hit
6077
+ * with no measured similarity, e.g. keyword mode, is kept). */
5832
6078
  search: (input: {
5833
6079
  query: string;
5834
6080
  collection?: string;
@@ -5838,6 +6084,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5838
6084
  rerank?: boolean;
5839
6085
  boosts?: Record<string, unknown>;
5840
6086
  min_score?: number;
6087
+ min_similarity?: number;
5841
6088
  }) => Promise<RagSearchResult>;
5842
6089
  /** Ingest into the backing vector-search collection (chunk → embed → index). */
5843
6090
  ingest: (collection: string, input: SearchIngestInput) => Promise<SearchIngestResult>;
@@ -5999,7 +6246,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5999
6246
  * (no oversell). Pass `job_id` to make the debit PROVISIONAL (held, not yet
6000
6247
  * committed): a linked jobs run that terminally fails auto-refunds the hold,
6001
6248
  * a success settles it. Throws VxilError(402, 'INSUFFICIENT_CREDITS') when the
6002
- * available balance can't cover `amount`.
6249
+ * available balance can't cover `amount`. Throws VxilError(409,
6250
+ * 'job_released') when `job_id` names a generation run that already ended
6251
+ * (cancelled or failed) before this hold landed: nothing is held.
6003
6252
  *
6004
6253
  * Ordered credits: pass `credit_types` (1–8, in spend order — e.g.
6005
6254
  * `['free', 'subscription', 'topup']`) instead of `credit_type`; the WHOLE
package/dist/index.js CHANGED
@@ -429,6 +429,11 @@ export class Vxil {
429
429
  return { data: parsed.data, meta: parsed.meta ?? { request_id: '' }, response: res };
430
430
  }
431
431
  users = {
432
+ /** Create or replace a tenant user by id. Upserting a deleted id brings it
433
+ * back; upserting an ERASED id re-creates it as a new, empty user
434
+ * (`erased_at` cleared — the erase already removed the old data). While
435
+ * the erased user's files are still being erased the call fails with 409
436
+ * `user_erase_pending`; retry later. `bulk` is all-or-nothing. */
432
437
  upsert: async (user) => (await this.call('POST', '/v1/users', user)).data,
433
438
  bulk: async (users) => (await this.call('POST', '/v1/users/bulk', { users })).data.upserted,
434
439
  get: async (id, opts) => (await this.call('GET', `/v1/users/${encodeURIComponent(id)}${opts?.includeDeleted ? '?include_deleted=true' : ''}`)).data,
@@ -638,12 +643,24 @@ export class Vxil {
638
643
  ...(opts?.brief === false ? { brief: false } : {}),
639
644
  })).data,
640
645
  };
646
+ /** The project audit log. Needs `audit:read` (the narrow read-only scope —
647
+ * declare it on a function to read the log from its callback token) or
648
+ * `features:read`. Server-only: a public / thin-client key cannot read it. */
641
649
  audit = {
650
+ /**
651
+ * The audit trail, newest first. `subject` narrows to the events about ONE
652
+ * thing: a user id (`payload.user_id` / `payload.id`), a cms record
653
+ * (`payload.item_id` — the record's history), a verified end user
654
+ * (`payload.end_user_id` — what that person changed through a thin-client
655
+ * key or a function acting for them, and their payments events), or a
656
+ * principal (`actor`). Server keys only.
657
+ */
642
658
  list: async (q) => {
643
659
  const s = qs({
644
660
  since: q?.since || undefined,
645
661
  cursor: q?.cursor || undefined,
646
662
  limit: q?.limit || undefined,
663
+ subject: q?.subject || undefined,
647
664
  });
648
665
  return (await this.call('GET', `/v1/audit${s}`)).data.events;
649
666
  },
@@ -657,6 +674,7 @@ export class Vxil {
657
674
  until: q?.until || undefined,
658
675
  after_id: q?.after_id || undefined,
659
676
  limit: q?.limit || undefined,
677
+ subject: q?.subject || undefined,
660
678
  });
661
679
  const { response: res, text } = await this.transport.send(`${this.base}${this.path(`/v1/audit/export${s}`)}`, {
662
680
  method: 'GET',
@@ -864,7 +882,8 @@ export class Vxil {
864
882
  * terminal run answers `409 not_cancellable`. */
865
883
  cancel: async (runId) => (await this.call('POST', `/v1/jobs/runs/${encodeURIComponent(runId)}/cancel`)).data,
866
884
  /** Clone a terminal run into a fresh queued run. A generation run answers
867
- * `409 not_replayable` — submit the generation again instead. */
885
+ * `409 not_replayable` — submit the generation again instead; a redacted
886
+ * or erased run answers `409 run_redacted` (its payload is gone). */
868
887
  replay: async (runId) => (await this.call('POST', `/v1/jobs/runs/${encodeURIComponent(runId)}/replay`)).data,
869
888
  /** SERVER-ONLY (403 server_only in end-user mode). Account merge: move the
870
889
  * credit-hold owner (`reserve_credits.user_id`) of every run of
@@ -878,6 +897,22 @@ export class Vxil {
878
897
  * files re-key it does not check that `into_user_id` exists.
879
898
  * Scope jobs:write. */
880
899
  reKey: async (input) => (await this.call('POST', '/v1/jobs/runs/re-key', input)).data,
900
+ /** SERVER-ONLY (403 server_only in end-user mode). Redact FINISHED runs
901
+ * that hold personal data: the payload becomes `{ erased: true }`, the
902
+ * result, progress, callback body and error text are cleared, a
903
+ * generation's provider request body/headers are dropped and its credit
904
+ * hold's user id is pseudonymized (unless its settle is still owed). The
905
+ * row itself stays (state, timings, job_name). The same fields a GDPR
906
+ * erase clears on the runs it can link to a user — use this for runs
907
+ * nothing links to one (a plain queue run, an async function call).
908
+ * `{ run_ids }` (≤500, one call; open runs are left alone and counted in
909
+ * `skipped_open`) or `{ user_id }` (the generation runs whose credit hold
910
+ * names that user — ONE page of ≤500 per call, call again until `done`).
911
+ * Idempotent. Audited as `jobs.runs.redacted` (counts only; none when a
912
+ * call redacted nothing). NOT cleared: concurrency_key, debounce_key,
913
+ * idempotency_key and target_url — keep user ids out of those (hash them).
914
+ * A redacted run does not replay. Scope jobs:write. */
915
+ redact: async (input) => (await this.call('POST', '/v1/jobs/runs/redact', input)).data,
881
916
  /**
882
917
  * Suspend the RUNNING run until an event (call from the executing
883
918
  * handler, then return 200 — the suspension wins). `state: 'resumed'` =
@@ -995,8 +1030,10 @@ export class Vxil {
995
1030
  /** On success the guest keeps its user id (`user_id` unchanged).
996
1031
  * If the claimed email ALREADY has an account, the guest is MERGED
997
1032
  * into it: `user_id` is the existing account, `merged: true`, and a
998
- * fresh `session.token` (same session, re-signed for the merged
999
- * identity — swap it client-side; other guest sessions are revoked).
1033
+ * fresh `session.token` (the same session carried over to the merged
1034
+ * identity under a new session id — swap it client-side: the old
1035
+ * guest token is revoked, the refresh token keeps working; other
1036
+ * guest sessions are revoked).
1000
1037
  * `rekeyed` (present on a merge): true ⇒ the guest's payments / cms /
1001
1038
  * files rows were already moved onto `user_id` when this returned, so
1002
1039
  * the merged user's next request finds them; false ⇒ the move ran
@@ -1182,15 +1219,33 @@ export class Vxil {
1182
1219
  },
1183
1220
  confirm: async (token) => (await this.call('POST', '/v1/auth/email/verify/confirm', { token })).data,
1184
1221
  },
1185
- /** Social sign-in. The web `start`/`callback` flows are browser redirects
1186
- * (not JSON calls): send the browser to
1187
- * `GET /v1/auth/oauth/{provider}/start?redirect_uri=…` (server-side, with
1188
- * your key) and hand the returned `code`+`state` to `…/callback`; `native`
1222
+ /** Social sign-in. The web flow: `startUrl` fetches the provider authorize
1223
+ * URL with your key (a browser cannot follow the header-carrying 302 of
1224
+ * `GET /v1/auth/oauth/{provider}/start` itself), you navigate the browser
1225
+ * there, and hand the returned `code`+`state` to `…/callback`; `native`
1189
1226
  * is the mobile / broker token-exchange the SDK wraps. `oidc` is the
1190
1227
  * tenant's generic OIDC / SSO issuer (auth config `providers.oidc` —
1191
1228
  * Okta / Entra / Auth0 / any OpenID Connect IdP, or a SAML broker that
1192
1229
  * speaks OIDC); it rides the same three routes. */
1193
1230
  oauth: {
1231
+ /** Start the web sign-in from a browser SPA: answers the provider
1232
+ * `authorize_url` (PKCE challenge, one-shot `state`, and for `oidc` a
1233
+ * nonce bound to the flow — all held server-side) for you to navigate
1234
+ * to: `location.assign((await vx.auth.oauth.startUrl('oidc',
1235
+ * { redirect_uri })).authorize_url)`. The provider returns to
1236
+ * `redirect_uri` with `?code&state`, which your page sends to
1237
+ * `GET /v1/auth/oauth/{provider}/callback` for the session. The same
1238
+ * rules as the redirect form apply: `redirect_uri` must match the auth
1239
+ * config `security.allowedRedirectOrigins` when set (else
1240
+ * `422 redirect_not_allowed`), and the state expires after
1241
+ * `expires_in` seconds (600). `anonymous_token` (a guest bearer)
1242
+ * promotes or merges that guest at the callback. */
1243
+ startUrl: async (provider, input) => {
1244
+ // plain encodeURIComponent (no URLSearchParams — React Native ≤ 0.79)
1245
+ const q = `redirect_uri=${encodeURIComponent(input.redirect_uri)}`
1246
+ + (input.anonymous_token ? `&anonymous_token=${encodeURIComponent(input.anonymous_token)}` : '');
1247
+ return (await this.call('GET', `/v1/auth/oauth/${encodeURIComponent(provider)}/start?${q}`, undefined, { accept: 'application/json' })).data;
1248
+ },
1194
1249
  /** Native social sign-in: exchange a provider `id_token`/`access_token`
1195
1250
  * for a vxil session (`linked` marks whether the user was created or
1196
1251
  * matched to an existing identity). */
@@ -1201,6 +1256,9 @@ export class Vxil {
1201
1256
  * (`linked: 'merged'`, `user_id` = that account, `rekeyed` says whether
1202
1257
  * the guest's owner-scoped rows already moved — see
1203
1258
  * `anonymous.link.verify`). */
1259
+ /** `nonce`: the value your app put on the IdP authorize request. For
1260
+ * When sent, the id_token's `nonce` claim must equal it (401
1261
+ * `oauth_token_invalid` otherwise); omitted, it is not checked. */
1204
1262
  input) => (await this.call('POST', `/v1/auth/oauth/${encodeURIComponent(provider)}/native`, input)).data,
1205
1263
  },
1206
1264
  };
@@ -1311,6 +1369,19 @@ export class Vxil {
1311
1369
  * (default) restores today's behaviour. Server keys are never affected.
1312
1370
  * Audited; takes effect on the next request. */
1313
1371
  setEndUserAccess: async (collection, access) => (await this.call('POST', `/v1/cms/collections/${encodeURIComponent(collection)}/fields`, { end_user_access: access })).data,
1372
+ /** Set (or clear, with `[]` / `null`) the collection's END-USER WRITE
1373
+ * ROLES (guide ch. 9): a verified end user must hold one of `roles` (the
1374
+ * session's verified role claims — orgs roles) to write the collection,
1375
+ * otherwise every write door answers `403 role_required`. Reads are
1376
+ * unaffected; server keys are never affected. Audited; takes effect on
1377
+ * the next write. */
1378
+ setWriteRoles: async (collection, roles) => (await this.call('POST', `/v1/cms/collections/${encodeURIComponent(collection)}/fields`, { write_roles: roles ?? [] })).data,
1379
+ /** Set (1..3650 days) or clear (`null`) the collection's RETENTION (guide
1380
+ * ch. 4): the nightly platform sweep soft-deletes live items created more
1381
+ * than `days` ago — bounded per project per night, no restore, purged at
1382
+ * least 30 days later like any delete, one `cms.collection.retention_swept` audit event
1383
+ * per collection per sweep. Audited (`cms.collection.retain_days.set`). */
1384
+ setRetention: async (collection, days) => (await this.call('POST', `/v1/cms/collections/${encodeURIComponent(collection)}/fields`, { retain_days: days })).data,
1314
1385
  /** Re-project the collection's index slots after an `index_slot` move
1315
1386
  * (guide ch. 4). Slots are projected on WRITE only, so until this runs,
1316
1387
  * stored rows keep their OLD projection: the new slot is NULL and the
@@ -1379,6 +1450,16 @@ export class Vxil {
1379
1450
  /** `lock` serializes same-key writers (a per-key lock); `guard`
1380
1451
  * is the declarative capacity/overlap invariant (requires lock) — guide ch. 4. */
1381
1452
  create: async (collection, input) => (await this.call('POST', `/v1/cms/items/${encodeURIComponent(collection)}`, input)).data,
1453
+ /** Upsert by a declared `unique` field (guide ch. 4): creates the item, or
1454
+ * — when a live item already holds `data[field]` — merges `patch` into
1455
+ * THAT item instead (one transaction; update hooks, guards, version bump
1456
+ * and a `cms.item.updated` audit, exactly like `patch`; null clears a
1457
+ * field). `created` tells which happened (201 vs 200). Concurrent upserts
1458
+ * of one key serialize, so they never create a duplicate. */
1459
+ upsert: async (collection, input) => {
1460
+ const r = await this.call('POST', `/v1/cms/items/${encodeURIComponent(collection)}`, input);
1461
+ return { ...r.data, created: r.response.status === 201 };
1462
+ },
1382
1463
  /** `expand` inlines relation/file fields into `data` (guide ch. 4): the
1383
1464
  * full item envelope, a `{ object_id, $ref: 'files' }` stub for a file,
1384
1465
  * the bare id on a cycle/depth cut, `null` for an invisible target. */
@@ -1388,7 +1469,9 @@ export class Vxil {
1388
1469
  },
1389
1470
  /**
1390
1471
  * The bounded query DSL. filter ops: $eq $ne $gt $gte $lt $lte $in
1391
- * $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
1472
+ * ($ne is NULL-safe: a row whose field is absent or null counts as not
1473
+ * equal; on a dotted join term the row still needs a readable target,
1474
+ * so a null or dangling relation is excluded) $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
1392
1475
  * fields) $arrayContains/$anyOf (json/relation array containment, indexed);
1393
1476
  * range/sort needs slot-indexed fields. This is the
1394
1477
  * DELIBERATELY-UNTYPED escape hatch: it accepts everything the server
@@ -1619,6 +1702,17 @@ export class Vxil {
1619
1702
  * `features:write`. (DELETE /v1/functions/:name)
1620
1703
  */
1621
1704
  delete: async (name, opts) => (await this.call('DELETE', `/v1/functions/${encodeURIComponent(name)}`, undefined, opts?.ifMatch !== undefined ? { 'if-match': String(opts.ifMatch) } : {})).data,
1705
+ /**
1706
+ * Log records of ONE function, one per invocation (outcome, wall/cpu ms,
1707
+ * console lines, exceptions), oldest first. Pass the returned `next_since`
1708
+ * back as `since` to read only newer records (a tail); without it, the most
1709
+ * recent records of the last 24 hours. Kept 14 days. Needs
1710
+ * `functions:read`. (GET /v1/functions/:name/logs)
1711
+ */
1712
+ logs: async (name, opts) => {
1713
+ const s = qs({ since: opts?.since || undefined });
1714
+ return (await this.call('GET', `/v1/functions/${encodeURIComponent(name)}/logs${s}`)).data;
1715
+ },
1622
1716
  };
1623
1717
  /** The MCP aggregation surface (the `mcp` feature). */
1624
1718
  mcp = {
@@ -1757,7 +1851,19 @@ export class Vxil {
1757
1851
  */
1758
1852
  test: async (subId) => (await this.call('POST', `/v1/webhooks/subscriptions/${encodeURIComponent(subId)}/test`)).data,
1759
1853
  sources: {
1760
- /** Register an inbound source; the receiver URL is returned ONCE. */
1854
+ /** Register an inbound source; the receiver URL is returned ONCE.
1855
+ *
1856
+ * Destination: `forward_url` (your endpoint) OR `target_function` (a
1857
+ * deployed function that declares a bare `trigger: { kind: 'webhook' }`
1858
+ * binding — it receives `payload.event === 'inbound_webhook.received'`
1859
+ * with the inbound envelope as `payload.data`), never both; neither =
1860
+ * store only (read with `events.list`).
1861
+ *
1862
+ * `provider: 'hmac'` is the parameterized preset for any provider that
1863
+ * signs an HMAC of the raw body (Shopify, Intercom, Linear, Zendesk, …):
1864
+ * `verify` says which header, sha256|sha1, hex|base64, an optional prefix
1865
+ * and an optional timestamp header + tolerance. Like stripe/paddle/slack it
1866
+ * fails closed (401) until the source's signing secret is set. */
1761
1867
  create: async (input) => (await this.call('POST', '/v1/webhooks/sources', input)).data,
1762
1868
  list: async () => (await this.call('GET', '/v1/webhooks/sources')).data.sources,
1763
1869
  delete: async (sourceId) => {
@@ -1768,11 +1874,21 @@ export class Vxil {
1768
1874
  test: async (sourceId) => (await this.call('POST', `/v1/webhooks/sources/${encodeURIComponent(sourceId)}/test`)).data,
1769
1875
  },
1770
1876
  events: {
1877
+ /** Received events. Newest first, paged with `cursor`; or — with `after`
1878
+ * (your watermark, an event id) — OLDEST first from just after it, the
1879
+ * drain shape: store `next_after` and pass it back, `has_more` says
1880
+ * another page waits. The `after` drain answers only events received
1881
+ * at least 10 s ago (ids are not strictly arrival-ordered, so a fresher
1882
+ * event could otherwise commit below a stored watermark and be
1883
+ * skipped); `cursor` and `event_id` reads are not lagged. `event_id`
1884
+ * reads exactly one event. `cursor` and `after` are exclusive. */
1771
1885
  list: async (q) => {
1772
1886
  const s = qs({
1773
1887
  source_id: q?.source_id || undefined,
1774
1888
  status: q?.status || undefined,
1775
1889
  cursor: q?.cursor || undefined,
1890
+ after: q?.after || undefined,
1891
+ event_id: q?.event_id || undefined,
1776
1892
  limit: q?.limit || undefined,
1777
1893
  });
1778
1894
  return (await this.call('GET', `/v1/webhooks/events${s}`)).data;
@@ -1875,8 +1991,14 @@ export class Vxil {
1875
1991
  * matching its TokenProvider type *structurally* (no import — a static
1876
1992
  * import would break the single-file served sdk.mjs) that re-mints via
1877
1993
  * POST /v1/realtime/tokens with `defaults` merged over the channel the
1878
- * client asks for. Server-side (Node) use only — it needs the API key;
1879
- * browsers must fetch tokens from YOUR backend instead. */
1994
+ * client asks for. Two safe ways to use it:
1995
+ * - on a server, with a server key (`user_id` names the subject); or
1996
+ * - in a browser / mobile app, with a PUBLIC `end_user_required` key and
1997
+ * the signed-in user's session (`endUserToken` on the client): the mint
1998
+ * runs in end-user mode and the token's subject is FORCED to the
1999
+ * verified user (any `user_id` you pass is overridden), so no
2000
+ * backend of your own is needed.
2001
+ * Never ship a server key to a browser. */
1880
2002
  tokenProvider: (defaults) => (ctx) => this.realtime.mintToken({ channel: ctx.channel, ...defaults }),
1881
2003
  /** One-shot: mint a token AND open the WebSocket. Uses the global
1882
2004
  * `WebSocket` (browser / Node ≥22). On older Node, pass a constructor —
@@ -2170,8 +2292,10 @@ export class Vxil {
2170
2292
  * would ground on, with rerank + metadata boosts applied — no generation,
2171
2293
  * no token spend. `boosts`/`rerank`/`min_score` override the rag config.
2172
2294
  * `min_score` floors the EFFECTIVE `score`, which is a RANK value (about
2173
- * 0.016–0.033 at the top): a floor above ~0.033 drops every hit. Judge
2174
- * relevance by each hit's `similarity` (cosine, -1..1) instead. */
2295
+ * 0.016–0.033 at the top): a floor above ~0.033 drops every hit. For a
2296
+ * relevance threshold use `min_similarity`, which floors each hit's cosine
2297
+ * `similarity` (-1..1; overrides config `retrieval.minSimilarity`; a hit
2298
+ * with no measured similarity, e.g. keyword mode, is kept). */
2175
2299
  search: async (input) => (await this.call('POST', '/v1/rag/search', input)).data,
2176
2300
  /** Ingest into the backing vector-search collection (chunk → embed → index). */
2177
2301
  ingest: async (collection, input) => (await this.call('POST', `/v1/rag/ingest/${encodeURIComponent(collection)}`, input)).data,
@@ -2309,7 +2433,9 @@ export class Vxil {
2309
2433
  * (no oversell). Pass `job_id` to make the debit PROVISIONAL (held, not yet
2310
2434
  * committed): a linked jobs run that terminally fails auto-refunds the hold,
2311
2435
  * a success settles it. Throws VxilError(402, 'INSUFFICIENT_CREDITS') when the
2312
- * available balance can't cover `amount`.
2436
+ * available balance can't cover `amount`. Throws VxilError(409,
2437
+ * 'job_released') when `job_id` names a generation run that already ended
2438
+ * (cancelled or failed) before this hold landed: nothing is held.
2313
2439
  *
2314
2440
  * Ordered credits: pass `credit_types` (1–8, in spend order — e.g.
2315
2441
  * `['free', 'subscription', 'topup']`) instead of `credit_type`; the WHOLE
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vxil/sdk",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Typed client for the Vxil REST API (notifications, auth, jobs, files, cms, comments, webhooks, realtime, orgs, rate-limits).",