@versionone/agility-sdk 1.0.0-alpha.3 → 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 +9 -3
- package/dist/index.cjs +124 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +144 -23
- package/dist/index.d.ts +144 -23
- package/dist/index.js +122 -10
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -395,7 +395,7 @@ function parseViolation(message) {
|
|
|
395
395
|
const rule = parts[1], rest = parts.slice(2).filter((p) => p !== "");
|
|
396
396
|
if (rest[0] === "AttributeDefinition" && rest[1] && rest[2]) return { rule, attribute: `${rest[2]}.${rest[1]}`, tag: rest.join("'") };
|
|
397
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 };
|
|
398
|
+
return { rule, attribute: token, tag: !token && rest.length > 1 ? rest.join("'") : null };
|
|
399
399
|
}
|
|
400
400
|
function failuresOf(response) {
|
|
401
401
|
return (response.commandFailures?.commands ?? []).map(({ error, ...echoed }) => ({
|
|
@@ -407,6 +407,7 @@ function failuresOf(response) {
|
|
|
407
407
|
}
|
|
408
408
|
function outcomeOf(response) {
|
|
409
409
|
return {
|
|
410
|
+
requestId: typeof response.requestId === "string" && response.requestId ? response.requestId : null,
|
|
410
411
|
created: (response.assetsCreated?.oidTokens ?? []).map(momentless),
|
|
411
412
|
modified: (response.assetsModified?.oidTokens ?? []).map(momentless),
|
|
412
413
|
operated: (response.assetsOperatedOn?.oidTokens ?? []).map(momentless),
|
|
@@ -415,13 +416,14 @@ function outcomeOf(response) {
|
|
|
415
416
|
durationSeconds: typeof response.durationSeconds === "number" ? response.durationSeconds : null
|
|
416
417
|
};
|
|
417
418
|
}
|
|
418
|
-
var emptyOutcome = () => ({ created: [], modified: [], operated: [], failures: [], queryResults: [], durationSeconds: null });
|
|
419
|
+
var emptyOutcome = () => ({ requestId: null, created: [], modified: [], operated: [], failures: [], queryResults: [], durationSeconds: null });
|
|
419
420
|
function mergeOutcome(into, done, offset) {
|
|
420
421
|
into.created.push(...done.created);
|
|
421
422
|
into.modified.push(...done.modified);
|
|
422
423
|
into.operated.push(...done.operated);
|
|
423
424
|
into.failures.push(...done.failures.map((f) => ({ ...f, index: f.index === null ? null : f.index + offset })));
|
|
424
425
|
into.queryResults.push(...done.queryResults);
|
|
426
|
+
into.requestId = null;
|
|
425
427
|
if (done.durationSeconds !== null) into.durationSeconds = (into.durationSeconds ?? 0) + done.durationSeconds;
|
|
426
428
|
return into;
|
|
427
429
|
}
|
|
@@ -1102,6 +1104,65 @@ async function reversible(transport, receipt, options = {}) {
|
|
|
1102
1104
|
return { commands, drifted, reactivated };
|
|
1103
1105
|
}
|
|
1104
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
|
+
|
|
1105
1166
|
// src/types/asset.ts
|
|
1106
1167
|
var ASSET_STATE_CODES = { Future: 0, Active: 64, Closed: 128, Dead: 192, Template: 200, OldBrokenDownStory: 208, Deprecated: 254, Deleted: 255 };
|
|
1107
1168
|
var STATE_OF_CODE = Object.fromEntries(Object.entries(ASSET_STATE_CODES).map(([name, code]) => [code, name]));
|
|
@@ -1427,6 +1488,8 @@ __export(rest1_exports, {
|
|
|
1427
1488
|
history: () => history,
|
|
1428
1489
|
list: () => list2,
|
|
1429
1490
|
momentless: () => momentless,
|
|
1491
|
+
operate: () => operate,
|
|
1492
|
+
template: () => template,
|
|
1430
1493
|
toQuery: () => toQuery
|
|
1431
1494
|
});
|
|
1432
1495
|
var toRef = (doc) => ({ _oid: momentless(doc.idref) });
|
|
@@ -1481,6 +1544,19 @@ async function get2(transport, oid, select) {
|
|
|
1481
1544
|
const doc = await transport.request({ method: "GET", path: `/rest-1.v1/Data/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, query: { sel: select?.join(",") }, responseType: "json" });
|
|
1482
1545
|
return fromAssetDocument(doc);
|
|
1483
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
|
+
}
|
|
1484
1560
|
var history = (transport, target, params) => {
|
|
1485
1561
|
const [type, id] = target.split(":");
|
|
1486
1562
|
const path = id ? `/rest-1.v1/Hist/${encodeURIComponent(type)}/${encodeURIComponent(id)}` : `/rest-1.v1/Hist/${encodeURIComponent(type)}`;
|
|
@@ -1578,16 +1654,39 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1578
1654
|
return localize(this.transport, tags, options);
|
|
1579
1655
|
}
|
|
1580
1656
|
/**
|
|
1581
|
-
* rest-1.v1, for what
|
|
1582
|
-
* true`), one asset by OID without a page,
|
|
1583
|
-
* moment)
|
|
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`).
|
|
1584
1665
|
*/
|
|
1585
1666
|
data = {
|
|
1586
1667
|
list: (type, params) => list2(this.transport, type, params),
|
|
1587
1668
|
get: (oid, select) => get2(this.transport, oid, select),
|
|
1588
|
-
history: (target, params) => history(this.transport, target, params)
|
|
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)
|
|
1589
1688
|
};
|
|
1590
|
-
/** @deprecated since 1.0.0-alpha.3: `client.query` / `client.queries`
|
|
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. */
|
|
1591
1690
|
queryV1 = {
|
|
1592
1691
|
query: (q) => this.query(q),
|
|
1593
1692
|
queries: (qs) => this.queries(qs)
|
|
@@ -1619,7 +1718,7 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1619
1718
|
await this.schema.ensureFor(commands);
|
|
1620
1719
|
return sendGuarded(this.transport, commands, { ...options, lookup: this.schema });
|
|
1621
1720
|
},
|
|
1622
|
-
/** @deprecated since 1.0.0-alpha.3: use `client.query` (or `client.queryPage`
|
|
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. */
|
|
1623
1722
|
query: async (q) => {
|
|
1624
1723
|
await this.schema.ensureFor([q]);
|
|
1625
1724
|
return query(this.transport, q, { lookup: this.schema });
|
|
@@ -1679,8 +1778,19 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1679
1778
|
};
|
|
1680
1779
|
/** Where a browser opens things on this instance: an asset's page, an attachment's bytes, an embedded image. */
|
|
1681
1780
|
urls = {
|
|
1682
|
-
/**
|
|
1683
|
-
|
|
1781
|
+
/**
|
|
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.
|
|
1786
|
+
*/
|
|
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)`);
|
|
1793
|
+
},
|
|
1684
1794
|
attachment: (id) => attachmentUrl(this.config.baseUrl, id),
|
|
1685
1795
|
embeddedImage: (id) => embeddedImageUrl(this.config.baseUrl, id)
|
|
1686
1796
|
};
|
|
@@ -1708,7 +1818,9 @@ export {
|
|
|
1708
1818
|
attachment_exports as attachment,
|
|
1709
1819
|
createFetchTransport,
|
|
1710
1820
|
describe as describeBatch,
|
|
1821
|
+
fields_exports as fields,
|
|
1711
1822
|
guard,
|
|
1823
|
+
importantName,
|
|
1712
1824
|
isAssetNumber,
|
|
1713
1825
|
isOidToken,
|
|
1714
1826
|
kindOf,
|