@vxil/sdk 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -382,6 +382,55 @@ export interface JobRun {
382
382
  * the fire); `started_at − scheduled_for` is how late it started. null for a
383
383
  * run you enqueued (and for runs fired before 2026-10-01). */
384
384
  scheduled_for?: string | null;
385
+ /** the single-run read only: what the run completed with — the `result` of
386
+ * its signed callback's `completed` body, or the value a generation run
387
+ * settled with (the fields its status mirror received; an over-64 KiB value
388
+ * reads `{ truncated: true, bytes, max_bytes }`). null when nothing reported
389
+ * one — a handler's own 2xx response body is never stored. */
390
+ result?: unknown;
391
+ /** the single-run read only: the latest progress report of the run — a
392
+ * `processing` ping on its signed callback (plain run) or its provider's
393
+ * webhook (generation run). null until one arrives. */
394
+ progress?: JobRunProgress | null;
395
+ }
396
+ /** A run's latest progress report (`JobRun.progress`): only these keys pass. */
397
+ export interface JobRunProgress {
398
+ /** 0..100 */
399
+ progress?: number;
400
+ /** ≤ 64 chars */
401
+ stage?: string;
402
+ /** ≤ 200 chars */
403
+ message?: string;
404
+ /** when the report was stored (ISO) */
405
+ at: string;
406
+ }
407
+ /** The body an external worker POSTs to a run's signed `callback_url`
408
+ * (`postRunCallback`). `completed` ends the run `succeeded` with `result`
409
+ * stored (≤ 64 KiB of JSON); `failed` dead-letters it with `error`;
410
+ * `processing` only records progress (repeatable). On a run suspended in
411
+ * `jobs.wait(…)`, completed / failed WAKE the handler instead, with
412
+ * `payload.wakeup = { via: 'callback', status, result?, error? }`. */
413
+ export type RunCallbackBody = {
414
+ status: 'completed';
415
+ result?: unknown;
416
+ } | {
417
+ status: 'failed';
418
+ error?: string;
419
+ } | {
420
+ status: 'processing';
421
+ progress?: number;
422
+ stage?: string;
423
+ message?: string;
424
+ };
425
+ /** What a run callback answers: the run's state after it (`deduplicated: true`
426
+ * when the URL was already used or the run was already terminal — nothing
427
+ * changed). */
428
+ export interface RunCallbackAnswer {
429
+ run_id: string;
430
+ state: JobRun['state'];
431
+ deduplicated?: boolean;
432
+ woken?: boolean;
433
+ event?: string;
385
434
  }
386
435
  /** The run states no later write can move — what `waitForRun` and an
387
436
  * async+wait invoke resolve `done: true` on. */
@@ -661,7 +710,25 @@ export interface FileObject {
661
710
  size_bytes: number;
662
711
  status: 'pending' | 'available' | 'deleted';
663
712
  created_at: string;
713
+ /** The object's public URL when it is published to the public asset host
714
+ * (`files.publish`), else null. */
715
+ public_url?: string | null;
716
+ }
717
+ /** A published object (guide ch. 6, files, "Public asset delivery"): a stable,
718
+ * content-addressed URL on vxil's public asset host, served with
719
+ * `Cache-Control: public, max-age=31536000, immutable`. `variants` maps each
720
+ * declared image preset (`publicAssets.variants`) to its URL — images only. */
721
+ export interface PublishedFile {
722
+ object_id: string;
723
+ url: string;
724
+ sha256: string;
725
+ content_type: string;
726
+ size_bytes: number;
727
+ published_at: string | null;
728
+ variants: Record<string, string>;
664
729
  }
730
+ /** Why one id of a bulk publish was not published. */
731
+ export type PublishErrorCode = 'not_found' | 'upload_incomplete' | 'content_type_not_publishable' | 'object_too_large' | 'quota_exceeded' | 'batch_budget_exceeded' | 'asset_taken_down' | 'publish_failed';
665
732
  /** A live shared link as listed by GET /v1/files/{id}/shared-links (guide ch. 6,
666
733
  * files). `downloads` is the burn-on-read counter; `max_downloads` null =
667
734
  * unlimited (1 = a one-time link). A link that hit its cap, expired, or was
@@ -971,10 +1038,13 @@ export interface JobRunEventPayload {
971
1038
  level?: 'error';
972
1039
  state?: 'broken';
973
1040
  /** `job.dead_lettered` only, and only when something other than the
974
- * executor killed the run: `reaped` (the stuck-run reaper) or
975
- * `queue_backstop` (the queue's own retries ran out). Absent when the run
1041
+ * executor killed the run: `reaped` (the stuck-run reaper),
1042
+ * `queue_backstop` (the queue's own retries ran out), `callback_failed`
1043
+ * (the run's signed callback reported `status: 'failed'`) or
1044
+ * `callback_timeout` (the handler handed the run off with a 202 and no
1045
+ * callback completed it within its lifetime). Absent when the run
976
1046
  * exhausted its attempts normally. */
