@haddowg/json-api-client 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ajv.d.cts +1 -1
- package/dist/ajv.d.mts +1 -1
- package/dist/index.cjs +54 -14
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +54 -14
- package/dist/index.mjs.map +1 -1
- package/dist/{validate-DgCUbzgT.d.cts → validate-B98ghVgw.d.cts} +125 -32
- package/dist/validate-B98ghVgw.d.cts.map +1 -0
- package/dist/{validate-DgCUbzgT.d.mts → validate-B98ghVgw.d.mts} +125 -32
- package/dist/validate-B98ghVgw.d.mts.map +1 -0
- package/package.json +1 -1
- package/dist/validate-DgCUbzgT.d.cts.map +0 -1
- package/dist/validate-DgCUbzgT.d.mts.map +0 -1
package/dist/ajv.d.cts
CHANGED
package/dist/ajv.d.mts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -540,12 +540,31 @@ const ATOMIC_MEDIA_TYPE = mediaType({ ext: [ATOMIC_EXT] });
|
|
|
540
540
|
//#region src/request.ts
|
|
541
541
|
const isAbsolute = (path) => /^https?:\/\//i.test(path);
|
|
542
542
|
const RELATIVE_ORIGIN = "http://relative.invalid";
|
|
543
|
-
/**
|
|
543
|
+
/**
|
|
544
|
+
* Resolve a server link against `baseUrl` per RFC 3986 and check where it lands. A link on the
|
|
545
|
+
* base's origin is followed; a relative base (e.g. `/api`) takes the page's origin in a browser and
|
|
546
|
+
* otherwise only accepts a link that keeps the host. Anything that isn't plain http(s) is refused.
|
|
547
|
+
*/
|
|
544
548
|
function resolveLink(link, baseUrl) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
+
let normalised = link.replace(/[\t\n\r]/g, "");
|
|
550
|
+
let start = 0;
|
|
551
|
+
while (start < normalised.length && normalised.charCodeAt(start) <= 32) start++;
|
|
552
|
+
normalised = normalised.slice(start);
|
|
553
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(normalised)?.[1]?.toLowerCase();
|
|
554
|
+
if (scheme !== void 0 && scheme !== "http" && scheme !== "https") throw new Error(`Refusing to follow link "${link}": only http(s) links are followed`);
|
|
555
|
+
const relativeBase = !isAbsolute(baseUrl);
|
|
556
|
+
const page = globalThis.location?.href;
|
|
557
|
+
const base = new URL(baseUrl, relativeBase ? typeof page === "string" ? page : RELATIVE_ORIGIN : void 0);
|
|
558
|
+
const resolved = new URL(link, base);
|
|
559
|
+
const sameOrigin = resolved.origin === base.origin && (scheme === void 0 || /^https?:\/\//i.test(normalised));
|
|
560
|
+
if (!sameOrigin) return {
|
|
561
|
+
url: resolved.href,
|
|
562
|
+
sameOrigin
|
|
563
|
+
};
|
|
564
|
+
return {
|
|
565
|
+
url: relativeBase ? resolved.pathname + resolved.search + resolved.hash : resolved.href,
|
|
566
|
+
sameOrigin
|
|
567
|
+
};
|
|
549
568
|
}
|
|
550
569
|
const append = (parts, key, value) => {
|
|
551
570
|
if (value === void 0 || value === null || value === "") return;
|
|
@@ -583,14 +602,21 @@ function serializeQuery(query) {
|
|
|
583
602
|
return parts.join("&");
|
|
584
603
|
}
|
|
585
604
|
/**
|
|
586
|
-
* Execute a request: build the URL (a server link resolved against `baseUrl
|
|
587
|
-
* verbatim, else `baseUrl + path`), apply content negotiation and any async per-request headers,
|
|
605
|
+
* Execute a request: build the URL (a server link resolved against `baseUrl` and held to its
|
|
606
|
+
* origin, an absolute path verbatim, else `baseUrl + path`), apply content negotiation and any async per-request headers,
|
|
588
607
|
* then drive the transport. A 2xx with a body parses to a {@link Document}; an empty /
|
|
589
608
|
* 204 body resolves to `undefined`. A non-2xx throws a {@link JsonApiError} carrying the
|
|
590
609
|
* parsed error objects (tolerating a non-JSON error body with an empty list).
|
|
591
610
|
*/
|
|
592
611
|
async function execute(ctx, req) {
|
|
593
|
-
let url
|
|
612
|
+
let url;
|
|
613
|
+
let sendHeaders = true;
|
|
614
|
+
if (req.link) {
|
|
615
|
+
const link = resolveLink(req.path, ctx.baseUrl);
|
|
616
|
+
if (!link.sameOrigin && ctx.allowCrossOriginLinks !== true) throw new Error(`Refusing to follow link "${req.path}": it leaves the base URL's origin (set allowCrossOriginLinks to follow it without the configured headers)`);
|
|
617
|
+
url = link.url;
|
|
618
|
+
sendHeaders = link.sameOrigin;
|
|
619
|
+
} else url = isAbsolute(req.path) ? req.path : ctx.baseUrl + req.path;
|
|
594
620
|
if (req.query) {
|
|
595
621
|
const qs = serializeQuery(req.query);
|
|
596
622
|
if (qs) url += (url.includes("?") ? "&" : "?") + qs;
|
|
@@ -610,7 +636,7 @@ async function execute(ctx, req) {
|
|
|
610
636
|
headers["Content-Type"] = req.contentType ?? negotiated;
|
|
611
637
|
transportReq.body = req.raw && typeof req.body === "string" ? req.body : JSON.stringify(req.body);
|
|
612
638
|
}
|
|
613
|
-
if (ctx.headers) mergeHeaders(headers, await ctx.headers());
|
|
639
|
+
if (ctx.headers && sendHeaders) mergeHeaders(headers, await ctx.headers());
|
|
614
640
|
const res = await ctx.transport(transportReq);
|
|
615
641
|
if (res.status >= 200 && res.status < 300) return parseDocument(res.body);
|
|
616
642
|
throw new JsonApiError(res.status, parseErrors(res.body));
|
|
@@ -1020,6 +1046,18 @@ const fetchTransport = async (req) => {
|
|
|
1020
1046
|
/** Substitute `{key}` placeholders in a path template (`/albums/{id}/{rel}` -> `/albums/1/tracks`). */
|
|
1021
1047
|
const fill = (template, vars) => template.replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? `{${key}}`);
|
|
1022
1048
|
/**
|
|
1049
|
+
* Fill a type's path template, putting a relation's URI segment (its `uriField`, else its name)
|
|
1050
|
+
* in place of `{rel}`. `vars.rel` stays the relation's name everywhere else.
|
|
1051
|
+
*/
|
|
1052
|
+
function fillPath(ctx, type, template, vars) {
|
|
1053
|
+
const rel = vars["rel"];
|
|
1054
|
+
const uriField = rel === void 0 ? void 0 : ctx.descriptor[type]?.relations[rel]?.uriField;
|
|
1055
|
+
return fill(template, uriField === void 0 ? vars : {
|
|
1056
|
+
...vars,
|
|
1057
|
+
rel: uriField
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1023
1061
|
* Run an operation: resolve the path template from the type's descriptor, fill its
|
|
1024
1062
|
* placeholders, execute, and materialise the resulting document. A no-body / 204 response
|
|
1025
1063
|
* materialises to `undefined`; an operation the type does not declare (no path template) throws
|
|
@@ -1032,7 +1070,7 @@ async function run(ctx, type, operation, vars, query, linkage = false, relationF
|
|
|
1032
1070
|
if (template === void 0) throw new Error(`No "${operation}" path declared for type "${type}"`);
|
|
1033
1071
|
const req = {
|
|
1034
1072
|
method: "GET",
|
|
1035
|
-
path:
|
|
1073
|
+
path: fillPath(ctx, type, template, vars)
|
|
1036
1074
|
};
|
|
1037
1075
|
if (query !== void 0) {
|
|
1038
1076
|
const { signal, ...params } = query;
|
|
@@ -1082,7 +1120,7 @@ async function runWrite(ctx, spec) {
|
|
|
1082
1120
|
if (template === void 0) throw new Error(`No "${spec.operation}" path declared for type "${spec.type}"`);
|
|
1083
1121
|
const req = {
|
|
1084
1122
|
method: spec.method,
|
|
1085
|
-
path:
|
|
1123
|
+
path: fillPath(ctx, spec.type, template, spec.vars),
|
|
1086
1124
|
body: spec.body
|
|
1087
1125
|
};
|
|
1088
1126
|
if (spec.query !== void 0) req.query = spec.query;
|
|
@@ -1181,8 +1219,9 @@ function relationshipAccessor(ctx, type, id, rel) {
|
|
|
1181
1219
|
return mutations === void 0 ? true : mutations[verb] === true;
|
|
1182
1220
|
};
|
|
1183
1221
|
const accessor = {};
|
|
1184
|
-
|
|
1185
|
-
if (relation?.
|
|
1222
|
+
const readable = relation?.writeOnly !== true;
|
|
1223
|
+
if (readable && relation?.relationship !== false) accessor["get"] = (query) => run(ctx, type, "fetchRelationship", vars, query, true, rel);
|
|
1224
|
+
if (readable && relation?.related !== false) accessor["related"] = (query) => run(ctx, type, "fetchRelated", vars, query, false, rel);
|
|
1186
1225
|
if (cardinality === "many") {
|
|
1187
1226
|
if (advertises("add")) accessor["add"] = (refs) => mutate("POST", refs);
|
|
1188
1227
|
if (advertises("remove")) accessor["remove"] = (refs) => mutate("DELETE", refs);
|
|
@@ -1267,7 +1306,8 @@ function createClient(descriptor, options) {
|
|
|
1267
1306
|
const request = {
|
|
1268
1307
|
baseUrl: options.baseUrl,
|
|
1269
1308
|
transport: options.transport ?? fetchTransport,
|
|
1270
|
-
...options.headers !== void 0 ? { headers: options.headers } : {}
|
|
1309
|
+
...options.headers !== void 0 ? { headers: options.headers } : {},
|
|
1310
|
+
...options.allowCrossOriginLinks !== void 0 ? { allowCrossOriginLinks: options.allowCrossOriginLinks } : {}
|
|
1271
1311
|
};
|
|
1272
1312
|
const validator = resolveValidator(options.validate);
|
|
1273
1313
|
const ctx = {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as RelationshipAccessor, $t as ActionDescriptor, A as DefaultActionTypes, At as DocumentEnvelope, B as IdentifierMember, Bt as relatedPaginatorKind, C as fetchTransport, Ct as ATOMIC_MEDIA_TYPE, D as Collection, Dt as ResourceIdentifier, E as Client, Et as MediaTypeParams, F as FieldSelectionMap, Ft as RelationView, G as MemberName, Gt as PageLinks, H as IncludedRelations, Ht as ExactPageParams, I as FieldsMap, It as ResourceEnvelope, J as ReadableRelationName, Jt as PageParams, K as PivotInput, Kt as PageMeta, L as FilterKey, Lt as ResourceLinks, M as DefaultWriteAttributes, Mt as EdgeEnvelope, N as EdgeMembers, Nt as MaterialiseContext, O as CountToken, Ot as mediaType, P as ErrorCodeOf, Pt as NavigateOptions, Q as RelationReadQuery, Qt as resolvePaginator, R as FilterQuery, Rt as materialise, S as TransportResponse, St as ATOMIC_EXT, T as AtomicResultOf, Tt as LocalIdentifier, U as LinkageRef, Ut as Page, V as IncludePath, Vt as CANONICAL_PAGE_KEYS, W as ListPageParams, Wt as PageKeys, X as RelationPageParams, Xt as ResolvedPaginator, Y as RelationName, Yt as PageParamsOf, Z as RelationPaginator, Zt as TypedPage, _ as HANDLE_RESERVED, _n as StrategyDescriptor, _t as AtomicResult, a as Validator, an as Cardinality, at as SortToken, b as JsonApiTransport, bt as AtomicUpdateInput, c as resolveValidator, cn as MenuDescriptor, ct as TypedReadQuery, d as JsonApiContext, dn as PaginatorRole, dt as AtomicCreateHandle, en as ActionInput, et as ResourceAccessors, f as JsonApiRequest, fn as PaginatorStrategy, ft as AtomicCreateInput, g as ClientOptions, gn as ResourceDescriptor, gt as AtomicRef, h as serializeQuery, hn as RelationMutations, ht as AtomicRecorder, i as ValidationOption, in as AtomicDescriptor, it as SortQuery, j as DefaultAttributes, jt as Edge, k as CreateInput, kt as ArrayEnvelope, l as validateDocument, ln as PaginatorDescriptor, lt as UpdateInput, m as execute, mn as RelationDescriptor, mt as AtomicHandle, n as SchemaMap, nn as ActionScope, nt as ResourceObjectView, o as WireResource, on as ClientIdPolicy, ot as TypeAccessor, p as ReadQuery, pn as PivotDescriptor, pt as AtomicDeleteHandle, q as ReadResult, qt as PageOf, r as ValidationConfig, rn as ApiDescriptor, rt as SingleReadQuery, s as assertJsonApiDocument, sn as CountableDescriptor, st as TypeName, t as ResolvedValidator, tn as ActionOutput, tt as ResourceHandle, u as Document, un as PaginatorKind, ut as WriteOptions, v as HeadersInit, vt as AtomicResults, w as ActionsAccessor, wt as JSON_API_MEDIA_TYPE, x as TransportRequest, xt as runAtomic, y as createClient, yt as AtomicUpdateHandle, z as HydratedMember, zt as relatedPaginator } from "./validate-B98ghVgw.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/errors.d.ts
|
|
4
4
|
/**
|
|
@@ -216,5 +216,5 @@ declare function withRemappedRelationshipPaths(error: JsonApiError, rel: string,
|
|
|
216
216
|
*/
|
|
217
217
|
declare function withRemappedAtomicPaths(error: JsonApiError, descriptor: ApiDescriptor, typeForOp: (opIndex: number) => string | undefined): JsonApiError;
|
|
218
218
|
//#endregion
|
|
219
|
-
export { ATOMIC_EXT, ATOMIC_MEDIA_TYPE, ActionDescriptor, ActionInput, ActionOutput, ActionScope, ActionsAccessor, ApiDescriptor, ArrayEnvelope, AtomicCreateHandle, AtomicCreateInput, AtomicDeleteHandle, AtomicDescriptor, AtomicHandle, AtomicRecorder, AtomicRef, AtomicResult, AtomicResultOf, AtomicResults, AtomicUpdateHandle, AtomicUpdateInput, CANONICAL_PAGE_KEYS, Cardinality, Client, ClientIdPolicy, ClientOptions, Collection, CountToken, CountableDescriptor, CreateInput, DefaultActionTypes, DefaultAttributes, DefaultWriteAttributes, Document, DocumentEnvelope, Edge, EdgeEnvelope, EdgeMembers, ErrorCodeGuard, ErrorCodeOf, ExactPageParams, FieldSelectionMap, FieldsMap, FilterKey, FilterQuery, HANDLE_RESERVED, HeadersInit, HydratedMember, IdentifierMember, IncludePath, IncludedRelations, JSON_API_MEDIA_TYPE, JsonApiContext, JsonApiError, JsonApiErrorObject, JsonApiRequest, JsonApiTransport, LinkageRef, ListPageParams, LocalIdentifier, MaterialiseContext, MediaTypeParams, MemberName, MenuDescriptor, NavigateOptions, Page, PageKeys, PageLinks, PageMeta, PageOf, PageParams, PageParamsOf, PaginatorDescriptor, PaginatorKind, PaginatorRole, PaginatorStrategy, PivotInput, ReadQuery, ReadResult, RelationDescriptor, RelationMutations, RelationName, RelationPageParams, RelationPaginator, RelationReadQuery, RelationView, RelationshipAccessor, RelationshipDocument, ResolvedPaginator, ResolvedValidator, ResourceAccessors, ResourceDescriptor, ResourceEnvelope, ResourceHandle, ResourceIdentifier, ResourceLinks, ResourceObjectView, SchemaMap, SingleReadQuery, SortQuery, SortToken, StrategyDescriptor, StructuralGuardError, ToDocumentOptions, TransportRequest, TransportResponse, TypeAccessor, TypeName, TypedPage, TypedReadQuery, UpdateInput, ValidationConfig, ValidationOption, Validator, WireResource, WriteDocument, WriteOptions, assertJsonApiDocument, createClient, execute, fetchTransport, hasErrorCode, materialise, mediaType, relatedPaginator, relatedPaginatorKind, remapPointer, remapRelationshipPointer, resolvePaginator, resolveValidator, runAtomic, serializeQuery, toDocument, toRelationshipDocument, validateDocument, withRemappedAtomicPaths, withRemappedPaths, withRemappedRelationshipPaths };
|
|
219
|
+
export { ATOMIC_EXT, ATOMIC_MEDIA_TYPE, ActionDescriptor, ActionInput, ActionOutput, ActionScope, ActionsAccessor, ApiDescriptor, ArrayEnvelope, AtomicCreateHandle, AtomicCreateInput, AtomicDeleteHandle, AtomicDescriptor, AtomicHandle, AtomicRecorder, AtomicRef, AtomicResult, AtomicResultOf, AtomicResults, AtomicUpdateHandle, AtomicUpdateInput, CANONICAL_PAGE_KEYS, Cardinality, Client, ClientIdPolicy, ClientOptions, Collection, CountToken, CountableDescriptor, CreateInput, DefaultActionTypes, DefaultAttributes, DefaultWriteAttributes, Document, DocumentEnvelope, Edge, EdgeEnvelope, EdgeMembers, ErrorCodeGuard, ErrorCodeOf, ExactPageParams, FieldSelectionMap, FieldsMap, FilterKey, FilterQuery, HANDLE_RESERVED, HeadersInit, HydratedMember, IdentifierMember, IncludePath, IncludedRelations, JSON_API_MEDIA_TYPE, JsonApiContext, JsonApiError, JsonApiErrorObject, JsonApiRequest, JsonApiTransport, LinkageRef, ListPageParams, LocalIdentifier, MaterialiseContext, MediaTypeParams, MemberName, MenuDescriptor, NavigateOptions, Page, PageKeys, PageLinks, PageMeta, PageOf, PageParams, PageParamsOf, PaginatorDescriptor, PaginatorKind, PaginatorRole, PaginatorStrategy, PivotDescriptor, PivotInput, ReadQuery, ReadResult, ReadableRelationName, RelationDescriptor, RelationMutations, RelationName, RelationPageParams, RelationPaginator, RelationReadQuery, RelationView, RelationshipAccessor, RelationshipDocument, ResolvedPaginator, ResolvedValidator, ResourceAccessors, ResourceDescriptor, ResourceEnvelope, ResourceHandle, ResourceIdentifier, ResourceLinks, ResourceObjectView, SchemaMap, SingleReadQuery, SortQuery, SortToken, StrategyDescriptor, StructuralGuardError, ToDocumentOptions, TransportRequest, TransportResponse, TypeAccessor, TypeName, TypedPage, TypedReadQuery, UpdateInput, ValidationConfig, ValidationOption, Validator, WireResource, WriteDocument, WriteOptions, assertJsonApiDocument, createClient, execute, fetchTransport, hasErrorCode, materialise, mediaType, relatedPaginator, relatedPaginatorKind, remapPointer, remapRelationshipPointer, resolvePaginator, resolveValidator, runAtomic, serializeQuery, toDocument, toRelationshipDocument, validateDocument, withRemappedAtomicPaths, withRemappedPaths, withRemappedRelationshipPaths };
|
|
220
220
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as RelationshipAccessor, $t as ActionDescriptor, A as DefaultActionTypes, At as DocumentEnvelope, B as IdentifierMember, Bt as relatedPaginatorKind, C as fetchTransport, Ct as ATOMIC_MEDIA_TYPE, D as Collection, Dt as ResourceIdentifier, E as Client, Et as MediaTypeParams, F as FieldSelectionMap, Ft as RelationView, G as MemberName, Gt as PageLinks, H as IncludedRelations, Ht as ExactPageParams, I as FieldsMap, It as ResourceEnvelope, J as ReadableRelationName, Jt as PageParams, K as PivotInput, Kt as PageMeta, L as FilterKey, Lt as ResourceLinks, M as DefaultWriteAttributes, Mt as EdgeEnvelope, N as EdgeMembers, Nt as MaterialiseContext, O as CountToken, Ot as mediaType, P as ErrorCodeOf, Pt as NavigateOptions, Q as RelationReadQuery, Qt as resolvePaginator, R as FilterQuery, Rt as materialise, S as TransportResponse, St as ATOMIC_EXT, T as AtomicResultOf, Tt as LocalIdentifier, U as LinkageRef, Ut as Page, V as IncludePath, Vt as CANONICAL_PAGE_KEYS, W as ListPageParams, Wt as PageKeys, X as RelationPageParams, Xt as ResolvedPaginator, Y as RelationName, Yt as PageParamsOf, Z as RelationPaginator, Zt as TypedPage, _ as HANDLE_RESERVED, _n as StrategyDescriptor, _t as AtomicResult, a as Validator, an as Cardinality, at as SortToken, b as JsonApiTransport, bt as AtomicUpdateInput, c as resolveValidator, cn as MenuDescriptor, ct as TypedReadQuery, d as JsonApiContext, dn as PaginatorRole, dt as AtomicCreateHandle, en as ActionInput, et as ResourceAccessors, f as JsonApiRequest, fn as PaginatorStrategy, ft as AtomicCreateInput, g as ClientOptions, gn as ResourceDescriptor, gt as AtomicRef, h as serializeQuery, hn as RelationMutations, ht as AtomicRecorder, i as ValidationOption, in as AtomicDescriptor, it as SortQuery, j as DefaultAttributes, jt as Edge, k as CreateInput, kt as ArrayEnvelope, l as validateDocument, ln as PaginatorDescriptor, lt as UpdateInput, m as execute, mn as RelationDescriptor, mt as AtomicHandle, n as SchemaMap, nn as ActionScope, nt as ResourceObjectView, o as WireResource, on as ClientIdPolicy, ot as TypeAccessor, p as ReadQuery, pn as PivotDescriptor, pt as AtomicDeleteHandle, q as ReadResult, qt as PageOf, r as ValidationConfig, rn as ApiDescriptor, rt as SingleReadQuery, s as assertJsonApiDocument, sn as CountableDescriptor, st as TypeName, t as ResolvedValidator, tn as ActionOutput, tt as ResourceHandle, u as Document, un as PaginatorKind, ut as WriteOptions, v as HeadersInit, vt as AtomicResults, w as ActionsAccessor, wt as JSON_API_MEDIA_TYPE, x as TransportRequest, xt as runAtomic, y as createClient, yt as AtomicUpdateHandle, z as HydratedMember, zt as relatedPaginator } from "./validate-B98ghVgw.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/errors.d.ts
|
|
4
4
|
/**
|
|
@@ -216,5 +216,5 @@ declare function withRemappedRelationshipPaths(error: JsonApiError, rel: string,
|
|
|
216
216
|
*/
|
|
217
217
|
declare function withRemappedAtomicPaths(error: JsonApiError, descriptor: ApiDescriptor, typeForOp: (opIndex: number) => string | undefined): JsonApiError;
|
|
218
218
|
//#endregion
|
|
219
|
-
export { ATOMIC_EXT, ATOMIC_MEDIA_TYPE, ActionDescriptor, ActionInput, ActionOutput, ActionScope, ActionsAccessor, ApiDescriptor, ArrayEnvelope, AtomicCreateHandle, AtomicCreateInput, AtomicDeleteHandle, AtomicDescriptor, AtomicHandle, AtomicRecorder, AtomicRef, AtomicResult, AtomicResultOf, AtomicResults, AtomicUpdateHandle, AtomicUpdateInput, CANONICAL_PAGE_KEYS, Cardinality, Client, ClientIdPolicy, ClientOptions, Collection, CountToken, CountableDescriptor, CreateInput, DefaultActionTypes, DefaultAttributes, DefaultWriteAttributes, Document, DocumentEnvelope, Edge, EdgeEnvelope, EdgeMembers, ErrorCodeGuard, ErrorCodeOf, ExactPageParams, FieldSelectionMap, FieldsMap, FilterKey, FilterQuery, HANDLE_RESERVED, HeadersInit, HydratedMember, IdentifierMember, IncludePath, IncludedRelations, JSON_API_MEDIA_TYPE, JsonApiContext, JsonApiError, JsonApiErrorObject, JsonApiRequest, JsonApiTransport, LinkageRef, ListPageParams, LocalIdentifier, MaterialiseContext, MediaTypeParams, MemberName, MenuDescriptor, NavigateOptions, Page, PageKeys, PageLinks, PageMeta, PageOf, PageParams, PageParamsOf, PaginatorDescriptor, PaginatorKind, PaginatorRole, PaginatorStrategy, PivotInput, ReadQuery, ReadResult, RelationDescriptor, RelationMutations, RelationName, RelationPageParams, RelationPaginator, RelationReadQuery, RelationView, RelationshipAccessor, RelationshipDocument, ResolvedPaginator, ResolvedValidator, ResourceAccessors, ResourceDescriptor, ResourceEnvelope, ResourceHandle, ResourceIdentifier, ResourceLinks, ResourceObjectView, SchemaMap, SingleReadQuery, SortQuery, SortToken, StrategyDescriptor, StructuralGuardError, ToDocumentOptions, TransportRequest, TransportResponse, TypeAccessor, TypeName, TypedPage, TypedReadQuery, UpdateInput, ValidationConfig, ValidationOption, Validator, WireResource, WriteDocument, WriteOptions, assertJsonApiDocument, createClient, execute, fetchTransport, hasErrorCode, materialise, mediaType, relatedPaginator, relatedPaginatorKind, remapPointer, remapRelationshipPointer, resolvePaginator, resolveValidator, runAtomic, serializeQuery, toDocument, toRelationshipDocument, validateDocument, withRemappedAtomicPaths, withRemappedPaths, withRemappedRelationshipPaths };
|
|
219
|
+
export { ATOMIC_EXT, ATOMIC_MEDIA_TYPE, ActionDescriptor, ActionInput, ActionOutput, ActionScope, ActionsAccessor, ApiDescriptor, ArrayEnvelope, AtomicCreateHandle, AtomicCreateInput, AtomicDeleteHandle, AtomicDescriptor, AtomicHandle, AtomicRecorder, AtomicRef, AtomicResult, AtomicResultOf, AtomicResults, AtomicUpdateHandle, AtomicUpdateInput, CANONICAL_PAGE_KEYS, Cardinality, Client, ClientIdPolicy, ClientOptions, Collection, CountToken, CountableDescriptor, CreateInput, DefaultActionTypes, DefaultAttributes, DefaultWriteAttributes, Document, DocumentEnvelope, Edge, EdgeEnvelope, EdgeMembers, ErrorCodeGuard, ErrorCodeOf, ExactPageParams, FieldSelectionMap, FieldsMap, FilterKey, FilterQuery, HANDLE_RESERVED, HeadersInit, HydratedMember, IdentifierMember, IncludePath, IncludedRelations, JSON_API_MEDIA_TYPE, JsonApiContext, JsonApiError, JsonApiErrorObject, JsonApiRequest, JsonApiTransport, LinkageRef, ListPageParams, LocalIdentifier, MaterialiseContext, MediaTypeParams, MemberName, MenuDescriptor, NavigateOptions, Page, PageKeys, PageLinks, PageMeta, PageOf, PageParams, PageParamsOf, PaginatorDescriptor, PaginatorKind, PaginatorRole, PaginatorStrategy, PivotDescriptor, PivotInput, ReadQuery, ReadResult, ReadableRelationName, RelationDescriptor, RelationMutations, RelationName, RelationPageParams, RelationPaginator, RelationReadQuery, RelationView, RelationshipAccessor, RelationshipDocument, ResolvedPaginator, ResolvedValidator, ResourceAccessors, ResourceDescriptor, ResourceEnvelope, ResourceHandle, ResourceIdentifier, ResourceLinks, ResourceObjectView, SchemaMap, SingleReadQuery, SortQuery, SortToken, StrategyDescriptor, StructuralGuardError, ToDocumentOptions, TransportRequest, TransportResponse, TypeAccessor, TypeName, TypedPage, TypedReadQuery, UpdateInput, ValidationConfig, ValidationOption, Validator, WireResource, WriteDocument, WriteOptions, assertJsonApiDocument, createClient, execute, fetchTransport, hasErrorCode, materialise, mediaType, relatedPaginator, relatedPaginatorKind, remapPointer, remapRelationshipPointer, resolvePaginator, resolveValidator, runAtomic, serializeQuery, toDocument, toRelationshipDocument, validateDocument, withRemappedAtomicPaths, withRemappedPaths, withRemappedRelationshipPaths };
|
|
220
220
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -539,12 +539,31 @@ const ATOMIC_MEDIA_TYPE = mediaType({ ext: [ATOMIC_EXT] });
|
|
|
539
539
|
//#region src/request.ts
|
|
540
540
|
const isAbsolute = (path) => /^https?:\/\//i.test(path);
|
|
541
541
|
const RELATIVE_ORIGIN = "http://relative.invalid";
|
|
542
|
-
/**
|
|
542
|
+
/**
|
|
543
|
+
* Resolve a server link against `baseUrl` per RFC 3986 and check where it lands. A link on the
|
|
544
|
+
* base's origin is followed; a relative base (e.g. `/api`) takes the page's origin in a browser and
|
|
545
|
+
* otherwise only accepts a link that keeps the host. Anything that isn't plain http(s) is refused.
|
|
546
|
+
*/
|
|
543
547
|
function resolveLink(link, baseUrl) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
+
let normalised = link.replace(/[\t\n\r]/g, "");
|
|
549
|
+
let start = 0;
|
|
550
|
+
while (start < normalised.length && normalised.charCodeAt(start) <= 32) start++;
|
|
551
|
+
normalised = normalised.slice(start);
|
|
552
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(normalised)?.[1]?.toLowerCase();
|
|
553
|
+
if (scheme !== void 0 && scheme !== "http" && scheme !== "https") throw new Error(`Refusing to follow link "${link}": only http(s) links are followed`);
|
|
554
|
+
const relativeBase = !isAbsolute(baseUrl);
|
|
555
|
+
const page = globalThis.location?.href;
|
|
556
|
+
const base = new URL(baseUrl, relativeBase ? typeof page === "string" ? page : RELATIVE_ORIGIN : void 0);
|
|
557
|
+
const resolved = new URL(link, base);
|
|
558
|
+
const sameOrigin = resolved.origin === base.origin && (scheme === void 0 || /^https?:\/\//i.test(normalised));
|
|
559
|
+
if (!sameOrigin) return {
|
|
560
|
+
url: resolved.href,
|
|
561
|
+
sameOrigin
|
|
562
|
+
};
|
|
563
|
+
return {
|
|
564
|
+
url: relativeBase ? resolved.pathname + resolved.search + resolved.hash : resolved.href,
|
|
565
|
+
sameOrigin
|
|
566
|
+
};
|
|
548
567
|
}
|
|
549
568
|
const append = (parts, key, value) => {
|
|
550
569
|
if (value === void 0 || value === null || value === "") return;
|
|
@@ -582,14 +601,21 @@ function serializeQuery(query) {
|
|
|
582
601
|
return parts.join("&");
|
|
583
602
|
}
|
|
584
603
|
/**
|
|
585
|
-
* Execute a request: build the URL (a server link resolved against `baseUrl
|
|
586
|
-
* verbatim, else `baseUrl + path`), apply content negotiation and any async per-request headers,
|
|
604
|
+
* Execute a request: build the URL (a server link resolved against `baseUrl` and held to its
|
|
605
|
+
* origin, an absolute path verbatim, else `baseUrl + path`), apply content negotiation and any async per-request headers,
|
|
587
606
|
* then drive the transport. A 2xx with a body parses to a {@link Document}; an empty /
|
|
588
607
|
* 204 body resolves to `undefined`. A non-2xx throws a {@link JsonApiError} carrying the
|
|
589
608
|
* parsed error objects (tolerating a non-JSON error body with an empty list).
|
|
590
609
|
*/
|
|
591
610
|
async function execute(ctx, req) {
|
|
592
|
-
let url
|
|
611
|
+
let url;
|
|
612
|
+
let sendHeaders = true;
|
|
613
|
+
if (req.link) {
|
|
614
|
+
const link = resolveLink(req.path, ctx.baseUrl);
|
|
615
|
+
if (!link.sameOrigin && ctx.allowCrossOriginLinks !== true) throw new Error(`Refusing to follow link "${req.path}": it leaves the base URL's origin (set allowCrossOriginLinks to follow it without the configured headers)`);
|
|
616
|
+
url = link.url;
|
|
617
|
+
sendHeaders = link.sameOrigin;
|
|
618
|
+
} else url = isAbsolute(req.path) ? req.path : ctx.baseUrl + req.path;
|
|
593
619
|
if (req.query) {
|
|
594
620
|
const qs = serializeQuery(req.query);
|
|
595
621
|
if (qs) url += (url.includes("?") ? "&" : "?") + qs;
|
|
@@ -609,7 +635,7 @@ async function execute(ctx, req) {
|
|
|
609
635
|
headers["Content-Type"] = req.contentType ?? negotiated;
|
|
610
636
|
transportReq.body = req.raw && typeof req.body === "string" ? req.body : JSON.stringify(req.body);
|
|
611
637
|
}
|
|
612
|
-
if (ctx.headers) mergeHeaders(headers, await ctx.headers());
|
|
638
|
+
if (ctx.headers && sendHeaders) mergeHeaders(headers, await ctx.headers());
|
|
613
639
|
const res = await ctx.transport(transportReq);
|
|
614
640
|
if (res.status >= 200 && res.status < 300) return parseDocument(res.body);
|
|
615
641
|
throw new JsonApiError(res.status, parseErrors(res.body));
|
|
@@ -1019,6 +1045,18 @@ const fetchTransport = async (req) => {
|
|
|
1019
1045
|
/** Substitute `{key}` placeholders in a path template (`/albums/{id}/{rel}` -> `/albums/1/tracks`). */
|
|
1020
1046
|
const fill = (template, vars) => template.replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? `{${key}}`);
|
|
1021
1047
|
/**
|
|
1048
|
+
* Fill a type's path template, putting a relation's URI segment (its `uriField`, else its name)
|
|
1049
|
+
* in place of `{rel}`. `vars.rel` stays the relation's name everywhere else.
|
|
1050
|
+
*/
|
|
1051
|
+
function fillPath(ctx, type, template, vars) {
|
|
1052
|
+
const rel = vars["rel"];
|
|
1053
|
+
const uriField = rel === void 0 ? void 0 : ctx.descriptor[type]?.relations[rel]?.uriField;
|
|
1054
|
+
return fill(template, uriField === void 0 ? vars : {
|
|
1055
|
+
...vars,
|
|
1056
|
+
rel: uriField
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1022
1060
|
* Run an operation: resolve the path template from the type's descriptor, fill its
|
|
1023
1061
|
* placeholders, execute, and materialise the resulting document. A no-body / 204 response
|
|
1024
1062
|
* materialises to `undefined`; an operation the type does not declare (no path template) throws
|
|
@@ -1031,7 +1069,7 @@ async function run(ctx, type, operation, vars, query, linkage = false, relationF
|
|
|
1031
1069
|
if (template === void 0) throw new Error(`No "${operation}" path declared for type "${type}"`);
|
|
1032
1070
|
const req = {
|
|
1033
1071
|
method: "GET",
|
|
1034
|
-
path:
|
|
1072
|
+
path: fillPath(ctx, type, template, vars)
|
|
1035
1073
|
};
|
|
1036
1074
|
if (query !== void 0) {
|
|
1037
1075
|
const { signal, ...params } = query;
|
|
@@ -1081,7 +1119,7 @@ async function runWrite(ctx, spec) {
|
|
|
1081
1119
|
if (template === void 0) throw new Error(`No "${spec.operation}" path declared for type "${spec.type}"`);
|
|
1082
1120
|
const req = {
|
|
1083
1121
|
method: spec.method,
|
|
1084
|
-
path:
|
|
1122
|
+
path: fillPath(ctx, spec.type, template, spec.vars),
|
|
1085
1123
|
body: spec.body
|
|
1086
1124
|
};
|
|
1087
1125
|
if (spec.query !== void 0) req.query = spec.query;
|
|
@@ -1180,8 +1218,9 @@ function relationshipAccessor(ctx, type, id, rel) {
|
|
|
1180
1218
|
return mutations === void 0 ? true : mutations[verb] === true;
|
|
1181
1219
|
};
|
|
1182
1220
|
const accessor = {};
|
|
1183
|
-
|
|
1184
|
-
if (relation?.
|
|
1221
|
+
const readable = relation?.writeOnly !== true;
|
|
1222
|
+
if (readable && relation?.relationship !== false) accessor["get"] = (query) => run(ctx, type, "fetchRelationship", vars, query, true, rel);
|
|
1223
|
+
if (readable && relation?.related !== false) accessor["related"] = (query) => run(ctx, type, "fetchRelated", vars, query, false, rel);
|
|
1185
1224
|
if (cardinality === "many") {
|
|
1186
1225
|
if (advertises("add")) accessor["add"] = (refs) => mutate("POST", refs);
|
|
1187
1226
|
if (advertises("remove")) accessor["remove"] = (refs) => mutate("DELETE", refs);
|
|
@@ -1266,7 +1305,8 @@ function createClient(descriptor, options) {
|
|
|
1266
1305
|
const request = {
|
|
1267
1306
|
baseUrl: options.baseUrl,
|
|
1268
1307
|
transport: options.transport ?? fetchTransport,
|
|
1269
|
-
...options.headers !== void 0 ? { headers: options.headers } : {}
|
|
1308
|
+
...options.headers !== void 0 ? { headers: options.headers } : {},
|
|
1309
|
+
...options.allowCrossOriginLinks !== void 0 ? { allowCrossOriginLinks: options.allowCrossOriginLinks } : {}
|
|
1270
1310
|
};
|
|
1271
1311
|
const validator = resolveValidator(options.validate);
|
|
1272
1312
|
const ctx = {
|