@versionone/agility-sdk 1.0.0-alpha.0 → 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 +41 -16
- package/dist/index.cjs +302 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4116 -3853
- package/dist/index.d.ts +4116 -3853
- package/dist/index.js +298 -92
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,8 @@ __export(index_exports, {
|
|
|
35
35
|
TypeCache: () => TypeCache,
|
|
36
36
|
UNGUARDABLE_TYPES: () => UNGUARDABLE_TYPES,
|
|
37
37
|
UnguardableError: () => UnguardableError,
|
|
38
|
+
UsageError: () => UsageError,
|
|
39
|
+
WrappedError: () => WrappedError,
|
|
38
40
|
assetApi: () => assetApi_exports,
|
|
39
41
|
assetStateOf: () => assetStateOf,
|
|
40
42
|
attachment: () => attachment_exports,
|
|
@@ -43,11 +45,13 @@ __export(index_exports, {
|
|
|
43
45
|
guard: () => guard,
|
|
44
46
|
isAssetNumber: () => isAssetNumber,
|
|
45
47
|
isOidToken: () => isOidToken,
|
|
48
|
+
kindOf: () => kindOf,
|
|
46
49
|
loc2: () => loc2_exports,
|
|
47
50
|
meta: () => meta_exports,
|
|
48
51
|
momentless: () => momentless,
|
|
49
52
|
namesOf: () => namesOf,
|
|
50
53
|
parseOid: () => parseOid,
|
|
54
|
+
parseViolation: () => parseViolation,
|
|
51
55
|
queryV1: () => queryV1_exports,
|
|
52
56
|
resolveNumbers: () => resolveNumbers,
|
|
53
57
|
resolveRef: () => resolveRef,
|
|
@@ -67,16 +71,61 @@ __export(index_exports, {
|
|
|
67
71
|
module.exports = __toCommonJS(index_exports);
|
|
68
72
|
|
|
69
73
|
// src/core/errors.ts
|
|
74
|
+
function kindOf(status, code) {
|
|
75
|
+
switch (code) {
|
|
76
|
+
case "ABORTED":
|
|
77
|
+
return "aborted";
|
|
78
|
+
case "TIMEOUT":
|
|
79
|
+
case "NETWORK_ERROR":
|
|
80
|
+
return "transient";
|
|
81
|
+
case "BAD_JSON":
|
|
82
|
+
case "BAD_RESPONSE":
|
|
83
|
+
return "protocol";
|
|
84
|
+
case "USAGE":
|
|
85
|
+
return "usage";
|
|
86
|
+
case "CommandFailure":
|
|
87
|
+
return "commandFailure";
|
|
88
|
+
case "QUERY_REJECTED":
|
|
89
|
+
case "TOO_LARGE":
|
|
90
|
+
return "invalid";
|
|
91
|
+
case "NOT_FOUND":
|
|
92
|
+
return "notFound";
|
|
93
|
+
}
|
|
94
|
+
if (status === 401) return "auth";
|
|
95
|
+
if (status === 403) return "forbidden";
|
|
96
|
+
if (status === 404) return "notFound";
|
|
97
|
+
if (status === 408 || status === 429 || status >= 500) return "transient";
|
|
98
|
+
if (status >= 400) return "invalid";
|
|
99
|
+
return "unknown";
|
|
100
|
+
}
|
|
101
|
+
var causeOf = (details) => details && typeof details === "object" && "cause" in details ? details.cause : void 0;
|
|
70
102
|
var ApiError = class extends Error {
|
|
103
|
+
/** The HTTP status; 0 when no answer came back (network, timeout, abort, usage). */
|
|
71
104
|
status;
|
|
72
105
|
code;
|
|
73
106
|
details;
|
|
74
|
-
|
|
75
|
-
|
|
107
|
+
kind;
|
|
108
|
+
constructor(message, status, code, details, kind = kindOf(status, code)) {
|
|
109
|
+
const cause = causeOf(details);
|
|
110
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
76
111
|
this.name = "ApiError";
|
|
77
112
|
this.status = status;
|
|
78
113
|
this.code = code;
|
|
79
114
|
this.details = details;
|
|
115
|
+
this.kind = kind;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var UsageError = class extends ApiError {
|
|
119
|
+
constructor(message, details) {
|
|
120
|
+
super(message, 0, "USAGE", details, "usage");
|
|
121
|
+
this.name = "UsageError";
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var WrappedError = class extends ApiError {
|
|
125
|
+
constructor(message, code, cause, details) {
|
|
126
|
+
const inner = cause instanceof ApiError ? cause : void 0;
|
|
127
|
+
super(message, inner?.status ?? 0, code, { ...details, cause }, inner?.kind ?? "unknown");
|
|
128
|
+
this.name = "WrappedError";
|
|
80
129
|
}
|
|
81
130
|
};
|
|
82
131
|
|
|
@@ -216,7 +265,7 @@ function isOidToken(value) {
|
|
|
216
265
|
}
|
|
217
266
|
function parseOid(token) {
|
|
218
267
|
const m = OID_TOKEN.exec(token);
|
|
219
|
-
if (!m) throw new
|
|
268
|
+
if (!m) throw new UsageError(`"${token}" is not an OID token (Story:1234)`);
|
|
220
269
|
return { type: m[1], id: Number(m[2]), moment: m[3] === void 0 ? null : Number(m[3]) };
|
|
221
270
|
}
|
|
222
271
|
function momentless(token) {
|
|
@@ -240,6 +289,7 @@ __export(assetApi_exports, {
|
|
|
240
289
|
mergeOutcome: () => mergeOutcome,
|
|
241
290
|
momentless: () => momentless,
|
|
242
291
|
outcomeOf: () => outcomeOf,
|
|
292
|
+
parseViolation: () => parseViolation,
|
|
243
293
|
preview: () => preview,
|
|
244
294
|
queries: () => queries,
|
|
245
295
|
query: () => query,
|
|
@@ -405,11 +455,20 @@ function toNumber(value) {
|
|
|
405
455
|
const n = typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
406
456
|
return Number.isNaN(n) ? value : n;
|
|
407
457
|
}
|
|
458
|
+
function parseViolation(message) {
|
|
459
|
+
const parts = message.trim().split("'");
|
|
460
|
+
if (parts[0] !== "Violation" || !parts[1]) return null;
|
|
461
|
+
const rule = parts[1], rest = parts.slice(2).filter((p) => p !== "");
|
|
462
|
+
if (rest[0] === "AttributeDefinition" && rest[1] && rest[2]) return { rule, attribute: `${rest[2]}.${rest[1]}`, tag: rest.join("'") };
|
|
463
|
+
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;
|
|
464
|
+
return { rule, attribute: token, tag: rest.length ? rest.join("'") : null };
|
|
465
|
+
}
|
|
408
466
|
function failuresOf(response) {
|
|
409
467
|
return (response.commandFailures?.commands ?? []).map(({ error, ...echoed }) => ({
|
|
410
468
|
index: typeof error === "object" && error && typeof error.sourceCommandIndex === "number" ? error.sourceCommandIndex : null,
|
|
411
469
|
message: typeof error === "string" ? error : error?.message || "command failed",
|
|
412
|
-
echoed
|
|
470
|
+
echoed,
|
|
471
|
+
violation: parseViolation(typeof error === "string" ? error : error?.message || "")
|
|
413
472
|
}));
|
|
414
473
|
}
|
|
415
474
|
function outcomeOf(response) {
|
|
@@ -435,11 +494,14 @@ function mergeOutcome(into, done, offset) {
|
|
|
435
494
|
var CommandFailureError = class extends ApiError {
|
|
436
495
|
failures;
|
|
437
496
|
response;
|
|
497
|
+
/** What the batch did, in the model's terms: the commands that succeeded are committed. */
|
|
498
|
+
outcome;
|
|
438
499
|
constructor(response, failures = failuresOf(response)) {
|
|
439
500
|
super(failures.map((f) => f.message).join("; ") || "command failed", 200, "CommandFailure", failures);
|
|
440
501
|
this.name = "CommandFailureError";
|
|
441
502
|
this.failures = failures;
|
|
442
503
|
this.response = response;
|
|
504
|
+
this.outcome = outcomeOf(response);
|
|
443
505
|
}
|
|
444
506
|
};
|
|
445
507
|
var bodyOf = (c) => {
|
|
@@ -475,7 +537,7 @@ async function send(transport, commands, options = {}) {
|
|
|
475
537
|
const results = response.queryResult?.results ?? [];
|
|
476
538
|
let aligned = realign(queries3, results, failed);
|
|
477
539
|
if (!aligned) {
|
|
478
|
-
if (!failures.some((f) => f.index === null)) throw new
|
|
540
|
+
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);
|
|
479
541
|
aligned = queries3.map(() => null);
|
|
480
542
|
}
|
|
481
543
|
const contextOf = (i) => {
|
|
@@ -497,7 +559,7 @@ async function query(transport, q, options = {}) {
|
|
|
497
559
|
return rows ?? [];
|
|
498
560
|
}
|
|
499
561
|
async function queryPage(transport, q, options = {}) {
|
|
500
|
-
if (!q.page) throw new
|
|
562
|
+
if (!q.page) throw new UsageError(`query of ${q.from} without a page: every api/asset read must be bounded`);
|
|
501
563
|
const response = await send(transport, [{ ...q, needTotal: true }], { ...options, throwOnFailure: true });
|
|
502
564
|
const rows = response.queryResult.results[0] ?? [];
|
|
503
565
|
const reported = response.queryResult.resultMetaData?.[0]?.total;
|
|
@@ -505,7 +567,7 @@ async function queryPage(transport, q, options = {}) {
|
|
|
505
567
|
return { rows, total, more: total === null ? rows.length === q.page.size : q.page.start + rows.length < total };
|
|
506
568
|
}
|
|
507
569
|
async function queries(transport, qs, options = {}) {
|
|
508
|
-
for (const q of qs) if (!q.page) throw new
|
|
570
|
+
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`);
|
|
509
571
|
const response = await send(transport, qs, { ...options, throwOnFailure: true });
|
|
510
572
|
return qs.map((_, i) => response.queryResult.results[i] ?? []);
|
|
511
573
|
}
|
|
@@ -543,7 +605,7 @@ function fromBase64(text) {
|
|
|
543
605
|
}
|
|
544
606
|
function numericId(spec) {
|
|
545
607
|
const m = /^(?:[A-Za-z]+:)?(\d+)(?::\d+)?$/.exec(String(spec).trim());
|
|
546
|
-
if (!m) throw new
|
|
608
|
+
if (!m) throw new UsageError(`"${spec}" is not an asset id (5678, or an OID token such as Attachment:5678)`);
|
|
547
609
|
return m[1];
|
|
548
610
|
}
|
|
549
611
|
var join = (baseUrl, path) => `${baseUrl.replace(/\/+$/, "")}/${path}`;
|
|
@@ -639,9 +701,9 @@ async function get(transport, id) {
|
|
|
639
701
|
const file = await getFile(transport, meta.id);
|
|
640
702
|
return { ...meta, bytes: file.bytes, contentType: file.contentType ?? meta.contentType, recordedContentType: meta.contentType };
|
|
641
703
|
}
|
|
642
|
-
var AttachmentUploadError = class extends
|
|
704
|
+
var AttachmentUploadError = class extends WrappedError {
|
|
643
705
|
constructor(attachment, cause) {
|
|
644
|
-
super(`${attachment.oid} was created on its asset with no content, and uploading the file failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
706
|
+
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);
|
|
645
707
|
this.attachment = attachment;
|
|
646
708
|
this.name = "AttachmentUploadError";
|
|
647
709
|
}
|
|
@@ -674,7 +736,7 @@ async function remove(transport, id) {
|
|
|
674
736
|
// src/services/guard.ts
|
|
675
737
|
var CHUNK = 100;
|
|
676
738
|
var UNGUARDABLE_TYPES = ["Idea", "IdeaChangeSet", "IdeaStatus", "IdeaComment", "IdeaCategory", "IdeaForum", "IdeaUser"];
|
|
677
|
-
var UnguardableError = class extends
|
|
739
|
+
var UnguardableError = class extends UsageError {
|
|
678
740
|
reasons;
|
|
679
741
|
constructor(reasons) {
|
|
680
742
|
super(`cannot guard this batch: ${reasons.map((r) => `command ${r.index}: ${r.reason}`).join("; ")}`);
|
|
@@ -740,7 +802,7 @@ function guard(projected, stamps) {
|
|
|
740
802
|
unstamped.push(x.oid);
|
|
741
803
|
continue;
|
|
742
804
|
}
|
|
743
|
-
if (!STAMP.test(stamp)) throw new
|
|
805
|
+
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 });
|
|
744
806
|
const scope = { from: typeOfOid(x.oid), filter: ["ID=$oid", `ChangeDate='${stamp}'`], with: { $oid: x.oid } };
|
|
745
807
|
commands.push(x.update ? { ...scope, update: x.update } : { ...scope, execute: x.execute });
|
|
746
808
|
}
|
|
@@ -757,16 +819,14 @@ function passesOf(projected) {
|
|
|
757
819
|
}
|
|
758
820
|
return passes;
|
|
759
821
|
}
|
|
760
|
-
var GuardedPassError = class extends
|
|
822
|
+
var GuardedPassError = class extends WrappedError {
|
|
761
823
|
constructor(cause, outcome, sent, pass) {
|
|
762
|
-
super(`guarded send failed in pass ${pass + 1}${pass ? " (earlier passes applied)" : ""}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
763
|
-
this.cause = cause;
|
|
824
|
+
super(`guarded send failed in pass ${pass + 1}${pass ? " (earlier passes applied)" : ""}: ${cause instanceof Error ? cause.message : String(cause)}`, "GUARDED_PASS", cause);
|
|
764
825
|
this.outcome = outcome;
|
|
765
826
|
this.sent = sent;
|
|
766
827
|
this.pass = pass;
|
|
767
828
|
this.name = "GuardedPassError";
|
|
768
829
|
}
|
|
769
|
-
cause;
|
|
770
830
|
outcome;
|
|
771
831
|
sent;
|
|
772
832
|
pass;
|
|
@@ -889,9 +949,46 @@ async function snapshot(transport, described) {
|
|
|
889
949
|
}
|
|
890
950
|
return before;
|
|
891
951
|
}
|
|
892
|
-
var INVERSE = { Inactivate: "Reactivate", Reactivate: "Inactivate", Delete: "Undelete", Undelete: "Delete" };
|
|
893
|
-
|
|
952
|
+
var INVERSE = { Inactivate: "Reactivate", QuickClose: "Reactivate", Reactivate: "Inactivate", Delete: "Undelete", Undelete: "Delete" };
|
|
953
|
+
var CASCADING = /* @__PURE__ */ new Set(["Inactivate", "QuickClose"]);
|
|
954
|
+
var CASCADE_DEPTH = 8;
|
|
955
|
+
async function openDescendantsOf(transport, schema, roots) {
|
|
956
|
+
const out = {};
|
|
957
|
+
const rootOf = new Map(roots.map((r) => [momentless(r), momentless(r)]));
|
|
958
|
+
const seen = new Set(rootOf.keys());
|
|
959
|
+
let frontier = [...rootOf.keys()];
|
|
960
|
+
for (let depth = 0; depth < CASCADE_DEPTH && frontier.length; depth += 1) {
|
|
961
|
+
const children = /* @__PURE__ */ new Map();
|
|
962
|
+
for (const [type, list3] of byType(frontier)) {
|
|
963
|
+
await schema.load(type);
|
|
964
|
+
if (!schema.attribute(type, "Children")) continue;
|
|
965
|
+
for (const part of chunks(list3)) {
|
|
966
|
+
const rows = await query(transport, { from: type, select: ["Children"], filter: ["ID=$oids"], with: { $oids: part }, page: { size: part.length, start: 0 } });
|
|
967
|
+
for (const r of rows) {
|
|
968
|
+
const parent = momentless(r._oid);
|
|
969
|
+
for (const c of [].concat(plain(r.Children) ?? [])) {
|
|
970
|
+
const child = momentless(String(c));
|
|
971
|
+
if (seen.has(child)) continue;
|
|
972
|
+
seen.add(child);
|
|
973
|
+
children.set(child, rootOf.get(parent));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
const states = children.size ? await statesOf(transport, [...children.keys()]) : {};
|
|
979
|
+
frontier = [];
|
|
980
|
+
for (const [child, root] of children) {
|
|
981
|
+
if (states[child] === void 0 || isClosed(states[child])) continue;
|
|
982
|
+
rootOf.set(child, root);
|
|
983
|
+
(out[root] ??= []).push(child);
|
|
984
|
+
frontier.push(child);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return out;
|
|
988
|
+
}
|
|
989
|
+
function reverseOf(described, before, done, context = {}) {
|
|
894
990
|
const commands = [], notes = [];
|
|
991
|
+
const cannot = (oid, op) => context.canOperate?.(typeOfOid(oid), op) === false;
|
|
895
992
|
const modified = new Set(done.modified.map(momentless)), operated = new Set(done.operated.map(momentless));
|
|
896
993
|
for (const u of described.updates) {
|
|
897
994
|
if (!modified.has(u.oid)) continue;
|
|
@@ -916,26 +1013,48 @@ function reverseOf(described, before, done) {
|
|
|
916
1013
|
for (const e of described.executes) {
|
|
917
1014
|
if (!operated.has(e.oid)) continue;
|
|
918
1015
|
const inverse = INVERSE[e.op];
|
|
919
|
-
if (inverse)
|
|
920
|
-
|
|
1016
|
+
if (!inverse) {
|
|
1017
|
+
notes.push(`${e.oid}: ${e.op} has no known inverse (was ${String(before[e.oid]?.AssetState ?? "?")})`);
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (cannot(e.oid, inverse)) {
|
|
1021
|
+
notes.push(`${e.oid}: ${typeOfOid(e.oid)} has no ${inverse} operation, so its ${e.op} is not reversed`);
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
commands.push({ from: e.oid, execute: inverse });
|
|
1025
|
+
if (!CASCADING.has(e.op)) continue;
|
|
1026
|
+
const open = context.openDescendants?.[e.oid] ?? [];
|
|
1027
|
+
const closed = context.cascaded ? context.cascaded[e.oid] ?? [] : void 0;
|
|
1028
|
+
if (closed === void 0) {
|
|
1029
|
+
if (open.length) notes.push(`${e.oid}: ${e.op} may also have closed ${open.length} open descendant${open.length === 1 ? "" : "s"} (${open.join(", ")}); they could not be read back, so they are not reopened`);
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
for (const d of closed) {
|
|
1033
|
+
if (cannot(d, "Reactivate")) notes.push(`${d}: closed with ${e.oid}, but ${typeOfOid(d)} has no Reactivate operation`);
|
|
1034
|
+
else commands.push({ from: d, execute: "Reactivate" });
|
|
1035
|
+
}
|
|
1036
|
+
if (closed.length) notes.push(`${e.oid}: ${e.op} also closed ${closed.join(", ")}; reopened after it`);
|
|
1037
|
+
}
|
|
1038
|
+
for (const oid of [...done.created].reverse().map(momentless)) {
|
|
1039
|
+
if (cannot(oid, "Delete")) notes.push(`${oid}: ${typeOfOid(oid)} has no Delete operation, so it is not deleted${typeOfOid(oid) === "Conversation" ? " (the server removes a Conversation once its last Expression is deleted)" : ""}`);
|
|
1040
|
+
else commands.push({ from: oid, execute: "Delete" });
|
|
921
1041
|
}
|
|
922
|
-
for (const oid of [...done.created].reverse()) commands.push({ from: momentless(oid), execute: "Delete" });
|
|
923
1042
|
for (const d of described.deferred ?? []) notes.push(`command ${d.index} (${d.alias}): an aliased ${d.kind} of ${d.from}, sent as written with no snapshot; not reversed`);
|
|
924
1043
|
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");
|
|
925
1044
|
return { commands, notes };
|
|
926
1045
|
}
|
|
927
|
-
var BatchRecordError = class extends
|
|
1046
|
+
var BatchRecordError = class extends WrappedError {
|
|
928
1047
|
receipt;
|
|
929
1048
|
constructor(cause, receipt) {
|
|
930
|
-
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)}`,
|
|
1049
|
+
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);
|
|
931
1050
|
this.name = "BatchRecordError";
|
|
932
1051
|
this.receipt = receipt;
|
|
933
1052
|
}
|
|
934
1053
|
};
|
|
935
|
-
var BatchSendError = class extends
|
|
1054
|
+
var BatchSendError = class extends WrappedError {
|
|
936
1055
|
receipt;
|
|
937
1056
|
constructor(cause, receipt) {
|
|
938
|
-
super(`the batch send failed; whether the server applied it is unknown: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1057
|
+
super(`the batch send failed; whether the server applied it is unknown: ${cause instanceof Error ? cause.message : String(cause)}`, "BATCH_SEND", cause);
|
|
939
1058
|
this.name = "BatchSendError";
|
|
940
1059
|
this.receipt = receipt;
|
|
941
1060
|
}
|
|
@@ -943,7 +1062,7 @@ var BatchSendError = class extends Error {
|
|
|
943
1062
|
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
944
1063
|
async function runBatch(transport, schema, commands, options = {}) {
|
|
945
1064
|
const useGuard = options.guard ?? true;
|
|
946
|
-
if (!commands.length) throw new
|
|
1065
|
+
if (!commands.length) throw new UsageError("the batch is empty");
|
|
947
1066
|
const refused = unguardable(commands);
|
|
948
1067
|
const fatal = refused.filter((r) => r.reason.includes("rejected by the server"));
|
|
949
1068
|
if (fatal.length || useGuard && refused.length) throw new UnguardableError(useGuard ? refused : fatal);
|
|
@@ -954,12 +1073,14 @@ async function runBatch(transport, schema, commands, options = {}) {
|
|
|
954
1073
|
const described = describe(commands, projected);
|
|
955
1074
|
const touched = touchedBy(projected);
|
|
956
1075
|
const names = await namesOf(transport, schema, touched, noteFailure);
|
|
957
|
-
const receipt = { receipt: 1, request: commands, previewedAt, preview: projected, described, names: Object.fromEntries(names), before: {}, sent: false, guarded: false, created: [], touched: [], after: {}, skipped: [], reverse: { commands: [], notes: [] }, notes };
|
|
1076
|
+
const receipt = { receipt: 1, request: commands, previewedAt, preview: projected, described, names: Object.fromEntries(names), before: {}, sent: false, guarded: false, created: [], touched: [], after: {}, skipped: [], failed: [], openDescendants: {}, cascaded: {}, reverse: { commands: [], notes: [] }, notes };
|
|
958
1077
|
const nothing = !described.creates.length && !described.updates.length && !described.executes.length && !described.queries.length && !described.deferred.length;
|
|
959
1078
|
if (nothing) return receipt;
|
|
960
1079
|
for (const d of described.deferred) notes.push(`command ${d.index} (${d.alias}): an aliased ${d.kind}, which preview does not expand; sent as written, not snapshotted, not reversible`);
|
|
961
1080
|
receipt.snapshotAt = now();
|
|
962
1081
|
receipt.before = await snapshot(transport, described);
|
|
1082
|
+
const closing = described.executes.filter((e) => CASCADING.has(e.op)).map((e) => e.oid);
|
|
1083
|
+
if (closing.length) receipt.openDescendants = await openDescendantsOf(transport, schema, closing);
|
|
963
1084
|
if (options.dryRun) return receipt;
|
|
964
1085
|
if (options.confirm && !await options.confirm(receipt)) return receipt;
|
|
965
1086
|
let unstamped = [];
|
|
@@ -991,13 +1112,29 @@ async function runBatch(transport, schema, commands, options = {}) {
|
|
|
991
1112
|
const unnamed = { number: null, name: null };
|
|
992
1113
|
receipt.created = done.created.map((oid, i) => ({ alias: described.creates[i]?.alias ?? null, type: described.creates[i]?.type ?? typeOfOid(oid), oid, ...unnamed }));
|
|
993
1114
|
receipt.touched = hit.map((oid) => ({ oid, ...names.get(oid) ?? unnamed }));
|
|
994
|
-
|
|
995
|
-
|
|
1115
|
+
const sentAs = receipt.sentCommands ?? [];
|
|
1116
|
+
const oidOfSent = (i) => {
|
|
1117
|
+
const body = i === null ? void 0 : sentAs[i] ? bodyOf3(sentAs[i]) : void 0;
|
|
1118
|
+
const id = body?.with?.$oid ?? body?.from;
|
|
1119
|
+
return typeof id === "string" && id.includes(":") ? momentless(id) : null;
|
|
1120
|
+
};
|
|
1121
|
+
receipt.failed = [...new Set(done.failures.map((f) => oidOfSent(f.index)).filter((oid) => !!oid && touched.includes(oid) && !hit.includes(oid)))];
|
|
1122
|
+
receipt.skipped = useGuard ? [.../* @__PURE__ */ new Set([...unstamped, ...touched.filter((oid) => !hit.includes(oid) && !receipt.failed.includes(oid))])] : [];
|
|
1123
|
+
for (const type of new Set([...done.created, ...hit].map((oid) => typeOfOid(momentless(oid))))) await schema.load(type).catch(() => null);
|
|
1124
|
+
const canOperate = (type, op) => schema.canOperate(type, op);
|
|
1125
|
+
const operatedNow = new Set(done.operated.map(momentless));
|
|
1126
|
+
const closed = Object.keys(receipt.openDescendants).filter((oid) => operatedNow.has(oid) && receipt.openDescendants[oid].length);
|
|
1127
|
+
receipt.reverse = reverseOf(described, receipt.before, done, { openDescendants: receipt.openDescendants, cascaded: closed.length ? void 0 : {}, canOperate });
|
|
996
1128
|
try {
|
|
1129
|
+
if (closed.length) {
|
|
1130
|
+
const states = await statesOf(transport, closed.flatMap((oid) => receipt.openDescendants[oid]));
|
|
1131
|
+
receipt.cascaded = Object.fromEntries(closed.map((oid) => [oid, receipt.openDescendants[oid].filter((d) => isClosed(states[d]))]));
|
|
1132
|
+
receipt.reverse = reverseOf(described, receipt.before, done, { openDescendants: receipt.openDescendants, cascaded: receipt.cascaded, canOperate });
|
|
1133
|
+
}
|
|
997
1134
|
const more = await namesOf(transport, schema, [...done.created, ...hit.filter((oid) => !names.has(oid))], noteFailure);
|
|
998
1135
|
receipt.created = receipt.created.map((c) => ({ ...c, ...more.get(c.oid) ?? unnamed }));
|
|
999
1136
|
receipt.touched = receipt.touched.map((t) => names.has(t.oid) ? t : { ...t, ...more.get(t.oid) ?? unnamed });
|
|
1000
|
-
const all = [...done.created, ...hit];
|
|
1137
|
+
const all = [.../* @__PURE__ */ new Set([...done.created, ...hit, ...Object.values(receipt.cascaded).flat()])];
|
|
1001
1138
|
receipt.after = all.length ? await stampsOf(transport, all) : {};
|
|
1002
1139
|
} catch (error) {
|
|
1003
1140
|
throw new BatchRecordError(error, receipt);
|
|
@@ -1096,7 +1233,7 @@ async function resolveNumbers(transport, numbers, options = {}) {
|
|
|
1096
1233
|
for (const n of numbers) if (!firsts.has(n.trim().toUpperCase())) firsts.set(n.trim().toUpperCase(), n.trim());
|
|
1097
1234
|
const asked = [...firsts.values()];
|
|
1098
1235
|
const invalid = asked.filter((n) => !isAssetNumber(n));
|
|
1099
|
-
if (invalid.length) throw new
|
|
1236
|
+
if (invalid.length) throw new UsageError(`not asset numbers (expected a prefix and digits, S-1234): ${invalid.join(", ")}`);
|
|
1100
1237
|
const byNumber = /* @__PURE__ */ new Map();
|
|
1101
1238
|
for (let i = 0; i < asked.length; i += CHUNK3) {
|
|
1102
1239
|
for (const r of await askRoots(transport, asked.slice(i, i + CHUNK3), options.types)) {
|
|
@@ -1117,7 +1254,7 @@ async function resolveRef(transport, spec, options = {}) {
|
|
|
1117
1254
|
return { number: null, oid, type: typeOfOid(oid), name: null, assetState: null };
|
|
1118
1255
|
}
|
|
1119
1256
|
if (isAssetNumber(text)) return (await resolveNumbers(transport, [text], options)).found[0] ?? null;
|
|
1120
|
-
throw new
|
|
1257
|
+
throw new UsageError(`"${spec}" is neither an asset number (S-1234) nor an OID token (Story:1234)`);
|
|
1121
1258
|
}
|
|
1122
1259
|
|
|
1123
1260
|
// src/wire/meta.ts
|
|
@@ -1161,7 +1298,8 @@ function toAssetType(doc) {
|
|
|
1161
1298
|
...doc.DefaultOrderBy ? { DefaultOrderBy: doc.DefaultOrderBy.tokenref } : {},
|
|
1162
1299
|
...doc.DefaultDisplayBy ? { DefaultDisplayBy: doc.DefaultDisplayBy.tokenref } : {},
|
|
1163
1300
|
...doc.ShortName ? { ShortName: doc.ShortName.tokenref } : {},
|
|
1164
|
-
Attributes: Object.fromEntries(Object.entries(doc.Attributes ?? {}).map(([key, value]) => [key, toAttributeDefinition(value)]))
|
|
1301
|
+
Attributes: Object.fromEntries(Object.entries(doc.Attributes ?? {}).map(([key, value]) => [key, toAttributeDefinition(value)])),
|
|
1302
|
+
...doc.Operations ? { Operations: Object.keys(doc.Operations).map((token) => token.slice(token.indexOf(".") + 1)) } : {}
|
|
1165
1303
|
};
|
|
1166
1304
|
}
|
|
1167
1305
|
function toMetamodel(doc) {
|
|
@@ -1178,7 +1316,7 @@ async function getAssetType(transport, token) {
|
|
|
1178
1316
|
}
|
|
1179
1317
|
async function getAttributeDefinition(transport, tokenref) {
|
|
1180
1318
|
const [type, attribute] = tokenref.split(".");
|
|
1181
|
-
if (!type || !attribute) throw new
|
|
1319
|
+
if (!type || !attribute) throw new UsageError(`"${tokenref}" is not an attribute token ("Story.Name")`);
|
|
1182
1320
|
return toAttributeDefinition(await transport.request({ method: "GET", path: `/meta.v1/${encodeURIComponent(type)}/${encodeURIComponent(attribute)}`, responseType: "json" }));
|
|
1183
1321
|
}
|
|
1184
1322
|
|
|
@@ -1225,6 +1363,15 @@ var Schema = class _Schema {
|
|
|
1225
1363
|
attribute(type, name) {
|
|
1226
1364
|
return attributeOf(this, type, name);
|
|
1227
1365
|
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Can the type do this operation (Delete, Reactivate, …)? Undefined when
|
|
1368
|
+
* the type is not loaded or its definition carries no operations, so a
|
|
1369
|
+
* caller can tell "no" from "don't know".
|
|
1370
|
+
*/
|
|
1371
|
+
canOperate(type, op) {
|
|
1372
|
+
const t = this.assetType(type);
|
|
1373
|
+
return t?.Operations ? t.Operations.includes(op) : void 0;
|
|
1374
|
+
}
|
|
1228
1375
|
/** The tokens loaded so far. */
|
|
1229
1376
|
get loaded() {
|
|
1230
1377
|
return [...this.types.entries()].filter(([, t]) => t !== null).map(([token]) => token);
|
|
@@ -1296,7 +1443,7 @@ async function localize(transport, tags, options = {}) {
|
|
|
1296
1443
|
const encoded = encodeURIComponent(toLson(unique));
|
|
1297
1444
|
const headers = options.culture ? { "Accept-Language": options.culture } : void 0;
|
|
1298
1445
|
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 });
|
|
1299
|
-
if (!result || typeof result !== "object" || Array.isArray(result)) throw new
|
|
1446
|
+
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);
|
|
1300
1447
|
return result;
|
|
1301
1448
|
}
|
|
1302
1449
|
async function localizeOne(transport, tag, options = {}) {
|
|
@@ -1316,7 +1463,7 @@ function rejected(error) {
|
|
|
1316
1463
|
return messages.length ? new ApiError(messages.join("; "), error.status, "QUERY_REJECTED", error.details) : error;
|
|
1317
1464
|
}
|
|
1318
1465
|
async function queries2(transport, qs, options = {}) {
|
|
1319
|
-
for (const q of qs) if (!q.page) throw new
|
|
1466
|
+
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`);
|
|
1320
1467
|
if (!qs.length) return [];
|
|
1321
1468
|
let response;
|
|
1322
1469
|
try {
|
|
@@ -1377,8 +1524,8 @@ function fromAssetDocument(doc, { history: history2 = false } = {}) {
|
|
|
1377
1524
|
}
|
|
1378
1525
|
function toQuery(params) {
|
|
1379
1526
|
return {
|
|
1380
|
-
sel: params.sel?.join(","),
|
|
1381
|
-
where: params.where?.length ? params.where.join(";") : void 0,
|
|
1527
|
+
sel: (params.select ?? params.sel)?.join(","),
|
|
1528
|
+
where: (params.filter ?? params.where)?.length ? (params.filter ?? params.where).join(";") : void 0,
|
|
1382
1529
|
with: params.with ? Object.entries(params.with).map(([k, v]) => `${k}=${[].concat(v).join(",")}`).join("|") : void 0,
|
|
1383
1530
|
find: params.find,
|
|
1384
1531
|
findin: params.findin?.join(","),
|
|
@@ -1390,14 +1537,14 @@ function toQuery(params) {
|
|
|
1390
1537
|
};
|
|
1391
1538
|
}
|
|
1392
1539
|
async function read(transport, path, params, history2) {
|
|
1393
|
-
if (!params.page) throw new
|
|
1540
|
+
if (!params.page) throw new UsageError(`rest-1 read of ${path} without a page: every read must be bounded`);
|
|
1394
1541
|
const doc = await transport.request({ method: "GET", path, query: toQuery(params), responseType: "json" });
|
|
1395
1542
|
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 };
|
|
1396
1543
|
}
|
|
1397
1544
|
var list2 = (transport, type, params) => read(transport, `/rest-1.v1/Data/${encodeURIComponent(type)}`, params, false);
|
|
1398
|
-
async function get2(transport, oid,
|
|
1545
|
+
async function get2(transport, oid, select) {
|
|
1399
1546
|
const [type, id] = momentless(oid).split(":");
|
|
1400
|
-
const doc = await transport.request({ method: "GET", path: `/rest-1.v1/Data/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, query: { sel:
|
|
1547
|
+
const doc = await transport.request({ method: "GET", path: `/rest-1.v1/Data/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, query: { sel: select?.join(",") }, responseType: "json" });
|
|
1401
1548
|
return fromAssetDocument(doc);
|
|
1402
1549
|
}
|
|
1403
1550
|
var history = (transport, target, params) => {
|
|
@@ -1407,6 +1554,8 @@ var history = (transport, target, params) => {
|
|
|
1407
1554
|
};
|
|
1408
1555
|
|
|
1409
1556
|
// src/client.ts
|
|
1557
|
+
var DEFAULT_LIMIT = 1e3;
|
|
1558
|
+
var ME_SELECT = ["Name", "Username", "Nickname", "Email"];
|
|
1410
1559
|
var AgilityApiClient = class _AgilityApiClient {
|
|
1411
1560
|
transport;
|
|
1412
1561
|
schema;
|
|
@@ -1438,6 +1587,48 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1438
1587
|
const transport = { request: (options) => base.request({ ...options, signal: options.signal ?? signal }) };
|
|
1439
1588
|
return new _AgilityApiClient(this.config, { transport, schema: this.schema });
|
|
1440
1589
|
}
|
|
1590
|
+
async query(q) {
|
|
1591
|
+
await this.schema.ensureFor([q]);
|
|
1592
|
+
return query2(this.transport, q, { lookup: this.schema });
|
|
1593
|
+
}
|
|
1594
|
+
async queries(qs) {
|
|
1595
|
+
await this.schema.ensureFor(qs);
|
|
1596
|
+
return queries2(this.transport, [...qs], { lookup: this.schema });
|
|
1597
|
+
}
|
|
1598
|
+
async get(oid, select) {
|
|
1599
|
+
const rows = await this.query({ from: momentless(oid), ...select ? { select } : {}, page: { size: 1, start: 0 } });
|
|
1600
|
+
return rows[0] ?? null;
|
|
1601
|
+
}
|
|
1602
|
+
async queryPage(q) {
|
|
1603
|
+
await this.schema.ensureFor([q]);
|
|
1604
|
+
return queryPage(this.transport, q, { lookup: this.schema });
|
|
1605
|
+
}
|
|
1606
|
+
async *pages(q, options = {}) {
|
|
1607
|
+
if (!q.page) throw new UsageError(`query of ${q.from} without a page: every read must be bounded`);
|
|
1608
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
1609
|
+
const size = q.page.size;
|
|
1610
|
+
let start = q.page.start, read2 = 0;
|
|
1611
|
+
while (read2 < limit) {
|
|
1612
|
+
const want = Math.min(size, limit - read2);
|
|
1613
|
+
const rows = await this.query({ ...q, page: { size: want, start } });
|
|
1614
|
+
if (rows.length) yield rows;
|
|
1615
|
+
read2 += rows.length;
|
|
1616
|
+
start += rows.length;
|
|
1617
|
+
if (rows.length < want) return;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
async all(q, options = {}) {
|
|
1621
|
+
const out = [];
|
|
1622
|
+
for await (const page2 of this.pages(q, options)) out.push(...page2);
|
|
1623
|
+
return out;
|
|
1624
|
+
}
|
|
1625
|
+
/** The member these credentials belong to (Member.IsSelf, live 2026-11-12): what a comment's Author is. */
|
|
1626
|
+
async me() {
|
|
1627
|
+
const [row] = await this.query({ from: "Member", select: ME_SELECT, filter: ["IsSelf='true'"], page: { size: 1, start: 0 } });
|
|
1628
|
+
if (!row) throw new ApiError("the server knows no member for these credentials", 0, "NOT_FOUND");
|
|
1629
|
+
return row;
|
|
1630
|
+
}
|
|
1631
|
+
// ---- the endpoints behind the reads -----------------------------------------------
|
|
1441
1632
|
/** meta.v1: the instance's schema, raw. (The translated, cached view is `schema`.) */
|
|
1442
1633
|
meta = {
|
|
1443
1634
|
metamodel: () => getMetamodel(this.transport),
|
|
@@ -1446,12 +1637,65 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1446
1637
|
};
|
|
1447
1638
|
/**
|
|
1448
1639
|
* loc-2.v1: labels for tags, in one request (meta's DisplayName values are
|
|
1449
|
-
* tags). An unmatched tag comes back as
|
|
1450
|
-
* "missing", so neither does this.
|
|
1640
|
+
* tags, and so is a violation's `tag`). An unmatched tag comes back as
|
|
1641
|
+
* itself; the server never says "missing", so neither does this.
|
|
1451
1642
|
*/
|
|
1452
1643
|
localize(tags, options) {
|
|
1453
1644
|
return localize(this.transport, tags, options);
|
|
1454
1645
|
}
|
|
1646
|
+
/**
|
|
1647
|
+
* rest-1.v1, for what query.v1 cannot do: deleted assets (`deleted:
|
|
1648
|
+
* true`), one asset by OID without a page, and history (one row per
|
|
1649
|
+
* moment). Parameters are spelled as a query's (`select`, `filter`).
|
|
1650
|
+
*/
|
|
1651
|
+
data = {
|
|
1652
|
+
list: (type, params) => list2(this.transport, type, params),
|
|
1653
|
+
get: (oid, select) => get2(this.transport, oid, select),
|
|
1654
|
+
history: (target, params) => history(this.transport, target, params)
|
|
1655
|
+
};
|
|
1656
|
+
/** @deprecated since 1.0.0-alpha.3: `client.query` / `client.queries` read through query.v1. Removed in 1.0.0. */
|
|
1657
|
+
queryV1 = {
|
|
1658
|
+
query: (q) => this.query(q),
|
|
1659
|
+
queries: (qs) => this.queries(qs)
|
|
1660
|
+
};
|
|
1661
|
+
// ---- writes -----------------------------------------------------------------------
|
|
1662
|
+
/** api/asset: batches, answered in the model's terms. */
|
|
1663
|
+
assets = {
|
|
1664
|
+
/**
|
|
1665
|
+
* Send a batch. The Outcome says what it did — created, modified and
|
|
1666
|
+
* operated OIDs, failures by index (each with its parsed violation),
|
|
1667
|
+
* query rows per query command — and never the envelope. api/asset
|
|
1668
|
+
* commits per command and answers 200 either way, so a failed command
|
|
1669
|
+
* is in `failures`, or thrown as a CommandFailureError with
|
|
1670
|
+
* `throwOnFailure` (the error carries the same Outcome).
|
|
1671
|
+
*/
|
|
1672
|
+
send: async (commands, options) => {
|
|
1673
|
+
await this.schema.ensureFor(commands);
|
|
1674
|
+
return outcomeOf(await send(this.transport, commands, { ...options, lookup: this.schema }));
|
|
1675
|
+
},
|
|
1676
|
+
/** The server's projection of a batch: one command per asset it would touch, nothing applied. */
|
|
1677
|
+
preview: (commands) => preview(this.transport, commands),
|
|
1678
|
+
/**
|
|
1679
|
+
* Send as a compare-and-set: each asset is changed only if nobody has
|
|
1680
|
+
* changed it since the preview read it; the rest come back `skipped`.
|
|
1681
|
+
* Throws UnguardableError, before any request, for shapes it cannot
|
|
1682
|
+
* cover.
|
|
1683
|
+
*/
|
|
1684
|
+
sendGuarded: async (commands, options) => {
|
|
1685
|
+
await this.schema.ensureFor(commands);
|
|
1686
|
+
return sendGuarded(this.transport, commands, { ...options, lookup: this.schema });
|
|
1687
|
+
},
|
|
1688
|
+
/** @deprecated since 1.0.0-alpha.3: use `client.query` (or `client.queryPage` for a total). Removed in 1.0.0. */
|
|
1689
|
+
query: async (q) => {
|
|
1690
|
+
await this.schema.ensureFor([q]);
|
|
1691
|
+
return query(this.transport, q, { lookup: this.schema });
|
|
1692
|
+
},
|
|
1693
|
+
/** @deprecated since 1.0.0-alpha.3: use `client.queries`. Removed in 1.0.0. */
|
|
1694
|
+
queries: async (qs) => {
|
|
1695
|
+
await this.schema.ensureFor(qs);
|
|
1696
|
+
return queries(this.transport, qs, { lookup: this.schema });
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1455
1699
|
/**
|
|
1456
1700
|
* Services: what a person types, turned into assets. Numbers resolve in
|
|
1457
1701
|
* one api/asset request across every number root; an OID token is taken
|
|
@@ -1474,7 +1718,7 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1474
1718
|
/**
|
|
1475
1719
|
* The files attached to assets: metadata on Attachment assets, bytes
|
|
1476
1720
|
* through attachment.img. `add` sends a small file inline (one request)
|
|
1477
|
-
* and a large one in two steps
|
|
1721
|
+
* and a large one in two steps.
|
|
1478
1722
|
*/
|
|
1479
1723
|
attachments = {
|
|
1480
1724
|
list: (asset, options) => list(this.transport, asset, options),
|
|
@@ -1484,7 +1728,8 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1484
1728
|
add: (asset, data, options) => add(this.transport, asset, data, options),
|
|
1485
1729
|
put: (id, data, contentType) => put(this.transport, id, data, contentType),
|
|
1486
1730
|
remove: (id) => remove(this.transport, id),
|
|
1487
|
-
|
|
1731
|
+
/** @deprecated since 1.0.0-alpha.3: `client.urls.attachment`. Removed in 1.0.0. */
|
|
1732
|
+
url: (id) => this.urls.attachment(id)
|
|
1488
1733
|
};
|
|
1489
1734
|
/**
|
|
1490
1735
|
* Images embedded in an asset's rich text (Description and other long
|
|
@@ -1494,55 +1739,16 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1494
1739
|
*/
|
|
1495
1740
|
embeddedImages = {
|
|
1496
1741
|
upload: (asset, data, options) => uploadEmbeddedImage(this.transport, asset, data, options),
|
|
1497
|
-
|
|
1742
|
+
/** @deprecated since 1.0.0-alpha.3: `client.urls.embeddedImage`. Removed in 1.0.0. */
|
|
1743
|
+
url: (id) => this.urls.embeddedImage(id),
|
|
1498
1744
|
normalize: (html) => normalizeEmbeddedImageUrls(html, this.config.baseUrl)
|
|
1499
1745
|
};
|
|
1500
|
-
/**
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
/**
|
|
1507
|
-
* query.v1: reads only, the same query bodies as assets.query, rows
|
|
1508
|
-
* translated the same way. The endpoint cannot write, so it is the safe
|
|
1509
|
-
* place to explore a query; its requests may be retried like a GET.
|
|
1510
|
-
*/
|
|
1511
|
-
queryV1 = {
|
|
1512
|
-
query: async (q) => {
|
|
1513
|
-
await this.schema.ensureFor([q]);
|
|
1514
|
-
return query2(this.transport, q, { lookup: this.schema });
|
|
1515
|
-
},
|
|
1516
|
-
queries: async (qs) => {
|
|
1517
|
-
await this.schema.ensureFor(qs);
|
|
1518
|
-
return queries2(this.transport, qs, { lookup: this.schema });
|
|
1519
|
-
}
|
|
1520
|
-
};
|
|
1521
|
-
/** api/asset: batches and queries, rows translated through the schema. */
|
|
1522
|
-
assets = {
|
|
1523
|
-
send: async (commands, options) => {
|
|
1524
|
-
await this.schema.ensureFor(commands);
|
|
1525
|
-
return send(this.transport, commands, { ...options, lookup: this.schema });
|
|
1526
|
-
},
|
|
1527
|
-
preview: (commands) => preview(this.transport, commands),
|
|
1528
|
-
/**
|
|
1529
|
-
* Send as a compare-and-set: each asset is changed only if nobody has
|
|
1530
|
-
* changed it since the preview read it; the rest come back `skipped`.
|
|
1531
|
-
* Throws UnguardableError, before any request, for shapes it cannot
|
|
1532
|
-
* cover.
|
|
1533
|
-
*/
|
|
1534
|
-
sendGuarded: async (commands, options) => {
|
|
1535
|
-
await this.schema.ensureFor(commands);
|
|
1536
|
-
return sendGuarded(this.transport, commands, { ...options, lookup: this.schema });
|
|
1537
|
-
},
|
|
1538
|
-
query: async (q) => {
|
|
1539
|
-
await this.schema.ensureFor([q]);
|
|
1540
|
-
return query(this.transport, q, { lookup: this.schema });
|
|
1541
|
-
},
|
|
1542
|
-
queries: async (qs) => {
|
|
1543
|
-
await this.schema.ensureFor(qs);
|
|
1544
|
-
return queries(this.transport, qs, { lookup: this.schema });
|
|
1545
|
-
}
|
|
1746
|
+
/** Where a browser opens things on this instance: an asset's page, an attachment's bytes, an embedded image. */
|
|
1747
|
+
urls = {
|
|
1748
|
+
/** The asset's page: assetdetail.v1 redirects to the type's own detail page (live 2026-11-12). */
|
|
1749
|
+
asset: (oid) => `${this.config.baseUrl.replace(/\/+$/, "")}/assetdetail.v1?oid=${momentless(oid)}`,
|
|
1750
|
+
attachment: (id) => attachmentUrl(this.config.baseUrl, id),
|
|
1751
|
+
embeddedImage: (id) => embeddedImageUrl(this.config.baseUrl, id)
|
|
1546
1752
|
};
|
|
1547
1753
|
};
|
|
1548
1754
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1562,6 +1768,8 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1562
1768
|
TypeCache,
|
|
1563
1769
|
UNGUARDABLE_TYPES,
|
|
1564
1770
|
UnguardableError,
|
|
1771
|
+
UsageError,
|
|
1772
|
+
WrappedError,
|
|
1565
1773
|
assetApi,
|
|
1566
1774
|
assetStateOf,
|
|
1567
1775
|
attachment,
|
|
@@ -1570,11 +1778,13 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1570
1778
|
guard,
|
|
1571
1779
|
isAssetNumber,
|
|
1572
1780
|
isOidToken,
|
|
1781
|
+
kindOf,
|
|
1573
1782
|
loc2,
|
|
1574
1783
|
meta,
|
|
1575
1784
|
momentless,
|
|
1576
1785
|
namesOf,
|
|
1577
1786
|
parseOid,
|
|
1787
|
+
parseViolation,
|
|
1578
1788
|
queryV1,
|
|
1579
1789
|
resolveNumbers,
|
|
1580
1790
|
resolveRef,
|