@stone-js/resources 0.8.9 → 0.8.11

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.
@@ -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
+ }
@@ -1,62 +1,147 @@
1
- import { IResource, ResourceContext, ResourceEnvelope, ResourceOutput } from './declarations.js';
1
+ import { Promiseable } from '@stone-js/core';
2
+ import { IContractChecker } from './ContractChecker.js';
3
+ import { ContractViolationPolicy, IResource, ResourceContext, ResourceEnvelope, ResourceOutput, ResourceSchema } from './declarations.js';
2
4
  /**
3
- * Base API resource — the declarative way to shape what your domain exposes.
5
+ * Base API resource — the layer responsible for exposing data.
4
6
  *
5
- * Extend it and implement {@link Resource.toArray} to map a model to its public shape. Everything
6
- * else (sparse fieldsets, dropping conditional fields, collections, envelopes) is handled for you.
7
- * A resource is decoupled from controllers and platform-agnostic: the same resource shapes data on
8
- * the backend and on the frontend.
7
+ * A resource answers two questions, and the second is what makes it worth having. *What leaves?* —
8
+ * and *what did you promise leaves?* The promise is a schema, so the same declaration validates the
9
+ * response, documents it in the published contract, and lets a caller ask for a named subset of it.
10
+ *
11
+ * Projection is the schema's own work: what the schema does not describe is not exposed, so a field
12
+ * added to a model later — a password hash, an internal flag — cannot leak by being forgotten.
9
13
  *
10
14
  * @example
11
15
  * ```ts
12
- * class UserResource extends Resource<User> {
13
- * toArray (user: User, ctx: ResourceContext) {
14
- * return {
15
- * id: user.id,
16
- * name: user.name,
17
- * email: this.when(ctx.self === true, user.email),
18
- * posts: this.whenIncluded(ctx, 'posts', () => postResource.collection(user.posts))
19
- * }
16
+ * @ApiResource('user')
17
+ * export class UserResource extends Resource<User> {
18
+ * constructor ({ posts }: { posts: PostService }) {
19
+ * super()
20
+ * this.posts = posts
21
+ * }
22
+ *
23
+ * schema () {
24
+ * return z.object({ id: z.number(), name: z.string(), posts: z.array(z.string()).optional() })
25
+ * }
26
+ *
27
+ * fragments () {
28
+ * return { summary: z.object({ id: z.number(), name: z.string() }) }
29
+ * }
30
+ *
31
+ * async data (user: User) {
32
+ * return { ...user, posts: await this.posts.titlesOf(user.id) }
20
33
  * }
21
34
  * }
22
35
  * ```
23
36
  */
