@pipelex/sdk 0.63.0 → 0.65.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 +26 -0
- package/dist/cli/script.d.ts +1 -1
- package/dist/client.d.ts +162 -7
- package/dist/client.js +222 -10
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js.map +1 -1
- package/dist/method-layouts.d.ts +195 -0
- package/dist/method-layouts.js +121 -0
- package/dist/method-layouts.js.map +1 -0
- package/dist/models.d.ts +27 -0
- package/dist/product-models.d.ts +12 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +3 -1
- package/docs/errors.md +2 -0
- package/docs/method-layouts.md +168 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,6 +178,31 @@ const copy = await client.copyMethod(
|
|
|
178
178
|
);
|
|
179
179
|
if (copy.outcome === "copied") console.log(copy.method.copied_from); // the original it names
|
|
180
180
|
|
|
181
|
+
// Store a layout a producer designed for one pipe of a published version, over that version's
|
|
182
|
+
// tree; a write of the same tree, pipe, catalog and prompt hash replaces it. A validate by
|
|
183
|
+
// method_id naming `layouts` then carries the layouts beside the descriptor, newest first, and
|
|
184
|
+
// leaves `layouts` out when there are none: render the plain form then.
|
|
185
|
+
const v1 = await client.getMethodVersion(created.method_id, 1);
|
|
186
|
+
const layoutKey = crypto.randomUUID(); // one key per layout, sent again unchanged on each retry
|
|
187
|
+
await client.storeMethodLayout(created.method_id, 1, {
|
|
188
|
+
tree_digest: v1.tree_digest!,
|
|
189
|
+
pipe_ref: "greeting.greet",
|
|
190
|
+
catalog: "composer",
|
|
191
|
+
prompt_hash: "a1b2c3d4e5f6",
|
|
192
|
+
jsonl: '{"op":"add","path":"/root","value":"page"}',
|
|
193
|
+
provenance: { producer: "pipelex-method", model: "claude-4.8-opus", style: "editorial" },
|
|
194
|
+
}, { idempotencyKey: layoutKey });
|
|
195
|
+
const laidOut = await client.validate({ method_id: `${created.method_id}@1` }, false, {
|
|
196
|
+
views: ["input_form", "layouts"],
|
|
197
|
+
});
|
|
198
|
+
if (laidOut.is_valid) console.log(laidOut.layouts?.map((layout) => layout.catalog) ?? "plain form");
|
|
199
|
+
|
|
200
|
+
// A host opening the page reads the same layouts with no dry run, and a builder can switch a
|
|
201
|
+
// page's design off, per catalog, without losing it; the list is written whole.
|
|
202
|
+
const { items: laidOutPages } = await client.listMethodLayouts(created.method_id, 1);
|
|
203
|
+
console.log(laidOutPages.length);
|
|
204
|
+
await client.setMethodLayoutsOff(created.method_id, ["composer"]);
|
|
205
|
+
|
|
181
206
|
// The template catalog: the public methods every organization reads, in the platform's order, with
|
|
182
207
|
// the categories that have a template and each card's thumbnails behind short-lived links. A
|
|
183
208
|
// category's `labels` name it by language tag and may lack a language, in which case a client
|
|
@@ -302,6 +327,7 @@ These pages ship inside the published package, so a reader who has only installe
|
|
|
302
327
|
| [`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 |
|
|
303
328
|
| [`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 |
|
|
304
329
|
| [`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` |
|
|
330
|
+
| [`docs/method-layouts.md`](./docs/method-layouts.md) | A method's stored layouts: `storeMethodLayout`, which stores a layout designed for one pipe of a published version, its closed body and its refusals, the layout, its provenance and its written sample, `listMethodLayouts`, which reads a version's layouts with no dry run, `setMethodLayoutsOff`, which switches a page's design off, and the `layouts` a validate by `method_id` carries when `views` names `layouts`, absent when there are none |
|
|
305
331
|
| [`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
332
|
| [`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 |
|
|
307
333
|
| [`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 |
|
package/dist/cli/script.d.ts
CHANGED
|
@@ -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.
|
|
18
|
+
export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.65.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,5 +1,6 @@
|
|
|
1
1
|
import type { MTHDSProtocol, ModelCategory, ModelDeck, RunOptions, StartOptions, VersionInfo } from "mthds/protocol";
|
|
2
|
-
import type { CodegenRequest, CodegenResponse, CrateRouteOptions, DictPipeOutput, FormatResponse, LintResponse, ModelCheckCategory, ModelReferenceVerdict, PipeIORequest, PipeIOResponse, PipelexRunResultStart, PipelexValidateSource, PipelexValidationResult, ResolveRequest, ResolveResponse, ValidateMethodSelector } from "./models.js";
|
|
2
|
+
import type { CodegenRequest, CodegenResponse, CrateRouteOptions, DictPipeOutput, FormatResponse, LintResponse, ModelCheckCategory, ModelReferenceVerdict, PipeIORequest, PipeIOResponse, PipelexRunResultStart, PipelexValidateSource, PipelexValidationResult, ResolveRequest, ResolveResponse, ValidateMethodSelector, ValidateView } from "./models.js";
|
|
3
|
+
import type { MethodLayout, MethodLayoutInput, MethodLayoutList } from "./method-layouts.js";
|
|
3
4
|
import { type GetRunResultOptions, type RunProgress, type RunRead, type RunResults, type RunResultState, type StartAndWaitForResultOptions, type WaitForResultOptions } from "./runs.js";
|
|
4
5
|
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
6
|
import type { MethodTreeForms, MethodTreeLimits } from "./method-trees.js";
|
|
@@ -173,6 +174,55 @@ export interface MethodCopyRequestOptions {
|
|
|
173
174
|
*/
|
|
174
175
|
idempotencyKey?: string | null;
|
|
175
176
|
}
|
|
177
|
+
/** Options for `listMethodLayouts`. */
|
|
178
|
+
export interface MethodLayoutListOptions {
|
|
179
|
+
/**
|
|
180
|
+
* Caller-driven cancellation, such as an `AbortSignal.timeout(…)` for a page that can do without
|
|
181
|
+
* the layouts; the abort reason propagates untouched.
|
|
182
|
+
*/
|
|
183
|
+
signal?: AbortSignal;
|
|
184
|
+
}
|
|
185
|
+
/** Options for `setMethodLayoutsOff`. */
|
|
186
|
+
export interface MethodLayoutsOffOptions {
|
|
187
|
+
/**
|
|
188
|
+
* The write's idempotency key, sent as the `Idempotency-Key` header of
|
|
189
|
+
* `PUT /v1/methods/{id}/layouts-off`, and not sent at all when absent or `null`. Generate one per
|
|
190
|
+
* switch the caller means to make (a `crypto.randomUUID()` is a good key), and keep it, with the
|
|
191
|
+
* arguments unchanged, across that write's retries.
|
|
192
|
+
*
|
|
193
|
+
* The write is the whole list, so a write sent again without a key writes that list again, and
|
|
194
|
+
* undoes a switch that landed in between. Under the same key the hosted API answers the retry
|
|
195
|
+
* with the first write's stored answer and runs nothing. The rest of the rule, a key's form and
|
|
196
|
+
* its `409`s included, is `MethodLayoutWriteOptions.idempotencyKey`'s.
|
|
197
|
+
*/
|
|
198
|
+
idempotencyKey?: string | null;
|
|
199
|
+
}
|
|
200
|
+
/** Options for `storeMethodLayout`. */
|
|
201
|
+
export interface MethodLayoutWriteOptions {
|
|
202
|
+
/**
|
|
203
|
+
* The write's idempotency key, sent as the `Idempotency-Key` header of
|
|
204
|
+
* `POST /v1/methods/{id}/versions/{n}/layouts`, and not sent at all when absent or `null`.
|
|
205
|
+
* Generate one per layout the caller means to store (a `crypto.randomUUID()` is a good key), and
|
|
206
|
+
* keep it, with the arguments unchanged, across that write's retries.
|
|
207
|
+
*
|
|
208
|
+
* A write sent again without a key runs again: it stamps the layout with a new `stored_at`, which
|
|
209
|
+
* moves it ahead of every layout stored since in the newest-first order, and applies the bound
|
|
210
|
+
* again, so a layout another producer stored meanwhile, a newer prompt of the same pipe and
|
|
211
|
+
* catalog included, can be the one removed. Under the same key the hosted API answers the retry
|
|
212
|
+
* with the first write's stored answer and runs nothing, so a write whose answer was lost, as on
|
|
213
|
+
* a client timeout, can be sent again unchanged. The write's `200` and its `4xx` refusals are
|
|
214
|
+
* stored alike; a `5xx` and a refusal for its timing (a `408`, `425` or `429`) are not, so the
|
|
215
|
+
* same key sends the write afresh. The same key with a different body is refused (`409`
|
|
216
|
+
* `request.idempotency_conflict`, whose `retry` is `no`), as is a key whose earlier request is
|
|
217
|
+
* still being handled (`409` `request.idempotency_key_in_use`, whose `retry` is `yes`); these
|
|
218
|
+
* two are the key's own answers and are never stored, so a retry after the second runs as the
|
|
219
|
+
* key's first request would.
|
|
220
|
+
*
|
|
221
|
+
* A key is held to the rule a start's is (`PipelexStartRequestOptions.idempotencyKey`): 1 to
|
|
222
|
+
* 255 visible ASCII characters, anything else a `RequestArgumentError` before any request.
|
|
223
|
+
*/
|
|
224
|
+
idempotencyKey?: string | null;
|
|
225
|
+
}
|
|
176
226
|
/**
|
|
177
227
|
* `start()` / `startAndWaitForResult()` options — the same, for the durable path, plus the start
|
|
178
228
|
* request's own `idempotencyKey`.
|
|
@@ -191,8 +241,13 @@ export interface ValidateOptions {
|
|
|
191
241
|
* `"markdown"`, so both arms carry `rendered_markdown`.
|
|
192
242
|
*/
|
|
193
243
|
render?: string[];
|
|
194
|
-
/**
|
|
195
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Structured-view opt-in tokens, e.g. `["input_form", "output_form"]`, or
|
|
246
|
+
* `["input_form", "layouts"]` for a stored method's descriptor and the layouts over it; sent
|
|
247
|
+
* verbatim, and only when given. A token this SDK does not know (`ValidateView`) is sent as its
|
|
248
|
+
* raw string.
|
|
249
|
+
*/
|
|
250
|
+
views?: (ValidateView | (string & {}))[];
|
|
196
251
|
/**
|
|
197
252
|
* Per-call request ceiling; defaults to the 20-min execute ceiling. A positive number
|
|
198
253
|
* no larger than 2147483647, the longest delay a timer honours, else a `RangeError`.
|
|
@@ -284,8 +339,8 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
284
339
|
* `url` is a fully resolved absolute URL. `mayStartRun` marks a request that may create a run,
|
|
285
340
|
* whose errors then say whether it may have (see `RunCreation`).
|
|
286
341
|
* `idempotencyKey`, already checked by `idempotencyKeyOf`, is sent as the
|
|
287
|
-
* `Idempotency-Key` header; only `start`, `copyMethod`, `
|
|
288
|
-
* `replayRunWebhookDelivery` pass one.
|
|
342
|
+
* `Idempotency-Key` header; only `start`, `copyMethod`, `storeMethodLayout`,
|
|
343
|
+
* `setMethodLayoutsOff`, `rotateWebhookSecret` and `replayRunWebhookDelivery` pass one.
|
|
289
344
|
*/
|
|
290
345
|
private requestRaw;
|
|
291
346
|
/**
|
|
@@ -315,7 +370,7 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
315
370
|
private requestProductObject;
|
|
316
371
|
/**
|
|
317
372
|
* A methods-catalog route answering one stored method (`createMethod`, `writeDraft`,
|
|
318
|
-
* `renameMethod`), read as `readObjectAnswer` reads it.
|
|
373
|
+
* `renameMethod`, `setMethodLayoutsOff`), read as `readObjectAnswer` reads it.
|
|
319
374
|
*/
|
|
320
375
|
private requestMethodData;
|
|
321
376
|
/**
|
|
@@ -565,7 +620,10 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
565
620
|
* `options.views` is the sibling opt-in for *structured* views where `render`
|
|
566
621
|
* carries *rendered text*. The two lists are independent, each resolving its own
|
|
567
622
|
* tokens against its own supported set, and each supported token adds a same-named
|
|
568
|
-
* top-level field to the valid arm — `input_form` and `output_form
|
|
623
|
+
* top-level field to the valid arm — `input_form` and `output_form`, and on a hosted
|
|
624
|
+
* validate by `method_id`, `layouts`, the layouts stored for the resolved tree
|
|
625
|
+
* (`PipelexValidationReport.layouts`, absent when there are none), each entry read as
|
|
626
|
+
* `storeMethodLayout`'s answer is and one that does not read as a layout left out. Unlike
|
|
569
627
|
* `render`, this client sends `views` ONLY when the caller asks: the point of an
|
|
570
628
|
* opt-in view is that the default response stays byte-identical, and the
|
|
571
629
|
* highest-frequency consumers (hook pipelines, CI gates, agent loops) never pay for
|
|
@@ -1372,6 +1430,103 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
1372
1430
|
* one, which names no tree to serve.
|
|
1373
1431
|
*/
|
|
1374
1432
|
getMethodVersion(methodId: string, version: number): Promise<MethodVersion>;
|
|
1433
|
+
/**
|
|
1434
|
+
* Store a layout designed for one pipe of a published version of a method —
|
|
1435
|
+
* `POST /v1/methods/{id}/versions/{n}/layouts` — and answer it as stored, with the `stored_at` the
|
|
1436
|
+
* platform stamped.
|
|
1437
|
+
*
|
|
1438
|
+
* A layout is a data file a producer designed over the version's input-form descriptor, in the
|
|
1439
|
+
* vocabulary of a catalog of components. The platform stores it beside the method, keyed by the
|
|
1440
|
+
* content it was designed for, and hands it back through `listMethodLayouts` and on every validate
|
|
1441
|
+
* of the method by `method_id` that names `layouts` among its `views` and resolves the same tree
|
|
1442
|
+
* (`PipelexValidationReport.layouts`). It never produces, parses or judges one.
|
|
1443
|
+
*
|
|
1444
|
+
* `layout.tree_digest` must be the digest of the tree `version` holds, its
|
|
1445
|
+
* `MethodVersionSummary.tree_digest`: a producer designs over the descriptor of the version it was
|
|
1446
|
+
* handed. **A write of the same identity replaces the layout**, the identity being `tree_digest`,
|
|
1447
|
+
* `pipe_ref`, `catalog` and `prompt_hash`, and is answered the same `200` whether it stored a new
|
|
1448
|
+
* layout or replaced one. `options.idempotencyKey` is sent as the `Idempotency-Key` header, so a
|
|
1449
|
+
* write whose answer was lost is sent again under the same key and answered as the first was; sent
|
|
1450
|
+
* again without one, it runs again, with a new `stored_at` and the bound applied again
|
|
1451
|
+
* (`MethodLayoutWriteOptions`). A content keeps at most 16
|
|
1452
|
+
* layouts, every pipe, catalog and prompt hash together; past the bound a write removes first what
|
|
1453
|
+
* newer prompts of the same pipe and catalog superseded (see `docs/method-layouts.md`).
|
|
1454
|
+
* `layout.sample_json` keeps a written sample with the layout, never a run's output.
|
|
1455
|
+
*
|
|
1456
|
+
* The write changes the method, so it takes an `admin` or a `member` of the method's
|
|
1457
|
+
* organization. Takes a bare catalog id, as `getMethod` does, and the version number. Sends the
|
|
1458
|
+
* members of `MethodLayoutInput` and nothing else, the route's body being closed at both levels,
|
|
1459
|
+
* and leaves out `sample_json` and each optional provenance member that is `null` or omitted, for
|
|
1460
|
+
* the platform's default. Throws `RequestArgumentError` before any request for a `version` that is not a positive
|
|
1461
|
+
* integer and for a `layout` or a `provenance` that is not an object; every other member is sent
|
|
1462
|
+
* as given. Throws `ApiResponseError` when the platform refuses the write, which stores nothing:
|
|
1463
|
+
* `422` `request.invalid` for a member breaking its shape or bound, naming it in `params.field`
|
|
1464
|
+
* and `location.field` (`provenance.<member>` for one of the provenance's), and, naming
|
|
1465
|
+
* `tree_digest`, for a digest that is not the tree the version holds; `403` `auth.forbidden`
|
|
1466
|
+
* naming the `member` requirement for a `runner` or a caller with no recognised role; `404`
|
|
1467
|
+
* `pipelex.catalog.not_found` for an unknown method or another organization's, its
|
|
1468
|
+
* `params.resource` `method`, and for a version the method never published, its
|
|
1469
|
+
* `params.resource` `version`; `409` `pipelex.catalog.being_deleted` while the method's erasure is
|
|
1470
|
+
* pending, under way or stopped; and `500` `runtime.unexpected` for a version stored before
|
|
1471
|
+
* methods were trees, which names no tree. Under an idempotency key, `409`
|
|
1472
|
+
* `request.idempotency_conflict` for the key sent with a different body and `409`
|
|
1473
|
+
* `request.idempotency_key_in_use` while its earlier request is still being handled; a key that is
|
|
1474
|
+
* not 1 to 255 visible ASCII characters is a `RequestArgumentError` before any request. An answer
|
|
1475
|
+
* that does not read as a layout (`methodLayoutFault`) is an answer the SDK cannot read, thrown as
|
|
1476
|
+
* an `ApiResponseError` too.
|
|
1477
|
+
*/
|
|
1478
|
+
storeMethodLayout(methodId: string, version: number, layout: MethodLayoutInput, options?: MethodLayoutWriteOptions): Promise<MethodLayout>;
|
|
1479
|
+
/**
|
|
1480
|
+
* The layouts stored for the content of a published version of a method —
|
|
1481
|
+
* `GET /v1/methods/{id}/versions/{n}/layouts` — newest first, at most 16, `items` empty when none
|
|
1482
|
+
* is stored. It touches only the platform's database and calls no runner, so it costs a host far
|
|
1483
|
+
* less than the `layouts` view of `validate`, which dry-runs every pipe of the method.
|
|
1484
|
+
*
|
|
1485
|
+
* It answers what is stored whatever `layouts_off` says: the switch is the host's to honour.
|
|
1486
|
+
* Another organization's public method reads too, with the layouts its builders stored, and a
|
|
1487
|
+
* `runner` reads what it is shown. Takes a bare catalog id, as `getMethod` does, and the version
|
|
1488
|
+
* number. `options.signal` cancels the read. Throws `RequestArgumentError` for a `version` that is
|
|
1489
|
+
* not a positive integer, before any request.
|
|
1490
|
+
*
|
|
1491
|
+
* Throws `ApiResponseError`: `404` `pipelex.catalog.not_found` for an unknown method or another
|
|
1492
|
+
* organization's internal one, its `params.resource` `method`, and for a version the method never
|
|
1493
|
+
* published, its `params.resource` `version`; `409` `pipelex.catalog.being_deleted` while the
|
|
1494
|
+
* erasure of the caller's own method is pending, under way or stopped; `500` `runtime.unexpected`
|
|
1495
|
+
* for a version stored before methods were trees, which names no tree. An answer whose `items` is
|
|
1496
|
+
* not an array is an answer the SDK cannot read, thrown as an `ApiResponseError` too; an entry
|
|
1497
|
+
* that does not read as a layout is left out (`readMethodLayouts`), so one layout it cannot read
|
|
1498
|
+
* never costs the caller the others.
|
|
1499
|
+
*/
|
|
1500
|
+
listMethodLayouts(methodId: string, version: number, options?: MethodLayoutListOptions): Promise<MethodLayoutList>;
|
|
1501
|
+
/**
|
|
1502
|
+
* Set which catalogs of a method have their layouts switched off —
|
|
1503
|
+
* `PUT /v1/methods/{id}/layouts-off` — and return the method, its `layouts_off` the list as held.
|
|
1504
|
+
*
|
|
1505
|
+
* `catalogs` is the whole list and replaces the one held, each catalog id once, at most 8, empty
|
|
1506
|
+
* when every design is on: to change one, read `layouts_off` and write the new list. The switch
|
|
1507
|
+
* acts at once, with no publish, and holds across every publish. A layout switched off is kept,
|
|
1508
|
+
* and `listMethodLayouts` still answers it; a host draws the plain page instead. Like a rename, it
|
|
1509
|
+
* never moves the draft token, so the `updated_at` a caller holds stays valid.
|
|
1510
|
+
*
|
|
1511
|
+
* The last write to land is the one held. `options.idempotencyKey` is sent as the
|
|
1512
|
+
* `Idempotency-Key` header, so a write whose answer was lost is sent again under the same key and
|
|
1513
|
+
* answered as the first was (`MethodLayoutsOffOptions`).
|
|
1514
|
+
*
|
|
1515
|
+
* The write changes the method, so it takes an `admin` or a `member` of the method's
|
|
1516
|
+
* organization. Takes a bare catalog id, as `getMethod` does. Throws `RequestArgumentError` before
|
|
1517
|
+
* any request for `catalogs` that is not an array; its entries are sent as given. Throws
|
|
1518
|
+
* `ApiResponseError`: `404` `pipelex.catalog.not_found` for an unknown method or another
|
|
1519
|
+
* organization's, a public one included; `403` `auth.forbidden` naming the `member` requirement
|
|
1520
|
+
* for a `runner` or a caller with no recognised role; `409` `pipelex.catalog.being_deleted` while
|
|
1521
|
+
* the method's erasure is pending, under way or stopped; `413` `request.too_large` for a list that
|
|
1522
|
+
* would leave the method too large to publish, which a list that does not grow never is; and
|
|
1523
|
+
* `422` `request.invalid` for more than 8 catalogs, a catalog named twice or one that is not a
|
|
1524
|
+
* slug. Under an idempotency key, `409` `request.idempotency_conflict` for the key sent with a
|
|
1525
|
+
* different list and `409` `request.idempotency_key_in_use` while its earlier request is still
|
|
1526
|
+
* being handled; a key that is not 1 to 255 visible ASCII characters is a `RequestArgumentError`
|
|
1527
|
+
* before any request.
|
|
1528
|
+
*/
|
|
1529
|
+
setMethodLayoutsOff(methodId: string, catalogs: readonly string[], options?: MethodLayoutsOffOptions): Promise<MethodData>;
|
|
1375
1530
|
/**
|
|
1376
1531
|
* Copy a method into an organization the caller may change methods in —
|
|
1377
1532
|
* `POST /v1/methods/{id}/copy`: the method's own organization for a duplicate, another team, or
|
package/dist/client.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// source combinations are legal is an invariant of `RunRequest` itself, so this
|
|
3
3
|
// client and the MTHDS runners cannot disagree about what they reject.
|
|
4
4
|
import { assertOneMethodSource, buildProblem, isProblem, parseJsonBody, readProblemBody, } from "mthds/protocol";
|
|
5
|
+
import { LAYOUT_INPUT_MEMBERS, PROVENANCE_INPUT_MEMBERS, methodLayoutFault, readMethodLayout, readMethodLayouts, } from "./method-layouts.js";
|
|
5
6
|
import { assertArtifactSelection, assertWaitOptions, pollUntilResult, readRunDegradation, RUN_STATUSES, runProgressFault, runStatusFilter, selectionIncludesMainStuff, throwIfAborted, withCheckedProgressHealth, } from "./runs.js";
|
|
6
7
|
import { ABORT_TIMEOUT, ApiResponseError, argumentInvalidProblem, ApiUnreachableError, MissingMainStuffError, PagingNotTerminatingError, PipelineExecuteTimeoutError, ProblemError, RequestArgumentError, RunLifecycleUnavailableError, RunStillRunningError, } from "./errors.js";
|
|
7
8
|
import { isPlainObject } from "./records.js";
|
|
@@ -326,6 +327,27 @@ const COPY_DRAFT_TOKEN_ARGUMENT = {
|
|
|
326
327
|
what: "an expected_draft_updated_at, the draft token the caller last read, the method's " +
|
|
327
328
|
"`updated_at`, a string, or none",
|
|
328
329
|
};
|
|
330
|
+
/** What each argument of a layout write is, as a refusal of it names it. */
|
|
331
|
+
const LAYOUT_VERSION_ARGUMENT = {
|
|
332
|
+
name: "version",
|
|
333
|
+
what: "a version number, the published version the layout was designed for, a positive integer",
|
|
334
|
+
};
|
|
335
|
+
const LAYOUT_LIST_VERSION_ARGUMENT = {
|
|
336
|
+
name: "version",
|
|
337
|
+
what: "a version number, the published version whose layouts to read, a positive integer",
|
|
338
|
+
};
|
|
339
|
+
const LAYOUTS_OFF_CATALOGS_ARGUMENT = {
|
|
340
|
+
name: "catalogs",
|
|
341
|
+
what: "the ids of the catalogs to switch off as an array, the whole list, empty for none",
|
|
342
|
+
};
|
|
343
|
+
const LAYOUT_ARGUMENT = {
|
|
344
|
+
name: "layout",
|
|
345
|
+
what: "the layout to store as an object, a MethodLayoutInput",
|
|
346
|
+
};
|
|
347
|
+
const LAYOUT_PROVENANCE_ARGUMENT = {
|
|
348
|
+
name: "provenance",
|
|
349
|
+
what: "the layout's provenance as an object naming at least its producer and its model",
|
|
350
|
+
};
|
|
329
351
|
/**
|
|
330
352
|
* The refusal of an argument a call cannot take, worded as every such refusal is:
|
|
331
353
|
* `<call>() takes <what>; got <got>.`, with what made it unusable as `cause` when there is one.
|
|
@@ -374,12 +396,16 @@ function requireNameAndInputs(name, inputs, call, needs) {
|
|
|
374
396
|
}
|
|
375
397
|
/**
|
|
376
398
|
* A caller's value as the refusal of a number argument names it: a number by its value, anything
|
|
377
|
-
* else by its type, as `requireNonEmptyId` names a value,
|
|
378
|
-
*
|
|
399
|
+
* else by its type, as `requireNonEmptyId` names a value, `null` as itself and an array as one, so
|
|
400
|
+
* a refusal of a value that is not an object never answers an array with "got object". Never by
|
|
401
|
+
* `String`, which throws for an object with no prototype and would replace the refusal with a
|
|
402
|
+
* `TypeError`.
|
|
379
403
|
*/
|
|
380
404
|
function describeNumberArgument(value) {
|
|
381
405
|
if (typeof value === "number")
|
|
382
406
|
return String(value);
|
|
407
|
+
if (Array.isArray(value))
|
|
408
|
+
return "an array";
|
|
383
409
|
return value === null ? "null" : typeof value;
|
|
384
410
|
}
|
|
385
411
|
/**
|
|
@@ -582,8 +608,8 @@ export class PipelexApiClient {
|
|
|
582
608
|
* `url` is a fully resolved absolute URL. `mayStartRun` marks a request that may create a run,
|
|
583
609
|
* whose errors then say whether it may have (see `RunCreation`).
|
|
584
610
|
* `idempotencyKey`, already checked by `idempotencyKeyOf`, is sent as the
|
|
585
|
-
* `Idempotency-Key` header; only `start`, `copyMethod`, `
|
|
586
|
-
* `replayRunWebhookDelivery` pass one.
|
|
611
|
+
* `Idempotency-Key` header; only `start`, `copyMethod`, `storeMethodLayout`,
|
|
612
|
+
* `setMethodLayoutsOff`, `rotateWebhookSecret` and `replayRunWebhookDelivery` pass one.
|
|
587
613
|
*/
|
|
588
614
|
async requestRaw(method, url, options = {}) {
|
|
589
615
|
const hasBody = options.body !== undefined;
|
|
@@ -700,7 +726,7 @@ export class PipelexApiClient {
|
|
|
700
726
|
}
|
|
701
727
|
/**
|
|
702
728
|
* A methods-catalog route answering one stored method (`createMethod`, `writeDraft`,
|
|
703
|
-
* `renameMethod`), read as `readObjectAnswer` reads it.
|
|
729
|
+
* `renameMethod`, `setMethodLayoutsOff`), read as `readObjectAnswer` reads it.
|
|
704
730
|
*/
|
|
705
731
|
async requestMethodData(method, endpoint, body, options = {}) {
|
|
706
732
|
const res = await this.requestProductAnswer(method, endpoint, body, options);
|
|
@@ -1181,7 +1207,10 @@ export class PipelexApiClient {
|
|
|
1181
1207
|
* `options.views` is the sibling opt-in for *structured* views where `render`
|
|
1182
1208
|
* carries *rendered text*. The two lists are independent, each resolving its own
|
|
1183
1209
|
* tokens against its own supported set, and each supported token adds a same-named
|
|
1184
|
-
* top-level field to the valid arm — `input_form` and `output_form
|
|
1210
|
+
* top-level field to the valid arm — `input_form` and `output_form`, and on a hosted
|
|
1211
|
+
* validate by `method_id`, `layouts`, the layouts stored for the resolved tree
|
|
1212
|
+
* (`PipelexValidationReport.layouts`, absent when there are none), each entry read as
|
|
1213
|
+
* `storeMethodLayout`'s answer is and one that does not read as a layout left out. Unlike
|
|
1185
1214
|
* `render`, this client sends `views` ONLY when the caller asks: the point of an
|
|
1186
1215
|
* opt-in view is that the default response stays byte-identical, and the
|
|
1187
1216
|
* highest-frequency consumers (hook pipelines, CI gates, agent loops) never pay for
|
|
@@ -1239,7 +1268,7 @@ export class PipelexApiClient {
|
|
|
1239
1268
|
if (res.status < 200 || res.status >= 300) {
|
|
1240
1269
|
this.throwApiResponseError("POST", "validate", res);
|
|
1241
1270
|
}
|
|
1242
|
-
return withVerdictItems(this.readObjectAnswer("POST", "validate", res));
|
|
1271
|
+
return withReadLayouts(withVerdictItems(this.readObjectAnswer("POST", "validate", res)));
|
|
1243
1272
|
}
|
|
1244
1273
|
// ── Tools extensions (Pipelex API — `/v1/lint`, `/v1/format`) ─────────
|
|
1245
1274
|
//
|
|
@@ -2635,6 +2664,140 @@ export class PipelexApiClient {
|
|
|
2635
2664
|
const res = await this.requestProductAnswer("GET", endpoint, undefined);
|
|
2636
2665
|
return this.readSavedTree("GET", endpoint, withLinks(res), "a published version");
|
|
2637
2666
|
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Store a layout designed for one pipe of a published version of a method —
|
|
2669
|
+
* `POST /v1/methods/{id}/versions/{n}/layouts` — and answer it as stored, with the `stored_at` the
|
|
2670
|
+
* platform stamped.
|
|
2671
|
+
*
|
|
2672
|
+
* A layout is a data file a producer designed over the version's input-form descriptor, in the
|
|
2673
|
+
* vocabulary of a catalog of components. The platform stores it beside the method, keyed by the
|
|
2674
|
+
* content it was designed for, and hands it back through `listMethodLayouts` and on every validate
|
|
2675
|
+
* of the method by `method_id` that names `layouts` among its `views` and resolves the same tree
|
|
2676
|
+
* (`PipelexValidationReport.layouts`). It never produces, parses or judges one.
|
|
2677
|
+
*
|
|
2678
|
+
* `layout.tree_digest` must be the digest of the tree `version` holds, its
|
|
2679
|
+
* `MethodVersionSummary.tree_digest`: a producer designs over the descriptor of the version it was
|
|
2680
|
+
* handed. **A write of the same identity replaces the layout**, the identity being `tree_digest`,
|
|
2681
|
+
* `pipe_ref`, `catalog` and `prompt_hash`, and is answered the same `200` whether it stored a new
|
|
2682
|
+
* layout or replaced one. `options.idempotencyKey` is sent as the `Idempotency-Key` header, so a
|
|
2683
|
+
* write whose answer was lost is sent again under the same key and answered as the first was; sent
|
|
2684
|
+
* again without one, it runs again, with a new `stored_at` and the bound applied again
|
|
2685
|
+
* (`MethodLayoutWriteOptions`). A content keeps at most 16
|
|
2686
|
+
* layouts, every pipe, catalog and prompt hash together; past the bound a write removes first what
|
|
2687
|
+
* newer prompts of the same pipe and catalog superseded (see `docs/method-layouts.md`).
|
|
2688
|
+
* `layout.sample_json` keeps a written sample with the layout, never a run's output.
|
|
2689
|
+
*
|
|
2690
|
+
* The write changes the method, so it takes an `admin` or a `member` of the method's
|
|
2691
|
+
* organization. Takes a bare catalog id, as `getMethod` does, and the version number. Sends the
|
|
2692
|
+
* members of `MethodLayoutInput` and nothing else, the route's body being closed at both levels,
|
|
2693
|
+
* and leaves out `sample_json` and each optional provenance member that is `null` or omitted, for
|
|
2694
|
+
* the platform's default. Throws `RequestArgumentError` before any request for a `version` that is not a positive
|
|
2695
|
+
* integer and for a `layout` or a `provenance` that is not an object; every other member is sent
|
|
2696
|
+
* as given. Throws `ApiResponseError` when the platform refuses the write, which stores nothing:
|
|
2697
|
+
* `422` `request.invalid` for a member breaking its shape or bound, naming it in `params.field`
|
|
2698
|
+
* and `location.field` (`provenance.<member>` for one of the provenance's), and, naming
|
|
2699
|
+
* `tree_digest`, for a digest that is not the tree the version holds; `403` `auth.forbidden`
|
|
2700
|
+
* naming the `member` requirement for a `runner` or a caller with no recognised role; `404`
|
|
2701
|
+
* `pipelex.catalog.not_found` for an unknown method or another organization's, its
|
|
2702
|
+
* `params.resource` `method`, and for a version the method never published, its
|
|
2703
|
+
* `params.resource` `version`; `409` `pipelex.catalog.being_deleted` while the method's erasure is
|
|
2704
|
+
* pending, under way or stopped; and `500` `runtime.unexpected` for a version stored before
|
|
2705
|
+
* methods were trees, which names no tree. Under an idempotency key, `409`
|
|
2706
|
+
* `request.idempotency_conflict` for the key sent with a different body and `409`
|
|
2707
|
+
* `request.idempotency_key_in_use` while its earlier request is still being handled; a key that is
|
|
2708
|
+
* not 1 to 255 visible ASCII characters is a `RequestArgumentError` before any request. An answer
|
|
2709
|
+
* that does not read as a layout (`methodLayoutFault`) is an answer the SDK cannot read, thrown as
|
|
2710
|
+
* an `ApiResponseError` too.
|
|
2711
|
+
*/
|
|
2712
|
+
async storeMethodLayout(methodId, version, layout, options = {}) {
|
|
2713
|
+
const call = "storeMethodLayout";
|
|
2714
|
+
if (!Number.isSafeInteger(version) || version < 1) {
|
|
2715
|
+
throw argumentRefusal(call, LAYOUT_VERSION_ARGUMENT, describeNumberArgument(version), "not_a_positive_integer");
|
|
2716
|
+
}
|
|
2717
|
+
const endpoint = `${methodPath(methodId, call)}/versions/${version}/layouts`;
|
|
2718
|
+
const body = methodLayoutBody(layout, call);
|
|
2719
|
+
const idempotencyKey = idempotencyKeyOf(options, "store the layout");
|
|
2720
|
+
const res = await this.requestProductAnswer("POST", endpoint, body, { idempotencyKey });
|
|
2721
|
+
const answer = this.readObjectAnswer("POST", endpoint, res);
|
|
2722
|
+
const stored = readMethodLayout(answer);
|
|
2723
|
+
if (stored === null) {
|
|
2724
|
+
throw this.unreadableAnswer("POST", `/${API_PREFIX}/${endpoint}`, res, `a layout ${methodLayoutFault(answer) ?? "that is not one"}`);
|
|
2725
|
+
}
|
|
2726
|
+
return stored;
|
|
2727
|
+
}
|
|
2728
|
+
/**
|
|
2729
|
+
* The layouts stored for the content of a published version of a method —
|
|
2730
|
+
* `GET /v1/methods/{id}/versions/{n}/layouts` — newest first, at most 16, `items` empty when none
|
|
2731
|
+
* is stored. It touches only the platform's database and calls no runner, so it costs a host far
|
|
2732
|
+
* less than the `layouts` view of `validate`, which dry-runs every pipe of the method.
|
|
2733
|
+
*
|
|
2734
|
+
* It answers what is stored whatever `layouts_off` says: the switch is the host's to honour.
|
|
2735
|
+
* Another organization's public method reads too, with the layouts its builders stored, and a
|
|
2736
|
+
* `runner` reads what it is shown. Takes a bare catalog id, as `getMethod` does, and the version
|
|
2737
|
+
* number. `options.signal` cancels the read. Throws `RequestArgumentError` for a `version` that is
|
|
2738
|
+
* not a positive integer, before any request.
|
|
2739
|
+
*
|
|
2740
|
+
* Throws `ApiResponseError`: `404` `pipelex.catalog.not_found` for an unknown method or another
|
|
2741
|
+
* organization's internal one, its `params.resource` `method`, and for a version the method never
|
|
2742
|
+
* published, its `params.resource` `version`; `409` `pipelex.catalog.being_deleted` while the
|
|
2743
|
+
* erasure of the caller's own method is pending, under way or stopped; `500` `runtime.unexpected`
|
|
2744
|
+
* for a version stored before methods were trees, which names no tree. An answer whose `items` is
|
|
2745
|
+
* not an array is an answer the SDK cannot read, thrown as an `ApiResponseError` too; an entry
|
|
2746
|
+
* that does not read as a layout is left out (`readMethodLayouts`), so one layout it cannot read
|
|
2747
|
+
* never costs the caller the others.
|
|
2748
|
+
*/
|
|
2749
|
+
async listMethodLayouts(methodId, version, options = {}) {
|
|
2750
|
+
const call = "listMethodLayouts";
|
|
2751
|
+
if (!Number.isSafeInteger(version) || version < 1) {
|
|
2752
|
+
throw argumentRefusal(call, LAYOUT_LIST_VERSION_ARGUMENT, describeNumberArgument(version), "not_a_positive_integer");
|
|
2753
|
+
}
|
|
2754
|
+
const endpoint = `${methodPath(methodId, call)}/versions/${version}/layouts`;
|
|
2755
|
+
const res = await this.requestProductAnswer("GET", endpoint, undefined, {
|
|
2756
|
+
signal: options.signal,
|
|
2757
|
+
});
|
|
2758
|
+
const answer = this.readObjectAnswer("GET", endpoint, res);
|
|
2759
|
+
if (!Array.isArray(answer.items)) {
|
|
2760
|
+
throw this.unreadableAnswer("GET", `/${API_PREFIX}/${endpoint}`, res, "a list of layouts whose `items` is not an array");
|
|
2761
|
+
}
|
|
2762
|
+
return { ...answer, items: readMethodLayouts(answer.items) };
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* Set which catalogs of a method have their layouts switched off —
|
|
2766
|
+
* `PUT /v1/methods/{id}/layouts-off` — and return the method, its `layouts_off` the list as held.
|
|
2767
|
+
*
|
|
2768
|
+
* `catalogs` is the whole list and replaces the one held, each catalog id once, at most 8, empty
|
|
2769
|
+
* when every design is on: to change one, read `layouts_off` and write the new list. The switch
|
|
2770
|
+
* acts at once, with no publish, and holds across every publish. A layout switched off is kept,
|
|
2771
|
+
* and `listMethodLayouts` still answers it; a host draws the plain page instead. Like a rename, it
|
|
2772
|
+
* never moves the draft token, so the `updated_at` a caller holds stays valid.
|
|
2773
|
+
*
|
|
2774
|
+
* The last write to land is the one held. `options.idempotencyKey` is sent as the
|
|
2775
|
+
* `Idempotency-Key` header, so a write whose answer was lost is sent again under the same key and
|
|
2776
|
+
* answered as the first was (`MethodLayoutsOffOptions`).
|
|
2777
|
+
*
|
|
2778
|
+
* The write changes the method, so it takes an `admin` or a `member` of the method's
|
|
2779
|
+
* organization. Takes a bare catalog id, as `getMethod` does. Throws `RequestArgumentError` before
|
|
2780
|
+
* any request for `catalogs` that is not an array; its entries are sent as given. Throws
|
|
2781
|
+
* `ApiResponseError`: `404` `pipelex.catalog.not_found` for an unknown method or another
|
|
2782
|
+
* organization's, a public one included; `403` `auth.forbidden` naming the `member` requirement
|
|
2783
|
+
* for a `runner` or a caller with no recognised role; `409` `pipelex.catalog.being_deleted` while
|
|
2784
|
+
* the method's erasure is pending, under way or stopped; `413` `request.too_large` for a list that
|
|
2785
|
+
* would leave the method too large to publish, which a list that does not grow never is; and
|
|
2786
|
+
* `422` `request.invalid` for more than 8 catalogs, a catalog named twice or one that is not a
|
|
2787
|
+
* slug. Under an idempotency key, `409` `request.idempotency_conflict` for the key sent with a
|
|
2788
|
+
* different list and `409` `request.idempotency_key_in_use` while its earlier request is still
|
|
2789
|
+
* being handled; a key that is not 1 to 255 visible ASCII characters is a `RequestArgumentError`
|
|
2790
|
+
* before any request.
|
|
2791
|
+
*/
|
|
2792
|
+
async setMethodLayoutsOff(methodId, catalogs, options = {}) {
|
|
2793
|
+
const call = "setMethodLayoutsOff";
|
|
2794
|
+
const endpoint = `${methodPath(methodId, call)}/layouts-off`;
|
|
2795
|
+
if (!Array.isArray(catalogs)) {
|
|
2796
|
+
throw argumentRefusal(call, LAYOUTS_OFF_CATALOGS_ARGUMENT, describeNumberArgument(catalogs), "not_a_list");
|
|
2797
|
+
}
|
|
2798
|
+
const idempotencyKey = idempotencyKeyOf(options, "set the layouts off");
|
|
2799
|
+
return this.requestMethodData("PUT", endpoint, { catalogs }, { idempotencyKey });
|
|
2800
|
+
}
|
|
2638
2801
|
/**
|
|
2639
2802
|
* Copy a method into an organization the caller may change methods in —
|
|
2640
2803
|
* `POST /v1/methods/{id}/copy`: the method's own organization for a duplicate, another team, or
|
|
@@ -4402,13 +4565,14 @@ const MAX_IDEMPOTENCY_KEY_LENGTH = 255;
|
|
|
4402
4565
|
*/
|
|
4403
4566
|
const VISIBLE_ASCII = /^[\x21-\x7E]+$/;
|
|
4404
4567
|
/**
|
|
4405
|
-
* The idempotency key a start, a method copy, a
|
|
4568
|
+
* The idempotency key a start, a method copy, a layout write, a write of the layouts switched off,
|
|
4569
|
+
* a secret rotation or a delivery replay sends, or
|
|
4406
4570
|
* `undefined` when the options carry none (absent or `null`). A key that is not a string of 1 to
|
|
4407
4571
|
* 255 visible ASCII characters is refused with a `RequestArgumentError` rather than dropped, an
|
|
4408
4572
|
* empty one included, since a request sent without the key its caller gave would lose the
|
|
4409
4573
|
* protection the caller asked for without a word. `request` says what the request does, as the
|
|
4410
|
-
* refusal's last sentence names it ("start the run", "copy the method", "
|
|
4411
|
-
* "replay the delivery"). The message never quotes the key, which a caller may derive from
|
|
4574
|
+
* refusal's last sentence names it ("start the run", "copy the method", "store the layout",
|
|
4575
|
+
* "set the layouts off", "rotate the secret", "replay the delivery"). The message never quotes the key, which a caller may derive from
|
|
4412
4576
|
* something private. The Python SDK holds a key to the same rule, in the same words.
|
|
4413
4577
|
*/
|
|
4414
4578
|
function idempotencyKeyOf(options, request = "start the run") {
|
|
@@ -4771,6 +4935,54 @@ function withVerdictItems(answer) {
|
|
|
4771
4935
|
read.warnings = readProblemItems(answer.warnings);
|
|
4772
4936
|
return read;
|
|
4773
4937
|
}
|
|
4938
|
+
/**
|
|
4939
|
+
* A valid verdict with its `layouts` read entry by entry (`readMethodLayouts`), only when the
|
|
4940
|
+
* answer carries the key: each entry that reads as a layout is kept, in the answer's order, and a
|
|
4941
|
+
* list left with none is removed, so `layouts` is either absent or holds at least one layout, as
|
|
4942
|
+
* the platform sends it. Any other answer is returned as it came.
|
|
4943
|
+
*/
|
|
4944
|
+
function withReadLayouts(answer) {
|
|
4945
|
+
if (!isPlainObject(answer) || answer.is_valid !== true || !Object.hasOwn(answer, "layouts")) {
|
|
4946
|
+
return answer;
|
|
4947
|
+
}
|
|
4948
|
+
const read = { ...answer };
|
|
4949
|
+
const layouts = readMethodLayouts(answer.layouts);
|
|
4950
|
+
if (layouts.length === 0)
|
|
4951
|
+
delete read.layouts;
|
|
4952
|
+
else
|
|
4953
|
+
read.layouts = layouts;
|
|
4954
|
+
return read;
|
|
4955
|
+
}
|
|
4956
|
+
/** `source`'s members that `members` lists, each `optional` one left out when `null` or omitted. */
|
|
4957
|
+
function sentMembers(members, source) {
|
|
4958
|
+
const sent = {};
|
|
4959
|
+
for (const [member, presence] of Object.entries(members)) {
|
|
4960
|
+
const value = source[member];
|
|
4961
|
+
if (presence === "required" || value != null)
|
|
4962
|
+
sent[member] = value;
|
|
4963
|
+
}
|
|
4964
|
+
return sent;
|
|
4965
|
+
}
|
|
4966
|
+
/**
|
|
4967
|
+
* The body `storeMethodLayout` sends for `layout`: the members of `MethodLayoutInput` and nothing
|
|
4968
|
+
* else, since the route's body is closed at both levels, `sample_json` and each provenance member
|
|
4969
|
+
* that is `null` or omitted left out for the platform's default. The members are `LAYOUT_INPUT_MEMBERS` and
|
|
4970
|
+
* `PROVENANCE_INPUT_MEMBERS`, which the build holds to the interfaces. A layout or a provenance that is not an object is
|
|
4971
|
+
* refused with `RequestArgumentError` before anything is sent; every other member is sent as given,
|
|
4972
|
+
* for the platform to hold to its shapes, its `422` naming the member it refuses.
|
|
4973
|
+
*/
|
|
4974
|
+
function methodLayoutBody(layout, call) {
|
|
4975
|
+
if (!isPlainObject(layout)) {
|
|
4976
|
+
throw argumentRefusal(call, LAYOUT_ARGUMENT, describeNumberArgument(layout), "not_an_object");
|
|
4977
|
+
}
|
|
4978
|
+
const provenance = layout.provenance;
|
|
4979
|
+
if (!isPlainObject(provenance)) {
|
|
4980
|
+
throw argumentRefusal(call, LAYOUT_PROVENANCE_ARGUMENT, describeNumberArgument(provenance), "not_an_object");
|
|
4981
|
+
}
|
|
4982
|
+
const body = sentMembers(LAYOUT_INPUT_MEMBERS, layout);
|
|
4983
|
+
body.provenance = sentMembers(PROVENANCE_INPUT_MEMBERS, provenance);
|
|
4984
|
+
return body;
|
|
4985
|
+
}
|
|
4774
4986
|
/**
|
|
4775
4987
|
* A run record with its `error` kept only when it is a problem document, and read as `null`
|
|
4776
4988
|
* otherwise. Only when the record carries the `error` key, so a server that does not serve it
|