@lumavox/server 0.2.1 → 0.4.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.
@@ -15,7 +15,7 @@
15
15
  * - **Each read sees its own capability**: an image's id is a 404 on
16
16
  * `videos.get`. `generations.get` reads any the key may generate.
17
17
  */
18
- import type { GenerationInfo, GenerationPage, ListGenerationsData, SkyboxStyle } from "../gen/index.js";
18
+ import type { GenerationInfo, GenerationPage, ListGenerationsData, SkyboxStylePage } from "../gen/index.js";
19
19
  import type { RequestOptions } from "../transport.js";
20
20
  import { Polled } from "./resource.js";
21
21
  export type GenerationListParams = NonNullable<ListGenerationsData["query"]>;
@@ -28,8 +28,8 @@ export declare class Jobs<Body> extends Polled<GenerationInfo> {
28
28
  }
29
29
  /** `/v1/skybox`, and the provider's styles. */
30
30
  export declare class Skybox<Body> extends Jobs<Body> {
31
- /** The styles available now, each with its `style_id`; empty when the provider is unreachable. */
32
- styles(options?: RequestOptions): Promise<SkyboxStyle[]>;
31
+ /** The styles available now, in one page: each `id` is a `style_id`; none when the provider is unreachable. */
32
+ styles(options?: RequestOptions): Promise<SkyboxStylePage>;
33
33
  }
34
34
  /** Every generation the key may make, read-only. */
