@zeroxsolutions/jsonapi 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 0.13.2 (2026-10-06)
2
+
3
+ No code changes. It depends on @zeroxsolutions/response, so its version follows response 0.14.4.
4
+
5
+ ## 0.13.1 (2026-09-30)
6
+
7
+ ### 🚀 Features
8
+
9
+ - **jsonapi:** build the request document a client sends ([20ca1a6](https://github.com/zeroxsolutions/zeroxsolutions/commit/20ca1a6))
10
+
11
+ ### ❤️ Thank You
12
+
13
+ - Claude Opus 5.5 (1M context)
14
+ - Lương Văn Tú
15
+
1
16
  ## 0.13.0 (2026-09-29)
2
17
 
3
18
  ### 🩹 Fixes
package/README.md CHANGED
@@ -27,7 +27,7 @@ what keeps `hono` out of the package an SPA also loads.
27
27
  | Import | Use in | What it holds |
28
28
  | -------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29
29
  | `@zeroxsolutions/jsonapi` | services, gateway | `negotiateJsonApi` - the whole decision, from `Content-Type`, `Accept` and the `ext` URIs this surface supports (`supportedExtensions`), to the `JsonApiError` to reject with or `null`: 415 where `Content-Type` is the JSON:API media type carrying a parameter beyond `ext`/`profile` or an `ext` outside `supportedExtensions`; 406 where `Accept` holds JSON:API instances and every one carries such a parameter or `ext`; nothing else |
30
- | `@zeroxsolutions/jsonapi/client` | SPA, external consumers | `deserializeDocument` (jsona), `readResource` / `readCollection` over it - the same flattening, typed off the route's own document - `readRelated` for what an `include` resolved, which no document type can name; and `ApiError`, the non-2xx a caller throws once a response has come back |
30
+ | `@zeroxsolutions/jsonapi/client` | SPA, external consumers | `deserializeDocument` (jsona), `readResource` / `readCollection` over it - the same flattening, typed off the route's own document - `readRelated` for what an `include` resolved, which no document type can name; `toRequestDocument`, the JSON:API request document a write sends, each relationship wrapped in its relationship object; and `ApiError`, the non-2xx a caller throws once a response has come back |
31
31
 
32
32
  A transport mounts the decision rather than calling it: `jsonApiContentNegotiation` is the Hono adapter
33
33
  over it, and it lives with the other framework wiring. `negotiateJsonApi` itself takes the surface's
@@ -1,7 +1,9 @@
1
1
  /**
2
- * The client half of the standard: the document a response carries, flattened once, and the failure a caller throws.
2
+ * The client half of the standard: the document a response carries, flattened once, the document a write sends, and
3
+ * the failure a caller throws.
3
4
  */
4
5
  export { ApiError } from './api-error.js';
5
6
  export { deserializeDocument } from './deserialize.js';
6
7
  export { readCollection, readRelated, readResource, type Collection, type CollectionOf, type Resource, type ResourceOf, } from './read-document.js';
8
+ export { toRequestDocument, type RelationshipInput, type ToRequestDocument } from './request-document.js';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EACL,cAAc,EACd,WAAW,EACX,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EACL,cAAc,EACd,WAAW,EACX,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,6 +1,8 @@
1
1
  /**
2
- * The client half of the standard: the document a response carries, flattened once, and the failure a caller throws.
2
+ * The client half of the standard: the document a response carries, flattened once, the document a write sends, and
3
+ * the failure a caller throws.
3
4
  */
4
5
  export { ApiError } from './api-error.js';
5
6
  export { deserializeDocument } from './deserialize.js';
6
7
  export { readCollection, readRelated, readResource, } from './read-document.js';
8
+ export { toRequestDocument } from './request-document.js';
@@ -0,0 +1,43 @@
1
+ /** One relationship as a client writes it: the related resource's identifier, `null` to clear a to-one, or a list. */
2
+ export type RelationshipInput = {
3
+ readonly type: string;
4
+ readonly id: string;
5
+ } | null | readonly {
6
+ readonly type: string;
7
+ readonly id: string;
8
+ }[];
9
+ /**
10
+ * The request document {@link toRequestDocument} returns: the `type`, then each member it was handed and no other, so
11
+ * a route's declared body checks it member by member.
12
+ */
13
+ export type ToRequestDocument<T extends string, Id, A, L> = {
14
+ data: {
15
+ type: T;
16
+ } & ([Id] extends [string] ? {
17
+ id: Id;
18
+ } : unknown) & ([A] extends [never] ? unknown : {
19
+ attributes: A;
20
+ }) & ([L] extends [never] ? unknown : {
21
+ relationships: {
22
+ [K in keyof L]: {
23
+ data: L[K];
24
+ };
25
+ };
26
+ });
27
+ };
28
+ /**
29
+ * Builds the JSON:API request document a write sends: one resource object of `type`, its attributes, and each
30
+ * relationship wrapped in the relationship object the standard requires, so no caller writes `{ data: ... }` by hand.
31
+ *
32
+ * The result is typed from the call alone, and never from the type it is assigned to, so a route's declared body can
33
+ * refuse a document missing the `id` an update needs.
34
+ *
35
+ * @param init - `id` on an update and never on a create; the attributes the write changes; each relationship as the
36
+ * related resource's identifier, `null` to clear a to-one, or a list for a to-many
37
+ */
38
+ export declare function toRequestDocument<const T extends string, Id extends string | undefined = undefined, A extends object = never, const L extends Readonly<Record<string, RelationshipInput>> = never>(type: T, init: {
39
+ id?: Id;
40
+ attributes?: A;
41
+ relationships?: L;
42
+ }): ToRequestDocument<T, NoInfer<Id>, NoInfer<A>, NoInfer<L>>;
43
+ //# sourceMappingURL=request-document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-document.d.ts","sourceRoot":"","sources":["../../src/client/request-document.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9C,IAAI,GACJ,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC;AAE9D;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI;IAC1D,IAAI,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG;QAAE,EAAE,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,GAChE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG;QAAE,UAAU,EAAE,CAAC,CAAA;KAAE,CAAC,GACnD,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG;QAAE,aAAa,EAAE;aAAG,CAAC,IAAI,MAAM,CAAC,GAAG;gBAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;aAAE;SAAE,CAAA;KAAE,CAAC,CAAC;CAC3F,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,CAAC,CAAC,SAAS,MAAM,EACtB,EAAE,SAAS,MAAM,GAAG,SAAS,GAAG,SAAS,EACzC,CAAC,SAAS,MAAM,GAAG,KAAK,EACxB,KAAK,CAAC,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,GAAG,KAAK,EAEnE,IAAI,EAAE,CAAC,EACP,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAAC,aAAa,CAAC,EAAE,CAAC,CAAA;CAAE,GACnD,iBAAiB,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAe3D"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Builds the JSON:API request document a write sends: one resource object of `type`, its attributes, and each
3
+ * relationship wrapped in the relationship object the standard requires, so no caller writes `{ data: ... }` by hand.
4
+ *
5
+ * The result is typed from the call alone, and never from the type it is assigned to, so a route's declared body can
6
+ * refuse a document missing the `id` an update needs.
7
+ *
8
+ * @param init - `id` on an update and never on a create; the attributes the write changes; each relationship as the
9
+ * related resource's identifier, `null` to clear a to-one, or a list for a to-many
10
+ */
11
+ export function toRequestDocument(type, init) {
12
+ const { id, attributes, relationships } = init;
13
+ const linkage = relationships === undefined
14
+ ? undefined
15
+ : Object.fromEntries(Object.entries(relationships).map(([name, data]) => [name, { data }]));
16
+ // The members are the ones handed, which is what the conditional type above spells.
17
+ return {
18
+ data: {
19
+ type,
20
+ ...(id === undefined ? {} : { id }),
21
+ ...(attributes === undefined ? {} : { attributes }),
22
+ ...(linkage === undefined ? {} : { relationships: linkage }),
23
+ },
24
+ };
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxsolutions/jsonapi",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -30,6 +30,6 @@
30
30
  "dependencies": {
31
31
  "jsona": "^1.12.0",
32
32
  "tslib": "^2.3.0",
33
- "@zeroxsolutions/response": "0.14.2"
33
+ "@zeroxsolutions/response": "0.14.4"
34
34
  }
35
35
  }