@zeroxsolutions/response 0.12.2 → 0.14.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 +72 -0
- package/README.md +1 -0
- package/dist/created-response.d.ts +23 -0
- package/dist/created-response.d.ts.map +1 -0
- package/dist/created-response.js +19 -0
- package/dist/document-graph.d.ts +58 -0
- package/dist/document-graph.d.ts.map +1 -0
- package/dist/document-graph.js +1 -0
- package/dist/document-load.d.ts +1 -1
- package/dist/document-load.d.ts.map +1 -1
- package/dist/document-load.js +20 -8
- package/dist/document-shape.d.ts +12 -10
- package/dist/document-shape.d.ts.map +1 -1
- package/dist/document-shape.js +21 -10
- package/dist/document.d.ts +17 -5
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +19 -7
- package/dist/errors/error-document.d.ts +7 -2
- package/dist/errors/error-document.d.ts.map +1 -1
- package/dist/errors/error-document.js +7 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/member-name.d.ts +9 -0
- package/dist/member-name.d.ts.map +1 -0
- package/dist/member-name.js +15 -0
- package/dist/primitives.d.ts +174 -2
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +14 -2
- package/dist/relationship.d.ts +13 -6
- package/dist/relationship.d.ts.map +1 -1
- package/dist/relationship.js +14 -5
- package/dist/resource.d.ts +18 -4
- package/dist/resource.d.ts.map +1 -1
- package/dist/resource.js +73 -3
- package/dist/serialize.d.ts +14 -4
- package/dist/serialize.d.ts.map +1 -1
- package/dist/serialize.js +32 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,75 @@
|
|
|
1
|
+
## 0.14.0 (2026-09-28)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **response:** answer a created resource with its location ([e9c0086](https://github.com/zeroxsolutions/zeroxsolutions/commit/e9c0086))
|
|
6
|
+
- ⚠️ **response:** refuse a declaration the standard's field rules forbid ([30e6b77](https://github.com/zeroxsolutions/zeroxsolutions/commit/30e6b77))
|
|
7
|
+
|
|
8
|
+
### 🩹 Fixes
|
|
9
|
+
|
|
10
|
+
- ⚠️ **response:** walk a pipe's input when checking member names ([0949d22](https://github.com/zeroxsolutions/zeroxsolutions/commit/0949d22))
|
|
11
|
+
- **response:** match link types to their schemas ([0f7c79b](https://github.com/zeroxsolutions/zeroxsolutions/commit/0f7c79b))
|
|
12
|
+
- ⚠️ **response:** refuse a resource id that is not a string ([57f60ab](https://github.com/zeroxsolutions/zeroxsolutions/commit/57f60ab))
|
|
13
|
+
- **response:** keep a primary resource out of included ([c38859f](https://github.com/zeroxsolutions/zeroxsolutions/commit/c38859f))
|
|
14
|
+
- ⚠️ **response:** read only the document members the standard defines ([fc5c51a](https://github.com/zeroxsolutions/zeroxsolutions/commit/fc5c51a))
|
|
15
|
+
- ⚠️ **response:** refuse an error object that carries no member ([7d888ed](https://github.com/zeroxsolutions/zeroxsolutions/commit/7d888ed))
|
|
16
|
+
|
|
17
|
+
### ⚠️ Breaking Changes
|
|
18
|
+
|
|
19
|
+
- **response:** walk a pipe's input when checking member names ([0949d22](https://github.com/zeroxsolutions/zeroxsolutions/commit/0949d22))
|
|
20
|
+
a nested attribute member name that breaks the
|
|
21
|
+
standard's member-name rule, and sits behind a .transform() or
|
|
22
|
+
.pipe(), now throws at jsonApiResource's own declaration where it
|
|
23
|
+
previously passed unchecked.
|
|
24
|
+
- **response:** refuse a declaration the standard's field rules forbid ([30e6b77](https://github.com/zeroxsolutions/zeroxsolutions/commit/30e6b77))
|
|
25
|
+
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.
|
|
26
|
+
- **response:** refuse a resource id that is not a string ([57f60ab](https://github.com/zeroxsolutions/zeroxsolutions/commit/57f60ab))
|
|
27
|
+
Row['id'] is typed string, and serializing a row, embedded row or foreign key whose id is not a string throws TypeError.
|
|
28
|
+
- **response:** read only the document members the standard defines ([fc5c51a](https://github.com/zeroxsolutions/zeroxsolutions/commit/fc5c51a))
|
|
29
|
+
a data document carrying errors, an error document carrying data, and an empty relationship object are refused.
|
|
30
|
+
- **response:** refuse an error object that carries no member ([7d888ed](https://github.com/zeroxsolutions/zeroxsolutions/commit/7d888ed))
|
|
31
|
+
toErrorDocument throws on an empty error object.
|
|
32
|
+
|
|
33
|
+
### ❤️ Thank You
|
|
34
|
+
|
|
35
|
+
- Claude Opus 5.5 (1M context)
|
|
36
|
+
- Lương Văn Tú
|
|
37
|
+
|
|
38
|
+
## 0.13.0 (2026-09-27)
|
|
39
|
+
|
|
40
|
+
### 🚀 Features
|
|
41
|
+
|
|
42
|
+
- ⚠️ **response:** type the fieldsets and include paths a document answers ([98bbb58](https://github.com/zeroxsolutions/zeroxsolutions/commit/98bbb58))
|
|
43
|
+
- ⚠️ **response:** keep a resource's type, attributes and relationships as literals ([738889e](https://github.com/zeroxsolutions/zeroxsolutions/commit/738889e))
|
|
44
|
+
|
|
45
|
+
### 🩹 Fixes
|
|
46
|
+
|
|
47
|
+
- ⚠️ **response:** pin an include path stopping at a wide edge ([515a3e9](https://github.com/zeroxsolutions/zeroxsolutions/commit/515a3e9))
|
|
48
|
+
|
|
49
|
+
### ⚠️ Breaking Changes
|
|
50
|
+
|
|
51
|
+
- **response:** pin an include path stopping at a wide edge ([515a3e9](https://github.com/zeroxsolutions/zeroxsolutions/commit/515a3e9))
|
|
52
|
+
jsonApiResource's type parameters moved from <A, C> to
|
|
53
|
+
<T, A, R, C>, so an explicit `jsonApiResource<typeof attrs, Ctx>` no
|
|
54
|
+
longer compiles; remove the type arguments and let inference type it.
|
|
55
|
+
The exported ResourceOptions<C> is now ResourceOptions<R>.
|
|
56
|
+
BREAKING CHANGE: an explicit toOne<Ctx> or toMany<Ctx> no longer
|
|
57
|
+
compiles; remove the type argument.
|
|
58
|
+
- **response:** type the fieldsets and include paths a document answers ([98bbb58](https://github.com/zeroxsolutions/zeroxsolutions/commit/98bbb58))
|
|
59
|
+
declaredDocumentShape answers typed fieldsets and include
|
|
60
|
+
paths, walks at most three relationships by default, and answers a fieldset
|
|
61
|
+
only for a type a kept path reaches. DeclaredDocumentShape is removed;
|
|
62
|
+
TypedDocumentShape replaces it.
|
|
63
|
+
- **response:** keep a resource's type, attributes and relationships as literals ([738889e](https://github.com/zeroxsolutions/zeroxsolutions/commit/738889e))
|
|
64
|
+
toOne and toMany take the related resource as their first type
|
|
65
|
+
parameter and infer the loader context; a call passing the context explicitly,
|
|
66
|
+
toOne<Ctx>(...), no longer compiles and drops the explicit argument.
|
|
67
|
+
|
|
68
|
+
### ❤️ Thank You
|
|
69
|
+
|
|
70
|
+
- Claude Opus 5.5 (1M context)
|
|
71
|
+
- Lương Văn Tú
|
|
72
|
+
|
|
1
73
|
## 0.12.2 (2026-09-27)
|
|
2
74
|
|
|
3
75
|
### 🚀 Features
|
package/README.md
CHANGED
|
@@ -134,6 +134,7 @@ app.onError(
|
|
|
134
134
|
namespace: 'academics', // every code THESE handlers mint carries it, so one surface has one vocabulary
|
|
135
135
|
category: ['academics', 'http'],
|
|
136
136
|
resolve: createDomainErrorResolver(academicsErrors),
|
|
137
|
+
authenticationChallenge: 'Bearer',
|
|
137
138
|
}),
|
|
138
139
|
);
|
|
139
140
|
```
|
|
@@ -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
|
+
}
|
|
@@ -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 {};
|
package/dist/document-load.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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"}
|
package/dist/document-load.js
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
|
20
|
-
const
|
|
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
|
-
|
|
38
|
-
|
|
49
|
+
assertRowId(type, row);
|
|
50
|
+
answered.set(row.id, row);
|
|
39
51
|
}
|
|
40
52
|
return related;
|
|
41
53
|
}
|
package/dist/document-shape.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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<
|
|
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,
|
|
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"}
|
package/dist/document-shape.js
CHANGED
|
@@ -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
|
|
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
|
|
11
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/document.d.ts
CHANGED
|
@@ -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?:
|
|
7
|
+
links?: RelationshipLinks;
|
|
8
8
|
meta?: Meta;
|
|
9
9
|
}
|
|
10
10
|
/** A resource object, with `attributes` carrying the declared attribute type. */
|
|
@@ -20,7 +20,7 @@ export interface ResourceObject<A> {
|
|
|
20
20
|
export interface ResourceDocument<A> {
|
|
21
21
|
data: ResourceObject<A>;
|
|
22
22
|
included?: ResourceObject<unknown>[];
|
|
23
|
-
links?:
|
|
23
|
+
links?: TopLevelLinks;
|
|
24
24
|
meta?: Meta;
|
|
25
25
|
jsonapi?: JsonApiObject;
|
|
26
26
|
}
|
|
@@ -32,7 +32,12 @@ export interface CollectionDocument<A> {
|
|
|
32
32
|
meta?: Meta;
|
|
33
33
|
jsonapi?: JsonApiObject;
|
|
34
34
|
}
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Wraps `attributes` into a resource object - `{ type, id, attributes?, relationships?, links?, meta? }`.
|
|
37
|
+
*
|
|
38
|
+
* `attributes` parses as absent here, unlike {@link ResourceObject}'s own required member: this schema
|
|
39
|
+
* also validates a document read off another server, which may publish a resource with none.
|
|
40
|
+
*/
|
|
36
41
|
export declare function resourceObjectSchema<C>(type: string, attributes: ZodObject, relationships?: Readonly<Record<string, RelationshipDef<C>>>): ZodType;
|
|
37
42
|
/**
|
|
38
43
|
* The document for a SINGLE resource - `{ data, included?, links?, meta?, jsonapi? }`.
|
|
@@ -40,8 +45,15 @@ export declare function resourceObjectSchema<C>(type: string, attributes: ZodObj
|
|
|
40
45
|
* `A` is asserted rather than inferred: the schema is assembled from a relationship graph reached
|
|
41
46
|
* through thunks, so nothing static carries the attribute type, and a client typing a route off this
|
|
42
47
|
* schema would otherwise read every member as `unknown`.
|
|
48
|
+
*
|
|
49
|
+
* Strict at the top level: the standard forbids any top-level member beyond these (extension members
|
|
50
|
+
* aside), which is what refuses a `data` document that also carries `errors`.
|
|
43
51
|
*/
|
|
44
52
|
export declare function resourceDocumentSchema<A = unknown, C = unknown>(root: RelatedResource<C>): ZodType<ResourceDocument<A>>;
|
|
45
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The document for a COLLECTION - the same members, with `data` an array and paginated `links`.
|
|
55
|
+
*
|
|
56
|
+
* Strict at the top level for the same reason as {@link resourceDocumentSchema}.
|
|
57
|
+
*/
|
|
46
58
|
export declare function collectionDocumentSchema<A = unknown, C = unknown>(root: RelatedResource<C>): ZodType<CollectionDocument<A>>;
|
|
47
59
|
//# sourceMappingURL=document.d.ts.map
|
package/dist/document.d.ts.map
CHANGED
|
@@ -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,
|
|
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,EAML,KAAK,aAAa,EAClB,KAAK,IAAI,EACT,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,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,iBAAiB,CAAC;IAC1B,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;IAGX,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;;;;;GAKG;AACH,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;;;;;;;;;GASG;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;;;;GAIG;AACH,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"}
|
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
|
-
/**
|
|
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,19 +37,26 @@ 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.
|
|
45
|
+
return z.strictObject({
|
|
38
46
|
data: root.resourceObject,
|
|
39
47
|
included: includedSchema(root).optional(),
|
|
40
|
-
links:
|
|
48
|
+
links: topLevelLinksSchema.optional(),
|
|
41
49
|
meta: metaSchema.optional(),
|
|
42
50
|
jsonapi: jsonApiObjectSchema.optional(),
|
|
43
51
|
});
|
|
44
52
|
}
|
|
45
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The document for a COLLECTION - the same members, with `data` an array and paginated `links`.
|
|
55
|
+
*
|
|
56
|
+
* Strict at the top level for the same reason as {@link resourceDocumentSchema}.
|
|
57
|
+
*/
|
|
46
58
|
export function collectionDocumentSchema(root) {
|
|
47
|
-
return z.
|
|
59
|
+
return z.strictObject({
|
|
48
60
|
data: z.array(root.resourceObject),
|
|
49
61
|
included: includedSchema(root).optional(),
|
|
50
62
|
links: paginationLinksSchema.optional(),
|
|
@@ -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
|
|
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.$
|
|
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,
|
|
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
|
|
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.
|
|
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,13 +2,15 @@
|
|
|
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 { createdResponse } from './created-response.js';
|
|
5
6
|
export type { CollectionDocument, Relationship, ResourceDocument, ResourceObject } from './document.js';
|
|
6
|
-
export
|
|
7
|
+
export * from './document-graph.js';
|
|
8
|
+
export { declaredDocumentShape } from './document-shape.js';
|
|
7
9
|
export { createDomainErrorResolver, defineErrorMap, errorEntry, type DomainErrorAnswer, type DomainErrorEntry, } from './domain-error-resolver.js';
|
|
8
10
|
export type { DomainErrorMap, ErrorClass, UpstreamErrorMap } from './error-map.js';
|
|
9
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';
|
|
10
12
|
export { JSON_API_MEDIA_TYPE } from './media-type.js';
|
|
11
|
-
export type { JsonApiObject, Link, Meta, PaginationLinks, ResourceIdentifier, ResourceLinks } from './primitives.js';
|
|
13
|
+
export type { JsonApiObject, Link, Meta, PaginationLinks, RelationshipLinks, ResourceIdentifier, ResourceLinks, TopLevelLinks, } from './primitives.js';
|
|
12
14
|
export { toMany, toOne, type LoadContextArguments, type RelatedResource, type RelationshipDef, type RelationshipLoader, type RelationshipOptions, } from './relationship.js';
|
|
13
15
|
export { jsonApiResource, type JsonApiResource, type ResourceOptions } from './resource.js';
|
|
14
16
|
export type { CollectionExtras, LoadedRows, RequestedDocumentShape, ResourceSerializer, Row } from './serialize.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,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,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,6 +2,8 @@
|
|
|
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';
|
|
6
|
+
export * from './document-graph.js';
|
|
5
7
|
export { declaredDocumentShape } from './document-shape.js';
|
|
6
8
|
export { createDomainErrorResolver, defineErrorMap, errorEntry, } from './domain-error-resolver.js';
|
|
7
9
|
export { BASE_ERROR_CODES, errorDocumentSchema, JsonApiError, toErrorDocument, toErrorObject, } from './errors/index.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a string is a legal JSON:API member name.
|
|
3
|
+
*
|
|
4
|
+
* Copied rather than imported at `packages/query/src/bracket-params.ts`'s own `isMemberName`: this
|
|
5
|
+
* package may not import `@zeroxsolutions/query` (nor the reverse), so each side of that boundary
|
|
6
|
+
* keeps its own copy of the one rule instead.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isMemberName(name: string): boolean;
|
|
9
|
+
//# sourceMappingURL=member-name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"member-name.d.ts","sourceRoot":"","sources":["../src/member-name.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Characters a member name may start or end with: the standard's globally allowed characters. */
|
|
2
|
+
const GLOBAL = 'a-zA-Z0-9\\u0080-\\u{10FFFF}';
|
|
3
|
+
/** Additionally allowed between the first and last character: hyphen, low line and space. */
|
|
4
|
+
const MIDDLE = '\\-_ ';
|
|
5
|
+
const MEMBER_NAME = new RegExp(`^[${GLOBAL}](?:[${GLOBAL}${MIDDLE}]*[${GLOBAL}])?$`, 'u');
|
|
6
|
+
/**
|
|
7
|
+
* Whether a string is a legal JSON:API member name.
|
|
8
|
+
*
|
|
9
|
+
* Copied rather than imported at `packages/query/src/bracket-params.ts`'s own `isMemberName`: this
|
|
10
|
+
* package may not import `@zeroxsolutions/query` (nor the reverse), so each side of that boundary
|
|
11
|
+
* keeps its own copy of the one rule instead.
|
|
12
|
+
*/
|
|
13
|
+
export function isMemberName(name) {
|
|
14
|
+
return MEMBER_NAME.test(name);
|
|
15
|
+
}
|