@craftainer/crud-sdk 0.2.0 → 0.4.0
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 +28 -2
- package/dist/cache/index.d.mts +7 -4
- package/dist/cache/index.mjs +24 -9
- package/dist/cache/index.mjs.map +1 -1
- package/dist/{client-B8fSsx4L.d.mts → client-B--FzoDe.d.mts} +27 -6
- package/dist/{client-Bv0yCuwC.mjs → client-DkSP1q63.mjs} +68 -18
- package/dist/client-DkSP1q63.mjs.map +1 -0
- package/dist/codegen/cli.mjs +37 -4
- package/dist/codegen/cli.mjs.map +1 -1
- package/dist/{events-DpSWzSN9.d.mts → events-Bp__8_4V.d.mts} +17 -6
- package/dist/{events-Cqj9Rbon.mjs → events-DD5dodtF.mjs} +54 -9
- package/dist/events-DD5dodtF.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/tanstack/index.d.mts +6 -4
- package/dist/tanstack/index.mjs +9 -3
- package/dist/tanstack/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/client-Bv0yCuwC.mjs.map +0 -1
- package/dist/events-Cqj9Rbon.mjs.map +0 -1
|
@@ -142,6 +142,12 @@ interface QueryParams {
|
|
|
142
142
|
readonly limitParam?: string | undefined;
|
|
143
143
|
readonly sortParam?: string | undefined;
|
|
144
144
|
readonly includeArchived?: boolean | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* Only rows with `archived_at` set (list only; implies `include_archived`).
|
|
147
|
+
* Emitted only when `true`; combining `true` with `includeArchived: false`
|
|
148
|
+
* throws `InvalidQueryError`, as the server answers 422.
|
|
149
|
+
*/
|
|
150
|
+
readonly onlyArchived?: boolean | undefined;
|
|
145
151
|
readonly includeUnpublished?: boolean | undefined;
|
|
146
152
|
}
|
|
147
153
|
/** Percent-encodes everything but the RFC 3986 unreserved characters `A-Za-z0-9-._~`. */
|
|
@@ -157,10 +163,11 @@ declare function formatScalar(field: QueryField, value: unknown): string;
|
|
|
157
163
|
/**
|
|
158
164
|
* The query string (no leading `?`) for a list, get or bulk call. Order of
|
|
159
165
|
* parameters: `id`, filters sorted by (field, operator), sort, skip, limit,
|
|
160
|
-
* `include_archived`, `include_unpublished`. The sort/skip/limit names are
|
|
166
|
+
* `include_archived`, `only_archived`, `include_unpublished`. The sort/skip/limit names are
|
|
161
167
|
* `sortParam`/`skipParam`/`limitParam` (the manifest's `pagination`); the
|
|
162
168
|
* `id` parameter is always `id`. Flags are emitted whenever they are not
|
|
163
|
-
* `undefined
|
|
169
|
+
* `undefined`, except `only_archived`, which is emitted only when `true`
|
|
170
|
+
* (absent means false; it is a reserved key, not a filter).
|
|
164
171
|
*/
|
|
165
172
|
declare function buildQuery(fields: readonly QueryField[], params?: QueryParams): string;
|
|
166
173
|
//#endregion
|
|
@@ -169,20 +176,26 @@ declare function buildQuery(fields: readonly QueryField[], params?: QueryParams)
|
|
|
169
176
|
interface RequestOptions {
|
|
170
177
|
readonly signal?: AbortSignal | undefined;
|
|
171
178
|
}
|
|
172
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* Options of `list` and `iterAll`. `onlyArchived: true` returns only archived
|
|
181
|
+
* rows (it implies `includeArchived`; with `includeArchived: false` the call
|
|
182
|
+
* throws `InvalidQueryError` before any request). Non-archivable resources
|
|
183
|
+
* ignore it.
|
|
184
|
+
*/
|
|
173
185
|
interface ListOptions extends RequestOptions {
|
|
174
186
|
readonly filters?: Filters | undefined;
|
|
175
187
|
readonly sort?: Sort | undefined;
|
|
176
188
|
readonly skip?: number | undefined;
|
|
177
189
|
readonly limit?: number | undefined;
|
|
178
190
|
readonly includeArchived?: boolean | undefined;
|
|
191
|
+
readonly onlyArchived?: boolean | undefined;
|
|
179
192
|
readonly includeUnpublished?: boolean | undefined;
|
|
180
193
|
}
|
|
181
194
|
/** Options of `update` and `delete`: `ifMatch` is an ETag (`412` when stale). */
|
|
182
195
|
interface WriteOptions extends RequestOptions {
|
|
183
196
|
readonly ifMatch?: string | undefined;
|
|
184
197
|
}
|
|
185
|
-
/** Options of `stats`. */
|
|
198
|
+
/** Options of `stats`. There is no `onlyArchived` (the server rejects it on `/stats`). */
|
|
186
199
|
interface StatsOptions extends RequestOptions {
|
|
187
200
|
readonly bucket?: string | undefined;
|
|
188
201
|
readonly includeArchived?: boolean | undefined;
|
|
@@ -229,7 +242,7 @@ declare class ResourceClient<Read extends object = JsonObject, Create extends ob
|
|
|
229
242
|
/** The manifest entry this handle is bound to. */
|
|
230
243
|
readonly resource: Resource;
|
|
231
244
|
/** Use `CrudClient.resource`. */
|
|
232
|
-
constructor(baseUrl: string, resource: Resource, transport: Transport);
|
|
245
|
+
constructor(baseUrl: string, resource: Resource, transport: Transport, allowedOrigins?: readonly string[]);
|
|
233
246
|
/** One page of records. */
|
|
234
247
|
list(options?: ListOptions): Promise<Read[]>;
|
|
235
248
|
/** Every record, paging with `skip += limit` until a page shorter than `limit`. */
|
|
@@ -283,6 +296,14 @@ interface ClientOptions {
|
|
|
283
296
|
* cached data, and keep it constant across reloads of a persisted cache.
|
|
284
297
|
*/
|
|
285
298
|
readonly id?: string | undefined;
|
|
299
|
+
/**
|
|
300
|
+
* Exact origins (`https://files.example`) that manifest-supplied
|
|
301
|
+
* `base_path`, `paths.*` and `events.path` may resolve to besides
|
|
302
|
+
* `baseUrl`'s own. By default such a URL (absolute or protocol-relative) is
|
|
303
|
+
* refused with a `ManifestError`, because the token would follow it. For
|
|
304
|
+
* split-origin deployments only.
|
|
305
|
+
*/
|
|
306
|
+
readonly allowedOrigins?: readonly string[] | undefined;
|
|
286
307
|
}
|
|
287
308
|
/** Discovers the manifest and hands out {@link ResourceClient}s. */
|
|
288
309
|
declare class CrudClient {
|
|
@@ -313,4 +334,4 @@ declare class CrudClient {
|
|
|
313
334
|
declare function createClient(options: ClientOptions): CrudClient;
|
|
314
335
|
//#endregion
|
|
315
336
|
export { Pagination as A, Capabilities as C, JsonObject as D, GetJson as E, manifestUrlFromCatalog as F, parseManifest as I, Schemas as M, findResource as N, Manifest as O, loadManifest as P, CacheHints as S, FieldSpec as T, encodeParams as _, PredictOptions as a, AuthSpec as b, RestoreOptions as c, createClient as d, Filters as f, buildQuery as g, Sort as h, ListOptions as i, Resource as j, PROFILE as k, StatsOptions as l, QueryParams as m, ClientOptions as n, RequestOptions as o, QueryField as p, CrudClient as r, ResourceClient as s, BulkResult as t, WriteOptions as u, formatScalar as v, EventsSpec as w, CATALOG_PATH as x, quote as y };
|
|
316
|
-
//# sourceMappingURL=client-
|
|
337
|
+
//# sourceMappingURL=client-B--FzoDe.d.mts.map
|
|
@@ -423,9 +423,20 @@ const MAX_REGEX_LENGTH = 200;
|
|
|
423
423
|
function quote(text) {
|
|
424
424
|
return encodeURIComponent(text).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
425
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* {@link quote} for caller input: a lone surrogate (an emoji cut in half)
|
|
428
|
+
* cannot be UTF-8 encoded, which is an invalid argument, not a `URIError`.
|
|
429
|
+
*/
|
|
430
|
+
function encode(text) {
|
|
431
|
+
try {
|
|
432
|
+
return quote(text);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
throw new InvalidQueryError("a query value is not well-formed Unicode (lone surrogate)", { cause: error });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
426
437
|
/** Joins already-stringified pairs, encoding keys and values. */
|
|
427
438
|
function encodeParams(pairs) {
|
|
428
|
-
return pairs.map(([k, v]) => `${
|
|
439
|
+
return pairs.map(([k, v]) => `${encode(k)}=${encode(v)}`).join("&");
|
|
429
440
|
}
|
|
430
441
|
function formatDatetime(value) {
|
|
431
442
|
if (Number.isNaN(value.getTime())) return;
|
|
@@ -466,11 +477,11 @@ function filterPair(field, op, value) {
|
|
|
466
477
|
if (!Array.isArray(value) || value.length === 0) throw new InvalidQueryError(`"${key}" takes a non-empty array`);
|
|
467
478
|
const parts = value.map((item) => formatScalar(field, item));
|
|
468
479
|
if (parts.some((part) => part.includes(","))) throw new InvalidQueryError(`"${key}": a value contains a comma, which separates the list`);
|
|
469
|
-
return [key, parts.map(
|
|
480
|
+
return [key, parts.map((part) => encode(part)).join(",")];
|
|
470
481
|
}
|
|
471
482
|
const text = formatScalar(field, value);
|
|
472
483
|
if (op === "regex" && [...text].length > MAX_REGEX_LENGTH) throw new InvalidQueryError(`"${key}": a regex is limited to ${MAX_REGEX_LENGTH} characters`);
|
|
473
|
-
return [key,
|
|
484
|
+
return [key, encode(text)];
|
|
474
485
|
}
|
|
475
486
|
const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
476
487
|
function filterPairs(fields, filters) {
|
|
@@ -498,7 +509,7 @@ function sortValue(fields, sort) {
|
|
|
498
509
|
const name = part.startsWith("-") ? part.slice(1) : part;
|
|
499
510
|
const field = fields.get(name);
|
|
500
511
|
if (field === void 0 || !field.sortable) throw new InvalidQueryError(`cannot sort by "${name}"`);
|
|
501
|
-
return
|
|
512
|
+
return encode(part);
|
|
502
513
|
});
|
|
503
514
|
if (out.length === 0) throw new InvalidQueryError("sort is empty");
|
|
504
515
|
return out.join(",");
|
|
@@ -510,43 +521,78 @@ function intArg(name, value, minimum) {
|
|
|
510
521
|
/**
|
|
511
522
|
* The query string (no leading `?`) for a list, get or bulk call. Order of
|
|
512
523
|
* parameters: `id`, filters sorted by (field, operator), sort, skip, limit,
|
|
513
|
-
* `include_archived`, `include_unpublished`. The sort/skip/limit names are
|
|
524
|
+
* `include_archived`, `only_archived`, `include_unpublished`. The sort/skip/limit names are
|
|
514
525
|
* `sortParam`/`skipParam`/`limitParam` (the manifest's `pagination`); the
|
|
515
526
|
* `id` parameter is always `id`. Flags are emitted whenever they are not
|
|
516
|
-
* `undefined
|
|
527
|
+
* `undefined`, except `only_archived`, which is emitted only when `true`
|
|
528
|
+
* (absent means false; it is a reserved key, not a filter).
|
|
517
529
|
*/
|
|
518
530
|
function buildQuery(fields, params = {}) {
|
|
531
|
+
if (params.onlyArchived === true && params.includeArchived === false) throw new InvalidQueryError("onlyArchived cannot be combined with includeArchived: false");
|
|
519
532
|
const byName = new Map(fields.map((f) => [f.name, f]));
|
|
520
533
|
const parts = [];
|
|
521
534
|
if (params.recordId !== void 0) parts.push(`id=${intArg("recordId", params.recordId, 0)}`);
|
|
522
|
-
for (const [key, value] of filterPairs(byName, params.filters ?? {})) parts.push(`${
|
|
523
|
-
if (params.sort !== void 0) parts.push(`${
|
|
524
|
-
if (params.skip !== void 0) parts.push(`${
|
|
535
|
+
for (const [key, value] of filterPairs(byName, params.filters ?? {})) parts.push(`${encode(key)}=${value}`);
|
|
536
|
+
if (params.sort !== void 0) parts.push(`${encode(params.sortParam ?? "sort")}=${sortValue(byName, params.sort)}`);
|
|
537
|
+
if (params.skip !== void 0) parts.push(`${encode(params.skipParam ?? "skip")}=${intArg("skip", params.skip, 0)}`);
|
|
525
538
|
if (params.limit !== void 0) {
|
|
526
539
|
const text = intArg("limit", params.limit, 1);
|
|
527
540
|
if (params.maxLimit !== void 0 && params.limit > params.maxLimit) throw new InvalidQueryError(`limit ${params.limit} exceeds the server maximum ${params.maxLimit}`);
|
|
528
|
-
parts.push(`${
|
|
541
|
+
parts.push(`${encode(params.limitParam ?? "limit")}=${text}`);
|
|
529
542
|
}
|
|
530
543
|
if (params.includeArchived !== void 0) parts.push(`include_archived=${params.includeArchived}`);
|
|
544
|
+
if (params.onlyArchived === true) parts.push("only_archived=true");
|
|
531
545
|
if (params.includeUnpublished !== void 0) parts.push(`include_unpublished=${params.includeUnpublished}`);
|
|
532
546
|
return parts.join("&");
|
|
533
547
|
}
|
|
534
548
|
//#endregion
|
|
535
549
|
//#region src/client.ts
|
|
536
550
|
/** Fetch client: manifest-driven resource handles over a middleware chain. */
|
|
537
|
-
|
|
551
|
+
/**
|
|
552
|
+
* The origins of `allowedOrigins`, normalised (`https://a.example:443` and
|
|
553
|
+
* `https://a.example` are one). Anything that is not an http(s) origin is a
|
|
554
|
+
* configuration error.
|
|
555
|
+
*/
|
|
556
|
+
function normalizeOrigins(origins) {
|
|
557
|
+
const out = /* @__PURE__ */ new Set();
|
|
558
|
+
for (const value of origins) {
|
|
559
|
+
let origin;
|
|
560
|
+
try {
|
|
561
|
+
const url = new URL(value);
|
|
562
|
+
origin = (url.protocol === "http:" || url.protocol === "https:") && url.origin !== "null" ? url.origin : void 0;
|
|
563
|
+
} catch {
|
|
564
|
+
origin = void 0;
|
|
565
|
+
}
|
|
566
|
+
if (origin === void 0) throw new ClientError(`allowedOrigins entry "${value}" is not an http(s) origin`);
|
|
567
|
+
out.add(origin);
|
|
568
|
+
}
|
|
569
|
+
return out;
|
|
570
|
+
}
|
|
538
571
|
/** Builds the requests of one resource. */
|
|
539
572
|
var Plan = class {
|
|
540
573
|
#baseUrl;
|
|
574
|
+
#origins;
|
|
541
575
|
resource;
|
|
542
|
-
constructor(baseUrl, resource) {
|
|
576
|
+
constructor(baseUrl, resource, allowedOrigins) {
|
|
543
577
|
this.#baseUrl = baseUrl;
|
|
578
|
+
this.#origins = /* @__PURE__ */ new Set([...allowedOrigins, new URL(baseUrl).origin]);
|
|
544
579
|
this.resource = resource;
|
|
545
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Resolves a manifest-supplied path against `baseUrl`. The client sends its
|
|
583
|
+
* bearer token there, so a path that leaves baseUrl's origin (an absolute
|
|
584
|
+
* URL or a protocol-relative `//host`) is refused unless the client lists
|
|
585
|
+
* that origin in `allowedOrigins`.
|
|
586
|
+
*/
|
|
587
|
+
#url(path) {
|
|
588
|
+
const url = new URL(path, this.#baseUrl);
|
|
589
|
+
if (!this.#origins.has(url.origin)) throw new ManifestError(`resource "${this.resource.name}": the manifest path "${path}" points at another origin (${url.origin}), where the token would follow; pass allowedOrigins to the client to allow it`);
|
|
590
|
+
return url.href;
|
|
591
|
+
}
|
|
546
592
|
#call(method, op, path, query = "", extra = {}) {
|
|
547
593
|
return {
|
|
548
594
|
method,
|
|
549
|
-
url:
|
|
595
|
+
url: this.#url(path) + (query === "" ? "" : `?${query}`),
|
|
550
596
|
op,
|
|
551
597
|
resource: this.resource.name,
|
|
552
598
|
recordId: extra.recordId,
|
|
@@ -583,7 +629,7 @@ var Plan = class {
|
|
|
583
629
|
eventsUrl() {
|
|
584
630
|
const events = this.resource.capabilities.events;
|
|
585
631
|
if (events === null) throw new CapabilityError(`resource "${this.resource.name}" does not support events`);
|
|
586
|
-
return
|
|
632
|
+
return this.#url(events.path);
|
|
587
633
|
}
|
|
588
634
|
list(o) {
|
|
589
635
|
const query = this.#query({
|
|
@@ -592,6 +638,7 @@ var Plan = class {
|
|
|
592
638
|
skip: o.skip,
|
|
593
639
|
limit: o.limit,
|
|
594
640
|
includeArchived: o.includeArchived,
|
|
641
|
+
onlyArchived: o.onlyArchived,
|
|
595
642
|
includeUnpublished: o.includeUnpublished
|
|
596
643
|
});
|
|
597
644
|
return this.#call("GET", "list", this.#base, query);
|
|
@@ -682,9 +729,9 @@ var ResourceClient = class {
|
|
|
682
729
|
#plan;
|
|
683
730
|
#transport;
|
|
684
731
|
/** Use `CrudClient.resource`. */
|
|
685
|
-
constructor(baseUrl, resource, transport) {
|
|
732
|
+
constructor(baseUrl, resource, transport, allowedOrigins = []) {
|
|
686
733
|
this.resource = resource;
|
|
687
|
-
this.#plan = new Plan(baseUrl, resource);
|
|
734
|
+
this.#plan = new Plan(baseUrl, resource, normalizeOrigins(allowedOrigins));
|
|
688
735
|
this.#transport = transport;
|
|
689
736
|
}
|
|
690
737
|
async #one(call, options) {
|
|
@@ -779,6 +826,7 @@ var CrudClient = class {
|
|
|
779
826
|
#tokenProvider;
|
|
780
827
|
#middleware;
|
|
781
828
|
#manifestUrl;
|
|
829
|
+
#allowedOrigins;
|
|
782
830
|
#manifests = /* @__PURE__ */ new Map();
|
|
783
831
|
#transport;
|
|
784
832
|
constructor(options) {
|
|
@@ -788,6 +836,8 @@ var CrudClient = class {
|
|
|
788
836
|
this.#tokenProvider = options.tokenProvider;
|
|
789
837
|
this.#middleware = [...options.middleware ?? []];
|
|
790
838
|
this.#manifestUrl = options.manifestUrl;
|
|
839
|
+
this.#allowedOrigins = [...options.allowedOrigins ?? []];
|
|
840
|
+
normalizeOrigins(this.#allowedOrigins);
|
|
791
841
|
this.#transport = {
|
|
792
842
|
json: async (call, signal) => {
|
|
793
843
|
const response = await this.#send(call, signal);
|
|
@@ -910,7 +960,7 @@ var CrudClient = class {
|
|
|
910
960
|
async resource(name, apiVersion) {
|
|
911
961
|
const resource = findResource(await this.manifest(), name, apiVersion);
|
|
912
962
|
if (resource === void 0) throw new UnknownResourceError(`the manifest has no resource "${name}"${apiVersion === void 0 ? "" : ` ${apiVersion}`} for this caller`);
|
|
913
|
-
return new ResourceClient(this.baseUrl, resource, this.#transport);
|
|
963
|
+
return new ResourceClient(this.baseUrl, resource, this.#transport, this.#allowedOrigins);
|
|
914
964
|
}
|
|
915
965
|
};
|
|
916
966
|
/** Creates a client; the manifest loads on first use. */
|
|
@@ -920,4 +970,4 @@ function createClient(options) {
|
|
|
920
970
|
//#endregion
|
|
921
971
|
export { UnknownResourceError as A, NotFound as C, ResponseError as D, RateLimited as E, problemFromResponse as M, composeMiddleware as N, ServerError as O, ManifestError as S, ProblemError as T, ConnectionFailed as _, encodeParams as a, InvalidQueryError as b, CATALOG_PATH as c, loadManifest as d, manifestUrlFromCatalog as f, ClientError as g, CapabilityError as h, buildQuery as i, ValidationFailed as j, Unauthorized as k, PROFILE as l, BadRequest as m, ResourceClient as n, formatScalar as o, parseManifest as p, createClient as r, quote as s, CrudClient as t, findResource as u, CrudError as v, PreconditionFailed as w, Locked as x, Forbidden as y };
|
|
922
972
|
|
|
923
|
-
//# sourceMappingURL=client-
|
|
973
|
+
//# sourceMappingURL=client-DkSP1q63.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-DkSP1q63.mjs","names":["isObject"],"sources":["../src/interfaces.ts","../src/errors.ts","../src/manifest.ts","../src/query.ts","../src/client.ts"],"sourcesContent":["/**\n * Contracts shared by the client core (TS1) and the cache layer (TS2).\n * Fixed in TS0 (plan interface I3): change them only through the plan.\n */\n\n/** A value or a promise of it. */\nexport type MaybePromise<T> = T | Promise<T>;\n\n/** What a request does, as classified by the client. */\nexport type Op =\n | \"manifest\"\n | \"list\"\n | \"record\"\n | \"filters\"\n | \"stats\"\n | \"predict\"\n | \"revisions\"\n | \"create\"\n | \"update\"\n | \"delete\"\n | \"bulk_update\"\n | \"bulk_delete\"\n | \"clone\"\n | \"draft\"\n | \"publish\"\n | \"restore\"\n | \"events\";\n\n/**\n * One stored `200` response. Times are epoch milliseconds (`Date.now()`).\n * Structured-clone safe, so IndexedDB can store it as is.\n */\nexport interface CacheEntry {\n readonly status: number;\n readonly headers: ReadonlyArray<readonly [string, string]>;\n /** JSON text; the SDK only caches JSON. */\n readonly body: string;\n readonly storedAt: number;\n /** `<= storedAt` means \"always revalidate\". */\n readonly freshUntil: number;\n /** End of the stale-while-revalidate window (`>= freshUntil`). */\n readonly staleUntil: number;\n readonly etag?: string;\n}\n\n/**\n * Cache backend. Keys are opaque, slash-separated strings built by\n * `./cache`'s `cacheKey`\n * (`v1/{resource}/{kind}/{subject}/{apiVersion}/{scope}/{query}`, with `_`\n * when there is no apiVersion).\n * `ttlMs` is how long the backend may keep the entry (undefined = backend\n * default); freshness lives in `CacheEntry`. `clear(prefix)` removes every\n * key starting with `prefix`; `clear()` removes everything the store owns.\n * Methods may be sync or async. They must not reject for a missing key\n * (`get` resolves `undefined`), and a failing backend must not fail the\n * request: the cache layer swallows store errors and treats them as a miss.\n */\nexport interface CacheStore {\n get(key: string): MaybePromise<CacheEntry | undefined>;\n set(key: string, entry: CacheEntry, ttlMs?: number): MaybePromise<void>;\n delete(key: string): MaybePromise<void>;\n clear(prefix?: string): MaybePromise<void>;\n}\n\n/**\n * What the client knows about a request that a bare `Request` does not. The\n * client builds it for every call; middleware read it, so they never parse\n * URLs or read Authorization.\n */\nexport interface RequestContext {\n /** The request about to be sent; replace it by passing a new context to `next`. */\n readonly request: Request;\n /** Manifest name; `undefined` for the manifest document itself. */\n readonly resource: string | undefined;\n readonly op: Op;\n /** The record the request targets, when it targets one. */\n readonly recordId: number | undefined;\n /**\n * \"anon\", or the first 32 hex characters of the SHA-256 of the\n * Authorization credential actually sent.\n */\n readonly scope: string;\n /**\n * The resource's `api_version` from the manifest, or `null` when it has\n * none. Part of the cache key, so versions never share entries (the cache\n * layer renders `null` as `_`).\n */\n readonly apiVersion: string | null;\n readonly signal: AbortSignal | undefined;\n}\n\n/** Calls the next middleware, or the network when none is left. */\nexport type Next = (ctx: RequestContext) => Promise<Response>;\n\n/**\n * Onion middleware. In `chain = [a, b]`, `a` is outermost: the request runs\n * a -> b -> fetch and the response returns fetch -> b -> a. A middleware may\n * replace `ctx.request` (pass `{ ...ctx, request }` to `next`), return\n * without calling `next` (short-circuit, e.g. a fresh cache hit), or replace\n * the response (e.g. a `304` rebuilt from the cache). It must not consume\n * `response.body` unless it returns a different `Response`; use\n * `response.clone()` to read it.\n */\nexport type Middleware = (ctx: RequestContext, next: Next) => Promise<Response>;\n\n/**\n * Returns the bearer token, or `null`/`undefined` for an anonymous call.\n * `refresh: true` is passed once after a `401`, to force a new token.\n */\nexport type TokenProvider = (opts: {\n readonly refresh: boolean;\n}) => MaybePromise<string | null | undefined>;\n\n/** One record-mutation event from `GET {basePath}/events` (the server's event envelope). */\nexport interface CrudEvent {\n /** The server's event label (e.g. \"hero\"), not the manifest name. */\n readonly resource: string;\n readonly recordId: number;\n readonly action: \"create\" | \"update\" | \"delete\" | \"restore\";\n /** The read view of the record after the change. */\n readonly snapshot: Readonly<Record<string, unknown>>;\n /** ISO 8601, UTC. */\n readonly timestamp: string;\n}\n\n/**\n * Folds `chain` (outermost first) around `terminal`, usually the network\n * `fetch`, into one `Next`. Each middleware's `next` is the rest of the\n * chain; an empty chain is `terminal`. A throwing middleware yields a\n * rejected promise.\n */\nexport function composeMiddleware(\n chain: readonly Middleware[],\n terminal: Next,\n): Next {\n const dispatch = async (\n index: number,\n ctx: RequestContext,\n ): Promise<Response> => {\n const middleware = chain[index];\n if (middleware === undefined) {\n return terminal(ctx);\n }\n return middleware(ctx, (next) => dispatch(index + 1, next));\n };\n return (ctx) => dispatch(0, ctx);\n}\n","/**\n * Error hierarchy: client-side errors and RFC 9457 problem details (ADR-0004).\n * Mirrors the Python SDK class for class.\n */\n\n/** Base class of every error this package throws on purpose. */\nexport class CrudError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n/** The call was refused before any request was sent. */\nexport class ClientError extends CrudError {}\n\n/** The resource does not support the operation (manifest capability or path missing). */\nexport class CapabilityError extends ClientError {}\n\n/** A filter, sort or paging argument is not valid for the resource. */\nexport class InvalidQueryError extends ClientError {}\n\n/** The discovery document or the manifest is missing, unusable or of an unknown version. */\nexport class ManifestError extends ClientError {}\n\n/** The manifest lists no such resource for this caller. */\nexport class UnknownResourceError extends ClientError {}\n\n/** A successful response whose body is not the JSON the client expects; `cause` holds the parse error. */\nexport class ResponseError extends CrudError {}\n\n/** The request never produced a response; `cause` holds the transport error. */\nexport class ConnectionFailed extends CrudError {}\n\n/** One normalized validation error (`422`). */\nexport interface ProblemDetail {\n readonly loc: readonly (string | number)[];\n readonly msg: string;\n readonly type: string;\n}\n\n/** Members of a {@link ProblemError}. */\nexport interface ProblemInit {\n readonly status: number;\n readonly title: string;\n readonly detail?: string | undefined;\n readonly type?: string | undefined;\n readonly instance?: string | undefined;\n readonly errors?: readonly ProblemDetail[] | undefined;\n readonly headers?: Headers | undefined;\n readonly response?: Response | undefined;\n}\n\nconst NO_TYPE = \"about:blank\";\n\n/** An error response, as RFC 9457 problem details. */\nexport class ProblemError extends CrudError {\n readonly status: number;\n readonly title: string;\n readonly detail: string | undefined;\n readonly type: string;\n readonly instance: string | undefined;\n readonly errors: readonly ProblemDetail[];\n readonly headers: Headers;\n readonly response: Response | undefined;\n\n constructor(init: ProblemInit) {\n const errors = init.errors ?? [];\n let message = `${init.status} ${init.title}`;\n if (init.detail) {\n message += `: ${init.detail}`;\n } else if (errors.length > 0) {\n message += `: ${errors.map((e) => `${e.loc.join(\".\")}: ${e.msg}`).join(\"; \")}`;\n }\n super(message);\n this.status = init.status;\n this.title = init.title;\n this.detail = init.detail;\n this.type = init.type ?? NO_TYPE;\n this.instance = init.instance;\n this.errors = errors;\n this.headers = init.headers ?? new Headers();\n this.response = init.response;\n }\n}\n\n/** 400. */\nexport class BadRequest extends ProblemError {}\n\n/** 401; `wwwAuthenticate` keeps the challenge. */\nexport class Unauthorized extends ProblemError {\n get wwwAuthenticate(): string | undefined {\n return this.headers.get(\"www-authenticate\") ?? undefined;\n }\n}\n\n/** 403. */\nexport class Forbidden extends ProblemError {}\n\n/** 404. */\nexport class NotFound extends ProblemError {}\n\n/** 412: a stale `If-Match`. */\nexport class PreconditionFailed extends ProblemError {}\n\n/** 422; `errors` holds normalized `{loc, msg, type}` entries. */\nexport class ValidationFailed extends ProblemError {}\n\n/** 423. */\nexport class Locked extends ProblemError {}\n\n/** 429; `retryAfter` is the `Retry-After` header in seconds, when it is a number. */\nexport class RateLimited extends ProblemError {\n get retryAfter(): number | undefined {\n const raw = this.headers.get(\"retry-after\");\n if (raw === null || raw.trim() === \"\") {\n return undefined;\n }\n const seconds = Number(raw);\n return Number.isNaN(seconds) ? undefined : seconds;\n }\n}\n\n/** 5xx. */\nexport class ServerError extends ProblemError {}\n\nconst BY_STATUS: ReadonlyMap<number, typeof ProblemError> = new Map([\n [400, BadRequest],\n [401, Unauthorized],\n [403, Forbidden],\n [404, NotFound],\n [412, PreconditionFailed],\n [422, ValidationFailed],\n [423, Locked],\n [429, RateLimited],\n]);\n\n// Python's `http.HTTPStatus` phrases, so both SDKs synthesize the same title.\nconst PHRASES: ReadonlyMap<number, string> = new Map([\n [100, \"Continue\"],\n [101, \"Switching Protocols\"],\n [102, \"Processing\"],\n [103, \"Early Hints\"],\n [200, \"OK\"],\n [201, \"Created\"],\n [202, \"Accepted\"],\n [203, \"Non-Authoritative Information\"],\n [204, \"No Content\"],\n [205, \"Reset Content\"],\n [206, \"Partial Content\"],\n [207, \"Multi-Status\"],\n [208, \"Already Reported\"],\n [226, \"IM Used\"],\n [300, \"Multiple Choices\"],\n [301, \"Moved Permanently\"],\n [302, \"Found\"],\n [303, \"See Other\"],\n [304, \"Not Modified\"],\n [305, \"Use Proxy\"],\n [307, \"Temporary Redirect\"],\n [308, \"Permanent Redirect\"],\n [400, \"Bad Request\"],\n [401, \"Unauthorized\"],\n [402, \"Payment Required\"],\n [403, \"Forbidden\"],\n [404, \"Not Found\"],\n [405, \"Method Not Allowed\"],\n [406, \"Not Acceptable\"],\n [407, \"Proxy Authentication Required\"],\n [408, \"Request Timeout\"],\n [409, \"Conflict\"],\n [410, \"Gone\"],\n [411, \"Length Required\"],\n [412, \"Precondition Failed\"],\n [413, \"Content Too Large\"],\n [414, \"URI Too Long\"],\n [415, \"Unsupported Media Type\"],\n [416, \"Range Not Satisfiable\"],\n [417, \"Expectation Failed\"],\n [418, \"I'm a Teapot\"],\n [421, \"Misdirected Request\"],\n [422, \"Unprocessable Content\"],\n [423, \"Locked\"],\n [424, \"Failed Dependency\"],\n [425, \"Too Early\"],\n [426, \"Upgrade Required\"],\n [428, \"Precondition Required\"],\n [429, \"Too Many Requests\"],\n [431, \"Request Header Fields Too Large\"],\n [451, \"Unavailable For Legal Reasons\"],\n [500, \"Internal Server Error\"],\n [501, \"Not Implemented\"],\n [502, \"Bad Gateway\"],\n [503, \"Service Unavailable\"],\n [504, \"Gateway Timeout\"],\n [505, \"HTTP Version Not Supported\"],\n [506, \"Variant Also Negotiates\"],\n [507, \"Insufficient Storage\"],\n [508, \"Loop Detected\"],\n [510, \"Not Extended\"],\n [511, \"Network Authentication Required\"],\n]);\n\ntype Obj = Record<string, unknown>;\n\nconst isObject = (value: unknown): value is Obj =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nfunction normalizeErrors(items: readonly unknown[]): ProblemDetail[] {\n return items.map((item) => {\n if (isObject(item)) {\n return {\n loc: Array.isArray(item[\"loc\"])\n ? (item[\"loc\"] as (string | number)[])\n : [],\n msg: String(item[\"msg\"] ?? \"\"),\n type: String(item[\"type\"] ?? \"\"),\n };\n }\n return { loc: [], msg: String(item), type: \"\" };\n });\n}\n\n/**\n * Builds the matching `ProblemError` subclass from an error response. A body\n * that is not a JSON object yields a synthesized problem from the status\n * phrase. Reads a clone, so `response.body` stays readable.\n */\nexport async function problemFromResponse(\n response: Response,\n): Promise<ProblemError> {\n const status = response.status;\n const phrase = PHRASES.get(status) ?? \"Error\";\n let parsed: unknown;\n try {\n parsed = JSON.parse(await response.clone().text());\n } catch {\n parsed = undefined;\n }\n const body: Obj = isObject(parsed) ? parsed : {};\n const rawDetail = body[\"detail\"];\n const rawErrors = Array.isArray(rawDetail) ? rawDetail : body[\"errors\"];\n const Cls =\n BY_STATUS.get(status) ?? (status >= 500 ? ServerError : ProblemError);\n const instance = body[\"instance\"];\n return new Cls({\n status,\n title: String(body[\"title\"] || phrase),\n detail: typeof rawDetail === \"string\" ? rawDetail : undefined,\n type: String(body[\"type\"] || NO_TYPE),\n instance: typeof instance === \"string\" ? instance : undefined,\n errors: Array.isArray(rawErrors) ? normalizeErrors(rawErrors) : [],\n headers: response.headers,\n response,\n });\n}\n","/**\n * Typed models of the CRUD manifest (v1) and discovery via the api-catalog.\n * Members keep their wire names (`skip_param`, `max_age`, ...). Members this\n * version does not know are ignored, as the manifest spec requires.\n */\nimport { ManifestError } from \"./errors.ts\";\n\nexport const PROFILE = \"https://craftainer.github.io/schemas/crud-manifest/v1\";\nexport const CATALOG_PATH = \"/.well-known/api-catalog\";\n\n/** A JSON object. */\nexport type JsonObject = Record<string, unknown>;\n\n/** One filterable and sortable field. */\nexport interface FieldSpec {\n readonly name: string;\n readonly value_type: string;\n readonly filter_ops: readonly string[];\n readonly sortable: boolean;\n readonly choices: readonly string[] | null;\n readonly numeric: boolean;\n readonly categorical: boolean;\n readonly read_only: boolean;\n readonly required: boolean;\n}\n\n/** Offset paging parameters and bounds. */\nexport interface Pagination {\n readonly style: string;\n readonly skip_param: string;\n readonly limit_param: string;\n readonly sort_param: string;\n readonly default_limit: number;\n readonly max_limit: number;\n readonly total: boolean;\n}\n\n/** The SSE stream of a resource. */\nexport interface EventsSpec {\n readonly transport: string;\n readonly path: string;\n /** The server-side event label (not the manifest name). */\n readonly resource: string;\n readonly resume_param: string;\n readonly replay: string;\n readonly keepalive_seconds: number;\n readonly actions: readonly string[];\n}\n\n/** What the resource supports. */\nexport interface Capabilities {\n readonly bulk_update: boolean;\n readonly bulk_delete: boolean;\n readonly clone: boolean;\n readonly draft_publish: boolean;\n readonly archive_restore: boolean;\n readonly revisions: boolean;\n readonly events: EventsSpec | null;\n readonly stats: boolean;\n readonly predict: boolean;\n readonly conditional_requests: boolean;\n readonly owner_scoped: boolean;\n readonly schedulable: boolean;\n}\n\n/** Authentication scheme and roles. */\nexport interface AuthSpec {\n readonly scheme: string;\n readonly oidc_discovery_url: string | null;\n readonly public_read: boolean;\n readonly read_roles: readonly string[];\n readonly write_roles: readonly string[];\n readonly delete_roles: readonly string[];\n}\n\n/** The manifest `cache` hints of a resource. */\nexport interface CacheHints {\n readonly etag: boolean;\n readonly max_age: number;\n readonly stats_ttl: number | null;\n readonly stale_while_revalidate: number;\n readonly list_etag: string | null;\n readonly record_etag: string | null;\n readonly revalidate_only: boolean;\n}\n\n/** JSON Schemas of the record views (plain objects). */\nexport interface Schemas {\n readonly read: JsonObject;\n readonly create: JsonObject;\n readonly update: JsonObject;\n readonly draft: JsonObject | null;\n}\n\n/** One resource (`name` + `api_version`). */\nexport interface Resource {\n readonly name: string;\n readonly title: string;\n readonly api_version: string;\n readonly base_path: string;\n readonly formats: Readonly<Record<string, string>>;\n readonly id_field: string;\n readonly schemas: Schemas;\n readonly fields: readonly FieldSpec[];\n readonly pagination: Pagination;\n readonly capabilities: Capabilities;\n readonly paths: Readonly<Record<string, string>>;\n readonly bulk: { readonly max_matched: number };\n readonly auth: AuthSpec;\n readonly cache: CacheHints;\n}\n\n/** The manifest document, as filtered to the caller. */\nexport interface Manifest {\n readonly manifest_version: string;\n readonly service: { readonly name: string; readonly version: string };\n readonly resources: readonly Resource[];\n}\n\nconst isObject = (value: unknown): value is JsonObject =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nfunction bad(where: string, key: string, what: string): never {\n throw new ManifestError(`${where}.${key}: ${what}`);\n}\n\nfunction str(obj: JsonObject, key: string, where: string): string {\n const value = obj[key];\n return typeof value === \"string\"\n ? value\n : bad(where, key, \"missing or not a string\");\n}\n\nfunction bool(obj: JsonObject, key: string, where: string): boolean {\n const value = obj[key];\n return typeof value === \"boolean\"\n ? value\n : bad(where, key, \"missing or not a boolean\");\n}\n\nfunction num(obj: JsonObject, key: string, where: string): number {\n const value = obj[key];\n return typeof value === \"number\" && Number.isFinite(value)\n ? value\n : bad(where, key, \"missing or not a number\");\n}\n\nfunction int(obj: JsonObject, key: string, where: string): number {\n const value = num(obj, key, where);\n return Number.isInteger(value) ? value : bad(where, key, \"not an integer\");\n}\n\nfunction obj(parent: JsonObject, key: string, where: string): JsonObject {\n const value = parent[key];\n return isObject(value) ? value : bad(where, key, \"missing or not an object\");\n}\n\nfunction list(\n parent: JsonObject,\n key: string,\n where: string,\n): readonly unknown[] {\n const value = parent[key];\n return Array.isArray(value)\n ? value\n : bad(where, key, \"missing or not a list\");\n}\n\nfunction strs(\n parent: JsonObject,\n key: string,\n where: string,\n): readonly string[] {\n const items = list(parent, key, where);\n return items.every((item) => typeof item === \"string\")\n ? (items as readonly string[])\n : bad(where, key, \"expected a list of strings\");\n}\n\n/** A member that may be absent or `null`. */\nfunction opt<T>(\n parent: JsonObject,\n key: string,\n where: string,\n read: (parent: JsonObject, key: string, where: string) => T,\n): T | null {\n return parent[key] === undefined || parent[key] === null\n ? null\n : read(parent, key, where);\n}\n\nfunction stringMap(\n parent: JsonObject,\n key: string,\n where: string,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(obj(parent, key, where))) {\n if (typeof v === \"string\") {\n out[k] = v;\n }\n }\n return out;\n}\n\nfunction objects(parent: JsonObject, key: string, where: string): JsonObject[] {\n return list(parent, key, where).map((item, i) =>\n isObject(item) ? item : bad(`${where}.${key}`, `[${i}]`, \"not an object\"),\n );\n}\n\nfunction parseField(o: JsonObject, w: string): FieldSpec {\n return {\n name: str(o, \"name\", w),\n value_type: str(o, \"value_type\", w),\n filter_ops: strs(o, \"filter_ops\", w),\n sortable: bool(o, \"sortable\", w),\n choices: opt(o, \"choices\", w, strs),\n numeric: bool(o, \"numeric\", w),\n categorical: bool(o, \"categorical\", w),\n read_only: bool(o, \"read_only\", w),\n required: bool(o, \"required\", w),\n };\n}\n\nfunction parseEvents(o: JsonObject, w: string): EventsSpec {\n return {\n transport: str(o, \"transport\", w),\n path: str(o, \"path\", w),\n resource: str(o, \"resource\", w),\n resume_param: str(o, \"resume_param\", w),\n replay: str(o, \"replay\", w),\n keepalive_seconds: num(o, \"keepalive_seconds\", w),\n actions: strs(o, \"actions\", w),\n };\n}\n\nfunction parseCapabilities(o: JsonObject, w: string): Capabilities {\n return {\n bulk_update: bool(o, \"bulk_update\", w),\n bulk_delete: bool(o, \"bulk_delete\", w),\n clone: bool(o, \"clone\", w),\n draft_publish: bool(o, \"draft_publish\", w),\n archive_restore: bool(o, \"archive_restore\", w),\n revisions: bool(o, \"revisions\", w),\n events: opt(o, \"events\", w, (p, k, ww) =>\n parseEvents(obj(p, k, ww), `${ww}.${k}`),\n ),\n stats: bool(o, \"stats\", w),\n predict: bool(o, \"predict\", w),\n conditional_requests: bool(o, \"conditional_requests\", w),\n owner_scoped: bool(o, \"owner_scoped\", w),\n schedulable: bool(o, \"schedulable\", w),\n };\n}\n\nfunction parseResource(o: JsonObject, w: string): Resource {\n const schemas = obj(o, \"schemas\", w);\n const pagination = obj(o, \"pagination\", w);\n const auth = obj(o, \"auth\", w);\n const cache = obj(o, \"cache\", w);\n const sw = `${w}.schemas`;\n return {\n name: str(o, \"name\", w),\n title: str(o, \"title\", w),\n api_version: str(o, \"api_version\", w),\n base_path: str(o, \"base_path\", w),\n formats: stringMap(o, \"formats\", w),\n id_field: str(o, \"id_field\", w),\n schemas: {\n read: obj(schemas, \"read\", sw),\n create: obj(schemas, \"create\", sw),\n update: obj(schemas, \"update\", sw),\n draft: opt(schemas, \"draft\", sw, obj),\n },\n fields: objects(o, \"fields\", w).map((f, i) =>\n parseField(f, `${w}.fields[${i}]`),\n ),\n pagination: {\n style: str(pagination, \"style\", w),\n skip_param: str(pagination, \"skip_param\", w),\n limit_param: str(pagination, \"limit_param\", w),\n sort_param: str(pagination, \"sort_param\", w),\n default_limit: int(pagination, \"default_limit\", w),\n max_limit: int(pagination, \"max_limit\", w),\n total: bool(pagination, \"total\", w),\n },\n capabilities: parseCapabilities(\n obj(o, \"capabilities\", w),\n `${w}.capabilities`,\n ),\n paths: stringMap(o, \"paths\", w),\n bulk: { max_matched: int(obj(o, \"bulk\", w), \"max_matched\", `${w}.bulk`) },\n auth: {\n scheme: str(auth, \"scheme\", w),\n oidc_discovery_url: opt(auth, \"oidc_discovery_url\", w, str),\n public_read: bool(auth, \"public_read\", w),\n read_roles: strs(auth, \"read_roles\", w),\n write_roles: strs(auth, \"write_roles\", w),\n delete_roles: strs(auth, \"delete_roles\", w),\n },\n cache: {\n etag: bool(cache, \"etag\", w),\n max_age: int(cache, \"max_age\", w),\n stats_ttl: opt(cache, \"stats_ttl\", w, int),\n stale_while_revalidate: int(cache, \"stale_while_revalidate\", w),\n list_etag: opt(cache, \"list_etag\", w, str),\n record_etag: opt(cache, \"record_etag\", w, str),\n revalidate_only: bool(cache, \"revalidate_only\", w),\n },\n };\n}\n\n/** Parses a manifest document; unknown members are ignored, unknown versions refused. */\nexport function parseManifest(document: unknown): Manifest {\n if (!isObject(document)) {\n throw new ManifestError(\"the manifest is not a JSON object\");\n }\n const version = str(document, \"manifest_version\", \"manifest\");\n if (version !== \"1\") {\n throw new ManifestError(`unsupported manifest_version \"${version}\"`);\n }\n const service = obj(document, \"service\", \"manifest\");\n return {\n manifest_version: version,\n service: {\n name: str(service, \"name\", \"manifest.service\"),\n version: str(service, \"version\", \"manifest.service\"),\n },\n resources: objects(document, \"resources\", \"manifest\").map((r, i) =>\n parseResource(r, `resources[${i}]`),\n ),\n };\n}\n\n/**\n * The resource called `name`: the one with `apiVersion` when given, else the\n * highest `api_version` (`v2` beats `v1`); `undefined` when there is none.\n */\nexport function findResource(\n manifest: Manifest,\n name: string,\n apiVersion?: string,\n): Resource | undefined {\n const rank = (r: Resource): number =>\n Number.parseInt(r.api_version.slice(1), 10) || 0;\n let best: Resource | undefined;\n for (const r of manifest.resources) {\n if (\n r.name === name &&\n (apiVersion === undefined || r.api_version === apiVersion)\n ) {\n if (best === undefined || rank(r) > rank(best)) {\n best = r;\n }\n }\n }\n return best;\n}\n\n/** The absolute manifest URL: the first `describedby` link with the manifest profile. */\nexport function manifestUrlFromCatalog(\n linkset: unknown,\n catalogUrl: string,\n): string {\n const entries = isObject(linkset) ? linkset[\"linkset\"] : undefined;\n for (const entry of Array.isArray(entries) ? entries : []) {\n const links = isObject(entry) ? entry[\"describedby\"] : undefined;\n for (const link of Array.isArray(links) ? links : []) {\n if (\n isObject(link) &&\n link[\"profile\"] === PROFILE &&\n typeof link[\"href\"] === \"string\"\n ) {\n const url = new URL(link[\"href\"], catalogUrl);\n if (url.origin !== new URL(catalogUrl).origin) {\n // The client sends its bearer token to this URL: stay on the server's origin.\n throw new ManifestError(\n `the api-catalog points at another origin (${url.origin}); pass manifestUrl to use it`,\n );\n }\n return url.href;\n }\n }\n }\n throw new ManifestError(\n `the api-catalog has no describedby link with profile ${PROFILE}`,\n );\n}\n\n/** Fetches a JSON document; `catalog` marks the api-catalog request (kept out of the manifest cache). */\nexport type GetJson = (url: string, catalog: boolean) => Promise<unknown>;\n\n/**\n * Loads and parses the manifest, via api-catalog discovery from `baseUrl` or\n * a direct `manifestUrl`.\n */\nexport async function loadManifest(\n getJson: GetJson,\n baseUrl: string,\n manifestUrl?: string,\n): Promise<Manifest> {\n let url = manifestUrl;\n if (url === undefined) {\n const catalogUrl = new URL(CATALOG_PATH, baseUrl).href;\n url = manifestUrlFromCatalog(await getJson(catalogUrl, true), catalogUrl);\n }\n return parseManifest(await getJson(url, false));\n}\n","/**\n * Query-string builder with `crud_query` semantics, validated against the\n * manifest fields. Produces the same bytes as the Python SDK.\n */\nimport { InvalidQueryError } from \"./errors.ts\";\nimport type { FieldSpec } from \"./manifest.ts\";\n\nconst MAX_REGEX_LENGTH = 200;\n\n/** The part of a manifest field the builder needs. */\nexport type QueryField = Pick<\n FieldSpec,\n \"name\" | \"value_type\" | \"filter_ops\" | \"sortable\" | \"choices\"\n>;\n\n/** Filter arguments: `field` (equality, emitted bare) or `field__op` -> value. */\nexport type Filters = Readonly<Record<string, unknown>>;\n\n/** `\"name,-other\"` or `[\"name\", \"-other\"]`. */\nexport type Sort = string | readonly string[];\n\n/** Everything {@link buildQuery} takes beside the fields. */\nexport interface QueryParams {\n readonly recordId?: number | undefined;\n readonly filters?: Filters | undefined;\n readonly sort?: Sort | undefined;\n readonly skip?: number | undefined;\n readonly limit?: number | undefined;\n readonly maxLimit?: number | undefined;\n readonly skipParam?: string | undefined;\n readonly limitParam?: string | undefined;\n readonly sortParam?: string | undefined;\n readonly includeArchived?: boolean | undefined;\n /**\n * Only rows with `archived_at` set (list only; implies `include_archived`).\n * Emitted only when `true`; combining `true` with `includeArchived: false`\n * throws `InvalidQueryError`, as the server answers 422.\n */\n readonly onlyArchived?: boolean | undefined;\n readonly includeUnpublished?: boolean | undefined;\n}\n\n/** Percent-encodes everything but the RFC 3986 unreserved characters `A-Za-z0-9-._~`. */\nexport function quote(text: string): string {\n return encodeURIComponent(text).replace(\n /[!'()*]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\n/**\n * {@link quote} for caller input: a lone surrogate (an emoji cut in half)\n * cannot be UTF-8 encoded, which is an invalid argument, not a `URIError`.\n */\nfunction encode(text: string): string {\n try {\n return quote(text);\n } catch (error) {\n // `encodeURIComponent` throws only `URIError`.\n throw new InvalidQueryError(\n \"a query value is not well-formed Unicode (lone surrogate)\",\n { cause: error },\n );\n }\n}\n\n/** Joins already-stringified pairs, encoding keys and values. */\nexport function encodeParams(\n pairs: readonly (readonly [string, string])[],\n): string {\n return pairs.map(([k, v]) => `${encode(k)}=${encode(v)}`).join(\"&\");\n}\n\nfunction formatDatetime(value: Date): string | undefined {\n if (Number.isNaN(value.getTime())) {\n return undefined;\n }\n const iso = value.toISOString();\n if (iso.length !== 24) {\n return undefined; // expanded years (outside 0000-9999)\n }\n const millis = iso.slice(20, 23).replace(/0+$/, \"\");\n return millis === \"\"\n ? `${iso.slice(0, 19)}Z`\n : `${iso.slice(0, 19)}.${millis}Z`;\n}\n\nfunction formatDate(value: string): string | undefined {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n return undefined;\n }\n const parsed = new Date(`${value}T00:00:00Z`);\n return Number.isNaN(parsed.getTime()) ||\n parsed.toISOString().slice(0, 10) !== value\n ? undefined\n : value;\n}\n\nfunction scalarText(kind: string, value: unknown): string | undefined {\n switch (kind) {\n case \"integer\":\n return typeof value === \"number\" && Number.isSafeInteger(value)\n ? String(value)\n : undefined;\n case \"number\":\n return typeof value === \"number\" && Number.isFinite(value)\n ? String(value)\n : undefined;\n case \"boolean\":\n return typeof value === \"boolean\" ? String(value) : undefined;\n case \"date\":\n return typeof value === \"string\" ? formatDate(value) : undefined;\n case \"datetime\":\n return value instanceof Date ? formatDatetime(value) : undefined;\n default:\n return typeof value === \"string\" ? value : undefined;\n }\n}\n\n/**\n * Wire form of one value for `field`; throws `InvalidQueryError` on a type\n * mismatch. Dates are `YYYY-MM-DD` strings, datetimes `Date`s (UTC, with\n * millisecond precision, trailing zeros trimmed).\n */\nexport function formatScalar(field: QueryField, value: unknown): string {\n const text = scalarText(field.value_type, value);\n if (text === undefined) {\n throw new InvalidQueryError(\n `field \"${field.name}\" (${field.value_type}) cannot take ${String(value)}`,\n );\n }\n if (\n field.value_type === \"enum\" &&\n field.choices !== null &&\n !field.choices.includes(text)\n ) {\n throw new InvalidQueryError(\n `field \"${field.name}\" accepts only ${JSON.stringify(field.choices)}, not \"${text}\"`,\n );\n }\n return text;\n}\n\nfunction filterPair(\n field: QueryField,\n op: string,\n value: unknown,\n): [string, string] {\n const key = op === \"eq\" ? field.name : `${field.name}__${op}`;\n if (op === \"in\") {\n if (!Array.isArray(value) || value.length === 0) {\n throw new InvalidQueryError(`\"${key}\" takes a non-empty array`);\n }\n const parts = value.map((item: unknown) => formatScalar(field, item));\n if (parts.some((part) => part.includes(\",\"))) {\n throw new InvalidQueryError(\n `\"${key}\": a value contains a comma, which separates the list`,\n );\n }\n return [key, parts.map((part) => encode(part)).join(\",\")];\n }\n const text = formatScalar(field, value);\n if (op === \"regex\" && [...text].length > MAX_REGEX_LENGTH) {\n throw new InvalidQueryError(\n `\"${key}\": a regex is limited to ${MAX_REGEX_LENGTH} characters`,\n );\n }\n return [key, encode(text)];\n}\n\nconst compare = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);\n\nfunction filterPairs(\n fields: ReadonlyMap<string, QueryField>,\n filters: Filters,\n): [string, string][] {\n const parsed = new Map<\n string,\n { name: string; op: string; pair: [string, string] }\n >();\n for (const [rawKey, value] of Object.entries(filters)) {\n const at = rawKey.lastIndexOf(\"__\");\n const name = at < 0 ? rawKey : rawKey.slice(0, at);\n const op = at < 0 ? \"eq\" : rawKey.slice(at + 2);\n const field = fields.get(name);\n if (field === undefined) {\n throw new InvalidQueryError(`unknown filter field \"${name}\"`);\n }\n if (!field.filter_ops.includes(op)) {\n throw new InvalidQueryError(\n `field \"${name}\" does not support the \"${op}\" operator`,\n );\n }\n if (name === \"id\" && op === \"eq\") {\n throw new InvalidQueryError(\n \"filter on id with recordId, not an equality filter\",\n );\n }\n const id = `${name}\\u0000${op}`;\n if (parsed.has(id)) {\n throw new InvalidQueryError(`filter \"${name}\" \"${op}\" given twice`);\n }\n parsed.set(id, { name, op, pair: filterPair(field, op, value) });\n }\n return [...parsed.values()]\n .sort((a, b) => compare(a.name, b.name) || compare(a.op, b.op))\n .map((entry) => entry.pair);\n}\n\nfunction sortValue(\n fields: ReadonlyMap<string, QueryField>,\n sort: Sort,\n): string {\n const parts = typeof sort === \"string\" ? sort.split(\",\") : sort;\n const out = parts.map((part) => {\n const name = part.startsWith(\"-\") ? part.slice(1) : part;\n const field = fields.get(name);\n if (field === undefined || !field.sortable) {\n throw new InvalidQueryError(`cannot sort by \"${name}\"`);\n }\n return encode(part);\n });\n if (out.length === 0) {\n throw new InvalidQueryError(\"sort is empty\");\n }\n return out.join(\",\");\n}\n\nfunction intArg(name: string, value: unknown, minimum: number): string {\n if (\n typeof value !== \"number\" ||\n !Number.isSafeInteger(value) ||\n value < minimum\n ) {\n throw new InvalidQueryError(`${name} must be an integer >= ${minimum}`);\n }\n return String(value);\n}\n\n/**\n * The query string (no leading `?`) for a list, get or bulk call. Order of\n * parameters: `id`, filters sorted by (field, operator), sort, skip, limit,\n * `include_archived`, `only_archived`, `include_unpublished`. The sort/skip/limit names are\n * `sortParam`/`skipParam`/`limitParam` (the manifest's `pagination`); the\n * `id` parameter is always `id`. Flags are emitted whenever they are not\n * `undefined`, except `only_archived`, which is emitted only when `true`\n * (absent means false; it is a reserved key, not a filter).\n */\nexport function buildQuery(\n fields: readonly QueryField[],\n params: QueryParams = {},\n): string {\n if (params.onlyArchived === true && params.includeArchived === false) {\n throw new InvalidQueryError(\n \"onlyArchived cannot be combined with includeArchived: false\",\n );\n }\n const byName = new Map(fields.map((f) => [f.name, f]));\n const parts: string[] = [];\n if (params.recordId !== undefined) {\n parts.push(`id=${intArg(\"recordId\", params.recordId, 0)}`);\n }\n for (const [key, value] of filterPairs(byName, params.filters ?? {})) {\n parts.push(`${encode(key)}=${value}`); // values arrive encoded; `in` keeps its commas\n }\n if (params.sort !== undefined) {\n parts.push(\n `${encode(params.sortParam ?? \"sort\")}=${sortValue(byName, params.sort)}`,\n );\n }\n if (params.skip !== undefined) {\n parts.push(\n `${encode(params.skipParam ?? \"skip\")}=${intArg(\"skip\", params.skip, 0)}`,\n );\n }\n if (params.limit !== undefined) {\n const text = intArg(\"limit\", params.limit, 1);\n if (params.maxLimit !== undefined && params.limit > params.maxLimit) {\n throw new InvalidQueryError(\n `limit ${params.limit} exceeds the server maximum ${params.maxLimit}`,\n );\n }\n parts.push(`${encode(params.limitParam ?? \"limit\")}=${text}`);\n }\n if (params.includeArchived !== undefined) {\n parts.push(`include_archived=${params.includeArchived}`);\n }\n if (params.onlyArchived === true) {\n parts.push(\"only_archived=true\");\n }\n if (params.includeUnpublished !== undefined) {\n parts.push(`include_unpublished=${params.includeUnpublished}`);\n }\n return parts.join(\"&\");\n}\n","/** Fetch client: manifest-driven resource handles over a middleware chain. */\nimport { cacheScope } from \"./auth.ts\";\nimport {\n CapabilityError,\n ClientError,\n ConnectionFailed,\n InvalidQueryError,\n ManifestError,\n ResponseError,\n UnknownResourceError,\n problemFromResponse,\n} from \"./errors.ts\";\nimport { composeMiddleware } from \"./interfaces.ts\";\nimport type { Middleware, Op, TokenProvider } from \"./interfaces.ts\";\nimport { findResource, loadManifest } from \"./manifest.ts\";\nimport type { JsonObject, Manifest, Resource } from \"./manifest.ts\";\nimport { buildQuery, encodeParams } from \"./query.ts\";\nimport type { Filters, QueryParams, Sort } from \"./query.ts\";\n\n/** Per-call options. */\nexport interface RequestOptions {\n readonly signal?: AbortSignal | undefined;\n}\n\n/**\n * Options of `list` and `iterAll`. `onlyArchived: true` returns only archived\n * rows (it implies `includeArchived`; with `includeArchived: false` the call\n * throws `InvalidQueryError` before any request). Non-archivable resources\n * ignore it.\n */\nexport interface ListOptions extends RequestOptions {\n readonly filters?: Filters | undefined;\n readonly sort?: Sort | undefined;\n readonly skip?: number | undefined;\n readonly limit?: number | undefined;\n readonly includeArchived?: boolean | undefined;\n readonly onlyArchived?: boolean | undefined;\n readonly includeUnpublished?: boolean | undefined;\n}\n\n/** Options of `update` and `delete`: `ifMatch` is an ETag (`412` when stale). */\nexport interface WriteOptions extends RequestOptions {\n readonly ifMatch?: string | undefined;\n}\n\n/** Options of `stats`. There is no `onlyArchived` (the server rejects it on `/stats`). */\nexport interface StatsOptions extends RequestOptions {\n readonly bucket?: string | undefined;\n readonly includeArchived?: boolean | undefined;\n readonly includeUnpublished?: boolean | undefined;\n}\n\n/** Options of `predict`. */\nexport interface PredictOptions extends RequestOptions {\n readonly field?: string | undefined;\n readonly periods?: number | undefined;\n readonly bucket?: string | undefined;\n}\n\n/** Options of `restore`: exactly one of `id` and `filters`. */\nexport interface RestoreOptions extends RequestOptions {\n readonly id?: number | undefined;\n readonly filters?: Filters | undefined;\n}\n\n/** The result of a bulk call. */\nexport interface BulkResult {\n readonly matched: number;\n readonly ids: readonly number[];\n}\n\nexport interface Call {\n readonly method: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n readonly url: string;\n readonly op: Op;\n readonly resource: string | undefined;\n readonly recordId: number | undefined;\n readonly apiVersion: string | null;\n readonly json?: unknown;\n readonly headers?: Readonly<Record<string, string>>;\n}\n\n/** What a {@link ResourceClient} needs from its client. */\nexport interface Transport {\n json(call: Call, signal: AbortSignal | undefined): Promise<unknown>;\n none(call: Call, signal: AbortSignal | undefined): Promise<void>;\n}\n\n/**\n * The origins of `allowedOrigins`, normalised (`https://a.example:443` and\n * `https://a.example` are one). Anything that is not an http(s) origin is a\n * configuration error.\n */\nfunction normalizeOrigins(origins: readonly string[]): ReadonlySet<string> {\n const out = new Set<string>();\n for (const value of origins) {\n let origin: string | undefined;\n try {\n const url = new URL(value);\n origin =\n (url.protocol === \"http:\" || url.protocol === \"https:\") &&\n url.origin !== \"null\"\n ? url.origin\n : undefined;\n } catch {\n origin = undefined;\n }\n if (origin === undefined) {\n throw new ClientError(\n `allowedOrigins entry \"${value}\" is not an http(s) origin`,\n );\n }\n out.add(origin);\n }\n return out;\n}\n\n/** Builds the requests of one resource. */\nclass Plan {\n readonly #baseUrl: string;\n readonly #origins: ReadonlySet<string>;\n readonly resource: Resource;\n\n constructor(\n baseUrl: string,\n resource: Resource,\n allowedOrigins: ReadonlySet<string>,\n ) {\n this.#baseUrl = baseUrl;\n this.#origins = new Set([...allowedOrigins, new URL(baseUrl).origin]);\n this.resource = resource;\n }\n\n /**\n * Resolves a manifest-supplied path against `baseUrl`. The client sends its\n * bearer token there, so a path that leaves baseUrl's origin (an absolute\n * URL or a protocol-relative `//host`) is refused unless the client lists\n * that origin in `allowedOrigins`.\n */\n #url(path: string): string {\n const url = new URL(path, this.#baseUrl);\n if (!this.#origins.has(url.origin)) {\n throw new ManifestError(\n `resource \"${this.resource.name}\": the manifest path \"${path}\" points at another origin (${url.origin}), ` +\n \"where the token would follow; pass allowedOrigins to the client to allow it\",\n );\n }\n return url.href;\n }\n\n #call(\n method: Call[\"method\"],\n op: Op,\n path: string,\n query = \"\",\n extra: {\n recordId?: number | undefined;\n json?: unknown;\n ifMatch?: string | undefined;\n } = {},\n ): Call {\n const url = this.#url(path) + (query === \"\" ? \"\" : `?${query}`);\n return {\n method,\n url,\n op,\n resource: this.resource.name,\n recordId: extra.recordId,\n apiVersion: this.resource.api_version,\n ...(extra.json === undefined ? {} : { json: extra.json }),\n ...(extra.ifMatch === undefined\n ? {}\n : { headers: { \"If-Match\": extra.ifMatch } }),\n };\n }\n\n #query(params: QueryParams = {}): string {\n const paging = this.resource.pagination;\n return buildQuery(this.resource.fields, {\n maxLimit: paging.max_limit,\n skipParam: paging.skip_param,\n limitParam: paging.limit_param,\n sortParam: paging.sort_param,\n ...params,\n });\n }\n\n #subPath(key: string, supported: boolean): string {\n const path = this.resource.paths[key];\n if (!supported || path === undefined) {\n throw new CapabilityError(\n `resource \"${this.resource.name}\" does not support ${key}`,\n );\n }\n return path;\n }\n\n #need(supported: boolean, what: string): void {\n if (!supported) {\n throw new CapabilityError(\n `resource \"${this.resource.name}\" does not support ${what}`,\n );\n }\n }\n\n #bulkFilters(filters: Filters): string {\n if (Object.keys(filters).length === 0) {\n throw new InvalidQueryError(\"a bulk operation needs at least one filter\");\n }\n return this.#query({ filters });\n }\n\n get #base(): string {\n return this.resource.base_path;\n }\n\n eventsUrl(): string {\n const events = this.resource.capabilities.events;\n if (events === null) {\n throw new CapabilityError(\n `resource \"${this.resource.name}\" does not support events`,\n );\n }\n return this.#url(events.path);\n }\n\n list(o: ListOptions): Call {\n const query = this.#query({\n filters: o.filters,\n sort: o.sort,\n skip: o.skip,\n limit: o.limit,\n includeArchived: o.includeArchived,\n onlyArchived: o.onlyArchived,\n includeUnpublished: o.includeUnpublished,\n });\n return this.#call(\"GET\", \"list\", this.#base, query);\n }\n\n get(id: number): Call {\n return this.#call(\n \"GET\",\n \"record\",\n this.#base,\n this.#query({ recordId: id }),\n {\n recordId: id,\n },\n );\n }\n\n create(data: unknown): Call {\n return this.#call(\"POST\", \"create\", this.#base, \"\", { json: data });\n }\n\n update(id: number, data: unknown, ifMatch: string | undefined): Call {\n return this.#call(\n \"PATCH\",\n \"update\",\n this.#base,\n this.#query({ recordId: id }),\n {\n recordId: id,\n json: data,\n ifMatch,\n },\n );\n }\n\n updateMany(filters: Filters, data: unknown): Call {\n this.#need(this.resource.capabilities.bulk_update, \"bulk_update\");\n return this.#call(\n \"PATCH\",\n \"bulk_update\",\n this.#base,\n this.#bulkFilters(filters),\n {\n json: data,\n },\n );\n }\n\n delete(id: number, ifMatch: string | undefined): Call {\n return this.#call(\n \"DELETE\",\n \"delete\",\n this.#base,\n this.#query({ recordId: id }),\n {\n recordId: id,\n ifMatch,\n },\n );\n }\n\n deleteMany(filters: Filters): Call {\n this.#need(this.resource.capabilities.bulk_delete, \"bulk_delete\");\n return this.#call(\n \"DELETE\",\n \"bulk_delete\",\n this.#base,\n this.#bulkFilters(filters),\n );\n }\n\n restore(id: number | undefined, filters: Filters | undefined): Call {\n const path = this.#subPath(\n \"restore\",\n this.resource.capabilities.archive_restore,\n );\n if ((id === undefined) === (filters === undefined)) {\n throw new InvalidQueryError(\n \"restore takes exactly one of id and filters\",\n );\n }\n const query =\n filters === undefined\n ? this.#query({ recordId: id })\n : this.#bulkFilters(filters);\n return this.#call(\"POST\", \"restore\", path, query, { recordId: id });\n }\n\n stats(o: StatsOptions): Call {\n const path = this.#subPath(\"stats\", this.resource.capabilities.stats);\n const pairs: [string, string][] =\n o.bucket === undefined ? [] : [[\"bucket\", o.bucket]];\n if (o.includeArchived !== undefined) {\n pairs.push([\"include_archived\", String(o.includeArchived)]);\n }\n if (o.includeUnpublished !== undefined) {\n pairs.push([\"include_unpublished\", String(o.includeUnpublished)]);\n }\n return this.#call(\"GET\", \"stats\", path, encodeParams(pairs));\n }\n\n predict(o: PredictOptions): Call {\n const path = this.#subPath(\"predict\", this.resource.capabilities.predict);\n const pairs: [string, string][] = [];\n if (o.field !== undefined) {\n const spec = this.resource.fields.find((f) => f.name === o.field);\n if (spec === undefined || !spec.numeric) {\n throw new InvalidQueryError(\n `cannot predict \"${o.field}\": not a numeric field`,\n );\n }\n pairs.push([\"field\", o.field]);\n }\n if (o.periods !== undefined) {\n if (!Number.isSafeInteger(o.periods) || o.periods < 1) {\n throw new InvalidQueryError(\"periods must be an integer >= 1\");\n }\n pairs.push([\"periods\", String(o.periods)]);\n }\n if (o.bucket !== undefined) {\n pairs.push([\"bucket\", o.bucket]);\n }\n return this.#call(\"GET\", \"predict\", path, encodeParams(pairs));\n }\n\n clone(id: number): Call {\n const path = this.#subPath(\"clone\", this.resource.capabilities.clone);\n return this.#call(\"POST\", \"clone\", path, this.#query({ recordId: id }), {\n recordId: id,\n });\n }\n\n draft(data: unknown): Call {\n const path = this.#subPath(\n \"draft\",\n this.resource.capabilities.draft_publish,\n );\n return this.#call(\"POST\", \"draft\", path, \"\", { json: data });\n }\n\n publish(id: number): Call {\n const path = this.#subPath(\n \"publish\",\n this.resource.capabilities.draft_publish,\n );\n return this.#call(\"POST\", \"publish\", path, this.#query({ recordId: id }), {\n recordId: id,\n });\n }\n\n filters(): Call {\n return this.#call(\"GET\", \"filters\", this.#subPath(\"filters\", true));\n }\n\n revisions(id: number): Call {\n const path = this.#subPath(\n \"revisions\",\n this.resource.capabilities.revisions,\n );\n return this.#call(\"GET\", \"revisions\", path, this.#query({ recordId: id }), {\n recordId: id,\n });\n }\n}\n\n/**\n * Operations on one resource. The type parameters are the record views;\n * `crud-sdk-codegen` writes interfaces to pass for them (they are not\n * checked against the manifest at runtime).\n */\nexport class ResourceClient<\n Read extends object = JsonObject,\n Create extends object = JsonObject,\n Update extends object = JsonObject,\n> {\n /** The manifest entry this handle is bound to. */\n readonly resource: Resource;\n readonly #plan: Plan;\n readonly #transport: Transport;\n\n /** Use `CrudClient.resource`. */\n constructor(\n baseUrl: string,\n resource: Resource,\n transport: Transport,\n allowedOrigins: readonly string[] = [],\n ) {\n this.resource = resource;\n this.#plan = new Plan(baseUrl, resource, normalizeOrigins(allowedOrigins));\n this.#transport = transport;\n }\n\n async #one<T>(call: Call, options: RequestOptions): Promise<T> {\n return (await this.#transport.json(call, options.signal)) as T;\n }\n\n /** One page of records. */\n async list(options: ListOptions = {}): Promise<Read[]> {\n return this.#one(this.#plan.list(options), options);\n }\n\n /** Every record, paging with `skip += limit` until a page shorter than `limit`. */\n async *iterAll(\n options: ListOptions = {},\n ): AsyncGenerator<Read, void, undefined> {\n const size = options.limit ?? this.resource.pagination.default_limit;\n let skip = options.skip ?? 0;\n for (;;) {\n const page = await this.list({ ...options, skip, limit: size });\n yield* page;\n if (page.length < size) {\n return;\n }\n skip += size;\n }\n }\n\n /** One record. */\n async get(id: number, options: RequestOptions = {}): Promise<Read> {\n return this.#one(this.#plan.get(id), options);\n }\n\n /** Creates a record. */\n async create(data: Create, options: RequestOptions = {}): Promise<Read> {\n return this.#one(this.#plan.create(data), options);\n }\n\n /** Patches one record; `ifMatch` is an ETag (`412` when stale). */\n async update(\n id: number,\n data: Update,\n options: WriteOptions = {},\n ): Promise<Read> {\n return this.#one(this.#plan.update(id, data, options.ifMatch), options);\n }\n\n /** Patches every record matching `filters`. */\n async updateMany(\n filters: Filters,\n data: Update,\n options: RequestOptions = {},\n ): Promise<BulkResult> {\n return this.#one(this.#plan.updateMany(filters, data), options);\n }\n\n /** Deletes one record; `ifMatch` as for `update`. */\n async delete(id: number, options: WriteOptions = {}): Promise<void> {\n await this.#transport.none(\n this.#plan.delete(id, options.ifMatch),\n options.signal,\n );\n }\n\n /** Deletes every record matching `filters`. */\n async deleteMany(\n filters: Filters,\n options: RequestOptions = {},\n ): Promise<BulkResult> {\n return this.#one(this.#plan.deleteMany(filters), options);\n }\n\n /** Restores archived records, by `id` or by `filters`. */\n async restore(options: RestoreOptions): Promise<BulkResult> {\n return this.#one(this.#plan.restore(options.id, options.filters), options);\n }\n\n /** Aggregates over the resource. */\n async stats(options: StatsOptions = {}): Promise<JsonObject> {\n return this.#one(this.#plan.stats(options), options);\n }\n\n /** A forecast. */\n async predict(options: PredictOptions = {}): Promise<JsonObject> {\n return this.#one(this.#plan.predict(options), options);\n }\n\n /** Copies a record; returns the copy. */\n async clone(id: number, options: RequestOptions = {}): Promise<Read> {\n return this.#one(this.#plan.clone(id), options);\n }\n\n /** Creates a draft (needs `draft_publish`). */\n async createDraft(data: Create, options: RequestOptions = {}): Promise<Read> {\n return this.#one(this.#plan.draft(data), options);\n }\n\n /** Publishes a draft (needs `draft_publish`). */\n async publish(id: number, options: RequestOptions = {}): Promise<Read> {\n return this.#one(this.#plan.publish(id), options);\n }\n\n /** The server's filter metadata (`kind` instead of `value_type`). */\n async filters(options: RequestOptions = {}): Promise<JsonObject[]> {\n return this.#one(this.#plan.filters(), options);\n }\n\n /** Revisions of one record, newest first. */\n async revisions(\n id: number,\n options: RequestOptions = {},\n ): Promise<JsonObject[]> {\n return this.#one(this.#plan.revisions(id), options);\n }\n\n /** Absolute URL of the resource's SSE stream. */\n eventsUrl(): string {\n return this.#plan.eventsUrl();\n }\n}\n\n/** Options of {@link createClient}. */\nexport interface ClientOptions {\n /** The server origin (or any URL on it). */\n readonly baseUrl: string;\n /** The network `fetch`; defaults to `globalThis.fetch`. */\n readonly fetch?: typeof fetch | undefined;\n /** Returns the bearer token; called again with `refresh: true` once after a `401`. */\n readonly tokenProvider?: TokenProvider | undefined;\n /** Outermost first; see `composeMiddleware`. */\n readonly middleware?: readonly Middleware[] | undefined;\n /** Skips api-catalog discovery. */\n readonly manifestUrl?: string | undefined;\n /**\n * Stable identity of this client in TanStack query keys. Defaults to\n * `baseUrl`; set it when two clients share a `baseUrl` but must not share\n * cached data, and keep it constant across reloads of a persisted cache.\n */\n readonly id?: string | undefined;\n /**\n * Exact origins (`https://files.example`) that manifest-supplied\n * `base_path`, `paths.*` and `events.path` may resolve to besides\n * `baseUrl`'s own. By default such a URL (absolute or protocol-relative) is\n * refused with a `ManifestError`, because the token would follow it. For\n * split-origin deployments only.\n */\n readonly allowedOrigins?: readonly string[] | undefined;\n}\n\n/** Discovers the manifest and hands out {@link ResourceClient}s. */\nexport class CrudClient {\n readonly baseUrl: string;\n /** Stable identity in TanStack query keys (`ClientOptions.id`, else `baseUrl`). */\n readonly id: string;\n readonly #fetch: typeof fetch | undefined;\n readonly #tokenProvider: TokenProvider | undefined;\n readonly #middleware: Middleware[];\n readonly #manifestUrl: string | undefined;\n readonly #allowedOrigins: readonly string[];\n readonly #manifests = new Map<string, Promise<Manifest>>();\n readonly #transport: Transport;\n\n constructor(options: ClientOptions) {\n this.baseUrl = options.baseUrl;\n this.id = options.id ?? options.baseUrl;\n this.#fetch = options.fetch;\n this.#tokenProvider = options.tokenProvider;\n this.#middleware = [...(options.middleware ?? [])];\n this.#manifestUrl = options.manifestUrl;\n this.#allowedOrigins = [...(options.allowedOrigins ?? [])];\n normalizeOrigins(this.#allowedOrigins); // fail at createClient, not at the first call\n this.#transport = {\n json: async (call, signal) => {\n const response = await this.#send(call, signal);\n if (response.status === 204) {\n return undefined;\n }\n try {\n return (await response.json()) as unknown;\n } catch (error) {\n throw new ResponseError(\n `${call.method} ${call.url}: the ${response.status} response body is not valid JSON`,\n { cause: error },\n );\n }\n },\n none: async (call, signal) => {\n await (await this.#send(call, signal)).body?.cancel();\n },\n };\n }\n\n /** Adds middleware after the existing ones (innermost); it sees every later request. */\n use(middleware: Middleware): this {\n this.#middleware.push(middleware);\n return this;\n }\n\n async #token(refresh: boolean): Promise<string | null> {\n // An empty token sends no header, so it is anonymous for the scope too.\n return (await this.#tokenProvider?.({ refresh })) || null;\n }\n\n /** The current bearer token, or `null` when anonymous. */\n token(refresh = false): Promise<string | null> {\n return this.#token(refresh);\n }\n\n /** The cache scope of the current caller (`\"anon\"` or a hash of the token). */\n async scope(): Promise<string> {\n return cacheScope(await this.#token(false));\n }\n\n /** The `{ client, scope }` namespace the `./tanstack` key factory takes. */\n async namespace(): Promise<{\n readonly client: string;\n readonly scope: string;\n }> {\n return { client: this.id, scope: await this.scope() };\n }\n\n async #dispatch(\n call: Call,\n token: string | null,\n signal: AbortSignal | undefined,\n ): Promise<Response> {\n const headers = new Headers({\n Accept: \"application/json\",\n ...call.headers,\n });\n if (token !== null) {\n headers.set(\"Authorization\", `Bearer ${token}`);\n }\n const init: RequestInit = {\n method: call.method,\n headers,\n signal: signal ?? null,\n };\n if (call.json !== undefined) {\n headers.set(\"Content-Type\", \"application/json\");\n init.body = JSON.stringify(call.json);\n }\n const request = new Request(call.url, init);\n const terminal = async (ctx: {\n readonly request: Request;\n }): Promise<Response> => {\n try {\n return await (this.#fetch ?? globalThis.fetch)(ctx.request);\n } catch (error) {\n if (ctx.request.signal.aborted) {\n throw error;\n }\n throw new ConnectionFailed(\n error instanceof Error ? error.message : String(error),\n {\n cause: error,\n },\n );\n }\n };\n return composeMiddleware(\n this.#middleware,\n terminal,\n )({\n request,\n resource: call.resource,\n op: call.op,\n recordId: call.recordId,\n scope: await cacheScope(token),\n apiVersion: call.apiVersion,\n signal,\n });\n }\n\n /** Sends `call`, retrying once with a refreshed token after a `401`; throws on `>= 400`. */\n async #send(call: Call, signal: AbortSignal | undefined): Promise<Response> {\n const token = await this.#token(false);\n let response = await this.#dispatch(call, token, signal);\n if (response.status === 401 && this.#tokenProvider !== undefined) {\n const fresh = await this.#token(true);\n if (fresh !== null && fresh !== token) {\n await response.body?.cancel();\n response = await this.#dispatch(call, fresh, signal);\n }\n }\n if (response.status >= 400) {\n throw await problemFromResponse(response);\n }\n return response;\n }\n\n async #getJson(url: string, catalog: boolean): Promise<unknown> {\n const call: Call = {\n method: \"GET\",\n url,\n op: \"manifest\",\n resource: undefined,\n recordId: undefined,\n apiVersion: null,\n // The catalog shares the \"manifest\" op; no-store keeps it out of the manifest cache entry.\n ...(catalog ? { headers: { \"Cache-Control\": \"no-store\" } } : {}),\n };\n const response = await this.#send(call, undefined);\n try {\n return (await response.json()) as unknown;\n } catch (error) {\n throw new ManifestError(`${url} is not JSON`, { cause: error });\n }\n }\n\n /** The manifest for this caller, loaded once per caller scope (`refresh: true` reloads). */\n async manifest(\n options: { readonly refresh?: boolean } = {},\n ): Promise<Manifest> {\n const scope = await this.scope(); // the manifest is filtered per caller\n let loading = this.#manifests.get(scope);\n if (loading === undefined || options.refresh === true) {\n const started = loadManifest(\n (url, catalog) => this.#getJson(url, catalog),\n this.baseUrl,\n this.#manifestUrl,\n );\n loading = started;\n if (!this.#manifests.has(scope)) {\n this.#manifests.clear(); // rotated tokens must not pile up manifests\n }\n this.#manifests.set(scope, started);\n started.catch(() => {\n if (this.#manifests.get(scope) === started) {\n this.#manifests.delete(scope);\n }\n });\n }\n return loading;\n }\n\n /** A handle for `name` (the highest `api_version` unless given). */\n async resource<\n Read extends object = JsonObject,\n Create extends object = JsonObject,\n Update extends object = JsonObject,\n >(\n name: string,\n apiVersion?: string,\n ): Promise<ResourceClient<Read, Create, Update>> {\n const resource = findResource(await this.manifest(), name, apiVersion);\n if (resource === undefined) {\n throw new UnknownResourceError(\n `the manifest has no resource \"${name}\"${apiVersion === undefined ? \"\" : ` ${apiVersion}`} for this caller`,\n );\n }\n return new ResourceClient<Read, Create, Update>(\n this.baseUrl,\n resource,\n this.#transport,\n this.#allowedOrigins,\n );\n }\n}\n\n/** Creates a client; the manifest loads on first use. */\nexport function createClient(options: ClientOptions): CrudClient {\n return new CrudClient(options);\n}\n"],"mappings":"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"}
|
package/dist/codegen/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { p as parseManifest, r as createClient, v as CrudError } from "../client-
|
|
2
|
+
import { p as parseManifest, r as createClient, v as CrudError } from "../client-DkSP1q63.mjs";
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
//#region src/codegen/generate.ts
|
|
5
5
|
/** Loads the optional peer `json-schema-to-typescript`; a clear error when it is missing. */
|
|
@@ -37,6 +37,39 @@ function withoutTitles(node, isPropertyMap = false) {
|
|
|
37
37
|
function typeName(resource) {
|
|
38
38
|
return `${resource.name} ${resource.api_version}`.split(/[^A-Za-z0-9]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
39
39
|
}
|
|
40
|
+
/** A `--manifest` URL without credentials, query or fragment (they may hold secrets and are not provenance). */
|
|
41
|
+
function describeSource(source) {
|
|
42
|
+
if (!/^https?:\/\//i.test(source)) return source;
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL(source);
|
|
45
|
+
url.username = "";
|
|
46
|
+
url.password = "";
|
|
47
|
+
url.search = "";
|
|
48
|
+
url.hash = "";
|
|
49
|
+
return url.href;
|
|
50
|
+
} catch {
|
|
51
|
+
return source;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The interface name of every resource and view, or a `CrudError` when two
|
|
56
|
+
* resources would share one (`hero-x` and `hero_x`) or a name is not a valid
|
|
57
|
+
* TypeScript identifier (a leading digit, or nothing left after stripping).
|
|
58
|
+
*/
|
|
59
|
+
function interfaceNames(manifest) {
|
|
60
|
+
const owners = /* @__PURE__ */ new Map();
|
|
61
|
+
const names = /* @__PURE__ */ new Map();
|
|
62
|
+
for (const resource of manifest.resources) {
|
|
63
|
+
const label = `${resource.name} ${resource.api_version}`;
|
|
64
|
+
const name = typeName(resource);
|
|
65
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) throw new CrudError(`resource "${label}" maps to "${name}", which is not a valid TypeScript identifier`);
|
|
66
|
+
const clash = owners.get(name);
|
|
67
|
+
if (clash !== void 0) throw new CrudError(`resources "${clash}" and "${label}" both map to the type name "${name}"`);
|
|
68
|
+
owners.set(name, label);
|
|
69
|
+
names.set(resource, name);
|
|
70
|
+
}
|
|
71
|
+
return names;
|
|
72
|
+
}
|
|
40
73
|
/**
|
|
41
74
|
* TypeScript source with one interface per resource and record view:
|
|
42
75
|
* `<Name><Version>Read`, `...Create`, `...Update` and, when the resource has
|
|
@@ -44,11 +77,11 @@ function typeName(resource) {
|
|
|
44
77
|
*/
|
|
45
78
|
async function generateTypes(manifest, source, compile) {
|
|
46
79
|
const chunks = [
|
|
47
|
-
`// Generated by crud-sdk-codegen from ${source} (${manifest.service.name} ${manifest.service.version}).`,
|
|
80
|
+
`// Generated by crud-sdk-codegen from ${describeSource(source)} (${manifest.service.name} ${manifest.service.version}).`,
|
|
48
81
|
"// Do not edit by hand: regenerate.",
|
|
49
82
|
""
|
|
50
83
|
];
|
|
51
|
-
for (const resource of manifest
|
|
84
|
+
for (const [resource, base] of interfaceNames(manifest)) {
|
|
52
85
|
const views = [
|
|
53
86
|
["Read", resource.schemas.read],
|
|
54
87
|
["Create", resource.schemas.create],
|
|
@@ -56,7 +89,7 @@ async function generateTypes(manifest, source, compile) {
|
|
|
56
89
|
["Draft", resource.schemas.draft]
|
|
57
90
|
];
|
|
58
91
|
for (const [view, schema] of views) if (schema !== null) {
|
|
59
|
-
const name =
|
|
92
|
+
const name = base + view;
|
|
60
93
|
const root = {
|
|
61
94
|
...withoutTitles(schema),
|
|
62
95
|
title: name
|
package/dist/codegen/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../../src/codegen/generate.ts","../../src/codegen/run.ts","../../src/codegen/cli.ts"],"sourcesContent":["import { CrudError } from \"../errors.ts\";\nimport type { JsonObject, Manifest, Resource } from \"../manifest.ts\";\n\n/** The part of `json-schema-to-typescript`'s `compile` this tool uses. */\nexport type Compile = (\n schema: JsonObject,\n name: string,\n options: Record<string, unknown>,\n) => Promise<string>;\n\n/** Loads the optional peer `json-schema-to-typescript`; a clear error when it is missing. */\nexport async function loadCompiler(): Promise<Compile> {\n try {\n const mod = await import(\"json-schema-to-typescript\");\n return mod.compile as unknown as Compile;\n } catch (error) {\n throw new CrudError(\n \"crud-sdk-codegen needs the optional peer dependency json-schema-to-typescript: \" +\n \"install it (for example `pnpm add -D json-schema-to-typescript`) and run again\",\n { cause: error },\n );\n }\n}\n\nconst OPTIONS = {\n bannerComment: \"\",\n additionalProperties: false,\n // Inline `$defs` so two resources never declare the same helper type twice.\n declareExternallyReferenced: false,\n};\n\nconst DATA_KEYWORDS: ReadonlySet<string> = new Set([\n \"default\",\n \"const\",\n \"enum\",\n \"examples\",\n]);\n\n/**\n * Drops `title` annotations below the root: the compiler turns each into a\n * named type alias, which would collide across resources (and `Name` says\n * little). A property that is itself called `title` stays.\n */\nfunction withoutTitles(node: unknown, isPropertyMap = false): unknown {\n if (Array.isArray(node)) {\n return node.map((item) => withoutTitles(item));\n }\n if (typeof node !== \"object\" || node === null) {\n return node;\n }\n const out: JsonObject = {};\n for (const [key, value] of Object.entries(node)) {\n if (isPropertyMap || key !== \"title\" || typeof value !== \"string\") {\n // Data keywords hold values, not schemas: leave them whole.\n out[key] =\n !isPropertyMap && DATA_KEYWORDS.has(key)\n ? value\n : withoutTitles(value, !isPropertyMap && key === \"properties\");\n }\n }\n return out;\n}\n\n/** `heroes` + `v2` -> `HeroesV2` (the prefix of the generated interface names). */\nexport function typeName(\n resource: Pick<Resource, \"name\" | \"api_version\">,\n): string {\n const words = `${resource.name} ${resource.api_version}`.split(\n /[^A-Za-z0-9]+/,\n );\n return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\"\");\n}\n\n/**\n * TypeScript source with one interface per resource and record view:\n * `<Name><Version>Read`, `...Create`, `...Update` and, when the resource has\n * one, `...Draft`. The output has no timestamp, so it is reproducible.\n */\nexport async function generateTypes(\n manifest: Manifest,\n source: string,\n compile: Compile,\n): Promise<string> {\n const chunks = [\n `// Generated by crud-sdk-codegen from ${source} (${manifest.service.name} ${manifest.service.version}).`,\n \"// Do not edit by hand: regenerate.\",\n \"\",\n ];\n for (const resource of manifest.resources) {\n const views = [\n [\"Read\", resource.schemas.read],\n [\"Create\", resource.schemas.create],\n [\"Update\", resource.schemas.update],\n [\"Draft\", resource.schemas.draft],\n ] as const;\n for (const [view, schema] of views) {\n if (schema !== null) {\n const name = typeName(resource) + view;\n // The root title names the interface.\n const root = { ...(withoutTitles(schema) as JsonObject), title: name };\n chunks.push((await compile(root, name, OPTIONS)).trimEnd(), \"\");\n }\n }\n }\n return chunks.join(\"\\n\");\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { createClient } from \"../client.ts\";\nimport { parseManifest } from \"../manifest.ts\";\nimport type { Manifest } from \"../manifest.ts\";\nimport { generateTypes, loadCompiler } from \"./generate.ts\";\nimport type { Compile } from \"./generate.ts\";\n\nconst USAGE = `Usage: crud-sdk-codegen --manifest <url|file> --out <file>\n\nWrites TypeScript interfaces (Read, Create, Update per resource) from the\nJSON Schemas of a CRUD manifest. A URL is the manifest itself, or a server\norigin (path \"/\") to discover it through the api-catalog. The manifest is\nper caller: set CRUD_SDK_TOKEN to a bearer token to see more than the\nanonymous view. Needs the optional peer json-schema-to-typescript.\n`;\n\n/** What {@link run} touches outside itself; tests replace them. */\nexport interface Deps {\n readonly fetch?: typeof fetch | undefined;\n readonly env?: Readonly<Record<string, string | undefined>> | undefined;\n readonly loadCompiler?: (() => Promise<Compile>) | undefined;\n readonly readFile?: ((path: string) => Promise<string>) | undefined;\n readonly writeFile?:\n ((path: string, text: string) => Promise<void>) | undefined;\n readonly stdout?: ((text: string) => void) | undefined;\n readonly stderr?: ((text: string) => void) | undefined;\n}\n\nfunction parseArgs(\n argv: readonly string[],\n): { manifest: string; out: string } | undefined {\n let manifest: string | undefined;\n let out: string | undefined;\n for (let i = 0; i < argv.length; i += 2) {\n if (argv[i] === \"--manifest\") {\n manifest = argv[i + 1];\n } else if (argv[i] === \"--out\") {\n out = argv[i + 1];\n } else {\n return undefined;\n }\n }\n return manifest === undefined || out === undefined\n ? undefined\n : { manifest, out };\n}\n\nasync function readManifest(source: string, deps: Deps): Promise<Manifest> {\n if (!/^https?:\\/\\//.test(source)) {\n const text = await (deps.readFile ?? ((p) => readFile(p, \"utf8\")))(source);\n return parseManifest(JSON.parse(text));\n }\n const url = new URL(source);\n const token = deps.env?.[\"CRUD_SDK_TOKEN\"];\n const client = createClient({\n baseUrl: url.href,\n fetch: deps.fetch,\n tokenProvider:\n token === undefined || token === \"\" ? undefined : () => token,\n ...(url.pathname === \"/\" ? {} : { manifestUrl: url.href }),\n });\n return client.manifest();\n}\n\n/** Runs the CLI; resolves to the process exit code. */\nexport async function run(\n argv: readonly string[],\n deps: Deps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((text) => process.stdout.write(text));\n const stderr = deps.stderr ?? ((text) => process.stderr.write(text));\n const args = parseArgs(argv);\n if (args === undefined) {\n stderr(USAGE);\n return 2;\n }\n try {\n const manifest = await readManifest(args.manifest, deps);\n const compile = await (deps.loadCompiler ?? loadCompiler)();\n const text = await generateTypes(manifest, args.manifest, compile);\n await (deps.writeFile ?? ((p, t) => writeFile(p, t, \"utf8\")))(\n args.out,\n text,\n );\n stdout(`wrote ${args.out} (${manifest.resources.length} resources)\\n`);\n return 0;\n } catch (error) {\n stderr(\n `crud-sdk-codegen: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n return 1;\n }\n}\n","#!/usr/bin/env node\n// `crud-sdk-codegen` bin: writes TypeScript types from a CRUD manifest.\n// Needs the optional peer `json-schema-to-typescript`.\nimport { run } from \"./run.ts\";\n\nprocess.exitCode = await run(process.argv.slice(2), { env: process.env });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../../src/codegen/generate.ts","../../src/codegen/run.ts","../../src/codegen/cli.ts"],"sourcesContent":["import { CrudError } from \"../errors.ts\";\nimport type { JsonObject, Manifest, Resource } from \"../manifest.ts\";\n\n/** The part of `json-schema-to-typescript`'s `compile` this tool uses. */\nexport type Compile = (\n schema: JsonObject,\n name: string,\n options: Record<string, unknown>,\n) => Promise<string>;\n\n/** Loads the optional peer `json-schema-to-typescript`; a clear error when it is missing. */\nexport async function loadCompiler(): Promise<Compile> {\n try {\n const mod = await import(\"json-schema-to-typescript\");\n return mod.compile as unknown as Compile;\n } catch (error) {\n throw new CrudError(\n \"crud-sdk-codegen needs the optional peer dependency json-schema-to-typescript: \" +\n \"install it (for example `pnpm add -D json-schema-to-typescript`) and run again\",\n { cause: error },\n );\n }\n}\n\nconst OPTIONS = {\n bannerComment: \"\",\n additionalProperties: false,\n // Inline `$defs` so two resources never declare the same helper type twice.\n declareExternallyReferenced: false,\n};\n\nconst DATA_KEYWORDS: ReadonlySet<string> = new Set([\n \"default\",\n \"const\",\n \"enum\",\n \"examples\",\n]);\n\n/**\n * Drops `title` annotations below the root: the compiler turns each into a\n * named type alias, which would collide across resources (and `Name` says\n * little). A property that is itself called `title` stays.\n */\nfunction withoutTitles(node: unknown, isPropertyMap = false): unknown {\n if (Array.isArray(node)) {\n return node.map((item) => withoutTitles(item));\n }\n if (typeof node !== \"object\" || node === null) {\n return node;\n }\n const out: JsonObject = {};\n for (const [key, value] of Object.entries(node)) {\n if (isPropertyMap || key !== \"title\" || typeof value !== \"string\") {\n // Data keywords hold values, not schemas: leave them whole.\n out[key] =\n !isPropertyMap && DATA_KEYWORDS.has(key)\n ? value\n : withoutTitles(value, !isPropertyMap && key === \"properties\");\n }\n }\n return out;\n}\n\n/** `heroes` + `v2` -> `HeroesV2` (the prefix of the generated interface names). */\nexport function typeName(\n resource: Pick<Resource, \"name\" | \"api_version\">,\n): string {\n const words = `${resource.name} ${resource.api_version}`.split(\n /[^A-Za-z0-9]+/,\n );\n return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\"\");\n}\n\n/** A `--manifest` URL without credentials, query or fragment (they may hold secrets and are not provenance). */\nexport function describeSource(source: string): string {\n if (!/^https?:\\/\\//i.test(source)) {\n return source;\n }\n try {\n const url = new URL(source);\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return url.href;\n } catch {\n return source;\n }\n}\n\n/**\n * The interface name of every resource and view, or a `CrudError` when two\n * resources would share one (`hero-x` and `hero_x`) or a name is not a valid\n * TypeScript identifier (a leading digit, or nothing left after stripping).\n */\nfunction interfaceNames(manifest: Manifest): Map<Resource, string> {\n const owners = new Map<string, string>();\n const names = new Map<Resource, string>();\n for (const resource of manifest.resources) {\n const label = `${resource.name} ${resource.api_version}`;\n const name = typeName(resource);\n if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {\n throw new CrudError(\n `resource \"${label}\" maps to \"${name}\", which is not a valid TypeScript identifier`,\n );\n }\n const clash = owners.get(name);\n if (clash !== undefined) {\n throw new CrudError(\n `resources \"${clash}\" and \"${label}\" both map to the type name \"${name}\"`,\n );\n }\n owners.set(name, label);\n names.set(resource, name);\n }\n return names;\n}\n\n/**\n * TypeScript source with one interface per resource and record view:\n * `<Name><Version>Read`, `...Create`, `...Update` and, when the resource has\n * one, `...Draft`. The output has no timestamp, so it is reproducible.\n */\nexport async function generateTypes(\n manifest: Manifest,\n source: string,\n compile: Compile,\n): Promise<string> {\n const chunks = [\n `// Generated by crud-sdk-codegen from ${describeSource(source)} (${manifest.service.name} ${manifest.service.version}).`,\n \"// Do not edit by hand: regenerate.\",\n \"\",\n ];\n for (const [resource, base] of interfaceNames(manifest)) {\n const views = [\n [\"Read\", resource.schemas.read],\n [\"Create\", resource.schemas.create],\n [\"Update\", resource.schemas.update],\n [\"Draft\", resource.schemas.draft],\n ] as const;\n for (const [view, schema] of views) {\n if (schema !== null) {\n const name = base + view;\n // The root title names the interface.\n const root = { ...(withoutTitles(schema) as JsonObject), title: name };\n chunks.push((await compile(root, name, OPTIONS)).trimEnd(), \"\");\n }\n }\n }\n return chunks.join(\"\\n\");\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { createClient } from \"../client.ts\";\nimport { parseManifest } from \"../manifest.ts\";\nimport type { Manifest } from \"../manifest.ts\";\nimport { generateTypes, loadCompiler } from \"./generate.ts\";\nimport type { Compile } from \"./generate.ts\";\n\nconst USAGE = `Usage: crud-sdk-codegen --manifest <url|file> --out <file>\n\nWrites TypeScript interfaces (Read, Create, Update per resource) from the\nJSON Schemas of a CRUD manifest. A URL is the manifest itself, or a server\norigin (path \"/\") to discover it through the api-catalog. The manifest is\nper caller: set CRUD_SDK_TOKEN to a bearer token to see more than the\nanonymous view. Needs the optional peer json-schema-to-typescript.\n`;\n\n/** What {@link run} touches outside itself; tests replace them. */\nexport interface Deps {\n readonly fetch?: typeof fetch | undefined;\n readonly env?: Readonly<Record<string, string | undefined>> | undefined;\n readonly loadCompiler?: (() => Promise<Compile>) | undefined;\n readonly readFile?: ((path: string) => Promise<string>) | undefined;\n readonly writeFile?:\n ((path: string, text: string) => Promise<void>) | undefined;\n readonly stdout?: ((text: string) => void) | undefined;\n readonly stderr?: ((text: string) => void) | undefined;\n}\n\nfunction parseArgs(\n argv: readonly string[],\n): { manifest: string; out: string } | undefined {\n let manifest: string | undefined;\n let out: string | undefined;\n for (let i = 0; i < argv.length; i += 2) {\n if (argv[i] === \"--manifest\") {\n manifest = argv[i + 1];\n } else if (argv[i] === \"--out\") {\n out = argv[i + 1];\n } else {\n return undefined;\n }\n }\n return manifest === undefined || out === undefined\n ? undefined\n : { manifest, out };\n}\n\nasync function readManifest(source: string, deps: Deps): Promise<Manifest> {\n if (!/^https?:\\/\\//.test(source)) {\n const text = await (deps.readFile ?? ((p) => readFile(p, \"utf8\")))(source);\n return parseManifest(JSON.parse(text));\n }\n const url = new URL(source);\n const token = deps.env?.[\"CRUD_SDK_TOKEN\"];\n const client = createClient({\n baseUrl: url.href,\n fetch: deps.fetch,\n tokenProvider:\n token === undefined || token === \"\" ? undefined : () => token,\n ...(url.pathname === \"/\" ? {} : { manifestUrl: url.href }),\n });\n return client.manifest();\n}\n\n/** Runs the CLI; resolves to the process exit code. */\nexport async function run(\n argv: readonly string[],\n deps: Deps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((text) => process.stdout.write(text));\n const stderr = deps.stderr ?? ((text) => process.stderr.write(text));\n const args = parseArgs(argv);\n if (args === undefined) {\n stderr(USAGE);\n return 2;\n }\n try {\n const manifest = await readManifest(args.manifest, deps);\n const compile = await (deps.loadCompiler ?? loadCompiler)();\n const text = await generateTypes(manifest, args.manifest, compile);\n await (deps.writeFile ?? ((p, t) => writeFile(p, t, \"utf8\")))(\n args.out,\n text,\n );\n stdout(`wrote ${args.out} (${manifest.resources.length} resources)\\n`);\n return 0;\n } catch (error) {\n stderr(\n `crud-sdk-codegen: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n return 1;\n }\n}\n","#!/usr/bin/env node\n// `crud-sdk-codegen` bin: writes TypeScript types from a CRUD manifest.\n// Needs the optional peer `json-schema-to-typescript`.\nimport { run } from \"./run.ts\";\n\nprocess.exitCode = await run(process.argv.slice(2), { env: process.env });\n"],"mappings":"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"}
|