977
- reason?: 'reaped' | 'queue_backstop';
1047
+ reason?: 'reaped' | 'queue_backstop' | 'callback_failed' | 'callback_timeout';
978
1048
  }
979
1049
  /** The provider-reported environment of the money (`production` | `sandbox`). */
980
1050
  export type PaymentsEventEnvironment = 'production' | 'sandbox';
@@ -1366,6 +1436,13 @@ export interface JobDelivery<P = unknown> {
1366
1436
  payload: P;
1367
1437
  /** a schedule-fired run only: the slot it was due for (ISO, UTC) */
1368
1438
  scheduled_for?: string;
1439
+ /** a run enqueued with `callback` only: its CURRENT single-use signed
1440
+ * callback URL. Hand it to the worker that finishes the job (no API key
1441
+ * needed) and answer 202 — the run waits until that worker POSTs
1442
+ * `{ status: 'completed', result }` / `{ status: 'failed', error }` /
1443
+ * `{ status: 'processing', progress, stage, message }` to it (see
1444
+ * `postRunCallback`). */
1445
+ callback_url?: string;
1369
1446
  }
1370
1447
  /** The `?since=` replay read (GET /v1/ai/generations/{id}/stream). */
1371
1448
  export interface AiResumePage {
@@ -1798,6 +1875,16 @@ export declare function listCmsPublic(tenantId: string, collection: string, quer
1798
1875
  items: CmsPublicRow[];
1799
1876
  next_cursor: string | null;
1800
1877
  }>;
1878
+ /** POST a run's single-use signed `callback_url` (guide ch. 6, "Hand a run to
1879
+ * an external worker") — KEYLESS: no API key, no `Vxil` client; the URL is the
1880
+ * credential, so call this from the worker that finishes the job (a render
1881
+ * farm, a GPU box, another queue's task). Idempotent: a repeat of a used URL
1882
+ * answers `deduplicated: true`. A non-2xx throws `VxilError` (401 a forged or
1883
+ * altered URL, 410 `callback_expired`, 413 `result_too_large`, 404 a run that
1884
+ * did not opt in). */
1885
+ export declare function postRunCallback(callbackUrl: string, body: RunCallbackBody, opts?: {
1886
+ fetch?: typeof fetch;
1887
+ }): Promise<RunCallbackAnswer>;
1801
1888
  /** The structural shape a `vxil gen`-generated `VxilSchema` satisfies. The base
1802
1889
  * `Vxil` class is generic over it (`new Vxil<VxilSchema>(...)`), exactly the
1803
1890
  * `createClient<Database>()` move — types are layered on; the runtime is
@@ -2831,7 +2918,14 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2831
2918
  readonly jobs: {
2832
2919
  /** Enqueue a one-off job; Vxil POSTs a signed callback to target_url with
2833
2920
  * retries. deliver_after (ISO) / delay_seconds (≤ 30 d, at most one of the
2834
- * two) defer the first delivery; > 12 h returns state 'delayed'. */
2921
+ * two) defer the first delivery; > 12 h returns state 'delayed'.
2922
+ *
2923
+ * `callback: true` (or `{ ttl_seconds }`, 60 s..31 d; default 24 h) opts
2924
+ * the run in to a KEYLESS signed `callback_url` (in the answer and on every
2925
+ * delivery): an external worker POSTs it to complete / fail the run, report
2926
+ * progress, or wake a `wait`. A handler that answers 202 HANDS the run off
2927
+ * — it waits for that callback (dead-lettered as `CallbackTimeout` when the
2928
+ * lifetime passes). See `postRunCallback`. */
2835
2929
  enqueue: (input: {
2836
2930
  job_name: string;
2837
2931
  target_url: string;
@@ -2840,11 +2934,15 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2840
2934
  max_attempts?: number;
2841
2935
  deliver_after?: string;
2842
2936
  delay_seconds?: number;
2937
+ callback?: boolean | {
2938
+ ttl_seconds?: number;
2939
+ };
2843
2940
  }) => Promise<{
2844
2941
  run_id: string;
2845
2942
  state: string;
2846
2943
  deduplicated?: boolean;
2847
2944
  deliver_after?: string;
2945
+ callback_url?: string;
2848
2946
  }>;
