@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.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;
|
|
@@ -827,9 +883,46 @@ async function snapshot(transport, described) {
|
|
|
827
883
|
}
|
|
828
884
|
return before;
|
|
829
885
|
}
|
|
830
|
-
var INVERSE = { Inactivate: "Reactivate", Reactivate: "Inactivate", Delete: "Undelete", Undelete: "Delete" };
|
|
831
|
-
|
|
886
|
+
var INVERSE = { Inactivate: "Reactivate", QuickClose: "Reactivate", Reactivate: "Inactivate", Delete: "Undelete", Undelete: "Delete" };
|
|
887
|
+
var CASCADING = /* @__PURE__ */ new Set(["Inactivate", "QuickClose"]);
|
|
888
|
+
var CASCADE_DEPTH = 8;
|
|
889
|
+
async function openDescendantsOf(transport, schema, roots) {
|
|
890
|
+
const out = {};
|
|
891
|
+
const rootOf = new Map(roots.map((r) => [momentless(r), momentless(r)]));
|
|
892
|
+
const seen = new Set(rootOf.keys());
|
|
893
|
+
let frontier = [...rootOf.keys()];
|
|
894
|
+
for (let depth = 0; depth < CASCADE_DEPTH && frontier.length; depth += 1) {
|
|
895
|
+
const children = /* @__PURE__ */ new Map();
|
|
896
|
+
for (const [type, list3] of byType(frontier)) {
|
|
897
|
+
await schema.load(type);
|
|
898
|
+
if (!schema.attribute(type, "Children")) continue;
|
|
899
|
+
for (const part of chunks(list3)) {
|
|
900
|
+
const rows = await query(transport, { from: type, select: ["Children"], filter: ["ID=$oids"], with: { $oids: part }, page: { size: part.length, start: 0 } });
|
|
901
|
+
for (const r of rows) {
|
|
902
|
+
const parent = momentless(r._oid);
|
|
903
|
+
for (const c of [].concat(plain(r.Children) ?? [])) {
|
|
904
|
+
const child = momentless(String(c));
|
|
905
|
+
if (seen.has(child)) continue;
|
|
906
|
+
seen.add(child);
|
|
907
|
+
children.set(child, rootOf.get(parent));
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const states = children.size ? await statesOf(transport, [...children.keys()]) : {};
|
|
913
|
+
frontier = [];
|
|
914
|
+
for (const [child, root] of children) {
|
|
915
|
+
if (states[child] === void 0 || isClosed(states[child])) continue;
|
|
916
|
+
rootOf.set(child, root);
|
|
917
|
+
(out[root] ??= []).push(child);
|
|
918
|
+
frontier.push(child);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return out;
|
|
922
|
+
}
|
|
923
|
+
function reverseOf(described, before, done, context = {}) {
|
|
832
924
|
const commands = [], notes = [];
|
|
925
|
+
const cannot = (oid, op) => context.canOperate?.(typeOfOid(oid), op) === false;
|
|
833
926
|
const modified = new Set(done.modified.map(momentless)), operated = new Set(done.operated.map(momentless));
|
|
834
927
|
for (const u of described.updates) {
|
|
835
928
|
if (!modified.has(u.oid)) continue;
|
|
@@ -854,26 +947,48 @@ function reverseOf(described, before, done) {
|
|
|
854
947
|
for (const e of described.executes) {
|
|
855
948
|
if (!operated.has(e.oid)) continue;
|
|
856
949
|
const inverse = INVERSE[e.op];
|
|
857
|
-
if (inverse)
|
|
858
|
-
|
|
950
|
+
if (!inverse) {
|
|
951
|
+
notes.push(`${e.oid}: ${e.op} has no known inverse (was ${String(before[e.oid]?.AssetState ?? "?")})`);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (cannot(e.oid, inverse)) {
|
|
955
|
+
notes.push(`${e.oid}: ${typeOfOid(e.oid)} has no ${inverse} operation, so its ${e.op} is not reversed`);
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
commands.push({ from: e.oid, execute: inverse });
|
|
959
|
+
if (!CASCADING.has(e.op)) continue;
|
|
960
|
+
const open = context.openDescendants?.[e.oid] ?? [];
|
|
961
|
+
const closed = context.cascaded ? context.cascaded[e.oid] ?? [] : void 0;
|
|
962
|
+
if (closed === void 0) {
|
|
963
|
+
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`);
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
for (const d of closed) {
|
|
967
|
+
if (cannot(d, "Reactivate")) notes.push(`${d}: closed with ${e.oid}, but ${typeOfOid(d)} has no Reactivate operation`);
|
|
968
|
+
else commands.push({ from: d, execute: "Reactivate" });
|
|
969
|
+
}
|
|
970
|
+
if (closed.length) notes.push(`${e.oid}: ${e.op} also closed ${closed.join(", ")}; reopened after it`);
|
|
971
|
+
}
|
|
972
|
+
for (const oid of [...done.created].reverse().map(momentless)) {
|
|
973
|
+
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)" : ""}`);
|
|
974
|
+
else commands.push({ from: oid, execute: "Delete" });
|
|
859
975
|
}
|
|
860
|
-
for (const oid of [...done.created].reverse()) commands.push({ from: momentless(oid), execute: "Delete" });
|
|
861
976
|
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`);
|
|
862
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");
|
|
863
978
|
return { commands, notes };
|
|
864
979
|
}
|
|
865
|
-
var BatchRecordError = class extends
|
|
980
|
+
var BatchRecordError = class extends WrappedError {
|
|
866
981
|
receipt;
|
|
867
982
|
constructor(cause, receipt) {
|
|
868
|
-
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);
|
|
869
984
|
this.name = "BatchRecordError";
|
|
870
985
|
this.receipt = receipt;
|
|
871
986
|
}
|
|
872
987
|
};
|
|
873
|
-
var BatchSendError = class extends
|
|
988
|
+
var BatchSendError = class extends WrappedError {
|
|
874
989
|
receipt;
|
|
875
990
|
constructor(cause, receipt) {
|
|
876
|
-
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);
|
|
877
992
|
this.name = "BatchSendError";
|
|
878
993
|
this.receipt = receipt;
|
|
879
994
|
}
|
|
@@ -881,7 +996,7 @@ var BatchSendError = class extends Error {
|
|
|
881
996
|
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
882
997
|
async function runBatch(transport, schema, commands, options = {}) {
|
|
883
998
|
const useGuard = options.guard ?? true;
|
|
884
|
-
if (!commands.length) throw new
|
|
999
|
+
if (!commands.length) throw new UsageError("the batch is empty");
|
|
885
1000
|
const refused = unguardable(commands);
|
|
886
1001
|
const fatal = refused.filter((r) => r.reason.includes("rejected by the server"));
|
|
887
1002
|
if (fatal.length || useGuard && refused.length) throw new UnguardableError(useGuard ? refused : fatal);
|
|
@@ -892,12 +1007,14 @@ async function runBatch(transport, schema, commands, options = {}) {
|
|
|
892
1007
|
const described = describe(commands, projected);
|
|
893
1008
|
const touched = touchedBy(projected);
|
|
894
1009
|
const names = await namesOf(transport, schema, touched, noteFailure);
|
|
895
|
-
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 };
|
|
1010
|
+
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 };
|
|
896
1011
|
const nothing = !described.creates.length && !described.updates.length && !described.executes.length && !described.queries.length && !described.deferred.length;
|
|
897
1012
|
if (nothing) return receipt;
|
|
898
1013
|
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`);
|
|
899
1014
|
receipt.snapshotAt = now();
|
|
900
1015
|
receipt.before = await snapshot(transport, described);
|
|
1016
|
+
const closing = described.executes.filter((e) => CASCADING.has(e.op)).map((e) => e.oid);
|
|
1017
|
+
if (closing.length) receipt.openDescendants = await openDescendantsOf(transport, schema, closing);
|
|
901
1018
|
if (options.dryRun) return receipt;
|
|
902
1019
|
if (options.confirm && !await options.confirm(receipt)) return receipt;
|
|
903
1020
|
let unstamped = [];
|
|
@@ -929,13 +1046,29 @@ async function runBatch(transport, schema, commands, options = {}) {
|
|
|
929
1046
|
const unnamed = { number: null, name: null };
|
|
930
1047
|
receipt.created = done.created.map((oid, i) => ({ alias: described.creates[i]?.alias ?? null, type: described.creates[i]?.type ?? typeOfOid(oid), oid, ...unnamed }));
|
|
931
1048
|
receipt.touched = hit.map((oid) => ({ oid, ...names.get(oid) ?? unnamed }));
|
|
932
|
-
|
|
933
|
-
|
|
1049
|
+
const sentAs = receipt.sentCommands ?? [];
|
|
1050
|
+
const oidOfSent = (i) => {
|
|
1051
|
+
const body = i === null ? void 0 : sentAs[i] ? bodyOf3(sentAs[i]) : void 0;
|
|
1052
|
+
const id = body?.with?.$oid ?? body?.from;
|
|
1053
|
+
return typeof id === "string" && id.includes(":") ? momentless(id) : null;
|
|
1054
|
+
};
|
|
1055
|
+
receipt.failed = [...new Set(done.failures.map((f) => oidOfSent(f.index)).filter((oid) => !!oid && touched.includes(oid) && !hit.includes(oid)))];
|
|
1056
|
+
receipt.skipped = useGuard ? [.../* @__PURE__ */ new Set([...unstamped, ...touched.filter((oid) => !hit.includes(oid) && !receipt.failed.includes(oid))])] : [];
|
|
1057
|
+
for (const type of new Set([...done.created, ...hit].map((oid) => typeOfOid(momentless(oid))))) await schema.load(type).catch(() => null);
|
|
1058
|
+
const canOperate = (type, op) => schema.canOperate(type, op);
|
|
1059
|
+
const operatedNow = new Set(done.operated.map(momentless));
|
|
1060
|
+
const closed = Object.keys(receipt.openDescendants).filter((oid) => operatedNow.has(oid) && receipt.openDescendants[oid].length);
|
|
1061
|
+
receipt.reverse = reverseOf(described, receipt.before, done, { openDescendants: receipt.openDescendants, cascaded: closed.length ? void 0 : {}, canOperate });
|
|
934
1062
|
try {
|
|
1063
|
+
if (closed.length) {
|
|
1064
|
+
const states = await statesOf(transport, closed.flatMap((oid) => receipt.openDescendants[oid]));
|
|
1065
|
+
receipt.cascaded = Object.fromEntries(closed.map((oid) => [oid, receipt.openDescendants[oid].filter((d) => isClosed(states[d]))]));
|
|
1066
|
+
receipt.reverse = reverseOf(described, receipt.before, done, { openDescendants: receipt.openDescendants, cascaded: receipt.cascaded, canOperate });
|
|
1067
|
+
}
|
|
935
1068
|
const more = await namesOf(transport, schema, [...done.created, ...hit.filter((oid) => !names.has(oid))], noteFailure);
|
|
936
1069
|
receipt.created = receipt.created.map((c) => ({ ...c, ...more.get(c.oid) ?? unnamed }));
|
|
937
1070
|
receipt.touched = receipt.touched.map((t) => names.has(t.oid) ? t : { ...t, ...more.get(t.oid) ?? unnamed });
|
|
938
|
-
const all = [...done.created, ...hit];
|
|
1071
|
+
const all = [.../* @__PURE__ */ new Set([...done.created, ...hit, ...Object.values(receipt.cascaded).flat()])];
|
|
939
1072
|
receipt.after = all.length ? await stampsOf(transport, all) : {};
|
|
940
1073
|
} catch (error) {
|
|
941
1074
|
throw new BatchRecordError(error, receipt);
|
|
@@ -1034,7 +1167,7 @@ async function resolveNumbers(transport, numbers, options = {}) {
|
|
|
1034
1167
|
for (const n of numbers) if (!firsts.has(n.trim().toUpperCase())) firsts.set(n.trim().toUpperCase(), n.trim());
|
|
1035
1168
|
const asked = [...firsts.values()];
|
|
1036
1169
|
const invalid = asked.filter((n) => !isAssetNumber(n));
|
|
1037
|
-
if (invalid.length) throw new
|
|
1170
|
+
if (invalid.length) throw new UsageError(`not asset numbers (expected a prefix and digits, S-1234): ${invalid.join(", ")}`);
|
|
1038
1171
|
const byNumber = /* @__PURE__ */ new Map();
|
|
1039
1172
|
for (let i = 0; i < asked.length; i += CHUNK3) {
|
|
1040
1173
|
for (const r of await askRoots(transport, asked.slice(i, i + CHUNK3), options.types)) {
|
|
@@ -1055,7 +1188,7 @@ async function resolveRef(transport, spec, options = {}) {
|
|
|
1055
1188
|
return { number: null, oid, type: typeOfOid(oid), name: null, assetState: null };
|
|
1056
1189
|
}
|
|
1057
1190
|
if (isAssetNumber(text)) return (await resolveNumbers(transport, [text], options)).found[0] ?? null;
|
|
1058
|
-
throw new
|
|
1191
|
+
throw new UsageError(`"${spec}" is neither an asset number (S-1234) nor an OID token (Story:1234)`);
|
|
1059
1192
|
}
|
|
1060
1193
|
|
|
1061
1194
|
// src/wire/meta.ts
|
|
@@ -1099,7 +1232,8 @@ function toAssetType(doc) {
|
|
|
1099
1232
|
...doc.DefaultOrderBy ? { DefaultOrderBy: doc.DefaultOrderBy.tokenref } : {},
|
|
1100
1233
|
...doc.DefaultDisplayBy ? { DefaultDisplayBy: doc.DefaultDisplayBy.tokenref } : {},
|
|
1101
1234
|
...doc.ShortName ? { ShortName: doc.ShortName.tokenref } : {},
|
|
1102
|
-
Attributes: Object.fromEntries(Object.entries(doc.Attributes ?? {}).map(([key, value]) => [key, toAttributeDefinition(value)]))
|
|
1235
|
+
Attributes: Object.fromEntries(Object.entries(doc.Attributes ?? {}).map(([key, value]) => [key, toAttributeDefinition(value)])),
|
|
1236
|
+
...doc.Operations ? { Operations: Object.keys(doc.Operations).map((token) => token.slice(token.indexOf(".") + 1)) } : {}
|
|
1103
1237
|
};
|
|
1104
1238
|
}
|
|
1105
1239
|
function toMetamodel(doc) {
|
|
@@ -1116,7 +1250,7 @@ async function getAssetType(transport, token) {
|
|
|
1116
1250
|
}
|
|
1117
1251
|
async function getAttributeDefinition(transport, tokenref) {
|
|
1118
1252
|
const [type, attribute] = tokenref.split(".");
|
|
1119
|
-
if (!type || !attribute) throw new
|
|
1253
|
+
if (!type || !attribute) throw new UsageError(`"${tokenref}" is not an attribute token ("Story.Name")`);
|
|
1120
1254
|
return toAttributeDefinition(await transport.request({ method: "GET", path: `/meta.v1/${encodeURIComponent(type)}/${encodeURIComponent(attribute)}`, responseType: "json" }));
|
|
1121
1255
|
}
|
|
1122
1256
|
|
|
@@ -1163,6 +1297,15 @@ var Schema = class _Schema {
|
|
|
1163
1297
|
attribute(type, name) {
|
|
1164
1298
|
return attributeOf(this, type, name);
|
|
1165
1299
|
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Can the type do this operation (Delete, Reactivate, …)? Undefined when
|
|
1302
|
+
* the type is not loaded or its definition carries no operations, so a
|
|
1303
|
+
* caller can tell "no" from "don't know".
|
|
1304
|
+
*/
|
|
1305
|
+
canOperate(type, op) {
|
|
1306
|
+
const t = this.assetType(type);
|
|
1307
|
+
return t?.Operations ? t.Operations.includes(op) : void 0;
|
|
1308
|
+
}
|
|
1166
1309
|
/** The tokens loaded so far. */
|
|
1167
1310
|
get loaded() {
|
|
1168
1311
|
return [...this.types.entries()].filter(([, t]) => t !== null).map(([token]) => token);
|
|
@@ -1234,7 +1377,7 @@ async function localize(transport, tags, options = {}) {
|
|
|
1234
1377
|
const encoded = encodeURIComponent(toLson(unique));
|
|
1235
1378
|
const headers = options.culture ? { "Accept-Language": options.culture } : void 0;
|
|
1236
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 });
|
|
1237
|
-
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);
|
|
1238
1381
|
return result;
|
|
1239
1382
|
}
|
|
1240
1383
|
async function localizeOne(transport, tag, options = {}) {
|
|
@@ -1254,7 +1397,7 @@ function rejected(error) {
|
|
|
1254
1397
|
return messages.length ? new ApiError(messages.join("; "), error.status, "QUERY_REJECTED", error.details) : error;
|
|
1255
1398
|
}
|
|
1256
1399
|
async function queries2(transport, qs, options = {}) {
|
|
1257
|
-
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`);
|
|
1258
1401
|
if (!qs.length) return [];
|
|
1259
1402
|
let response;
|
|
1260
1403
|
try {
|
|
@@ -1315,8 +1458,8 @@ function fromAssetDocument(doc, { history: history2 = false } = {}) {
|
|
|
1315
1458
|
}
|
|
1316
1459
|
function toQuery(params) {
|
|
1317
1460
|
return {
|
|
1318
|
-
sel: params.sel?.join(","),
|
|
1319
|
-
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,
|
|
1320
1463
|
with: params.with ? Object.entries(params.with).map(([k, v]) => `${k}=${[].concat(v).join(",")}`).join("|") : void 0,
|
|
1321
1464
|
find: params.find,
|
|
1322
1465
|
findin: params.findin?.join(","),
|
|
@@ -1328,14 +1471,14 @@ function toQuery(params) {
|
|
|
1328
1471
|
};
|
|
1329
1472
|
}
|
|
1330
1473
|
async function read(transport, path, params, history2) {
|
|
1331
|
-
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`);
|
|
1332
1475
|
const doc = await transport.request({ method: "GET", path, query: toQuery(params), responseType: "json" });
|
|
1333
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 };
|
|
1334
1477
|
}
|
|
1335
1478
|
var list2 = (transport, type, params) => read(transport, `/rest-1.v1/Data/${encodeURIComponent(type)}`, params, false);
|
|
1336
|
-
async function get2(transport, oid,
|
|
1479
|
+
async function get2(transport, oid, select) {
|
|
1337
1480
|
const [type, id] = momentless(oid).split(":");
|
|
1338
|
-
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" });
|
|
1339
1482
|
return fromAssetDocument(doc);
|
|
1340
1483
|
}
|
|
1341
1484
|
var history = (transport, target, params) => {
|
|
@@ -1345,6 +1488,8 @@ var history = (transport, target, params) => {
|
|
|
1345
1488
|
};
|
|
1346
1489
|
|
|
1347
1490
|
// src/client.ts
|
|
1491
|
+
var DEFAULT_LIMIT = 1e3;
|
|
1492
|
+
var ME_SELECT = ["Name", "Username", "Nickname", "Email"];
|
|
1348
1493
|
var AgilityApiClient = class _AgilityApiClient {
|
|
1349
1494
|
transport;
|
|
1350
1495
|
schema;
|
|
@@ -1376,6 +1521,48 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1376
1521
|
const transport = { request: (options) => base.request({ ...options, signal: options.signal ?? signal }) };
|
|
1377
1522
|
return new _AgilityApiClient(this.config, { transport, schema: this.schema });
|
|
1378
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 -----------------------------------------------
|
|
1379
1566
|
/** meta.v1: the instance's schema, raw. (The translated, cached view is `schema`.) */
|
|
1380
1567
|
meta = {
|
|
1381
1568
|
metamodel: () => getMetamodel(this.transport),
|
|
@@ -1384,12 +1571,65 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1384
1571
|
};
|
|
1385
1572
|
/**
|
|
1386
1573
|
* loc-2.v1: labels for tags, in one request (meta's DisplayName values are
|
|
1387
|
-
* tags). An unmatched tag comes back as
|
|
1388
|
-
* "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.
|
|
1389
1576
|
*/
|
|
1390
1577
|
localize(tags, options) {
|
|
1391
1578
|
return localize(this.transport, tags, options);
|
|
1392
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
|
+
};
|
|
1393
1633
|
/**
|
|
1394
1634
|
* Services: what a person types, turned into assets. Numbers resolve in
|
|
1395
1635
|
* one api/asset request across every number root; an OID token is taken
|
|
@@ -1412,7 +1652,7 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1412
1652
|
/**
|
|
1413
1653
|
* The files attached to assets: metadata on Attachment assets, bytes
|
|
1414
1654
|
* through attachment.img. `add` sends a small file inline (one request)
|
|
1415
|
-
* and a large one in two steps
|
|
1655
|
+
* and a large one in two steps.
|
|
1416
1656
|
*/
|
|
1417
1657
|
attachments = {
|
|
1418
1658
|
list: (asset, options) => list(this.transport, asset, options),
|
|
@@ -1422,7 +1662,8 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1422
1662
|
add: (asset, data, options) => add(this.transport, asset, data, options),
|
|
1423
1663
|
put: (id, data, contentType) => put(this.transport, id, data, contentType),
|
|
1424
1664
|
remove: (id) => remove(this.transport, id),
|
|
1425
|
-
|
|
1665
|
+
/** @deprecated since 1.0.0-alpha.3: `client.urls.attachment`. Removed in 1.0.0. */
|
|
1666
|
+
url: (id) => this.urls.attachment(id)
|
|
1426
1667
|
};
|
|
1427
1668
|
/**
|
|
1428
1669
|
* Images embedded in an asset's rich text (Description and other long
|
|
@@ -1432,55 +1673,16 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1432
1673
|
*/
|
|
1433
1674
|
embeddedImages = {
|
|
1434
1675
|
upload: (asset, data, options) => uploadEmbeddedImage(this.transport, asset, data, options),
|
|
1435
|
-
|
|
1676
|
+
/** @deprecated since 1.0.0-alpha.3: `client.urls.embeddedImage`. Removed in 1.0.0. */
|
|
1677
|
+
url: (id) => this.urls.embeddedImage(id),
|
|
1436
1678
|
normalize: (html) => normalizeEmbeddedImageUrls(html, this.config.baseUrl)
|
|
1437
1679
|
};
|
|
1438
|
-
/**
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
/**
|
|
1445
|
-
* query.v1: reads only, the same query bodies as assets.query, rows
|
|
1446
|
-
* translated the same way. The endpoint cannot write, so it is the safe
|
|
1447
|
-
* place to explore a query; its requests may be retried like a GET.
|
|
1448
|
-
*/
|
|
1449
|
-
queryV1 = {
|
|
1450
|
-
query: async (q) => {
|
|
1451
|
-
await this.schema.ensureFor([q]);
|
|
1452
|
-
return query2(this.transport, q, { lookup: this.schema });
|
|
1453
|
-
},
|
|
1454
|
-
queries: async (qs) => {
|
|
1455
|
-
await this.schema.ensureFor(qs);
|
|
1456
|
-
return queries2(this.transport, qs, { lookup: this.schema });
|
|
1457
|
-
}
|
|
1458
|
-
};
|
|
1459
|
-
/** api/asset: batches and queries, rows translated through the schema. */
|
|
1460
|
-
assets = {
|
|
1461
|
-
send: async (commands, options) => {
|
|
1462
|
-
await this.schema.ensureFor(commands);
|
|
1463
|
-
return send(this.transport, commands, { ...options, lookup: this.schema });
|
|
1464
|
-
},
|
|
1465
|
-
preview: (commands) => preview(this.transport, commands),
|
|
1466
|
-
/**
|
|
1467
|
-
* Send as a compare-and-set: each asset is changed only if nobody has
|
|
1468
|
-
* changed it since the preview read it; the rest come back `skipped`.
|
|
1469
|
-
* Throws UnguardableError, before any request, for shapes it cannot
|
|
1470
|
-
* cover.
|
|
1471
|
-
*/
|
|
1472
|
-
sendGuarded: async (commands, options) => {
|
|
1473
|
-
await this.schema.ensureFor(commands);
|
|
1474
|
-
return sendGuarded(this.transport, commands, { ...options, lookup: this.schema });
|
|
1475
|
-
},
|
|
1476
|
-
query: async (q) => {
|
|
1477
|
-
await this.schema.ensureFor([q]);
|
|
1478
|
-
return query(this.transport, q, { lookup: this.schema });
|
|
1479
|
-
},
|
|
1480
|
-
queries: async (qs) => {
|
|
1481
|
-
await this.schema.ensureFor(qs);
|
|
1482
|
-
return queries(this.transport, qs, { lookup: this.schema });
|
|
1483
|
-
}
|
|
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)
|
|
1484
1686
|
};
|
|
1485
1687
|
};
|
|
1486
1688
|
export {
|
|
@@ -1499,6 +1701,8 @@ export {
|
|
|
1499
1701
|
TypeCache,
|
|
1500
1702
|
UNGUARDABLE_TYPES,
|
|
1501
1703
|
UnguardableError,
|
|
1704
|
+
UsageError,
|
|
1705
|
+
WrappedError,
|
|
1502
1706
|
assetApi_exports as assetApi,
|
|
1503
1707
|
assetStateOf,
|
|
1504
1708
|
attachment_exports as attachment,
|
|
@@ -1507,11 +1711,13 @@ export {
|
|
|
1507
1711
|
guard,
|
|
1508
1712
|
isAssetNumber,
|
|
1509
1713
|
isOidToken,
|
|
1714
|
+
kindOf,
|
|
1510
1715
|
loc2_exports as loc2,
|
|
1511
1716
|
meta_exports as meta,
|
|
1512
1717
|
momentless,
|
|
1513
1718
|
namesOf,
|
|
1514
1719
|
parseOid,
|
|
1720
|
+
parseViolation,
|
|
1515
1721
|
queryV1_exports as queryV1,
|
|
1516
1722
|
resolveNumbers,
|
|
1517
1723
|
resolveRef,
|