@haddowg/json-api-client 1.3.1 → 1.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 +6 -0
- package/dist/ajv.d.cts +1 -1
- package/dist/ajv.d.mts +1 -1
- package/dist/index.cjs +162 -18
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +162 -19
- package/dist/index.mjs.map +1 -1
- package/dist/{validate-B98ghVgw.d.cts → validate-CQv5lmqO.d.cts} +208 -19
- package/dist/validate-CQv5lmqO.d.cts.map +1 -0
- package/dist/{validate-B98ghVgw.d.mts → validate-CQv5lmqO.d.mts} +208 -19
- package/dist/validate-CQv5lmqO.d.mts.map +1 -0
- package/package.json +1 -1
- package/dist/validate-B98ghVgw.d.cts.map +0 -1
- package/dist/validate-B98ghVgw.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -297,6 +297,12 @@ Retries are deliberately out of scope here — the transport (or the TanStack la
|
|
|
297
297
|
or `$next({ signal })`). Forward it to your HTTP library so the request can be cancelled; the
|
|
298
298
|
default `fetch` transport passes it to `fetch`.
|
|
299
299
|
|
|
300
|
+
Two optional members matter only for custom actions with non-JSON:API media types. Send
|
|
301
|
+
`req.rawBody` as is when it's set: a binary, `FormData` or `URLSearchParams` body in place of
|
|
302
|
+
`req.body`. When `req.responseType` is `'blob'` or `'arraybuffer'`, read a successful response
|
|
303
|
+
into `binary` as that type and leave `body` empty; read an error response as text in `body` as
|
|
304
|
+
usual. The `fetch` transport does both.
|
|
305
|
+
|
|
300
306
|
## Opt-in validation
|
|
301
307
|
|
|
302
308
|
The core runtime trusts the wire (you own both ends; the envelope is invariant) and only runs the
|
package/dist/ajv.d.cts
CHANGED
package/dist/ajv.d.mts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -570,7 +570,7 @@ const append = (parts, key, value) => {
|
|
|
570
570
|
if (value === void 0 || value === null || value === "") return;
|
|
571
571
|
parts.push(`${key}=${encodeURIComponent(String(value))}`);
|
|
572
572
|
};
|
|
573
|
-
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
573
|
+
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
574
574
|
/**
|
|
575
575
|
* Append a filter value under its (already-bracketed) key. A plain object is a STRUCTURED filter
|
|
576
576
|
* value — a `Range`/`DateRange` deepObject (`{ min, max }`) — and recurses into nested bracketed
|
|
@@ -579,7 +579,7 @@ const isPlainObject = (value) => typeof value === "object" && value !== null &&
|
|
|
579
579
|
* wire as `filter[k]=[object Object]` and be silently ignored by the server (D23).
|
|
580
580
|
*/
|
|
581
581
|
const appendFilterValue = (parts, key, value) => {
|
|
582
|
-
if (isPlainObject(value)) {
|
|
582
|
+
if (isPlainObject$1(value)) {
|
|
583
583
|
for (const [subKey, subValue] of Object.entries(value)) appendFilterValue(parts, `${key}[${subKey}]`, subValue);
|
|
584
584
|
return;
|
|
585
585
|
}
|
|
@@ -609,6 +609,22 @@ function serializeQuery(query) {
|
|
|
609
609
|
* parsed error objects (tolerating a non-JSON error body with an empty list).
|
|
610
610
|
*/
|
|
611
611
|
async function execute(ctx, req) {
|
|
612
|
+
return parseDocument((await send(ctx, req)).body);
|
|
613
|
+
}
|
|
614
|
+
async function executeRaw(ctx, req, as) {
|
|
615
|
+
const res = await send(ctx, req, as === "text" ? void 0 : as);
|
|
616
|
+
if (as === "text") return res.body === "" ? void 0 : res.body;
|
|
617
|
+
if (res.status === 204) return;
|
|
618
|
+
if (res.binary === void 0) throw new Error("The transport returned no binary body for a request that asked for one; a custom transport must read the response as binary when the request sets responseType.");
|
|
619
|
+
return res.binary;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Build the URL (a server link resolved against `baseUrl` and held to its origin, an absolute
|
|
623
|
+
* path verbatim, else `baseUrl + path`), apply content negotiation and any async per-request
|
|
624
|
+
* headers, then drive the transport. A non-2xx throws a {@link JsonApiError} carrying the parsed
|
|
625
|
+
* error objects (tolerating a non-JSON error body with an empty list).
|
|
626
|
+
*/
|
|
627
|
+
async function send(ctx, req, responseType) {
|
|
612
628
|
let url;
|
|
613
629
|
let sendHeaders = true;
|
|
614
630
|
if (req.link) {
|
|
@@ -632,15 +648,23 @@ async function execute(ctx, req) {
|
|
|
632
648
|
headers
|
|
633
649
|
};
|
|
634
650
|
if (req.signal !== void 0) transportReq.signal = req.signal;
|
|
635
|
-
if (
|
|
636
|
-
|
|
637
|
-
|
|
651
|
+
if (responseType !== void 0) transportReq.responseType = responseType;
|
|
652
|
+
if (req.body !== void 0 || req.method.toUpperCase() === "POST") {
|
|
653
|
+
const contentType = req.contentType === void 0 ? negotiated : req.contentType;
|
|
654
|
+
if (contentType !== null) headers["Content-Type"] = contentType;
|
|
638
655
|
}
|
|
656
|
+
if (typeof req.body === "string" && req.raw) transportReq.body = req.body;
|
|
657
|
+
else if (req.raw && isRawBody(req.body)) transportReq.rawBody = req.body;
|
|
658
|
+
else if (req.body !== void 0) transportReq.body = JSON.stringify(req.body);
|
|
639
659
|
if (ctx.headers && sendHeaders) mergeHeaders(headers, await ctx.headers());
|
|
640
660
|
const res = await ctx.transport(transportReq);
|
|
641
|
-
if (res.status >= 200 && res.status < 300) return
|
|
661
|
+
if (res.status >= 200 && res.status < 300) return res;
|
|
642
662
|
throw new JsonApiError(res.status, parseErrors(res.body));
|
|
643
663
|
}
|
|
664
|
+
/** Whether a raw body goes to the transport as is rather than through `JSON.stringify`. */
|
|
665
|
+
function isRawBody(body) {
|
|
666
|
+
return typeof Blob !== "undefined" && body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || typeof FormData !== "undefined" && body instanceof FormData || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams;
|
|
667
|
+
}
|
|
644
668
|
/**
|
|
645
669
|
* Merge a `HeadersInit` (object / tuple array / `Headers`) into the outgoing map,
|
|
646
670
|
* preserving the caller's casing (unlike the WHATWG `Headers`, which lowercases). The
|
|
@@ -1028,13 +1052,23 @@ const fetchTransport = async (req) => {
|
|
|
1028
1052
|
method: req.method,
|
|
1029
1053
|
headers: req.headers
|
|
1030
1054
|
};
|
|
1031
|
-
if (req.
|
|
1055
|
+
if (req.rawBody !== void 0) init.body = req.rawBody;
|
|
1056
|
+
else if (req.body !== void 0) init.body = req.body;
|
|
1032
1057
|
if (req.signal !== void 0) init.signal = req.signal;
|
|
1033
1058
|
const res = await fetch(req.url, init);
|
|
1034
1059
|
const headers = {};
|
|
1035
1060
|
res.headers.forEach((value, key) => {
|
|
1036
1061
|
headers[key] = value;
|
|
1037
1062
|
});
|
|
1063
|
+
if (req.responseType !== void 0 && res.ok) {
|
|
1064
|
+
const binary = req.responseType === "blob" ? await res.blob() : await res.arrayBuffer();
|
|
1065
|
+
return {
|
|
1066
|
+
status: res.status,
|
|
1067
|
+
headers,
|
|
1068
|
+
body: "",
|
|
1069
|
+
binary
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1038
1072
|
return {
|
|
1039
1073
|
status: res.status,
|
|
1040
1074
|
headers,
|
|
@@ -1145,36 +1179,145 @@ async function runWrite(ctx, spec) {
|
|
|
1145
1179
|
* - `raw` — the caller's payload, sent with the action's declared media type (e.g.
|
|
1146
1180
|
* `application/octet-stream`), falling back to a wildcard when the spec declared none.
|
|
1147
1181
|
*
|
|
1182
|
+
* An action listing several request media types (`inputMedia`) sends the one the caller picks
|
|
1183
|
+
* with `options.contentType`, or JSON:API when listed, else the first.
|
|
1184
|
+
*
|
|
1148
1185
|
* The response is shaped by the action's `output`: `document` materialises into the resource /
|
|
1149
1186
|
* collection view, `meta` returns the document's top-level `meta`, `none` resolves `undefined` (a
|
|
1150
|
-
* `204` / no body).
|
|
1187
|
+
* `204` / no body). An action declaring `rawOutput` answers in the media type the caller picks
|
|
1188
|
+
* with `options.accept`: JSON:API as above, or a `string`, a `Blob` or `ArrayBuffer`, or parsed
|
|
1189
|
+
* JSON by the media type's kind.
|
|
1151
1190
|
*/
|
|
1152
|
-
async function runAction(ctx, action, vars, input) {
|
|
1191
|
+
async function runAction(ctx, action, vars, input, options = {}) {
|
|
1153
1192
|
const req = {
|
|
1154
1193
|
method: action.method ?? "POST",
|
|
1155
1194
|
path: fill(action.path, vars)
|
|
1156
1195
|
};
|
|
1196
|
+
if (options.signal !== void 0) req.signal = options.signal;
|
|
1157
1197
|
let remap;
|
|
1158
|
-
|
|
1198
|
+
const media = requestMedia(action, options.contentType);
|
|
1199
|
+
if (media !== void 0 && media.kind !== "document") {
|
|
1200
|
+
req.body = encodeFields(media.mediaType, media.kind, input);
|
|
1201
|
+
req.raw = true;
|
|
1202
|
+
req.contentType = sentContentType(media.mediaType, media.kind, req.body);
|
|
1203
|
+
} else if (action.input === "document") if (action.inputType !== void 0) {
|
|
1159
1204
|
const inputType = action.inputType;
|
|
1160
1205
|
req.body = toDocument(ctx.descriptor, inputType, input ?? {});
|
|
1161
1206
|
remap = (error) => withRemappedPaths(error, ctx.descriptor, inputType);
|
|
1162
1207
|
} else req.body = input;
|
|
1163
1208
|
else if (action.input === "raw") {
|
|
1164
|
-
req.body = input;
|
|
1165
|
-
req.contentType = action.contentType ?? "*/*";
|
|
1209
|
+
req.body = encodeFields(action.contentType ?? "*/*", void 0, input);
|
|
1210
|
+
req.contentType = sentContentType(action.contentType ?? "*/*", void 0, req.body);
|
|
1166
1211
|
req.raw = true;
|
|
1167
1212
|
}
|
|
1168
|
-
|
|
1213
|
+
const raw = responseMedia(action, options.accept);
|
|
1169
1214
|
try {
|
|
1170
|
-
|
|
1215
|
+
if (raw !== void 0) {
|
|
1216
|
+
req.accept = raw.mediaType;
|
|
1217
|
+
if (raw.kind === "binary") return await executeRaw(ctx.request, req, options.responseType === "arrayBuffer" ? "arraybuffer" : "blob");
|
|
1218
|
+
const text = await executeRaw(ctx.request, req, "text");
|
|
1219
|
+
return raw.kind === "json" && text !== void 0 ? JSON.parse(text) : text;
|
|
1220
|
+
}
|
|
1221
|
+
const doc = await execute(ctx.request, req);
|
|
1222
|
+
if (action.output === "none" || doc === void 0) return;
|
|
1223
|
+
if (action.output === "meta") return doc.meta;
|
|
1224
|
+
return materialise(doc, ctx.materialise);
|
|
1171
1225
|
} catch (error) {
|
|
1172
1226
|
if (remap !== void 0 && error instanceof JsonApiError) throw remap(error);
|
|
1173
1227
|
throw error;
|
|
1174
1228
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* The request media type an action with several (`inputMedia`) sends: the caller's pick, matched
|
|
1232
|
+
* exactly or through a declared wildcard such as `image/*`, else JSON:API when listed, else the
|
|
1233
|
+
* first. `undefined` for an action with a single request media type.
|
|
1234
|
+
*/
|
|
1235
|
+
function requestMedia(action, picked) {
|
|
1236
|
+
const listed = action.inputMedia;
|
|
1237
|
+
if (listed === void 0 || listed.length === 0) {
|
|
1238
|
+
if (picked !== void 0) throw new Error(`This action declares no request media types to choose "${picked}" from.`);
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
if (picked === void 0) {
|
|
1242
|
+
const media = listed.find((entry) => entry.mediaType === "application/vnd.api+json") ?? listed[0];
|
|
1243
|
+
return {
|
|
1244
|
+
mediaType: media.mediaType,
|
|
1245
|
+
kind: media.kind
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
const essence = picked.split(";")[0].trim().toLowerCase();
|
|
1249
|
+
const match = listed.find((entry) => entry.mediaType === essence) ?? listed.find((entry) => entry.mediaType === `${essence.split("/")[0]}/*`) ?? listed.find((entry) => entry.mediaType === "*/*");
|
|
1250
|
+
if (match === void 0) throw new Error(`This action does not accept "${picked}"; it accepts ${listed.map((entry) => entry.mediaType).join(", ")}.`);
|
|
1251
|
+
return {
|
|
1252
|
+
mediaType: picked,
|
|
1253
|
+
kind: match.kind
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* A multipart or URL-encoded body given as a plain object of fields, encoded into a `FormData` or
|
|
1258
|
+
* `URLSearchParams`. Any other body, a ready `FormData` included, is sent as it is.
|
|
1259
|
+
*/
|
|
1260
|
+
function encodeFields(mediaType, kind, body) {
|
|
1261
|
+
const multipart = kind === "multipart" || mediaType.startsWith("multipart/");
|
|
1262
|
+
if (!multipart && !(kind === "form" || mediaType === "application/x-www-form-urlencoded") || !isPlainObject(body)) return body;
|
|
1263
|
+
return multipart ? toFormData(body) : toSearchParams(body);
|
|
1264
|
+
}
|
|
1265
|
+
/** Whether a body is a plain object of fields rather than a ready payload. */
|
|
1266
|
+
function isPlainObject(value) {
|
|
1267
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1268
|
+
const proto = Object.getPrototypeOf(value);
|
|
1269
|
+
return proto === Object.prototype || proto === null;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* The entries a field contributes: nothing for `null` or `undefined`, one `name[]` entry per item
|
|
1273
|
+
* for an array (the bracket suffix is how PHP servers read a repeated field as a list), else one
|
|
1274
|
+
* `name` entry.
|
|
1275
|
+
*/
|
|
1276
|
+
function fieldEntries(name, value) {
|
|
1277
|
+
if (value === null || value === void 0) return [];
|
|
1278
|
+
if (Array.isArray(value)) return value.filter((item) => item !== null && item !== void 0).map((item) => [`${name}[]`, item]);
|
|
1279
|
+
return [[name, value]];
|
|
1280
|
+
}
|
|
1281
|
+
/** A field's text value: booleans as `true` or `false`, everything else through `String`. */
|
|
1282
|
+
function fieldText(value) {
|
|
1283
|
+
return typeof value === "boolean" ? value ? "true" : "false" : String(value);
|
|
1284
|
+
}
|
|
1285
|
+
/** A multipart body from fields. A `File` keeps its name; a bare `Blob` is named after its field. */
|
|
1286
|
+
function toFormData(fields) {
|
|
1287
|
+
const data = new FormData();
|
|
1288
|
+
for (const [field, value] of Object.entries(fields)) for (const [name, item] of fieldEntries(field, value)) if (typeof Blob !== "undefined" && item instanceof Blob) {
|
|
1289
|
+
const fileName = typeof File !== "undefined" && item instanceof File && item.name !== "" ? item.name : field;
|
|
1290
|
+
data.append(name, item, fileName);
|
|
1291
|
+
} else data.append(name, fieldText(item));
|
|
1292
|
+
return data;
|
|
1293
|
+
}
|
|
1294
|
+
/** A URL-encoded body from fields. */
|
|
1295
|
+
function toSearchParams(fields) {
|
|
1296
|
+
const params = new URLSearchParams();
|
|
1297
|
+
for (const [field, value] of Object.entries(fields)) for (const [name, item] of fieldEntries(field, value)) params.append(name, fieldText(item));
|
|
1298
|
+
return params;
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* The `Content-Type` for a non-JSON:API body. A multipart `FormData` body gets none, so the
|
|
1302
|
+
* transport writes the boundary. A wildcard media type takes a `Blob` body's own type.
|
|
1303
|
+
*/
|
|
1304
|
+
function sentContentType(mediaType, kind, body) {
|
|
1305
|
+
if ((kind === "multipart" || mediaType.startsWith("multipart/")) && typeof FormData !== "undefined" && body instanceof FormData) return null;
|
|
1306
|
+
if (mediaType.includes("*") && typeof Blob !== "undefined" && body instanceof Blob) return body.type === "" ? mediaType : body.type;
|
|
1307
|
+
return mediaType;
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* The non-JSON:API media type an action is asked to answer in: the caller's `accept` when it isn't
|
|
1311
|
+
* JSON:API, else, with no `accept`, the first raw media type of an action that answers in no
|
|
1312
|
+
* JSON:API document. `undefined` means a JSON:API response.
|
|
1313
|
+
*/
|
|
1314
|
+
function responseMedia(action, accept) {
|
|
1315
|
+
const raw = action.rawOutput ?? [];
|
|
1316
|
+
if (accept === void 0) return action.output === "none" ? raw[0] : void 0;
|
|
1317
|
+
if (accept === "application/vnd.api+json") return;
|
|
1318
|
+
const media = raw.find((entry) => entry.mediaType === accept);
|
|
1319
|
+
if (media === void 0) throw new Error(`This action does not answer in "${accept}"; it answers in ${[...action.output === "none" ? [] : [JSON_API_MEDIA_TYPE], ...raw.map((entry) => entry.mediaType)].join(", ")}.`);
|
|
1320
|
+
return media;
|
|
1178
1321
|
}
|
|
1179
1322
|
/**
|
|
1180
1323
|
* Build the typed `.actions` accessor at one scope: a plain object exposing exactly the
|
|
@@ -1186,7 +1329,7 @@ async function runAction(ctx, action, vars, input) {
|
|
|
1186
1329
|
function actionsAccessor(ctx, type, scope, vars) {
|
|
1187
1330
|
const actions = ctx.descriptor[type]?.actions ?? {};
|
|
1188
1331
|
const out = {};
|
|
1189
|
-
for (const [name, action] of Object.entries(actions)) if (action.scope === scope) out[name] = (
|
|
1332
|
+
for (const [name, action] of Object.entries(actions)) if (action.scope === scope) out[name] = (...args) => action.input === "none" ? runAction(ctx, action, vars, void 0, args[0]) : runAction(ctx, action, vars, args[0], args[1]);
|
|
1190
1333
|
return out;
|
|
1191
1334
|
}
|
|
1192
1335
|
/**
|
|
@@ -1360,6 +1503,7 @@ exports.StructuralGuardError = StructuralGuardError;
|
|
|
1360
1503
|
exports.assertJsonApiDocument = assertJsonApiDocument;
|
|
1361
1504
|
exports.createClient = createClient;
|
|
1362
1505
|
exports.execute = execute;
|
|
1506
|
+
exports.executeRaw = executeRaw;
|
|
1363
1507
|
exports.fetchTransport = fetchTransport;
|
|
1364
1508
|
exports.hasErrorCode = hasErrorCode;
|
|
1365
1509
|
exports.materialise = materialise;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as ReadResult, $t as PageOf, A as BinaryBody, At as JSON_API_MEDIA_TYPE, B as ErrorCodeOf, Bt as NavigateOptions, C as TransportRawBody, Cn as RawOutputMedia, Ct as AtomicResult, D as ActionCallOptions, Dn as StrategyDescriptor, Dt as runAtomic, E as fetchTransport, En as ResourceDescriptor, Et as AtomicUpdateInput, F as CreateInput, Ft as ArrayEnvelope, G as HydratedMember, Gt as relatedPaginator, H as FieldsMap, Ht as ResourceEnvelope, I as DefaultActionTypes, It as DocumentEnvelope, J as IncludedRelations, Jt as ExactPageParams, K as IdentifierMember, Kt as relatedPaginatorKind, L as DefaultAttributes, Lt as Edge, M as Client, Mt as MediaTypeParams, N as Collection, Nt as ResourceIdentifier, O as ActionsAccessor, Ot as ATOMIC_EXT, P as CountToken, Pt as mediaType, Q as PivotInput, Qt as PageMeta, R as DefaultWriteAttributes, Rt as EdgeEnvelope, S as JsonApiTransport, Sn as RawMediaKind, St as AtomicRef, T as TransportResponse, Tn as RelationMutations, Tt as AtomicUpdateHandle, U as FilterKey, Ut as ResourceLinks, V as FieldSelectionMap, Vt as RelationView, W as FilterQuery, Wt as materialise, X as ListPageParams, Xt as PageKeys, Y as LinkageRef, Yt as Page, Z as MemberName, Zt as PageLinks, _ as serializeQuery, _n as PaginatorDescriptor, _t as AtomicCreateHandle, a as Validator, an as ActionDescriptor, at as RelationshipAccessor, b as HeadersInit, bn as PaginatorStrategy, bt as AtomicHandle, c as resolveValidator, cn as ActionScope, ct as ResourceObjectView, d as JsonApiContext, dn as Cardinality, dt as SortToken, en as PageParams, et as ReadableRelationName, f as JsonApiRequest, fn as ClientIdPolicy, ft as TypeAccessor, g as executeRaw, gn as MenuDescriptor, gt as WriteOptions, h as execute, hn as InputMediaKind, ht as UpdateInput, i as ValidationOption, in as resolvePaginator, it as RelationReadQuery, j as BinaryResponseType, jt as LocalIdentifier, k as AtomicResultOf, kt as ATOMIC_MEDIA_TYPE, l as validateDocument, ln as ApiDescriptor, lt as SingleReadQuery, m as ReadQuery, mn as InputMedia, mt as TypedReadQuery, n as SchemaMap, nn as ResolvedPaginator, nt as RelationPageParams, o as WireResource, on as ActionInput, ot as ResourceAccessors, p as RawResponseType, pn as CountableDescriptor, pt as TypeName, q as IncludePath, qt as CANONICAL_PAGE_KEYS, r as ValidationConfig, rn as TypedPage, rt as RelationPaginator, s as assertJsonApiDocument, sn as ActionOutput, st as ResourceHandle, t as ResolvedValidator, tn as PageParamsOf, tt as RelationName, u as Document, un as AtomicDescriptor, ut as SortQuery, v as ClientOptions, vn as PaginatorKind, vt as AtomicCreateInput, w as TransportRequest, wn as RelationDescriptor, wt as AtomicResults, x as createClient, xn as PivotDescriptor, xt as AtomicRecorder, y as HANDLE_RESERVED, yn as PaginatorRole, yt as AtomicDeleteHandle, z as EdgeMembers, zt as MaterialiseContext } from "./validate-CQv5lmqO.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, 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 };
|
|
219
|
+
export { ATOMIC_EXT, ATOMIC_MEDIA_TYPE, ActionCallOptions, ActionDescriptor, ActionInput, ActionOutput, ActionScope, ActionsAccessor, ApiDescriptor, ArrayEnvelope, AtomicCreateHandle, AtomicCreateInput, AtomicDeleteHandle, AtomicDescriptor, AtomicHandle, AtomicRecorder, AtomicRef, AtomicResult, AtomicResultOf, AtomicResults, AtomicUpdateHandle, AtomicUpdateInput, BinaryBody, BinaryResponseType, 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, InputMedia, InputMediaKind, 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, RawMediaKind, RawOutputMedia, RawResponseType, 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, TransportRawBody, TransportRequest, TransportResponse, TypeAccessor, TypeName, TypedPage, TypedReadQuery, UpdateInput, ValidationConfig, ValidationOption, Validator, WireResource, WriteDocument, WriteOptions, assertJsonApiDocument, createClient, execute, executeRaw, 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 ReadResult, $t as PageOf, A as BinaryBody, At as JSON_API_MEDIA_TYPE, B as ErrorCodeOf, Bt as NavigateOptions, C as TransportRawBody, Cn as RawOutputMedia, Ct as AtomicResult, D as ActionCallOptions, Dn as StrategyDescriptor, Dt as runAtomic, E as fetchTransport, En as ResourceDescriptor, Et as AtomicUpdateInput, F as CreateInput, Ft as ArrayEnvelope, G as HydratedMember, Gt as relatedPaginator, H as FieldsMap, Ht as ResourceEnvelope, I as DefaultActionTypes, It as DocumentEnvelope, J as IncludedRelations, Jt as ExactPageParams, K as IdentifierMember, Kt as relatedPaginatorKind, L as DefaultAttributes, Lt as Edge, M as Client, Mt as MediaTypeParams, N as Collection, Nt as ResourceIdentifier, O as ActionsAccessor, Ot as ATOMIC_EXT, P as CountToken, Pt as mediaType, Q as PivotInput, Qt as PageMeta, R as DefaultWriteAttributes, Rt as EdgeEnvelope, S as JsonApiTransport, Sn as RawMediaKind, St as AtomicRef, T as TransportResponse, Tn as RelationMutations, Tt as AtomicUpdateHandle, U as FilterKey, Ut as ResourceLinks, V as FieldSelectionMap, Vt as RelationView, W as FilterQuery, Wt as materialise, X as ListPageParams, Xt as PageKeys, Y as LinkageRef, Yt as Page, Z as MemberName, Zt as PageLinks, _ as serializeQuery, _n as PaginatorDescriptor, _t as AtomicCreateHandle, a as Validator, an as ActionDescriptor, at as RelationshipAccessor, b as HeadersInit, bn as PaginatorStrategy, bt as AtomicHandle, c as resolveValidator, cn as ActionScope, ct as ResourceObjectView, d as JsonApiContext, dn as Cardinality, dt as SortToken, en as PageParams, et as ReadableRelationName, f as JsonApiRequest, fn as ClientIdPolicy, ft as TypeAccessor, g as executeRaw, gn as MenuDescriptor, gt as WriteOptions, h as execute, hn as InputMediaKind, ht as UpdateInput, i as ValidationOption, in as resolvePaginator, it as RelationReadQuery, j as BinaryResponseType, jt as LocalIdentifier, k as AtomicResultOf, kt as ATOMIC_MEDIA_TYPE, l as validateDocument, ln as ApiDescriptor, lt as SingleReadQuery, m as ReadQuery, mn as InputMedia, mt as TypedReadQuery, n as SchemaMap, nn as ResolvedPaginator, nt as RelationPageParams, o as WireResource, on as ActionInput, ot as ResourceAccessors, p as RawResponseType, pn as CountableDescriptor, pt as TypeName, q as IncludePath, qt as CANONICAL_PAGE_KEYS, r as ValidationConfig, rn as TypedPage, rt as RelationPaginator, s as assertJsonApiDocument, sn as ActionOutput, st as ResourceHandle, t as ResolvedValidator, tn as PageParamsOf, tt as RelationName, u as Document, un as AtomicDescriptor, ut as SortQuery, v as ClientOptions, vn as PaginatorKind, vt as AtomicCreateInput, w as TransportRequest, wn as RelationDescriptor, wt as AtomicResults, x as createClient, xn as PivotDescriptor, xt as AtomicRecorder, y as HANDLE_RESERVED, yn as PaginatorRole, yt as AtomicDeleteHandle, z as EdgeMembers, zt as MaterialiseContext } from "./validate-CQv5lmqO.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, 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 };
|
|
219
|
+
export { ATOMIC_EXT, ATOMIC_MEDIA_TYPE, ActionCallOptions, ActionDescriptor, ActionInput, ActionOutput, ActionScope, ActionsAccessor, ApiDescriptor, ArrayEnvelope, AtomicCreateHandle, AtomicCreateInput, AtomicDeleteHandle, AtomicDescriptor, AtomicHandle, AtomicRecorder, AtomicRef, AtomicResult, AtomicResultOf, AtomicResults, AtomicUpdateHandle, AtomicUpdateInput, BinaryBody, BinaryResponseType, 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, InputMedia, InputMediaKind, 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, RawMediaKind, RawOutputMedia, RawResponseType, 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, TransportRawBody, TransportRequest, TransportResponse, TypeAccessor, TypeName, TypedPage, TypedReadQuery, UpdateInput, ValidationConfig, ValidationOption, Validator, WireResource, WriteDocument, WriteOptions, assertJsonApiDocument, createClient, execute, executeRaw, 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
|
@@ -569,7 +569,7 @@ const append = (parts, key, value) => {
|
|
|
569
569
|
if (value === void 0 || value === null || value === "") return;
|
|
570
570
|
parts.push(`${key}=${encodeURIComponent(String(value))}`);
|
|
571
571
|
};
|
|
572
|
-
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
572
|
+
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
573
573
|
/**
|
|
574
574
|
* Append a filter value under its (already-bracketed) key. A plain object is a STRUCTURED filter
|
|
575
575
|
* value — a `Range`/`DateRange` deepObject (`{ min, max }`) — and recurses into nested bracketed
|
|
@@ -578,7 +578,7 @@ const isPlainObject = (value) => typeof value === "object" && value !== null &&
|
|
|
578
578
|
* wire as `filter[k]=[object Object]` and be silently ignored by the server (D23).
|
|
579
579
|
*/
|
|
580
580
|
const appendFilterValue = (parts, key, value) => {
|
|
581
|
-
if (isPlainObject(value)) {
|
|
581
|
+
if (isPlainObject$1(value)) {
|
|
582
582
|
for (const [subKey, subValue] of Object.entries(value)) appendFilterValue(parts, `${key}[${subKey}]`, subValue);
|
|
583
583
|
return;
|
|
584
584
|
}
|
|
@@ -608,6 +608,22 @@ function serializeQuery(query) {
|
|
|
608
608
|
* parsed error objects (tolerating a non-JSON error body with an empty list).
|
|
609
609
|
*/
|
|
610
610
|
async function execute(ctx, req) {
|
|
611
|
+
return parseDocument((await send(ctx, req)).body);
|
|
612
|
+
}
|
|
613
|
+
async function executeRaw(ctx, req, as) {
|
|
614
|
+
const res = await send(ctx, req, as === "text" ? void 0 : as);
|
|
615
|
+
if (as === "text") return res.body === "" ? void 0 : res.body;
|
|
616
|
+
if (res.status === 204) return;
|
|
617
|
+
if (res.binary === void 0) throw new Error("The transport returned no binary body for a request that asked for one; a custom transport must read the response as binary when the request sets responseType.");
|
|
618
|
+
return res.binary;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Build the URL (a server link resolved against `baseUrl` and held to its origin, an absolute
|
|
622
|
+
* path verbatim, else `baseUrl + path`), apply content negotiation and any async per-request
|
|
623
|
+
* headers, then drive the transport. A non-2xx throws a {@link JsonApiError} carrying the parsed
|
|
624
|
+
* error objects (tolerating a non-JSON error body with an empty list).
|
|
625
|
+
*/
|
|
626
|
+
async function send(ctx, req, responseType) {
|
|
611
627
|
let url;
|
|
612
628
|
let sendHeaders = true;
|
|
613
629
|
if (req.link) {
|
|
@@ -631,15 +647,23 @@ async function execute(ctx, req) {
|
|
|
631
647
|
headers
|
|
632
648
|
};
|
|
633
649
|
if (req.signal !== void 0) transportReq.signal = req.signal;
|
|
634
|
-
if (
|
|
635
|
-
|
|
636
|
-
|
|
650
|
+
if (responseType !== void 0) transportReq.responseType = responseType;
|
|
651
|
+
if (req.body !== void 0 || req.method.toUpperCase() === "POST") {
|
|
652
|
+
const contentType = req.contentType === void 0 ? negotiated : req.contentType;
|
|
653
|
+
if (contentType !== null) headers["Content-Type"] = contentType;
|
|
637
654
|
}
|
|
655
|
+
if (typeof req.body === "string" && req.raw) transportReq.body = req.body;
|
|
656
|
+
else if (req.raw && isRawBody(req.body)) transportReq.rawBody = req.body;
|
|
657
|
+
else if (req.body !== void 0) transportReq.body = JSON.stringify(req.body);
|
|
638
658
|
if (ctx.headers && sendHeaders) mergeHeaders(headers, await ctx.headers());
|
|
639
659
|
const res = await ctx.transport(transportReq);
|
|
640
|
-
if (res.status >= 200 && res.status < 300) return
|
|
660
|
+
if (res.status >= 200 && res.status < 300) return res;
|
|
641
661
|
throw new JsonApiError(res.status, parseErrors(res.body));
|
|
642
662
|
}
|
|
663
|
+
/** Whether a raw body goes to the transport as is rather than through `JSON.stringify`. */
|
|
664
|
+
function isRawBody(body) {
|
|
665
|
+
return typeof Blob !== "undefined" && body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || typeof FormData !== "undefined" && body instanceof FormData || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams;
|
|
666
|
+
}
|
|
643
667
|
/**
|
|
644
668
|
* Merge a `HeadersInit` (object / tuple array / `Headers`) into the outgoing map,
|
|
645
669
|
* preserving the caller's casing (unlike the WHATWG `Headers`, which lowercases). The
|
|
@@ -1027,13 +1051,23 @@ const fetchTransport = async (req) => {
|
|
|
1027
1051
|
method: req.method,
|
|
1028
1052
|
headers: req.headers
|
|
1029
1053
|
};
|
|
1030
|
-
if (req.
|
|
1054
|
+
if (req.rawBody !== void 0) init.body = req.rawBody;
|
|
1055
|
+
else if (req.body !== void 0) init.body = req.body;
|
|
1031
1056
|
if (req.signal !== void 0) init.signal = req.signal;
|
|
1032
1057
|
const res = await fetch(req.url, init);
|
|
1033
1058
|
const headers = {};
|
|
1034
1059
|
res.headers.forEach((value, key) => {
|
|
1035
1060
|
headers[key] = value;
|
|
1036
1061
|
});
|
|
1062
|
+
if (req.responseType !== void 0 && res.ok) {
|
|
1063
|
+
const binary = req.responseType === "blob" ? await res.blob() : await res.arrayBuffer();
|
|
1064
|
+
return {
|
|
1065
|
+
status: res.status,
|
|
1066
|
+
headers,
|
|
1067
|
+
body: "",
|
|
1068
|
+
binary
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1037
1071
|
return {
|
|
1038
1072
|
status: res.status,
|
|
1039
1073
|
headers,
|
|
@@ -1144,36 +1178,145 @@ async function runWrite(ctx, spec) {
|
|
|
1144
1178
|
* - `raw` — the caller's payload, sent with the action's declared media type (e.g.
|
|
1145
1179
|
* `application/octet-stream`), falling back to a wildcard when the spec declared none.
|
|
1146
1180
|
*
|
|
1181
|
+
* An action listing several request media types (`inputMedia`) sends the one the caller picks
|
|
1182
|
+
* with `options.contentType`, or JSON:API when listed, else the first.
|
|
1183
|
+
*
|
|
1147
1184
|
* The response is shaped by the action's `output`: `document` materialises into the resource /
|
|
1148
1185
|
* collection view, `meta` returns the document's top-level `meta`, `none` resolves `undefined` (a
|
|
1149
|
-
* `204` / no body).
|
|
1186
|
+
* `204` / no body). An action declaring `rawOutput` answers in the media type the caller picks
|
|
1187
|
+
* with `options.accept`: JSON:API as above, or a `string`, a `Blob` or `ArrayBuffer`, or parsed
|
|
1188
|
+
* JSON by the media type's kind.
|
|
1150
1189
|
*/
|
|
1151
|
-
async function runAction(ctx, action, vars, input) {
|
|
1190
|
+
async function runAction(ctx, action, vars, input, options = {}) {
|
|
1152
1191
|
const req = {
|
|
1153
1192
|
method: action.method ?? "POST",
|
|
1154
1193
|
path: fill(action.path, vars)
|
|
1155
1194
|
};
|
|
1195
|
+
if (options.signal !== void 0) req.signal = options.signal;
|
|
1156
1196
|
let remap;
|
|
1157
|
-
|
|
1197
|
+
const media = requestMedia(action, options.contentType);
|
|
1198
|
+
if (media !== void 0 && media.kind !== "document") {
|
|
1199
|
+
req.body = encodeFields(media.mediaType, media.kind, input);
|
|
1200
|
+
req.raw = true;
|
|
1201
|
+
req.contentType = sentContentType(media.mediaType, media.kind, req.body);
|
|
1202
|
+
} else if (action.input === "document") if (action.inputType !== void 0) {
|
|
1158
1203
|
const inputType = action.inputType;
|
|
1159
1204
|
req.body = toDocument(ctx.descriptor, inputType, input ?? {});
|
|
1160
1205
|
remap = (error) => withRemappedPaths(error, ctx.descriptor, inputType);
|
|
1161
1206
|
} else req.body = input;
|
|
1162
1207
|
else if (action.input === "raw") {
|
|
1163
|
-
req.body = input;
|
|
1164
|
-
req.contentType = action.contentType ?? "*/*";
|
|
1208
|
+
req.body = encodeFields(action.contentType ?? "*/*", void 0, input);
|
|
1209
|
+
req.contentType = sentContentType(action.contentType ?? "*/*", void 0, req.body);
|
|
1165
1210
|
req.raw = true;
|
|
1166
1211
|
}
|
|
1167
|
-
|
|
1212
|
+
const raw = responseMedia(action, options.accept);
|
|
1168
1213
|
try {
|
|
1169
|
-
|
|
1214
|
+
if (raw !== void 0) {
|
|
1215
|
+
req.accept = raw.mediaType;
|
|
1216
|
+
if (raw.kind === "binary") return await executeRaw(ctx.request, req, options.responseType === "arrayBuffer" ? "arraybuffer" : "blob");
|
|
1217
|
+
const text = await executeRaw(ctx.request, req, "text");
|
|
1218
|
+
return raw.kind === "json" && text !== void 0 ? JSON.parse(text) : text;
|
|
1219
|
+
}
|
|
1220
|
+
const doc = await execute(ctx.request, req);
|
|
1221
|
+
if (action.output === "none" || doc === void 0) return;
|
|
1222
|
+
if (action.output === "meta") return doc.meta;
|
|
1223
|
+
return materialise(doc, ctx.materialise);
|
|
1170
1224
|
} catch (error) {
|
|
1171
1225
|
if (remap !== void 0 && error instanceof JsonApiError) throw remap(error);
|
|
1172
1226
|
throw error;
|
|
1173
1227
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* The request media type an action with several (`inputMedia`) sends: the caller's pick, matched
|
|
1231
|
+
* exactly or through a declared wildcard such as `image/*`, else JSON:API when listed, else the
|
|
1232
|
+
* first. `undefined` for an action with a single request media type.
|
|
1233
|
+
*/
|
|
1234
|
+
function requestMedia(action, picked) {
|
|
1235
|
+
const listed = action.inputMedia;
|
|
1236
|
+
if (listed === void 0 || listed.length === 0) {
|
|
1237
|
+
if (picked !== void 0) throw new Error(`This action declares no request media types to choose "${picked}" from.`);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (picked === void 0) {
|
|
1241
|
+
const media = listed.find((entry) => entry.mediaType === "application/vnd.api+json") ?? listed[0];
|
|
1242
|
+
return {
|
|
1243
|
+
mediaType: media.mediaType,
|
|
1244
|
+
kind: media.kind
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
const essence = picked.split(";")[0].trim().toLowerCase();
|
|
1248
|
+
const match = listed.find((entry) => entry.mediaType === essence) ?? listed.find((entry) => entry.mediaType === `${essence.split("/")[0]}/*`) ?? listed.find((entry) => entry.mediaType === "*/*");
|
|
1249
|
+
if (match === void 0) throw new Error(`This action does not accept "${picked}"; it accepts ${listed.map((entry) => entry.mediaType).join(", ")}.`);
|
|
1250
|
+
return {
|
|
1251
|
+
mediaType: picked,
|
|
1252
|
+
kind: match.kind
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* A multipart or URL-encoded body given as a plain object of fields, encoded into a `FormData` or
|
|
1257
|
+
* `URLSearchParams`. Any other body, a ready `FormData` included, is sent as it is.
|
|
1258
|
+
*/
|
|
1259
|
+
function encodeFields(mediaType, kind, body) {
|
|
1260
|
+
const multipart = kind === "multipart" || mediaType.startsWith("multipart/");
|
|
1261
|
+
if (!multipart && !(kind === "form" || mediaType === "application/x-www-form-urlencoded") || !isPlainObject(body)) return body;
|
|
1262
|
+
return multipart ? toFormData(body) : toSearchParams(body);
|
|
1263
|
+
}
|
|
1264
|
+
/** Whether a body is a plain object of fields rather than a ready payload. */
|
|
1265
|
+
function isPlainObject(value) {
|
|
1266
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1267
|
+
const proto = Object.getPrototypeOf(value);
|
|
1268
|
+
return proto === Object.prototype || proto === null;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* The entries a field contributes: nothing for `null` or `undefined`, one `name[]` entry per item
|
|
1272
|
+
* for an array (the bracket suffix is how PHP servers read a repeated field as a list), else one
|
|
1273
|
+
* `name` entry.
|
|
1274
|
+
*/
|
|
1275
|
+
function fieldEntries(name, value) {
|
|
1276
|
+
if (value === null || value === void 0) return [];
|
|
1277
|
+
if (Array.isArray(value)) return value.filter((item) => item !== null && item !== void 0).map((item) => [`${name}[]`, item]);
|
|
1278
|
+
return [[name, value]];
|
|
1279
|
+
}
|
|
1280
|
+
/** A field's text value: booleans as `true` or `false`, everything else through `String`. */
|
|
1281
|
+
function fieldText(value) {
|
|
1282
|
+
return typeof value === "boolean" ? value ? "true" : "false" : String(value);
|
|
1283
|
+
}
|
|
1284
|
+
/** A multipart body from fields. A `File` keeps its name; a bare `Blob` is named after its field. */
|
|
1285
|
+
function toFormData(fields) {
|
|
1286
|
+
const data = new FormData();
|
|
1287
|
+
for (const [field, value] of Object.entries(fields)) for (const [name, item] of fieldEntries(field, value)) if (typeof Blob !== "undefined" && item instanceof Blob) {
|
|
1288
|
+
const fileName = typeof File !== "undefined" && item instanceof File && item.name !== "" ? item.name : field;
|
|
1289
|
+
data.append(name, item, fileName);
|
|
1290
|
+
} else data.append(name, fieldText(item));
|
|
1291
|
+
return data;
|
|
1292
|
+
}
|
|
1293
|
+
/** A URL-encoded body from fields. */
|
|
1294
|
+
function toSearchParams(fields) {
|
|
1295
|
+
const params = new URLSearchParams();
|
|
1296
|
+
for (const [field, value] of Object.entries(fields)) for (const [name, item] of fieldEntries(field, value)) params.append(name, fieldText(item));
|
|
1297
|
+
return params;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* The `Content-Type` for a non-JSON:API body. A multipart `FormData` body gets none, so the
|
|
1301
|
+
* transport writes the boundary. A wildcard media type takes a `Blob` body's own type.
|
|
1302
|
+
*/
|
|
1303
|
+
function sentContentType(mediaType, kind, body) {
|
|
1304
|
+
if ((kind === "multipart" || mediaType.startsWith("multipart/")) && typeof FormData !== "undefined" && body instanceof FormData) return null;
|
|
1305
|
+
if (mediaType.includes("*") && typeof Blob !== "undefined" && body instanceof Blob) return body.type === "" ? mediaType : body.type;
|
|
1306
|
+
return mediaType;
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* The non-JSON:API media type an action is asked to answer in: the caller's `accept` when it isn't
|
|
1310
|
+
* JSON:API, else, with no `accept`, the first raw media type of an action that answers in no
|
|
1311
|
+
* JSON:API document. `undefined` means a JSON:API response.
|
|
1312
|
+
*/
|
|
1313
|
+
function responseMedia(action, accept) {
|
|
1314
|
+
const raw = action.rawOutput ?? [];
|
|
1315
|
+
if (accept === void 0) return action.output === "none" ? raw[0] : void 0;
|
|
1316
|
+
if (accept === "application/vnd.api+json") return;
|
|
1317
|
+
const media = raw.find((entry) => entry.mediaType === accept);
|
|
1318
|
+
if (media === void 0) throw new Error(`This action does not answer in "${accept}"; it answers in ${[...action.output === "none" ? [] : [JSON_API_MEDIA_TYPE], ...raw.map((entry) => entry.mediaType)].join(", ")}.`);
|
|
1319
|
+
return media;
|
|
1177
1320
|
}
|
|
1178
1321
|
/**
|
|
1179
1322
|
* Build the typed `.actions` accessor at one scope: a plain object exposing exactly the
|
|
@@ -1185,7 +1328,7 @@ async function runAction(ctx, action, vars, input) {
|
|
|
1185
1328
|
function actionsAccessor(ctx, type, scope, vars) {
|
|
1186
1329
|
const actions = ctx.descriptor[type]?.actions ?? {};
|
|
1187
1330
|
const out = {};
|
|
1188
|
-
for (const [name, action] of Object.entries(actions)) if (action.scope === scope) out[name] = (
|
|
1331
|
+
for (const [name, action] of Object.entries(actions)) if (action.scope === scope) out[name] = (...args) => action.input === "none" ? runAction(ctx, action, vars, void 0, args[0]) : runAction(ctx, action, vars, args[0], args[1]);
|
|
1189
1332
|
return out;
|
|
1190
1333
|
}
|
|
1191
1334
|
/**
|
|
@@ -1349,6 +1492,6 @@ function createClient(descriptor, options) {
|
|
|
1349
1492
|
});
|
|
1350
1493
|
}
|
|
1351
1494
|
//#endregion
|
|
1352
|
-
export { ATOMIC_EXT, ATOMIC_MEDIA_TYPE, CANONICAL_PAGE_KEYS, HANDLE_RESERVED, JSON_API_MEDIA_TYPE, JsonApiError, StructuralGuardError, assertJsonApiDocument, createClient, execute, fetchTransport, hasErrorCode, materialise, mediaType, relatedPaginator, relatedPaginatorKind, remapPointer, remapRelationshipPointer, resolvePaginator, resolveValidator, runAtomic, serializeQuery, toDocument, toRelationshipDocument, validateDocument, withRemappedAtomicPaths, withRemappedPaths, withRemappedRelationshipPaths };
|
|
1495
|
+
export { ATOMIC_EXT, ATOMIC_MEDIA_TYPE, CANONICAL_PAGE_KEYS, HANDLE_RESERVED, JSON_API_MEDIA_TYPE, JsonApiError, StructuralGuardError, assertJsonApiDocument, createClient, execute, executeRaw, fetchTransport, hasErrorCode, materialise, mediaType, relatedPaginator, relatedPaginatorKind, remapPointer, remapRelationshipPointer, resolvePaginator, resolveValidator, runAtomic, serializeQuery, toDocument, toRelationshipDocument, validateDocument, withRemappedAtomicPaths, withRemappedPaths, withRemappedRelationshipPaths };
|
|
1353
1496
|
|
|
1354
1497
|
//# sourceMappingURL=index.mjs.map
|