@versionone/agility-sdk 1.0.0-alpha.4 → 1.0.0-alpha.6
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 +62 -6
- package/README.md +8 -6
- package/dist/index.cjs +101 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +169 -96
- package/dist/index.d.ts +169 -96
- package/dist/index.js +101 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0-alpha.6
|
|
4
|
+
|
|
5
|
+
- The generated model is refreshed from meta.v1 26.3.2.13 (was
|
|
6
|
+
26.3.1.13): no type, attribute, flag or operation changed between the
|
|
7
|
+
two; only the version stamp moved.
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- **Node 22 or later** (`engines.node` is `>=22`, was `>=18`), and `dist`
|
|
12
|
+
is now built for Node 22+ (tsup's `target` was `node18`). Node 18
|
|
13
|
+
reached end of life in April 2025 and Node 20 in April 2026; neither
|
|
14
|
+
gets security fixes. CI tests the maintained LTS lines, 22, 24 and 26,
|
|
15
|
+
so a consumer on 18 or 20 was already untested. (The chat-bot consumer
|
|
16
|
+
runs 24 and the CLI consumer requires 22; the browser app consumer
|
|
17
|
+
does not run on Node.)
|
|
18
|
+
|
|
19
|
+
## 1.0.0-alpha.5
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Conditional schema reads** (for a browser app consumer). meta.v1 sends one
|
|
24
|
+
strong ETag for the whole schema (`"26.3.1.13.<stamp>"`, the same on
|
|
25
|
+
the document, a type and an attribute) and honours `If-None-Match` with
|
|
26
|
+
a 304. `client.meta.read({ ifNoneMatch })` and `readType(token, {
|
|
27
|
+
ifNoneMatch })` answer `{ value, etag }`, or null without a download when
|
|
28
|
+
the tag still matches; `client.meta.etag()` reads the tag alone from
|
|
29
|
+
the smallest resource. `TypeCache.etag` remembers the tag the loaded
|
|
30
|
+
types came with, and `client.schema.revalidate()` asks the server in
|
|
31
|
+
one small read and drops the loaded types only when the tag moved —
|
|
32
|
+
what a browser app's metamodel store does by hand. Transport: a request with
|
|
33
|
+
`acceptNotModified` resolves to undefined on a 304 after `onResponse`
|
|
34
|
+
sees it.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **Idempotent requests are resent once by default** (`retries` now
|
|
39
|
+
defaults to 1, was 0): a GET, a query.v1 read or a guarded batch that
|
|
40
|
+
says so, after a network failure or a 5xx, 150 ms later. A plain write
|
|
41
|
+
is never resent, as before. Pass `retries: 0` for the old behaviour.
|
|
42
|
+
(a CLI consumer: the doc promised a retry the default never made.) A 429 counts
|
|
43
|
+
as transient like a 5xx — the instance throttles per token — and a
|
|
44
|
+
Retry-After is honoured when sent, up to 30 s.
|
|
45
|
+
- **A rejected query's `details` are the exceptions alone**
|
|
46
|
+
(`{ type, message }[]`, exported as `QueryRejection`), not query.v1's
|
|
47
|
+
whole error document; `message` is unchanged. (a CLI consumer: a formatter that
|
|
48
|
+
printed details printed a JSON document.)
|
|
49
|
+
- **`CommandFailureDetail.echoed` is not enumerable**: still readable on
|
|
50
|
+
every failure for a corrected resend, but a serialised failure (a
|
|
51
|
+
receipt, a log line, a bot's result) carries index, message and
|
|
52
|
+
violation only. (chat-bot and CLI consumers: re-mapping failures to keep results
|
|
53
|
+
small.)
|
|
54
|
+
- **The committed `metamodel.json` names no instance**: every `href` is
|
|
55
|
+
spelled from `/meta.v1/` (`scripts/download-metamodel.mjs` rewrites
|
|
56
|
+
them on a pull). The generated types are unchanged; nothing in the SDK
|
|
57
|
+
reads an href.
|
|
58
|
+
|
|
3
59
|
## 1.0.0-alpha.4 — what the three adoptions asked for
|
|
4
60
|
|
|
5
61
|
### Added
|
|
6
62
|
|
|
7
|
-
- **Two narrow rest-1 paths** (
|
|
63
|
+
- **Two narrow rest-1 paths** (for a browser app consumer): `client.data.operate(oid,
|
|
8
64
|
op)` runs an operation for its result — api/asset performs a Copy but
|
|
9
65
|
reports only the source; rest-1 answers the copy's OID — and
|
|
10
66
|
`client.copy(oid)` is that for Copy. `client.data.template(type, {
|
|
@@ -22,9 +78,9 @@
|
|
|
22
78
|
module `fields`.
|
|
23
79
|
- **`client.urls.asset` takes an asset Number** as well as an OID
|
|
24
80
|
(`assetdetail.v1?Number=S-01001`): the readable form a CLI prints and a
|
|
25
|
-
chat message shows (
|
|
81
|
+
chat message shows (a CLI consumer, a chat-bot consumer).
|
|
26
82
|
- **`Outcome.requestId`**: the server's id for a batch, worth logging
|
|
27
|
-
(
|
|
83
|
+
(a chat-bot consumer). Null for an outcome merged from several sends.
|
|
28
84
|
- `outcomeOf` tolerating a partial envelope is now a stated contract
|
|
29
85
|
with a test: consumers' fakes may send only the fields they need.
|
|
30
86
|
|
|
@@ -32,7 +88,7 @@
|
|
|
32
88
|
|
|
33
89
|
- `parseViolation`: `tag` is null for the token spelling
|
|
34
90
|
(`Violation'Invalid'Epic.Scope`); it was the token, which is not a loc-2
|
|
35
|
-
tag (
|
|
91
|
+
tag (a CLI consumer).
|
|
36
92
|
- `CHANGELOG.md` ships in the npm package.
|
|
37
93
|
- Doc notes: `kind: "usage"` means nothing was sent (status 0), so an
|
|
38
94
|
error formatter should not blame the server; `client.query`'s resend
|
|
@@ -50,9 +106,9 @@
|
|
|
50
106
|
Alpha releases may break between versions. Every breaking change is listed
|
|
51
107
|
here with the one-line migration a consumer applies. A renamed export keeps
|
|
52
108
|
its old name as a `@deprecated` alias for one alpha and is removed in the
|
|
53
|
-
next; `1.0.0` follows
|
|
109
|
+
next; `1.0.0` follows the browser app consumer's adoption, and semver after that.
|
|
54
110
|
|
|
55
|
-
## 1.0.0-alpha.3 — the breaking batch
|
|
111
|
+
## 1.0.0-alpha.3 — the breaking batch
|
|
56
112
|
|
|
57
113
|
### Breaking
|
|
58
114
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @versionone/agility-sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for the Digital.ai Agility (formerly VersionOne) API. It works in Node (ESM or CommonJS) and the browser, ships its own types, and has no runtime dependencies.
|
|
3
|
+
TypeScript SDK for the Digital.ai Agility (formerly VersionOne) API. It works in Node 22 or later (ESM or CommonJS) and the browser, ships its own types, and has no runtime dependencies.
|
|
4
4
|
|
|
5
5
|
Not to be confused with `v1sdk`, the 2.x JavaScript SDK: this is a new API, not a compatible successor.
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ npm install @versionone/agility-sdk@alpha
|
|
|
15
15
|
```ts
|
|
16
16
|
import { AgilityApiClient } from "@versionone/agility-sdk";
|
|
17
17
|
|
|
18
|
-
const client = new AgilityApiClient({ baseUrl: "https://
|
|
18
|
+
const client = new AgilityApiClient({ baseUrl: "https://agility.example.com/MyInstance", accessToken: process.env.V1_ACCESS_TOKEN });
|
|
19
19
|
const stories = await client.query({ from: "Story", select: ["Name", "Estimate"], page: { size: 10, start: 0 } });
|
|
20
20
|
// stories[0].Estimate is a number | null; stories[0].Nmae does not compile
|
|
21
21
|
```
|
|
@@ -35,7 +35,7 @@ npm test
|
|
|
35
35
|
- `npm run typecheck` - run TypeScript type checks
|
|
36
36
|
- `npm run verify` - run typecheck + build + tests
|
|
37
37
|
- `npm run test` - run unit tests
|
|
38
|
-
- `npm run download:metamodel -- <baseAgilityUrl>` - refresh `metamodel.json` from an instance on the current release (instance-specific definitions stripped; `--raw` keeps them), then `npm run generate:types`. The cleanest source is a dev instance on a blank database (
|
|
38
|
+
- `npm run download:metamodel -- <baseAgilityUrl>` - refresh `metamodel.json` from an instance on the current release (instance-specific definitions stripped; `--raw` keeps them), then `npm run generate:types`. The cleanest source is a dev instance on a blank database (the product's dev tooling has a task for that); a hosted instance on the release gives the same file. The generated model is from meta.v1 **26.3.2.13** (hosted, 2026-11-14)
|
|
39
39
|
|
|
40
40
|
## Public API
|
|
41
41
|
|
|
@@ -52,7 +52,7 @@ Changes between alphas are in `CHANGELOG.md`, each with its migration line.
|
|
|
52
52
|
- `copy(oid)` - the Copy operation, answering the copy's OID (api/asset performs it but never says what it made); `data.operate(oid, op)` is the general form
|
|
53
53
|
- `fields.describe(type, scope)` - what a form needs: required attributes (meta), the ones the product shows there (`api/visibleFields`, custom fields included), the scheme's important fields and whether they are enforced at close
|
|
54
54
|
- `urls.asset(oidOrNumber)` / `attachment` / `embeddedImage` - where a browser opens each
|
|
55
|
-
- `data.list` / `get` / `history` / `template` - rest-1 reads: deleted assets, history, and a new asset's template for a context (`New/<Type>?ctx=`); `meta.*` - meta.v1 raw; `schema` - the translated, cached view; `localize` - loc-2 labels for tags (a violation's `tag` is one)
|
|
55
|
+
- `data.list` / `get` / `history` / `template` - rest-1 reads: deleted assets, history, and a new asset's template for a context (`New/<Type>?ctx=`); `meta.*` - meta.v1 raw, with conditional reads (`read` / `readType` with `ifNoneMatch`, `etag()`: meta.v1 sends one ETag for the whole schema); `schema` - the translated, cached view, `schema.revalidate()` drops it only when that tag moves; `localize` - loc-2 labels for tags (a violation's `tag` is one)
|
|
56
56
|
- Errors: everything thrown is an `ApiError` with a `kind` (`auth`, `forbidden`, `notFound`, `invalid`, `transient`, `aborted`, `commandFailure`, `protocol`, `usage`, `unknown`). A mistake caught before any request is a `UsageError`
|
|
57
57
|
- `TypeCache` - one instance's loaded type definitions, shared by many clients (`new AgilityApiClient(config, { types })`)
|
|
58
58
|
- `createFetchTransport` - configurable HTTP transport: `credentials` for same-origin cookie auth, `onUnauthorized` to re-authenticate on a 401 (resends idempotent requests once), and a per-request `signal`; `client.withSignal(signal)` ties a whole client to one
|
|
@@ -112,6 +112,8 @@ an approval, so a pushed tag waits for a maintainer before it stages. A
|
|
|
112
112
|
package must exist before it can be linked, so the very first version was
|
|
113
113
|
published once by hand by an `@versionone` org member.
|
|
114
114
|
|
|
115
|
-
##
|
|
115
|
+
## Contributing
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
Run `npm run verify` (typecheck, build, tests) before opening a pull request.
|
|
118
|
+
This repository is public: `CONTRIBUTING.md` says what may be committed
|
|
119
|
+
here and where working plans and consumer hand-offs live instead.
|
package/dist/index.cjs
CHANGED
|
@@ -185,10 +185,22 @@ function messageOf(data, status) {
|
|
|
185
185
|
return `HTTP ${status}`;
|
|
186
186
|
}
|
|
187
187
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
188
|
+
var MAX_RETRY_AFTER_MS = 3e4;
|
|
189
|
+
function retryDelay(response, attempt) {
|
|
190
|
+
const header = response.headers.get("retry-after");
|
|
191
|
+
if (header) {
|
|
192
|
+
const seconds = Number(header);
|
|
193
|
+
const ms = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(header) - Date.now();
|
|
194
|
+
if (Number.isFinite(ms) && ms > MAX_RETRY_AFTER_MS) return null;
|
|
195
|
+
if (Number.isFinite(ms) && ms > 0) return ms;
|
|
196
|
+
}
|
|
197
|
+
return 150 * (attempt + 1);
|
|
198
|
+
}
|
|
199
|
+
var retriable = (status) => status === 429 || status >= 500;
|
|
188
200
|
function createFetchTransport(config) {
|
|
189
201
|
const fetchImpl = config.fetchImpl ?? fetch;
|
|
190
202
|
const timeoutMs = config.timeoutMs ?? 6e4;
|
|
191
|
-
const retries = Math.max(0, config.retries ??
|
|
203
|
+
const retries = Math.max(0, config.retries ?? 1);
|
|
192
204
|
const authorization = config.authorization ?? (config.accessToken ? `Bearer ${config.accessToken}` : void 0);
|
|
193
205
|
const unauthorizedStatuses = config.unauthorizedStatuses ?? [401];
|
|
194
206
|
return {
|
|
@@ -231,6 +243,10 @@ function createFetchTransport(config) {
|
|
|
231
243
|
options.onResponse?.({ status: response.status, headers: response.headers });
|
|
232
244
|
return await readBody(response, responseType);
|
|
233
245
|
}
|
|
246
|
+
if (response.status === 304 && options.acceptNotModified) {
|
|
247
|
+
options.onResponse?.({ status: 304, headers: response.headers });
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
234
250
|
const data = await readBody(response, responseType === "bytes" ? "text" : responseType === "json" ? "auto" : responseType);
|
|
235
251
|
lastError = new ApiError(messageOf(data, response.status), response.status, void 0, data);
|
|
236
252
|
} catch (error) {
|
|
@@ -249,8 +265,10 @@ function createFetchTransport(config) {
|
|
|
249
265
|
}
|
|
250
266
|
break;
|
|
251
267
|
}
|
|
252
|
-
if (response.status
|
|
253
|
-
|
|
268
|
+
if (retriable(response.status) && attempt + 1 < attempts) {
|
|
269
|
+
const wait = retryDelay(response, attempt);
|
|
270
|
+
if (wait === null) break;
|
|
271
|
+
await delay(wait);
|
|
254
272
|
continue;
|
|
255
273
|
}
|
|
256
274
|
break;
|
|
@@ -457,6 +475,11 @@ function toNumber(value) {
|
|
|
457
475
|
const n = typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
458
476
|
return Number.isNaN(n) ? value : n;
|
|
459
477
|
}
|
|
478
|
+
function failureDetail(index, message, echoed) {
|
|
479
|
+
const detail = { index, message, violation: parseViolation(message) };
|
|
480
|
+
Object.defineProperty(detail, "echoed", { value: echoed, enumerable: false, writable: false });
|
|
481
|
+
return detail;
|
|
482
|
+
}
|
|
460
483
|
function parseViolation(message) {
|
|
461
484
|
const parts = message.trim().split("'");
|
|
462
485
|
if (parts[0] !== "Violation" || !parts[1]) return null;
|
|
@@ -466,12 +489,11 @@ function parseViolation(message) {
|
|
|
466
489
|
return { rule, attribute: token, tag: !token && rest.length > 1 ? rest.join("'") : null };
|
|
467
490
|
}
|
|
468
491
|
function failuresOf(response) {
|
|
469
|
-
return (response.commandFailures?.commands ?? []).map(({ error, ...echoed }) => (
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
echoed
|
|
473
|
-
|
|
474
|
-
}));
|
|
492
|
+
return (response.commandFailures?.commands ?? []).map(({ error, ...echoed }) => failureDetail(
|
|
493
|
+
typeof error === "object" && error && typeof error.sourceCommandIndex === "number" ? error.sourceCommandIndex : null,
|
|
494
|
+
typeof error === "string" ? error : error?.message || "command failed",
|
|
495
|
+
echoed
|
|
496
|
+
));
|
|
475
497
|
}
|
|
476
498
|
function outcomeOf(response) {
|
|
477
499
|
return {
|
|
@@ -489,7 +511,7 @@ function mergeOutcome(into, done, offset) {
|
|
|
489
511
|
into.created.push(...done.created);
|
|
490
512
|
into.modified.push(...done.modified);
|
|
491
513
|
into.operated.push(...done.operated);
|
|
492
|
-
into.failures.push(...done.failures.map((f) => (
|
|
514
|
+
into.failures.push(...done.failures.map((f) => failureDetail(f.index === null ? null : f.index + offset, f.message, f.echoed)));
|
|
493
515
|
into.queryResults.push(...done.queryResults);
|
|
494
516
|
into.requestId = null;
|
|
495
517
|
if (done.durationSeconds !== null) into.durationSeconds = (into.durationSeconds ?? 0) + done.durationSeconds;
|
|
@@ -1323,10 +1345,13 @@ async function resolveRef(transport, spec, options = {}) {
|
|
|
1323
1345
|
// src/wire/meta.ts
|
|
1324
1346
|
var meta_exports = {};
|
|
1325
1347
|
__export(meta_exports, {
|
|
1348
|
+
etag: () => etag,
|
|
1326
1349
|
getAssetType: () => getAssetType,
|
|
1327
1350
|
getAttributeDefinition: () => getAttributeDefinition,
|
|
1328
1351
|
getMetamodel: () => getMetamodel,
|
|
1329
1352
|
isMetamodelDocument: () => isMetamodelDocument,
|
|
1353
|
+
readAssetType: () => readAssetType,
|
|
1354
|
+
readMetamodel: () => readMetamodel,
|
|
1330
1355
|
toAssetType: () => toAssetType,
|
|
1331
1356
|
toAttributeDefinition: () => toAttributeDefinition,
|
|
1332
1357
|
toMetamodel: () => toMetamodel
|
|
@@ -1371,12 +1396,36 @@ function toMetamodel(doc) {
|
|
|
1371
1396
|
function isMetamodelDocument(value) {
|
|
1372
1397
|
return !!value && typeof value === "object" && typeof value.Version === "string" && !!value.AssetTypes && typeof value.AssetTypes === "object";
|
|
1373
1398
|
}
|
|
1399
|
+
var PROBE_PATH = "/meta.v1/BaseAsset/Name";
|
|
1400
|
+
async function conditional(transport, path, options) {
|
|
1401
|
+
let etag2 = null, status = 200;
|
|
1402
|
+
const onResponse = (r) => {
|
|
1403
|
+
status = r.status;
|
|
1404
|
+
etag2 = r.headers.get("etag");
|
|
1405
|
+
};
|
|
1406
|
+
const headers = options.ifNoneMatch ? { "if-none-match": options.ifNoneMatch } : void 0;
|
|
1407
|
+
const doc = await transport.request({ method: "GET", path, headers, responseType: "json", onResponse, acceptNotModified: !!options.ifNoneMatch });
|
|
1408
|
+
if (status === 304 || doc === void 0) return null;
|
|
1409
|
+
return { value: doc, etag: etag2 };
|
|
1410
|
+
}
|
|
1374
1411
|
async function getMetamodel(transport) {
|
|
1375
1412
|
return toMetamodel(await transport.request({ method: "GET", path: "/meta.v1", responseType: "json" }));
|
|
1376
1413
|
}
|
|
1414
|
+
async function readMetamodel(transport, options = {}) {
|
|
1415
|
+
const read2 = await conditional(transport, "/meta.v1", options);
|
|
1416
|
+
return read2 && { value: toMetamodel(read2.value), etag: read2.etag };
|
|
1417
|
+
}
|
|
1377
1418
|
async function getAssetType(transport, token) {
|
|
1378
1419
|
return toAssetType(await transport.request({ method: "GET", path: `/meta.v1/${encodeURIComponent(token)}`, responseType: "json" }));
|
|
1379
1420
|
}
|
|
1421
|
+
async function readAssetType(transport, token, options = {}) {
|
|
1422
|
+
const read2 = await conditional(transport, `/meta.v1/${encodeURIComponent(token)}`, options);
|
|
1423
|
+
return read2 && { value: toAssetType(read2.value), etag: read2.etag };
|
|
1424
|
+
}
|
|
1425
|
+
async function etag(transport, options = {}) {
|
|
1426
|
+
const read2 = await conditional(transport, PROBE_PATH, options);
|
|
1427
|
+
return read2 ? read2.etag : options.ifNoneMatch ?? null;
|
|
1428
|
+
}
|
|
1380
1429
|
async function getAttributeDefinition(transport, tokenref) {
|
|
1381
1430
|
const [type, attribute] = tokenref.split(".");
|
|
1382
1431
|
if (!type || !attribute) throw new UsageError(`"${tokenref}" is not an attribute token ("Story.Name")`);
|
|
@@ -1388,6 +1437,13 @@ var TypeCache = class _TypeCache {
|
|
|
1388
1437
|
/** Loaded types; null records that the instance has no such type, so it is not asked for again. */
|
|
1389
1438
|
types = /* @__PURE__ */ new Map();
|
|
1390
1439
|
loading = /* @__PURE__ */ new Map();
|
|
1440
|
+
/** The schema's tag the loaded types came with (meta.v1's ETag, one for the whole schema); null until a load, or for a seeded cache. */
|
|
1441
|
+
etag = null;
|
|
1442
|
+
/** Forget every loaded type (and the tag): the next query loads afresh. In-flight loads finish into the cache as before. */
|
|
1443
|
+
clear() {
|
|
1444
|
+
this.types.clear();
|
|
1445
|
+
this.etag = null;
|
|
1446
|
+
}
|
|
1391
1447
|
/** A cache seeded from a metamodel document. */
|
|
1392
1448
|
static fromMetamodel(metamodel) {
|
|
1393
1449
|
const cache = new _TypeCache();
|
|
@@ -1452,7 +1508,10 @@ var Schema = class _Schema {
|
|
|
1452
1508
|
this.types.set(token, null);
|
|
1453
1509
|
return null;
|
|
1454
1510
|
}
|
|
1455
|
-
const request =
|
|
1511
|
+
const request = readAssetType(this.transport, token).then((read2) => {
|
|
1512
|
+
if (read2?.etag) this.cache.etag = read2.etag;
|
|
1513
|
+
return read2 ? read2.value : null;
|
|
1514
|
+
}).catch((error) => {
|
|
1456
1515
|
if (error instanceof ApiError && error.status === 404) return null;
|
|
1457
1516
|
throw error;
|
|
1458
1517
|
}).then((type) => {
|
|
@@ -1462,6 +1521,23 @@ var Schema = class _Schema {
|
|
|
1462
1521
|
this.loading.set(token, request);
|
|
1463
1522
|
return request;
|
|
1464
1523
|
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Has the instance's schema changed since these types were loaded? One
|
|
1526
|
+
* small conditional read of meta.v1. Unchanged: nothing happens.
|
|
1527
|
+
* Changed (or never tagged, or the transport cannot see headers): the
|
|
1528
|
+
* loaded types are dropped, so the next query loads them again, and the
|
|
1529
|
+
* new tag is kept. Answers whether it changed and the current tag. A
|
|
1530
|
+
* schema without a transport (seeded) never changes.
|
|
1531
|
+
*/
|
|
1532
|
+
async revalidate() {
|
|
1533
|
+
if (!this.transport) return { changed: false, etag: this.cache.etag };
|
|
1534
|
+
const was = this.cache.etag;
|
|
1535
|
+
const now2 = await etag(this.transport, { ifNoneMatch: was });
|
|
1536
|
+
if (was && now2 === was) return { changed: false, etag: was };
|
|
1537
|
+
this.cache.clear();
|
|
1538
|
+
this.cache.etag = now2;
|
|
1539
|
+
return { changed: true, etag: now2 };
|
|
1540
|
+
}
|
|
1465
1541
|
/**
|
|
1466
1542
|
* Load whatever the queries in a batch need to have their rows translated:
|
|
1467
1543
|
* the `from` types, then the types behind every relation hop and sub-select,
|
|
@@ -1522,8 +1598,8 @@ __export(queryV1_exports, {
|
|
|
1522
1598
|
});
|
|
1523
1599
|
function rejected(error) {
|
|
1524
1600
|
const details = error.details;
|
|
1525
|
-
const
|
|
1526
|
-
return
|
|
1601
|
+
const rejections = (details?.exceptions ?? []).filter((e) => e && typeof e.message === "string" && e.message).map((e) => ({ type: typeof e.type === "string" ? e.type : null, message: e.message }));
|
|
1602
|
+
return rejections.length ? new ApiError(rejections.map((r) => r.message).join("; "), error.status, "QUERY_REJECTED", rejections) : error;
|
|
1527
1603
|
}
|
|
1528
1604
|
async function queries2(transport, qs, options = {}) {
|
|
1529
1605
|
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`);
|
|
@@ -1707,11 +1783,21 @@ var AgilityApiClient = class _AgilityApiClient {
|
|
|
1707
1783
|
return row;
|
|
1708
1784
|
}
|
|
1709
1785
|
// ---- the endpoints behind the reads -----------------------------------------------
|
|
1710
|
-
/**
|
|
1786
|
+
/**
|
|
1787
|
+
* meta.v1: the instance's schema, raw. (The translated, cached view is
|
|
1788
|
+
* `schema`; `schema.revalidate()` drops it when the schema's tag moves.)
|
|
1789
|
+
* `read` and `readType` are conditional: pass the tag a previous answer
|
|
1790
|
+
* carried and an unchanged schema answers null without a download —
|
|
1791
|
+
* meta.v1 sends one strong ETag for the whole schema. `etag()` reads that
|
|
1792
|
+
* tag alone, from the smallest resource.
|
|
1793
|
+
*/
|
|
1711
1794
|
meta = {
|
|
1712
1795
|
metamodel: () => getMetamodel(this.transport),
|
|
1796
|
+
read: (options) => readMetamodel(this.transport, options),
|
|
1713
1797
|
assetType: (token) => getAssetType(this.transport, token),
|
|
1714
|
-
|
|
1798
|
+
readType: (token, options) => readAssetType(this.transport, token, options),
|
|
1799
|
+
attributeDefinition: (tokenref) => getAttributeDefinition(this.transport, tokenref),
|
|
1800
|
+
etag: (options) => etag(this.transport, options)
|
|
1715
1801
|
};
|
|
1716
1802
|
/**
|
|
1717
1803
|
* loc-2.v1: labels for tags, in one request (meta's DisplayName values are
|