@pipelex/sdk 0.50.0 → 0.52.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
@@ -167,6 +167,18 @@ for await (const saved of client.iterateMethodExamples(created.method_id)) {
167
167
  // every example, oldest first
168
168
  }
169
169
 
170
+ // Copy a method into a team, or into your personal workspace, with its draft, its form inputs and
171
+ // its examples (web sessions only). A bundle naming storage the destination cannot read, or, in a
172
+ // duplicate, a file under the original's run files, which erasing the original would remove,
173
+ // answers `refused`, a verdict rather than an error; the key makes a retry copy once.
174
+ const copy = await client.copyMethod(
175
+ created.method_id,
176
+ { destination_org_id: "org_team", expected_draft_updated_at: draft.updated_at },
177
+ { idempotencyKey: crypto.randomUUID() },
178
+ );
179
+ if (copy.outcome === "copied") console.log(copy.method.copied_from); // the original it names
180
+ const copies = await client.listMethodCopies(created.method_id); // where the copies went
181
+
170
182
  // The organization's files, newest first: uploads, test files and the files runs made. Each row's
171
183
  // `uri` goes into a run's inputs as any address does. A filtered page can be empty and still carry
172
184
  // a nextCursor; iterateFiles follows it.
@@ -273,6 +285,7 @@ These pages ship inside the published package, so a reader who has only installe
273
285
  | [`docs/method-trees.md`](./docs/method-trees.md) | A method's tree: the four forms a run, a validation, a save and the crate routes take it in (a directory on disk among them), how it travels inline or by hash and what is uploaded, the files a step reads, the template check's warnings and which step reads which file of the tree, the deployment's limits, the read's listing with its links, `writeMethodTree`, and `MethodFileTransferError` |