35
35
  export declare class Generations extends Polled<GenerationInfo> {
@@ -15,10 +15,9 @@
15
15
  * - **Each read sees its own capability**: an image's id is a 404 on
16
16
  * `videos.get`. `generations.get` reads any the key may generate.
17
17
  */
18
- import { byCursor } from "../paging.js";
19
18
  import { poll } from "../wait.js";
20
19
  import { Polled } from "./resource.js";
21
- /** The route's own page bound. */
20
+ /** The route's largest page. */
22
21
  const PAGE = 100;
23
22
  /** One capability's queue: `/v1/images`, `/v1/videos`, … */
24
23
  export class Jobs extends Polled {
@@ -34,7 +33,7 @@ export class Jobs extends Polled {
34
33
  }
35
34
  /** `/v1/skybox`, and the provider's styles. */
36
35
  export class Skybox extends Jobs {
37
- /** The styles available now, each with its `style_id`; empty when the provider is unreachable. */
36
+ /** The styles available now, in one page: each `id` is a `style_id`; none when the provider is unreachable. */
38
37
  styles(options) {
39
38
  return this.send("GET", "/styles", options);
40
39
  }
@@ -47,9 +46,6 @@ export class Generations extends Polled {
47
46
  }
48
47
  /** Every generation, newest first, page after page (100 a page by default). */
49
48
  list(params = {}, options) {
50
- return byCursor(async (cursor) => {
51
- const page = await this.page({ limit: PAGE, ...params, cursor }, options);
52
- return { items: page.generations, next: page.next_cursor };
53
- });
49
+ return this.each("", params, options, PAGE);
54
50
  }
55
51
  }
@@ -10,27 +10,26 @@
10
10
  * - **`upload(file, fields)`** is the ticket, the `PUT` and `fromFile` in
11
11
  * one (`upload.ts`); the file's extension says how it is read, 10 MB at
12
12
  * most.
13
- * - **`list()` pages by offset**, 200 a page, with the `total` the filter
14
- * matches.
13
+ * - **`list()` pages by cursor**, 200 a page.
15
14
  * - **`update` with `expected_version`** refuses an edit made over
16
- * somebody else's (409); `delete` answers the entry as it was, and
17
- * `null` for one already gone.
15
+ * somebody else's (409); `delete` answers `true`, and `false` for one
16
+ * already gone.
18
17
  */
19
- import type { KnowledgeAssigned, KnowledgeAssignment, KnowledgeBulk, KnowledgeBulkResult, KnowledgeCategories, KnowledgeCreate, KnowledgeFileCreate, KnowledgeInfo, KnowledgeInsights, KnowledgeInsightsData, KnowledgeSearchList, KnowledgeSearchRequest, KnowledgeSearchResult, KnowledgeUpdate, KnowledgeUploadRequest, KnowledgeUploadTicket, KnowledgeUrlCreate, ListKnowledgeData } from "../gen/index.js";
18
+ import type { GetKnowledgeInsightsData, KnowledgeAgentsUpdate, KnowledgeAssigned, KnowledgeBulk, KnowledgeBulkResult, KnowledgeCategoryPage, KnowledgeCreate, KnowledgeFileCreate, KnowledgeInfo, KnowledgeInsights, KnowledgePage, KnowledgeSearchRequest, KnowledgeSearchResult, KnowledgeUpdate, KnowledgeUploadRequest, KnowledgeUploadTicket, KnowledgeUrlCreate, ListKnowledgeData } from "../gen/index.js";
20
19
  import type { RequestOptions } from "../transport.js";
21
20
  import { type UploadData, type UploadOptions } from "../upload.js";
22
21
  import { Waitable } from "./resource.js";
23
22
  export type KnowledgeListParams = NonNullable<ListKnowledgeData["query"]>;
24
- export type KnowledgeInsightsParams = NonNullable<KnowledgeInsightsData["query"]>;
23
+ export type KnowledgeInsightsParams = NonNullable<GetKnowledgeInsightsData["query"]>;
25
24
  /** A file's entry: `fromFile`'s fields, and its type when not its extension's. */
26
25
  export type KnowledgeUploadFields = Omit<KnowledgeFileCreate, "upload_id"> & {
27
26
  content_type?: string | null;
28
27
  };
29
28
  export declare class Knowledge extends Waitable<KnowledgeInfo, KnowledgeUpdate> {
30
- /** One page, with the `total` the filter matches. */
31
- page(params?: KnowledgeListParams, options?: RequestOptions): Promise<KnowledgeSearchList>;
29
+ /** One page, with the `next_cursor` of the next. */
30
+ page(params?: KnowledgeListParams, options?: RequestOptions): Promise<KnowledgePage>;
32
31
  /** Every entry the filter matches, page after page (200 a page by default). */
33
- list(params?: Omit<KnowledgeListParams, "offset">, options?: RequestOptions): AsyncGenerator<KnowledgeInfo, void, undefined>;
32
+ list(params?: Omit<KnowledgeListParams, "cursor">, options?: RequestOptions): AsyncGenerator<KnowledgeInfo, void, undefined>;
34
33
  /** A written entry, indexed after it is saved. */
35
34
  create(body: KnowledgeCreate, options?: RequestOptions): Promise<KnowledgeInfo>;
36
35
  /** Many written entries at once: what was made, and what was refused. */
@@ -48,11 +47,11 @@ export declare class Knowledge extends Waitable<KnowledgeInfo, KnowledgeUpdate>
48
47
  /** Reads its page again now. */
49
48
  refresh(id: string, options?: RequestOptions): Promise<KnowledgeInfo>;
50
49
  /** Replaces the agents that may look it up. */
51
- setAgents(id: string, body: KnowledgeAssignment, options?: RequestOptions): Promise<KnowledgeAssigned>;
50
+ setAgents(id: string, body: KnowledgeAgentsUpdate, options?: RequestOptions): Promise<KnowledgeAssigned>;
52
51
  /** What an agent would find for a question. */
53
52
  search(body: KnowledgeSearchRequest, options?: RequestOptions): Promise<KnowledgeSearchResult>;
54
- /** The categories in use. */
55
- categories(options?: RequestOptions): Promise<KnowledgeCategories>;
53
+ /** The categories in use, in one page. */
54
+ categories(options?: RequestOptions): Promise<KnowledgeCategoryPage>;
56
55
  /** What was looked up, and what went unanswered: 7 days by default. */
57
56
  insights(params?: KnowledgeInsightsParams, options?: RequestOptions): Promise<KnowledgeInsights>;
58
57
  }
@@ -10,28 +10,23 @@
10
10
  * - **`upload(file, fields)`** is the ticket, the `PUT` and `fromFile` in
11
11
  * one (`upload.ts`); the file's extension says how it is read, 10 MB at
12
12
  * most.
13
- * - **`list()` pages by offset**, 200 a page, with the `total` the filter
14
- * matches.
13
+ * - **`list()` pages by cursor**, 200 a page.
15
14
  * - **`update` with `expected_version`** refuses an edit made over
16
- * somebody else's (409); `delete` answers the entry as it was, and
17
- * `null` for one already gone.
15
+ * somebody else's (409); `delete` answers `true`, and `false` for one
16
+ * already gone.
18
17
  */
19
- import { byOffset } from "../paging.js";
20
18
  import { put, sizeOf, typeOf, } from "../upload.js";
21
19
  import { Waitable } from "./resource.js";
22
- /** The route's own page bound. */
20
+ /** The route's largest page. */
23
21
  const PAGE = 200;
24
22
  export class Knowledge extends Waitable {
25
- /** One page, with the `total` the filter matches. */
23
+ /** One page, with the `next_cursor` of the next. */
26
24
  page(params = {}, options) {
27
25
  return this.send("GET", "", options, params);
28
26
  }
29
27
  /** Every entry the filter matches, page after page (200 a page by default). */
30
28
  list(params = {}, options) {
31
- return byOffset(async (offset, limit) => {
32
- const page = await this.page({ ...params, offset, limit }, options);
33
- return { items: page.entries, total: page.total };
34
- }, params.limit ?? PAGE);
29
+ return this.each("", params, options, PAGE);
35
30
  }
36
31
  /** A written entry, indexed after it is saved. */
37
32
  create(body, options) {
@@ -58,7 +53,7 @@ export class Knowledge extends Waitable {
58
53
  const { content_type, ...body } = fields;
59
54
  const ticket = await this.uploadUrl({
60
55
  filename: body.filename,
61
- size: sizeOf(file, options.size) ?? null,
56
+ size_bytes: sizeOf(file, options.size) ?? null,
62
57
  content_type: typeOf(file, content_type) || null,
63
58
  }, options);
64
59
  await put(this.api, ticket, file, ticket.content_type, options);
@@ -80,7 +75,7 @@ export class Knowledge extends Waitable {
80
75
  search(body, options) {
81
76
  return this.send("POST", "/search", options, body);
82
77
  }
83
- /** The categories in use. */
78
+ /** The categories in use, in one page. */
84
79
  categories(options) {
85
80
  return this.send("GET", "/categories", options);
86
81
  }
@@ -1,21 +1,3 @@
1
- /**
2
- * What every resource shares, as a ladder of bases each method is written
3
- * once in — the SDK's size is a budget (`test/dist.test.ts`):
4
- *
5
- * - **`Resource`**: the transport, and the path its routes start with.
6
- * `send` takes the rest of the path, and `at(id)` is one item's, the id
7
- * encoded (an id the caller holds may carry `/` or `?`).
8
- * - **`Readable`**: `get(id)`; **`Polled`** adds `wait(id)` (`wait.ts`),
9
- * for a generation, which is read and waited on and nothing else.
10
- * - **`Editable`**: `update(id, body)` (`PATCH`) and `delete(id)`, which
11
- * answers `null` when there is nothing by that id — already done.
12
- * - **`Waitable`**: an editable whose items are jobs — a voice, an asset,
13
- * a knowledge entry — with `wait(id)` and `settled`, which an upload
14
- * answers through.
15
- *
16
- * Fields are `declare`d, not defined: each is assigned in a constructor,
17
- * and a definition is bytes in every customer's bundle.
18
- */
19
1
  import { type Method, type RequestOptions, type Transport } from "../transport.js";
20
2
  import type { UploadOptions } from "../upload.js";
21
3
  import { type Job } from "../wait.js";
@@ -27,6 +9,12 @@ export declare class Resource {
27
9
  protected send<T>(method: Method, rest: string, options?: RequestOptions, data?: unknown): Promise<T>;
28
10
  /** The rest of item `id`'s path, and of what follows it. */
29
11
  protected at(id: string, rest?: string): string;
12
+ /**
13
+ * Every item of the list at `base` + `rest`, page after page: `query`,
14
+ * then each page's cursor. `limit` is the route's largest page unless
15
+ * the query names one — fewer calls for a long list.
16
+ */
17
+ protected each<T>(rest: string, query?: object, options?: RequestOptions, limit?: number): AsyncGenerator<T, void, undefined>;
30
18
  }
31
19
  export declare class Readable<Item> extends Resource {
32
20
  /** One of them. A `NotFoundError` when there is none. */
@@ -36,15 +24,17 @@ export declare class Polled<Item extends Job> extends Readable<Item> {
36
24
  /** Reads it until its `status` is final (`wait.ts`). */
37
25
  wait(id: string, options?: RequestOptions): Promise<Item>;
38
26
  }
39
- export declare class Editable<Item, Update, Updated = Item, Deleted = Item> extends Readable<Item> {
27
+ export declare class Editable<Item, Update, Updated = Item, Deleted = boolean> extends Readable<Item> {
40
28
  /** Changes what `body` gives; absent fields are untouched. */
41
29
  update(id: string, body: Update, options?: RequestOptions): Promise<Updated>;
42
- /** Deletes it; `null` when there is none by that id — already done. */
43
- delete(id: string, options?: RequestOptions): Promise<Deleted | null>;
30
+ /** Deletes it: `true`, or `false` when there is none by that id — already done. */
31
+ delete(id: string, options?: RequestOptions): Promise<Deleted>;
32
+ /** The `DELETE` itself: its answer, `undefined` for a 204, `null` for a 404. */
33
+ protected drop<T>(id: string, options?: RequestOptions): Promise<T | null>;
44
34
  }
45
35
  export declare class Waitable<Item extends Job & {
46
36
  id: string;
47
- }, Update, Updated = Item, Deleted = Item> extends Editable<Item, Update, Updated, Deleted> {
37
+ }, Update, Updated = Item> extends Editable<Item, Update, Updated> {
48
38
  /** Reads it until its `status` is final (`wait.ts`). */
49
39
  wait(id: string, options?: RequestOptions): Promise<Item>;
50
40
  /** `item` as made, or as it ended when `options.wait` says to wait. */
@@ -4,11 +4,15 @@
4
4
  *
5
5
  * - **`Resource`**: the transport, and the path its routes start with.
6
6
  * `send` takes the rest of the path, and `at(id)` is one item's, the id
7
- * encoded (an id the caller holds may carry `/` or `?`).
7
+ * encoded (an id the caller holds may carry `/` or `?`). `each` is a
8
+ * list's every item, page after page (`paging.ts`).
8
9
  * - **`Readable`**: `get(id)`; **`Polled`** adds `wait(id)` (`wait.ts`),
9
10
  * for a generation, which is read and waited on and nothing else.
10
- * - **`Editable`**: `update(id, body)` (`PATCH`) and `delete(id)`, which
11
- * answers `null` when there is nothing by that id — already done.
11
+ * - **`Editable`**: `update(id, body)` (`PATCH`) and `delete(id)`. A
12
+ * delete of one thing is a 204 with no body, so it answers `true`, and
13
+ * `false` when there is nothing by that id — already done. One that
14
+ * reports what went with it (an end user's, a folder's) answers those
15
+ * counts, and `null` for nothing by that id: the same rule, with a body.
12
16
  * - **`Waitable`**: an editable whose items are jobs — a voice, an asset,
13
17
  * a knowledge entry — with `wait(id)` and `settled`, which an upload
14
18
  * answers through.
@@ -16,6 +20,7 @@
16
20
  * Fields are `declare`d, not defined: each is assigned in a constructor,
17
21
  * and a definition is bytes in every customer's bundle.
18
22
  */
23
+ import { pages } from "../paging.js";
19
24
  import { orNull, segment, } from "../transport.js";
20
25
  import { poll } from "../wait.js";
21
26
  export class Resource {
@@ -31,6 +36,14 @@ export class Resource {
31
36
  at(id, rest = "") {
32
37
  return `/${segment(id)}${rest}`;
33
38
  }
39
+ /**
40
+ * Every item of the list at `base` + `rest`, page after page: `query`,
41
+ * then each page's cursor. `limit` is the route's largest page unless
42
+ * the query names one — fewer calls for a long list.
43
+ */
44
+ each(rest, query, options, limit) {
45
+ return pages((cursor) => this.send("GET", rest, options, { limit, ...query, cursor }));
46
+ }
34
47
  }
35
48
  export class Readable extends Resource {
36
49
  /** One of them. A `NotFoundError` when there is none. */
@@ -49,8 +62,12 @@ export class Editable extends Readable {
49
62
  update(id, body, options) {
50
63
  return this.send("PATCH", this.at(id), options, body);
51
64
  }
52
- /** Deletes it; `null` when there is none by that id — already done. */
65
+ /** Deletes it: `true`, or `false` when there is none by that id — already done. */
53
66
  delete(id, options) {
67
+ return this.drop(id, options).then((gone) => gone !== null);
68
+ }
69
+ /** The `DELETE` itself: its answer, `undefined` for a 204, `null` for a 404. */
70
+ drop(id, options) {
54
71
  return orNull(this.send("DELETE", this.at(id), options));
55
72
  }
56
73
  }
@@ -6,10 +6,10 @@
6
6
  *
7
7
  * - **`tts.create(body)` answers the audio whole** (an `ArrayBuffer`, WAV
8
8
  * or raw `pcm`) **or, with `stream: true`, as it is made** (a
9
- * `ReadableStream` of `pcm`), beside what its headers say. A whole
10
- * answer names its sample rate (`X-Sample-Rate`); a stream names none,
11
- * so its rate is the one asked for, 24 000 Hz unset (the API's default,
12
- * `@ino/core/speech/catalog`; left-overs #111).
9
+ * `ReadableStream` of `pcm`), beside what its headers say. Both name
10
+ * their sample rate (`X-Sample-Rate`); a stream from an API before
11
+ * 0.31.10 named none, so without the header its rate is the one asked
12
+ * for, 24 000 Hz unset (the API's default, `@ino/core/speech/catalog`).
13
13
  * - **`stt.create(audio, params)` sends the audio as the body**, the
14
14
  * options as the query — the route's raw form, which a stream can be
15
15
  * sent as, unlike a multipart one. It answers the transcript, or with
@@ -17,7 +17,7 @@
17
17
  * only). Up to 25 MB.
18
18
  * - **A stream's timeout ends at its headers**; `signal` still ends it.
19
19
  */
20
- import type { StreamDone, StreamError, StreamSegment, StreamTranscript, TranscribeData, TranscriptWord, TtsRequest } from "../gen/index.js";
20
+ import type { TranscribeData, Transcription, TranscriptionStreamLine, TtsRequest } from "../gen/index.js";
21
21
  import type { RequestOptions } from "../transport.js";
22
22
  import { type UploadData } from "../upload.js";
23
23
  import { Resource } from "./resource.js";
@@ -51,23 +51,12 @@ export type SttParams = Omit<TranscribeData["body"], "file"> & {
51
51
  content_type?: string;
52
52
  };
53
53
  /**
54
- * `POST /v1/stt`'s whole answer. The document declares no schema for it
55
- * (left-overs #112), so this is the route's own (`transcript()` in
56
- * `apps/api/src/routes/stt.ts`), written out; `apps/api/test/sdk.test.ts`
57
- * holds the two together.
54
+ * `POST /v1/stt`'s whole answer, as the document declares it since api
55
+ * 0.31.10 (`Transcription`). The name the SDK has always exported.
58
56
  */
59
- export interface Transcript {
60
- text: string;
61
- language_code: string | null;
62
- language_probability: number | null;
63
- /** What was charged, per minute. */
64
- duration_seconds: number;
65
- model: string;
66
- /** With `timestamps: true`; `null` otherwise. */
67
- words: TranscriptWord[] | null;
68
- }
69
- /** One line of a streamed transcript. */
70
- export type SttEvent = StreamSegment | StreamTranscript | StreamError | StreamDone;
57
+ export type Transcript = Transcription;
58
+ /** One line of a streamed transcript: a `segment`, its `transcript` or `error`, and the `done`. */
59
+ export type SttEvent = TranscriptionStreamLine;
71
60
  export declare class Stt extends Resource {
72
61
  /** The transcript as it is made: an iterator of its events. */
73
62
  create(audio: UploadData, params: SttParams & {
@@ -6,10 +6,10 @@
6
6
  *
7
7
  * - **`tts.create(body)` answers the audio whole** (an `ArrayBuffer`, WAV
8
8
  * or raw `pcm`) **or, with `stream: true`, as it is made** (a
9
- * `ReadableStream` of `pcm`), beside what its headers say. A whole
10
- * answer names its sample rate (`X-Sample-Rate`); a stream names none,
11
- * so its rate is the one asked for, 24 000 Hz unset (the API's default,
12
- * `@ino/core/speech/catalog`; left-overs #111).
9
+ * `ReadableStream` of `pcm`), beside what its headers say. Both name
10
+ * their sample rate (`X-Sample-Rate`); a stream from an API before
11
+ * 0.31.10 named none, so without the header its rate is the one asked
12
+ * for, 24 000 Hz unset (the API's default, `@ino/core/speech/catalog`).
13
13
  * - **`stt.create(audio, params)` sends the audio as the body**, the
14
14
  * options as the query — the route's raw form, which a stream can be
15
15
  * sent as, unlike a multipart one. It answers the transcript, or with
@@ -11,7 +11,7 @@
11
11
  * - **`file(id, name)` needs no key** and answers the bytes; a template's
12
12
  * URLs already name each file and its `v`, which makes it cacheable.
13
13
  */
14
- import type { GenerationInfo, TemplateGenerateRequest, TemplateInfo, TemplateList } from "../gen/index.js";
14
+ import type { GenerationInfo, TemplateGenerateRequest, TemplateInfo, TemplatePage } from "../gen/index.js";
15
15
  import { type RequestOptions, type Transport } from "../transport.js";
16
16
  import type { Generations } from "./generations.js";
17
17
  import { Readable } from "./resource.js";
@@ -24,8 +24,10 @@ export interface TemplateFile {
24
24
  export declare class Templates extends Readable<TemplateInfo> {
25
25
  private readonly generations;
26
26
  constructor(api: Transport, base: string, generations: Generations);
27
+ /** The page of templates, as the route answers it: all of them, `next_cursor` `null`. */
28
+ page(options?: RequestOptions): Promise<TemplatePage>;
27
29
  /** Every template this organisation may generate from. */
28
- list(options?: RequestOptions): Promise<TemplateList>;
30
+ list(options?: RequestOptions): AsyncGenerator<TemplateInfo, void, undefined>;
29
31
  /** Generates from it, charged when accepted: the `gen_…` at once. */
30
32
  create(id: string, body?: TemplateGenerateRequest, options?: RequestOptions): Promise<GenerationInfo>;
31
33
  /** Generates from it and waits: the generation once it is final. */
@@ -20,9 +20,13 @@ export class Templates extends Readable {
20
20
  super(api, base);
21
21
  this.generations = generations;
22
22
  }
23
+ /** The page of templates, as the route answers it: all of them, `next_cursor` `null`. */
24
+ page(options) {
25
+ return this.send("GET", "", options);
26
+ }
23
27
  /** Every template this organisation may generate from. */
24
28
  list(options) {
25
- return this.send("GET", "", options);
29
+ return this.each("", {}, options);
26
30
  }
27
31
  /** Generates from it, charged when accepted: the `gen_…` at once. */
28
32
  create(id, body = {}, options) {
@@ -3,10 +3,10 @@
3
3
  * window of days, and `GET /v1/plan`, its plan and balance (`null` with
4
4
  * no subscription). Read-only, unmetered.
5
5
  */
6
- import type { OrgPlan, UsageData, UsageResponse } from "../gen/index.js";
6
+ import type { GetUsageData, OrgPlan, UsageResponse } from "../gen/index.js";
7
7
  import type { RequestOptions } from "../transport.js";
8
8
  import { Resource } from "./resource.js";
9
- export type UsageParams = NonNullable<UsageData["query"]>;
9
+ export type UsageParams = NonNullable<GetUsageData["query"]>;
10
10
  export declare class Usage extends Resource {
11
11
  /** Requests and seconds by capability, by day and by key: 30 days by default. */
12
12
  get(params?: UsageParams, options?: RequestOptions): Promise<UsageResponse>;
@@ -9,9 +9,11 @@
9
9
  * create in one (`upload.ts`); `consent: true` says you may clone it.
10
10
  * - **`design(body)` answers previews, not a voice**: keep one with
11
11
  * `saveDesign`. It is a provider call held open, and charged.
12
- * - **`delete` answers `false` when there was no such voice**: done.
12
+ * - **`delete` answers `true`**, and `false` when there was no such
13
+ * voice: done.
14
+ * - **`list()` reads one page**: the route answers every voice in it.
13
15
  */
14
- import type { VoiceDesignRequest, VoiceDesignResult, VoiceDesignSaveRequest, VoiceDetail, VoiceInfo, VoiceUpdate, VoiceUpload, VoiceUploadRequest, VoiceUploadTicket } from "../gen/index.js";
16
+ import type { VoiceDesignRequest, VoiceDesignResult, VoiceDesignSaveRequest, VoiceDetail, VoiceInfo, VoiceInfoPage, VoiceUpdate, VoiceUpload, VoiceUploadRequest, VoiceUploadTicket } from "../gen/index.js";
15
17
  import type { RequestOptions } from "../transport.js";
16
18
  import { type UploadData, type UploadOptions } from "../upload.js";
17
19
  import { Waitable } from "./resource.js";
@@ -21,17 +23,17 @@ export type VoiceUploadFields = Omit<VoiceUpload, "upload_id"> & {
21
23
  content_type?: string;
22
24
  };
23
25
  /** `update` renames a voice, or changes its description or defaults. */
24
- export declare class Voices extends Waitable<VoiceDetail, VoiceUpdate, VoiceDetail, boolean> {
26
+ export declare class Voices extends Waitable<VoiceDetail, VoiceUpdate> {
27
+ /** The page of voices, as the route answers it: all of them, `next_cursor` `null`. */
28
+ page(options?: RequestOptions): Promise<VoiceInfoPage>;
25
29
  /** Every voice this organisation may speak with. */
26
- list(options?: RequestOptions): Promise<VoiceInfo[]>;
30
+ list(options?: RequestOptions): AsyncGenerator<VoiceInfo, void, undefined>;
27
31
  /** Clones a recording already `PUT` to its ticket: `pending` at once. */
28
32
  create(body: VoiceUpload, options?: RequestOptions): Promise<VoiceDetail>;
29
33
  /** A signed URL to `PUT` a recording to, with exactly this type. */
30
34
  uploadUrl(body: VoiceUploadRequest, options?: RequestOptions): Promise<VoiceUploadTicket>;
31
35
  /** Uploads a recording and clones it: `uploadUrl`, the `PUT`, `create`. */
32
36
  upload(recording: UploadData, fields: VoiceUploadFields, options?: UploadOptions): Promise<VoiceDetail>;
33
- /** Deletes it: `true`, or `false` when there was none by that id (a 204 has no body). */
34
- delete(id: string, options?: RequestOptions): Promise<boolean>;
35
37
  /** Speaks its samples again, one per model. */
36
38
  resample(id: string, options?: RequestOptions): Promise<VoiceDetail>;
37
39
  /** Previews of a voice made from a description; charged. */
@@ -9,15 +9,21 @@
9
9
  * create in one (`upload.ts`); `consent: true` says you may clone it.
10
10
  * - **`design(body)` answers previews, not a voice**: keep one with
11
11
  * `saveDesign`. It is a provider call held open, and charged.
12
- * - **`delete` answers `false` when there was no such voice**: done.
12
+ * - **`delete` answers `true`**, and `false` when there was no such
13
+ * voice: done.
14
+ * - **`list()` reads one page**: the route answers every voice in it.
13
15
  */
14
16
  import { needType, put, } from "../upload.js";
15
17
  import { Waitable } from "./resource.js";
16
18
  /** `update` renames a voice, or changes its description or defaults. */
17
19
  export class Voices extends Waitable {
20
+ /** The page of voices, as the route answers it: all of them, `next_cursor` `null`. */
21
+ page(options) {
22
+ return this.send("GET", "", options);
23
+ }
18
24
  /** Every voice this organisation may speak with. */
19
25
  list(options) {
20
- return this.send("GET", "", options);
26
+ return this.each("", {}, options);
21
27
  }
22
28
  /** Clones a recording already `PUT` to its ticket: `pending` at once. */
23
29
  create(body, options) {
@@ -34,10 +40,6 @@ export class Voices extends Waitable {
34
40
  await put(this.api, ticket, recording, ticket.content_type, options);
35
41
  return this.settled(await this.create({ ...body, upload_id: ticket.upload_id }, options), options);
36
42
  }
37
- /** Deletes it: `true`, or `false` when there was none by that id (a 204 has no body). */
38
- async delete(id, options) {
39
- return (await super.delete(id, options)) !== null;
40
- }
41
43
  /** Speaks its samples again, one per model. */
42
44
  resample(id, options) {
43
45
  return this.send("POST", this.at(id, "/sample"), options);
@@ -49,9 +49,13 @@ export interface Answer<T> {
49
49
  }
50
50
  /** The longest `Retry-After` slept through; past it the 429 is thrown. */
51
51
  export declare const MAX_RETRY_AFTER_S = 60;
52
- /** A path parameter, encoded: an `external_id` may hold `/` or `?`. */
53
- export declare const segment: (value: string) => string;
54
- /** A request id of the API's own shape: 16 hex. */
52
+ /** A path parameter or a query's value, encoded: an `external_id` may hold `/` or `?`. */
53
+ export declare const segment: (value: string | number | boolean) => string;
54
+ /**
55
+ * A request id of the API's own shape: 16 hex. From `Math.random`, not
56
+ * `crypto`, which React Native lacks (this file is the client SDK's too):
57
+ * an id names a call in the logs and is no secret.
58
+ */
55
59
  export declare const newRequestId: () => string;
56
60
  /**
57
61
  * How long to wait before retry `attempt` (1, 2, …): the doubling backoff
package/dist/transport.js CHANGED
@@ -26,6 +26,10 @@
26
26
  * wins, up to a minute. A longer one is thrown at once as a
27
27
  * `RateLimitError` with its `retryAfter`: a call that sleeps for an hour
28
28
  * is not one a caller can reason about.
29
+ * - **The client SDK runs this file too** (`sdks/client/src/transport.ts`,
30
+ * a copy its `test/copies.test.ts` holds identical), on React Native's
31
+ * Hermes as well: so no `crypto`, `URLSearchParams`, `AbortSignal.any`
32
+ * or global `ReadableStream` taken for granted, each of which it lacks.
29
33
  */
30
34
  import { ConnectionError, errorFromResponse, NotFoundError, PlatformError, TimeoutError, } from "./errors.js";
31
35
  const RETRIED_STATUS = new Set([429, 502, 503, 504]);
@@ -33,10 +37,14 @@ const BASE_DELAY_MS = 500;
33
37
  const MAX_DELAY_MS = 8_000;
34
38
  /** The longest `Retry-After` slept through; past it the 429 is thrown. */
35
39
  export const MAX_RETRY_AFTER_S = 60;
36
- /** A path parameter, encoded: an `external_id` may hold `/` or `?`. */
40
+ /** A path parameter or a query's value, encoded: an `external_id` may hold `/` or `?`. */
37
41
  export const segment = (value) => encodeURIComponent(value);
38
- /** A request id of the API's own shape: 16 hex. */
39
- export const newRequestId = () => crypto.randomUUID().replaceAll("-", "").slice(0, 16);
42
+ /**
43
+ * A request id of the API's own shape: 16 hex. From `Math.random`, not
44
+ * `crypto`, which React Native lacks (this file is the client SDK's too):
45
+ * an id names a call in the logs and is no secret.
46
+ */
47
+ export const newRequestId = () => "x".repeat(16).replace(/x/g, () => ((Math.random() * 16) | 0).toString(16));
40
48
  /**
41
49
  * How long to wait before retry `attempt` (1, 2, …): the doubling backoff
42
50
  * less up to a quarter, or `Retry-After`'s seconds when the API sent one.
@@ -76,7 +84,7 @@ export function parsed(text, status, requestId) {
76
84
  catch (cause) {
77
85
  throw new PlatformError({
78
86
  status,
79
- type: "invalid_response",
87
+ code: "invalid_response",
80
88
  message: `HTTP ${status} with a body that is not JSON.`,
81
89
  requestId,
82
90
  cause,
@@ -101,7 +109,8 @@ export class Transport {
101
109
  }
102
110
  async request(call, options = {}) {
103
111
  const requestId = options.requestId ?? newRequestId();
104
- const once = call.raw instanceof ReadableStream;
112
+ // A stream by its shape: not every runtime has a global `ReadableStream`.
113
+ const once = "getReader" in Object(call.raw);
105
114
  const maxRetries = once
106
115
  ? 0
107
116
  : (options.maxRetries ?? this.config.maxRetries);
@@ -154,13 +163,13 @@ export class Transport {
154
163
  throw outcome.kind === "timeout"
155
164
  ? new TimeoutError({
156
165
  status: 0,
157
- type: "timeout",
166
+ code: "timeout",
158
167
  message: "The API did not answer in time.",
159
168
  requestId,
160
169
  })
161
170
  : new ConnectionError({
162
171
  status: 0,
163
- type: "connection_error",
172
+ code: "connection_error",
164
173
  message: "Could not reach the API.",
165
174
  requestId,
166
175
  cause: outcome.error,
@@ -170,27 +179,37 @@ export class Transport {
170
179
  }
171
180
  }
172
181
  url(call) {
173
- const params = new URLSearchParams();
182
+ // By hand: React Native's `URLSearchParams` is partial.
183
+ let query = "";
174
184
  for (const [key, value] of Object.entries(call.query ?? {})) {
175
- if (value !== undefined && value !== null)
176
- params.set(key, String(value));
185
+ if (value != null) {
186
+ query += `${query ? "&" : "?"}${key}=${segment(value)}`;
187
+ }
177
188
  }
178
- const query = params.toString();
179
189
  const base = /^https?:/.test(call.path) ? "" : this.config.baseUrl;
180
- return `${base}${call.path}${query ? `?${query}` : ""}`;
190
+ return base + call.path + query;
181
191
  }
182
192
  /**
183
193
  * One send and its body read, under this attempt's timer — which stops
184
194
  * at the headers of a stream the caller reads (and of nothing else).
185
195
  */
186
196
  async attempt(url, init, read, options) {
197
+ // The caller's signal joined to the timer by hand, its reason carried
198
+ // over: `AbortSignal.any` is not React Native's. Left joined for a
199
+ // stream, whose body the caller's abort still ends.
200
+ const { signal } = options;
187
201
  const timer = new AbortController();
188
- const clock = setTimeout(() => timer.abort(), options.timeoutMs ?? this.config.timeoutMs);
189
- const signal = options.signal
190
- ? AbortSignal.any([options.signal, timer.signal])
191
- : timer.signal;
202
+ const stop = () => timer.abort(signal?.reason);
203
+ const clock = setTimeout(stop, options.timeoutMs ?? this.config.timeoutMs);
204
+ signal?.addEventListener("abort", stop);
205
+ // Already aborted: no event will come, and `fetch` sends nothing.
206
+ if (signal?.aborted)
207
+ stop();
192
208
  try {
193
- const response = await this.config.fetch(url, { ...init, signal });
209
+ const response = await this.config.fetch(url, {
210
+ ...init,
211
+ signal: timer.signal,
212
+ });
194
213
  const body = !response.ok
195
214
  ? await response.text()
196
215
  : read === "stream"
@@ -202,14 +221,16 @@ export class Transport {
202
221
  }
203
222
  catch (error) {
204
223
  // The caller's abort is the caller's, whatever else happened.
205
- if (options.signal?.aborted)
206
- throw options.signal.reason;
224
+ if (signal?.aborted)
225
+ throw signal.reason;
207
226
  if (timer.signal.aborted)
208
227
  return { kind: "timeout" };
209
228
  return { kind: "network", error };
210
229
  }
211
230
  finally {
212
231
  clearTimeout(clock);
232
+ if (read !== "stream")
233
+ signal?.removeEventListener("abort", stop);
213
234
  }
214
235
  }
215
236
  }