@pipelex/sdk 0.59.0 → 0.61.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 +14 -2
- package/dist/cli/script.d.ts +1 -1
- package/dist/client.d.ts +132 -65
- package/dist/client.js +296 -82
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +7 -3
- package/dist/product-models.d.ts +214 -56
- package/dist/product-models.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/webhooks.d.ts +19 -1
- package/docs/architecture.md +6 -5
- package/docs/crate-routes.md +5 -5
- package/docs/errors.md +2 -0
- package/docs/files.md +1 -0
- package/docs/input-preparation.md +1 -1
- package/docs/method-copy.md +14 -19
- package/docs/method-trees.md +1 -1
- package/docs/public-methods.md +97 -0
- package/docs/webhooks.md +15 -4
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -582,7 +582,8 @@ export class PipelexApiClient {
|
|
|
582
582
|
* `url` is a fully resolved absolute URL. `mayStartRun` marks a request that may create a run,
|
|
583
583
|
* whose errors then say whether it may have (see `RunCreation`).
|
|
584
584
|
* `idempotencyKey`, already checked by `idempotencyKeyOf`, is sent as the
|
|
585
|
-
* `Idempotency-Key` header; only `start
|
|
585
|
+
* `Idempotency-Key` header; only `start`, `copyMethod`, `rotateWebhookSecret` and
|
|
586
|
+
* `replayRunWebhookDelivery` pass one.
|
|
586
587
|
*/
|
|
587
588
|
async requestRaw(method, url, options = {}) {
|
|
588
589
|
const hasBody = options.body !== undefined;
|
|
@@ -1304,10 +1305,10 @@ export class PipelexApiClient {
|
|
|
1304
1305
|
*
|
|
1305
1306
|
* The mapping is what makes their no-verdict arms usable: a crate route answers `422`
|
|
1306
1307
|
* for a request it cannot act on (a pipe selection `pipe-io` refuses; an unknown
|
|
1307
|
-
* `kind`/`target`, or a `pipe_ref` on the concept-set-wide `types` kind, on `codegen
|
|
1308
|
-
*
|
|
1309
|
-
* server-side as of pipelex-api 0.21.0). A caller branches on
|
|
1310
|
-
* never on a message.
|
|
1308
|
+
* `kind`/`target`, or a `pipe_ref` on the concept-set-wide `types` kind, on `codegen`;
|
|
1309
|
+
* the reserved registry-form `method_ref`, coded `request.unsupported`, on all three, the
|
|
1310
|
+
* address form being resolved server-side as of pipelex-api 0.21.0). A caller branches on
|
|
1311
|
+
* the problem's `code`, never on a message.
|
|
1311
1312
|
*
|
|
1312
1313
|
* All of these are static and inference-free, so they default to the management-call
|
|
1313
1314
|
* timeout. The internal raise is `crateRequestTimeoutMs`'s: the fetch-sized budget a
|
|
@@ -1429,9 +1430,8 @@ export class PipelexApiClient {
|
|
|
1429
1430
|
// unexposed — see their section above for why that blocks nothing. `pipe-io` is
|
|
1430
1431
|
// newer: a runner serves it from `pipelex-api` v0.33.0, and codes its selection
|
|
1431
1432
|
// refusals `request.pipe_not_found`, `request.pipe_unspecified` and
|
|
1432
|
-
// `request.pipe_ambiguous
|
|
1433
|
-
//
|
|
1434
|
-
// `docs/crate-routes.md`).
|
|
1433
|
+
// `request.pipe_ambiguous` from v0.90.0; the dev plane serves it too, measured on
|
|
1434
|
+
// api-dev.pipelex.com on 2026-10-31 (see `docs/crate-routes.md`).
|
|
1435
1435
|
//
|
|
1436
1436
|
// All three are STATIC routes (no dry-run sweep), so like every static sibling they
|
|
1437
1437
|
// take no `timeoutMs` — see the policy note on `requestExtension` before adding one
|
|
@@ -1460,7 +1460,7 @@ export class PipelexApiClient {
|
|
|
1460
1460
|
* protocol's `files` map or as a `directory`, `tree` or `contents` the client sends
|
|
1461
1461
|
* inline when small and by hash otherwise (see {@link CrateRequestBase}); an
|
|
1462
1462
|
* address-form `method_ref` (server-resolved, pipelex-api >= 0.21.0; the registry form
|
|
1463
|
-
*
|
|
1463
|
+
* is a `422` `request.unsupported`); or a hosted `method_id` (platform-resolved — see
|
|
1464
1464
|
* {@link PipelexHostedToolingExtensions}). An empty map or string is absent.
|
|
1465
1465
|
*
|
|
1466
1466
|
* `options.signal` stops the call between the steps a `directory`, `tree` or `contents`
|
|
@@ -1487,7 +1487,8 @@ export class PipelexApiClient {
|
|
|
1487
1487
|
* Same 200-verdict discipline and same three-form closure selector as
|
|
1488
1488
|
* {@link resolve}. Only a no-verdict condition throws `ApiResponseError`: an
|
|
1489
1489
|
* unknown `kind`/`target`, a `pipe_ref` on the concept-set-wide `types` kind, or
|
|
1490
|
-
* a malformed selector is a 422
|
|
1490
|
+
* a malformed selector is a 422, and so is a registry-form `method_ref`, coded
|
|
1491
|
+
* `request.unsupported`.
|
|
1491
1492
|
*/
|
|
1492
1493
|
async codegen(request, options = {}) {
|
|
1493
1494
|
return this.crateRequest("codegen", "codegen", request, options);
|
|
@@ -1520,9 +1521,9 @@ export class PipelexApiClient {
|
|
|
1520
1521
|
* Only a no-verdict condition throws `ApiResponseError`: a malformed selector and an
|
|
1521
1522
|
* over-limit file are `422` problems; a selection the route refuses is a `422` whose
|
|
1522
1523
|
* `code` is `request.pipe_not_found` (an unknown `pipe_ref`), `request.pipe_unspecified`
|
|
1523
|
-
* (no entry pipe) or `request.pipe_ambiguous`
|
|
1524
|
-
* candidates in its `params`; a registry-form `method_ref` is a
|
|
1525
|
-
* artifacts cannot be derived is a `500`.
|
|
1524
|
+
* (no entry pipe, or several bundles' `main_pipe` declarations) or `request.pipe_ambiguous`
|
|
1525
|
+
* (an ambiguous bare code), the candidates in its `params`; a registry-form `method_ref` is a
|
|
1526
|
+
* `422` `request.unsupported`; a pipe whose artifacts cannot be derived is a `500`.
|
|
1526
1527
|
*
|
|
1527
1528
|
* A `method_ref` gets the fetch-sized budget, as on the other crate routes. On the
|
|
1528
1529
|
* hosted API the gateway caps a request at about 30 seconds whatever the client allows,
|
|
@@ -1934,7 +1935,84 @@ export class PipelexApiClient {
|
|
|
1934
1935
|
yield* followCursor("listMethods", (cursor) => this.listMethods({ ...query, cursor }));
|
|
1935
1936
|
}
|
|
1936
1937
|
/**
|
|
1937
|
-
*
|
|
1938
|
+
* One page of the template catalog — `GET /v1/method-templates`: the public methods of the
|
|
1939
|
+
* organization the deployment names as its templates organization, the same for every caller.
|
|
1940
|
+
*
|
|
1941
|
+
* The templates the ordering lists lead, in its order, and every other template follows by name.
|
|
1942
|
+
* `ordering` names the ordering to serve, such as `default` or an arm of a test; an absent,
|
|
1943
|
+
* unknown or malformed name serves `default`, and the page's `ordering` names the one served,
|
|
1944
|
+
* `null` when there is none. `category` keeps one category's templates, by slug, in the same
|
|
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.
|
|
1948
|
+
*
|
|
1949
|
+
* Each `MethodTemplate` carries the method's id and name, its latest published version's number,
|
|
1950
|
+
* description and categories, its organization's display name and each thumbnail with its size
|
|
1951
|
+
* and a link to its bytes valid for about fifteen minutes. A template reads, runs and copies by
|
|
1952
|
+
* its `method_id`, as another organization's public method does (`getMethod`, `copyMethod`).
|
|
1953
|
+
*
|
|
1954
|
+
* Pass the page's `nextCursor` back as `cursor` with the same `ordering` and `category`, which the
|
|
1955
|
+
* cursor does not hold; `null` is the last page. `iterateMethodTemplates` follows it for you.
|
|
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.
|
|
1968
|
+
*/
|
|
1969
|
+
async listMethodTemplates(query = {}) {
|
|
1970
|
+
const search = productQuery({
|
|
1971
|
+
ordering: query.ordering,
|
|
1972
|
+
category: query.category,
|
|
1973
|
+
limit: query.limit,
|
|
1974
|
+
cursor: query.cursor,
|
|
1975
|
+
});
|
|
1976
|
+
const endpoint = `method-templates${search}`;
|
|
1977
|
+
const res = withLinks(await this.requestProductAnswer("GET", endpoint, undefined));
|
|
1978
|
+
const page = this.readObjectAnswer("GET", endpoint, res);
|
|
1979
|
+
const { items, next_cursor: nextCursor, ordering, categories } = page;
|
|
1980
|
+
let fault = null;
|
|
1981
|
+
if (!Array.isArray(items))
|
|
1982
|
+
fault = "`items` is not an array";
|
|
1983
|
+
else if (nextCursor !== null && typeof nextCursor !== "string") {
|
|
1984
|
+
fault = "`next_cursor` is neither a string nor null";
|
|
1985
|
+
}
|
|
1986
|
+
else if (ordering !== null && typeof ordering !== "string") {
|
|
1987
|
+
fault = "`ordering` is neither a string nor null";
|
|
1988
|
+
}
|
|
1989
|
+
else if (!Array.isArray(categories))
|
|
1990
|
+
fault = "`categories` is not an array";
|
|
1991
|
+
else
|
|
1992
|
+
fault = methodTemplatesFault(items) ?? methodCategoriesFault(categories);
|
|
1993
|
+
if (fault !== null) {
|
|
1994
|
+
throw this.unreadableAnswer("GET", `/${API_PREFIX}/${endpoint}`, res, `a page of templates whose ${fault}`);
|
|
1995
|
+
}
|
|
1996
|
+
return {
|
|
1997
|
+
items: items,
|
|
1998
|
+
nextCursor: nextCursor,
|
|
1999
|
+
ordering: ordering,
|
|
2000
|
+
categories: categories,
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Every template of the catalog, in the order served — follows `listMethodTemplates`'s cursor
|
|
2005
|
+
* for you, with the same `ordering` and `category` on every page. It stops on a `null` cursor or
|
|
2006
|
+
* on a cursor the server failed to advance, and throws `PagingNotTerminatingError` past a page
|
|
2007
|
+
* ceiling no real catalog reaches. The ordering served and the categories are on each page, so
|
|
2008
|
+
* read one page with `listMethodTemplates` when you need them.
|
|
2009
|
+
*/
|
|
2010
|
+
async *iterateMethodTemplates(query = {}) {
|
|
2011
|
+
yield* followCursor("listMethodTemplates", (cursor) => this.listMethodTemplates({ ...query, cursor }));
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Read one method with its files — `GET /v1/methods/{id}`. One of the caller's organization's
|
|
2015
|
+
* methods answers a `MethodDetail`: its identity, its draft
|
|
1938
2016
|
* (`files`, `input_data`) with the draft's token (`updated_at`) and digest (`draft_digest`),
|
|
1939
2017
|
* and its latest published version's summary (`latest_version`, `latest_published`), whatever
|
|
1940
2018
|
* the publish state. A method never published reads with both `null`, never as an error.
|
|
@@ -1942,6 +2020,11 @@ export class PipelexApiClient {
|
|
|
1942
2020
|
* link to its bytes valid for about fifteen minutes, and is empty for an empty draft;
|
|
1943
2021
|
* `writeMethodTree` brings the tree to disk.
|
|
1944
2022
|
*
|
|
2023
|
+
* **Another organization's public method reads too, in a narrower shape**, a
|
|
2024
|
+
* `PublicMethodDetail`: its latest published version, that version's description, categories
|
|
2025
|
+
* and files, and nothing of its draft or of the owner's people, so no `updated_at` to write or
|
|
2026
|
+
* publish with. Narrow the answer with `"updated_at" in method` before reading the draft.
|
|
2027
|
+
*
|
|
1945
2028
|
* Takes a bare catalog id: the method routes address the method itself, never a version of
|
|
1946
2029
|
* it. A caller holding `mt_…@3` strips the suffix with `parseMethodSelector` and reads that
|
|
1947
2030
|
* version with `getMethodVersion`. Every method route throws `RequestArgumentError` for a
|
|
@@ -1949,16 +2032,26 @@ export class PipelexApiClient {
|
|
|
1949
2032
|
* and for an id that is empty, `.` or `..`, which the URL would resolve into another route's
|
|
1950
2033
|
* path.
|
|
1951
2034
|
*
|
|
1952
|
-
* Throws `ApiResponseError`: `404` `pipelex.catalog.not_found` for an unknown
|
|
1953
|
-
*
|
|
1954
|
-
* `
|
|
1955
|
-
*
|
|
1956
|
-
* `
|
|
2035
|
+
* Throws `ApiResponseError`: `404` `pipelex.catalog.not_found` for an unknown method, for
|
|
2036
|
+
* another organization's internal one, and for a public one made internal again or being
|
|
2037
|
+
* erased; `409` `pipelex.catalog.being_deleted` while the erasure of the caller's own method
|
|
2038
|
+
* runs; and a `500` `runtime.unexpected` for a method whose tree the platform holds no record
|
|
2039
|
+
* of. An answer whose `files` is not a listing is an answer the SDK cannot read, thrown as an
|
|
2040
|
+
* `ApiResponseError` too, its links redacted, and so is one that lacks `updated_at` while it
|
|
2041
|
+
* carries a member only the owner's read has (`draft_digest`, `created_by_user_id`,
|
|
2042
|
+
* `input_data`, `pipe_output`, `copied_from`, `deletion_state`), which would otherwise pass for
|
|
2043
|
+
* another organization's public method, and a public read without an integer `latest_version`
|
|
2044
|
+
* and a `latest_published` object.
|
|
1957
2045
|
*/
|
|
1958
2046
|
async getMethod(methodId) {
|
|
1959
2047
|
const endpoint = methodPath(methodId, "getMethod");
|
|
1960
|
-
const res = await this.requestProductAnswer("GET", endpoint, undefined);
|
|
1961
|
-
|
|
2048
|
+
const res = withLinks(await this.requestProductAnswer("GET", endpoint, undefined));
|
|
2049
|
+
const method = this.readSavedTree("GET", endpoint, res, "a stored method");
|
|
2050
|
+
const fault = methodReadFault(method);
|
|
2051
|
+
if (fault !== null) {
|
|
2052
|
+
throw this.unreadableAnswer("GET", `/${API_PREFIX}/${endpoint}`, res, `a stored method ${fault}`);
|
|
2053
|
+
}
|
|
2054
|
+
return method;
|
|
1962
2055
|
}
|
|
1963
2056
|
/**
|
|
1964
2057
|
* Create a method — `POST /v1/methods`. The new method holds its tree as its draft and has no
|
|
@@ -2398,9 +2491,12 @@ export class PipelexApiClient {
|
|
|
2398
2491
|
*
|
|
2399
2492
|
* The answer is a `MethodPublishResult` discriminated on `outcome` — branch on it:
|
|
2400
2493
|
* `published` with the new `version`; `unchanged` with the existing latest `version`;
|
|
2401
|
-
* `refused` with a `reason
|
|
2402
|
-
*
|
|
2403
|
-
*
|
|
2494
|
+
* `refused` with a `reason`, a `message` and the runner's `validation` verdict, whose problem
|
|
2495
|
+
* items are read member by member as `validate` reads them. The reason is `invalid`, or
|
|
2496
|
+
* `not_runnable` for a draft that validates with pending signatures, and for a public method
|
|
2497
|
+
* also `thumbnail_missing`, `thumbnail_invalid` or `storage_reference` (see
|
|
2498
|
+
* `MethodPublishRefusalReason`); a reason this SDK does not know is passed through as its raw
|
|
2499
|
+
* string, still a verdict, so show its `message`. Every arm carries the `method`. A publish
|
|
2404
2500
|
* moves no token.
|
|
2405
2501
|
*
|
|
2406
2502
|
* Takes a bare catalog id, as `getMethod` does. Throws `RequestArgumentError` when
|
|
@@ -2415,11 +2511,14 @@ export class PipelexApiClient {
|
|
|
2415
2511
|
* cannot be reached, answers unusably or does not finish within the platform's deadline is a
|
|
2416
2512
|
* `502` or a `503` with nothing written, so a retry is safe; a runner that refuses the request
|
|
2417
2513
|
* itself is relayed under its own status. A retry of a publish that landed while its answer was
|
|
2418
|
-
* lost, as on a client timeout, answers `unchanged` with the version it wrote.
|
|
2514
|
+
* lost, as on a client timeout, answers `unchanged` with the version it wrote. A method made
|
|
2515
|
+
* public while its publish ran is a `503` with nothing written, safe to retry, which a retry
|
|
2516
|
+
* judges as a public method's publish.
|
|
2419
2517
|
*
|
|
2420
2518
|
* An answer whose `outcome` is not one of the outcomes above, that carries no `method` object,
|
|
2421
|
-
* or a refusal whose `
|
|
2422
|
-
* the SDK cannot read, thrown as an
|
|
2519
|
+
* or a refusal whose `reason` or `message` is not a string or whose `validation` is not a verdict
|
|
2520
|
+
* object with a boolean `is_valid`, is an answer the SDK cannot read, thrown as an
|
|
2521
|
+
* `ApiResponseError` too.
|
|
2423
2522
|
*/
|
|
2424
2523
|
async publishMethod(methodId, input) {
|
|
2425
2524
|
const token = input
|
|
@@ -2444,8 +2543,16 @@ export class PipelexApiClient {
|
|
|
2444
2543
|
// The refused arm carries the runner's verdict, whose problem items are read member by
|
|
2445
2544
|
// member as `validate` reads them, so one malformed item never costs the caller the others.
|
|
2446
2545
|
const validation = answer.validation;
|
|
2447
|
-
|
|
2448
|
-
|
|
2546
|
+
let fault = null;
|
|
2547
|
+
if (typeof answer.reason !== "string")
|
|
2548
|
+
fault = "`reason` is not a string";
|
|
2549
|
+
else if (typeof answer.message !== "string")
|
|
2550
|
+
fault = "`message` is not a string";
|
|
2551
|
+
else if (!isPlainObject(validation) || typeof validation.is_valid !== "boolean") {
|
|
2552
|
+
fault = "`validation` is not an object";
|
|
2553
|
+
}
|
|
2554
|
+
if (fault !== null) {
|
|
2555
|
+
throw this.unreadableAnswer("POST", `/${API_PREFIX}/${endpoint}`, res, `a publish result whose ${fault}`);
|
|
2449
2556
|
}
|
|
2450
2557
|
return {
|
|
2451
2558
|
...answer,
|
|
@@ -2460,11 +2567,13 @@ export class PipelexApiClient {
|
|
|
2460
2567
|
*
|
|
2461
2568
|
* Pass the page's `nextCursor` back as `cursor` to continue; `null` is the last page. A page
|
|
2462
2569
|
* may be short while `nextCursor` is set, because the platform reads versions whole. A method
|
|
2463
|
-
* never published answers an empty page.
|
|
2570
|
+
* never published answers an empty page. Another organization's public method lists its
|
|
2571
|
+
* versions too, each without its `published_by`.
|
|
2464
2572
|
*
|
|
2465
2573
|
* Takes a bare catalog id, as `getMethod` does. Throws `ApiResponseError`: `404`
|
|
2466
|
-
* `pipelex.catalog.not_found` for an unknown method
|
|
2467
|
-
* `
|
|
2574
|
+
* `pipelex.catalog.not_found` for an unknown method and for another organization's internal
|
|
2575
|
+
* one, `409` `pipelex.catalog.being_deleted` while the erasure of the caller's own method runs,
|
|
2576
|
+
* and `422` `request.invalid` for a cursor the listing did not issue for this method or a `limit`
|
|
2468
2577
|
* outside 1 to 100. A page whose `items` is not an array or whose `next_cursor` is neither a
|
|
2469
2578
|
* string nor `null` is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
|
|
2470
2579
|
*/
|
|
@@ -2476,12 +2585,15 @@ export class PipelexApiClient {
|
|
|
2476
2585
|
/**
|
|
2477
2586
|
* One published version of a method, with its files — `GET /v1/methods/{id}/versions/{n}`.
|
|
2478
2587
|
* `files` lists the version's tree as `getMethod` lists the draft's, each file with a link to
|
|
2479
|
-
* its bytes valid for about fifteen minutes, which `writeMethodTree` writes to disk.
|
|
2588
|
+
* its bytes valid for about fifteen minutes, which `writeMethodTree` writes to disk. Another
|
|
2589
|
+
* organization's public method reads its versions too, without their `published_by`, the files
|
|
2590
|
+
* linked in the organization that holds it.
|
|
2480
2591
|
*
|
|
2481
2592
|
* Takes a bare catalog id, as `getMethod` does, and the version number. Throws
|
|
2482
2593
|
* `RequestArgumentError` for a `version` that is not a positive integer, before any request.
|
|
2483
2594
|
* Throws `ApiResponseError`: `404` `pipelex.catalog.not_found` for a version the method never
|
|
2484
|
-
* published
|
|
2595
|
+
* published, for an unknown method and for another organization's internal one, `409`
|
|
2596
|
+
* `pipelex.catalog.being_deleted` while the erasure of the caller's own method runs, and `500`
|
|
2485
2597
|
* `runtime.unexpected` for a version stored before methods were trees and never rewritten as
|
|
2486
2598
|
* one, which names no tree to serve.
|
|
2487
2599
|
*/
|
|
@@ -2505,10 +2617,16 @@ export class PipelexApiClient {
|
|
|
2505
2617
|
* original stays as it was. The copy takes the draft, never the latest published version; with
|
|
2506
2618
|
* `expected_draft_updated_at`, it takes exactly the draft the caller last saw.
|
|
2507
2619
|
*
|
|
2620
|
+
* **Another organization's public method copies too**, a template of the catalog among them,
|
|
2621
|
+
* from its latest published version rather than its draft: that version's tree, description and
|
|
2622
|
+
* categories, and the method's examples, never the owner's form inputs, with `copied_from`
|
|
2623
|
+
* naming the `version`. Leave `expected_draft_updated_at` out then: sent with such a method, it
|
|
2624
|
+
* is refused with a `422` `request.invalid` naming it.
|
|
2625
|
+
*
|
|
2508
2626
|
* The answer is a `MethodCopyResult` discriminated on `outcome` — branch on it: `copied` with
|
|
2509
2627
|
* the copy as `method`, `examples_copied` and `stored_files_copied`; `refused` with the
|
|
2510
2628
|
* `reason` `bundle_names_other_storage`, a `message` and every `references` entry (`{ path, uri
|
|
2511
|
-
* }`) a `.mthds` file of the
|
|
2629
|
+
* }`) a `.mthds` file of the tree holds that the destination cannot read and, in a duplicate,
|
|
2512
2630
|
* that names a file under the original's run files, which erasing the original would remove,
|
|
2513
2631
|
* nothing written.
|
|
2514
2632
|
*
|
|
@@ -2527,7 +2645,8 @@ export class PipelexApiClient {
|
|
|
2527
2645
|
* that is not from a web session, or for a role that may not change methods, in the method's
|
|
2528
2646
|
* organization or in the destination; `404` `pipelex.org.not_found` for a destination the
|
|
2529
2647
|
* caller does not belong to, still being set up or unknown, alike; `404`
|
|
2530
|
-
* `pipelex.catalog.not_found` for an unknown method
|
|
2648
|
+
* `pipelex.catalog.not_found` for an unknown method and for another organization's internal
|
|
2649
|
+
* one; `409` `pipelex.catalog.being_deleted` while the erasure of the caller's own method runs;
|
|
2531
2650
|
* `409` `pipelex.catalog.update_conflict` for a draft that moved since
|
|
2532
2651
|
* `expected_draft_updated_at`; a `409` whose `domain` is `runtime` and `retry` `no`, for a
|
|
2533
2652
|
* draft whose tree the method's organization holds no record of or no longer holds every file
|
|
@@ -2591,30 +2710,6 @@ export class PipelexApiClient {
|
|
|
2591
2710
|
}
|
|
2592
2711
|
return answer;
|
|
2593
2712
|
}
|
|
2594
|
-
/**
|
|
2595
|
-
* One page of the copies made of a method, in the order of the copies' ids, which says nothing
|
|
2596
|
-
* of when each was made — `GET /v1/methods/{id}/copies`.
|
|
2597
|
-
*
|
|
2598
|
-
* Each row is the `MethodCopyMarker` the original keeps of a copy: the copy's id, the
|
|
2599
|
-
* destination's id, the member who made it and when. The destination is named by its id alone,
|
|
2600
|
-
* never its name, so a surface shows a destination's name only from the reader's own
|
|
2601
|
-
* memberships, and a copy in an organization the reader does not belong to stays a bare id. A
|
|
2602
|
-
* marker stays when its copy is erased, and the copy then reads as not found. Pass the page's
|
|
2603
|
-
* `nextCursor` back as `cursor` to continue; `null` is the last page. A method never copied,
|
|
2604
|
-
* and a copy itself, answer an empty page. The listing is open to every member of the method's
|
|
2605
|
-
* organization, a runner included, as every read of a method is.
|
|
2606
|
-
*
|
|
2607
|
-
* Takes a bare catalog id, as `getMethod` does. Throws `ApiResponseError`: `404`
|
|
2608
|
-
* `pipelex.catalog.not_found` for an unknown method, `409` `pipelex.catalog.being_deleted`, and
|
|
2609
|
-
* `422` `request.invalid` for a cursor the listing did not issue for this method or a `limit`
|
|
2610
|
-
* outside 1 to 100. A page whose `items` is not an array or whose `next_cursor` is neither a
|
|
2611
|
-
* string nor `null` is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
|
|
2612
|
-
*/
|
|
2613
|
-
async listMethodCopies(methodId, query = {}) {
|
|
2614
|
-
const search = productQuery({ limit: query.limit, cursor: query.cursor });
|
|
2615
|
-
const endpoint = `${methodPath(methodId, "listMethodCopies")}/copies`;
|
|
2616
|
-
return this.requestPage(`${endpoint}${search}`);
|
|
2617
|
-
}
|
|
2618
2713
|
/**
|
|
2619
2714
|
* Erase a method and everything it produced — `DELETE /v1/methods/{id}`.
|
|
2620
2715
|
*
|
|
@@ -3729,9 +3824,13 @@ export class PipelexApiClient {
|
|
|
3729
3824
|
return list;
|
|
3730
3825
|
}
|
|
3731
3826
|
/**
|
|
3732
|
-
* One delivery of a run's webhooks, with its event
|
|
3733
|
-
* `GET /v1/runs/{id}/webhook-deliveries/{delivery_id}`: the body
|
|
3734
|
-
*
|
|
3827
|
+
* One delivery of a run's webhooks, with its event —
|
|
3828
|
+
* `GET /v1/runs/{id}/webhook-deliveries/{delivery_id}`: the body its latest attempt sent, or,
|
|
3829
|
+
* before any attempt, the body its first attempt will send. Every attempt and replay of the
|
|
3830
|
+
* delivery sends that body, only its `attempt` changing, so the event matches what a receiver
|
|
3831
|
+
* logged only once an attempt was made: it then equals, as JSON, the body that attempt brought
|
|
3832
|
+
* the receiver. Compare the two as parsed JSON values, never as text: the read promises the same
|
|
3833
|
+
* value, not the same bytes.
|
|
3735
3834
|
* `deliveryId` is the delivery's `delivery_id`, the receiver's `webhook-id`.
|
|
3736
3835
|
*
|
|
3737
3836
|
* Throws `RequestArgumentError` for an id that is empty, `.` or `..`, or holds a lone surrogate,
|
|
@@ -3754,20 +3853,34 @@ export class PipelexApiClient {
|
|
|
3754
3853
|
* can read the run asks for one, an API key included, under the organization's replay rate.
|
|
3755
3854
|
*
|
|
3756
3855
|
* Answers the attempt number the replay is sent under; a replay asked again before it is sent is
|
|
3757
|
-
* that same attempt.
|
|
3758
|
-
*
|
|
3759
|
-
*
|
|
3760
|
-
*
|
|
3761
|
-
*
|
|
3762
|
-
*
|
|
3763
|
-
*
|
|
3764
|
-
*
|
|
3856
|
+
* that same attempt.
|
|
3857
|
+
*
|
|
3858
|
+
* `options.idempotencyKey` is sent as the `Idempotency-Key` header, and nothing is sent when it
|
|
3859
|
+
* is absent or `null`. The platform stores the replay's answer under the key, so the same replay
|
|
3860
|
+
* sent again under the same key, as after an answer lost to a time limit, gets the stored answer,
|
|
3861
|
+
* naming the same attempt, and the delivery is not sent twice. A key reused for another request
|
|
3862
|
+
* is refused `409` `request.idempotency_conflict`, and one whose earlier request is still being
|
|
3863
|
+
* handled `409` `request.idempotency_key_in_use`, whose `retry` is `yes`: send it again under the
|
|
3864
|
+
* same key after its `retry_after`. See `WebhookReplayOptions`.
|
|
3865
|
+
*
|
|
3866
|
+
* Throws `RequestArgumentError` for an id that is empty, `.` or `..`, or holds a lone surrogate,
|
|
3867
|
+
* or for an idempotency key that is not 1 to 255 visible ASCII characters, before any request.
|
|
3868
|
+
* Throws `ApiResponseError`: `404` `run.not_found` for a run the caller cannot read; `422`
|
|
3869
|
+
* `request.invalid` naming `delivery_id` for a delivery the run does not have; `409`
|
|
3870
|
+
* `pipelex.org.not_manageable` with the reason `webhook_delivery_pending` for a delivery still on
|
|
3871
|
+
* its retry schedule, or `webhook_delivery_attempts_exhausted` for one holding the most attempts
|
|
3872
|
+
* a delivery holds; `429` `account.rate_limited`, with its `retry_after`, past the
|
|
3873
|
+
* organization's replay rate; and a `500` or `503` when the replay could not be queued, which
|
|
3874
|
+
* then counts nothing against the rate.
|
|
3765
3875
|
*/
|
|
3766
|
-
async replayRunWebhookDelivery(runId, deliveryId) {
|
|
3876
|
+
async replayRunWebhookDelivery(runId, deliveryId, options = {}) {
|
|
3767
3877
|
const call = "replayRunWebhookDelivery";
|
|
3768
3878
|
const run = idPathSegment(runId, call, RUN_ID_ARGUMENT);
|
|
3769
3879
|
const delivery = idPathSegment(deliveryId, call, DELIVERY_ID_ARGUMENT);
|
|
3770
|
-
|
|
3880
|
+
const endpoint = `runs/${run}/webhook-deliveries/${delivery}/replay`;
|
|
3881
|
+
const idempotencyKey = idempotencyKeyOf(options, "replay the delivery");
|
|
3882
|
+
const res = await this.requestProductAnswer("POST", endpoint, undefined, { idempotencyKey });
|
|
3883
|
+
return this.readObjectAnswer("POST", endpoint, res);
|
|
3771
3884
|
}
|
|
3772
3885
|
/** Patch a run's status (admin/manual) — `PUT /v1/runs/{id}`. */
|
|
3773
3886
|
async updateRun(runId, input) {
|
|
@@ -4236,9 +4349,9 @@ function nonEmptyString(value) {
|
|
|
4236
4349
|
/** The request header the hosted API reads a request's idempotency key from. */
|
|
4237
4350
|
const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
4238
4351
|
/**
|
|
4239
|
-
* The longest idempotency key a
|
|
4352
|
+
* The longest idempotency key a request sends. It is room for any generated key, and keeps the
|
|
4240
4353
|
* hosted API's stored record of the key far inside its store's limit, which a longer key could
|
|
4241
|
-
* exceed, so that the
|
|
4354
|
+
* exceed, so that the request would run without the protection the key was sent for.
|
|
4242
4355
|
*/
|
|
4243
4356
|
const MAX_IDEMPOTENCY_KEY_LENGTH = 255;
|
|
4244
4357
|
/**
|
|
@@ -4248,13 +4361,14 @@ const MAX_IDEMPOTENCY_KEY_LENGTH = 255;
|
|
|
4248
4361
|
*/
|
|
4249
4362
|
const VISIBLE_ASCII = /^[\x21-\x7E]+$/;
|
|
4250
4363
|
/**
|
|
4251
|
-
* The idempotency key a start
|
|
4252
|
-
* (absent or `null`). A key that is not a string of 1 to
|
|
4253
|
-
* with a `RequestArgumentError` rather than dropped, an
|
|
4254
|
-
* without the key its caller gave would lose the
|
|
4255
|
-
* `request` says what the request does, as the
|
|
4256
|
-
*
|
|
4257
|
-
*
|
|
4364
|
+
* The idempotency key a start, a method copy, a secret rotation or a delivery replay sends, or
|
|
4365
|
+
* `undefined` when the options carry none (absent or `null`). A key that is not a string of 1 to
|
|
4366
|
+
* 255 visible ASCII characters is refused with a `RequestArgumentError` rather than dropped, an
|
|
4367
|
+
* empty one included, since a request sent without the key its caller gave would lose the
|
|
4368
|
+
* protection the caller asked for without a word. `request` says what the request does, as the
|
|
4369
|
+
* refusal's last sentence names it ("start the run", "copy the method", "rotate the secret",
|
|
4370
|
+
* "replay the delivery"). The message never quotes the key, which a caller may derive from
|
|
4371
|
+
* something private. The Python SDK holds a key to the same rule, in the same words.
|
|
4258
4372
|
*/
|
|
4259
4373
|
function idempotencyKeyOf(options, request = "start the run") {
|
|
4260
4374
|
const key = options.idempotencyKey;
|
|
@@ -4391,6 +4505,106 @@ function savedFilesFault(files) {
|
|
|
4391
4505
|
}
|
|
4392
4506
|
return null;
|
|
4393
4507
|
}
|
|
4508
|
+
/** The members only the owner's read of a method carries, never another organization's. */
|
|
4509
|
+
const OWNER_ONLY_METHOD_MEMBERS = [
|
|
4510
|
+
"draft_digest",
|
|
4511
|
+
"created_by_user_id",
|
|
4512
|
+
"input_data",
|
|
4513
|
+
"pipe_output",
|
|
4514
|
+
"copied_from",
|
|
4515
|
+
"deletion_state",
|
|
4516
|
+
];
|
|
4517
|
+
/**
|
|
4518
|
+
* Why a method read is not one the SDK reads, or `null` when it is. The owner's read carries the
|
|
4519
|
+
* draft's token, `updated_at`, which is what tells it from another organization's public method;
|
|
4520
|
+
* so an answer without the token that still carries a member only the owner's read has is a
|
|
4521
|
+
* damaged owner read rather than a public one, and a public read must carry the latest published
|
|
4522
|
+
* version it is about.
|
|
4523
|
+
*/
|
|
4524
|
+
function methodReadFault(method) {
|
|
4525
|
+
if ("updated_at" in method)
|
|
4526
|
+
return null;
|
|
4527
|
+
const ownerMember = OWNER_ONLY_METHOD_MEMBERS.find((member) => member in method);
|
|
4528
|
+
if (ownerMember !== undefined) {
|
|
4529
|
+
return `without \`updated_at\` that carries the owner's \`${ownerMember}\``;
|
|
4530
|
+
}
|
|
4531
|
+
if (!isPositiveInteger(method.latest_version)) {
|
|
4532
|
+
return "read as another organization's public method whose `latest_version` is not a positive integer";
|
|
4533
|
+
}
|
|
4534
|
+
if (!isPlainObject(method.latest_published)) {
|
|
4535
|
+
return "read as another organization's public method whose `latest_published` is not an object";
|
|
4536
|
+
}
|
|
4537
|
+
return null;
|
|
4538
|
+
}
|
|
4539
|
+
function isPositiveInteger(value) {
|
|
4540
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
4541
|
+
}
|
|
4542
|
+
function isStringArray(value) {
|
|
4543
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
4544
|
+
}
|
|
4545
|
+
/**
|
|
4546
|
+
* Why the catalog's templates are not ones the SDK reads, or `null` when they are: each an object
|
|
4547
|
+
* with what a card is drawn from, a string `method_id`, `name` and `organization_name`, a
|
|
4548
|
+
* `description` that is a string or `null`, a positive integer `latest_version`, an array of string `categories`, and `thumbnails`, each with
|
|
4549
|
+
* a string `path`, `media_type`, `sha256`, `url` and `expires_at` and a positive integer `width`
|
|
4550
|
+
* and `height`.
|
|
4551
|
+
*/
|
|
4552
|
+
function methodTemplatesFault(items) {
|
|
4553
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
4554
|
+
const item = items[index];
|
|
4555
|
+
if (!isPlainObject(item))
|
|
4556
|
+
return `\`items\` holds an entry that is not an object, at [${index}]`;
|
|
4557
|
+
for (const member of ["method_id", "name", "organization_name"]) {
|
|
4558
|
+
if (typeof item[member] !== "string") {
|
|
4559
|
+
return `\`items\` holds a template whose \`${member}\` is not a string, at [${index}]`;
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
if (item.description !== null &&
|
|
4563
|
+
item.description !== undefined &&
|
|
4564
|
+
typeof item.description !== "string") {
|
|
4565
|
+
return `\`items\` holds a template whose \`description\` is neither a string nor null, at [${index}]`;
|
|
4566
|
+
}
|
|
4567
|
+
if (!isPositiveInteger(item.latest_version)) {
|
|
4568
|
+
return `\`items\` holds a template whose \`latest_version\` is not a positive integer, at [${index}]`;
|
|
4569
|
+
}
|
|
4570
|
+
if (!isStringArray(item.categories)) {
|
|
4571
|
+
return `\`items\` holds a template whose \`categories\` is not an array of strings, at [${index}]`;
|
|
4572
|
+
}
|
|
4573
|
+
const thumbnails = item.thumbnails;
|
|
4574
|
+
if (!Array.isArray(thumbnails)) {
|
|
4575
|
+
return `\`items\` holds a template whose \`thumbnails\` is not an array, at [${index}]`;
|
|
4576
|
+
}
|
|
4577
|
+
for (let position = 0; position < thumbnails.length; position += 1) {
|
|
4578
|
+
const thumbnail = thumbnails[position];
|
|
4579
|
+
const where = `at [${index}].thumbnails[${position}]`;
|
|
4580
|
+
if (!isPlainObject(thumbnail))
|
|
4581
|
+
return `\`items\` holds a thumbnail that is not an object, ${where}`;
|
|
4582
|
+
for (const member of ["path", "media_type", "sha256", "url", "expires_at"]) {
|
|
4583
|
+
if (typeof thumbnail[member] !== "string") {
|
|
4584
|
+
return `\`items\` holds a thumbnail whose \`${member}\` is not a string, ${where}`;
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
for (const member of ["width", "height"]) {
|
|
4588
|
+
if (!isPositiveInteger(thumbnail[member])) {
|
|
4589
|
+
return `\`items\` holds a thumbnail whose \`${member}\` is not a positive integer, ${where}`;
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
return null;
|
|
4595
|
+
}
|
|
4596
|
+
/** Why the catalog's categories are not ones the SDK reads, or `null`: each a string `slug` and `label`. */
|
|
4597
|
+
function methodCategoriesFault(categories) {
|
|
4598
|
+
for (let index = 0; index < categories.length; index += 1) {
|
|
4599
|
+
const category = categories[index];
|
|
4600
|
+
if (!isPlainObject(category) ||
|
|
4601
|
+
typeof category.slug !== "string" ||
|
|
4602
|
+
typeof category.label !== "string") {
|
|
4603
|
+
return `\`categories\` holds an entry without a string \`slug\` and \`label\`, at [${index}]`;
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
return null;
|
|
4607
|
+
}
|
|
4394
4608
|
function extractNetworkErrorCode(err) {
|
|
4395
4609
|
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
4396
4610
|
return "ABORT_TIMEOUT";
|