274
286
  | [`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 |
275
287
  | [`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 |
288
+ | [`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, the copies a method lists, and `copied_from` |
276
289
  | [`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 |
277
290
  | [`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 |
278
291
  | [`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 |
@@ -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.50.0";
18
+ export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.52.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, ListMethodVersionsQuery, ListRunFilesQuery, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleAssistantSaveInput, MethodExampleAssistantSaveResult, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, 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, ListMethodCopiesQuery, ListMethodExamplesQuery, ListMethodsQuery, ListMethodVersionsQuery, ListRunFilesQuery, MethodCopyInput, MethodCopyPage, MethodCopyResult, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleAssistantSaveInput, MethodExampleAssistantSaveResult, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, 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 { UploadableAsset, UploadFileOptions, UploadRecord } from "./upload.js";
@@ -122,6 +122,36 @@ export interface PipelexStartRequestOptions {
122
122
  */
123
123
  idempotencyKey?: string | null;
124
124
  }
125
+ /**
126
+ * What a method copy sends beside its body: the request's own header, which no copy argument
127
+ * reaches, camelCase since it is no wire field of the copy.
128
+ */
129
+ export interface MethodCopyRequestOptions {
130
+ /**
131
+ * The copy's idempotency key, sent as the `Idempotency-Key` header of
132
+ * `POST /v1/methods/{id}/copy`, and not sent at all when absent or `null`. Generate one per copy
133
+ * the caller means to make (a `crypto.randomUUID()` is a good key), and keep it, with the
134
+ * arguments unchanged, across that copy's retries.
135
+ *
136
+ * The hosted API stores the copy's answer under the key for 24 hours, scoped to the caller and
137
+ * the method's organization, so the same copy sent again under the same key is answered with the
138
+ * stored answer and copies nothing more: a copy whose answer was lost, as on a client timeout,
139
+ * can be sent again unchanged and makes one copy, not two. A verdict and a `4xx` refusal of the
140
+ * copy are stored alike, so the same copy under the same key is answered the same again; a `5xx`
141
+ * and a refusal for its timing (a `408`, `425` or `429`) are not stored, and a copy that failed
142
+ * with one left nothing visible, but for the `503` whose `detail` names the copy (see
143
+ * `copyMethod`), so the same key sends it afresh. The same key with a different body is refused
144
+ * (`409` `conflict`, `input` and not retryable, which its `errorDomain` tells from the `runtime`
145
+ * `conflict` of an incomplete tree), as is a key whose earlier request is still being handled
146
+ * (`409` `idempotency_key_in_use`, retryable: send it again under the same key in a few
147
+ * seconds); a changed copy, such as one with a fresh draft token, goes under a new key. See
148
+ * `docs/method-copy.md`.
149
+ *
150
+ * A key is held to the rule a start's is (`PipelexStartRequestOptions.idempotencyKey`): 1 to
151
+ * 255 visible ASCII characters, anything else a `RequestArgumentError` before any request.
152
+ */
153
+ idempotencyKey?: string | null;
154
+ }
125
155
  /**
126
156
  * `start()` / `startAndWaitForResult()` options — the same, for the durable path, plus the start
127
157
  * request's own `idempotencyKey`.
@@ -231,7 +261,7 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
231
261
  * resolved absolute URL. `mayStartRun` marks a request that may create a run,
232
262
  * whose `ApiUnreachableError` then says whether it may have (see `RunCreation`).
233
263
  * `idempotencyKey`, already checked by `idempotencyKeyOf`, is sent as the
234
- * `Idempotency-Key` header; only `start` passes one.
264
+ * `Idempotency-Key` header; only `start` and `copyMethod` pass one.
235
265
  */
236
266
  private requestRaw;
237
267
  /**
@@ -1215,6 +1245,80 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1215
1245
  * never rewritten as one, which names no tree to serve.
1216
1246
  */
1217
1247
  getMethodVersion(methodId: string, version: number): Promise<MethodVersion>;
1248
+ /**
1249
+ * Copy a method into an organization the caller may change methods in —
1250
+ * `POST /v1/methods/{id}/copy`: the method's own organization for a duplicate, another team, or
1251
+ * the caller's personal workspace.
1252
+ *
1253
+ * The copy is a new, unpublished method of the destination, the caller's, holding the
1254
+ * original's draft tree (the same `draft_digest`), its name unless `name` gives another, its
1255
+ * form inputs and every saved example, each stored file they name copied into the destination,
1256
+ * and `copied_from` naming the original. Published versions and runs are not copied, and the
1257
+ * original stays as it was. The copy takes the draft, never the latest published version; with
1258
+ * `expected_draft_updated_at`, it takes exactly the draft the caller last saw.
1259
+ *
1260
+ * The answer is a `MethodCopyResult` discriminated on `outcome` — branch on it: `copied` with
1261
+ * the copy as `method`, `examples_copied` and `stored_files_copied`; `refused` with the
1262
+ * `reason` `bundle_names_other_storage`, a `message` and every `references` entry (`{ path, uri
1263
+ * }`) a `.mthds` file of the draft holds that the destination cannot read and, in a duplicate,
1264
+ * that names a file under the original's run files, which erasing the original would remove,
1265
+ * nothing written.
1266
+ *
1267
+ * The route serves web sessions only: a Pipelex API key belongs to one organization and cannot
1268
+ * speak for another. `options.idempotencyKey` is sent as the `Idempotency-Key` header, so a copy
1269
+ * sent again under the same key is answered with the first one's answer rather than copied
1270
+ * twice (see `MethodCopyRequestOptions`).
1271
+ *
1272
+ * Takes a bare catalog id, as `getMethod` does. Sends `destination_org_id` and, when given,
1273
+ * `name` and `expected_draft_updated_at`, and nothing else: the body is closed, and a `null`
1274
+ * reads as not given. Throws `RequestArgumentError` before any request for a
1275
+ * `destination_org_id` that is not a non-empty string, a `name` or an
1276
+ * `expected_draft_updated_at` given that is not a string, and a malformed idempotency key.
1277
+ * Throws `ApiResponseError` when no verdict was produced, each refusal writing nothing a reader
1278
+ * sees but for the `503` whose `detail` names the copy: `403` `forbidden` for a request that is
1279
+ * not from a web session; `403`
1280
+ * `role_not_permitted` for a role that may not change methods, in the method's organization or
1281
+ * in the destination; `404` `organization_not_found` for a destination the caller does not
1282
+ * belong to, still being set up or unknown, alike; `404` `not_found` for an unknown method;
1283
+ * `409` `method_being_deleted`; `409` `method_update_conflict` for a draft that moved since
1284
+ * `expected_draft_updated_at`; `409` `conflict`, `runtime` and not retryable, for a draft whose
1285
+ * tree the method's organization holds no record of or no longer holds every file of; `413`
1286
+ * `payload_too_large` for a copied example, or the copy's row, too large for one stored item,
1287
+ * the row keeping room for the summary a publish adds, so a `name` that would leave none is
1288
+ * refused; `422` `validation_failed` for a body that does not validate, such as a name holding a
1289
+ * lone surrogate; `422` `method_copy_too_large` past one of the copy's bounds on the stored files
1290
+ * it copies, named by `problemDocument.limit` (`MethodCopyLimit`); a retryable `502` when the
1291
+ * platform could not read the caller's memberships; a `500` `storage_backend_failed`, retryable
1292
+ * unless the platform has no usable credentials for storage; and a retryable `503` past the
1293
+ * copy's deadline, when a stored file it copies is written over meanwhile, or when DynamoDB
1294
+ * fails. When DynamoDB failed both the copy's row write and the read that would confirm it, that
1295
+ * `503`'s `detail` names the copy's method id: the copy may stand in the destination, so look for
1296
+ * that id there before copying again, since a retry may make a second copy. An answer whose
1297
+ * `outcome` is neither arm's, a `copied` one whose `method` is not an object or a `refused` one
1298
+ * whose `references` is not an array, is an answer the SDK cannot read, thrown as an
1299
+ * `ApiResponseError` too.
1300
+ */
1301
+ copyMethod(methodId: string, input: MethodCopyInput, options?: MethodCopyRequestOptions): Promise<MethodCopyResult>;
1302
+ /**
1303
+ * One page of the copies made of a method, in the order of the copies' ids, which says nothing
1304
+ * of when each was made — `GET /v1/methods/{id}/copies`.
1305
+ *
1306
+ * Each row is the `MethodCopyMarker` the original keeps of a copy: the copy's id, the
1307
+ * destination's id, the member who made it and when. The destination is named by its id alone,
1308
+ * never its name, so a surface shows a destination's name only from the reader's own
1309
+ * memberships, and a copy in an organization the reader does not belong to stays a bare id. A
1310
+ * marker stays when its copy is erased, and the copy then reads as not found. Pass the page's
1311
+ * `nextCursor` back as `cursor` to continue; `null` is the last page. A method never copied,
1312
+ * and a copy itself, answer an empty page. The listing is open to every member of the method's
1313
+ * organization, a runner included, as every read of a method is.
1314
+ *
1315
+ * Takes a bare catalog id, as `getMethod` does. Throws `ApiResponseError`: `404` `not_found` for
1316
+ * an unknown method, `409` `method_being_deleted`, `400` `malformed_request` for a cursor the
1317
+ * listing did not issue for this method, `422` for a `limit` outside 1 to 100. A page whose
1318
+ * `items` is not an array or whose `next_cursor` is neither a string nor `null` is an answer the
1319
+ * SDK cannot read, thrown as an `ApiResponseError` too.
1320
+ */
1321
+ listMethodCopies(methodId: string, query?: ListMethodCopiesQuery): Promise<MethodCopyPage>;
1218
1322
  /**
1219
1323
  * Erase a method and everything it produced — `DELETE /v1/methods/{id}`.
1220
1324
  *
package/dist/client.js CHANGED
@@ -233,6 +233,11 @@ const ARCHIVE_RUN_ID_ARGUMENT = "a run_id naming the run whose files to zip, a n
233
233
  const ARCHIVE_NAME_ARGUMENT = "a name for the zip, a string, or none";
234
234
  const DRAFT_PATH_ARGUMENT = "the tree path the file takes in the draft, a non-empty string";
235
235
  const DRAFT_TOKEN_ARGUMENT = "the draft token the caller last read, the method's `updated_at`, a string, or none";
236
+ /** What each argument of a method copy is, as a refusal of it names it. */
237
+ const COPY_DESTINATION_ARGUMENT = "a destination_org_id, the canonical id of the organization to copy into, a non-empty string";
238
+ const COPY_NAME_ARGUMENT = "a name for the copy, a string, or none";
239
+ const COPY_DRAFT_TOKEN_ARGUMENT = "an expected_draft_updated_at, the draft token the caller last read, the method's " +
240
+ "`updated_at`, a string, or none";
236
241
  /**
237
242
  * The refusal of an argument a call cannot take, worded as every such refusal is:
238
243
  * `<call>() takes <what>; got <got>.`, with what made it unusable as `cause` when there is one.
@@ -341,6 +346,8 @@ const isString = (value) => typeof value === "string";
341
346
  const FILE_ARCHIVE_STATES = ["queued", "building", "ready", "failed"];
342
347
  /** What a copy into a draft did in the blob store, the field its answer is discriminated on. */
343
348
  const DRAFT_FILE_BLOBS = ["copied", "already_held"];
349
+ /** What a method copy did, the field its answer is discriminated on. */
350
+ const METHOD_COPY_OUTCOMES = ["copied", "refused"];
344
351
  /** Whether a run's records are all written, the field a reader of its files branches on. */
345
352
  const RUN_FILES_STATES = ["listing", "listed"];
346
353
  /** The lists of a page of a run's files, each an array on every page. */
@@ -479,7 +486,7 @@ export class PipelexApiClient {
479
486
  * resolved absolute URL. `mayStartRun` marks a request that may create a run,
480
487
  * whose `ApiUnreachableError` then says whether it may have (see `RunCreation`).
481
488
  * `idempotencyKey`, already checked by `idempotencyKeyOf`, is sent as the
482
- * `Idempotency-Key` header; only `start` passes one.
489
+ * `Idempotency-Key` header; only `start` and `copyMethod` pass one.
483
490
  */
484
491
  async requestRaw(method, url, options = {}) {
485
492
  const hasBody = options.body !== undefined;
@@ -692,6 +699,7 @@ export class PipelexApiClient {
692
699
  body,
693
700
  timeoutMs: options.timeoutMs ?? POLL_REQUEST_TIMEOUT_MS,
694
701
  signal: options.signal,
702
+ idempotencyKey: options.idempotencyKey,
695
703
  });
696
704
  if (res.status < 200 || res.status >= 300) {
697
705
  this.throwApiResponseError(method, endpoint, res);
@@ -2347,6 +2355,125 @@ export class PipelexApiClient {
2347
2355
  const res = await this.requestProductAnswer("GET", endpoint, undefined);
2348
2356
  return this.readSavedTree("GET", endpoint, withLinks(res), "a published version");
2349
2357
  }
2358
+ /**
2359
+ * Copy a method into an organization the caller may change methods in —
2360
+ * `POST /v1/methods/{id}/copy`: the method's own organization for a duplicate, another team, or
2361
+ * the caller's personal workspace.
2362
+ *
2363
+ * The copy is a new, unpublished method of the destination, the caller's, holding the
2364
+ * original's draft tree (the same `draft_digest`), its name unless `name` gives another, its
2365
+ * form inputs and every saved example, each stored file they name copied into the destination,
2366
+ * and `copied_from` naming the original. Published versions and runs are not copied, and the
2367
+ * original stays as it was. The copy takes the draft, never the latest published version; with
2368
+ * `expected_draft_updated_at`, it takes exactly the draft the caller last saw.
2369
+ *
2370
+ * The answer is a `MethodCopyResult` discriminated on `outcome` — branch on it: `copied` with
2371
+ * the copy as `method`, `examples_copied` and `stored_files_copied`; `refused` with the
2372
+ * `reason` `bundle_names_other_storage`, a `message` and every `references` entry (`{ path, uri
2373
+ * }`) a `.mthds` file of the draft holds that the destination cannot read and, in a duplicate,
2374
+ * that names a file under the original's run files, which erasing the original would remove,
2375
+ * nothing written.
2376
+ *
2377
+ * The route serves web sessions only: a Pipelex API key belongs to one organization and cannot
2378
+ * speak for another. `options.idempotencyKey` is sent as the `Idempotency-Key` header, so a copy
2379
+ * sent again under the same key is answered with the first one's answer rather than copied
2380
+ * twice (see `MethodCopyRequestOptions`).
2381
+ *
2382
+ * Takes a bare catalog id, as `getMethod` does. Sends `destination_org_id` and, when given,
2383
+ * `name` and `expected_draft_updated_at`, and nothing else: the body is closed, and a `null`
2384
+ * reads as not given. Throws `RequestArgumentError` before any request for a
2385
+ * `destination_org_id` that is not a non-empty string, a `name` or an
2386
+ * `expected_draft_updated_at` given that is not a string, and a malformed idempotency key.
2387
+ * Throws `ApiResponseError` when no verdict was produced, each refusal writing nothing a reader
2388
+ * sees but for the `503` whose `detail` names the copy: `403` `forbidden` for a request that is
2389
+ * not from a web session; `403`
2390
+ * `role_not_permitted` for a role that may not change methods, in the method's organization or
2391
+ * in the destination; `404` `organization_not_found` for a destination the caller does not
2392
+ * belong to, still being set up or unknown, alike; `404` `not_found` for an unknown method;
2393
+ * `409` `method_being_deleted`; `409` `method_update_conflict` for a draft that moved since
2394
+ * `expected_draft_updated_at`; `409` `conflict`, `runtime` and not retryable, for a draft whose
2395
+ * tree the method's organization holds no record of or no longer holds every file of; `413`
2396
+ * `payload_too_large` for a copied example, or the copy's row, too large for one stored item,
2397
+ * the row keeping room for the summary a publish adds, so a `name` that would leave none is
2398
+ * refused; `422` `validation_failed` for a body that does not validate, such as a name holding a
2399
+ * lone surrogate; `422` `method_copy_too_large` past one of the copy's bounds on the stored files
2400
+ * it copies, named by `problemDocument.limit` (`MethodCopyLimit`); a retryable `502` when the
2401
+ * platform could not read the caller's memberships; a `500` `storage_backend_failed`, retryable
2402
+ * unless the platform has no usable credentials for storage; and a retryable `503` past the
2403
+ * copy's deadline, when a stored file it copies is written over meanwhile, or when DynamoDB
2404
+ * fails. When DynamoDB failed both the copy's row write and the read that would confirm it, that
2405
+ * `503`'s `detail` names the copy's method id: the copy may stand in the destination, so look for
2406
+ * that id there before copying again, since a retry may make a second copy. An answer whose
2407
+ * `outcome` is neither arm's, a `copied` one whose `method` is not an object or a `refused` one
2408
+ * whose `references` is not an array, is an answer the SDK cannot read, thrown as an
2409
+ * `ApiResponseError` too.
2410
+ */
2411
+ async copyMethod(methodId, input, options = {}) {
2412
+ const call = "copyMethod";
2413
+ const endpoint = `${methodPath(methodId, call)}/copy`;
2414
+ const { destination_org_id: destinationOrgId, name, expected_draft_updated_at: token, } = input ?? {};
2415
+ const body = {
2416
+ destination_org_id: requireNonEmptyId(destinationOrgId, call, COPY_DESTINATION_ARGUMENT),
2417
+ };
2418
+ if (name != null) {
2419
+ if (typeof name !== "string") {
2420
+ throw argumentRefusal(call, COPY_NAME_ARGUMENT, describeNumberArgument(name));
2421
+ }
2422
+ body.name = name;
2423
+ }
2424
+ if (token != null) {
2425
+ if (typeof token !== "string") {
2426
+ throw argumentRefusal(call, COPY_DRAFT_TOKEN_ARGUMENT, describeNumberArgument(token));
2427
+ }
2428
+ body.expected_draft_updated_at = token;
2429
+ }
2430
+ const idempotencyKey = idempotencyKeyOf(options, "copy the method");
2431
+ const res = await this.requestProductAnswer("POST", endpoint, body, { idempotencyKey });
2432
+ const answer = this.readObjectAnswer("POST", endpoint, res);
2433
+ let fault;
2434
+ switch (answer.outcome) {
2435
+ case "copied":
2436
+ if (!isPlainObject(answer.method)) {
2437
+ fault = "a `copied` copy result whose `method` is not an object";
2438
+ }
2439
+ break;
2440
+ case "refused":
2441
+ if (!Array.isArray(answer.references)) {
2442
+ fault = "a `refused` copy result whose `references` is not an array";
2443
+ }
2444
+ break;
2445
+ default:
2446
+ fault = `a copy result whose \`outcome\` is not ${inWords(METHOD_COPY_OUTCOMES.map((outcome) => `\`${outcome}\``), "or")}`;
2447
+ }
2448
+ if (fault !== undefined) {
2449
+ throw this.unreadableAnswer("POST", `/${API_PREFIX}/${endpoint}`, res, fault);
2450
+ }
2451
+ return answer;
2452
+ }
2453
+ /**
2454
+ * One page of the copies made of a method, in the order of the copies' ids, which says nothing
2455
+ * of when each was made — `GET /v1/methods/{id}/copies`.
2456
+ *
2457
+ * Each row is the `MethodCopyMarker` the original keeps of a copy: the copy's id, the
2458
+ * destination's id, the member who made it and when. The destination is named by its id alone,
2459
+ * never its name, so a surface shows a destination's name only from the reader's own
2460
+ * memberships, and a copy in an organization the reader does not belong to stays a bare id. A
2461
+ * marker stays when its copy is erased, and the copy then reads as not found. Pass the page's
2462
+ * `nextCursor` back as `cursor` to continue; `null` is the last page. A method never copied,
2463
+ * and a copy itself, answer an empty page. The listing is open to every member of the method's
2464
+ * organization, a runner included, as every read of a method is.
2465
+ *
2466
+ * Takes a bare catalog id, as `getMethod` does. Throws `ApiResponseError`: `404` `not_found` for
2467
+ * an unknown method, `409` `method_being_deleted`, `400` `malformed_request` for a cursor the
2468
+ * listing did not issue for this method, `422` for a `limit` outside 1 to 100. A page whose
2469
+ * `items` is not an array or whose `next_cursor` is neither a string nor `null` is an answer the
2470
+ * SDK cannot read, thrown as an `ApiResponseError` too.
2471
+ */
2472
+ async listMethodCopies(methodId, query = {}) {
2473
+ const search = productQuery({ limit: query.limit, cursor: query.cursor });
2474
+ const endpoint = `${methodPath(methodId, "listMethodCopies")}/copies`;
2475
+ return this.requestPage(`${endpoint}${search}`);
2476
+ }
2350
2477
  /**
2351
2478
  * Erase a method and everything it produced — `DELETE /v1/methods/{id}`.
2352
2479
  *
@@ -3802,7 +3929,7 @@ function assertLinkageMethodIdIsBare(options, treeForms) {
3802
3929
  function nonEmptyString(value) {
3803
3930
  return value != null && value.length > 0 ? value : undefined;
3804
3931
  }
3805
- /** The request header the hosted API reads a run start's idempotency key from. */
3932
+ /** The request header the hosted API reads a request's idempotency key from. */
3806
3933
  const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
3807
3934
  /**
3808
3935
  * The longest idempotency key a start sends. It is room for any generated key, and keeps the
@@ -3817,21 +3944,22 @@ const MAX_IDEMPOTENCY_KEY_LENGTH = 255;
3817
3944
  */
3818
3945
  const VISIBLE_ASCII = /^[\x21-\x7E]+$/;
3819
3946
  /**
3820
- * The idempotency key a start sends, or `undefined` when the options carry none (absent or
3821
- * `null`). A key that is not a string of 1 to 255 visible ASCII characters is refused with a
3822
- * `RequestArgumentError` rather than dropped, an empty one included, since a start sent without
3823
- * the key its caller gave would lose the protection the caller asked for without a word. The
3824
- * message never quotes the key, which a caller may derive from something private. The Python SDK
3825
- * holds a key to the same rule, in the same words.
3947
+ * The idempotency key a start or a method copy sends, or `undefined` when the options carry none
3948
+ * (absent or `null`). A key that is not a string of 1 to 255 visible ASCII characters is refused
3949
+ * with a `RequestArgumentError` rather than dropped, an empty one included, since a request sent
3950
+ * without the key its caller gave would lose the protection the caller asked for without a word.
3951
+ * `request` says what the request does, as the refusal's last sentence names it ("start the run",
3952
+ * "copy the method"). The message never quotes the key, which a caller may derive from something
3953
+ * private. The Python SDK holds a key to the same rule, in the same words.
3826
3954
  */
3827
- function idempotencyKeyOf(options) {
3955
+ function idempotencyKeyOf(options, request = "start the run") {
3828
3956
  const key = options.idempotencyKey;
3829
3957
  if (key == null)
3830
3958
  return undefined;
3831
3959
  const fault = idempotencyKeyFault(key);
3832
3960
  if (fault !== undefined) {
3833
3961
  throw new RequestArgumentError(`The idempotency key must be 1 to ${MAX_IDEMPOTENCY_KEY_LENGTH} visible ASCII characters ` +
3834
- `(\`!\` to \`~\`), and this one ${fault}. Leave it out to start the run without one.`);
3962
+ `(\`!\` to \`~\`), and this one ${fault}. Leave it out to ${request} without one.`);
3835
3963
  }
3836
3964
  return key;
3837
3965
  }