@zeroxsolutions/response 0.13.0 → 0.14.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/CHANGELOG.md CHANGED
@@ -1,3 +1,92 @@
1
+ ## 0.14.1 (2026-09-28)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **response:** type a serialized collection by the members it was handed ([5f1a2f5](https://github.com/zeroxsolutions/zeroxsolutions/commit/5f1a2f5))
6
+ - **response:** let a collection response declare its meta and links ([7286ca3](https://github.com/zeroxsolutions/zeroxsolutions/commit/7286ca3))
7
+ - **response:** ship the counted meta and offset links a collection sends ([44adb03](https://github.com/zeroxsolutions/zeroxsolutions/commit/44adb03))
8
+
9
+ ### 🩹 Fixes
10
+
11
+ - **response:** refuse a serialized collection that may lack a declared member ([f2e2c4f](https://github.com/zeroxsolutions/zeroxsolutions/commit/f2e2c4f))
12
+
13
+ ### ❤️ Thank You
14
+
15
+ - Claude Opus 5.5 (1M context)
16
+ - Lương Văn Tú
17
+
18
+ ## 0.14.0 (2026-09-28)
19
+
20
+ ### 🚀 Features
21
+
22
+ - **response:** answer a created resource with its location ([e9c0086](https://github.com/zeroxsolutions/zeroxsolutions/commit/e9c0086))
23
+ - ⚠️ **response:** refuse a declaration the standard's field rules forbid ([30e6b77](https://github.com/zeroxsolutions/zeroxsolutions/commit/30e6b77))
24
+
25
+ ### 🩹 Fixes
26
+
27
+ - ⚠️ **response:** walk a pipe's input when checking member names ([0949d22](https://github.com/zeroxsolutions/zeroxsolutions/commit/0949d22))
28
+ - **response:** match link types to their schemas ([0f7c79b](https://github.com/zeroxsolutions/zeroxsolutions/commit/0f7c79b))
29
+ - ⚠️ **response:** refuse a resource id that is not a string ([57f60ab](https://github.com/zeroxsolutions/zeroxsolutions/commit/57f60ab))
30
+ - **response:** keep a primary resource out of included ([c38859f](https://github.com/zeroxsolutions/zeroxsolutions/commit/c38859f))
31
+ - ⚠️ **response:** read only the document members the standard defines ([fc5c51a](https://github.com/zeroxsolutions/zeroxsolutions/commit/fc5c51a))
32
+ - ⚠️ **response:** refuse an error object that carries no member ([7d888ed](https://github.com/zeroxsolutions/zeroxsolutions/commit/7d888ed))
33
+
34
+ ### ⚠️ Breaking Changes
35
+
36
+ - **response:** walk a pipe's input when checking member names ([0949d22](https://github.com/zeroxsolutions/zeroxsolutions/commit/0949d22))
37
+ a nested attribute member name that breaks the
38
+ standard's member-name rule, and sits behind a .transform() or
39
+ .pipe(), now throws at jsonApiResource's own declaration where it
40
+ previously passed unchecked.
41
+ - **response:** refuse a declaration the standard's field rules forbid ([30e6b77](https://github.com/zeroxsolutions/zeroxsolutions/commit/30e6b77))
42
+ jsonApiResource throws, and fails to compile, on a field named type or id, a name shared by an attribute and a relationship, an illegal member name, or a foreign-key attribute beside its relationship.
43
+ - **response:** refuse a resource id that is not a string ([57f60ab](https://github.com/zeroxsolutions/zeroxsolutions/commit/57f60ab))
44
+ Row['id'] is typed string, and serializing a row, embedded row or foreign key whose id is not a string throws TypeError.
45
+ - **response:** read only the document members the standard defines ([fc5c51a](https://github.com/zeroxsolutions/zeroxsolutions/commit/fc5c51a))
46
+ a data document carrying errors, an error document carrying data, and an empty relationship object are refused.
47
+ - **response:** refuse an error object that carries no member ([7d888ed](https://github.com/zeroxsolutions/zeroxsolutions/commit/7d888ed))
48
+ toErrorDocument throws on an empty error object.
49
+
50
+ ### ❤️ Thank You
51
+
52
+ - Claude Opus 5.5 (1M context)
53
+ - Lương Văn Tú
54
+
55
+ ## 0.13.0 (2026-09-27)
56
+
57
+ ### 🚀 Features
58
+
59
+ - ⚠️ **response:** type the fieldsets and include paths a document answers ([98bbb58](https://github.com/zeroxsolutions/zeroxsolutions/commit/98bbb58))
60
+ - ⚠️ **response:** keep a resource's type, attributes and relationships as literals ([738889e](https://github.com/zeroxsolutions/zeroxsolutions/commit/738889e))
61
+
62
+ ### 🩹 Fixes
63
+
64
+ - ⚠️ **response:** pin an include path stopping at a wide edge ([515a3e9](https://github.com/zeroxsolutions/zeroxsolutions/commit/515a3e9))
65
+
66
+ ### ⚠️ Breaking Changes
67
+
68
+ - **response:** pin an include path stopping at a wide edge ([515a3e9](https://github.com/zeroxsolutions/zeroxsolutions/commit/515a3e9))
69
+ jsonApiResource's type parameters moved from <A, C> to
70
+ <T, A, R, C>, so an explicit `jsonApiResource<typeof attrs, Ctx>` no
71
+ longer compiles; remove the type arguments and let inference type it.
72
+ The exported ResourceOptions<C> is now ResourceOptions<R>.
73
+ BREAKING CHANGE: an explicit toOne<Ctx> or toMany<Ctx> no longer
74
+ compiles; remove the type argument.
75
+ - **response:** type the fieldsets and include paths a document answers ([98bbb58](https://github.com/zeroxsolutions/zeroxsolutions/commit/98bbb58))
76
+ declaredDocumentShape answers typed fieldsets and include
77
+ paths, walks at most three relationships by default, and answers a fieldset
78
+ only for a type a kept path reaches. DeclaredDocumentShape is removed;
79
+ TypedDocumentShape replaces it.
80
+ - **response:** keep a resource's type, attributes and relationships as literals ([738889e](https://github.com/zeroxsolutions/zeroxsolutions/commit/738889e))
81
+ toOne and toMany take the related resource as their first type
82
+ parameter and infer the loader context; a call passing the context explicitly,
83
+ toOne<Ctx>(...), no longer compiles and drops the explicit argument.
84
+
85
+ ### ❤️ Thank You
86
+
87
+ - Claude Opus 5.5 (1M context)
88
+ - Lương Văn Tú
89
+
1
90
  ## 0.12.2 (2026-09-27)
2
91
 
3
92
  ### 🚀 Features
package/README.md CHANGED
@@ -5,21 +5,38 @@ of `@zeroxsolutions/query`, which owns the query-string half, and shaped the sam
5
5
  wire spelling.
6
6
 
7
7
  ```ts
8
- import { errorResponse, jsonApiResource, listResponse, okResponse, toOne } from '@zeroxsolutions/response';
8
+ import {
9
+ countedCollectionMetaSchema,
10
+ errorResponse,
11
+ jsonApiResource,
12
+ listResponse,
13
+ offsetPaginationLinksSchema,
14
+ okResponse,
15
+ toOne,
16
+ } from '@zeroxsolutions/response';
9
17
 
10
18
  export const subjects = jsonApiResource('subjects', subjectAttributesSchema, {
11
19
  relationships: { org: toOne(() => orgs, (row) => row.org) },
12
20
  });
13
21
 
14
22
  responses: {
15
- 200: listResponse('Subjects', subjects), // { data: [...], included?, links? }
23
+ 200: listResponse('Subjects', subjects, { // { data: [...], included?, links, meta }
24
+ meta: countedCollectionMetaSchema,
25
+ links: offsetPaginationLinksSchema,
26
+ }),
16
27
  201: okResponse('Subject created', subjects), // { data: {...}, included?, ... }
17
28
  401: errorResponse('Authentication required'), // { errors: [...] }
18
29
  }
19
30
 
20
- return c.json(await subjects.serializeMany(rows, { links }, c.req.valid('query')));
31
+ return c.json(await subjects.serializeMany(rows, { links, meta: { total } }, c.req.valid('query')));
21
32
  ```
22
33
 
34
+ A collection declares the `meta` and `links` it sends on every answer, and each one declared is published
35
+ as required and typed. `serializeMany` types its document off what it is handed, so a handler that drops
36
+ `total` does not compile. A collection that declares neither keeps the standard's optional, free-form
37
+ members. `meta.total` is this house's own member: the cursor-pagination profile's `meta.page.total`
38
+ describes cursor paging, and these collections page by offset.
39
+
23
40
  Both take the **item** on a list and wrap it, so no route restates the carrier. They take a resource
24
41
  DECLARATION rather than a bare schema, because a document carries a `type` and its relationships, which
25
42
  a schema alone has nowhere to put.
@@ -134,6 +151,7 @@ app.onError(
134
151
  namespace: 'academics', // every code THESE handlers mint carries it, so one surface has one vocabulary
135
152
  category: ['academics', 'http'],
136
153
  resolve: createDomainErrorResolver(academicsErrors),
154
+ authenticationChallenge: 'Bearer',
137
155
  }),
138
156
  );
139
157
  ```
@@ -258,8 +276,9 @@ those three members and none of them means a path segment, so borrowing one woul
258
276
  One root, no subpath, and `src/index.ts` lists every name it publishes:
259
277
 
260
278
  - `okResponse` / `listResponse` / `errorResponse`, the `JsonApiResponse` they return (exported so a
261
- consumer's declaration emit can name it), and `errorDocumentSchema`, which `errorResponse` returns
262
- inside it;
279
+ consumer's declaration emit can name it), `errorDocumentSchema`, which `errorResponse` returns
280
+ inside it, and `countedCollectionMetaSchema` / `offsetPaginationLinksSchema`, which a collection route
281
+ passes to `listResponse`;
263
282
  - the resource declaration - `jsonApiResource`, `toOne` / `toMany`, `declaredDocumentShape` - and the
264
283
  types their signatures name;
265
284
  - the error document's types, `JsonApiError`, `BASE_ERROR_CODES`, `toErrorObject` / `toErrorDocument`;
@@ -267,7 +286,7 @@ One root, no subpath, and `src/index.ts` lists every name it publishes:
267
286
  the answer, entry and map types they take;
268
287
  - `JSON_API_MEDIA_TYPE`.
269
288
 
270
- The document and resource-object schemas, the link and identifier primitives and the serializer
289
+ The document and resource-object schemas, the other link and identifier primitives and the serializer
271
290
  functions are what `jsonApiResource` is built from, and the root does not publish them: a resource's own
272
291
  `document`, `collectionDocument`, `serialize` and `serializeMany` are the one way to declare and render a
273
292
  document.
@@ -1,7 +1,8 @@
1
1
  import type { output, ZodObject, ZodType } from 'zod';
2
- import type { CollectionDocument, ResourceDocument } from './document.js';
2
+ import { type CollectionDocument, type ResourceDocument } from './document.js';
3
3
  import { errorDocumentSchema } from './errors/index.js';
4
4
  import { JSON_API_MEDIA_TYPE } from './media-type.js';
5
+ import type { Meta, PaginationLinks } from './primitives.js';
5
6
  import type { JsonApiResource } from './resource.js';
6
7
  /**
7
8
  * An OpenAPI response object for this spelling - one media type, one schema, and the `Retry-After` a
@@ -45,6 +46,15 @@ export declare function errorResponse(description: string, options?: {
45
46
  }): JsonApiResponse<typeof errorDocumentSchema>;
46
47
  /** The response object for a single resource, off the resource's own declaration. */
47
48
  export declare function okResponse<A extends ZodObject, C = unknown>(description: string, resource: JsonApiResource<A, C>): JsonApiResponse<ZodType<ResourceDocument<output<A>>>>;
48
- /** The response object for a collection, off the resource's own declaration. */
49
- export declare function listResponse<A extends ZodObject, C = unknown>(description: string, resource: JsonApiResource<A, C>): JsonApiResponse<ZodType<CollectionDocument<output<A>>>>;
49
+ /**
50
+ * The response object for a collection, off the resource's own declaration.
51
+ *
52
+ * @param members - the `meta` and `links` this route sends on every answer. Each one declared is published
53
+ * as required and typed, so a client reads it without narrowing; one left out stays the standard's
54
+ * optional, free-form member.
55
+ */
56
+ export declare function listResponse<A extends ZodObject, C = unknown, M extends ZodType<Meta> = never, L extends ZodType<PaginationLinks> = never>(description: string, resource: JsonApiResource<A, C>, members?: {
57
+ meta?: M;
58
+ links?: L;
59
+ }): JsonApiResponse<ZodType<CollectionDocument<output<A>, output<M>, output<L>>>>;
50
60
  //# sourceMappingURL=api-response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-response.d.ts","sourceRoot":"","sources":["../src/api-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,OAAO;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,OAAO,mBAAmB,EAAE;QAAE,MAAM,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE;QAAE,aAAa,EAAE,gBAAgB,CAAA;KAAE,CAAC;CAC/C;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAC;CACzC,CAAC;AAOF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GACtC,eAAe,CAAC,OAAO,mBAAmB,CAAC,CAqB7C;AAED,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,GAAG,OAAO,EACzD,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC9B,eAAe,CAAC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAEvD;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,GAAG,OAAO,EAC3D,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC9B,eAAe,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAEzD"}
1
+ {"version":3,"file":"api-response.d.ts","sourceRoot":"","sources":["../src/api-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACtD,OAAO,EAA4B,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,OAAO;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,OAAO,mBAAmB,EAAE;QAAE,MAAM,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE;QAAE,aAAa,EAAE,gBAAgB,CAAA;KAAE,CAAC;CAC/C;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAC;CACzC,CAAC;AAOF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GACtC,eAAe,CAAC,OAAO,mBAAmB,CAAC,CAqB7C;AAED,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,GAAG,OAAO,EACzD,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC9B,eAAe,CAAC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,CAAC,SAAS,SAAS,EACnB,CAAC,GAAG,OAAO,EACX,CAAC,SAAS,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,EAC/B,CAAC,SAAS,OAAO,CAAC,eAAe,CAAC,GAAG,KAAK,EAE1C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAC/B,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,CAAA;CAAO,GACpC,eAAe,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAQ/E"}
@@ -1,3 +1,4 @@
1
+ import { collectionDocumentSchema } from './document.js';
1
2
  import { errorDocumentSchema } from './errors/index.js';
2
3
  import { JSON_API_MEDIA_TYPE } from './media-type.js';
3
4
  const answers = (description, schema) => ({
@@ -35,7 +36,16 @@ export function errorResponse(description, options) {
35
36
  export function okResponse(description, resource) {
36
37
  return answers(description, resource.document);
37
38
  }
38
- /** The response object for a collection, off the resource's own declaration. */
39
- export function listResponse(description, resource) {
40
- return answers(description, resource.collectionDocument);
39
+ /**
40
+ * The response object for a collection, off the resource's own declaration.
41
+ *
42
+ * @param members - the `meta` and `links` this route sends on every answer. Each one declared is published
43
+ * as required and typed, so a client reads it without narrowing; one left out stays the standard's
44
+ * optional, free-form member.
45
+ */
46
+ export function listResponse(description, resource, members = {}) {
47
+ const declared = members.meta !== undefined || members.links !== undefined;
48
+ return answers(description, declared
49
+ ? collectionDocumentSchema(resource, members)
50
+ : resource.collectionDocument);
41
51
  }
@@ -0,0 +1,23 @@
1
+ import type { Link } from './primitives.js';
2
+ /**
3
+ * The arguments of a 201 that created `document`'s resource: the document, the status, and the `Location` the standard
4
+ * asks a 201 to carry. Spread into `c.json(...)`.
5
+ *
6
+ * `type` and `id` sit in the bound alongside `links`, both of which every `ResourceObject` carries: a document
7
+ * whose resource has no `links` at all would otherwise share no property with a bound of `links` alone, which
8
+ * is a "weak type" TypeScript refuses to accept a value for - and a resource with no self link is the common case.
9
+ *
10
+ * @throws Error when the resource's `self` link names another location, since the standard requires the two to match
11
+ */
12
+ export declare function createdResponse<D extends {
13
+ data: {
14
+ type: string;
15
+ id: string;
16
+ links?: {
17
+ self?: Link;
18
+ };
19
+ };
20
+ }>(document: D, location: string): readonly [D, 201, {
21
+ Location: string;
22
+ }];
23
+ //# sourceMappingURL=created-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"created-response.d.ts","sourceRoot":"","sources":["../src/created-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAM5C;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,IAAI,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,EACvG,QAAQ,EAAE,CAAC,EACX,QAAQ,EAAE,MAAM,GACf,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAMzC"}
@@ -0,0 +1,19 @@
1
+ /** Where a link points, whichever of the standard's forms it takes. */
2
+ const hrefOf = (link) => link === null || link === undefined ? undefined : typeof link === 'string' ? link : link.href;
3
+ /**
4
+ * The arguments of a 201 that created `document`'s resource: the document, the status, and the `Location` the standard
5
+ * asks a 201 to carry. Spread into `c.json(...)`.
6
+ *
7
+ * `type` and `id` sit in the bound alongside `links`, both of which every `ResourceObject` carries: a document
8
+ * whose resource has no `links` at all would otherwise share no property with a bound of `links` alone, which
9
+ * is a "weak type" TypeScript refuses to accept a value for - and a resource with no self link is the common case.
10
+ *
11
+ * @throws Error when the resource's `self` link names another location, since the standard requires the two to match
12
+ */
13
+ export function createdResponse(document, location) {
14
+ const self = hrefOf(document.data.links?.self);
15
+ if (self !== undefined && self !== location) {
16
+ throw new Error(`The created resource's self link "${self}" differs from its Location "${location}".`);
17
+ }
18
+ return [document, 201, { Location: location }];
19
+ }
@@ -4,7 +4,7 @@
4
4
  * relationship declares belongs to `relationship.ts`.
5
5
  */
6
6
  import type { RelatedResource, RelationshipDef } from './relationship.js';
7
- import type { LoadedRows, RequestedDocumentShape, Row } from './serialize.js';
7
+ import { type LoadedRows, type RequestedDocumentShape, type Row } from './serialize.js';
8
8
  /**
9
9
  * What one loaded relationship hands the serializer for a row: the row its loader answered, or the
10
10
  * identifier alone where it answered none.
@@ -1 +1 @@
1
- {"version":3,"file":"document-load.d.ts","sourceRoot":"","sources":["../src/document-load.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AA0B9E;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,GAAG,EAAE,eAAe,CAAC,CAAC,CAAC,EACvB,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,UAAU,GAAG,SAAS,GAC7B,GAAG,GAAG,GAAG,EAAE,GAAG,IAAI,CAIpB;AA0BD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,EACxB,IAAI,EAAE,SAAS,GAAG,EAAE,EACpB,SAAS,EAAE,sBAAsB,GAAG,SAAS,EAC7C,OAAO,EAAE,CAAC,GACT,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA8BjC"}
1
+ {"version":3,"file":"document-load.d.ts","sourceRoot":"","sources":["../src/document-load.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,sBAAsB,EAAE,KAAK,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAmCrG;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,GAAG,EAAE,eAAe,CAAC,CAAC,CAAC,EACvB,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,UAAU,GAAG,SAAS,GAC7B,GAAG,GAAG,GAAG,EAAE,GAAG,IAAI,CAKpB;AA2BD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,EACxB,IAAI,EAAE,SAAS,GAAG,EAAE,EACpB,SAAS,EAAE,sBAAsB,GAAG,SAAS,EAC7C,OAAO,EAAE,CAAC,GACT,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA8BjC"}
@@ -1,7 +1,18 @@
1
- /** The ids one row carries for a loaded relationship. A null or absent key asks for nothing. */
2
- function readIds(value) {
1
+ import { assertRowId } from './serialize.js';
2
+ /**
3
+ * The ids one row carries for a loaded relationship. A null or absent key asks for nothing; any other
4
+ * non-string is refused rather than silently dropped, naming the relationship's own related `type`.
5
+ */
6
+ function readIds(value, type) {
3
7
  const values = Array.isArray(value) ? value : [value];
4
- return values.filter((one) => typeof one === 'string');
8
+ const ids = [];
9
+ for (const one of values) {
10
+ if (one === null || one === undefined)
11
+ continue;
12
+ assertRowId(type, { id: one });
13
+ ids.push(one);
14
+ }
15
+ return ids;
5
16
  }
6
17
  /** The rows one row already carries for a relationship that declares no loader. */
7
18
  function readRows(value) {
@@ -16,8 +27,9 @@ function readRows(value) {
16
27
  * linkage a client reads - only what lands under `included`.
17
28
  */
18
29
  export function loadedRelated(def, row, loaded) {
19
- const answered = loaded?.get(def.related().type);
20
- const rows = readIds(def.read(row)).map((id) => answered?.get(id) ?? { id });
30
+ const type = def.related().type;
31
+ const answered = loaded?.get(type);
32
+ const rows = readIds(def.read(row), type).map((id) => answered?.get(id) ?? { id });
21
33
  return def.cardinality === 'many' ? rows : (rows.at(0) ?? null);
22
34
  }
23
35
  /** Runs one relationship's loader, and answers the rows the next depth reads its ids off. */
@@ -29,13 +41,13 @@ async function loadRelated(def, rows, loaded, context) {
29
41
  const type = def.related().type;
30
42
  const answered = loaded.get(type) ?? new Map();
31
43
  loaded.set(type, answered);
32
- const ids = [...new Set(rows.flatMap((row) => readIds(def.read(row))))];
44
+ const ids = [...new Set(rows.flatMap((row) => readIds(def.read(row), type)))];
33
45
  if (ids.length === 0)
34
46
  return [];
35
47
  const related = await load(ids, context);
36
48
  for (const row of related) {
37
- if (typeof row.id === 'string')
38
- answered.set(row.id, row);
49
+ assertRowId(type, row);
50
+ answered.set(row.id, row);
39
51
  }
40
52
  return related;
41
53
  }
@@ -1,10 +1,10 @@
1
1
  import type { ZodObject, ZodType } from 'zod';
2
- import { type JsonApiObject, type Meta, type PaginationLinks, type ResourceIdentifier, type ResourceLinks } from './primitives.js';
2
+ import { type JsonApiObject, type Meta, type PaginationLinks, type RelationshipLinks, type ResourceIdentifier, type ResourceLinks, type TopLevelLinks } from './primitives.js';
3
3
  import { type RelatedResource, type RelationshipDef } from './relationship.js';
4
4
  /** One relationship as a document carries it - the linkage, plus its own links and meta. */
5
5
  export interface Relationship {
6
6
  data?: ResourceIdentifier | ResourceIdentifier[] | null;
7
- links?: ResourceLinks;
7
+ links?: RelationshipLinks;
8
8
  meta?: Meta;
9
9
  }
10
10
  /** A resource object, with `attributes` carrying the declared attribute type. */
@@ -20,19 +20,51 @@ export interface ResourceObject<A> {
20
20
  export interface ResourceDocument<A> {
21
21
  data: ResourceObject<A>;
22
22
  included?: ResourceObject<unknown>[];
23
- links?: ResourceLinks;
23
+ links?: TopLevelLinks;
24
24
  meta?: Meta;
25
25
  jsonapi?: JsonApiObject;
26
26
  }
27
- /** A collection document - the same members with `data` an array and paginated `links`. */
28
- export interface CollectionDocument<A> {
27
+ /**
28
+ * One top-level member of a collection document. A route that declared it sends it every time, so it is
29
+ * required and typed; one that did not keeps the standard's optional, free-form member.
30
+ */
31
+ type CollectionMember<K extends 'links' | 'meta', Declared, Loose> = [Declared] extends [never] ? {
32
+ [P in K]?: Loose;
33
+ } : {
34
+ [P in K]: Declared;
35
+ };
36
+ /**
37
+ * A collection document - the same members with `data` an array and paginated `links`.
38
+ *
39
+ * `M` and `L` are the `meta` and `links` a route declared, `never` where it declared none.
40
+ */
41
+ export type CollectionDocument<A, M = never, L = never> = {
29
42
  data: ResourceObject<A>[];
30
43
  included?: ResourceObject<unknown>[];
31
- links?: PaginationLinks;
32
- meta?: Meta;
33
44
  jsonapi?: JsonApiObject;
34
- }
35
- /** Wraps `attributes` into a resource object - `{ type, id, attributes, relationships?, links?, meta? }`. */
45
+ } & CollectionMember<'links', L, PaginationLinks> & CollectionMember<'meta', M, Meta>;
46
+ /**
47
+ * The collection document `serializeMany` answers for `extras`: each member it was handed is present and
48
+ * typed as handed, which is what a route's declared members are checked against.
49
+ *
50
+ * Tuple-wrapped so it does not distribute: extras that carry `meta` on one branch of a union and not the
51
+ * other may send none, and must not type it as present.
52
+ */
53
+ export type CollectionDocumentFor<A, X> = CollectionDocument<A, [
54
+ X
55
+ ] extends [{
56
+ meta: infer M;
57
+ }] ? M : never, [
58
+ X
59
+ ] extends [{
60
+ links: infer L;
61
+ }] ? L : never>;
62
+ /**
63
+ * Wraps `attributes` into a resource object - `{ type, id, attributes?, relationships?, links?, meta? }`.
64
+ *
65
+ * `attributes` parses as absent here, unlike {@link ResourceObject}'s own required member: this schema
66
+ * also validates a document read off another server, which may publish a resource with none.
67
+ */
36
68
  export declare function resourceObjectSchema<C>(type: string, attributes: ZodObject, relationships?: Readonly<Record<string, RelationshipDef<C>>>): ZodType;
37
69
  /**
38
70
  * The document for a SINGLE resource - `{ data, included?, links?, meta?, jsonapi? }`.
@@ -40,8 +72,20 @@ export declare function resourceObjectSchema<C>(type: string, attributes: ZodObj
40
72
  * `A` is asserted rather than inferred: the schema is assembled from a relationship graph reached
41
73
  * through thunks, so nothing static carries the attribute type, and a client typing a route off this
42
74
  * schema would otherwise read every member as `unknown`.
75
+ *
76
+ * Strict at the top level: the standard forbids any top-level member beyond these (extension members
77
+ * aside), which is what refuses a `data` document that also carries `errors`.
43
78
  */
44
79
  export declare function resourceDocumentSchema<A = unknown, C = unknown>(root: RelatedResource<C>): ZodType<ResourceDocument<A>>;
45
- /** The document for a COLLECTION - the same members, with `data` an array and paginated `links`. */
46
- export declare function collectionDocumentSchema<A = unknown, C = unknown>(root: RelatedResource<C>): ZodType<CollectionDocument<A>>;
80
+ /**
81
+ * The document for a COLLECTION - the same members, with `data` an array and paginated `links`.
82
+ *
83
+ * A member in `members` replaces the standard's optional one, so the route that declared it publishes it
84
+ * as required. Strict at the top level for the same reason as {@link resourceDocumentSchema}.
85
+ */
86
+ export declare function collectionDocumentSchema<A = unknown, C = unknown, M = never, L = never>(root: RelatedResource<C>, members?: {
87
+ meta?: ZodType;
88
+ links?: ZodType;
89
+ }): ZodType<CollectionDocument<A, M, L>>;
90
+ export {};
47
91
  //# sourceMappingURL=document.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAC9C,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,IAAI,EACT,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAA2C,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAExH,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,kBAAkB,GAAG,kBAAkB,EAAE,GAAG,IAAI,CAAC;IACxD,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,CAAC,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,yGAAyG;AACzG,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IACxB,QAAQ,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,2FAA2F;AAC3F,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,6GAA6G;AAC7G,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,SAAS,EACrB,aAAa,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAM,GAC/D,OAAO,CAUT;AAgBD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAC7D,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAQ9B;AAED,oGAAoG;AACpG,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAC/D,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAQhC"}
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
package/dist/document.js CHANGED
@@ -1,13 +1,18 @@
1
1
  import { z } from 'zod';
2
- import { jsonApiObjectSchema, metaSchema, paginationLinksSchema, resourceLinksSchema, } from './primitives.js';
2
+ import { jsonApiObjectSchema, metaSchema, paginationLinksSchema, resourceLinksSchema, topLevelLinksSchema, } from './primitives.js';
3
3
  import { relationshipsSchema, reachableResources } from './relationship.js';
4
- /** Wraps `attributes` into a resource object - `{ type, id, attributes, relationships?, links?, meta? }`. */
4
+ /**
5
+ * Wraps `attributes` into a resource object - `{ type, id, attributes?, relationships?, links?, meta? }`.
6
+ *
7
+ * `attributes` parses as absent here, unlike {@link ResourceObject}'s own required member: this schema
8
+ * also validates a document read off another server, which may publish a resource with none.
9
+ */
5
10
  export function resourceObjectSchema(type, attributes, relationships = {}) {
6
11
  const linkage = relationshipsSchema(relationships);
7
12
  return z.object({
8
13
  type: z.literal(type),
9
14
  id: z.string(),
10
- attributes,
15
+ attributes: attributes.optional(),
11
16
  ...(linkage ? { relationships: linkage.optional() } : {}),
12
17
  links: resourceLinksSchema.optional(),
13
18
  meta: metaSchema.optional(),
@@ -32,23 +37,31 @@ function includedSchema(root) {
32
37
  * `A` is asserted rather than inferred: the schema is assembled from a relationship graph reached
33
38
  * through thunks, so nothing static carries the attribute type, and a client typing a route off this
34
39
  * schema would otherwise read every member as `unknown`.
40
+ *
41
+ * Strict at the top level: the standard forbids any top-level member beyond these (extension members
42
+ * aside), which is what refuses a `data` document that also carries `errors`.
35
43
  */
36
44
  export function resourceDocumentSchema(root) {
37
- return z.object({
45
+ return z.strictObject({
38
46
  data: root.resourceObject,
39
47
  included: includedSchema(root).optional(),
40
- links: resourceLinksSchema.optional(),
48
+ links: topLevelLinksSchema.optional(),
41
49
  meta: metaSchema.optional(),
42
50
  jsonapi: jsonApiObjectSchema.optional(),
43
51
  });
44
52
  }
45
- /** The document for a COLLECTION - the same members, with `data` an array and paginated `links`. */
46
- export function collectionDocumentSchema(root) {
47
- return z.object({
53
+ /**
54
+ * The document for a COLLECTION - the same members, with `data` an array and paginated `links`.
55
+ *
56
+ * A member in `members` replaces the standard's optional one, so the route that declared it publishes it
57
+ * as required. Strict at the top level for the same reason as {@link resourceDocumentSchema}.
58
+ */
59
+ export function collectionDocumentSchema(root, members = {}) {
60
+ return z.strictObject({
48
61
  data: z.array(root.resourceObject),
49
62
  included: includedSchema(root).optional(),
50
- links: paginationLinksSchema.optional(),
51
- meta: metaSchema.optional(),
63
+ links: members.links ?? paginationLinksSchema.optional(),
64
+ meta: members.meta ?? metaSchema.optional(),
52
65
  jsonapi: jsonApiObjectSchema.optional(),
53
66
  });
54
67
  }
@@ -42,7 +42,7 @@ export declare const errorLinksSchema: z.ZodObject<{
42
42
  }, z.core.$strip>, z.ZodNull]>>;
43
43
  }, z.core.$strip>;
44
44
  export type ErrorLinks = z.infer<typeof errorLinksSchema>;
45
- /** A single JSON:API error object; every member is optional, and nothing here enforces the spec's at-least-one. */
45
+ /** A single JSON:API error object: every member is optional, and at least one is present. */
46
46
  export declare const errorObjectSchema: z.ZodObject<{
47
47
  id: z.ZodOptional<z.ZodString>;
48
48
  links: z.ZodOptional<z.ZodObject<{
@@ -94,6 +94,9 @@ export type ErrorObject = z.infer<typeof errorObjectSchema>;
94
94
  /**
95
95
  * A JSON:API error document - a top-level `errors` array of at least one member, which never carries `data`.
96
96
  *
97
+ * Strict at the top level: the standard forbids any top-level member beyond these (extension members
98
+ * aside), which is what refuses a document that carries both `errors` and `data`.
99
+ *
97
100
  * Nothing here renders the top-level `meta` or `links`. They are declared so the schema `errorResponse` publishes is
98
101
  * the whole document, and a client parsing another server's document keeps them.
99
102
  */
@@ -182,8 +185,10 @@ export declare const errorDocumentSchema: z.ZodObject<{
182
185
  }, z.core.$strip>>;
183
186
  jsonapi: z.ZodOptional<z.ZodObject<{
184
187
  version: z.ZodOptional<z.ZodString>;
188
+ ext: z.ZodOptional<z.ZodArray<z.ZodString>>;
189
+ profile: z.ZodOptional<z.ZodArray<z.ZodString>>;
185
190
  meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
186
191
  }, z.core.$strip>>;
187
- }, z.core.$strip>;
192
+ }, z.core.$strict>;
188
193
  export type ErrorDocument = z.infer<typeof errorDocumentSchema>;
189
194
  //# sourceMappingURL=error-document.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-document.d.ts","sourceRoot":"","sources":["../../src/errors/error-document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,sFAAsF;AACtF,eAAO,MAAM,iBAAiB;;;;iBAMlB,CAAC;AACb,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,qGAAqG;AACrG,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA8D,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,mHAAmH;AACnH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAWlB,CAAC;AACb,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAK9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"error-document.d.ts","sourceRoot":"","sources":["../../src/errors/error-document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,sFAAsF;AACtF,eAAO,MAAM,iBAAiB;;;;iBAMlB,CAAC;AACb,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,qGAAqG;AACrG,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA8D,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,6FAA6F;AAC7F,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAYoF,CAAC;AACnH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAK9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -10,7 +10,7 @@ export const errorSourceSchema = z
10
10
  .partial();
11
11
  /** An error object's `links`: a page about this occurrence, and a page about its type of problem. */
12
12
  export const errorLinksSchema = z.object({ about: linkSchema, type: linkSchema }).partial();
13
- /** A single JSON:API error object; every member is optional, and nothing here enforces the spec's at-least-one. */
13
+ /** A single JSON:API error object: every member is optional, and at least one is present. */
14
14
  export const errorObjectSchema = z
15
15
  .object({
16
16
  id: z.string(),
@@ -22,14 +22,18 @@ export const errorObjectSchema = z
22
22
  source: errorSourceSchema,
23
23
  meta: metaSchema,
24
24
  })
25
- .partial();
25
+ .partial()
26
+ .refine((object) => Object.keys(object).length > 0, { message: 'An error object carries at least one member.' });
26
27
  /**
27
28
  * A JSON:API error document - a top-level `errors` array of at least one member, which never carries `data`.
28
29
  *
30
+ * Strict at the top level: the standard forbids any top-level member beyond these (extension members
31
+ * aside), which is what refuses a document that carries both `errors` and `data`.
32
+ *
29
33
  * Nothing here renders the top-level `meta` or `links`. They are declared so the schema `errorResponse` publishes is
30
34
  * the whole document, and a client parsing another server's document keeps them.
31
35
  */
32
- export const errorDocumentSchema = z.object({
36
+ export const errorDocumentSchema = z.strictObject({
33
37
  errors: z.array(errorObjectSchema).min(1),
34
38
  meta: metaSchema.optional(),
35
39
  links: z.object({ self: linkSchema, describedby: linkSchema }).partial().optional(),
package/dist/index.d.ts CHANGED
@@ -2,14 +2,16 @@
2
2
  * The response half of the HTTP contract, in the one wire spelling this house answers: a JSON:API document.
3
3
  */
4
4
  export { errorResponse, listResponse, okResponse, type JsonApiResponse, type RetryAfterHeader, } from './api-response.js';
5
- export type { CollectionDocument, Relationship, ResourceDocument, ResourceObject } from './document.js';
5
+ export { createdResponse } from './created-response.js';
6
+ export type { CollectionDocument, CollectionDocumentFor, Relationship, ResourceDocument, ResourceObject, } from './document.js';
6
7
  export * from './document-graph.js';
7
8
  export { declaredDocumentShape } from './document-shape.js';
8
9
  export { createDomainErrorResolver, defineErrorMap, errorEntry, type DomainErrorAnswer, type DomainErrorEntry, } from './domain-error-resolver.js';
9
10
  export type { DomainErrorMap, ErrorClass, UpstreamErrorMap } from './error-map.js';
10
11
  export { BASE_ERROR_CODES, errorDocumentSchema, JsonApiError, toErrorDocument, toErrorObject, type ErrorDocument, type ErrorLinks, type ErrorObject, type ErrorSource, type ErrorStatus, type JsonApiErrorInit, type ResolvedError, } from './errors/index.js';
11
12
  export { JSON_API_MEDIA_TYPE } from './media-type.js';
12
- export type { JsonApiObject, Link, Meta, PaginationLinks, ResourceIdentifier, ResourceLinks } from './primitives.js';
13
+ export { countedCollectionMetaSchema, offsetPaginationLinksSchema } from './primitives.js';
14
+ export type { JsonApiObject, Link, Meta, PaginationLinks, RelationshipLinks, ResourceIdentifier, ResourceLinks, TopLevelLinks, } from './primitives.js';
13
15
  export { toMany, toOne, type LoadContextArguments, type RelatedResource, type RelationshipDef, type RelationshipLoader, type RelationshipOptions, } from './relationship.js';
14
16
  export { jsonApiResource, type JsonApiResource, type ResourceOptions } from './resource.js';
15
17
  export type { CollectionExtras, LoadedRows, RequestedDocumentShape, ResourceSerializer, Row } from './serialize.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,aAAa,EACb,YAAY,EACZ,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACxG,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EACL,yBAAyB,EACzB,cAAc,EACd,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrH,OAAO,EACL,MAAM,EACN,KAAK,EACL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAC5F,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACpH,OAAO,EAAE,6BAA6B,EAAE,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,aAAa,EACb,YAAY,EACZ,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,YAAY,EACZ,gBAAgB,EAChB,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EACL,yBAAyB,EACzB,cAAc,EACd,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AAC3F,YAAY,EACV,aAAa,EACb,IAAI,EACJ,IAAI,EACJ,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,MAAM,EACN,KAAK,EACL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAC5F,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACpH,OAAO,EAAE,6BAA6B,EAAE,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -2,11 +2,13 @@
2
2
  * The response half of the HTTP contract, in the one wire spelling this house answers: a JSON:API document.
3
3
  */
4
4
  export { errorResponse, listResponse, okResponse, } from './api-response.js';
5
+ export { createdResponse } from './created-response.js';
5
6
  export * from './document-graph.js';
6
7
  export { declaredDocumentShape } from './document-shape.js';
7
8
  export { createDomainErrorResolver, defineErrorMap, errorEntry, } from './domain-error-resolver.js';
8
9
  export { BASE_ERROR_CODES, errorDocumentSchema, JsonApiError, toErrorDocument, toErrorObject, } from './errors/index.js';
9
10
  export { JSON_API_MEDIA_TYPE } from './media-type.js';
11
+ export { countedCollectionMetaSchema, offsetPaginationLinksSchema } from './primitives.js';
10
12
  export { toMany, toOne, } from './relationship.js';
11
13
  export { jsonApiResource } from './resource.js';
12
14
  export { createUpstreamErrorTranslator } from './upstream-error.js';