2849
2947
  /** Atomic multi-enqueue (≤100 items; any invalid item rejects the whole
2850
2948
  * batch). Each item = the enqueue input, incl. per-item idempotency_key
@@ -2916,11 +3014,15 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2916
3014
  /** dotted path of the value a completed run settles with (mirrored as `result`) */
2917
3015
  result_path?: string;
2918
3016
  };
3017
+ /** `progress_fields`: the progress keys (`progress` / `stage` /
3018
+ * `message`) a webhook-mode `processing` callback also writes onto the
3019
+ * mirrored record — realtime clients of that record see them live */
2919
3020
  status_mirror?: {
2920
3021
  feature: string;
2921
3022
  collection: string;
2922
3023
  record_id: string;
2923
3024
  column?: string;
3025
+ progress_fields?: Array<"progress" | "stage" | "message">;
2924
3026
  };
2925
3027
  timeout?: {
2926
3028
  after_ms: number;
@@ -2988,12 +3090,21 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2988
3090
  }>;
2989
3091
  /**
2990
3092
  * Suspend the RUNNING run until an event (call from the executing
2991
- * handler, then return 200 — the suspension wins).
3093
+ * handler, then return 200 — the suspension wins). `state: 'resumed'` =
3094
+ * the event already fired (continue; `wakeup` is its payload). A run
3095
+ * enqueued with `callback` also gets its `callback_url`: an external worker
3096
+ * POSTing it wakes this wait with no API key.
2992
3097
  */
2993
3098
  wait: (runId: string, input: {
2994
3099
  event: string;
2995
3100
  timeout_seconds?: number;
2996
- }) => Promise<void>;
3101
+ }) => Promise<{
3102
+ run_id: string;
3103
+ state: "waiting" | "resumed";
3104
+ event: string;
3105
+ wakeup?: unknown;
3106
+ callback_url?: string;
3107
+ }>;
2997
3108
  /** Wake every run waiting on the event. */
2998
3109
  emitEvent: (event: string, payload?: Record<string, unknown>) => Promise<number>;
2999
3110
  /** Secret for verifying X-Vxil-Jobs-Signature on your callback endpoints. */
@@ -4708,7 +4819,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4708
4819
  }>;
4709
4820
  /** Soft delete; bytes are hard-deleted 30 days later. */
4710
4821
  delete: (objectId: string) => Promise<void>;
4711
- /** Aggregate storage usage vs quotas (the FilesManager Storage panel). */
4822
+ /** Aggregate storage usage vs quotas (the FilesManager Storage panel).
4823
+ * `public_assets` = published copies vs the plan's published-bytes ceiling
4824
+ * (identical bytes count once). */
4712
4825
  usage: () => Promise<{
4713
4826
  usage: {
4714
4827
  object_count: number;
@@ -4720,7 +4833,46 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4720
4833
  maxTotalBytes?: number;
4721
4834
  };
4722
4835
  available: Record<string, number | null>;
4836
+ public_assets?: {
4837
+ enabled: boolean;
4838
+ published_objects: number;
4839
+ published_bytes: number;
4840
+ max_published_bytes: number;
4841
+ bytes_remaining: number;
4842
+ };
4843
+ }>;
4844
+ /** SERVER-ONLY (403 server_only in end-user mode). Publish an available
4845
+ * object to vxil's public asset host: a stable, content-addressed URL
4846
+ * (`https://cdn.vxil.app/<tenant>/<sha256>.<ext>`) served from a global
4847
+ * edge cache with `Cache-Control: public, max-age=31536000, immutable`,
4848
+ * playable video/audio and CORS from `publicAssets.corsOrigins`. Needs
4849
+ * `files.publicAssets.enabled`. Publishable: png/jpeg/webp/avif/gif,
4850
+ * mp4/webm, mp3/m4a/ogg, woff2/woff, json — never HTML or SVG (422
4851
+ * `content_type_not_publishable`). Counts against the plan's
4852
+ * published-bytes ceiling (422 `quota_exceeded`). Idempotent: an already
4853
+ * published object answers its existing URL. Emits
4854
+ * `files.object.published`. */
4855
+ publish: (objectId: string) => Promise<PublishedFile>;
4856
+ /** SERVER-ONLY. Publish up to 100 objects (about 2 GiB of bytes) in one
4857
+ * call; a bad id lands in `errors[]` without failing the rest (ids past
4858
+ * the byte budget as `batch_budget_exceeded` — send them again),
4859
+ * `published[]` keeps your order. */
4860
+ publishMany: (objectIds: string[]) => Promise<{
4861
+ published: PublishedFile[];
4862
+ errors: Array<{
4863
+ object_id: string;
4864
+ code: PublishErrorCode;
4865
+ message: string;
4866
+ }>;
4723
4867
  }>;
