@versionone/agility-sdk 1.0.0-alpha.2 → 1.0.0-alpha.4
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/CHANGELOG.md +135 -0
- package/README.md +24 -11
- package/dist/index.cjs +317 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4201 -3851
- package/dist/index.d.ts +4201 -3851
- package/dist/index.js +311 -80
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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,15 +389,25 @@ 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: !token && rest.length > 1 ? 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) {
|
|
354
409
|
return {
|
|
410
|
+
requestId: typeof response.requestId === "string" && response.requestId ? response.requestId : null,
|
|
355
411
|
created: (response.assetsCreated?.oidTokens ?? []).map(momentless),
|
|
356
412
|
modified: (response.assetsModified?.oidTokens ?? []).map(momentless),
|
|
357
413
|
operated: (response.assetsOperatedOn?.oidTokens ?? []).map(momentless),
|
|
@@ -360,24 +416,28 @@ function outcomeOf(response) {
|
|
|
360
416
|
durationSeconds: typeof response.durationSeconds === "number" ? response.durationSeconds : null
|
|
361
417
|
};
|
|
362
418
|
}
|
|
363
|
-
var emptyOutcome = () => ({ created: [], modified: [], operated: [], failures: [], queryResults: [], durationSeconds: null });
|
|
419
|
+
var emptyOutcome = () => ({ requestId: null, created: [], modified: [], operated: [], failures: [], queryResults: [], durationSeconds: null });
|
|
364
420
|
function mergeOutcome(into, done, offset) {
|
|
365
421
|
into.created.push(...done.created);
|
|
366
422
|
into.modified.push(...done.modified);
|
|
367
423
|
into.operated.push(...done.operated);
|
|
368
424
|
into.failures.push(...done.failures.map((f) => ({ ...f, index: f.index === null ? null : f.index + offset })));
|
|
369
425
|
into.queryResults.push(...done.queryResults);
|
|
426
|
+
into.requestId = null;
|
|
370
427
|
if (done.durationSeconds !== null) into.durationSeconds = (into.durationSeconds ?? 0) + done.durationSeconds;
|
|
371
428
|
return into;
|
|
372
429
|
}
|
|
373
430
|
var CommandFailureError = class extends ApiError {
|
|
374
431
|
failures;
|
|
375
432
|
response;
|
|
433
|
+
/** What the batch did, in the model's terms: the commands that succeeded are committed. */
|
|
434
|
+
outcome;
|
|
376
435
|
constructor(response, failures = failuresOf(response)) {
|
|
377
436
|
super(failures.map((f) => f.message).join("; ") || "command failed", 200, "CommandFailure", failures);
|
|
378
437
|
this.name = "CommandFailureError";
|
|
379
438
|
this.failures = failures;
|
|
380
439
|
this.response = response;
|
|
440
|
+
this.outcome = outcomeOf(response);
|
|
381
441
|
}
|
|
382
442
|
};
|
|
383
443
|
var bodyOf = (c) => {
|
|
@@ -413,7 +473,7 @@ async function send(transport, commands, options = {}) {
|
|
|
413
473
|
const results = response.queryResult?.results ?? [];
|
|
414
474
|
let aligned = realign(queries3, results, failed);
|
|
415
475
|
if (!aligned) {
|
|
416
|
-
if (!failures.some((f) => f.index === null)) throw new
|
|
476
|
+
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
477
|
aligned = queries3.map(() => null);
|
|
418
478
|
}
|
|
419
479
|
const contextOf = (i) => {
|
|
@@ -435,7 +495,7 @@ async function query(transport, q, options = {}) {
|
|
|
435
495
|
return rows ?? [];
|
|
436
496
|
}
|
|
437
497
|
async function queryPage(transport, q, options = {}) {
|
|
438
|
-
if (!q.page) throw new
|
|
498
|
+
if (!q.page) throw new UsageError(`query of ${q.from} without a page: every api/asset read must be bounded`);
|
|
439
499
|
const response = await send(transport, [{ ...q, needTotal: true }], { ...options, throwOnFailure: true });
|
|
440
500
|
const rows = response.queryResult.results[0] ?? [];
|
|
441
501
|
const reported = response.queryResult.resultMetaData?.[0]?.total;
|
|
@@ -443,7 +503,7 @@ async function queryPage(transport, q, options = {}) {
|
|
|
443
503
|
return { rows, total, more: total === null ? rows.length === q.page.size : q.page.start + rows.length < total };
|
|
444
504
|
}
|
|
445
505
|
async function queries(transport, qs, options = {}) {
|
|
446
|
-
for (const q of qs) if (!q.page) throw new
|
|
506
|
+
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
507
|
const response = await send(transport, qs, { ...options, throwOnFailure: true });
|
|
448
508
|
return qs.map((_, i) => response.queryResult.results[i] ?? []);
|
|
449
509
|
}
|
|
@@ -481,7 +541,7 @@ function fromBase64(text) {
|
|
|
481
541
|
}
|
|
482
542
|
function numericId(spec) {
|
|
483
543
|
const m = /^(?:[A-Za-z]+:)?(\d+)(?::\d+)?$/.exec(String(spec).trim());
|
|
484
|
-
if (!m) throw new
|
|
544
|
+
if (!m) throw new UsageError(`"${spec}" is not an asset id (5678, or an OID token such as Attachment:5678)`);
|
|
485
545
|
return m[1];
|
|
486
546
|
}
|
|
487
547
|
var join = (baseUrl, path) => `${baseUrl.replace(/\/+$/, "")}/${path}`;
|
|
@@ -577,9 +637,9 @@ async function get(transport, id) {
|
|
|
577
637
|
const file = await getFile(transport, meta.id);
|
|
578
638
|
return { ...meta, bytes: file.bytes, contentType: file.contentType ?? meta.contentType, recordedContentType: meta.contentType };
|
|
579
639
|
}
|
|
580
|
-
var AttachmentUploadError = class extends
|
|
640
|
+
var AttachmentUploadError = class extends WrappedError {
|
|
581
641
|
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)}`,
|
|
642
|
+
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
643
|
this.attachment = attachment;
|
|
584
644
|
this.name = "AttachmentUploadError";
|
|
585
645
|
}
|
|
@@ -612,7 +672,7 @@ async function remove(transport, id) {
|
|
|
612
672
|
// src/services/guard.ts
|
|
613
673
|
var CHUNK = 100;
|
|
614
674
|
var UNGUARDABLE_TYPES = ["Idea", "IdeaChangeSet", "IdeaStatus", "IdeaComment", "IdeaCategory", "IdeaForum", "IdeaUser"];
|
|
615
|
-
var UnguardableError = class extends
|
|
675
|
+
var UnguardableError = class extends UsageError {
|
|
616
676
|
reasons;
|
|
617
677
|
constructor(reasons) {
|
|
618
678
|
super(`cannot guard this batch: ${reasons.map((r) => `command ${r.index}: ${r.reason}`).join("; ")}`);
|
|
@@ -678,7 +738,7 @@ function guard(projected, stamps) {
|
|
|
678
738
|
unstamped.push(x.oid);
|
|
679
739
|
continue;
|
|
680
740
|
}
|
|
681
|
-
if (!STAMP.test(stamp)) throw new
|
|
741
|
+
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
742
|
const scope = { from: typeOfOid(x.oid), filter: ["ID=$oid", `ChangeDate='${stamp}'`], with: { $oid: x.oid } };
|
|
683
743
|
commands.push(x.update ? { ...scope, update: x.update } : { ...scope, execute: x.execute });
|
|
684
744
|
}
|
|
@@ -695,16 +755,14 @@ function passesOf(projected) {
|
|
|
695
755
|
}
|
|
696
756
|
return passes;
|
|
697
757
|
}
|
|
698
|
-
var GuardedPassError = class extends
|
|
758
|
+
var GuardedPassError = class extends WrappedError {
|
|
699
759
|
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;
|
|
760
|
+
super(`guarded send failed in pass ${pass + 1}${pass ? " (earlier passes applied)" : ""}: ${cause instanceof Error ? cause.message : String(cause)}`, "GUARDED_PASS", cause);
|
|
702
761
|
this.outcome = outcome;
|
|
703
762
|
this.sent = sent;
|
|
704
763
|
this.pass = pass;
|
|
705
764
|
this.name = "GuardedPassError";
|
|
706
765
|
}
|
|
707
|
-
cause;
|
|
708
766
|
outcome;
|
|
709
767
|
sent;
|
|
710
768
|
pass;
|
|
@@ -921,18 +979,18 @@ function reverseOf(described, before, done, context = {}) {
|
|
|
921
979
|
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
980
|
return { commands, notes };
|
|
923
981
|
}
|
|
924
|
-
var BatchRecordError = class extends
|
|
982
|
+
var BatchRecordError = class extends WrappedError {
|
|
925
983
|
receipt;
|
|
926
984
|
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)}`,
|
|
985
|
+
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
986
|
this.name = "BatchRecordError";
|
|
929
987
|
this.receipt = receipt;
|
|
930
988
|
}
|
|
931
989
|
};
|
|
932
|
-
var BatchSendError = class extends
|
|
990
|
+
var BatchSendError = class extends WrappedError {
|
|
933
991
|
receipt;
|
|
934
992
|
constructor(cause, receipt) {
|
|
935
|
-
super(`the batch send failed; whether the server applied it is unknown: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
993
|
+
super(`the batch send failed; whether the server applied it is unknown: ${cause instanceof Error ? cause.message : String(cause)}`, "BATCH_SEND", cause);
|
|
936
994
|
this.name = "BatchSendError";
|
|
937
995
|
this.receipt = receipt;
|
|
938
996
|
}
|
|
@@ -940,7 +998,7 @@ var BatchSendError = class extends Error {
|
|
|
940
998
|
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
941
999
|
async function runBatch(transport, schema, commands, options = {}) {
|
|
942
1000
|
const useGuard = options.guard ?? true;
|
|
943
|
-
if (!commands.length) throw new
|
|
1001
|
+
if (!commands.length) throw new UsageError("the batch is empty");
|
|
944
1002
|
const refused = unguardable(commands);
|
|
945
1003
|
const fatal = refused.filter((r) => r.reason.includes("rejected by the server"));
|
|
946
1004
|
if (fatal.length || useGuard && refused.length) throw new UnguardableError(useGuard ? refused : fatal);
|
|
@@ -1046,6 +1104,65 @@ async function reversible(transport, receipt, options = {}) {
|
|
|
1046
1104
|
return { commands, drifted, reactivated };
|
|
1047
1105
|
}
|
|
1048
1106
|
|
|
1107
|
+
// src/wire/fields.ts
|
|
1108
|
+
var fields_exports = {};
|
|
1109
|
+
__export(fields_exports, {
|
|
1110
|
+
visibleFields: () => visibleFields,
|
|
1111
|
+
visibleFieldsByAsset: () => visibleFieldsByAsset
|
|
1112
|
+
});
|
|
1113
|
+
async function visibleFields(transport, types, scopes) {
|
|
1114
|
+
if (!types.length) return {};
|
|
1115
|
+
const params = [...scopes.map((s) => `scopeOid=${encodeURIComponent(momentless(s))}`), ...types.map((t) => `assetTypeToken=${encodeURIComponent(t)}`)].join("&");
|
|
1116
|
+
const answer = await transport.request({ method: "GET", path: `/api/visibleFields?${params}`, responseType: "json" });
|
|
1117
|
+
if (!Array.isArray(answer)) throw new ApiError("api/visibleFields did not answer a list", 200, "BAD_RESPONSE", answer);
|
|
1118
|
+
const out = {};
|
|
1119
|
+
for (const entry of answer) if (entry && typeof entry.assetType === "string") out[entry.assetType] = Array.isArray(entry.fields) ? entry.fields.filter((f) => typeof f === "string") : [];
|
|
1120
|
+
return out;
|
|
1121
|
+
}
|
|
1122
|
+
async function visibleFieldsByAsset(transport, oid) {
|
|
1123
|
+
const answer = await transport.request({ method: "GET", path: "/api/visibleFieldsByAsset", query: { oidToken: momentless(oid) }, responseType: "json" });
|
|
1124
|
+
if (!Array.isArray(answer)) throw new ApiError("api/visibleFieldsByAsset did not answer a list", 200, "BAD_RESPONSE", answer);
|
|
1125
|
+
return answer.filter((f) => typeof f === "string");
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// src/services/fields.ts
|
|
1129
|
+
async function lineage(schema, type) {
|
|
1130
|
+
const out = [];
|
|
1131
|
+
for (let t = type; t && !out.includes(t); ) {
|
|
1132
|
+
out.push(t);
|
|
1133
|
+
const def = await schema.load(t);
|
|
1134
|
+
t = def?.Base;
|
|
1135
|
+
}
|
|
1136
|
+
return out;
|
|
1137
|
+
}
|
|
1138
|
+
function importantName(token, types) {
|
|
1139
|
+
const dot = token.indexOf(".");
|
|
1140
|
+
if (dot < 0) return token;
|
|
1141
|
+
return types.includes(token.slice(0, dot)) ? token.slice(dot + 1) : null;
|
|
1142
|
+
}
|
|
1143
|
+
async function describe2(transport, schema, type, scope) {
|
|
1144
|
+
const scopeOid = momentless(scope);
|
|
1145
|
+
const types = await lineage(schema, type);
|
|
1146
|
+
const definition = await schema.load(type);
|
|
1147
|
+
const required = definition ? Object.values(definition.Attributes).filter((a) => a.IsRequired && !a.IsReadOnly).map((a) => a.Name).sort() : [];
|
|
1148
|
+
const [visible, [row]] = await Promise.all([
|
|
1149
|
+
visibleFields(transport, [type], [scopeOid]),
|
|
1150
|
+
query(transport, { from: "Scope", select: [{ from: "Scheme", select: ["ImportantFields", "IsRequiredAtClose"] }], filter: ["ID=$scope"], with: { $scope: scopeOid }, page: { size: 1, start: 0 } }, { lookup: schema })
|
|
1151
|
+
]);
|
|
1152
|
+
const scheme = row?.Scheme ?? null;
|
|
1153
|
+
const tokens = Array.isArray(scheme?.ImportantFields) ? scheme.ImportantFields.filter((t) => typeof t === "string") : [];
|
|
1154
|
+
const important = [...new Set(tokens.map((t) => importantName(t, types)).filter((n) => !!n))].sort();
|
|
1155
|
+
return {
|
|
1156
|
+
type,
|
|
1157
|
+
scope: scopeOid,
|
|
1158
|
+
scheme: scheme ? momentless(scheme._oid) : null,
|
|
1159
|
+
required,
|
|
1160
|
+
visible: visible[type] ?? [],
|
|
1161
|
+
important,
|
|
1162
|
+
requiredAtClose: scheme?.IsRequiredAtClose === true
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1049
1166
|
// src/types/asset.ts
|
|
1050
1167
|
var ASSET_STATE_CODES = { Future: 0, Active: 64, Closed: 128, Dead: 192, Template: 200, OldBrokenDownStory: 208, Deprecated: 254, Deleted: 255 };
|
|
1051
1168
|
var STATE_OF_CODE = Object.fromEntries(Object.entries(ASSET_STATE_CODES).map(([name, code]) => [code, name]));
|
|
@@ -1111,7 +1228,7 @@ async function resolveNumbers(transport, numbers, options = {}) {
|
|
|
1111
1228
|
for (const n of numbers) if (!firsts.has(n.trim().toUpperCase())) firsts.set(n.trim().toUpperCase(), n.trim());
|
|
1112
1229
|
const asked = [...firsts.values()];
|
|
1113
1230
|
const invalid = asked.filter((n) => !isAssetNumber(n));
|
|
1114
|
-
if (invalid.length) throw new
|
|
1231
|
+
if (invalid.length) throw new UsageError(`not asset numbers (expected a prefix and digits, S-1234): ${invalid.join(", ")}`);
|
|
1115
1232
|
const byNumber = /* @__PURE__ */ new Map();
|
|
1116
1233
|
for (let i = 0; i < asked.length; i += CHUNK3) {
|
|
1117
1234
|
for (const r of await askRoots(transport, asked.slice(i, i + CHUNK3), options.types)) {
|
|
@@ -1132,7 +1249,7 @@ async function resolveRef(transport, spec, options = {}) {
|
|
|
1132
1249
|
return { number: null, oid, type: typeOfOid(oid), name: null, assetState: null };
|
|
1133
1250
|
}
|
|
1134
1251
|
if (isAssetNumber(text)) return (await resolveNumbers(transport, [text], options)).found[0] ?? null;
|
|
1135
|
-
throw new
|
|
1252
|
+
throw new UsageError(`"${spec}" is neither an asset number (S-1234) nor an OID token (Story:1234)`);
|
|
1136
1253
|
}
|
|
1137
1254
|
|
|
1138
1255
|
// src/wire/meta.ts
|
|
@@ -1194,7 +1311,7 @@ async function getAssetType(transport, token) {
|
|
|
1194
1311
|
}
|
|
1195
1312
|
async function getAttributeDefinition(transport, tokenref) {
|
|
1196
1313
|
const [type, attribute] = tokenref.split(".");
|
|
1197
|
-
if (!type || !attribute) throw new
|
|
1314
|
+
if (!type || !attribute) throw new UsageError(`"${tokenref}" is not an attribute token ("Story.Name")`);
|
|
1198
1315
|
return toAttributeDefinition(await transport.request({ method: "GET", path: `/meta.v1/${encodeURIComponent(type)}/${encodeURIComponent(attribute)}`, responseType: "json" }));
|
|
1199
1316
|
}
|
|
1200
1317
|
|
|
@@ -1321,7 +1438,7 @@ async function localize(transport, tags, options = {}) {
|
|
|
1321
1438
|
const encoded = encodeURIComponent(toLson(unique));
|
|
1322
1439
|
const headers = options.culture ? { "Accept-Language": options.culture } : void 0;
|
|
1323
1440
|
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
|
|
1441
|
+
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
1442
|
return result;
|
|
1326
1443
|
}
|
|
1327
1444
|
async function localizeOne(transport, tag, options = {}) {
|
|
@@ -1341,7 +1458,7 @@ function rejected(error) {
|
|
|
1341
1458
|
return messages.length ? new ApiError(messages.join("; "), error.status, "QUERY_REJECTED", error.details) : error;
|
|
1342
1459
|
}
|
|
1343
1460
|
async function queries2(transport, qs, options = {}) {
|
|
1344
|
-
for (const q of qs) if (!q.page) throw new
|
|
1461
|
+
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
1462
|
if (!qs.length) return [];
|
|
1346
1463
|
let response;
|
|
1347
1464
|
try {
|
|
@@ -1371,6 +1488,8 @@ __export(rest1_exports, {
|
|
|
1371
1488
|
history: () => history,
|
|
1372
1489
|
list: () => list2,
|
|
1373
1490
|
momentless: () => momentless,
|
|
1491
|
+
operate: () => operate,
|
|
1492
|
+
template: () => template,
|
|
1374
1493
|
toQuery: () => toQuery
|
|
1375
1494
|
});
|
|
1376
1495
|
var toRef = (doc) => ({ _oid: momentless(doc.idref) });
|
|
@@ -1402,8 +1521,8 @@ function fromAssetDocument(doc, { history: history2 = false } = {}) {
|
|
|
1402
1521
|
}
|
|
1403
1522
|
function toQuery(params) {
|
|
1404
1523
|
return {
|
|
1405
|
-
sel: params.sel?.join(","),
|
|
1406
|
-
where: params.where?.length ? params.where.join(";") : void 0,
|
|
1524
|
+
sel: (params.select ?? params.sel)?.join(","),
|
|
1525
|
+
where: (params.filter ?? params.where)?.length ? (params.filter ?? params.where).join(";") : void 0,
|
|
1407
1526
|
with: params.with ? Object.entries(params.with).map(([k, v]) => `${k}=${[].concat(v).join(",")}`).join("|") : void 0,
|
|
1408
1527
|
find: params.find,
|
|
1409
1528
|
findin: params.findin?.join(","),
|
|
@@ -1415,16 +1534,29 @@ function toQuery(params) {
|
|
|
1415
1534
|
};
|
|
1416
1535
|
}
|
|
1417
1536
|
async function read(transport, path, params, history2) {
|
|
1418
|
-
if (!params.page) throw new
|
|
1537
|
+
if (!params.page) throw new UsageError(`rest-1 read of ${path} without a page: every read must be bounded`);
|
|
1419
1538
|
const doc = await transport.request({ method: "GET", path, query: toQuery(params), responseType: "json" });
|
|
1420
1539
|
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
1540
|
}
|
|
1422
1541
|
var list2 = (transport, type, params) => read(transport, `/rest-1.v1/Data/${encodeURIComponent(type)}`, params, false);
|
|
1423
|
-
async function get2(transport, oid,
|
|
1542
|
+
async function get2(transport, oid, select) {
|
|
1424
1543
|
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:
|
|
1544
|
+
const doc = await transport.request({ method: "GET", path: `/rest-1.v1/Data/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, query: { sel: select?.join(",") }, responseType: "json" });
|
|
1426
1545
|
return fromAssetDocument(doc);
|
|
1427
1546
|
}
|
|
1547
|
+
async function operate(transport, oid, op) {
|
|
1548
|
+
const [type, id] = momentless(oid).split(":");
|
|
1549
|
+
if (!type || !id) throw new UsageError(`"${oid}" is not an OID token (Story:1234)`);
|
|
1550
|
+
const doc = await transport.request({ method: "POST", path: `/rest-1.v1/Data/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, query: { op }, responseType: "json" });
|
|
1551
|
+
if (!doc || typeof doc.id !== "string" || !doc.id) throw new ApiError(`rest-1 ${op} on ${oid} did not answer with an asset`, 200, "BAD_RESPONSE", doc);
|
|
1552
|
+
const moment = doc.id.split(":")[2] ?? null;
|
|
1553
|
+
return { oid: momentless(doc.id), moment };
|
|
1554
|
+
}
|
|
1555
|
+
async function template(transport, type, options = {}) {
|
|
1556
|
+
const doc = await transport.request({ method: "GET", path: `/rest-1.v1/New/${encodeURIComponent(type)}`, query: { ctx: options.ctx ? momentless(options.ctx) : void 0 }, responseType: "json" });
|
|
1557
|
+
const { _oid: _ignored, ...values } = fromAssetDocument({ ...doc, id: doc.id || `${type}:0` });
|
|
1558
|
+
return values;
|
|
1559
|
+
}
|
|
1428
1560
|
var history = (transport, target, params) => {
|
|
1429
1561
|
const [type, id] = target.split(":");
|
|
1430
1562
|
const path = id ? `/rest-1.v1/Hist/${encodeURIComponent(type)}/${encodeURIComponent(id)}` : `/rest-1.v1/Hist/${encodeURIComponent(type)}`;
|
|
@@ -1432,6 +1564,8 @@ var history = (transport, target, params) => {
|
|
|
1432
1564
|
};
|
|
1433
1565
|
|
|
1434
1566
|
// src/client.ts
|
|
1567
|
+
var DEFAULT_LIMIT = 1e3;
|
|
1568
|
+
var ME_SELECT = ["Name", "Username", "Nickname", "Email"];
|
|
1435
1569
|
var AgilityApiClient = class _AgilityApiClient {
|
|
1436
1570
|
transport;
|
|
1437
1571
|
schema;
|
|
@@ -1463,6 +1597,48 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1463
1597
|
const transport = { request: (options) => base.request({ ...options, signal: options.signal ?? signal }) };
|
|
1464
1598
|
return new _AgilityApiClient(this.config, { transport, schema: this.schema });
|
|
1465
1599
|
}
|
|
1600
|
+
async query(q) {
|
|
1601
|
+
await this.schema.ensureFor([q]);
|
|
1602
|
+
return query2(this.transport, q, { lookup: this.schema });
|
|
1603
|
+
}
|
|
1604
|
+
async queries(qs) {
|
|
1605
|
+
await this.schema.ensureFor(qs);
|
|
1606
|
+
return queries2(this.transport, [...qs], { lookup: this.schema });
|
|
1607
|
+
}
|
|
1608
|
+
async get(oid, select) {
|
|
1609
|
+
const rows = await this.query({ from: momentless(oid), ...select ? { select } : {}, page: { size: 1, start: 0 } });
|
|
1610
|
+
return rows[0] ?? null;
|
|
1611
|
+
}
|
|
1612
|
+
async queryPage(q) {
|
|
1613
|
+
await this.schema.ensureFor([q]);
|
|
1614
|
+
return queryPage(this.transport, q, { lookup: this.schema });
|
|
1615
|
+
}
|
|
1616
|
+
async *pages(q, options = {}) {
|
|
1617
|
+
if (!q.page) throw new UsageError(`query of ${q.from} without a page: every read must be bounded`);
|
|
1618
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
1619
|
+
const size = q.page.size;
|
|
1620
|
+
let start = q.page.start, read2 = 0;
|
|
1621
|
+
while (read2 < limit) {
|
|
1622
|
+
const want = Math.min(size, limit - read2);
|
|
1623
|
+
const rows = await this.query({ ...q, page: { size: want, start } });
|
|
1624
|
+
if (rows.length) yield rows;
|
|
1625
|
+
read2 += rows.length;
|
|
1626
|
+
start += rows.length;
|
|
1627
|
+
if (rows.length < want) return;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
async all(q, options = {}) {
|
|
1631
|
+
const out = [];
|
|
1632
|
+
for await (const page2 of this.pages(q, options)) out.push(...page2);
|
|
1633
|
+
return out;
|
|
1634
|
+
}
|
|
1635
|
+
/** The member these credentials belong to (Member.IsSelf, live 2026-11-12): what a comment's Author is. */
|
|
1636
|
+
async me() {
|
|
1637
|
+
const [row] = await this.query({ from: "Member", select: ME_SELECT, filter: ["IsSelf='true'"], page: { size: 1, start: 0 } });
|
|
1638
|
+
if (!row) throw new ApiError("the server knows no member for these credentials", 0, "NOT_FOUND");
|
|
1639
|
+
return row;
|
|
1640
|
+
}
|
|
1641
|
+
// ---- the endpoints behind the reads -----------------------------------------------
|
|
1466
1642
|
/** meta.v1: the instance's schema, raw. (The translated, cached view is `schema`.) */
|
|
1467
1643
|
meta = {
|
|
1468
1644
|
metamodel: () => getMetamodel(this.transport),
|
|
@@ -1471,12 +1647,88 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1471
1647
|
};
|
|
1472
1648
|
/**
|
|
1473
1649
|
* 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.
|
|
1650
|
+
* tags, and so is a violation's `tag`). An unmatched tag comes back as
|
|
1651
|
+
* itself; the server never says "missing", so neither does this.
|
|
1476
1652
|
*/
|
|
1477
1653
|
localize(tags, options) {
|
|
1478
1654
|
return localize(this.transport, tags, options);
|
|
1479
1655
|
}
|
|
1656
|
+
/**
|
|
1657
|
+
* rest-1.v1, for what the other endpoints cannot do: deleted assets
|
|
1658
|
+
* (`deleted: true`), one asset by OID without a page, history (one row
|
|
1659
|
+
* per moment), a new asset's template (`template`: what the type's
|
|
1660
|
+
* initializer fills in for a context — a Timebox's next name and dates,
|
|
1661
|
+
* a Story's carry-over from a Request), and an operation's result
|
|
1662
|
+
* (`operate`: api/asset performs a Copy but never says what it made;
|
|
1663
|
+
* rest-1 answers the new OID). Parameters are spelled as a query's
|
|
1664
|
+
* (`select`, `filter`).
|
|
1665
|
+
*/
|
|
1666
|
+
data = {
|
|
1667
|
+
list: (type, params) => list2(this.transport, type, params),
|
|
1668
|
+
get: (oid, select) => get2(this.transport, oid, select),
|
|
1669
|
+
history: (target, params) => history(this.transport, target, params),
|
|
1670
|
+
template: (type, options) => template(this.transport, type, options),
|
|
1671
|
+
operate: (oid, op) => operate(this.transport, oid, op)
|
|
1672
|
+
};
|
|
1673
|
+
/** Copy an asset (the server's Copy operation) and answer the copy's OID: the one thing api/asset's `execute: "Copy"` does not say. */
|
|
1674
|
+
async copy(oid) {
|
|
1675
|
+
return (await operate(this.transport, oid, "Copy")).oid;
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* What a form needs for an asset type in a scope: the attributes the
|
|
1679
|
+
* server requires to create one (meta), the ones the product shows there
|
|
1680
|
+
* (api/visibleFields, custom fields included once configured), and the
|
|
1681
|
+
* scope's scheme's important fields and whether they are enforced at
|
|
1682
|
+
* close. `visible` alone answers for several types and scopes at once.
|
|
1683
|
+
*/
|
|
1684
|
+
fields = {
|
|
1685
|
+
describe: (type, scope) => describe2(this.transport, this.schema, type, scope),
|
|
1686
|
+
visible: (types, scopes) => visibleFields(this.transport, types, scopes),
|
|
1687
|
+
visibleByAsset: (oid) => visibleFieldsByAsset(this.transport, oid)
|
|
1688
|
+
};
|
|
1689
|
+
/** @deprecated since 1.0.0-alpha.3: use `client.query` / `client.queries` (the same endpoint, query.v1; only the name moved). Removed in 1.0.0. */
|
|
1690
|
+
queryV1 = {
|
|
1691
|
+
query: (q) => this.query(q),
|
|
1692
|
+
queries: (qs) => this.queries(qs)
|
|
1693
|
+
};
|
|
1694
|
+
// ---- writes -----------------------------------------------------------------------
|
|
1695
|
+
/** api/asset: batches, answered in the model's terms. */
|
|
1696
|
+
assets = {
|
|
1697
|
+
/**
|
|
1698
|
+
* Send a batch. The Outcome says what it did — created, modified and
|
|
1699
|
+
* operated OIDs, failures by index (each with its parsed violation),
|
|
1700
|
+
* query rows per query command — and never the envelope. api/asset
|
|
1701
|
+
* commits per command and answers 200 either way, so a failed command
|
|
1702
|
+
* is in `failures`, or thrown as a CommandFailureError with
|
|
1703
|
+
* `throwOnFailure` (the error carries the same Outcome).
|
|
1704
|
+
*/
|
|
1705
|
+
send: async (commands, options) => {
|
|
1706
|
+
await this.schema.ensureFor(commands);
|
|
1707
|
+
return outcomeOf(await send(this.transport, commands, { ...options, lookup: this.schema }));
|
|
1708
|
+
},
|
|
1709
|
+
/** The server's projection of a batch: one command per asset it would touch, nothing applied. */
|
|
1710
|
+
preview: (commands) => preview(this.transport, commands),
|
|
1711
|
+
/**
|
|
1712
|
+
* Send as a compare-and-set: each asset is changed only if nobody has
|
|
1713
|
+
* changed it since the preview read it; the rest come back `skipped`.
|
|
1714
|
+
* Throws UnguardableError, before any request, for shapes it cannot
|
|
1715
|
+
* cover.
|
|
1716
|
+
*/
|
|
1717
|
+
sendGuarded: async (commands, options) => {
|
|
1718
|
+
await this.schema.ensureFor(commands);
|
|
1719
|
+
return sendGuarded(this.transport, commands, { ...options, lookup: this.schema });
|
|
1720
|
+
},
|
|
1721
|
+
/** @deprecated since 1.0.0-alpha.3: use `client.query` (rows, through query.v1) or `client.queryPage` (rows and a total, through api/asset as this does). Removed in 1.0.0. */
|
|
1722
|
+
query: async (q) => {
|
|
1723
|
+
await this.schema.ensureFor([q]);
|
|
1724
|
+
return query(this.transport, q, { lookup: this.schema });
|
|
1725
|
+
},
|
|
1726
|
+
/** @deprecated since 1.0.0-alpha.3: use `client.queries`. Removed in 1.0.0. */
|
|
1727
|
+
queries: async (qs) => {
|
|
1728
|
+
await this.schema.ensureFor(qs);
|
|
1729
|
+
return queries(this.transport, qs, { lookup: this.schema });
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1480
1732
|
/**
|
|
1481
1733
|
* Services: what a person types, turned into assets. Numbers resolve in
|
|
1482
1734
|
* one api/asset request across every number root; an OID token is taken
|
|
@@ -1499,7 +1751,7 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1499
1751
|
/**
|
|
1500
1752
|
* The files attached to assets: metadata on Attachment assets, bytes
|
|
1501
1753
|
* through attachment.img. `add` sends a small file inline (one request)
|
|
1502
|
-
* and a large one in two steps
|
|
1754
|
+
* and a large one in two steps.
|
|
1503
1755
|
*/
|
|
1504
1756
|
attachments = {
|
|
1505
1757
|
list: (asset, options) => list(this.transport, asset, options),
|
|
@@ -1509,7 +1761,8 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1509
1761
|
add: (asset, data, options) => add(this.transport, asset, data, options),
|
|
1510
1762
|
put: (id, data, contentType) => put(this.transport, id, data, contentType),
|
|
1511
1763
|
remove: (id) => remove(this.transport, id),
|
|
1512
|
-
|
|
1764
|
+
/** @deprecated since 1.0.0-alpha.3: `client.urls.attachment`. Removed in 1.0.0. */
|
|
1765
|
+
url: (id) => this.urls.attachment(id)
|
|
1513
1766
|
};
|
|
1514
1767
|
/**
|
|
1515
1768
|
* Images embedded in an asset's rich text (Description and other long
|
|
@@ -1519,55 +1772,27 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1519
1772
|
*/
|
|
1520
1773
|
embeddedImages = {
|
|
1521
1774
|
upload: (asset, data, options) => uploadEmbeddedImage(this.transport, asset, data, options),
|
|
1522
|
-
|
|
1775
|
+
/** @deprecated since 1.0.0-alpha.3: `client.urls.embeddedImage`. Removed in 1.0.0. */
|
|
1776
|
+
url: (id) => this.urls.embeddedImage(id),
|
|
1523
1777
|
normalize: (html) => normalizeEmbeddedImageUrls(html, this.config.baseUrl)
|
|
1524
1778
|
};
|
|
1525
|
-
/**
|
|
1526
|
-
|
|
1527
|
-
list: (type, params) => list2(this.transport, type, params),
|
|
1528
|
-
get: (oid, sel) => get2(this.transport, oid, sel),
|
|
1529
|
-
history: (target, params) => history(this.transport, target, params)
|
|
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),
|
|
1779
|
+
/** Where a browser opens things on this instance: an asset's page, an attachment's bytes, an embedded image. */
|
|
1780
|
+
urls = {
|
|
1553
1781
|
/**
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
1557
|
-
*
|
|
1782
|
+
* The asset's page, from an OID token or an asset Number (S-01001,
|
|
1783
|
+
* what a person typed and what a message shows): assetdetail.v1
|
|
1784
|
+
* resolves either and redirects to the type's own detail page; a signed-
|
|
1785
|
+
* out reader lands there after logging in. Anything else is a UsageError.
|
|
1558
1786
|
*/
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
return query(this.transport, q, { lookup: this.schema });
|
|
1787
|
+
asset: (ref) => {
|
|
1788
|
+
const base = this.config.baseUrl.replace(/\/+$/, "");
|
|
1789
|
+
const text = ref.trim();
|
|
1790
|
+
if (isOidToken(text)) return `${base}/assetdetail.v1?oid=${momentless(text)}`;
|
|
1791
|
+
if (isAssetNumber(text)) return `${base}/assetdetail.v1?Number=${encodeURIComponent(text)}`;
|
|
1792
|
+
throw new UsageError(`"${ref}" is neither an OID token (Story:1234) nor an asset number (S-1234)`);
|
|
1566
1793
|
},
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
return queries(this.transport, qs, { lookup: this.schema });
|
|
1570
|
-
}
|
|
1794
|
+
attachment: (id) => attachmentUrl(this.config.baseUrl, id),
|
|
1795
|
+
embeddedImage: (id) => embeddedImageUrl(this.config.baseUrl, id)
|
|
1571
1796
|
};
|
|
1572
1797
|
};
|
|
1573
1798
|
export {
|
|
@@ -1586,19 +1811,25 @@ export {
|
|
|
1586
1811
|
TypeCache,
|
|
1587
1812
|
UNGUARDABLE_TYPES,
|
|
1588
1813
|
UnguardableError,
|
|
1814
|
+
UsageError,
|
|
1815
|
+
WrappedError,
|
|
1589
1816
|
assetApi_exports as assetApi,
|
|
1590
1817
|
assetStateOf,
|
|
1591
1818
|
attachment_exports as attachment,
|
|
1592
1819
|
createFetchTransport,
|
|
1593
1820
|
describe as describeBatch,
|
|
1821
|
+
fields_exports as fields,
|
|
1594
1822
|
guard,
|
|
1823
|
+
importantName,
|
|
1595
1824
|
isAssetNumber,
|
|
1596
1825
|
isOidToken,
|
|
1826
|
+
kindOf,
|
|
1597
1827
|
loc2_exports as loc2,
|
|
1598
1828
|
meta_exports as meta,
|
|
1599
1829
|
momentless,
|
|
1600
1830
|
namesOf,
|
|
1601
1831
|
parseOid,
|
|
1832
|
+
parseViolation,
|
|
1602
1833
|
queryV1_exports as queryV1,
|
|
1603
1834
|
resolveNumbers,
|
|
1604
1835
|
resolveRef,
|