@versionone/agility-sdk 1.0.0-alpha.2 → 1.0.0-alpha.3
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 +17 -10
- package/dist/index.cjs +205 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4112 -3883
- package/dist/index.d.ts +4112 -3883
- package/dist/index.js +201 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,16 +5,61 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/core/errors.ts
|
|
8
|
+
function kindOf(status, code) {
|
|
9
|
+
switch (code) {
|
|
10
|
+
case "ABORTED":
|
|
11
|
+
return "aborted";
|
|
12
|
+
case "TIMEOUT":
|
|
13
|
+
case "NETWORK_ERROR":
|
|
14
|
+
return "transient";
|
|
15
|
+
case "BAD_JSON":
|
|
16
|
+
case "BAD_RESPONSE":
|
|
17
|
+
return "protocol";
|
|
18
|
+
case "USAGE":
|
|
19
|
+
return "usage";
|
|
20
|
+
case "CommandFailure":
|
|
21
|
+
return "commandFailure";
|
|
22
|
+
case "QUERY_REJECTED":
|
|
23
|
+
case "TOO_LARGE":
|
|
24
|
+
return "invalid";
|
|
25
|
+
case "NOT_FOUND":
|
|
26
|
+
return "notFound";
|
|
27
|
+
}
|
|
28
|
+
if (status === 401) return "auth";
|
|
29
|
+
if (status === 403) return "forbidden";
|
|
30
|
+
if (status === 404) return "notFound";
|
|
31
|
+
if (status === 408 || status === 429 || status >= 500) return "transient";
|
|
32
|
+
if (status >= 400) return "invalid";
|
|
33
|
+
return "unknown";
|
|
34
|
+
}
|
|
35
|
+
var causeOf = (details) => details && typeof details === "object" && "cause" in details ? details.cause : void 0;
|
|
8
36
|
var ApiError = class extends Error {
|
|
37
|
+
/** The HTTP status; 0 when no answer came back (network, timeout, abort, usage). */
|
|
9
38
|
status;
|
|
10
39
|
code;
|
|
11
40
|
details;
|
|
12
|
-
|
|
13
|
-
|
|
41
|
+
kind;
|
|
42
|
+
constructor(message, status, code, details, kind = kindOf(status, code)) {
|
|
43
|
+
const cause = causeOf(details);
|
|
44
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
14
45
|
this.name = "ApiError";
|
|
15
46
|
this.status = status;
|
|
16
47
|
this.code = code;
|
|
17
48
|
this.details = details;
|
|
49
|
+
this.kind = kind;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var UsageError = class extends ApiError {
|
|
53
|
+
constructor(message, details) {
|
|
54
|
+
super(message, 0, "USAGE", details, "usage");
|
|
55
|
+
this.name = "UsageError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var WrappedError = class extends ApiError {
|
|
59
|
+
constructor(message, code, cause, details) {
|
|
60
|
+
const inner = cause instanceof ApiError ? cause : void 0;
|
|
61
|
+
super(message, inner?.status ?? 0, code, { ...details, cause }, inner?.kind ?? "unknown");
|
|
62
|
+
this.name = "WrappedError";
|
|
18
63
|
}
|
|
19
64
|
};
|
|
20
65
|
|
|
@@ -154,7 +199,7 @@ function isOidToken(value) {
|
|
|
154
199
|
}
|
|
155
200
|
function parseOid(token) {
|
|
156
201
|
const m = OID_TOKEN.exec(token);
|
|
157
|
-
if (!m) throw new
|
|
202
|
+
if (!m) throw new UsageError(`"${token}" is not an OID token (Story:1234)`);
|
|
158
203
|
return { type: m[1], id: Number(m[2]), moment: m[3] === void 0 ? null : Number(m[3]) };
|
|
159
204
|
}
|
|
160
205
|
function momentless(token) {
|
|
@@ -178,6 +223,7 @@ __export(assetApi_exports, {
|
|
|
178
223
|
mergeOutcome: () => mergeOutcome,
|
|
179
224
|
momentless: () => momentless,
|
|
180
225
|
outcomeOf: () => outcomeOf,
|
|
226
|
+
parseViolation: () => parseViolation,
|
|
181
227
|
preview: () => preview,
|
|
182
228
|
queries: () => queries,
|
|
183
229
|
query: () => query,
|
|
@@ -343,11 +389,20 @@ function toNumber(value) {
|
|
|
343
389
|
const n = typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
344
390
|
return Number.isNaN(n) ? value : n;
|
|
345
391
|
}
|
|
392
|
+
function parseViolation(message) {
|
|
393
|
+
const parts = message.trim().split("'");
|
|
394
|
+
if (parts[0] !== "Violation" || !parts[1]) return null;
|
|
395
|
+
const rule = parts[1], rest = parts.slice(2).filter((p) => p !== "");
|
|
396
|
+
if (rest[0] === "AttributeDefinition" && rest[1] && rest[2]) return { rule, attribute: `${rest[2]}.${rest[1]}`, tag: rest.join("'") };
|
|
397
|
+
const token = rest.length === 1 && /^[A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*$/.test(rest[0]) ? rest[0] : null;
|
|
398
|
+
return { rule, attribute: token, tag: rest.length ? rest.join("'") : null };
|
|
399
|
+
}
|
|
346
400
|
function failuresOf(response) {
|
|
347
401
|
return (response.commandFailures?.commands ?? []).map(({ error, ...echoed }) => ({
|
|
348
402
|
index: typeof error === "object" && error && typeof error.sourceCommandIndex === "number" ? error.sourceCommandIndex : null,
|
|
349
403
|
message: typeof error === "string" ? error : error?.message || "command failed",
|
|
350
|
-
echoed
|
|
404
|
+
echoed,
|
|
405
|
+
violation: parseViolation(typeof error === "string" ? error : error?.message || "")
|
|
351
406
|
}));
|
|
352
407
|
}
|
|
353
408
|
function outcomeOf(response) {
|
|
@@ -373,11 +428,14 @@ function mergeOutcome(into, done, offset) {
|
|
|
373
428
|
var CommandFailureError = class extends ApiError {
|
|
374
429
|
failures;
|
|
375
430
|
response;
|
|
431
|
+
/** What the batch did, in the model's terms: the commands that succeeded are committed. */
|
|
432
|
+
outcome;
|
|
376
433
|
constructor(response, failures = failuresOf(response)) {
|
|
377
434
|
super(failures.map((f) => f.message).join("; ") || "command failed", 200, "CommandFailure", failures);
|
|
378
435
|
this.name = "CommandFailureError";
|
|
379
436
|
this.failures = failures;
|
|
380
437
|
this.response = response;
|
|
438
|
+
this.outcome = outcomeOf(response);
|
|
381
439
|
}
|
|
382
440
|
};
|
|
383
441
|
var bodyOf = (c) => {
|
|
@@ -413,7 +471,7 @@ async function send(transport, commands, options = {}) {
|
|
|
413
471
|
const results = response.queryResult?.results ?? [];
|
|
414
472
|
let aligned = realign(queries3, results, failed);
|
|
415
473
|
if (!aligned) {
|
|
416
|
-
if (!failures.some((f) => f.index === null)) throw new
|
|
474
|
+
if (!failures.some((f) => f.index === null)) throw new ApiError(`api/asset returned ${results.length} query results for ${queries3.filter((q) => !failed.has(q.index)).length} queries that did not fail; cannot tell which result belongs to which query`, 200, "BAD_RESPONSE", response);
|
|
417
475
|
aligned = queries3.map(() => null);
|
|
418
476
|
}
|
|
419
477
|
const contextOf = (i) => {
|
|
@@ -435,7 +493,7 @@ async function query(transport, q, options = {}) {
|
|
|
435
493
|
return rows ?? [];
|
|
436
494
|
}
|
|
437
495
|
async function queryPage(transport, q, options = {}) {
|
|
438
|
-
if (!q.page) throw new
|
|
496
|
+
if (!q.page) throw new UsageError(`query of ${q.from} without a page: every api/asset read must be bounded`);
|
|
439
497
|
const response = await send(transport, [{ ...q, needTotal: true }], { ...options, throwOnFailure: true });
|
|
440
498
|
const rows = response.queryResult.results[0] ?? [];
|
|
441
499
|
const reported = response.queryResult.resultMetaData?.[0]?.total;
|
|
@@ -443,7 +501,7 @@ async function queryPage(transport, q, options = {}) {
|
|
|
443
501
|
return { rows, total, more: total === null ? rows.length === q.page.size : q.page.start + rows.length < total };
|
|
444
502
|
}
|
|
445
503
|
async function queries(transport, qs, options = {}) {
|
|
446
|
-
for (const q of qs) if (!q.page) throw new
|
|
504
|
+
for (const q of qs) if (!q.page) throw new UsageError(`query of ${q.from} without a page: every api/asset read must be bounded`);
|
|
447
505
|
const response = await send(transport, qs, { ...options, throwOnFailure: true });
|
|
448
506
|
return qs.map((_, i) => response.queryResult.results[i] ?? []);
|
|
449
507
|
}
|
|
@@ -481,7 +539,7 @@ function fromBase64(text) {
|
|
|
481
539
|
}
|
|
482
540
|
function numericId(spec) {
|
|
483
541
|
const m = /^(?:[A-Za-z]+:)?(\d+)(?::\d+)?$/.exec(String(spec).trim());
|
|
484
|
-
if (!m) throw new
|
|
542
|
+
if (!m) throw new UsageError(`"${spec}" is not an asset id (5678, or an OID token such as Attachment:5678)`);
|
|
485
543
|
return m[1];
|
|
486
544
|
}
|
|
487
545
|
var join = (baseUrl, path) => `${baseUrl.replace(/\/+$/, "")}/${path}`;
|
|
@@ -577,9 +635,9 @@ async function get(transport, id) {
|
|
|
577
635
|
const file = await getFile(transport, meta.id);
|
|
578
636
|
return { ...meta, bytes: file.bytes, contentType: file.contentType ?? meta.contentType, recordedContentType: meta.contentType };
|
|
579
637
|
}
|
|
580
|
-
var AttachmentUploadError = class extends
|
|
638
|
+
var AttachmentUploadError = class extends WrappedError {
|
|
581
639
|
constructor(attachment, cause) {
|
|
582
|
-
super(`${attachment.oid} was created on its asset with no content, and uploading the file failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
640
|
+
super(`${attachment.oid} was created on its asset with no content, and uploading the file failed: ${cause instanceof Error ? cause.message : String(cause)}`, "ATTACHMENT_UPLOAD", cause);
|
|
583
641
|
this.attachment = attachment;
|
|
584
642
|
this.name = "AttachmentUploadError";
|
|
585
643
|
}
|
|
@@ -612,7 +670,7 @@ async function remove(transport, id) {
|
|
|
612
670
|
// src/services/guard.ts
|
|
613
671
|
var CHUNK = 100;
|
|
614
672
|
var UNGUARDABLE_TYPES = ["Idea", "IdeaChangeSet", "IdeaStatus", "IdeaComment", "IdeaCategory", "IdeaForum", "IdeaUser"];
|
|
615
|
-
var UnguardableError = class extends
|
|
673
|
+
var UnguardableError = class extends UsageError {
|
|
616
674
|
reasons;
|
|
617
675
|
constructor(reasons) {
|
|
618
676
|
super(`cannot guard this batch: ${reasons.map((r) => `command ${r.index}: ${r.reason}`).join("; ")}`);
|
|
@@ -678,7 +736,7 @@ function guard(projected, stamps) {
|
|
|
678
736
|
unstamped.push(x.oid);
|
|
679
737
|
continue;
|
|
680
738
|
}
|
|
681
|
-
if (!STAMP.test(stamp)) throw new
|
|
739
|
+
if (!STAMP.test(stamp)) throw new ApiError(`${x.oid}: ChangeDate ${JSON.stringify(stamp)} is not a timestamp; refusing to put it in a filter`, 200, "BAD_RESPONSE", { oid: x.oid, stamp });
|
|
682
740
|
const scope = { from: typeOfOid(x.oid), filter: ["ID=$oid", `ChangeDate='${stamp}'`], with: { $oid: x.oid } };
|
|
683
741
|
commands.push(x.update ? { ...scope, update: x.update } : { ...scope, execute: x.execute });
|
|
684
742
|
}
|
|
@@ -695,16 +753,14 @@ function passesOf(projected) {
|
|
|
695
753
|
}
|
|
696
754
|
return passes;
|
|
697
755
|
}
|
|
698
|
-
var GuardedPassError = class extends
|
|
756
|
+
var GuardedPassError = class extends WrappedError {
|
|
699
757
|
constructor(cause, outcome, sent, pass) {
|
|
700
|
-
super(`guarded send failed in pass ${pass + 1}${pass ? " (earlier passes applied)" : ""}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
701
|
-
this.cause = cause;
|
|
758
|
+
super(`guarded send failed in pass ${pass + 1}${pass ? " (earlier passes applied)" : ""}: ${cause instanceof Error ? cause.message : String(cause)}`, "GUARDED_PASS", cause);
|
|
702
759
|
this.outcome = outcome;
|
|
703
760
|
this.sent = sent;
|
|
704
761
|
this.pass = pass;
|
|
705
762
|
this.name = "GuardedPassError";
|
|
706
763
|
}
|
|
707
|
-
cause;
|
|
708
764
|
outcome;
|
|
709
765
|
sent;
|
|
710
766
|
pass;
|
|
@@ -921,18 +977,18 @@ function reverseOf(described, before, done, context = {}) {
|
|
|
921
977
|
if (commands.length) notes.unshift("restores the state recorded at snapshotAt; anything that changed between then and sentAt is not reflected, and a scalar restored to null is cleared, not unset");
|
|
922
978
|
return { commands, notes };
|
|
923
979
|
}
|
|
924
|
-
var BatchRecordError = class extends
|
|
980
|
+
var BatchRecordError = class extends WrappedError {
|
|
925
981
|
receipt;
|
|
926
982
|
constructor(cause, receipt) {
|
|
927
|
-
super(`the batch was sent and its outcome recorded, but reading the assets back afterwards failed, so receipt.after is incomplete: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
983
|
+
super(`the batch was sent and its outcome recorded, but reading the assets back afterwards failed, so receipt.after is incomplete: ${cause instanceof Error ? cause.message : String(cause)}`, "BATCH_RECORD", cause);
|
|
928
984
|
this.name = "BatchRecordError";
|
|
929
985
|
this.receipt = receipt;
|
|
930
986
|
}
|
|
931
987
|
};
|
|
932
|
-
var BatchSendError = class extends
|
|
988
|
+
var BatchSendError = class extends WrappedError {
|
|
933
989
|
receipt;
|
|
934
990
|
constructor(cause, receipt) {
|
|
935
|
-
super(`the batch send failed; whether the server applied it is unknown: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
991
|
+
super(`the batch send failed; whether the server applied it is unknown: ${cause instanceof Error ? cause.message : String(cause)}`, "BATCH_SEND", cause);
|
|
936
992
|
this.name = "BatchSendError";
|
|
937
993
|
this.receipt = receipt;
|
|
938
994
|
}
|
|
@@ -940,7 +996,7 @@ var BatchSendError = class extends Error {
|
|
|
940
996
|
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
941
997
|
async function runBatch(transport, schema, commands, options = {}) {
|
|
942
998
|
const useGuard = options.guard ?? true;
|
|
943
|
-
if (!commands.length) throw new
|
|
999
|
+
if (!commands.length) throw new UsageError("the batch is empty");
|
|
944
1000
|
const refused = unguardable(commands);
|
|
945
1001
|
const fatal = refused.filter((r) => r.reason.includes("rejected by the server"));
|
|
946
1002
|
if (fatal.length || useGuard && refused.length) throw new UnguardableError(useGuard ? refused : fatal);
|
|
@@ -1111,7 +1167,7 @@ async function resolveNumbers(transport, numbers, options = {}) {
|
|
|
1111
1167
|
for (const n of numbers) if (!firsts.has(n.trim().toUpperCase())) firsts.set(n.trim().toUpperCase(), n.trim());
|
|
1112
1168
|
const asked = [...firsts.values()];
|
|
1113
1169
|
const invalid = asked.filter((n) => !isAssetNumber(n));
|
|
1114
|
-
if (invalid.length) throw new
|
|
1170
|
+
if (invalid.length) throw new UsageError(`not asset numbers (expected a prefix and digits, S-1234): ${invalid.join(", ")}`);
|
|
1115
1171
|
const byNumber = /* @__PURE__ */ new Map();
|
|
1116
1172
|
for (let i = 0; i < asked.length; i += CHUNK3) {
|
|
1117
1173
|
for (const r of await askRoots(transport, asked.slice(i, i + CHUNK3), options.types)) {
|
|
@@ -1132,7 +1188,7 @@ async function resolveRef(transport, spec, options = {}) {
|
|
|
1132
1188
|
return { number: null, oid, type: typeOfOid(oid), name: null, assetState: null };
|
|
1133
1189
|
}
|
|
1134
1190
|
if (isAssetNumber(text)) return (await resolveNumbers(transport, [text], options)).found[0] ?? null;
|
|
1135
|
-
throw new
|
|
1191
|
+
throw new UsageError(`"${spec}" is neither an asset number (S-1234) nor an OID token (Story:1234)`);
|
|
1136
1192
|
}
|
|
1137
1193
|
|
|
1138
1194
|
// src/wire/meta.ts
|
|
@@ -1194,7 +1250,7 @@ async function getAssetType(transport, token) {
|
|
|
1194
1250
|
}
|
|
1195
1251
|
async function getAttributeDefinition(transport, tokenref) {
|
|
1196
1252
|
const [type, attribute] = tokenref.split(".");
|
|
1197
|
-
if (!type || !attribute) throw new
|
|
1253
|
+
if (!type || !attribute) throw new UsageError(`"${tokenref}" is not an attribute token ("Story.Name")`);
|
|
1198
1254
|
return toAttributeDefinition(await transport.request({ method: "GET", path: `/meta.v1/${encodeURIComponent(type)}/${encodeURIComponent(attribute)}`, responseType: "json" }));
|
|
1199
1255
|
}
|
|
1200
1256
|
|
|
@@ -1321,7 +1377,7 @@ async function localize(transport, tags, options = {}) {
|
|
|
1321
1377
|
const encoded = encodeURIComponent(toLson(unique));
|
|
1322
1378
|
const headers = options.culture ? { "Accept-Language": options.culture } : void 0;
|
|
1323
1379
|
const result = encoded.length <= (options.maxGetLength ?? DEFAULT_MAX_GET_LENGTH) ? await transport.request({ method: "GET", path: `/loc-2.v1?${encoded}`, headers, responseType: "json" }) : await transport.request({ method: "POST", path: "/loc-2.v1", headers, body: encoded, contentType: "application/x-www-form-urlencoded", responseType: "json", idempotent: true });
|
|
1324
|
-
if (!result || typeof result !== "object" || Array.isArray(result)) throw new
|
|
1380
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) throw new ApiError(`loc-2.v1 answered an array of tags with ${JSON.stringify(result)?.slice(0, 100)}, not an object`, 200, "BAD_RESPONSE", result);
|
|
1325
1381
|
return result;
|
|
1326
1382
|
}
|
|
1327
1383
|
async function localizeOne(transport, tag, options = {}) {
|
|
@@ -1341,7 +1397,7 @@ function rejected(error) {
|
|
|
1341
1397
|
return messages.length ? new ApiError(messages.join("; "), error.status, "QUERY_REJECTED", error.details) : error;
|
|
1342
1398
|
}
|
|
1343
1399
|
async function queries2(transport, qs, options = {}) {
|
|
1344
|
-
for (const q of qs) if (!q.page) throw new
|
|
1400
|
+
for (const q of qs) if (!q.page) throw new UsageError(`query of ${q.from} without a page: every query.v1 read must be bounded`);
|
|
1345
1401
|
if (!qs.length) return [];
|
|
1346
1402
|
let response;
|
|
1347
1403
|
try {
|
|
@@ -1402,8 +1458,8 @@ function fromAssetDocument(doc, { history: history2 = false } = {}) {
|
|
|
1402
1458
|
}
|
|
1403
1459
|
function toQuery(params) {
|
|
1404
1460
|
return {
|
|
1405
|
-
sel: params.sel?.join(","),
|
|
1406
|
-
where: params.where?.length ? params.where.join(";") : void 0,
|
|
1461
|
+
sel: (params.select ?? params.sel)?.join(","),
|
|
1462
|
+
where: (params.filter ?? params.where)?.length ? (params.filter ?? params.where).join(";") : void 0,
|
|
1407
1463
|
with: params.with ? Object.entries(params.with).map(([k, v]) => `${k}=${[].concat(v).join(",")}`).join("|") : void 0,
|
|
1408
1464
|
find: params.find,
|
|
1409
1465
|
findin: params.findin?.join(","),
|
|
@@ -1415,14 +1471,14 @@ function toQuery(params) {
|
|
|
1415
1471
|
};
|
|
1416
1472
|
}
|
|
1417
1473
|
async function read(transport, path, params, history2) {
|
|
1418
|
-
if (!params.page) throw new
|
|
1474
|
+
if (!params.page) throw new UsageError(`rest-1 read of ${path} without a page: every read must be bounded`);
|
|
1419
1475
|
const doc = await transport.request({ method: "GET", path, query: toQuery(params), responseType: "json" });
|
|
1420
1476
|
return { rows: (doc.Assets ?? []).map((a) => fromAssetDocument(a, { history: history2 })), total: typeof doc.total === "number" && doc.total >= 0 ? doc.total : null, pageSize: doc.pageSize, pageStart: doc.pageStart };
|
|
1421
1477
|
}
|
|
1422
1478
|
var list2 = (transport, type, params) => read(transport, `/rest-1.v1/Data/${encodeURIComponent(type)}`, params, false);
|
|
1423
|
-
async function get2(transport, oid,
|
|
1479
|
+
async function get2(transport, oid, select) {
|
|
1424
1480
|
const [type, id] = momentless(oid).split(":");
|
|
1425
|
-
const doc = await transport.request({ method: "GET", path: `/rest-1.v1/Data/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, query: { sel:
|
|
1481
|
+
const doc = await transport.request({ method: "GET", path: `/rest-1.v1/Data/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, query: { sel: select?.join(",") }, responseType: "json" });
|
|
1426
1482
|
return fromAssetDocument(doc);
|
|
1427
1483
|
}
|
|
1428
1484
|
var history = (transport, target, params) => {
|
|
@@ -1432,6 +1488,8 @@ var history = (transport, target, params) => {
|
|
|
1432
1488
|
};
|
|
1433
1489
|
|
|
1434
1490
|
// src/client.ts
|
|
1491
|
+
var DEFAULT_LIMIT = 1e3;
|
|
1492
|
+
var ME_SELECT = ["Name", "Username", "Nickname", "Email"];
|
|
1435
1493
|
var AgilityApiClient = class _AgilityApiClient {
|
|
1436
1494
|
transport;
|
|
1437
1495
|
schema;
|
|
@@ -1463,6 +1521,48 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1463
1521
|
const transport = { request: (options) => base.request({ ...options, signal: options.signal ?? signal }) };
|
|
1464
1522
|
return new _AgilityApiClient(this.config, { transport, schema: this.schema });
|
|
1465
1523
|
}
|
|
1524
|
+
async query(q) {
|
|
1525
|
+
await this.schema.ensureFor([q]);
|
|
1526
|
+
return query2(this.transport, q, { lookup: this.schema });
|
|
1527
|
+
}
|
|
1528
|
+
async queries(qs) {
|
|
1529
|
+
await this.schema.ensureFor(qs);
|
|
1530
|
+
return queries2(this.transport, [...qs], { lookup: this.schema });
|
|
1531
|
+
}
|
|
1532
|
+
async get(oid, select) {
|
|
1533
|
+
const rows = await this.query({ from: momentless(oid), ...select ? { select } : {}, page: { size: 1, start: 0 } });
|
|
1534
|
+
return rows[0] ?? null;
|
|
1535
|
+
}
|
|
1536
|
+
async queryPage(q) {
|
|
1537
|
+
await this.schema.ensureFor([q]);
|
|
1538
|
+
return queryPage(this.transport, q, { lookup: this.schema });
|
|
1539
|
+
}
|
|
1540
|
+
async *pages(q, options = {}) {
|
|
1541
|
+
if (!q.page) throw new UsageError(`query of ${q.from} without a page: every read must be bounded`);
|
|
1542
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
1543
|
+
const size = q.page.size;
|
|
1544
|
+
let start = q.page.start, read2 = 0;
|
|
1545
|
+
while (read2 < limit) {
|
|
1546
|
+
const want = Math.min(size, limit - read2);
|
|
1547
|
+
const rows = await this.query({ ...q, page: { size: want, start } });
|
|
1548
|
+
if (rows.length) yield rows;
|
|
1549
|
+
read2 += rows.length;
|
|
1550
|
+
start += rows.length;
|
|
1551
|
+
if (rows.length < want) return;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
async all(q, options = {}) {
|
|
1555
|
+
const out = [];
|
|
1556
|
+
for await (const page2 of this.pages(q, options)) out.push(...page2);
|
|
1557
|
+
return out;
|
|
1558
|
+
}
|
|
1559
|
+
/** The member these credentials belong to (Member.IsSelf, live 2026-11-12): what a comment's Author is. */
|
|
1560
|
+
async me() {
|
|
1561
|
+
const [row] = await this.query({ from: "Member", select: ME_SELECT, filter: ["IsSelf='true'"], page: { size: 1, start: 0 } });
|
|
1562
|
+
if (!row) throw new ApiError("the server knows no member for these credentials", 0, "NOT_FOUND");
|
|
1563
|
+
return row;
|
|
1564
|
+
}
|
|
1565
|
+
// ---- the endpoints behind the reads -----------------------------------------------
|
|
1466
1566
|
/** meta.v1: the instance's schema, raw. (The translated, cached view is `schema`.) */
|
|
1467
1567
|
meta = {
|
|
1468
1568
|
metamodel: () => getMetamodel(this.transport),
|
|
@@ -1471,12 +1571,65 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1471
1571
|
};
|
|
1472
1572
|
/**
|
|
1473
1573
|
* loc-2.v1: labels for tags, in one request (meta's DisplayName values are
|
|
1474
|
-
* tags). An unmatched tag comes back as
|
|
1475
|
-
* "missing", so neither does this.
|
|
1574
|
+
* tags, and so is a violation's `tag`). An unmatched tag comes back as
|
|
1575
|
+
* itself; the server never says "missing", so neither does this.
|
|
1476
1576
|
*/
|
|
1477
1577
|
localize(tags, options) {
|
|
1478
1578
|
return localize(this.transport, tags, options);
|
|
1479
1579
|
}
|
|
1580
|
+
/**
|
|
1581
|
+
* rest-1.v1, for what query.v1 cannot do: deleted assets (`deleted:
|
|
1582
|
+
* true`), one asset by OID without a page, and history (one row per
|
|
1583
|
+
* moment). Parameters are spelled as a query's (`select`, `filter`).
|
|
1584
|
+
*/
|
|
1585
|
+
data = {
|
|
1586
|
+
list: (type, params) => list2(this.transport, type, params),
|
|
1587
|
+
get: (oid, select) => get2(this.transport, oid, select),
|
|
1588
|
+
history: (target, params) => history(this.transport, target, params)
|
|
1589
|
+
};
|
|
1590
|
+
/** @deprecated since 1.0.0-alpha.3: `client.query` / `client.queries` read through query.v1. Removed in 1.0.0. */
|
|
1591
|
+
queryV1 = {
|
|
1592
|
+
query: (q) => this.query(q),
|
|
1593
|
+
queries: (qs) => this.queries(qs)
|
|
1594
|
+
};
|
|
1595
|
+
// ---- writes -----------------------------------------------------------------------
|
|
1596
|
+
/** api/asset: batches, answered in the model's terms. */
|
|
1597
|
+
assets = {
|
|
1598
|
+
/**
|
|
1599
|
+
* Send a batch. The Outcome says what it did — created, modified and
|
|
1600
|
+
* operated OIDs, failures by index (each with its parsed violation),
|
|
1601
|
+
* query rows per query command — and never the envelope. api/asset
|
|
1602
|
+
* commits per command and answers 200 either way, so a failed command
|
|
1603
|
+
* is in `failures`, or thrown as a CommandFailureError with
|
|
1604
|
+
* `throwOnFailure` (the error carries the same Outcome).
|
|
1605
|
+
*/
|
|
1606
|
+
send: async (commands, options) => {
|
|
1607
|
+
await this.schema.ensureFor(commands);
|
|
1608
|
+
return outcomeOf(await send(this.transport, commands, { ...options, lookup: this.schema }));
|
|
1609
|
+
},
|
|
1610
|
+
/** The server's projection of a batch: one command per asset it would touch, nothing applied. */
|
|
1611
|
+
preview: (commands) => preview(this.transport, commands),
|
|
1612
|
+
/**
|
|
1613
|
+
* Send as a compare-and-set: each asset is changed only if nobody has
|
|
1614
|
+
* changed it since the preview read it; the rest come back `skipped`.
|
|
1615
|
+
* Throws UnguardableError, before any request, for shapes it cannot
|
|
1616
|
+
* cover.
|
|
1617
|
+
*/
|
|
1618
|
+
sendGuarded: async (commands, options) => {
|
|
1619
|
+
await this.schema.ensureFor(commands);
|
|
1620
|
+
return sendGuarded(this.transport, commands, { ...options, lookup: this.schema });
|
|
1621
|
+
},
|
|
1622
|
+
/** @deprecated since 1.0.0-alpha.3: use `client.query` (or `client.queryPage` for a total). Removed in 1.0.0. */
|
|
1623
|
+
query: async (q) => {
|
|
1624
|
+
await this.schema.ensureFor([q]);
|
|
1625
|
+
return query(this.transport, q, { lookup: this.schema });
|
|
1626
|
+
},
|
|
1627
|
+
/** @deprecated since 1.0.0-alpha.3: use `client.queries`. Removed in 1.0.0. */
|
|
1628
|
+
queries: async (qs) => {
|
|
1629
|
+
await this.schema.ensureFor(qs);
|
|
1630
|
+
return queries(this.transport, qs, { lookup: this.schema });
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1480
1633
|
/**
|
|
1481
1634
|
* Services: what a person types, turned into assets. Numbers resolve in
|
|
1482
1635
|
* one api/asset request across every number root; an OID token is taken
|
|
@@ -1499,7 +1652,7 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1499
1652
|
/**
|
|
1500
1653
|
* The files attached to assets: metadata on Attachment assets, bytes
|
|
1501
1654
|
* through attachment.img. `add` sends a small file inline (one request)
|
|
1502
|
-
* and a large one in two steps
|
|
1655
|
+
* and a large one in two steps.
|
|
1503
1656
|
*/
|
|
1504
1657
|
attachments = {
|
|
1505
1658
|
list: (asset, options) => list(this.transport, asset, options),
|
|
@@ -1509,7 +1662,8 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1509
1662
|
add: (asset, data, options) => add(this.transport, asset, data, options),
|
|
1510
1663
|
put: (id, data, contentType) => put(this.transport, id, data, contentType),
|
|
1511
1664
|
remove: (id) => remove(this.transport, id),
|
|
1512
|
-
|
|
1665
|
+
/** @deprecated since 1.0.0-alpha.3: `client.urls.attachment`. Removed in 1.0.0. */
|
|
1666
|
+
url: (id) => this.urls.attachment(id)
|
|
1513
1667
|
};
|
|
1514
1668
|
/**
|
|
1515
1669
|
* Images embedded in an asset's rich text (Description and other long
|
|
@@ -1519,55 +1673,16 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1519
1673
|
*/
|
|
1520
1674
|
embeddedImages = {
|
|
1521
1675
|
upload: (asset, data, options) => uploadEmbeddedImage(this.transport, asset, data, options),
|
|
1522
|
-
|
|
1676
|
+
/** @deprecated since 1.0.0-alpha.3: `client.urls.embeddedImage`. Removed in 1.0.0. */
|
|
1677
|
+
url: (id) => this.urls.embeddedImage(id),
|
|
1523
1678
|
normalize: (html) => normalizeEmbeddedImageUrls(html, this.config.baseUrl)
|
|
1524
1679
|
};
|
|
1525
|
-
/**
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
/**
|
|
1532
|
-
* query.v1: reads only, the same query bodies as assets.query, rows
|
|
1533
|
-
* translated the same way. The endpoint cannot write, so it is the safe
|
|
1534
|
-
* place to explore a query; its requests may be retried like a GET.
|
|
1535
|
-
*/
|
|
1536
|
-
queryV1 = {
|
|
1537
|
-
query: async (q) => {
|
|
1538
|
-
await this.schema.ensureFor([q]);
|
|
1539
|
-
return query2(this.transport, q, { lookup: this.schema });
|
|
1540
|
-
},
|
|
1541
|
-
queries: async (qs) => {
|
|
1542
|
-
await this.schema.ensureFor(qs);
|
|
1543
|
-
return queries2(this.transport, qs, { lookup: this.schema });
|
|
1544
|
-
}
|
|
1545
|
-
};
|
|
1546
|
-
/** api/asset: batches and queries, rows translated through the schema. */
|
|
1547
|
-
assets = {
|
|
1548
|
-
send: async (commands, options) => {
|
|
1549
|
-
await this.schema.ensureFor(commands);
|
|
1550
|
-
return send(this.transport, commands, { ...options, lookup: this.schema });
|
|
1551
|
-
},
|
|
1552
|
-
preview: (commands) => preview(this.transport, commands),
|
|
1553
|
-
/**
|
|
1554
|
-
* Send as a compare-and-set: each asset is changed only if nobody has
|
|
1555
|
-
* changed it since the preview read it; the rest come back `skipped`.
|
|
1556
|
-
* Throws UnguardableError, before any request, for shapes it cannot
|
|
1557
|
-
* cover.
|
|
1558
|
-
*/
|
|
1559
|
-
sendGuarded: async (commands, options) => {
|
|
1560
|
-
await this.schema.ensureFor(commands);
|
|
1561
|
-
return sendGuarded(this.transport, commands, { ...options, lookup: this.schema });
|
|
1562
|
-
},
|
|
1563
|
-
query: async (q) => {
|
|
1564
|
-
await this.schema.ensureFor([q]);
|
|
1565
|
-
return query(this.transport, q, { lookup: this.schema });
|
|
1566
|
-
},
|
|
1567
|
-
queries: async (qs) => {
|
|
1568
|
-
await this.schema.ensureFor(qs);
|
|
1569
|
-
return queries(this.transport, qs, { lookup: this.schema });
|
|
1570
|
-
}
|
|
1680
|
+
/** Where a browser opens things on this instance: an asset's page, an attachment's bytes, an embedded image. */
|
|
1681
|
+
urls = {
|
|
1682
|
+
/** The asset's page: assetdetail.v1 redirects to the type's own detail page (live 2026-11-12). */
|
|
1683
|
+
asset: (oid) => `${this.config.baseUrl.replace(/\/+$/, "")}/assetdetail.v1?oid=${momentless(oid)}`,
|
|
1684
|
+
attachment: (id) => attachmentUrl(this.config.baseUrl, id),
|
|
1685
|
+
embeddedImage: (id) => embeddedImageUrl(this.config.baseUrl, id)
|
|
1571
1686
|
};
|
|
1572
1687
|
};
|
|
1573
1688
|
export {
|
|
@@ -1586,6 +1701,8 @@ export {
|
|
|
1586
1701
|
TypeCache,
|
|
1587
1702
|
UNGUARDABLE_TYPES,
|
|
1588
1703
|
UnguardableError,
|
|
1704
|
+
UsageError,
|
|
1705
|
+
WrappedError,
|
|
1589
1706
|
assetApi_exports as assetApi,
|
|
1590
1707
|
assetStateOf,
|
|
1591
1708
|
attachment_exports as attachment,
|
|
@@ -1594,11 +1711,13 @@ export {
|
|
|
1594
1711
|
guard,
|
|
1595
1712
|
isAssetNumber,
|
|
1596
1713
|
isOidToken,
|
|
1714
|
+
kindOf,
|
|
1597
1715
|
loc2_exports as loc2,
|
|
1598
1716
|
meta_exports as meta,
|
|
1599
1717
|
momentless,
|
|
1600
1718
|
namesOf,
|
|
1601
1719
|
parseOid,
|
|
1720
|
+
parseViolation,
|
|
1602
1721
|
queryV1_exports as queryV1,
|
|
1603
1722
|
resolveNumbers,
|
|
1604
1723
|
resolveRef,
|