@stone-js/resources 0.8.9 → 0.8.10
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/dist/ContractChecker.d.ts +96 -0
- package/dist/Resource.d.ts +96 -28
- package/dist/declarations.d.ts +54 -16
- package/dist/defineResource.d.ts +30 -8
- package/dist/errors/ResourceContractError.d.ts +31 -0
- package/dist/helpers.d.ts +15 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +450 -106
- package/dist/middleware/ResourceRouteMiddleware.d.ts +33 -12
- package/dist/options/ResourcesBlueprint.d.ts +19 -0
- package/package.json +5 -4
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ContractIssue } from './errors/ResourceContractError.js';
|
|
2
|
+
/** What checking a projection against its contract produced. */
|
|
3
|
+
export interface ContractResult<T = unknown> {
|
|
4
|
+
success: boolean;
|
|
5
|
+
value?: T;
|
|
6
|
+
issues?: ContractIssue[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* What this module needs from a schema: the ability to run it and read the outcome.
|
|
10
|
+
*
|
|
11
|
+
* Declared so an application can substitute its own — a bespoke dialect, a shared engine it already
|
|
12
|
+
* configured — without this module knowing anything about it.
|
|
13
|
+
*/
|
|
14
|
+
export interface IContractChecker {
|
|
15
|
+
check: <T>(schema: unknown, data: unknown) => ContractResult<T>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Runs a schema and reports what it said.
|
|
19
|
+
*
|
|
20
|
+
* This module reads schemas; it does not validate requests, own engines, or keep a registry — so it
|
|
21
|
+
* carries its own reader rather than depending on a validation module to project data. The dialects
|
|
22
|
+
* it accepts are public specifications, not one library's API: Standard Schema first, then the
|
|
23
|
+
* `safeParse`/`parse` shape, then a plain `validate`. An application writes its schemas once and both
|
|
24
|
+
* sides of the boundary read them.
|
|
25
|
+
*
|
|
26
|
+
* Substitutable: pass your own {@link IContractChecker} and this one steps aside.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ContractChecker implements IContractChecker {
|
|
29
|
+
/**
|
|
30
|
+
* Factory.
|
|
31
|
+
*
|
|
32
|
+
* @returns A checker.
|
|
33
|
+
*/
|
|
34
|
+
static create(): ContractChecker;
|
|
35
|
+
/**
|
|
36
|
+
* Run a schema against a value.
|
|
37
|
+
*
|
|
38
|
+
* @param schema - The contract.
|
|
39
|
+
* @param data - What the resource produced.
|
|
40
|
+
* @returns Whether it holds, the parsed value, and what failed.
|
|
41
|
+
* @throws {TypeError} When the value is not a schema this can run, because guessing would mean
|
|
42
|
+
* projecting unchecked data while reporting success.
|
|
43
|
+
*/
|
|
44
|
+
check<T>(schema: unknown, data: unknown): ContractResult<T>;
|
|
45
|
+
/**
|
|
46
|
+
* Read a Standard Schema result.
|
|
47
|
+
*
|
|
48
|
+
* The synchronous path only: a schema whose validation is asynchronous returns a promise, and a
|
|
49
|
+
* promise is not a result. Saying so beats treating it as one, which is how `[object Promise]`
|
|
50
|
+
* reaches a response body.
|
|
51
|
+
*
|
|
52
|
+
* @param schema - The schema.
|
|
53
|
+
* @param data - The value.
|
|
54
|
+
* @returns The outcome.
|
|
55
|
+
*/
|
|
56
|
+
private fromStandard;
|
|
57
|
+
/**
|
|
58
|
+
* Read a `safeParse` result (the Zod-like shape).
|
|
59
|
+
*
|
|
60
|
+
* @param schema - The schema.
|
|
61
|
+
* @param data - The value.
|
|
62
|
+
* @returns The outcome.
|
|
63
|
+
*/
|
|
64
|
+
private fromSafeParse;
|
|
65
|
+
/**
|
|
66
|
+
* Read a native `validate` result: already the shape this module reports.
|
|
67
|
+
*
|
|
68
|
+
* @param schema - The schema.
|
|
69
|
+
* @param data - The value.
|
|
70
|
+
* @returns The outcome.
|
|
71
|
+
*/
|
|
72
|
+
private fromValidate;
|
|
73
|
+
/**
|
|
74
|
+
* Read a throwing `parse`.
|
|
75
|
+
*
|
|
76
|
+
* @param schema - The schema.
|
|
77
|
+
* @param data - The value.
|
|
78
|
+
* @returns The outcome.
|
|
79
|
+
*/
|
|
80
|
+
private fromParse;
|
|
81
|
+
/**
|
|
82
|
+
* Normalise one issue, whatever dialect reported it.
|
|
83
|
+
*
|
|
84
|
+
* @param issue - The raw issue.
|
|
85
|
+
* @returns The issue.
|
|
86
|
+
*/
|
|
87
|
+
private toIssue;
|
|
88
|
+
/** @param schema - The candidate. @returns Whether it speaks Standard Schema. */
|
|
89
|
+
private isStandard;
|
|
90
|
+
/** @param schema - The candidate. @returns Whether it exposes `safeParse`. */
|
|
91
|
+
private hasSafeParse;
|
|
92
|
+
/** @param schema - The candidate. @returns Whether it exposes `validate`. */
|
|
93
|
+
private hasValidate;
|
|
94
|
+
/** @param schema - The candidate. @returns Whether it exposes `parse`. */
|
|
95
|
+
private hasParse;
|
|
96
|
+
}
|
package/dist/Resource.d.ts
CHANGED
|
@@ -1,62 +1,130 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IContractChecker } from './ContractChecker.js';
|
|
2
|
+
import { ContractViolationPolicy, IResource, ResourceContext, ResourceEnvelope, ResourceOutput, ResourceSchema } from './declarations.js';
|
|
2
3
|
/**
|
|
3
|
-
* Base API resource — the
|
|
4
|
+
* Base API resource — the layer responsible for exposing data.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* A resource answers two questions, and the second is what makes it worth having. *What leaves?* —
|
|
7
|
+
* and *what did you promise leaves?* The promise is a schema, so the same declaration validates the
|
|
8
|
+
* response, documents it in the published contract, and lets a caller ask for a named subset of it.
|
|
9
|
+
*
|
|
10
|
+
* Projection is the schema's own work: what the schema does not describe is not exposed, so a field
|
|
11
|
+
* added to a model later — a password hash, an internal flag — cannot leak by being forgotten.
|
|
9
12
|
*
|
|
10
13
|
* @example
|
|
11
14
|
* ```ts
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* @ApiResource('user')
|
|
16
|
+
* export class UserResource extends Resource<User> {
|
|
17
|
+
* constructor ({ posts }: { posts: PostService }) {
|
|
18
|
+
* super()
|
|
19
|
+
* this.posts = posts
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* schema () {
|
|
23
|
+
* return z.object({ id: z.number(), name: z.string(), posts: z.array(z.string()).optional() })
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* fragments () {
|
|
27
|
+
* return { summary: z.object({ id: z.number(), name: z.string() }) }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* async data (user: User) {
|
|
31
|
+
* return { ...user, posts: await this.posts.titlesOf(user.id) }
|
|
20
32
|
* }
|
|
21
33
|
* }
|
|
22
34
|
* ```
|
|
23
35
|
*/
|
|
24
36
|
export declare abstract class Resource<Model = unknown, Output extends ResourceOutput = ResourceOutput> implements IResource<Model, Output> {
|
|
37
|
+
private readonly checker;
|
|
38
|
+
private readonly onViolation;
|
|
25
39
|
/**
|
|
26
|
-
*
|
|
40
|
+
* @param dependencies - Auto-wired services. Nothing is required: this module reads schemas with its
|
|
41
|
+
* own checker, so exposing data never depends on a validation module being
|
|
42
|
+
* enabled. Pass `checker` to substitute a dialect of your own.
|
|
43
|
+
*/
|
|
44
|
+
constructor(dependencies?: {
|
|
45
|
+
checker?: IContractChecker;
|
|
46
|
+
onViolation?: ContractViolationPolicy;
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* The contract: what this resource exposes.
|
|
27
50
|
*
|
|
28
|
-
* @param model - The domain model.
|
|
29
51
|
* @param context - The resource context.
|
|
30
|
-
* @returns The
|
|
52
|
+
* @returns The schema.
|
|
31
53
|
*/
|
|
32
|
-
abstract
|
|
54
|
+
abstract schema(context: ResourceContext): ResourceSchema | Promise<ResourceSchema>;
|
|
33
55
|
/**
|
|
34
|
-
*
|
|
56
|
+
* Project one model.
|
|
57
|
+
*
|
|
58
|
+
* The order is the design: complete the data, choose the contract the caller asked for, hold the
|
|
59
|
+
* result against it, then narrow. Validation happens *before* narrowing, so the promise is checked
|
|
60
|
+
* against everything the resource produced rather than against whatever survived a query parameter.
|
|
35
61
|
*
|
|
36
62
|
* @param model - The domain model.
|
|
37
63
|
* @param context - The resource context.
|
|
38
|
-
* @returns The
|
|
64
|
+
* @returns The projected output.
|
|
65
|
+
* @throws {ResourceContractError} When the data breaks the contract and the policy is `throw`.
|
|
39
66
|
*/
|
|
40
|
-
item(model: Model, context?: ResourceContext):
|
|
67
|
+
item(model: Model, context?: ResourceContext): Promise<Output>;
|
|
41
68
|
/**
|
|
42
|
-
*
|
|
69
|
+
* Project a collection.
|
|
70
|
+
*
|
|
71
|
+
* Sequential rather than concurrent: `data()` may reach a database or an API, and a hundred models
|
|
72
|
+
* turning into a hundred simultaneous calls is a denial of service an application performs on
|
|
73
|
+
* itself. A resource that wants concurrency batches inside its own `data()`, where it knows the cost.
|
|
43
74
|
*
|
|
44
75
|
* @param models - The domain models.
|
|
45
76
|
* @param context - The resource context.
|
|
46
|
-
* @returns The
|
|
77
|
+
* @returns The projected collection.
|
|
47
78
|
*/
|
|
48
|
-
collection(models: Model[], context?: ResourceContext):
|
|
79
|
+
collection(models: Model[], context?: ResourceContext): Promise<Output[]>;
|
|
49
80
|
/**
|
|
50
|
-
*
|
|
81
|
+
* Project into a `{ data, meta }` envelope.
|
|
51
82
|
*
|
|
52
83
|
* @param models - A model or a collection.
|
|
53
84
|
* @param context - The resource context.
|
|
54
85
|
* @param meta - Optional metadata (pagination, counts, …).
|
|
55
86
|
* @returns The envelope.
|
|
56
87
|
*/
|
|
57
|
-
response(models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>): ResourceEnvelope<
|
|
88
|
+
response(models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>): Promise<ResourceEnvelope<Output | Output[]>>;
|
|
89
|
+
/**
|
|
90
|
+
* Optional: shape or complete the model before it meets the schema.
|
|
91
|
+
*
|
|
92
|
+
* `declare`, not a field: an uninitialised class field is *defined* as `undefined` on the instance,
|
|
93
|
+
* which would shadow the very method a subclass wrote — the override would exist on the prototype
|
|
94
|
+
* and never be reached. This states the type and emits nothing.
|
|
95
|
+
*/
|
|
96
|
+
data?: (model: Model, context: ResourceContext) => unknown | Promise<unknown>;
|
|
97
|
+
/**
|
|
98
|
+
* Named subsets a caller may ask for. Override to expose fragments.
|
|
99
|
+
*/
|
|
100
|
+
fragments?: (context: ResourceContext) => Record<string, ResourceSchema> | Promise<Record<string, ResourceSchema>>;
|
|
101
|
+
/**
|
|
102
|
+
* The schema to hold this projection against: the requested fragment when the resource exposes one,
|
|
103
|
+
* the full contract otherwise.
|
|
104
|
+
*
|
|
105
|
+
* An unknown fragment falls back to the full contract rather than failing. A caller guessing
|
|
106
|
+
* `?view=nonsense` is asking a question, not attacking: answering the documented shape is more
|
|
107
|
+
* useful than a 500, and the fragments a resource exposes are published in the contract anyway.
|
|
108
|
+
*
|
|
109
|
+
* @param context - The resource context.
|
|
110
|
+
* @returns The schema.
|
|
111
|
+
*/
|
|
112
|
+
protected schemaFor(context: ResourceContext): Promise<ResourceSchema>;
|
|
113
|
+
/**
|
|
114
|
+
* Hold the data against the contract, and return what the contract describes.
|
|
115
|
+
*
|
|
116
|
+
* The schema is the projection: its parsed value is the output, so a field the contract does not
|
|
117
|
+
* mention is not exposed, whatever the model gains later.
|
|
118
|
+
*
|
|
119
|
+
* @param data - The completed data.
|
|
120
|
+
* @param schema - The contract.
|
|
121
|
+
* @param context - The resource context.
|
|
122
|
+
* @returns The projected value.
|
|
123
|
+
* @throws {ResourceContractError} When the data breaks the contract and the policy is `throw`.
|
|
124
|
+
*/
|
|
125
|
+
protected project(data: unknown, schema: ResourceSchema, context: ResourceContext): Promise<unknown>;
|
|
58
126
|
/**
|
|
59
|
-
* Include a value only when `condition`
|
|
127
|
+
* Include a value only when `condition` holds (otherwise the field is dropped).
|
|
60
128
|
*
|
|
61
129
|
* @param condition - Whether to include the value.
|
|
62
130
|
* @param value - The value, or a lazy factory (only evaluated when included).
|
|
@@ -64,7 +132,7 @@ export declare abstract class Resource<Model = unknown, Output extends ResourceO
|
|
|
64
132
|
*/
|
|
65
133
|
protected when<T>(condition: boolean, value: T | (() => T)): T | undefined;
|
|
66
134
|
/**
|
|
67
|
-
* Include a value only when the relation was requested
|
|
135
|
+
* Include a value only when the relation was requested through `context.include`.
|
|
68
136
|
*
|
|
69
137
|
* @param context - The resource context.
|
|
70
138
|
* @param name - The relation name.
|
package/dist/declarations.d.ts
CHANGED
|
@@ -1,36 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* include, and any extra data (the current user, the event, …) a resource may consult.
|
|
2
|
+
* A schema, in whatever shape the application already writes them.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* Anything `@stone-js/validation` accepts is accepted here: a Standard Schema (Zod, Valibot, ArkType
|
|
5
|
+
* and others), a Zod-like `safeParse`, or a native Stone.js schema. Resources do not define a schema
|
|
6
|
+
* language; they use the one the application already validates its input with, so a contract is
|
|
7
|
+
* written once in one dialect on both sides of the boundary.
|
|
8
|
+
*/
|
|
9
|
+
export type ResourceSchema = unknown;
|
|
10
|
+
/**
|
|
11
|
+
* The context a projection is given.
|
|
12
|
+
*
|
|
13
|
+
* Open on purpose: a resource reads whatever it needs, and the middleware fills in what the request
|
|
14
|
+
* carried. The authenticated principal is part of it, because deciding what a caller may see is the
|
|
15
|
+
* most common reason a projection differs between two callers.
|
|
6
16
|
*/
|
|
7
17
|
export interface ResourceContext {
|
|
8
|
-
/** Requested sparse fieldset —
|
|
18
|
+
/** Requested sparse fieldset — narrows the output to these top-level keys. */
|
|
9
19
|
fields?: string[];
|
|
10
20
|
/** Requested relations to embed. */
|
|
11
21
|
include?: string[];
|
|
12
|
-
/**
|
|
22
|
+
/** The requested fragment, when the caller asked for one by name. */
|
|
23
|
+
fragment?: string;
|
|
24
|
+
/** The authenticated principal, when the application has one. */
|
|
25
|
+
principal?: unknown;
|
|
26
|
+
/** The event being answered, for a resource that needs more than the parameters above. */
|
|
27
|
+
event?: unknown;
|
|
28
|
+
/** Anything else a resource needs. */
|
|
13
29
|
[key: string]: unknown;
|
|
14
30
|
}
|
|
15
31
|
/** A plain, serialisable output object. */
|
|
16
32
|
export type ResourceOutput = Record<string, unknown>;
|
|
17
33
|
/**
|
|
18
|
-
* A `{ data, meta }` envelope around a
|
|
34
|
+
* A `{ data, meta }` envelope around a projected item or collection.
|
|
19
35
|
*/
|
|
20
36
|
export interface ResourceEnvelope<T> {
|
|
21
37
|
data: T;
|
|
22
38
|
meta?: Record<string, unknown>;
|
|
23
39
|
}
|
|
24
40
|
/**
|
|
25
|
-
*
|
|
41
|
+
* What a resource does: turn a domain model into the shape a caller is allowed to see, and say what
|
|
42
|
+
* that shape is.
|
|
43
|
+
*
|
|
44
|
+
* Saying it is the point. A projection written as code answers "what does this return?" only by being
|
|
45
|
+
* read and trusted; a projection written as a schema answers it to a person, to `@stone-js/openapi`,
|
|
46
|
+
* and to the resource itself, which validates against it before anything leaves. One declaration, three
|
|
47
|
+
* consumers, and no way for the documentation to drift from the response.
|
|
26
48
|
*/
|
|
27
|
-
export interface IResource<Model = unknown, Output
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
export interface IResource<Model = unknown, Output = ResourceOutput> {
|
|
50
|
+
/** The contract: the schema every projection is validated against and documented from. */
|
|
51
|
+
schema: (context: ResourceContext) => ResourceSchema | Promise<ResourceSchema>;
|
|
52
|
+
/**
|
|
53
|
+
* Named subsets a caller may ask for, each with its own schema.
|
|
54
|
+
*
|
|
55
|
+
* A fragment is not a filter: it is a contract of its own, documented and validated like the full
|
|
56
|
+
* one. That is what makes `?view=summary` safe to expose.
|
|
57
|
+
*/
|
|
58
|
+
fragments?: (context: ResourceContext) => Record<string, ResourceSchema> | Promise<Record<string, ResourceSchema>>;
|
|
59
|
+
/**
|
|
60
|
+
* Optional hook to shape or complete the model before it meets the schema.
|
|
61
|
+
*
|
|
62
|
+
* Asynchronous, and resolved from the container, so it may reach any service: fetch a relation,
|
|
63
|
+
* translate a label, compute a total. Whatever it returns is what the schema then validates.
|
|
64
|
+
*/
|
|
65
|
+
data?: (model: Model, context: ResourceContext) => unknown | Promise<unknown>;
|
|
66
|
+
/** Project one model. */
|
|
67
|
+
item: (model: Model, context?: ResourceContext) => Promise<Output>;
|
|
68
|
+
/** Project a collection. */
|
|
69
|
+
collection: (models: Model[], context?: ResourceContext) => Promise<Output[]>;
|
|
70
|
+
/** Project into a `{ data, meta }` envelope. */
|
|
71
|
+
response: (models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>) => Promise<ResourceEnvelope<Output | Output[]>>;
|
|
36
72
|
}
|
|
73
|
+
/** What to do when the data does not match the contract the resource published. */
|
|
74
|
+
export type ContractViolationPolicy = 'throw' | 'warn';
|
package/dist/defineResource.d.ts
CHANGED
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
import { Resource } from './Resource.js';
|
|
2
|
-
import {
|
|
2
|
+
import { IContractChecker } from './ContractChecker.js';
|
|
3
|
+
import { ContractViolationPolicy, ResourceContext, ResourceOutput, ResourceSchema } from './declarations.js';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* class. Returns a full {@link Resource} (so you still get `item`/`collection`/`response` and
|
|
6
|
-
* sparse fieldsets for free).
|
|
5
|
+
* What an imperatively-defined resource declares.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
7
|
+
* The same three things a class declares, so neither paradigm can do something the other cannot.
|
|
8
|
+
*/
|
|
9
|
+
export interface ResourceDefinition<Model = unknown> {
|
|
10
|
+
/** The contract: what this resource exposes. A schema, or a function returning one. */
|
|
11
|
+
schema: ResourceSchema | ((context: ResourceContext) => ResourceSchema | Promise<ResourceSchema>);
|
|
12
|
+
/** Named subsets a caller may ask for, each with its own schema. */
|
|
13
|
+
fragments?: Record<string, ResourceSchema> | ((context: ResourceContext) => Record<string, ResourceSchema> | Promise<Record<string, ResourceSchema>>);
|
|
14
|
+
/** Optional hook to shape or complete the model before it meets the schema. */
|
|
15
|
+
data?: (model: Model, context: ResourceContext) => unknown | Promise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The imperative way to define a resource: an object instead of a class.
|
|
19
|
+
*
|
|
20
|
+
* Parity is the rule, so this declares exactly what a class declares and gets exactly what a class
|
|
21
|
+
* gets. It needs nothing injected: this module reads schemas with its own checker.
|
|
22
|
+
*
|
|
23
|
+
* @param definition - The schema, and optionally fragments and a `data()` hook.
|
|
24
|
+
* @param dependencies - Optional explicit services, for a resource used outside a request.
|
|
9
25
|
* @returns A resource.
|
|
10
26
|
*
|
|
11
27
|
* @example
|
|
12
28
|
* ```ts
|
|
13
|
-
* const userResource = defineResource<User>(
|
|
14
|
-
*
|
|
29
|
+
* export const userResource = defineResource<User>({
|
|
30
|
+
* schema: z.object({ id: z.number(), name: z.string() }),
|
|
31
|
+
* fragments: { summary: z.object({ id: z.number() }) },
|
|
32
|
+
* data: async (user) => ({ ...user, posts: await posts.titlesOf(user.id) })
|
|
33
|
+
* })
|
|
15
34
|
* ```
|
|
16
35
|
*/
|
|
17
|
-
export declare function defineResource<Model = unknown, Output extends ResourceOutput = ResourceOutput>(
|
|
36
|
+
export declare function defineResource<Model = unknown, Output extends ResourceOutput = ResourceOutput>(definition: ResourceDefinition<Model>, dependencies?: {
|
|
37
|
+
checker?: IContractChecker;
|
|
38
|
+
onViolation?: ContractViolationPolicy;
|
|
39
|
+
}): Resource<Model, Output>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ErrorOptions, RuntimeError } from '@stone-js/core';
|
|
2
|
+
/** One reason the data did not match the contract. */
|
|
3
|
+
export interface ContractIssue {
|
|
4
|
+
message: string;
|
|
5
|
+
path: Array<string | number>;
|
|
6
|
+
}
|
|
7
|
+
/** Options carrying what failed. */
|
|
8
|
+
export interface ResourceContractErrorOptions extends ErrorOptions {
|
|
9
|
+
issues?: ContractIssue[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Raised when what a handler produced does not match the schema its resource published.
|
|
13
|
+
*
|
|
14
|
+
* This is a server-side fault, deliberately. The resource's schema is a promise made to every caller
|
|
15
|
+
* and to the published contract; data that breaks it means the application is about to answer
|
|
16
|
+
* something it documented it would not. Returning it anyway is the failure — a client cannot detect
|
|
17
|
+
* it, and a consumer generated from the contract will break on a field that was supposed to be there.
|
|
18
|
+
*
|
|
19
|
+
* It fires on a genuine breach, not on a difference: a schema strips what it does not describe, so
|
|
20
|
+
* extra fields are simply not exposed. Reaching this means something the contract requires is missing
|
|
21
|
+
* or has the wrong type.
|
|
22
|
+
*/
|
|
23
|
+
export declare class ResourceContractError extends RuntimeError {
|
|
24
|
+
/** What failed, so a log says which field rather than "validation failed". */
|
|
25
|
+
readonly issues: ContractIssue[];
|
|
26
|
+
/**
|
|
27
|
+
* @param message - What went wrong.
|
|
28
|
+
* @param options - Additional error options, including the issues.
|
|
29
|
+
*/
|
|
30
|
+
constructor(message: string, options?: ResourceContractErrorOptions);
|
|
31
|
+
}
|
package/dist/helpers.d.ts
CHANGED
|
@@ -32,13 +32,26 @@ export declare function except<T extends ResourceOutput>(object: T, keys: string
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function applyFields<T extends ResourceOutput>(output: T, fields?: string[]): Partial<T>;
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* Build a {@link ResourceContext} from an incoming event.
|
|
36
|
+
*
|
|
37
|
+
* The parameter names are configuration, not convention: an API that already answers `?view=` or
|
|
38
|
+
* `?only=` keeps its own vocabulary instead of gaining a second one. Defaults are `fields`, `include`
|
|
39
|
+
* and `view`.
|
|
40
|
+
*
|
|
41
|
+
* The authenticated principal is read too, because deciding what a caller may see is the most common
|
|
42
|
+
* reason two callers get different shapes — and a resource that cannot see who is asking has to be
|
|
43
|
+
* told by the handler, which is exactly the plumbing this module exists to remove.
|
|
44
|
+
*
|
|
45
|
+
* Agnostic: the event only needs `get(key)`.
|
|
37
46
|
*
|
|
38
47
|
* @param event - Anything with `get(key)` (an `IncomingHttpEvent`, a URL search wrapper, …).
|
|
48
|
+
* @param blueprint - The blueprint carrying the parameter names, when there is one.
|
|
39
49
|
* @param extra - Extra context to merge in.
|
|
40
50
|
* @returns The resource context.
|
|
41
51
|
*/
|
|
42
52
|
export declare function contextFromEvent(event: {
|
|
43
53
|
get: <T>(key: string, fallback?: T) => T;
|
|
54
|
+
getUser?: <T>() => T;
|
|
55
|
+
}, blueprint?: {
|
|
56
|
+
get: <T>(key: string, fallback?: T) => T;
|
|
44
57
|
}, extra?: ResourceContext): ResourceContext;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './ContractChecker.js';
|
|
1
2
|
export * from './Resource.js';
|
|
2
3
|
export * from './declarations.js';
|
|
3
4
|
export * from './decorators/ApiResource.js';
|
|
@@ -5,6 +6,7 @@ export * from './decorators/Resources.js';
|
|
|
5
6
|
export * from './decorators/Returns.js';
|
|
6
7
|
export * from './decorators/constants.js';
|
|
7
8
|
export * from './defineResource.js';
|
|
9
|
+
export * from './errors/ResourceContractError.js';
|
|
8
10
|
export * from './helpers.js';
|
|
9
11
|
export * from './middleware/BlueprintMiddleware.js';
|
|
10
12
|
export * from './middleware/ResourceRouteMiddleware.js';
|