@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/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 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`).
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` read through query.v1. Removed in 1.0.0. */
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` for a total). Removed in 1.0.0. */
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
- /** 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)}`,
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,