@pipelex/sdk 0.61.0 → 0.63.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/README.md CHANGED
@@ -179,9 +179,14 @@ const copy = await client.copyMethod(
179
179
  if (copy.outcome === "copied") console.log(copy.method.copied_from); // the original it names
180
180
 
181
181
  // The template catalog: the public methods every organization reads, in the platform's order, with
182
- // the categories that have a template and each card's thumbnails behind short-lived links.
182
+ // the categories that have a template and each card's thumbnails behind short-lived links. A
183
+ // category's `labels` name it by language tag and may lack a language, in which case a client
184
+ // shows `label`, the English one.
183
185
  const templates = await client.listMethodTemplates({ category: "human-resources", limit: 8 });
184
- for (const tab of templates.categories) console.log(tab.slug, tab.label);
186
+ for (const tab of templates.categories) console.log(tab.slug, tab.labels.fr ?? tab.label);
187
+ // The catalog's whole list of categories, a category no template shows included.
188
+ const { categories } = await client.listMethodCategories();
189
+ console.log(categories.map((category) => category.slug));
185
190
  // Another organization's public method reads in a narrower shape, its latest published version
186
191
  // and no draft: `updated_at` is what tells it from one of your own.
187
192
  const first = templates.items[0];
@@ -297,8 +302,8 @@ These pages ship inside the published package, so a reader who has only installe
297
302
  | [`docs/files.md`](./docs/files.md) | An organization's files: `listFiles` and `iterateFiles`, the row, the whole record and its provenance, the query and its repeated filters, paging through filtered pages, one file and its uses, the records of an address, the rename rule, links that name the file, a zip of files and its caps, a run's files, the copy into a method's draft, and an upload recorded as a method's test file with the refusals that come with it |
298
303
  | [`docs/method-examples.md`](./docs/method-examples.md) | A method's saved examples: the create, the paged list and its iterator, the edit that raises the input revision, the latest-run write and its `recorded` or `stale` verdict, the build assistant's save and its `kept` or `created` verdict, how an integer past `Number.MAX_SAFE_INTEGER` in the inputs stays exact, and what is refused before sending |
299
304
  | [`docs/method-copy.md`](./docs/method-copy.md) | Copying a method into another organization or duplicating it: what a copy holds and the stored files it copies, the `copied` or `refused` verdict, every refusal, the copy's idempotency key, and `copied_from` |
300
- | [`docs/public-methods.md`](./docs/public-methods.md) | Public methods and the template catalog: `listMethodTemplates` and its iterator, the ordering, the categories and the thumbnails' links, another organization's public method read in its narrower shape, the copy of a public method, `visibility` and `categories` on a method, and the refusals a public method's publish adds |
301
- | [`docs/organization-branding.md`](./docs/organization-branding.md) | An organization's logos and colours: `getBranding`, which every member and API key reads, `updateOrganizationBranding`, an admin's write that sets, clears or keeps each key, and how a refused logo or colour names its slot in `errors` so a settings screen shows the sentence under it |
305
+ | [`docs/public-methods.md`](./docs/public-methods.md) | Public methods and the template catalog: `listMethodTemplates` and its iterator, the ordering, the categories with their labels by language and `listMethodCategories`, the thumbnails' links, another organization's public method read in its narrower shape, the copy of a public method, `visibility` and `categories` on a method, and the refusals a public method's publish adds |
306
+ | [`docs/organization-branding.md`](./docs/organization-branding.md) | An organization's logos, colours, app style and typeface, and its white-label entitlement: `getBranding`, which every member and API key reads, `updateOrganizationBranding`, a write by the `admin` or the `member` role that sets, clears or keeps each key, and how a refused logo, colour, style or typeface names its field so a settings screen shows the sentence under it |
302
307
  | [`docs/exact-integers.md`](./docs/exact-integers.md) | Which reads keep an integer past `Number.MAX_SAFE_INTEGER` exact, as a `bigint` (a run's inputs and outputs, an example's inputs), how a request body writes it back with its digits, and `parseExactJson` and `stringifyExactJson` for code showing such data as JSON text |
303
308
  | [`docs/crate-routes.md`](./docs/crate-routes.md) | `resolve`, `codegen` and `pipeIo` — the method's tree as the protocol's `files` map, with its dependencies, the normalized crate, the stamped types, and a method's I/O artifacts without a validation — and the offline `runCodegenCheck` that guards a committed tree |
304
309
  | [`docs/client-identification.md`](./docs/client-identification.md) | The `User-Agent` every API request carries, and `appInfo`, the option that puts your program's name in front of it |
@@ -15,7 +15,7 @@ import type { CommandIO } from "./io.js";
15
15
  * update notice and npm 12's run notices off the script's stderr, which carries the command's
16
16
  * own lines, while npm's warnings and errors still print.
17
17
  */
18
- export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.61.0";
18
+ export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.63.0";
19
19
  export declare const SCRIPT_WRITER = "@pipelex/sdk";
20
20
  export declare const SCRIPT_NEEDS = "Node 22.12 or later";
21
21
  /** Run `pipelex-sdk script` and return its exit code. */
package/dist/client.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { MTHDSProtocol, ModelCategory, ModelDeck, RunOptions, StartOptions, VersionInfo } from "mthds/protocol";
2
2
  import type { CodegenRequest, CodegenResponse, CrateRouteOptions, DictPipeOutput, FormatResponse, LintResponse, ModelCheckCategory, ModelReferenceVerdict, PipeIORequest, PipeIOResponse, PipelexRunResultStart, PipelexValidateSource, PipelexValidationResult, ResolveRequest, ResolveResponse, ValidateMethodSelector } from "./models.js";
3
3
  import { type GetRunResultOptions, type RunProgress, type RunRead, type RunResults, type RunResultState, type StartAndWaitForResultOptions, type WaitForResultOptions } from "./runs.js";
4
- import type { BillingPortalResponse, ChangePlanResponse, CheckoutResponse, DraftFileFromFileInput, DraftFileFromFileResult, FileArchive, FileArchiveRequest, FileDetail, FileLinkTarget, FileLinksResult, FilePage, FileRecord, FileRecordList, FileRenameInput, FileSummary, GetFileQuery, GetFilesByUriOptions, InvoiceView, Membership, MembershipsResponse, OrganizationBranding, OrganizationBrandingUpdate, ListFilesQuery, ListMethodExamplesQuery, ListMethodsQuery, ListMethodTemplatesQuery, ListMethodVersionsQuery, ListRunFilesQuery, MethodCopyInput, MethodCopyResult, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleAssistantSaveInput, MethodExampleAssistantSaveResult, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodTemplate, MethodTemplatePage, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, PublicMethodDetail, PipelexApiKeyCreated, PipelexApiKeyList, ListRunsQuery, RunHistoryItem, RunDetail, RunFilesPage, RunPage, PlanView, ResolvedStorageUrl, SavedMethodFile, SubscriptionResponse, UpdateRunInput, UploadGrant, UploadGrantInput, UploadInput, UploadedFile, UserProfile } from "./product-models.js";
4
+ import type { BillingPortalResponse, ChangePlanResponse, CheckoutResponse, DraftFileFromFileInput, DraftFileFromFileResult, FileArchive, FileArchiveRequest, FileDetail, FileLinkTarget, FileLinksResult, FilePage, FileRecord, FileRecordList, FileRenameInput, FileSummary, GetFileQuery, GetFilesByUriOptions, InvoiceView, Membership, MembershipsResponse, OrganizationBranding, OrganizationBrandingUpdate, ListFilesQuery, ListMethodExamplesQuery, ListMethodsQuery, ListMethodTemplatesQuery, ListMethodVersionsQuery, ListRunFilesQuery, MethodCategoryList, MethodCopyInput, MethodCopyResult, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleAssistantSaveInput, MethodExampleAssistantSaveResult, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodTemplate, MethodTemplatePage, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, PublicMethodDetail, PipelexApiKeyCreated, PipelexApiKeyList, ListRunsQuery, RunHistoryItem, RunDetail, RunFilesPage, RunPage, PlanView, ResolvedStorageUrl, SavedMethodFile, SubscriptionResponse, UpdateRunInput, UploadGrant, UploadGrantInput, UploadInput, UploadedFile, UserProfile } from "./product-models.js";
5
5
  import type { MethodTreeForms, MethodTreeLimits } from "./method-trees.js";
6
6
  import type { AppInfo } from "./user-agent.js";
7
7
  import type { WebhookDeliveryDetail, WebhookDeliveryList, WebhookReplayAccepted, WebhookReplayOptions, WebhookSecret, WebhookSecretRotateOptions } from "./webhooks.js";
@@ -996,8 +996,8 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
996
996
  * unknown or malformed name serves `default`, and the page's `ordering` names the one served,
997
997
  * `null` when there is none. `category` keeps one category's templates, by slug, in the same
998
998
  * order; an unknown slug keeps none. Whatever the filter, the page's `categories` names, with its
999
- * label, every category at least one template shows, in the catalog's order, so no tab drawn
1000
- * from it is ever empty.
999
+ * labels, every category at least one template shows, in the catalog's order, so no tab drawn
1000
+ * from it is ever empty; `listMethodCategories` reads the whole list.
1001
1001
  *
1002
1002
  * Each `MethodTemplate` carries the method's id and name, its latest published version's number,
1003
1003
  * description and categories, its organization's display name and each thumbnail with its size
@@ -1007,17 +1007,18 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1007
1007
  * Pass the page's `nextCursor` back as `cursor` with the same `ordering` and `category`, which the
1008
1008
  * cursor does not hold; `null` is the last page. `iterateMethodTemplates` follows it for you.
1009
1009
  *
1010
- * Throws `ApiResponseError`: `422` `request.invalid` for a `limit` outside 1 to 200 or a cursor of
1011
- * another listing, `422` `request.malformed` for a cursor that does not decode, `500`
1012
- * `runtime.misconfigured` when the templates organization the deployment names has no record or
1013
- * a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A page whose `items`
1014
- * or `categories` is not an array, whose `next_cursor` or `ordering` is neither a string nor
1015
- * `null`, a template lacking a member a card is drawn from (a string `method_id`, `name` and
1016
- * `organization_name`, a `description` that is a string or `null`, a positive integer
1017
- * `latest_version`, an array of string `categories`, and `thumbnails` each with a string `path`,
1018
- * `media_type`, `sha256`, `url` and `expires_at` and a positive integer `width` and `height`), and a
1019
- * category without a string `slug` and `label` are an answer the SDK cannot read, thrown as an
1020
- * `ApiResponseError` too, its links redacted.
1010
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` without an active organization, `422`
1011
+ * `request.invalid` for a `limit` outside 1 to 200 or a cursor of another listing, `422`
1012
+ * `request.malformed` for a cursor that does not decode, `500` `runtime.misconfigured` when the
1013
+ * templates organization the deployment names has no record or a link cannot be signed, and
1014
+ * `503` `runtime.unavailable`, safe to retry. A page whose `items` or `categories` is not an
1015
+ * array, whose `next_cursor` or `ordering` is neither a string nor `null`, a template lacking a
1016
+ * member a card is drawn from (a string `method_id`, `name` and `organization_name`, a
1017
+ * `description` that is a string or `null`, a positive integer `latest_version`, an array of
1018
+ * string `categories`, and `thumbnails` each with a string `path`, `media_type`, `sha256`, `url`
1019
+ * and `expires_at` and a positive integer `width` and `height`), and a category without a string
1020
+ * `slug` and `label` or whose `labels` is not an object of strings are an answer the SDK cannot
1021
+ * read, thrown as an `ApiResponseError` too, its links redacted.
1021
1022
  */
1022
1023
  listMethodTemplates(query?: ListMethodTemplatesQuery): Promise<MethodTemplatePage>;
1023
1024
  /**
@@ -1028,6 +1029,23 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1028
1029
  * read one page with `listMethodTemplates` when you need them.
1029
1030
  */
1030
1031
  iterateMethodTemplates(query?: Omit<ListMethodTemplatesQuery, "cursor">): AsyncGenerator<MethodTemplate, void, undefined>;
1032
+ /**
1033
+ * The catalog's whole list of categories — `GET /v1/method-categories`: every category an admin
1034
+ * of the platform put on it, in the catalog's order, whether or not a template shows it, the same
1035
+ * for every caller. Each `MethodCategory` carries its `slug`, the one a manifest declares, its
1036
+ * English `label`, and its `labels` by language tag, which may lack a language the platform
1037
+ * added after the category was stored; show `labels[locale] ?? label`.
1038
+ *
1039
+ * A template shows under the categories its manifest declares that are on this list, so this is
1040
+ * the list a method's categories are picked from. `listMethodTemplates` names, on every page, only
1041
+ * the categories some template shows, for the tabs a client draws.
1042
+ *
1043
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` without an active organization, and
1044
+ * `503` `runtime.unavailable`, safe to retry. An answer whose `categories` is not an array, or
1045
+ * holds a category without a string `slug` and `label` or whose `labels` is not an object of
1046
+ * strings, is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
1047
+ */
1048
+ listMethodCategories(): Promise<MethodCategoryList>;
1031
1049
  /**
1032
1050
  * Read one method with its files — `GET /v1/methods/{id}`. One of the caller's organization's
1033
1051
  * methods answers a `MethodDetail`: its identity, its draft
@@ -1597,32 +1615,41 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1597
1615
  }): Promise<Membership>;
1598
1616
  /**
1599
1617
  * The branding of the caller's active organization — `GET /v1/branding`: its name, its light
1600
- * and dark colour sets and its two logos. Any role reads it, `runner` included, by web session
1601
- * or API key, so an app scaffolded from the method-app template reads its organization's
1602
- * branding with its own key. A personal workspace, or a team that set nothing, answers every
1603
- * colour and logo `null`. A logo's `uri` is a `pipelex-storage://` reference, which
1604
- * `resolveStorageUrl` turns into a short-lived link to its bytes.
1605
- *
1606
- * Throws `ApiResponseError`: `400` when the caller has no active organization, `404` when its
1607
- * row is missing, and an answer that is not a JSON object, an empty one included.
1618
+ * and dark colour sets, its two logos, its app style and typeface, and whether it holds the
1619
+ * white-label entitlement. Any role reads it, `runner` included, by web session or API key, so
1620
+ * an app scaffolded from the method-app template reads its organization's branding with its own
1621
+ * key. A personal workspace, or a team that set nothing, answers every colour and logo `null`
1622
+ * and no style or typeface. A logo's `uri` is a `pipelex-storage://` reference, which
1623
+ * `resolveStorageUrl` turns into a short-lived link to its bytes. An answer without `style`,
1624
+ * `typeface` or `white_label`, as a platform that predates them sends, reads as `null`, `null`
1625
+ * and `false`.
1626
+ *
1627
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` when the caller has no active
1628
+ * organization, `404` when its row is missing, and an answer that is not a JSON object, an
1629
+ * empty one included.
1608
1630
  */
1609
1631
  getBranding(): Promise<OrganizationBranding>;
1610
1632
  /**
1611
- * Set or clear an organization's branding — `PATCH /v1/organizations/{org_id}/branding`, an
1612
- * admin's write to the active organization. Every key of `input` is optional: a value sets it,
1613
- * `null` clears it, and a key left out keeps its value (`OrganizationBrandingUpdate`), so `{}`
1614
- * writes nothing. A new logo is the `pipelex-storage://` URI of a file the organization uploaded
1615
- * on its own, which the platform reads and measures, then copies under `branding/`: the answer's
1616
- * `uri` is that copy, and sending it back unchanged keeps the logo. The logo a write replaces or
1617
- * clears is erased. Answers the branding as the write left it.
1633
+ * Set or clear an organization's branding — `PATCH /v1/organizations/{org_id}/branding`, a
1634
+ * write to the active organization by the `admin` or the `member` role, never a `runner`. Every
1635
+ * key of `input` is optional: a value sets it, `null` clears it, and a key left out keeps its
1636
+ * value (`OrganizationBrandingUpdate`), so `{}` writes nothing. A new logo is the
1637
+ * `pipelex-storage://` URI of a file the organization uploaded on its own, which the platform
1638
+ * reads and measures, then copies under `branding/`: the answer's `uri` is that copy, and sending
1639
+ * it back unchanged keeps the logo. The logo a write replaces or clears is erased. A style or a
1640
+ * typeface is an id of the shape `^[a-z0-9-]{1,32}$`, stored whether or not it names one that
1641
+ * exists. Answers the branding as the write left it, read as `getBranding` reads it.
1618
1642
  *
1619
1643
  * Throws `RequestArgumentError` for an organization id that is empty, `.` or `..`, before any
1620
1644
  * request. Throws `ApiResponseError`, refusing every change of the request when it refuses one:
1621
1645
  * `403` `pipelex.org.mismatch` when `orgId` is not the active organization, `403`
1622
- * `auth.forbidden` for a caller who is not its admin, `404` `pipelex.org.not_found` for a missing
1623
- * organization, `409` `pipelex.org.not_manageable` for a personal workspace, which has no
1624
- * branding, and `422` `request.invalid` for a colour that is not `#rrggbb` or a key the body does
1625
- * not know. A logo's refusal names its slot in its problem's `errors`, each item's
1646
+ * `auth.forbidden` for a runner or a caller with no recognised role, `404`
1647
+ * `pipelex.org.not_found` for a missing organization, `409` `pipelex.org.not_manageable` for a
1648
+ * personal workspace, which has no branding, and `422` `request.invalid` for a colour that is
1649
+ * not `#rrggbb`, a style or a typeface of another shape, or a key the body does not know, which
1650
+ * names the field in `location.field`: the problem's own when it refuses one field alone, each
1651
+ * item's of its `errors` when it refuses several. `white_label` is such a key, since only
1652
+ * Pipelex staff grant it. A logo's refusal names its slot in its problem's `errors`, each item's
1626
1653
  * `location.field` being `square_logo` or `wide_logo` and its `detail` the sentence to show under
1627
1654
  * that slot: `422` `pipelex.storage.uri_invalid` for a URI that is not a storage URI, `403`
1628
1655
  * `pipelex.org.mismatch` for another organization's file, and `422` `request.invalid` with an
package/dist/client.js CHANGED
@@ -1943,8 +1943,8 @@ export class PipelexApiClient {
1943
1943
  * unknown or malformed name serves `default`, and the page's `ordering` names the one served,
1944
1944
  * `null` when there is none. `category` keeps one category's templates, by slug, in the same
1945
1945
  * order; an unknown slug keeps none. Whatever the filter, the page's `categories` names, with its
1946
- * label, every category at least one template shows, in the catalog's order, so no tab drawn
1947
- * from it is ever empty.
1946
+ * labels, every category at least one template shows, in the catalog's order, so no tab drawn
1947
+ * from it is ever empty; `listMethodCategories` reads the whole list.
1948
1948
  *
1949
1949
  * Each `MethodTemplate` carries the method's id and name, its latest published version's number,
1950
1950
  * description and categories, its organization's display name and each thumbnail with its size
@@ -1954,17 +1954,18 @@ export class PipelexApiClient {
1954
1954
  * Pass the page's `nextCursor` back as `cursor` with the same `ordering` and `category`, which the
1955
1955
  * cursor does not hold; `null` is the last page. `iterateMethodTemplates` follows it for you.
1956
1956
  *
1957
- * Throws `ApiResponseError`: `422` `request.invalid` for a `limit` outside 1 to 200 or a cursor of
1958
- * another listing, `422` `request.malformed` for a cursor that does not decode, `500`
1959
- * `runtime.misconfigured` when the templates organization the deployment names has no record or
1960
- * a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A page whose `items`
1961
- * or `categories` is not an array, whose `next_cursor` or `ordering` is neither a string nor
1962
- * `null`, a template lacking a member a card is drawn from (a string `method_id`, `name` and
1963
- * `organization_name`, a `description` that is a string or `null`, a positive integer
1964
- * `latest_version`, an array of string `categories`, and `thumbnails` each with a string `path`,
1965
- * `media_type`, `sha256`, `url` and `expires_at` and a positive integer `width` and `height`), and a
1966
- * category without a string `slug` and `label` are an answer the SDK cannot read, thrown as an
1967
- * `ApiResponseError` too, its links redacted.
1957
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` without an active organization, `422`
1958
+ * `request.invalid` for a `limit` outside 1 to 200 or a cursor of another listing, `422`
1959
+ * `request.malformed` for a cursor that does not decode, `500` `runtime.misconfigured` when the
1960
+ * templates organization the deployment names has no record or a link cannot be signed, and
1961
+ * `503` `runtime.unavailable`, safe to retry. A page whose `items` or `categories` is not an
1962
+ * array, whose `next_cursor` or `ordering` is neither a string nor `null`, a template lacking a
1963
+ * member a card is drawn from (a string `method_id`, `name` and `organization_name`, a
1964
+ * `description` that is a string or `null`, a positive integer `latest_version`, an array of
1965
+ * string `categories`, and `thumbnails` each with a string `path`, `media_type`, `sha256`, `url`
1966
+ * and `expires_at` and a positive integer `width` and `height`), and a category without a string
1967
+ * `slug` and `label` or whose `labels` is not an object of strings are an answer the SDK cannot
1968
+ * read, thrown as an `ApiResponseError` too, its links redacted.
1968
1969
  */
1969
1970
  async listMethodTemplates(query = {}) {
1970
1971
  const search = productQuery({
@@ -2010,6 +2011,35 @@ export class PipelexApiClient {
2010
2011
  async *iterateMethodTemplates(query = {}) {
2011
2012
  yield* followCursor("listMethodTemplates", (cursor) => this.listMethodTemplates({ ...query, cursor }));
2012
2013
  }
2014
+ /**
2015
+ * The catalog's whole list of categories — `GET /v1/method-categories`: every category an admin
2016
+ * of the platform put on it, in the catalog's order, whether or not a template shows it, the same
2017
+ * for every caller. Each `MethodCategory` carries its `slug`, the one a manifest declares, its
2018
+ * English `label`, and its `labels` by language tag, which may lack a language the platform
2019
+ * added after the category was stored; show `labels[locale] ?? label`.
2020
+ *
2021
+ * A template shows under the categories its manifest declares that are on this list, so this is
2022
+ * the list a method's categories are picked from. `listMethodTemplates` names, on every page, only
2023
+ * the categories some template shows, for the tabs a client draws.
2024
+ *
2025
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` without an active organization, and
2026
+ * `503` `runtime.unavailable`, safe to retry. An answer whose `categories` is not an array, or
2027
+ * holds a category without a string `slug` and `label` or whose `labels` is not an object of
2028
+ * strings, is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
2029
+ */
2030
+ async listMethodCategories() {
2031
+ const endpoint = "method-categories";
2032
+ const res = await this.requestProductAnswer("GET", endpoint, undefined);
2033
+ const list = this.readObjectAnswer("GET", endpoint, res);
2034
+ const { categories } = list;
2035
+ const fault = Array.isArray(categories)
2036
+ ? methodCategoriesFault(categories)
2037
+ : "`categories` is not an array";
2038
+ if (fault !== null) {
2039
+ throw this.unreadableAnswer("GET", `/${API_PREFIX}/${endpoint}`, res, `a list of categories whose ${fault}`);
2040
+ }
2041
+ return list;
2042
+ }
2013
2043
  /**
2014
2044
  * Read one method with its files — `GET /v1/methods/{id}`. One of the caller's organization's
2015
2045
  * methods answers a `MethodDetail`: its identity, its draft
@@ -2999,34 +3029,44 @@ export class PipelexApiClient {
2999
3029
  }
3000
3030
  /**
3001
3031
  * The branding of the caller's active organization — `GET /v1/branding`: its name, its light
3002
- * and dark colour sets and its two logos. Any role reads it, `runner` included, by web session
3003
- * or API key, so an app scaffolded from the method-app template reads its organization's
3004
- * branding with its own key. A personal workspace, or a team that set nothing, answers every
3005
- * colour and logo `null`. A logo's `uri` is a `pipelex-storage://` reference, which
3006
- * `resolveStorageUrl` turns into a short-lived link to its bytes.
3007
- *
3008
- * Throws `ApiResponseError`: `400` when the caller has no active organization, `404` when its
3009
- * row is missing, and an answer that is not a JSON object, an empty one included.
3032
+ * and dark colour sets, its two logos, its app style and typeface, and whether it holds the
3033
+ * white-label entitlement. Any role reads it, `runner` included, by web session or API key, so
3034
+ * an app scaffolded from the method-app template reads its organization's branding with its own
3035
+ * key. A personal workspace, or a team that set nothing, answers every colour and logo `null`
3036
+ * and no style or typeface. A logo's `uri` is a `pipelex-storage://` reference, which
3037
+ * `resolveStorageUrl` turns into a short-lived link to its bytes. An answer without `style`,
3038
+ * `typeface` or `white_label`, as a platform that predates them sends, reads as `null`, `null`
3039
+ * and `false`.
3040
+ *
3041
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` when the caller has no active
3042
+ * organization, `404` when its row is missing, and an answer that is not a JSON object, an
3043
+ * empty one included.
3010
3044
  */
3011
3045
  async getBranding() {
3012
- return this.requestProductObject("GET", "branding");
3046
+ const answer = await this.requestProductObject("GET", "branding");
3047
+ return withCheckedBrandingChoices(answer);
3013
3048
  }
3014
3049
  /**
3015
- * Set or clear an organization's branding — `PATCH /v1/organizations/{org_id}/branding`, an
3016
- * admin's write to the active organization. Every key of `input` is optional: a value sets it,
3017
- * `null` clears it, and a key left out keeps its value (`OrganizationBrandingUpdate`), so `{}`
3018
- * writes nothing. A new logo is the `pipelex-storage://` URI of a file the organization uploaded
3019
- * on its own, which the platform reads and measures, then copies under `branding/`: the answer's
3020
- * `uri` is that copy, and sending it back unchanged keeps the logo. The logo a write replaces or
3021
- * clears is erased. Answers the branding as the write left it.
3050
+ * Set or clear an organization's branding — `PATCH /v1/organizations/{org_id}/branding`, a
3051
+ * write to the active organization by the `admin` or the `member` role, never a `runner`. Every
3052
+ * key of `input` is optional: a value sets it, `null` clears it, and a key left out keeps its
3053
+ * value (`OrganizationBrandingUpdate`), so `{}` writes nothing. A new logo is the
3054
+ * `pipelex-storage://` URI of a file the organization uploaded on its own, which the platform
3055
+ * reads and measures, then copies under `branding/`: the answer's `uri` is that copy, and sending
3056
+ * it back unchanged keeps the logo. The logo a write replaces or clears is erased. A style or a
3057
+ * typeface is an id of the shape `^[a-z0-9-]{1,32}$`, stored whether or not it names one that
3058
+ * exists. Answers the branding as the write left it, read as `getBranding` reads it.
3022
3059
  *
3023
3060
  * Throws `RequestArgumentError` for an organization id that is empty, `.` or `..`, before any
3024
3061
  * request. Throws `ApiResponseError`, refusing every change of the request when it refuses one:
3025
3062
  * `403` `pipelex.org.mismatch` when `orgId` is not the active organization, `403`
3026
- * `auth.forbidden` for a caller who is not its admin, `404` `pipelex.org.not_found` for a missing
3027
- * organization, `409` `pipelex.org.not_manageable` for a personal workspace, which has no
3028
- * branding, and `422` `request.invalid` for a colour that is not `#rrggbb` or a key the body does
3029
- * not know. A logo's refusal names its slot in its problem's `errors`, each item's
3063
+ * `auth.forbidden` for a runner or a caller with no recognised role, `404`
3064
+ * `pipelex.org.not_found` for a missing organization, `409` `pipelex.org.not_manageable` for a
3065
+ * personal workspace, which has no branding, and `422` `request.invalid` for a colour that is
3066
+ * not `#rrggbb`, a style or a typeface of another shape, or a key the body does not know, which
3067
+ * names the field in `location.field`: the problem's own when it refuses one field alone, each
3068
+ * item's of its `errors` when it refuses several. `white_label` is such a key, since only
3069
+ * Pipelex staff grant it. A logo's refusal names its slot in its problem's `errors`, each item's
3030
3070
  * `location.field` being `square_logo` or `wide_logo` and its `detail` the sentence to show under
3031
3071
  * that slot: `422` `pipelex.storage.uri_invalid` for a URI that is not a storage URI, `403`
3032
3072
  * `pipelex.org.mismatch` for another organization's file, and `422` `request.invalid` with an
@@ -3036,7 +3076,8 @@ export class PipelexApiClient {
3036
3076
  */
3037
3077
  async updateOrganizationBranding(orgId, input) {
3038
3078
  const segment = idPathSegment(orgId, "updateOrganizationBranding", ORG_ID_ARGUMENT);
3039
- return this.requestProductObject("PATCH", `organizations/${segment}/branding`, input);
3079
+ const answer = await this.requestProductObject("PATCH", `organizations/${segment}/branding`, input);
3080
+ return withCheckedBrandingChoices(answer);
3040
3081
  }
3041
3082
  /**
3042
3083
  * The organization's webhook signing secret — `GET /v1/organizations/{org_id}/webhook-secret`:
@@ -4593,7 +4634,10 @@ function methodTemplatesFault(items) {
4593
4634
  }
4594
4635
  return null;
4595
4636
  }
4596
- /** Why the catalog's categories are not ones the SDK reads, or `null`: each a string `slug` and `label`. */
4637
+ /**
4638
+ * Why the catalog's categories are not ones the SDK reads, or `null`: each a string `slug` and
4639
+ * `label`, and `labels`, an object whose every value is a string.
4640
+ */
4597
4641
  function methodCategoriesFault(categories) {
4598
4642
  for (let index = 0; index < categories.length; index += 1) {
4599
4643
  const category = categories[index];
@@ -4602,6 +4646,11 @@ function methodCategoriesFault(categories) {
4602
4646
  typeof category.label !== "string") {
4603
4647
  return `\`categories\` holds an entry without a string \`slug\` and \`label\`, at [${index}]`;
4604
4648
  }
4649
+ const labels = category.labels;
4650
+ if (!isPlainObject(labels) ||
4651
+ !Object.values(labels).every((label) => typeof label === "string")) {
4652
+ return `\`categories\` holds an entry whose \`labels\` is not an object of strings, at [${index}]`;
4653
+ }
4605
4654
  }
4606
4655
  return null;
4607
4656
  }
@@ -4749,6 +4798,21 @@ function withCheckedUse(use) {
4749
4798
  method_version: Number.isInteger(version) || version === "draft" ? version : null,
4750
4799
  };
4751
4800
  }
4801
+ /**
4802
+ * A branding with its `style` and `typeface` kept only when each is a string, each read as `null`
4803
+ * otherwise, and its `white_label` read as `true` only when it is `true`: when the answer leaves a
4804
+ * member out, as a platform that predates it does, sends `null`, or sends any other type. So each
4805
+ * is what `OrganizationBranding` declares, never `undefined`, and anything but the entitlement
4806
+ * itself reads as no entitlement, which keeps Pipelex's mark. The rest is returned as it came.
4807
+ */
4808
+ function withCheckedBrandingChoices(answer) {
4809
+ return {
4810
+ ...answer,
4811
+ style: typeof answer.style === "string" ? answer.style : null,
4812
+ typeface: typeof answer.typeface === "string" ? answer.typeface : null,
4813
+ white_label: answer.white_label === true,
4814
+ };
4815
+ }
4752
4816
  /**
4753
4817
  * A status read with its `degradation` checked by `readRunDegradation`. Only when the read carries
4754
4818
  * the key, so a platform that predates it still leaves it absent; `degraded` is left as sent.