@zeroxsolutions/response 0.12.1 → 0.13.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/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 0.12.2 (2026-09-27)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **response:** declare a refusal's Retry-After off errorResponse ([53cc8d1](https://github.com/zeroxsolutions/zeroxsolutions/commit/53cc8d1))
6
+ - **response:** add the status a refused burst answers with ([910c52d](https://github.com/zeroxsolutions/zeroxsolutions/commit/910c52d))
7
+
8
+ ### ❤️ Thank You
9
+
10
+ - Claude Opus 5 (1M context)
11
+ - Claude Opus 5.5 (1M context)
12
+ - Lương Văn Tú
13
+
1
14
  ## 0.12.1 (2026-09-18)
2
15
 
3
16
  ### 🚀 Features
@@ -4,7 +4,8 @@ import { errorDocumentSchema } from './errors/index.js';
4
4
  import { JSON_API_MEDIA_TYPE } from './media-type.js';
5
5
  import type { JsonApiResource } from './resource.js';
6
6
  /**
7
- * An OpenAPI response object for this spelling - one media type, one schema.
7
+ * An OpenAPI response object for this spelling - one media type, one schema, and the `Retry-After` a
8
+ * refusal that tells its caller how long to wait declares.
8
9
  *
9
10
  * Exported because a consumer emitting declarations has to NAME it: a route built from these helpers
10
11
  * is an exported const whose type mentions it, and a private one fails that emit with TS4023.
@@ -14,9 +15,34 @@ export interface JsonApiResponse<S extends ZodType> {
14
15
  content: Record<typeof JSON_API_MEDIA_TYPE, {
15
16
  schema: S;
16
17
  }>;
18
+ headers?: {
19
+ 'Retry-After': RetryAfterHeader;
20
+ };
17
21
  }
18
- /** The response object for a failure status - the `errors[]` document. */
19
- export declare function errorResponse(description: string): JsonApiResponse<typeof errorDocumentSchema>;
22
+ /**
23
+ * The `Retry-After` header object a refusal declares: whole seconds, sent on every answer it gives.
24
+ *
25
+ * A type alias, not an interface: `@hono/zod-openapi` types a header as openapi3-ts's `HeaderObject`,
26
+ * which carries an `x-${string}` index signature, and an interface is not assignable to one.
27
+ */
28
+ export type RetryAfterHeader = {
29
+ description: string;
30
+ required: true;
31
+ schema: {
32
+ type: 'integer';
33
+ minimum: 1;
34
+ };
35
+ };
36
+ /**
37
+ * The response object for a failure status - the `errors[]` document.
38
+ *
39
+ * @param options.retryAfterSeconds - how long a refused caller is told to wait, in whole seconds.
40
+ * Pass it only where the refusal always sets `Retry-After`, since the header is declared required.
41
+ * @throws {RangeError} when `retryAfterSeconds` is not a positive integer.
42
+ */
43
+ export declare function errorResponse(description: string, options?: {
44
+ retryAfterSeconds: number;
45
+ }): JsonApiResponse<typeof errorDocumentSchema>;
20
46
  /** The response object for a single resource, off the resource's own declaration. */
21
47
  export declare function okResponse<A extends ZodObject, C = unknown>(description: string, resource: JsonApiResource<A, C>): JsonApiResponse<ZodType<ResourceDocument<output<A>>>>;
22
48
  /** The response object for a collection, off the resource's own declaration. */
@@ -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;;;;;GAKG;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;CAC5D;AAOD,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CAAC,OAAO,mBAAmB,CAAC,CAE9F;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,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"}
@@ -4,9 +4,32 @@ const answers = (description, schema) => ({
4
4
  description,
5
5
  content: { [JSON_API_MEDIA_TYPE]: { schema } },
6
6
  });
7
- /** The response object for a failure status - the `errors[]` document. */
8
- export function errorResponse(description) {
9
- return answers(description, errorDocumentSchema);
7
+ /**
8
+ * The response object for a failure status - the `errors[]` document.
9
+ *
10
+ * @param options.retryAfterSeconds - how long a refused caller is told to wait, in whole seconds.
11
+ * Pass it only where the refusal always sets `Retry-After`, since the header is declared required.
12
+ * @throws {RangeError} when `retryAfterSeconds` is not a positive integer.
13
+ */
14
+ export function errorResponse(description, options) {
15
+ const response = answers(description, errorDocumentSchema);
16
+ if (!options) {
17
+ return response;
18
+ }
19
+ const { retryAfterSeconds } = options;
20
+ if (!Number.isInteger(retryAfterSeconds) || retryAfterSeconds < 1) {
21
+ throw new RangeError(`retryAfterSeconds must be a positive integer, got ${retryAfterSeconds}`);
22
+ }
23
+ return {
24
+ ...response,
25
+ headers: {
26
+ 'Retry-After': {
27
+ description: `Seconds to wait before calling again: ${retryAfterSeconds}`,
28
+ required: true,
29
+ schema: { type: 'integer', minimum: 1 },
30
+ },
31
+ },
32
+ };
10
33
  }
11
34
  /** The response object for a single resource, off the resource's own declaration. */
12
35
  export function okResponse(description, resource) {
@@ -0,0 +1,58 @@
1
+ import type { ZodObject } from 'zod';
2
+ import type { RelatedResource } from './relationship.js';
3
+ /** Counts a depth down by one; index 0 is where a walk stops. */
4
+ type Down = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8];
5
+ type NoMembers = Record<never, never>;
6
+ /**
7
+ * A resource's relationship map. One declaring none carries `Record<string, never>`, whose keys are every string, so
8
+ * it is read as having no keys at all.
9
+ */
10
+ type RelationshipsOf<R> = R extends {
11
+ readonly relationships: infer M;
12
+ } ? [M[keyof M]] extends [never] ? NoMembers : M : NoMembers;
13
+ type RelatedOf<D> = D extends {
14
+ readonly related: () => infer X;
15
+ } ? X : never;
16
+ type TypeOf<X> = X extends {
17
+ readonly type: infer T extends string;
18
+ } ? T : never;
19
+ type AttributesOf<X> = X extends {
20
+ readonly attributes: ZodObject<infer S>;
21
+ } ? S : NoMembers;
22
+ /** True where an edge was declared against the wide `RelatedResource`, which knows no type name. */
23
+ type IsWide<X> = string extends TypeOf<X> ? true : false;
24
+ /**
25
+ * Every dotted relationship path from `R`, at most `Depth` relationships long. A path never revisits a type,
26
+ * which is the rule `declaredDocumentShape` walks by, and a wide edge contributes its own name and nothing below.
27
+ *
28
+ * It names a resource's relationship paths, not the drizzle relation paths `@zeroxsolutions/db` exports under the same
29
+ * name `IncludePath`; a module importing both must alias one.
30
+ */
31
+ export type IncludePath<R, Depth extends number = 3, Seen extends string = TypeOf<R>> = Depth extends 0 ? never : {
32
+ [K in keyof RelationshipsOf<R> & string]: RelatedOf<RelationshipsOf<R>[K]> extends infer X ? IsWide<X> extends true ? K : TypeOf<X> extends Seen ? never : K | `${K}.${IncludePath<X, Down[Depth], Seen | TypeOf<X>>}` : never;
33
+ }[keyof RelationshipsOf<R> & string];
34
+ /** The resources those paths reach, `R` first. A wide edge reaches nothing a type could name. */
35
+ export type ReachableResource<R, Depth extends number = 3, Seen extends string = TypeOf<R>> = R | (Depth extends 0 ? never : {
36
+ [K in keyof RelationshipsOf<R> & string]: RelatedOf<RelationshipsOf<R>[K]> extends infer X ? IsWide<X> extends true ? never : TypeOf<X> extends Seen ? never : ReachableResource<X, Down[Depth], Seen | TypeOf<X>> : never;
37
+ }[keyof RelationshipsOf<R> & string]);
38
+ /** The type names a document rooted at `R` can carry. */
39
+ export type ReachableType<R, Depth extends number = 3> = TypeOf<ReachableResource<R, Depth>>;
40
+ /** What a fieldset of `X` may name: its attributes and its relationships, which the standard calls its fields. */
41
+ export type FieldName<X> = (keyof AttributesOf<X> | keyof RelationshipsOf<X>) & string;
42
+ /** A sparse fieldset per reachable type. */
43
+ export type Fieldsets<R, Depth extends number = 3> = {
44
+ [X in ReachableResource<R, Depth> as TypeOf<X>]?: readonly FieldName<X>[];
45
+ };
46
+ /** What `declaredDocumentShape` answers, typed off the same graph. */
47
+ export interface TypedDocumentShape<R, Depth extends number> {
48
+ /** Every reachable type with the fields it publishes: its attribute names, then its relationship names. */
49
+ fields: {
50
+ [X in ReachableResource<R, Depth> as TypeOf<X>]: FieldName<X>[];
51
+ };
52
+ /** Every relationship path from the root, dotted for a nested one. */
53
+ include: IncludePath<R, Depth>[];
54
+ }
55
+ /** A resource as far as the graph types care; `RelatedResource` already is one. */
56
+ export type GraphRoot = RelatedResource<never>;
57
+ export {};
58
+ //# sourceMappingURL=document-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-graph.d.ts","sourceRoot":"","sources":["../src/document-graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,17 +1,19 @@
1
- import { type RelatedResource } from './relationship.js';
2
- /** The fieldsets and relationship paths one resource declaration can answer. */
3
- export interface DeclaredDocumentShape {
4
- /** Every reachable type with the fields it publishes: its attribute names, then its relationship names. */
5
- fields: Record<string, string[]>;
6
- /** Every relationship path from the root, dotted for a nested one. */
7
- include: string[];
8
- }
1
+ import type { GraphRoot, TypedDocumentShape } from './document-graph.js';
9
2
  /**
10
- * What a client may ask a document to be shaped as, read off the declaration itself - the permit-list a query guard
3
+ * What a client may ask a document to be shaped as, read off the declaration itself - the permit list a query guard
11
4
  * needs, without a second copy of it.
12
5
  *
13
6
  * The attribute keys ARE the projection, so a list written by hand beside them permits a column the resource stopped
14
7
  * publishing, or refuses one it started, with both halves compiling and both suites green.
8
+ *
9
+ * `maxIncludeDepth` bounds a path in relationships, 3 by default: the depth a route's own relation load reaches. A
10
+ * fieldset is answered only for a type some kept path reaches, the root included.
11
+ *
12
+ * The answer's type is bounded by `maxIncludeDepth` only where it is a literal; a `number` variable leaves the type
13
+ * at its default depth of 3. Below an edge whose thunk is annotated as the wide `RelatedResource`, the walk still
14
+ * descends while the type stops at that edge, so the value can permit paths and types the type does not offer.
15
15
  */
16
- export declare function declaredDocumentShape<C>(root: RelatedResource<C>): DeclaredDocumentShape;
16
+ export declare function declaredDocumentShape<R extends GraphRoot, const D extends number = 3>(root: R, options?: {
17
+ maxIncludeDepth?: D;
18
+ }): TypedDocumentShape<R, D>;
17
19
  //# sourceMappingURL=document-shape.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"document-shape.d.ts","sourceRoot":"","sources":["../src/document-shape.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE7E,gFAAgF;AAChF,MAAM,WAAW,qBAAqB;IACpC,2GAA2G;IAC3G,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,sEAAsE;IACtE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAuBxF"}
1
+ {"version":3,"file":"document-shape.d.ts","sourceRoot":"","sources":["../src/document-shape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGzE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,EACnF,IAAI,EAAE,CAAC,EACP,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,CAAC,CAAA;CAAO,GACpC,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAyB1B"}
@@ -1,29 +1,40 @@
1
- import { reachableResources } from './relationship.js';
2
1
  /**
3
- * What a client may ask a document to be shaped as, read off the declaration itself - the permit-list a query guard
2
+ * What a client may ask a document to be shaped as, read off the declaration itself - the permit list a query guard
4
3
  * needs, without a second copy of it.
5
4
  *
6
5
  * The attribute keys ARE the projection, so a list written by hand beside them permits a column the resource stopped
7
6
  * publishing, or refuses one it started, with both halves compiling and both suites green.
7
+ *
8
+ * `maxIncludeDepth` bounds a path in relationships, 3 by default: the depth a route's own relation load reaches. A
9
+ * fieldset is answered only for a type some kept path reaches, the root included.
10
+ *
11
+ * The answer's type is bounded by `maxIncludeDepth` only where it is a literal; a `number` variable leaves the type
12
+ * at its default depth of 3. Below an edge whose thunk is annotated as the wide `RelatedResource`, the walk still
13
+ * descends while the type stops at that edge, so the value can permit paths and types the type does not offer.
8
14
  */
9
- export function declaredDocumentShape(root) {
10
- const fields = Object.fromEntries(reachableResources(root).map((resource) => [
11
- resource.type,
12
- [...Object.keys(resource.attributes.shape), ...Object.keys(resource.relationships)],
13
- ]));
15
+ export function declaredDocumentShape(root, options = {}) {
16
+ const maxDepth = options.maxIncludeDepth ?? 3;
17
+ const reached = new Map([[root.type, root]]);
14
18
  const include = [];
15
19
  // A path never revisits a type: two resources naming each other make `class.subject.class...` infinite, and the
16
20
  // second lap addresses the same rows the first one already compounded.
17
- const walk = (resource, prefix, seen) => {
21
+ const walk = (resource, prefix, seen, depth) => {
22
+ if (depth >= maxDepth)
23
+ return;
18
24
  for (const [name, def] of Object.entries(resource.relationships)) {
19
25
  const related = def.related();
20
26
  if (seen.has(related.type))
21
27
  continue;
22
28
  const path = prefix === '' ? name : `${prefix}.${name}`;
23
29
  include.push(path);
24
- walk(related, path, new Set([...seen, related.type]));
30
+ reached.set(related.type, related);
31
+ walk(related, path, new Set([...seen, related.type]), depth + 1);
25
32
  }
26
33
  };
27
- walk(root, '', new Set([root.type]));
34
+ walk(root, '', new Set([root.type]), 0);
35
+ const fields = Object.fromEntries([...reached.values()].map((resource) => [
36
+ resource.type,
37
+ [...Object.keys(resource.attributes.shape), ...Object.keys(resource.relationships)],
38
+ ]));
28
39
  return { fields, include };
29
40
  }
@@ -37,6 +37,10 @@ export declare const BASE_ERROR_CODES: {
37
37
  readonly status: 422;
38
38
  readonly title: "Unprocessable Entity";
39
39
  };
40
+ readonly TOO_MANY_REQUESTS: {
41
+ readonly status: 429;
42
+ readonly title: "Too Many Requests";
43
+ };
40
44
  readonly INTERNAL_SERVER_ERROR: {
41
45
  readonly status: 500;
42
46
  readonly title: "Internal Server Error";
@@ -1 +1 @@
1
- {"version":3,"file":"error-registry.d.ts","sourceRoot":"","sources":["../../src/errors/error-registry.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,sHAAsH;AACtH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWmB,CAAC"}
1
+ {"version":3,"file":"error-registry.d.ts","sourceRoot":"","sources":["../../src/errors/error-registry.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,sHAAsH;AACtH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYmB,CAAC"}
@@ -8,6 +8,7 @@ export const BASE_ERROR_CODES = {
8
8
  CONFLICT: { status: 409, title: 'Conflict' },
9
9
  UNSUPPORTED_MEDIA_TYPE: { status: 415, title: 'Unsupported Media Type' },
10
10
  UNPROCESSABLE_ENTITY: { status: 422, title: 'Unprocessable Entity' },
11
+ TOO_MANY_REQUESTS: { status: 429, title: 'Too Many Requests' },
11
12
  INTERNAL_SERVER_ERROR: { status: 500, title: 'Internal Server Error' },
12
13
  BAD_GATEWAY: { status: 502, title: 'Bad Gateway' },
13
14
  };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * The response half of the HTTP contract, in the one wire spelling this house answers: a JSON:API document.
3
3
  */
4
- export { errorResponse, listResponse, okResponse, type JsonApiResponse } from './api-response.js';
4
+ export { errorResponse, listResponse, okResponse, type JsonApiResponse, type RetryAfterHeader, } from './api-response.js';
5
5
  export type { CollectionDocument, Relationship, ResourceDocument, ResourceObject } from './document.js';
6
- export { declaredDocumentShape, type DeclaredDocumentShape } from './document-shape.js';
6
+ export * from './document-graph.js';
7
+ export { declaredDocumentShape } from './document-shape.js';
7
8
  export { createDomainErrorResolver, defineErrorMap, errorEntry, type DomainErrorAnswer, type DomainErrorEntry, } from './domain-error-resolver.js';
8
9
  export type { DomainErrorMap, ErrorClass, UpstreamErrorMap } from './error-map.js';
9
10
  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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClG,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACxG,OAAO,EAAE,qBAAqB,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACxF,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,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"}
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The response half of the HTTP contract, in the one wire spelling this house answers: a JSON:API document.
3
3
  */
4
- export { errorResponse, listResponse, okResponse } from './api-response.js';
4
+ export { errorResponse, listResponse, okResponse, } from './api-response.js';
5
+ export * from './document-graph.js';
5
6
  export { declaredDocumentShape } from './document-shape.js';
6
7
  export { createDomainErrorResolver, defineErrorMap, errorEntry, } from './domain-error-resolver.js';
7
8
  export { BASE_ERROR_CODES, errorDocumentSchema, JsonApiError, toErrorDocument, toErrorObject, } from './errors/index.js';
@@ -44,20 +44,27 @@ export interface RelatedResource<C = unknown> {
44
44
  */
45
45
  serializerFor(requested?: RequestedDocumentShape, loaded?: LoadedRows): ResourceSerializer;
46
46
  }
47
+ /** The context a related resource's loaders run under, read off the resource so a caller never spells it. */
48
+ export type ContextOf<R> = R extends RelatedResource<infer C> ? C : unknown;
47
49
  /** A relationship: the resource it points at, how many, how to read it off a row, and who loads it. */
48
- export interface RelationshipDef<C = unknown> {
50
+ export interface RelationshipDef<C = unknown, R extends RelatedResource<never> = RelatedResource<C>> {
49
51
  readonly cardinality: 'one' | 'many';
50
52
  /** A thunk, so two resources may name each other without a module-evaluation cycle. */
51
- readonly related: () => RelatedResource<C>;
53
+ readonly related: () => R;
52
54
  /** Reads the related row(s) off the primary row, or the related id(s) where a loader is declared. */
53
55
  readonly read: (row: Row) => unknown;
54
56
  /** Absent where the primary row already carries the related rows. */
55
57
  readonly load?: RelationshipLoader<C>;
56
58
  }
57
- /** Declares a to-one relationship - its `data` is a single identifier, or null. */
58
- export declare function toOne<C = unknown>(related: () => RelatedResource<C>, read: (row: Row) => unknown, options?: RelationshipOptions<C>): RelationshipDef<C>;
59
- /** Declares a to-many relationship - its `data` is an array of identifiers. */
60
- export declare function toMany<C = unknown>(related: () => RelatedResource<C>, read: (row: Row) => unknown, options?: RelationshipOptions<C>): RelationshipDef<C>;
59
+ /**
60
+ * Declares a to-one relationship - its `data` is a single identifier, or null.
61
+ *
62
+ * The context is inferred from the loader and from the related resource. An explicit `toOne<Ctx>(...)`
63
+ * would stop TypeScript inferring the related resource, which then widens and drops every literal below it.
64
+ */
65
+ export declare function toOne<const R extends RelatedResource<never>, C = ContextOf<R>>(related: () => R, read: (row: Row) => unknown, options?: RelationshipOptions<C>): RelationshipDef<NoInfer<C>, R>;
66
+ /** Declares a to-many relationship - its `data` is an array of identifiers. The context is inferred as for `toOne`. */
67
+ export declare function toMany<const R extends RelatedResource<never>, C = ContextOf<R>>(related: () => R, read: (row: Row) => unknown, options?: RelationshipOptions<C>): RelationshipDef<NoInfer<C>, R>;
61
68
  /** The linkage schema for one relationship, pinned to the related `type` so a wrong type fails the parse. */
62
69
  export declare function relationshipSchema<C>(def: RelationshipDef<C>): ZodType;
63
70
  /** The `relationships` member for a whole resource - one key per declaration, absent when it declares none. */
@@ -1 +1 @@
1
- {"version":3,"file":"relationship.d.ts","sourceRoot":"","sources":["../src/relationship.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relationship.d.ts","sourceRoot":"","sources":["../src/relationship.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAElG;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC;CAC/D;AAED,kGAAkG;AAClG,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,kHAAkH;IAClH,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;CACtC;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,OAAO,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAE/F,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,CAAC,EAAE,gBAAgB,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG,CAAC,CAG3E;AAED,6GAA6G;AAC7G,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,gHAAgH;IAChH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,wGAAwG;IACxG,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,aAAa,CAAC,SAAS,CAAC,EAAE,sBAAsB,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,kBAAkB,CAAC;CAC5F;AAED,6GAA6G;AAC7G,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;AAE5E,uGAAuG;AACvG,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,eAAe,CAAC,KAAK,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC;IACjG,QAAQ,CAAC,WAAW,EAAE,KAAK,GAAG,MAAM,CAAC;IACrC,uFAAuF;IACvF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;IACrC,qEAAqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,EAC5E,OAAO,EAAE,MAAM,CAAC,EAChB,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,EAC3B,OAAO,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC/B,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAEhC;AAED,uHAAuH;AACvH,wBAAgB,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,EAC7E,OAAO,EAAE,MAAM,CAAC,EAChB,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,EAC3B,OAAO,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC/B,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAEhC;AAED,6GAA6G;AAC7G,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,OAAO,CAOtE;AAED,+GAA+G;AAC/G,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,GAC1D,OAAO,GAAG,SAAS,CAIrB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,CASpF"}
@@ -5,11 +5,16 @@ export function loadContext(contextArguments) {
5
5
  // A conditional tuple's element does not narrow back to `C`, and this is the one place that reads it.
6
6
  return contextArguments[0];
7
7
  }
8
- /** Declares a to-one relationship - its `data` is a single identifier, or null. */
8
+ /**
9
+ * Declares a to-one relationship - its `data` is a single identifier, or null.
10
+ *
11
+ * The context is inferred from the loader and from the related resource. An explicit `toOne<Ctx>(...)`
12
+ * would stop TypeScript inferring the related resource, which then widens and drops every literal below it.
13
+ */
9
14
  export function toOne(related, read, options) {
10
15
  return { cardinality: 'one', related, read, load: options?.load };
11
16
  }
12
- /** Declares a to-many relationship - its `data` is an array of identifiers. */
17
+ /** Declares a to-many relationship - its `data` is an array of identifiers. The context is inferred as for `toOne`. */
13
18
  export function toMany(related, read, options) {
14
19
  return { cardinality: 'many', related, read, load: options?.load };
15
20
  }
@@ -2,10 +2,12 @@ import type { output, ZodObject, ZodType } from 'zod';
2
2
  import { type CollectionDocument, type ResourceDocument } from './document.js';
3
3
  import { type LoadContextArguments, type RelatedResource, type RelationshipDef } from './relationship.js';
4
4
  import { type CollectionExtras, type RequestedDocumentShape, type Row } from './serialize.js';
5
+ /** The context every loader in a relationship map runs under. */
6
+ type ContextOfRelationships<R> = R[keyof R] extends RelationshipDef<infer C, any> ? C : unknown;
5
7
  /** Per-entity overrides for a resource declaration. */
6
- export interface ResourceOptions<C> {
8
+ export interface ResourceOptions<R> {
7
9
  /** Relationships this resource declares, keyed by the member name they take in `relationships`. */
8
- relationships?: Readonly<Record<string, RelationshipDef<C>>>;
10
+ relationships?: R;
9
11
  }
10
12
  /**
11
13
  * One resource type declared once: the schemas a route answers with, and the serializer that produces
@@ -18,8 +20,10 @@ export interface ResourceOptions<C> {
18
20
  * `C` is the context its loaders are called with, inferred from the relationships it declares and from
19
21
  * the resources they point at, so every resource in one document's graph loads under one context.
20
22
  */
21
- export interface JsonApiResource<A extends ZodObject, C = unknown> extends RelatedResource<C> {
23
+ export interface JsonApiResource<A extends ZodObject, C = unknown, T extends string = string, R extends Readonly<Record<string, RelationshipDef<any, any>>> = Readonly<Record<string, RelationshipDef<C>>>> extends RelatedResource<C> {
24
+ readonly type: T;
22
25
  readonly attributes: A;
26
+ readonly relationships: R;
23
27
  /** The single-resource document schema - `{ data, included?, links?, meta?, jsonapi? }`. */
24
28
  readonly document: ZodType<ResourceDocument<output<A>>>;
25
29
  /** The collection document schema - the same members with `data` an array. */
@@ -36,5 +40,6 @@ export interface JsonApiResource<A extends ZodObject, C = unknown> extends Relat
36
40
  serializeMany(rows: Row[], extras?: CollectionExtras, requested?: RequestedDocumentShape, ...context: LoadContextArguments<C>): Promise<CollectionDocument<output<A>>>;
37
41
  }
38
42
  /** Declares a JSON:API resource type - its `type`, the schema of its `attributes`, and its relationships. */
39
- export declare function jsonApiResource<A extends ZodObject, C = unknown>(type: string, attributes: A, options?: ResourceOptions<C>): JsonApiResource<A, C>;
43
+ export declare function jsonApiResource<const T extends string, A extends ZodObject, const R extends Readonly<Record<string, RelationshipDef<any, any>>> = Readonly<Record<string, never>>, C = ContextOfRelationships<R>>(type: T, attributes: A, options?: ResourceOptions<R>): JsonApiResource<A, C, T, R>;
44
+ export {};
40
45
  //# sourceMappingURL=resource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACtD,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAe,KAAK,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACvH,OAAO,EAIL,KAAK,gBAAgB,EAErB,KAAK,sBAAsB,EAE3B,KAAK,GAAG,EACT,MAAM,gBAAgB,CAAC;AAExB,uDAAuD;AACvD,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,mGAAmG;IACnG,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC9D;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,GAAG,OAAO,CAAE,SAAQ,eAAe,CAAC,CAAC,CAAC;IAC3F,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IACvB,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,8EAA8E;IAC9E,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,SAAS,CACP,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,sBAAsB,EAClC,GAAG,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,4FAA4F;IAC5F,aAAa,CACX,IAAI,EAAE,GAAG,EAAE,EACX,MAAM,CAAC,EAAE,gBAAgB,EACzB,SAAS,CAAC,EAAE,sBAAsB,EAClC,GAAG,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3C;AAgBD,6GAA6G;AAC7G,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,GAAG,OAAO,EAC9D,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,CAAC,EACb,OAAO,GAAE,eAAe,CAAC,CAAC,CAAM,GAC/B,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAsEvB"}
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACtD,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAe,KAAK,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACvH,OAAO,EAIL,KAAK,gBAAgB,EAErB,KAAK,sBAAsB,EAE3B,KAAK,GAAG,EACT,MAAM,gBAAgB,CAAC;AAExB,iEAAiE;AAEjE,KAAK,sBAAsB,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,eAAe,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;AAEhG,uDAAuD;AACvD,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,mGAAmG;IACnG,aAAa,CAAC,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe,CAC9B,CAAC,SAAS,SAAS,EACnB,CAAC,GAAG,OAAO,EACX,CAAC,SAAS,MAAM,GAAG,MAAM,EAEzB,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAC5G,SAAQ,eAAe,CAAC,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,8EAA8E;IAC9E,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,SAAS,CACP,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,sBAAsB,EAClC,GAAG,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,4FAA4F;IAC5F,aAAa,CACX,IAAI,EAAE,GAAG,EAAE,EACX,MAAM,CAAC,EAAE,gBAAgB,EACzB,SAAS,CAAC,EAAE,sBAAsB,EAClC,GAAG,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3C;AAgBD,6GAA6G;AAC7G,wBAAgB,eAAe,CAC7B,KAAK,CAAC,CAAC,SAAS,MAAM,EACtB,CAAC,SAAS,SAAS,EAEnB,KAAK,CAAC,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,EACrG,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,EAC7B,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,GAAE,eAAe,CAAC,CAAC,CAAM,GAAG,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAwEvF"}
package/dist/resource.js CHANGED
@@ -17,7 +17,9 @@ function shapeKey(requested) {
17
17
  }
18
18
  /** Declares a JSON:API resource type - its `type`, the schema of its `attributes`, and its relationships. */
19
19
  export function jsonApiResource(type, attributes, options = {}) {
20
- const relationships = options.relationships ?? {};
20
+ // Safe: when `relationships` is omitted, R is inferred as its own empty-relationships default above,
21
+ // which `{}` already satisfies.
22
+ const relationships = (options.relationships ?? {});
21
23
  // The attribute keys ARE the projection, so a row member the schema does not declare never reaches the wire.
22
24
  const projection = Object.fromEntries(Object.keys(attributes.shape).map((name) => [name, 1]));
23
25
  const serializers = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxsolutions/response",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,