37
+ /**
38
+ * What a resource may be handed when the container builds it.
39
+ *
40
+ * Only names this module binds itself, because destructuring reads every one of them: a name nothing
41
+ * bound would throw before the resource ever exists. Configuration is not in here on purpose, it
42
+ * comes from the blueprint, which is where configuration lives.
43
+ */
44
+ export interface ResourceDependencies {
45
+ /** The reader every projection is held against, bound as `contractChecker`. */
46
+ contractChecker?: IContractChecker;
47
+ }
24
48
  export declare abstract class Resource<Model = unknown, Output extends ResourceOutput = ResourceOutput> implements IResource<Model, Output> {
49
+ protected checker: IContractChecker;
50
+ protected onViolation: ContractViolationPolicy;
25
51
  /**
26
- * Map a model to its public shape (before field filtering).
52
+ * @param dependencies - Auto-wired services.
53
+ *
54
+ * One name, bound by this module's own blueprint, so the container resolves it like any other service
55
+ * and this constructor reads it plainly. That is the whole point: a dependency read off a container
56
+ * that never bound it is not optional, it throws, which is what made every container-resolved
57
+ * resource fail. The answer was to register the checker, not to test for its presence.
58
+ *
59
+ * One name and no more, because destructuring reads each one: a resource must not have to know
60
+ * which services happen to be bound. The violation policy is configuration, and it travels with the
61
+ * request in the context, from `stone.resources.onViolation`. Substituting the dialect is a matter
62
+ * of binding `contractChecker` yourself.
63
+ */
64
+ constructor({ contractChecker }?: ResourceDependencies);
65
+ /**
66
+ * The contract: what this resource exposes.
27
67
  *
28
- * @param model - The domain model.
29
68
  * @param context - The resource context.
30
- * @returns The public shape.
69
+ * @returns The schema.
31
70
  */
32
- abstract toArray(model: Model, context: ResourceContext): Output;
71
+ abstract schema(context: ResourceContext): ResourceSchema | Promise<ResourceSchema>;
33
72
  /**
34
- * Transform one model, applying the requested sparse fieldset and dropping undefined fields.
73
+ * Project one model.
74
+ *
75
+ * The order is the design: complete the data, choose the contract the caller asked for, hold the
76
+ * result against it, then narrow. Validation happens *before* narrowing, so the promise is checked
77
+ * against everything the resource produced rather than against whatever survived a query parameter.
35
78
  *
36
79
  * @param model - The domain model.
37
80
  * @param context - The resource context.
38
- * @returns The filtered public shape.
81
+ * @returns The projected output.
82
+ * @throws {ResourceContractError} When the data breaks the contract and the policy is `throw`.
39
83
  */
40
- item(model: Model, context?: ResourceContext): Partial<Output>;
84
+ item(model: Model, context?: ResourceContext): Promise<Output>;
41
85
  /**
42
- * Transform a collection of models.
86
+ * Project a collection.
87
+ *
88
+ * Sequential rather than concurrent: `data()` may reach a database or an API, and a hundred models
89
+ * turning into a hundred simultaneous calls is a denial of service an application performs on
90
+ * itself. A resource that wants concurrency batches inside its own `data()`, where it knows the cost.
43
91
  *
44
92
  * @param models - The domain models.
45
93
  * @param context - The resource context.
46
- * @returns The transformed collection.
94
+ * @returns The projected collection.
47
95
  */
48
- collection(models: Model[], context?: ResourceContext): Array<Partial<Output>>;
96
+ collection(models: Model[], context?: ResourceContext): Promise<Output[]>;
49
97
  /**
50
- * Wrap a model or a collection in a `{ data, meta }` envelope.
98
+ * Project into a `{ data, meta }` envelope.
51
99
  *
52
100
  * @param models - A model or a collection.
53
101
  * @param context - The resource context.
54
102
  * @param meta - Optional metadata (pagination, counts, …).
55
103
  * @returns The envelope.
56
104
  */
57
- response(models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>): ResourceEnvelope<Partial<Output> | Array<Partial<Output>>>;
105
+ response(models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>): Promise<ResourceEnvelope<Output | Output[]>>;
106
+ /**
107
+ * Optional: shape or complete the model before it meets the schema.
108
+ *
109
+ * `declare`, not a field: an uninitialised class field is *defined* as `undefined` on the instance,
110
+ * which would shadow the very method a subclass wrote — the override would exist on the prototype
111
+ * and never be reached. This states the type and emits nothing.
112
+ */
113
+ data?: (model: Model, context: ResourceContext) => Promiseable<unknown>;
114
+ /**
115
+ * Named subsets a caller may ask for. Override to expose fragments.
116
+ */
117
+ fragments?: (context: ResourceContext) => Record<string, ResourceSchema> | Promise<Record<string, ResourceSchema>>;
118
+ /**
119
+ * The schema to hold this projection against: the requested fragment when the resource exposes one,
120
+ * the full contract otherwise.
121
+ *
122
+ * An unknown fragment falls back to the full contract rather than failing. A caller guessing
123
+ * `?view=nonsense` is asking a question, not attacking: answering the documented shape is more
124
+ * useful than a 500, and the fragments a resource exposes are published in the contract anyway.
125
+ *
126
+ * @param context - The resource context.
127
+ * @returns The schema.
128
+ */
129
+ protected schemaFor(context: ResourceContext): Promise<ResourceSchema>;
130
+ /**
131
+ * Hold the data against the contract, and return what the contract describes.
132
+ *
133
+ * The schema is the projection: its parsed value is the output, so a field the contract does not
134
+ * mention is not exposed, whatever the model gains later.
135
+ *
136
+ * @param data - The completed data.
137
+ * @param schema - The contract.
138
+ * @param context - The resource context.
139
+ * @returns The projected value.
140
+ * @throws {ResourceContractError} When the data breaks the contract and the policy is `throw`.
141
+ */
142
+ protected project(data: unknown, schema: ResourceSchema, context: ResourceContext): Promise<unknown>;
58
143
  /**
59
- * Include a value only when `condition` is truthy (otherwise the field is dropped).
144
+ * Include a value only when `condition` holds (otherwise the field is dropped).
60
145
  *
61
146
  * @param condition - Whether to include the value.
62
147
  * @param value - The value, or a lazy factory (only evaluated when included).
@@ -64,7 +149,7 @@ export declare abstract class Resource<Model = unknown, Output extends ResourceO
64
149
  */
65
150
  protected when<T>(condition: boolean, value: T | (() => T)): T | undefined;
66
151
  /**
67
- * Include a value only when the relation was requested via `context.include`.
152
+ * Include a value only when the relation was requested through `context.include`.
68
153
  *
69
154
  * @param context - The resource context.
70
155
  * @param name - The relation name.
@@ -1,36 +1,75 @@
1
+ import { Promiseable } from '@stone-js/core';
1
2
  /**
2
- * The context that shapes a transformation: which fields the client asked for, which relations to
3
- * include, and any extra data (the current user, the event, …) a resource may consult.
3
+ * A schema, in whatever shape the application already writes them.
4
4
  *
5
- * It is intentionally open so resources can read whatever they need while staying agnostic.
5
+ * Anything `@stone-js/validation` accepts is accepted here: a Standard Schema (Zod, Valibot, ArkType
6
+ * and others), a Zod-like `safeParse`, or a native Stone.js schema. Resources do not define a schema
7
+ * language; they use the one the application already validates its input with, so a contract is
8
+ * written once in one dialect on both sides of the boundary.
9
+ */
10
+ export type ResourceSchema = unknown;
11
+ /**
12
+ * The context a projection is given.
13
+ *
14
+ * Open on purpose: a resource reads whatever it needs, and the middleware fills in what the request
15
+ * carried. The authenticated principal is part of it, because deciding what a caller may see is the
16
+ * most common reason a projection differs between two callers.
6
17
  */
7
18
  export interface ResourceContext {
8
- /** Requested sparse fieldset — when set, the output is limited to these top-level keys. */
19
+ /** Requested sparse fieldset — narrows the output to these top-level keys. */
9
20
  fields?: string[];
10
21
  /** Requested relations to embed. */
11
22
  include?: string[];
12
- /** Anything else a resource needs (e.g. the authenticated principal). */
23
+ /** The requested fragment, when the caller asked for one by name. */
24
+ fragment?: string;
25
+ /** The authenticated principal, when the application has one. */
26
+ principal?: unknown;
27
+ /** The event being answered, for a resource that needs more than the parameters above. */
28
+ event?: unknown;
29
+ /** Anything else a resource needs. */
13
30
  [key: string]: unknown;
14
31
  }
15
32
  /** A plain, serialisable output object. */
16
33
  export type ResourceOutput = Record<string, unknown>;
17
34
  /**
18
- * A `{ data, meta }` envelope around a transformed item or collection.
35
+ * A `{ data, meta }` envelope around a projected item or collection.
19
36
  */
20
37
  export interface ResourceEnvelope<T> {
21
38
  data: T;
22
39
  meta?: Record<string, unknown>;
23
40
  }
24
41
  /**
25
- * The resource contract: transform a model (or a collection) into its public representation.
42
+ * What a resource does: turn a domain model into the shape a caller is allowed to see, and say what
43
+ * that shape is.
44
+ *
45
+ * Saying it is the point. A projection written as code answers "what does this return?" only by being
46
+ * read and trusted; a projection written as a schema answers it to a person, to `@stone-js/openapi`,
47
+ * and to the resource itself, which validates against it before anything leaves. One declaration, three
48
+ * consumers, and no way for the documentation to drift from the response.
26
49
  */
27
- export interface IResource<Model = unknown, Output extends ResourceOutput = ResourceOutput> {
28
- /** Transform one model into its public shape (before field filtering). */
29
- toArray: (model: Model, context: ResourceContext) => Output;
30
- /** Transform one model, applying sparse fieldsets and dropping undefined fields. */
31
- item: (model: Model, context?: ResourceContext) => Partial<Output>;
32
- /** Transform a collection. */
33
- collection: (models: Model[], context?: ResourceContext) => Array<Partial<Output>>;
34
- /** Wrap a model or collection in a `{ data, meta }` envelope. */
35
- response: (models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>) => ResourceEnvelope<Partial<Output> | Array<Partial<Output>>>;
50
+ export interface IResource<Model = unknown, Output = ResourceOutput> {
51
+ /** The contract: the schema every projection is validated against and documented from. */
52
+ schema: (context: ResourceContext) => ResourceSchema | Promise<ResourceSchema>;
53
+ /**
54
+ * Named subsets a caller may ask for, each with its own schema.
55
+ *
56
+ * A fragment is not a filter: it is a contract of its own, documented and validated like the full
57
+ * one. That is what makes `?view=summary` safe to expose.
58
+ */
59
+ fragments?: (context: ResourceContext) => Record<string, ResourceSchema> | Promise<Record<string, ResourceSchema>>;
60
+ /**
61
+ * Optional hook to shape or complete the model before it meets the schema.
62
+ *
63
+ * Asynchronous, and resolved from the container, so it may reach any service: fetch a relation,
64
+ * translate a label, compute a total. Whatever it returns is what the schema then validates.
65
+ */
66
+ data?: (model: Model, context: ResourceContext) => Promiseable<unknown>;
67
+ /** Project one model. */
68
+ item: (model: Model, context?: ResourceContext) => Promise<Output>;
69
+ /** Project a collection. */
70
+ collection: (models: Model[], context?: ResourceContext) => Promise<Output[]>;
71
+ /** Project into a `{ data, meta }` envelope. */
72
+ response: (models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>) => Promise<ResourceEnvelope<Output | Output[]>>;
36
73
  }
74
+ /** What to do when the data does not match the contract the resource published. */
75
+ export type ContractViolationPolicy = 'throw' | 'warn';
@@ -1,21 +1,30 @@
1
+ import { ClassType } from '@stone-js/core';
1
2
  /**
2
- * Class decorator: register a resource class under a name.
3
+ * Declare a class as an API resource.
3
4
  *
5
+ * Three statements in one, which is why nothing has to be wired by hand:
6
+ *
7
+ * 1. **It is a service.** The container builds it, as a singleton, which means its constructor is
8
+ * auto-wired like any other class: whatever it destructures is resolved for it, from the checker
9
+ * it holds its contract against to the repository its `data()` needs to complete a model. Nothing
10
+ * reads dependencies conditionally, because the container has them.
11
+ * 2. **It is reachable by name.** The alias is bound in the container as `resource:<name>`, prefixed
12
+ * on purpose: an application is free to bind its own `user` service, and a resource named `user`
13
+ * must not compete for that name.
14
+ * 3. **It activates the module.** The blueprint comes with the decorator, so a resource declared this
15
+ * way is registered and projected without a second gesture, and `resource: 'user'` on a route
16
+ * resolves to this class.
17
+ *
18
+ * @param alias - The name a route refers to it by. Defaults to the class name.
19
+ * @returns A class decorator.
20
+ *
21
+ * @example
4
22
  * ```ts
5
23
  * @ApiResource('user')
6
24
  * export class UserResource extends Resource<User> {
7
- * toArray (user: User) { return { id: user.id, name: user.name } }
25
+ * constructor (private readonly posts: PostRepository) { super() }
26
+ * schema (): unknown { return z.object({ id: z.number(), name: z.string() }) }
8
27
  * }
9
28
  * ```
10
- *
11
- * Routes and handlers then refer to it by name (`@Returns('user')`, or `{ resource: 'user' }`), so
12
- * resources live in their own files, organised however the application likes, and nothing has to be
13
- * imported at the route. The class is resolved by the container, so its constructor receives services
14
- * and `toArray` can use them: a resource that formats dates for the caller's locale needs i18n, and
15
- * this is how it gets it.
16
- *
17
- * @param alias - The name the resource is registered under. Defaults to the class name, which the
18
- * discovery middleware fills in, since it is the one holding the class.
19
- * @returns A class decorator.
20
29
  */
21
- export declare const ApiResource: (alias?: string) => ClassDecorator;
30
+ export declare const ApiResource: <T extends ClassType = ClassType>(alias?: string) => ClassDecorator;
@@ -1,17 +1,40 @@
1
+ import { Promiseable } from '@stone-js/core';
1
2
  import { Resource } from './Resource.js';
2
- import { ResourceContext, ResourceOutput } from './declarations.js';
3
+ import { IContractChecker } from './ContractChecker.js';
4
+ import { ContractViolationPolicy, ResourceContext, ResourceOutput, ResourceSchema } from './declarations.js';
3
5
  /**
4
- * The imperative/functional way to define a resource — a plain transform function instead of a
5
- * class. Returns a full {@link Resource} (so you still get `item`/`collection`/`response` and
6
- * sparse fieldsets for free).
6
+ * What an imperatively-defined resource declares.
7
7
  *
8
- * @param transform - Maps a model to its public shape.
8
+ * The same three things a class declares, so neither paradigm can do something the other cannot.
9
+ */
10
+ export interface ResourceDefinition<Model = unknown> {
11
+ /** The contract: what this resource exposes. A schema, or a function returning one. */
12
+ schema: ResourceSchema | ((context: ResourceContext) => ResourceSchema | Promise<ResourceSchema>);
13
+ /** Named subsets a caller may ask for, each with its own schema. */
14
+ fragments?: Record<string, ResourceSchema> | ((context: ResourceContext) => Record<string, ResourceSchema> | Promise<Record<string, ResourceSchema>>);
15
+ /** Optional hook to shape or complete the model before it meets the schema. */
16
+ data?: (model: Model, context: ResourceContext) => Promiseable<unknown>;
17
+ }
18
+ /**
19
+ * The imperative way to define a resource: an object instead of a class.
20
+ *
21
+ * Parity is the rule, so this declares exactly what a class declares and gets exactly what a class
22
+ * gets. It needs nothing injected: this module reads schemas with its own checker.
23
+ *
24
+ * @param definition - The schema, and optionally fragments and a `data()` hook.
25
+ * @param dependencies - Optional explicit services, for a resource used outside a request.
9
26
  * @returns A resource.
10
27
  *
11
28
  * @example
12
29
  * ```ts
13
- * const userResource = defineResource<User>((user) => ({ id: user.id, name: user.name }))
14
- * userResource.collection(users, { fields: ['id'] })
30
+ * export const userResource = defineResource<User>({
31
+ * schema: z.object({ id: z.number(), name: z.string() }),
32
+ * fragments: { summary: z.object({ id: z.number() }) },
33
+ * data: async (user) => ({ ...user, posts: await posts.titlesOf(user.id) })
34
+ * })
15
35
  * ```
16
36
  */
17
- export declare function defineResource<Model = unknown, Output extends ResourceOutput = ResourceOutput>(transform: (model: Model, context: ResourceContext) => Output): Resource<Model, Output>;
37
+ export declare function defineResource<Model = unknown, Output extends ResourceOutput = ResourceOutput>(definition: ResourceDefinition<Model>, dependencies?: {
38
+ checker?: IContractChecker;
39
+ onViolation?: ContractViolationPolicy;
40
+ }): 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
- * Builds a {@link ResourceContext} from an incoming event's `fields` and `include` query
36
- * parameters (comma-separated). Agnostic: the event only needs a `get(key)` method.
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,11 +1,13 @@
1
- export * from './Resource.js';
1
+ export * from './ContractChecker.js';
2
2
  export * from './declarations.js';
3
3
  export * from './decorators/ApiResource.js';
4
+ export * from './decorators/constants.js';
4
5
  export * from './decorators/Resources.js';
5
6
  export * from './decorators/Returns.js';
6
- export * from './decorators/constants.js';
7
7
  export * from './defineResource.js';
8
+ export * from './errors/ResourceContractError.js';
8
9
  export * from './helpers.js';
9
10
  export * from './middleware/BlueprintMiddleware.js';
10
11
  export * from './middleware/ResourceRouteMiddleware.js';
11
12
  export * from './options/ResourcesBlueprint.js';
13
+ export * from './Resource.js';