@pipelex/sdk 0.34.1 → 0.35.1
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 +23 -3
- package/dist/cli/help.d.ts +3 -3
- package/dist/cli/help.js +6 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/method.d.ts +9 -3
- package/dist/cli/method.js +30 -7
- package/dist/cli/method.js.map +1 -1
- package/dist/cli/script.d.ts +6 -2
- package/dist/cli/script.js +13 -6
- package/dist/cli/script.js.map +1 -1
- package/dist/client.d.ts +178 -40
- package/dist/client.js +267 -42
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +34 -9
- package/dist/errors.js +16 -9
- package/dist/errors.js.map +1 -1
- package/dist/execute-result.d.ts +6 -0
- package/dist/execute-result.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/method-selector.d.ts +42 -0
- package/dist/method-selector.js +63 -0
- package/dist/method-selector.js.map +1 -0
- package/dist/models.d.ts +22 -9
- package/dist/product-models.d.ts +221 -11
- package/dist/runs.d.ts +13 -0
- package/dist/runs.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +6 -5
- package/docs/cli.md +6 -6
- package/docs/crate-routes.md +4 -2
- package/docs/errors.md +18 -5
- package/docs/input-preparation.md +5 -3
- package/docs/run-results.md +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -49,12 +49,12 @@ const VALIDATE_MARKDOWN_RENDER_FORMAT = "markdown";
|
|
|
49
49
|
*/
|
|
50
50
|
const BARE_RUNNER_IMPLEMENTATION = "pipelex-api";
|
|
51
51
|
/**
|
|
52
|
-
* Parse a wire
|
|
52
|
+
* Parse a wire model's `python` into the public shape (`MethodFile[]`). A stored `python` that
|
|
53
53
|
* is not the serialized `[{ name, content }]` list is server data the SDK cannot read, not a
|
|
54
54
|
* caller's argument, so `mthds`'s refusal is handed to `unreadable`, which builds the error to
|
|
55
55
|
* throw from the answer that carried it.
|
|
56
56
|
*/
|
|
57
|
-
function
|
|
57
|
+
function withParsedPython(wire, unreadable) {
|
|
58
58
|
const { python, ...rest } = wire;
|
|
59
59
|
if (python == null)
|
|
60
60
|
return rest;
|
|
@@ -71,10 +71,26 @@ function methodDataFromWire(wire, unreadable) {
|
|
|
71
71
|
* → `""`, the clear sentinel; non-empty → the JSON array). It is never sent as an
|
|
72
72
|
* array on the wire.
|
|
73
73
|
*/
|
|
74
|
-
function
|
|
74
|
+
function withWirePython(input) {
|
|
75
75
|
const { python, ...rest } = input;
|
|
76
76
|
return python === undefined ? rest : { ...rest, python: serializeMethodFiles(python) };
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* The path of a method route, `methods/{id}`, for a bare catalog id. The method routes address
|
|
80
|
+
* the method itself, never one of its versions, and the platform does not parse a suffix there:
|
|
81
|
+
* `mt_x@3` would be looked up as an id of its own and answer `404 not_found`, which reads as a
|
|
82
|
+
* method that does not exist. So a suffixed id is refused before anything is sent, saying how to
|
|
83
|
+
* read what it names. Stripping it instead would answer the draft for a caller that named a
|
|
84
|
+
* version.
|
|
85
|
+
*/
|
|
86
|
+
function methodPath(methodId) {
|
|
87
|
+
if (typeof methodId === "string" && methodId.includes("@")) {
|
|
88
|
+
throw new RequestArgumentError(`"${methodId}" carries a version suffix, and the method routes take a bare catalog id: ` +
|
|
89
|
+
"they address the method itself, never one of its versions. Strip the suffix with " +
|
|
90
|
+
"parseMethodSelector, and read a published version with getMethodVersion.");
|
|
91
|
+
}
|
|
92
|
+
return `methods/${encodeURIComponent(methodId)}`;
|
|
93
|
+
}
|
|
78
94
|
export class PipelexApiClient {
|
|
79
95
|
apiKey;
|
|
80
96
|
baseUrl;
|
|
@@ -236,28 +252,35 @@ export class PipelexApiClient {
|
|
|
236
252
|
}
|
|
237
253
|
/**
|
|
238
254
|
* A methods-catalog route answering one stored method (`getMethod`, `createMethod`,
|
|
239
|
-
* `
|
|
240
|
-
* absent (`null` stays "no source", which `getMethodClosure` reports), and a stored `python`
|
|
241
|
-
* the SDK cannot parse, are an answer it cannot read, so each throws an `ApiResponseError` built
|
|
242
|
-
* from that answer, the latter with `mthds`'s refusal as `cause`: `runtime`, not retryable,
|
|
243
|
-
* since no change to the call fixes it.
|
|
255
|
+
* `writeDraft`, `renameMethod`), parsed into the public shape by `readStoredSources`.
|
|
244
256
|
*/
|
|
245
257
|
async requestMethodData(method, endpoint, body) {
|
|
246
258
|
const res = await this.requestProductAnswer(method, endpoint, body);
|
|
247
259
|
const wire = this.readObjectAnswer(method, endpoint, res);
|
|
260
|
+
return this.readStoredSources(method, endpoint, res, wire, "a stored method");
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The stored sources of a method or of a published version, read from the answer `res`
|
|
264
|
+
* carried, into the public shape. A stored `mthds` that is neither a string nor absent (`null`
|
|
265
|
+
* stays "no source", which `getMethodClosure` reports), and a stored `python` the SDK cannot
|
|
266
|
+
* parse, are an answer it cannot read, so each throws an `ApiResponseError` built from that
|
|
267
|
+
* answer, the latter with `mthds`'s refusal as `cause`: `runtime`, not retryable, since no
|
|
268
|
+
* change to the call fixes it. `subject` names what carried them in the message.
|
|
269
|
+
*/
|
|
270
|
+
readStoredSources(method, endpoint, res, wire, subject) {
|
|
248
271
|
const source = wire.mthds;
|
|
249
272
|
if (source != null && typeof source !== "string") {
|
|
250
|
-
throw this.unreadableAnswer(method, `/${API_PREFIX}/${endpoint}`, res,
|
|
273
|
+
throw this.unreadableAnswer(method, `/${API_PREFIX}/${endpoint}`, res, `${subject} whose \`mthds\` field is not a string`);
|
|
251
274
|
}
|
|
252
|
-
return
|
|
275
|
+
return withParsedPython(wire, (cause) => this.unreadableAnswer(method, `/${API_PREFIX}/${endpoint}`, res, `${subject} whose \`python\` field could not be read`, cause));
|
|
253
276
|
}
|
|
254
277
|
/**
|
|
255
|
-
* One page of a cursor-paged product route (`listMethods`, `listRuns`),
|
|
256
|
-
* always serializes it: `items` an array, `next_cursor` a string or
|
|
257
|
-
* either is an answer the SDK cannot read, so it throws the
|
|
258
|
-
* builds, `runtime` and not retryable, rather than
|
|
259
|
-
* walk: `items` that is not iterable, or a missing
|
|
260
|
-
* page, which `iterateRuns` would follow forever.
|
|
278
|
+
* One page of a cursor-paged product route (`listMethods`, `listMethodVersions`, `listRuns`),
|
|
279
|
+
* read as the platform always serializes it: `items` an array, `next_cursor` a string or
|
|
280
|
+
* `null`. A page breaking either is an answer the SDK cannot read, so it throws the
|
|
281
|
+
* `ApiResponseError` `unreadableAnswer` builds, `runtime` and not retryable, rather than
|
|
282
|
+
* handing the iterators a page they cannot walk: `items` that is not iterable, or a missing
|
|
283
|
+
* cursor that is neither the end nor a next page, which `iterateRuns` would follow forever.
|
|
261
284
|
*/
|
|
262
285
|
async requestPage(endpoint) {
|
|
263
286
|
const res = await this.requestProductAnswer("GET", endpoint, undefined);
|
|
@@ -430,6 +453,7 @@ export class PipelexApiClient {
|
|
|
430
453
|
}
|
|
431
454
|
assertRunSourcesExclusive(options);
|
|
432
455
|
assertMethodRefPairsWithNothing(options);
|
|
456
|
+
assertLinkageMethodIdIsBare(options);
|
|
433
457
|
const request = {
|
|
434
458
|
pipe_code: options.pipe_code,
|
|
435
459
|
mthds_contents: options.mthds_contents,
|
|
@@ -497,6 +521,7 @@ export class PipelexApiClient {
|
|
|
497
521
|
}
|
|
498
522
|
assertRunSourcesExclusive(options);
|
|
499
523
|
assertMethodRefPairsWithNothing(options);
|
|
524
|
+
assertLinkageMethodIdIsBare(options);
|
|
500
525
|
// `?? undefined` so JSON.stringify drops absent fields from the wire body.
|
|
501
526
|
const request = {
|
|
502
527
|
pipe_code: options.pipe_code ?? undefined,
|
|
@@ -559,13 +584,17 @@ export class PipelexApiClient {
|
|
|
559
584
|
* `method_ref` run, the package's real file names feeding the diagnostics'
|
|
560
585
|
* source labels;
|
|
561
586
|
* - **`{ method_id }`** — a stored method's catalog id, hosted-only: the
|
|
562
|
-
* platform resolves it and injects
|
|
563
|
-
* the request (a bare runner rejects the request
|
|
564
|
-
* understands).
|
|
587
|
+
* platform resolves the version it names and injects that version's `.mthds`
|
|
588
|
+
* files before the runner sees the request (a bare runner rejects the request
|
|
589
|
+
* as carrying no source it understands). A bare `mt_…` validates the latest
|
|
590
|
+
* published version, `mt_…@<n>` version `n` and `mt_…@draft` the draft.
|
|
565
591
|
*
|
|
566
592
|
* A selector-resolution failure (fetch failure, no package at the address, an
|
|
567
|
-
* unknown or foreign-org id
|
|
568
|
-
* `
|
|
593
|
+
* unknown or foreign-org id, a bare id of a method never published, `409`
|
|
594
|
+
* `method_not_published`, a version never published, `404`
|
|
595
|
+
* `method_version_not_found`, or a malformed suffix, `422`) is a non-2xx
|
|
596
|
+
* `ApiResponseError` — never an `is_valid: false` verdict, which is reserved
|
|
597
|
+
* for actual MTHDS content.
|
|
569
598
|
*
|
|
570
599
|
* `mthdsSources` (optional, parallel to inline contents) names each submitted
|
|
571
600
|
* content — a Pipelex-API extension threaded onto `blueprint.source`, so
|
|
@@ -1207,16 +1236,31 @@ export class PipelexApiClient {
|
|
|
1207
1236
|
cursor = page.nextCursor;
|
|
1208
1237
|
}
|
|
1209
1238
|
}
|
|
1210
|
-
/**
|
|
1239
|
+
/**
|
|
1240
|
+
* Read one method — `GET /v1/methods/{id}`: its identity, its draft (`mthds`, `python`,
|
|
1241
|
+
* `input_data`) with the draft's token (`updated_at`) and digest (`draft_digest`), and its
|
|
1242
|
+
* latest published version's summary (`latest_version`, `latest_published`), whatever the
|
|
1243
|
+
* publish state. A method never published reads with both `null`, never as an error.
|
|
1244
|
+
*
|
|
1245
|
+
* Takes a bare catalog id: the method routes address the method itself, never a version of
|
|
1246
|
+
* it. A caller holding `mt_…@3` strips the suffix with `parseMethodSelector` and reads that
|
|
1247
|
+
* version with `getMethodVersion`. Every method route throws `RequestArgumentError` for a
|
|
1248
|
+
* suffixed id, before any request, rather than read back the `404` the platform would answer.
|
|
1249
|
+
*/
|
|
1211
1250
|
async getMethod(methodId) {
|
|
1212
|
-
return this.requestMethodData("GET",
|
|
1251
|
+
return this.requestMethodData("GET", methodPath(methodId));
|
|
1213
1252
|
}
|
|
1214
1253
|
/**
|
|
1215
|
-
* Resolve a stored method's id into
|
|
1216
|
-
* semantic layer over `getMethod` (the platform has no route that
|
|
1217
|
-
* parsed closure). Fetches the method, parses its polymorphic
|
|
1218
|
-
* with `methodSourceToContents`, and labels each resulting file
|
|
1219
|
-
* `method_id` as its `source` provenance.
|
|
1254
|
+
* Resolve a stored method's id into the runnable MTHDS closure of its DRAFT — a
|
|
1255
|
+
* client-side semantic layer over `getMethod` (the platform has no route that
|
|
1256
|
+
* returns a parsed closure). Fetches the method, parses its draft's polymorphic
|
|
1257
|
+
* `mthds` source with `methodSourceToContents`, and labels each resulting file
|
|
1258
|
+
* with the `method_id` as its `source` provenance.
|
|
1259
|
+
*
|
|
1260
|
+
* **This is the draft, not what a bare id runs.** A run, a validation or a pipe I/O
|
|
1261
|
+
* read by a bare `method_id` resolves to the latest PUBLISHED version, which the
|
|
1262
|
+
* draft may be ahead of. For the closure of a version, read it with
|
|
1263
|
+
* `getMethodVersion` and parse its `mthds` with `methodSourceToContents`.
|
|
1220
1264
|
*
|
|
1221
1265
|
* This is the LOCAL expansion utility — for callers that want the files in
|
|
1222
1266
|
* hand (to edit, to diff, or to send to a bare runner, which has no catalog to
|
|
@@ -1225,11 +1269,11 @@ export class PipelexApiClient {
|
|
|
1225
1269
|
* `prepareInputs`, which composes a `pipeIo` of its own) take the id as a
|
|
1226
1270
|
* pass-through instead; nothing in this client expands an id behind your back.
|
|
1227
1271
|
*
|
|
1228
|
-
*
|
|
1229
|
-
* an unknown OR foreign-org id is a
|
|
1230
|
-
* `not_found`), which propagates unchanged. A
|
|
1231
|
-
* parses to nothing throws `EmptyMethodSourceError`
|
|
1232
|
-
* the row exists but has no runnable source yet.
|
|
1272
|
+
* Takes a bare catalog id, as `getMethod` does. Requires an API key: the methods
|
|
1273
|
+
* catalog is org-scoped to the key's org, so an unknown OR foreign-org id is a
|
|
1274
|
+
* `getMethod` `404` (`ApiResponseError` `not_found`), which propagates unchanged. A
|
|
1275
|
+
* real, in-org method whose draft parses to nothing throws `EmptyMethodSourceError`
|
|
1276
|
+
* (distinct from the 404) — the row exists but has no runnable source yet.
|
|
1233
1277
|
*/
|
|
1234
1278
|
async getMethodClosure(methodId) {
|
|
1235
1279
|
const method = await this.getMethod(methodId);
|
|
@@ -1239,13 +1283,153 @@ export class PipelexApiClient {
|
|
|
1239
1283
|
}
|
|
1240
1284
|
return contents.map((content) => ({ content, source: methodId }));
|
|
1241
1285
|
}
|
|
1242
|
-
/**
|
|
1286
|
+
/**
|
|
1287
|
+
* Create a method — `POST /v1/methods`. The new method holds the input as its draft and has
|
|
1288
|
+
* no published version, so its bare id answers `409 method_not_published` on the run and
|
|
1289
|
+
* tooling routes until its first `publishMethod`; its draft runs as `mt_…@draft` at once.
|
|
1290
|
+
*
|
|
1291
|
+
* Throws `ApiResponseError`: `413` `payload_too_large` for a method that would leave no room
|
|
1292
|
+
* for a publish, measured as a draft write is, and `422` `validation_failed` for Python files
|
|
1293
|
+
* no run could import or for text holding a lone surrogate.
|
|
1294
|
+
*/
|
|
1243
1295
|
async createMethod(input) {
|
|
1244
|
-
return this.requestMethodData("POST", "methods",
|
|
1296
|
+
return this.requestMethodData("POST", "methods", withWirePython(input));
|
|
1245
1297
|
}
|
|
1246
|
-
/**
|
|
1247
|
-
|
|
1248
|
-
|
|
1298
|
+
/**
|
|
1299
|
+
* Replace a method's draft — `PUT /v1/methods/{id}/draft` — and return the method with the
|
|
1300
|
+
* draft's new token (`updated_at`) and digest (`draft_digest`).
|
|
1301
|
+
*
|
|
1302
|
+
* The draft is never validated on write, and writing it changes nothing for the callers of
|
|
1303
|
+
* the method's bare id, who run the latest published version until the next `publishMethod`.
|
|
1304
|
+
* `python` and `input_data` are three-way (see `MethodDraftInput`). With
|
|
1305
|
+
* `expected_updated_at`, the write is a compare-and-swap on the draft token: a draft that
|
|
1306
|
+
* moved since is refused with a `409` whose `code` is `method_update_conflict`, and nothing is
|
|
1307
|
+
* written. Without it, last writer wins.
|
|
1308
|
+
*
|
|
1309
|
+
* Takes a bare catalog id, as `getMethod` does. Throws `ApiResponseError`: `404` `not_found`
|
|
1310
|
+
* for an unknown or foreign-org method, `409` `method_being_deleted` while its erasure runs,
|
|
1311
|
+
* `413` `payload_too_large` for a draft that would grow the method past the room a publish
|
|
1312
|
+
* needs, which keeps a draft that saves publishable, `422` `validation_failed` for Python
|
|
1313
|
+
* files no run could import or for text holding a lone surrogate, `403` for a read-only key,
|
|
1314
|
+
* and a `503` that wrote nothing, safe to retry, when the method kept changing under the
|
|
1315
|
+
* write.
|
|
1316
|
+
*/
|
|
1317
|
+
async writeDraft(methodId, input) {
|
|
1318
|
+
return this.requestMethodData("PUT", `${methodPath(methodId)}/draft`, withWirePython(input));
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Rename a method — `PATCH /v1/methods/{id}` — and return it. The name belongs to the method,
|
|
1322
|
+
* not to a version: a rename changes nothing else, moves no token, and never commits the
|
|
1323
|
+
* draft, so the `updated_at` a caller holds stays valid for its next `writeDraft` or
|
|
1324
|
+
* `publishMethod`.
|
|
1325
|
+
*
|
|
1326
|
+
* Takes a bare catalog id, as `getMethod` does. Throws `ApiResponseError`: `404` `not_found`,
|
|
1327
|
+
* `409` `method_being_deleted`, `403` for a read-only key, `422` for an empty name or one
|
|
1328
|
+
* holding a lone surrogate, `413` `payload_too_large` for a name so long it would leave the
|
|
1329
|
+
* method too large to publish, and a `503` that wrote nothing, safe to retry, when draft
|
|
1330
|
+
* writes kept landing under the rename.
|
|
1331
|
+
*/
|
|
1332
|
+
async renameMethod(methodId, input) {
|
|
1333
|
+
return this.requestMethodData("PATCH", methodPath(methodId), {
|
|
1334
|
+
name: input.name,
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Publish a method's draft as its next version — `POST /v1/methods/{id}/publish`.
|
|
1339
|
+
*
|
|
1340
|
+
* `expected_draft_updated_at` is the draft token the caller last saw, and it is required: a
|
|
1341
|
+
* publish never takes a draft its caller has not seen. The platform checks the token, answers
|
|
1342
|
+
* `unchanged` without asking the runner when the draft's digest equals the latest version's,
|
|
1343
|
+
* and otherwise validates the draft and, when it validates and runs, writes version N+1.
|
|
1344
|
+
*
|
|
1345
|
+
* The answer is a `MethodPublishResult` discriminated on `outcome` — branch on it:
|
|
1346
|
+
* `published` with the new `version`; `unchanged` with the existing latest `version`;
|
|
1347
|
+
* `refused` with a `reason` (`invalid`, or `not_runnable` for a draft that validates with
|
|
1348
|
+
* pending signatures), a `message` and the runner's `validation` verdict. Every arm carries the
|
|
1349
|
+
* `method`. A publish moves no token.
|
|
1350
|
+
*
|
|
1351
|
+
* Takes a bare catalog id, as `getMethod` does. Throws `RequestArgumentError` when
|
|
1352
|
+
* `expected_draft_updated_at` is not a string, before any request. Throws `ApiResponseError`
|
|
1353
|
+
* when no verdict was produced: `409` `method_update_conflict` for a draft that moved since
|
|
1354
|
+
* the token, `409` `method_being_deleted`, `404` `not_found`, `422` for a draft with no
|
|
1355
|
+
* `.mthds` file or whose file names a run could not assemble (one name used by a `.mthds` and
|
|
1356
|
+
* a Python file), `413` `payload_too_large` for a draft too large to publish, and `403` for a
|
|
1357
|
+
* read-only key.
|
|
1358
|
+
*
|
|
1359
|
+
* Only a draft that differs from the latest version reaches the runner, and a runner that
|
|
1360
|
+
* cannot be reached, answers unusably or does not finish within the platform's deadline is a
|
|
1361
|
+
* `502` or a `503` with nothing written, so a retry is safe; a runner that refuses the request
|
|
1362
|
+
* itself is relayed under its own status. A retry of a publish that landed while its answer was
|
|
1363
|
+
* lost, as on a client timeout, answers `unchanged` with the version it wrote.
|
|
1364
|
+
*
|
|
1365
|
+
* An answer whose `outcome` is not one of the outcomes above, or that carries no `method`
|
|
1366
|
+
* object, is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
|
|
1367
|
+
*/
|
|
1368
|
+
async publishMethod(methodId, input) {
|
|
1369
|
+
const token = input
|
|
1370
|
+
?.expected_draft_updated_at;
|
|
1371
|
+
if (typeof token !== "string") {
|
|
1372
|
+
throw new RequestArgumentError("publishMethod() needs expected_draft_updated_at: the draft token (the method's " +
|
|
1373
|
+
"updated_at) the caller last saw, so a publish never takes a draft it has not seen.");
|
|
1374
|
+
}
|
|
1375
|
+
const endpoint = `${methodPath(methodId)}/publish`;
|
|
1376
|
+
const res = await this.requestProductAnswer("POST", endpoint, {
|
|
1377
|
+
expected_draft_updated_at: token,
|
|
1378
|
+
});
|
|
1379
|
+
const answer = this.readObjectAnswer("POST", endpoint, res);
|
|
1380
|
+
const { outcome, method: stored } = answer;
|
|
1381
|
+
if (outcome !== "published" && outcome !== "unchanged" && outcome !== "refused") {
|
|
1382
|
+
throw this.unreadableAnswer("POST", `/${API_PREFIX}/${endpoint}`, res, "a publish result whose `outcome` is not `published`, `unchanged` or `refused`");
|
|
1383
|
+
}
|
|
1384
|
+
if (!isPlainObject(stored)) {
|
|
1385
|
+
throw this.unreadableAnswer("POST", `/${API_PREFIX}/${endpoint}`, res, "a publish result whose `method` is not an object");
|
|
1386
|
+
}
|
|
1387
|
+
const method = this.readStoredSources("POST", endpoint, res, stored, "a stored method");
|
|
1388
|
+
return { ...answer, outcome, method };
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* One page of a method's published versions, newest first, without their sources —
|
|
1392
|
+
* `GET /v1/methods/{id}/versions`.
|
|
1393
|
+
*
|
|
1394
|
+
* Pass the page's `nextCursor` back as `cursor` to continue; `null` is the last page. A page
|
|
1395
|
+
* may be short while `nextCursor` is set, because the platform reads versions whole. A method
|
|
1396
|
+
* never published answers an empty page.
|
|
1397
|
+
*
|
|
1398
|
+
* Takes a bare catalog id, as `getMethod` does. Throws `ApiResponseError`: `404` `not_found`
|
|
1399
|
+
* for an unknown method, `409` `method_being_deleted`, `400` `malformed_request` for a cursor
|
|
1400
|
+
* the listing did not issue for this method, `422` for a `limit` outside 1 to 100. A page whose `items` is not an array or whose `next_cursor` is neither a string nor `null`
|
|
1401
|
+
* is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
|
|
1402
|
+
*/
|
|
1403
|
+
async listMethodVersions(methodId, query = {}) {
|
|
1404
|
+
const params = new URLSearchParams();
|
|
1405
|
+
// `!== undefined`, not truthiness — mirroring `listMethods`: an explicit empty cursor is bad
|
|
1406
|
+
// input the API should refuse, not an omission.
|
|
1407
|
+
if (query.limit !== undefined)
|
|
1408
|
+
params.set("limit", String(query.limit));
|
|
1409
|
+
if (query.cursor !== undefined)
|
|
1410
|
+
params.set("cursor", query.cursor);
|
|
1411
|
+
const suffix = params.toString();
|
|
1412
|
+
const endpoint = `${methodPath(methodId)}/versions`;
|
|
1413
|
+
return this.requestPage(suffix ? `${endpoint}?${suffix}` : endpoint);
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* One published version of a method, with its sources —
|
|
1417
|
+
* `GET /v1/methods/{id}/versions/{n}`. Its `python` is parsed into `MethodFile[]` as a
|
|
1418
|
+
* method's is.
|
|
1419
|
+
*
|
|
1420
|
+
* Takes a bare catalog id, as `getMethod` does, and the version number. Throws
|
|
1421
|
+
* `RequestArgumentError` for a `version` that is not a positive integer, before any request.
|
|
1422
|
+
* Throws `ApiResponseError`: `404` `method_version_not_found` for a version the method never
|
|
1423
|
+
* published, `404` `not_found` for an unknown method, `409` `method_being_deleted`.
|
|
1424
|
+
*/
|
|
1425
|
+
async getMethodVersion(methodId, version) {
|
|
1426
|
+
if (!Number.isSafeInteger(version) || version < 1) {
|
|
1427
|
+
throw new RequestArgumentError(`getMethodVersion() takes a version number, a positive integer; got ${String(version)}.`);
|
|
1428
|
+
}
|
|
1429
|
+
const endpoint = `${methodPath(methodId)}/versions/${version}`;
|
|
1430
|
+
const res = await this.requestProductAnswer("GET", endpoint, undefined);
|
|
1431
|
+
const wire = this.readObjectAnswer("GET", endpoint, res);
|
|
1432
|
+
return this.readStoredSources("GET", endpoint, res, wire, "a published version");
|
|
1249
1433
|
}
|
|
1250
1434
|
/**
|
|
1251
1435
|
* Erase a method and everything it produced — `DELETE /v1/methods/{id}`.
|
|
@@ -1260,11 +1444,13 @@ export class PipelexApiClient {
|
|
|
1260
1444
|
* it with a `409`.
|
|
1261
1445
|
*
|
|
1262
1446
|
* A double-clicked delete is safe: the claim is a conditional write, so the
|
|
1263
|
-
* second call is an `ApiResponseError` (`409
|
|
1264
|
-
* cascade over the same runs. An unknown or foreign-org id is a `404`.
|
|
1447
|
+
* second call is an `ApiResponseError` (`409 method_being_deleted`) rather than a
|
|
1448
|
+
* second cascade over the same runs. An unknown or foreign-org id is a `404`. The
|
|
1449
|
+
* erasure deletes the method's published versions with the rest. Takes a bare catalog id,
|
|
1450
|
+
* as `getMethod` does: the whole method is erased, never one of its versions.
|
|
1265
1451
|
*/
|
|
1266
1452
|
async deleteMethod(methodId) {
|
|
1267
|
-
return this.requestProduct("DELETE",
|
|
1453
|
+
return this.requestProduct("DELETE", methodPath(methodId));
|
|
1268
1454
|
}
|
|
1269
1455
|
/** The caller's org memberships + active-org feature flags — `GET /v1/organizations/memberships`. */
|
|
1270
1456
|
async listMemberships() {
|
|
@@ -1471,6 +1657,12 @@ export class PipelexApiClient {
|
|
|
1471
1657
|
* `createdFrom` / `createdTo` are applied server-side as index key
|
|
1472
1658
|
* conditions, so a bounded page genuinely reads less. They are INSTANTS,
|
|
1473
1659
|
* not days — see `ListRunsQuery`.
|
|
1660
|
+
*
|
|
1661
|
+
* Takes a bare catalog id: the history files the runs of every version and
|
|
1662
|
+
* of the draft together under it, so `mt_…@3` names no history of its own
|
|
1663
|
+
* and the platform refuses it with a `400`. Strip a suffix with
|
|
1664
|
+
* `parseMethodSelector`, and read which version a run ran from its
|
|
1665
|
+
* `method_version`.
|
|
1474
1666
|
*/
|
|
1475
1667
|
async listRuns(methodId, query = {}) {
|
|
1476
1668
|
const params = new URLSearchParams({ method_id: methodId });
|
|
@@ -1511,6 +1703,8 @@ export class PipelexApiClient {
|
|
|
1511
1703
|
* cliff: it yields until the server says there is no more, the caller decides
|
|
1512
1704
|
* when to stop, and only one page is ever in memory. If you truly want an
|
|
1513
1705
|
* array, `Array.fromAsync` makes that your explicit choice.
|
|
1706
|
+
*
|
|
1707
|
+
* Takes a bare catalog id, as `listRuns` does.
|
|
1514
1708
|
*/
|
|
1515
1709
|
async *iterateRuns(methodId, query = {}) {
|
|
1516
1710
|
let cursor;
|
|
@@ -1782,7 +1976,8 @@ function assertRunSourcesExclusive(options) {
|
|
|
1782
1976
|
*
|
|
1783
1977
|
* The one documented run-route exception is deliberately NOT here: inline
|
|
1784
1978
|
* source + `method_id` stays legal (the inline source runs; the id demotes to
|
|
1785
|
-
* run-history linkage)
|
|
1979
|
+
* run-history linkage), its one condition, a bare id, held by
|
|
1980
|
+
* `assertLinkageMethodIdIsBare` below. `pipe_code` beside a `method_ref` is legal too — it
|
|
1786
1981
|
* overrides the manifest's `main_pipe`. The wording of the first two errors
|
|
1787
1982
|
* mirrors the server's validator; presence semantics match it as well
|
|
1788
1983
|
* (`mthds_contents` counts when non-empty, a bundle encoding counts when the
|
|
@@ -1802,6 +1997,36 @@ function assertMethodRefPairsWithNothing(options) {
|
|
|
1802
1997
|
"and takes no run-history linkage id. Send exactly one method selector.");
|
|
1803
1998
|
}
|
|
1804
1999
|
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Enforce the run routes' linkage clause, mirroring the platform's own `422` so a
|
|
2002
|
+
* suffixed linkage id fails before anything hits the wire. Beside an inline source
|
|
2003
|
+
* (`mthds_contents`, `files` or `bundle_b64`) the `method_id` is run-history linkage
|
|
2004
|
+
* and must be a bare catalog id: the inline source is what runs, so a version suffix
|
|
2005
|
+
* (`mt_…@3`, `mt_…@draft`) would claim a version that did not. A `method_id` alone
|
|
2006
|
+
* keeps its suffix, which names the version to run.
|
|
2007
|
+
*
|
|
2008
|
+
* An inline source counts only when it reaches the wire, as the platform counts it:
|
|
2009
|
+
* `mthds_contents` when non-empty and a bundle encoding when `hasBundlePayload` says
|
|
2010
|
+
* it carries one, since `nonEmptyFiles` and `nonEmptyString` drop an empty `files: {}`
|
|
2011
|
+
* or `bundle_b64: ""` from the body, and a suffixed id beside one then runs the version
|
|
2012
|
+
* it names. That differs on purpose from `assertMethodRefPairsWithNothing`, which
|
|
2013
|
+
* mirrors the platform's presence-based pairing validator. Only the suffix is checked,
|
|
2014
|
+
* by its `@`, since the catalog id's alphabet has none: the id itself stays a
|
|
2015
|
+
* pass-through the platform resolves.
|
|
2016
|
+
*/
|
|
2017
|
+
function assertLinkageMethodIdIsBare(options) {
|
|
2018
|
+
const methodId = nonEmptyString(options.method_id);
|
|
2019
|
+
if (methodId === undefined || !methodId.includes("@"))
|
|
2020
|
+
return;
|
|
2021
|
+
const hasInlineSource = (options.mthds_contents != null && options.mthds_contents.length > 0) ||
|
|
2022
|
+
hasBundlePayload(options);
|
|
2023
|
+
if (!hasInlineSource)
|
|
2024
|
+
return;
|
|
2025
|
+
throw new RequestArgumentError(`method_id "${methodId}" beside an inline source is run-history linkage and must be a bare ` +
|
|
2026
|
+
"catalog id: the inline source is what runs, so a version suffix would claim a version " +
|
|
2027
|
+
"that did not. Send the bare id (parseMethodSelector(...).method_id), or drop the inline " +
|
|
2028
|
+
"source to run the version the selector names.");
|
|
2029
|
+
}
|
|
1805
2030
|
/**
|
|
1806
2031
|
* Normalize a bundle encoding for the wire: an empty map / string is NOT a
|
|
1807
2032
|
* runnable bundle, so it must not be sent (the runner rejects a zero-file
|