4868
+ /** SERVER-ONLY. Take an object off the public asset host. Its public copy
4869
+ * is deleted once no other object of yours has the same bytes, and the
4870
+ * edge cache stops serving it within about a minute and a half (a
4871
+ * browser that already downloaded it keeps its copy). Deleting the object
4872
+ * unpublishes it too. 404 when it is not published; 503
4873
+ * `unpublish_failed` when the public copy could not be removed right
4874
+ * then (it stays published — retry). Emits `files.object.unpublished`. */
4875
+ unpublish: (objectId: string) => Promise<void>;
4724
4876
  /** OCR / text extraction (guide ch. 6, files, BYO-key add-on). Small/mock inputs
4725
4877
  * extract inline (status `available`); large inputs (or `async:true`) return
4726
4878
  * 202 with a `job_id` — poll `getText()`. */
package/dist/index.js CHANGED
@@ -182,6 +182,36 @@ export async function listCmsPublic(tenantId, collection, query, opts) {
182
182
  }
183
183
  return parsed.data ?? { items: [], next_cursor: null };
184
184
  }
185
+ /** POST a run's single-use signed `callback_url` (guide ch. 6, "Hand a run to
186
+ * an external worker") — KEYLESS: no API key, no `Vxil` client; the URL is the
187
+ * credential, so call this from the worker that finishes the job (a render
188
+ * farm, a GPU box, another queue's task). Idempotent: a repeat of a used URL
189
+ * answers `deduplicated: true`. A non-2xx throws `VxilError` (401 a forged or
190
+ * altered URL, 410 `callback_expired`, 413 `result_too_large`, 404 a run that
191
+ * did not opt in). */
192
+ export async function postRunCallback(callbackUrl, body, opts) {
193
+ const fetchImpl = opts?.fetch ?? fetch;
194
+ const res = await fetchImpl(callbackUrl, {
195
+ method: 'POST',
196
+ headers: { 'content-type': 'application/json' },
197
+ body: JSON.stringify(body),
198
+ });
199
+ const text = await res.text();
200
+ let parsed = {};
201
+ if (text.length > 0) {
202
+ try {
203
+ parsed = JSON.parse(text);
204
+ }
205
+ catch {
206
+ throw new VxilError(res.status, `http_${res.status}`, text.slice(0, 200), undefined, undefined, undefined, parseRetryAfter(res.headers.get('retry-after')));
207
+ }
208
+ }
209
+ if (!res.ok || parsed.error || !parsed.data) {
210
+ const e = parsed.error ?? { code: `http_${res.status}`, message: text.slice(0, 200) };
211
+ throw new VxilError(res.status, e.code, e.message, e.hint, e.fixUrl, parsed.meta?.request_id, parseRetryAfter(res.headers.get('retry-after')));
212
+ }
213
+ return parsed.data;
214
+ }
185
215
  export class Vxil {
186
216
  base;
187
217
  key;
@@ -651,7 +681,14 @@ export class Vxil {
651
681
  jobs = {
652
682
  /** Enqueue a one-off job; Vxil POSTs a signed callback to target_url with
653
683
  * retries. deliver_after (ISO) / delay_seconds (≤ 30 d, at most one of the
654
- * two) defer the first delivery; > 12 h returns state 'delayed'. */
684
+ * two) defer the first delivery; > 12 h returns state 'delayed'.
685
+ *
686
+ * `callback: true` (or `{ ttl_seconds }`, 60 s..31 d; default 24 h) opts
687
+ * the run in to a KEYLESS signed `callback_url` (in the answer and on every
688
+ * delivery): an external worker POSTs it to complete / fail the run, report
689
+ * progress, or wake a `wait`. A handler that answers 202 HANDS the run off
690
+ * — it waits for that callback (dead-lettered as `CallbackTimeout` when the
691
+ * lifetime passes). See `postRunCallback`. */
655
692
  enqueue: async (input) => (await this.call('POST', '/v1/jobs/enqueue', input)).data,
656
693
  /** Atomic multi-enqueue (≤100 items; any invalid item rejects the whole
657
694
  * batch). Each item = the enqueue input, incl. per-item idempotency_key
@@ -717,11 +754,12 @@ export class Vxil {
717
754
  replay: async (runId) => (await this.call('POST', `/v1/jobs/runs/${encodeURIComponent(runId)}/replay`)).data,
718
755
  /**
719
756
  * Suspend the RUNNING run until an event (call from the executing
720
- * handler, then return 200 — the suspension wins).
757
+ * handler, then return 200 — the suspension wins). `state: 'resumed'` =
758
+ * the event already fired (continue; `wakeup` is its payload). A run
759
+ * enqueued with `callback` also gets its `callback_url`: an external worker
760
+ * POSTing it wakes this wait with no API key.
721
761
  */
722
- wait: async (runId, input) => {
723
- await this.call('POST', `/v1/jobs/runs/${encodeURIComponent(runId)}/wait`, input);
724
- },
762
+ wait: async (runId, input) => (await this.call('POST', `/v1/jobs/runs/${encodeURIComponent(runId)}/wait`, input)).data,
725
763
  /** Wake every run waiting on the event. */
726
764
  emitEvent: async (event, payload) => (await this.call('POST', '/v1/jobs/events', { event, ...(payload ? { payload } : {}) })).data.woken,
727
765
  /** Secret for verifying X-Vxil-Jobs-Signature on your callback endpoints. */
@@ -1733,8 +1771,37 @@ export class Vxil {
1733
1771
  delete: async (objectId) => {
1734
1772
  await this.call('DELETE', `/v1/files/${encodeURIComponent(objectId)}`);
1735
1773
  },
1736
- /** Aggregate storage usage vs quotas (the FilesManager Storage panel). */
1774
+ /** Aggregate storage usage vs quotas (the FilesManager Storage panel).
1775
+ * `public_assets` = published copies vs the plan's published-bytes ceiling
1776
+ * (identical bytes count once). */
1737
1777
  usage: async () => (await this.call('GET', '/v1/files/usage')).data,
1778
+ /** SERVER-ONLY (403 server_only in end-user mode). Publish an available
1779
+ * object to vxil's public asset host: a stable, content-addressed URL
1780
+ * (`https://cdn.vxil.app/<tenant>/<sha256>.<ext>`) served from a global
1781
+ * edge cache with `Cache-Control: public, max-age=31536000, immutable`,
1782
+ * playable video/audio and CORS from `publicAssets.corsOrigins`. Needs
1783
+ * `files.publicAssets.enabled`. Publishable: png/jpeg/webp/avif/gif,
1784
+ * mp4/webm, mp3/m4a/ogg, woff2/woff, json — never HTML or SVG (422
1785
+ * `content_type_not_publishable`). Counts against the plan's
1786
+ * published-bytes ceiling (422 `quota_exceeded`). Idempotent: an already
1787
+ * published object answers its existing URL. Emits
1788
+ * `files.object.published`. */
1789
+ publish: async (objectId) => (await this.call('POST', `/v1/files/${encodeURIComponent(objectId)}/publish`)).data,
1790
+ /** SERVER-ONLY. Publish up to 100 objects (about 2 GiB of bytes) in one
1791
+ * call; a bad id lands in `errors[]` without failing the rest (ids past
1792
+ * the byte budget as `batch_budget_exceeded` — send them again),
1793
+ * `published[]` keeps your order. */
1794
+ publishMany: async (objectIds) => (await this.call('POST', '/v1/files/publish', { object_ids: objectIds })).data,
1795
+ /** SERVER-ONLY. Take an object off the public asset host. Its public copy
1796
+ * is deleted once no other object of yours has the same bytes, and the
1797
+ * edge cache stops serving it within about a minute and a half (a
1798
+ * browser that already downloaded it keeps its copy). Deleting the object
1799
+ * unpublishes it too. 404 when it is not published; 503
1800
+ * `unpublish_failed` when the public copy could not be removed right
1801
+ * then (it stays published — retry). Emits `files.object.unpublished`. */
1802
+ unpublish: async (objectId) => {
1803
+ await this.call('DELETE', `/v1/files/${encodeURIComponent(objectId)}/publish`);
1804
+ },
1738
1805
  /** OCR / text extraction (guide ch. 6, files, BYO-key add-on). Small/mock inputs
1739
1806
  * extract inline (status `available`); large inputs (or `async:true`) return
1740
1807
  * 202 with a `job_id` — poll `getText()`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vxil/sdk",
3
- "version": "0.13.1",
3
+ "version": "0.14.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).",