@cleverbrush/server 4.0.0 → 4.2.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/README.md +72 -0
- package/dist/CacheTag.d.ts +76 -0
- package/dist/Endpoint.d.ts +173 -39
- package/dist/Server.d.ts +1 -0
- package/dist/Subscription.d.ts +7 -0
- package/dist/chunk-BNRQFILU.js +2 -0
- package/dist/chunk-BNRQFILU.js.map +1 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/middlewares/Idempotency.d.ts +52 -0
- package/dist/middlewares/ResponseCache.d.ts +50 -0
- package/dist/types.d.ts +45 -0
- package/package.json +6 -4
- package/dist/chunk-RQOGR2EW.js +0 -2
- package/dist/chunk-RQOGR2EW.js.map +0 -1
package/README.md
CHANGED
|
@@ -124,6 +124,36 @@ const CreateUser = endpoint
|
|
|
124
124
|
.operationId('createUser');
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
### Cache Tags
|
|
128
|
+
|
|
129
|
+
Tag-based cache invalidation. Tags declared on endpoints flow to the
|
|
130
|
+
[`cacheTags` middleware](/client/cache-tags) for automatic HTTP caching and
|
|
131
|
+
invalidation on mutating requests.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const ListTodos = endpoint
|
|
135
|
+
.get('/api/todos')
|
|
136
|
+
.query(TodoListQuerySchema)
|
|
137
|
+
.cacheTag('todo-list', p => ({ page: p.query.page, limit: p.query.limit }))
|
|
138
|
+
.returns(array(TodoSchema));
|
|
139
|
+
|
|
140
|
+
const UpdateTodo = endpoint
|
|
141
|
+
.patch('/api/todos/:id')
|
|
142
|
+
.body(UpdateTodoBody)
|
|
143
|
+
.clearsCacheTag('todo-list') // clears the collection cache
|
|
144
|
+
.clearsCacheTag('todo', p => ({ id: p.params.id })) // clears specific entity
|
|
145
|
+
.returns(TodoSchema);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- **`.cacheTag(name)`** — declares the endpoint's data belongs to a cache
|
|
149
|
+
group. Use on GET endpoints.
|
|
150
|
+
- **`.clearsCacheTag(name)`** — declares that this mutation clears matching
|
|
151
|
+
cache entries on success. Use on POST / PUT / PATCH / DELETE.
|
|
152
|
+
- **`.cacheTag(name, p => ({ ... }))`** — property-based tag; each selected
|
|
153
|
+
property becomes part of the cache key (different pages → different entries).
|
|
154
|
+
- **Immutability** — both methods return a new builder; the original is
|
|
155
|
+
unchanged.
|
|
156
|
+
|
|
127
157
|
## Registering and Handling Endpoints
|
|
128
158
|
|
|
129
159
|
```ts
|
|
@@ -155,6 +185,48 @@ await server.listen(3000);
|
|
|
155
185
|
| `ActionResult.stream(readable, contentType)` | 200 | Pipes a `Readable` |
|
|
156
186
|
| `ActionResult.status(status)` | any | Bare status, no body |
|
|
157
187
|
|
|
188
|
+
## File Upload
|
|
189
|
+
|
|
190
|
+
Accept file uploads via `multipart/form-data` by chaining `.upload()` on an endpoint:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { endpoint } from '@cleverbrush/server';
|
|
194
|
+
import { object, string } from '@cleverbrush/schema';
|
|
195
|
+
|
|
196
|
+
const UploadAvatar = endpoint
|
|
197
|
+
.post('/api/avatar')
|
|
198
|
+
.upload({ maxFileSize: 2 * 1024 * 1024, allowedMimeTypes: ['image/*'] })
|
|
199
|
+
.body(object({ description: string().optional() }))
|
|
200
|
+
.authorize(UserPrincipal);
|
|
201
|
+
|
|
202
|
+
const handler: Handler<typeof UploadAvatar> = async ({ body, files }) => {
|
|
203
|
+
const avatar = files['avatar'];
|
|
204
|
+
// avatar: FilePart { filename, mimeType, buffer, size }
|
|
205
|
+
return ActionResult.created({ name: avatar.filename });
|
|
206
|
+
};
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The `files` object on the handler context contains one `FilePart` entry per uploaded file field. Non-file form fields are validated against the body schema and available via `body`.
|
|
210
|
+
|
|
211
|
+
### Options
|
|
212
|
+
|
|
213
|
+
| Option | Type | Default | Description |
|
|
214
|
+
|--------|------|---------|-------------|
|
|
215
|
+
| `maxFileSize` | `number` | 10 MB | Maximum file size per file in bytes |
|
|
216
|
+
| `allowedMimeTypes` | `string[]` | all | MIME type allowlist (supports `image/*` glob) |
|
|
217
|
+
| `maxFileCount` | `number` | 10 | Maximum number of files per request |
|
|
218
|
+
|
|
219
|
+
### FilePart type
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
interface FilePart {
|
|
223
|
+
readonly filename: string;
|
|
224
|
+
readonly mimeType: string;
|
|
225
|
+
readonly buffer: Buffer;
|
|
226
|
+
readonly size: number;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
158
230
|
## Middleware
|
|
159
231
|
|
|
160
232
|
```ts
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type ObjectSchemaBuilder, type SchemaBuilder } from '@cleverbrush/schema';
|
|
2
|
+
/**
|
|
3
|
+
* An accessor that can extract a property value from a structured
|
|
4
|
+
* request root. Wraps a {@link PropertyDescriptor}'s `getValue`
|
|
5
|
+
* closure so the middleware layer does not need to know about schemas.
|
|
6
|
+
*/
|
|
7
|
+
export interface CacheTagPropertyAccessor {
|
|
8
|
+
getValue(root: {
|
|
9
|
+
params: Record<string, unknown>;
|
|
10
|
+
body: unknown;
|
|
11
|
+
query: Record<string, unknown>;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
}): {
|
|
14
|
+
value?: unknown;
|
|
15
|
+
success: boolean;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A serialisable cache-tag definition stored on endpoint metadata.
|
|
20
|
+
*
|
|
21
|
+
* `properties` maps human-readable key names (used as label segments
|
|
22
|
+
* in the final cache key) to accessors that resolve the actual value
|
|
23
|
+
* from call-time request data.
|
|
24
|
+
*/
|
|
25
|
+
export interface CacheTagDefinition {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly properties: Readonly<Record<string, CacheTagPropertyAccessor>>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds a synthetic `object({ params, body, query, headers })` schema
|
|
31
|
+
* from the endpoint's schema definitions and returns its
|
|
32
|
+
* `PropertyDescriptorTree` so callers can write type-safe selectors like:
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* endpoint.cacheTag('todo', p => ({
|
|
36
|
+
* id: p.query.id,
|
|
37
|
+
* fromBodyId: p.body.id
|
|
38
|
+
* }))
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Only non-null schemas are included in the synthetic schema.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createCacheTagTree(schemas: {
|
|
44
|
+
paramsSchema?: SchemaBuilder<any, any, any, any, any> | null;
|
|
45
|
+
bodySchema?: SchemaBuilder<any, any, any, any, any> | null;
|
|
46
|
+
querySchema?: ObjectSchemaBuilder<any, any, any, any, any, any, any> | null;
|
|
47
|
+
headerSchema?: ObjectSchemaBuilder<any, any, any, any, any, any, any> | null;
|
|
48
|
+
}): any;
|
|
49
|
+
/**
|
|
50
|
+
* Serialises the result of a cache-tag selector callback into a
|
|
51
|
+
* {@link CacheTagDefinition} that can be stored on endpoint metadata
|
|
52
|
+
* and forwarded to the client middleware.
|
|
53
|
+
*
|
|
54
|
+
* Each value in `descriptors` must be a {@link PropertyDescriptor} —
|
|
55
|
+
* its `getValue` closure is wrapped in a {@link CacheTagPropertyAccessor}.
|
|
56
|
+
*
|
|
57
|
+
* @throws If any value is not a valid property descriptor.
|
|
58
|
+
*/
|
|
59
|
+
export declare function serializeTag(name: string, descriptors: Record<string, unknown>): CacheTagDefinition;
|
|
60
|
+
/**
|
|
61
|
+
* Computes a deterministic cache key from a tag definition and live
|
|
62
|
+
* request data.
|
|
63
|
+
*
|
|
64
|
+
* - Simple tags (no properties) produce just the tag name.
|
|
65
|
+
* - Tags with properties produce `name:key1=val1,key2=val2` where
|
|
66
|
+
* keys are sorted alphabetically for determinism.
|
|
67
|
+
*
|
|
68
|
+
* Properties whose `getValue` returns `success: false` are skipped
|
|
69
|
+
* (their value is not included in the key).
|
|
70
|
+
*/
|
|
71
|
+
export declare function computeCacheKey(tag: CacheTagDefinition, root: {
|
|
72
|
+
params: Record<string, unknown>;
|
|
73
|
+
body: unknown;
|
|
74
|
+
query: Record<string, unknown>;
|
|
75
|
+
headers: Record<string, string>;
|
|
76
|
+
}): string;
|
package/dist/Endpoint.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { InferType, ObjectSchemaBuilder, ParseStringSchemaBuilder, PropertyDescriptorTree, SchemaBuilder } from '@cleverbrush/schema';
|
|
2
|
+
import { SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema';
|
|
2
3
|
import type { ActionResult, ContentResult, FileResult, JsonResult, NoContentResult, RedirectResult, StatusCodeResult, StreamResult } from './ActionResult.js';
|
|
4
|
+
import type { CacheTagDefinition } from './CacheTag.js';
|
|
3
5
|
import type { RequestContext } from './RequestContext.js';
|
|
4
6
|
import { type SubscriptionBuilder, type SubscriptionHandlerEntry } from './Subscription.js';
|
|
5
|
-
import type { Middleware } from './types.js';
|
|
7
|
+
import type { FilePart, Middleware, RejectedFile, UploadOptions } from './types.js';
|
|
6
8
|
type Simplify<T> = {
|
|
7
9
|
[K in keyof T]: T[K];
|
|
8
10
|
} & {};
|
|
9
11
|
type HasKeys<T> = keyof T extends never ? false : true;
|
|
10
|
-
type ActionContextParts<TParams, TBody, TQuery, THeaders, TPrincipal> = {
|
|
12
|
+
type ActionContextParts<TParams, TBody, TQuery, THeaders, TPrincipal, TUpload extends boolean> = {
|
|
11
13
|
context: RequestContext;
|
|
12
14
|
} & (HasKeys<TParams> extends true ? {
|
|
13
15
|
params: TParams;
|
|
@@ -19,14 +21,17 @@ type ActionContextParts<TParams, TBody, TQuery, THeaders, TPrincipal> = {
|
|
|
19
21
|
headers: THeaders;
|
|
20
22
|
} : {}) & (TPrincipal extends undefined ? {} : {
|
|
21
23
|
principal: TPrincipal;
|
|
22
|
-
})
|
|
24
|
+
}) & (TUpload extends true ? {
|
|
25
|
+
files: Record<string, FilePart>;
|
|
26
|
+
rejectedFiles?: RejectedFile[];
|
|
27
|
+
} : {});
|
|
23
28
|
/**
|
|
24
29
|
* The fully-typed argument object passed to endpoint handlers.
|
|
25
30
|
*
|
|
26
31
|
* The shape is inferred from the `EndpointBuilder` chain — only the keys
|
|
27
32
|
* actually configured (body, query, headers, params, principal) are present.
|
|
28
33
|
*/
|
|
29
|
-
export type ActionContext<E> = E extends EndpointBuilder<infer TParams, infer TBody, infer TQuery, infer THeaders, any, infer TPrincipal, any, any, any> ? Simplify<ActionContextParts<TParams, TBody, TQuery, THeaders, TPrincipal>> : never;
|
|
34
|
+
export type ActionContext<E> = E extends EndpointBuilder<infer TParams, infer TBody, infer TQuery, infer THeaders, any, infer TPrincipal, any, any, any, infer TUpload> ? Simplify<ActionContextParts<TParams, TBody, TQuery, THeaders, TPrincipal, TUpload>> : never;
|
|
30
35
|
type InferServices<T> = {
|
|
31
36
|
[K in keyof T]: T[K] extends SchemaBuilder<any, any, any, any, any> ? InferType<T[K]> : never;
|
|
32
37
|
};
|
|
@@ -34,13 +39,13 @@ type InferServices<T> = {
|
|
|
34
39
|
* Extracts the injected service schemas map from an `EndpointBuilder` type.
|
|
35
40
|
* Used internally by the `Handler` type to derive the `services` argument.
|
|
36
41
|
*/
|
|
37
|
-
export type ServiceSchemas<E> = E extends EndpointBuilder<any, any, any, any, infer TServices, any, any, any, any> ? TServices : {};
|
|
38
|
-
type ResponseType<E> = E extends EndpointBuilder<any, any, any, any, any, any, any, infer TResponse, any> ? TResponse extends SchemaBuilder<any, any, any, any, any> ? InferType<TResponse> : TResponse : any;
|
|
42
|
+
export type ServiceSchemas<E> = E extends EndpointBuilder<any, any, any, any, infer TServices, any, any, any, any, any> ? TServices : {};
|
|
43
|
+
type ResponseType<E> = E extends EndpointBuilder<any, any, any, any, any, any, any, infer TResponse, any, any> ? TResponse extends SchemaBuilder<any, any, any, any, any> ? InferType<TResponse> : TResponse : any;
|
|
39
44
|
/**
|
|
40
45
|
* Extracts the `TResponses` map from an `EndpointBuilder` type.
|
|
41
46
|
* `TResponses` is a `Record<number, BodyType>` inferred from `.responses()`.
|
|
42
47
|
*/
|
|
43
|
-
export type ResponsesOf<E> = E extends EndpointBuilder<any, any, any, any, any, any, any, any, infer TResponses> ? TResponses : never;
|
|
48
|
+
export type ResponsesOf<E> = E extends EndpointBuilder<any, any, any, any, any, any, any, any, infer TResponses, any> ? TResponses : never;
|
|
44
49
|
type HasResponses<E> = keyof ResponsesOf<E> extends never ? false : true;
|
|
45
50
|
/**
|
|
46
51
|
* The union of permitted return values for a handler whose endpoint
|
|
@@ -64,7 +69,7 @@ export type AllowedResponseReturn<TResponses extends Record<number, any>> = {
|
|
|
64
69
|
*/
|
|
65
70
|
type HandlerReturn<E> = HasResponses<E> extends true ? AllowedResponseReturn<ResponsesOf<E>> : ResponseType<E> | ActionResult;
|
|
66
71
|
export type Handler<E> = HasKeys<ServiceSchemas<E>> extends true ? (arg: ActionContext<E>, services: Simplify<InferServices<ServiceSchemas<E>>>) => HandlerReturn<E> | Promise<HandlerReturn<E>> : (arg: ActionContext<E>) => HandlerReturn<E> | Promise<HandlerReturn<E>>;
|
|
67
|
-
type AnyEndpoint = EndpointBuilder<any, any, any, any, any, any, any, any, any>;
|
|
72
|
+
type AnyEndpoint = EndpointBuilder<any, any, any, any, any, any, any, any, any, any>;
|
|
68
73
|
type AnySubscriptionBuilder = SubscriptionBuilder<any, any, any, any, any, any, any, any>;
|
|
69
74
|
/**
|
|
70
75
|
* A single handler entry in a {@link HandlerMap}.
|
|
@@ -298,6 +303,17 @@ export interface EndpointMetadata {
|
|
|
298
303
|
* OpenAPI Operation Object.
|
|
299
304
|
*/
|
|
300
305
|
readonly callbacks: Record<string, CallbackDefinition> | null;
|
|
306
|
+
/**
|
|
307
|
+
* When set, the endpoint accepts `multipart/form-data` uploads.
|
|
308
|
+
* The configuration controls max file size, allowed MIME types, etc.
|
|
309
|
+
* @see `EndpointBuilder.upload()`
|
|
310
|
+
*/
|
|
311
|
+
readonly fileUpload: UploadOptions | null;
|
|
312
|
+
/**
|
|
313
|
+
* Cache tags declared via `.clearsCacheTag()`, providing tag-based cache
|
|
314
|
+
* key computation for the client middleware.
|
|
315
|
+
*/
|
|
316
|
+
readonly cacheTags: readonly CacheTagDefinition[];
|
|
301
317
|
}
|
|
302
318
|
/**
|
|
303
319
|
* Immutable, fluent builder for HTTP endpoint definitions.
|
|
@@ -324,7 +340,37 @@ export interface EndpointMetadata {
|
|
|
324
340
|
type InferResponsesMap<T extends Record<number, SchemaBuilder<any, any, any, any, any> | null>> = {
|
|
325
341
|
[K in keyof T]: T[K] extends SchemaBuilder<any, any, any, any, any> ? InferType<T[K]> : null;
|
|
326
342
|
};
|
|
327
|
-
|
|
343
|
+
/**
|
|
344
|
+
* A leaf node in a cache-tag property tree — mirrors the shape of the
|
|
345
|
+
* actual runtime {@link PropertyDescriptor} so the compiler accepts
|
|
346
|
+
* values selected by the consumer.
|
|
347
|
+
*/
|
|
348
|
+
interface CacheTagPropertyLeaf {
|
|
349
|
+
readonly [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {
|
|
350
|
+
readonly getValue: (obj: Record<string, unknown>) => {
|
|
351
|
+
readonly value?: unknown;
|
|
352
|
+
readonly success: boolean;
|
|
353
|
+
};
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/** Recursively builds a typed property tree from an inferred object shape. */
|
|
357
|
+
type CacheTagPropertyTree<T> = CacheTagPropertyLeaf & (T extends Record<string, unknown> ? {
|
|
358
|
+
readonly [K in keyof T]-?: CacheTagPropertyTree<T[K]>;
|
|
359
|
+
} : unknown);
|
|
360
|
+
/**
|
|
361
|
+
* The typed tree passed to the `.clearsCacheTag(name, selector)` callback.
|
|
362
|
+
*
|
|
363
|
+
* `p.params`, `p.query`, and `p.headers` provide IDE completion for
|
|
364
|
+
* each schema's property names, while `p.body` resolves through the
|
|
365
|
+
* body schema's `InferType`.
|
|
366
|
+
*/
|
|
367
|
+
type CacheTagSelector<TParams, TBody, TQuery, THeaders> = {
|
|
368
|
+
readonly params: [keyof TParams] extends [never] ? Record<string, never> : TParams extends Record<string, unknown> ? CacheTagPropertyTree<TParams> : Record<string, never>;
|
|
369
|
+
readonly body: TBody extends undefined ? undefined : TBody extends SchemaBuilder<any, any, any, any, any> ? CacheTagPropertyTree<InferType<TBody>> : Record<string, never>;
|
|
370
|
+
readonly query: [keyof TQuery] extends [never] ? Record<string, never> : TQuery extends Record<string, unknown> ? CacheTagPropertyTree<TQuery> : Record<string, never>;
|
|
371
|
+
readonly headers: [keyof THeaders] extends [never] ? Record<string, never> : THeaders extends Record<string, unknown> ? CacheTagPropertyTree<THeaders> : Record<string, never>;
|
|
372
|
+
};
|
|
373
|
+
export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {}, THeaders = {}, TServices = {}, TPrincipal = undefined, TRoles extends string = string, TResponse = any, TResponses extends Record<number, any> = {}, TUpload extends boolean = false> {
|
|
328
374
|
#private;
|
|
329
375
|
constructor(method: string, basePath: string, pathTemplate: RoutePath, bodySchema: SchemaBuilder<any, any, any, any, any> | null, querySchema: ObjectSchemaBuilder<any, any, any, any, any, any, any> | null, headerSchema: ObjectSchemaBuilder<any, any, any, any, any, any, any> | null, serviceSchemas?: Record<string, SchemaBuilder<any, any, any, any, any>> | null, authRoles?: readonly string[] | null, summary?: string | null, description?: string | null, tags?: readonly string[], operationId?: string | null, deprecated?: boolean, responseSchema?: SchemaBuilder<any, any, any, any, any> | null, responsesSchemas?: Record<number, SchemaBuilder<any, any, any, any, any> | null> | null, example?: unknown | null, examples?: Record<string, {
|
|
330
376
|
summary?: string;
|
|
@@ -338,15 +384,15 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
338
384
|
}> | null, responseHeaderSchema?: ObjectSchemaBuilder<any, any, any, any, any, any, any> | null, externalDocs?: {
|
|
339
385
|
url: string;
|
|
340
386
|
description?: string;
|
|
341
|
-
} | null, links?: Record<string, LinkDefinition> | null, callbacks?: Record<string, CallbackDefinition> | null);
|
|
387
|
+
} | null, links?: Record<string, LinkDefinition> | null, callbacks?: Record<string, CallbackDefinition> | null, fileUpload?: UploadOptions | null, cacheTags?: readonly CacheTagDefinition[]);
|
|
342
388
|
/** Define the request body schema. Validation failures return 422 Problem Details. */
|
|
343
|
-
body<TSchema extends SchemaBuilder<any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TSchema, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
389
|
+
body<TSchema extends SchemaBuilder<any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TSchema, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
344
390
|
/** Define the query string schema (must be an object schema). Validation failures return 422. */
|
|
345
|
-
query<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, InferType<TSchema>, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
391
|
+
query<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, InferType<TSchema>, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
346
392
|
/** Define an expected request headers schema (must be an object schema). */
|
|
347
|
-
headers<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, TQuery, InferType<TSchema>, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
393
|
+
headers<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, TQuery, InferType<TSchema>, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
348
394
|
/** Declare DI services to be resolved per-request and passed as the second handler argument. */
|
|
349
|
-
inject<TSchemas extends Record<string, SchemaBuilder<any, any, any, any, any>>>(schemas: TSchemas): EndpointBuilder<TParams, TBody, TQuery, THeaders, TSchemas, TPrincipal, TRoles, TResponse, TResponses>;
|
|
395
|
+
inject<TSchemas extends Record<string, SchemaBuilder<any, any, any, any, any>>>(schemas: TSchemas): EndpointBuilder<TParams, TBody, TQuery, THeaders, TSchemas, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
350
396
|
/**
|
|
351
397
|
* Mark this endpoint as requiring authorization.
|
|
352
398
|
*
|
|
@@ -356,8 +402,16 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
356
402
|
*
|
|
357
403
|
* If no roles are specified, any authenticated user is allowed.
|
|
358
404
|
*/
|
|
359
|
-
authorize<TSchema extends SchemaBuilder<any, any, any, any, any>>(principalSchema: TSchema, ...roles: TRoles[]): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, InferType<TSchema>, TRoles, TResponse, TResponses>;
|
|
360
|
-
authorize(...roles: TRoles[]): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, unknown, TRoles, TResponse, TResponses>;
|
|
405
|
+
authorize<TSchema extends SchemaBuilder<any, any, any, any, any>>(principalSchema: TSchema, ...roles: TRoles[]): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, InferType<TSchema>, TRoles, TResponse, TResponses, TUpload>;
|
|
406
|
+
authorize(...roles: TRoles[]): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, unknown, TRoles, TResponse, TResponses, TUpload>;
|
|
407
|
+
/**
|
|
408
|
+
* Mark this endpoint as public — no authentication required.
|
|
409
|
+
*
|
|
410
|
+
* Calling `.public()` sets `authRoles` to `null`, overriding any
|
|
411
|
+
* previously set authorization requirements (from `.authorize()` or
|
|
412
|
+
* an inherited scoped factory).
|
|
413
|
+
*/
|
|
414
|
+
public(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
361
415
|
/**
|
|
362
416
|
* Declare the response type for OpenAPI spec generation.
|
|
363
417
|
*
|
|
@@ -365,8 +419,8 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
365
419
|
* - `returns<T>()` — generic type only, no runtime schema
|
|
366
420
|
* - `returns(schema)` — provides a schema for spec generation and type inference
|
|
367
421
|
*/
|
|
368
|
-
returns<T>(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, T, TResponses>;
|
|
369
|
-
returns<TSchema extends SchemaBuilder<any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TSchema, TResponses>;
|
|
422
|
+
returns<T>(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, T, TResponses, TUpload>;
|
|
423
|
+
returns<TSchema extends SchemaBuilder<any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TSchema, TResponses, TUpload>;
|
|
370
424
|
/**
|
|
371
425
|
* Declare per-status-code response schemas for OpenAPI generation and
|
|
372
426
|
* handler return-type enforcement.
|
|
@@ -389,17 +443,17 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
389
443
|
* };
|
|
390
444
|
* ```
|
|
391
445
|
*/
|
|
392
|
-
responses<const T extends Record<number, SchemaBuilder<any, any, any, any, any> | null>>(map: T): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, InferResponsesMap<T
|
|
446
|
+
responses<const T extends Record<number, SchemaBuilder<any, any, any, any, any> | null>>(map: T): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, InferResponsesMap<T>, TUpload>;
|
|
393
447
|
/** Short, human-readable summary for OpenAPI operation objects. */
|
|
394
|
-
summary(text: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
448
|
+
summary(text: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
395
449
|
/** Longer description for OpenAPI operation objects. Supports Markdown. */
|
|
396
|
-
description(text: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
450
|
+
description(text: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
397
451
|
/** OpenAPI tags grouping this operation in generated documentation. */
|
|
398
|
-
tags(...tags: string[]): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
452
|
+
tags(...tags: string[]): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
399
453
|
/** A unique, stable identifier for this operation in OpenAPI spec. */
|
|
400
|
-
operationId(id: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
454
|
+
operationId(id: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
401
455
|
/** Mark this endpoint as deprecated in OpenAPI spec output. */
|
|
402
|
-
deprecated(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
456
|
+
deprecated(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
403
457
|
/**
|
|
404
458
|
* Provide a single example value for the request body.
|
|
405
459
|
*
|
|
@@ -408,7 +462,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
408
462
|
*
|
|
409
463
|
* @param value - An example request body value.
|
|
410
464
|
*/
|
|
411
|
-
example(value: TBody): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
465
|
+
example(value: TBody): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
412
466
|
/**
|
|
413
467
|
* Provide named examples for the request body.
|
|
414
468
|
*
|
|
@@ -421,7 +475,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
421
475
|
summary?: string;
|
|
422
476
|
description?: string;
|
|
423
477
|
value: TBody;
|
|
424
|
-
}>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
478
|
+
}>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
425
479
|
/**
|
|
426
480
|
* Declare that this endpoint produces a binary file response.
|
|
427
481
|
*
|
|
@@ -431,7 +485,33 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
431
485
|
* @param contentType - MIME type (default: `'application/octet-stream'`).
|
|
432
486
|
* @param description - Optional response description for the spec.
|
|
433
487
|
*/
|
|
434
|
-
producesFile(contentType?: string, description?: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
488
|
+
producesFile(contentType?: string, description?: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
489
|
+
/**
|
|
490
|
+
* Mark this endpoint as accepting `multipart/form-data` file uploads.
|
|
491
|
+
*
|
|
492
|
+
* When set, the server parses the request body with a streaming multipart
|
|
493
|
+
* parser instead of the default JSON deserializer. File fields are made
|
|
494
|
+
* available to the handler via `arg.files` (a `Record<string, FilePart>`),
|
|
495
|
+
* while non-file form fields are validated against the body schema and
|
|
496
|
+
* available via `arg.body`.
|
|
497
|
+
*
|
|
498
|
+
* @param options - Upload configuration (max file size, allowed MIME types, etc.).
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```ts
|
|
502
|
+
* const UploadAvatar = endpoint
|
|
503
|
+
* .post('/api/avatar')
|
|
504
|
+
* .upload({ maxFileSize: 2 * 1024 * 1024, allowedMimeTypes: ['image/*'] })
|
|
505
|
+
* .authorize(PrincipalSchema)
|
|
506
|
+
* .responses({ 200: AvatarSchema });
|
|
507
|
+
*
|
|
508
|
+
* const handler: Handler<typeof UploadAvatar> = async ({ files }) => {
|
|
509
|
+
* const avatar = files['avatar'];
|
|
510
|
+
* // avatar: { filename, mimeType, buffer, size }
|
|
511
|
+
* };
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
upload(options?: UploadOptions): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, true>;
|
|
435
515
|
/**
|
|
436
516
|
* The full path for this endpoint, combining `basePath` and `pathTemplate`.
|
|
437
517
|
*
|
|
@@ -465,7 +545,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
465
545
|
*/
|
|
466
546
|
produces(contentTypes: Record<string, {
|
|
467
547
|
schema?: SchemaBuilder<any, any, any, any, any>;
|
|
468
|
-
}>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
548
|
+
}>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
469
549
|
/**
|
|
470
550
|
* Declare response headers emitted by this endpoint.
|
|
471
551
|
*
|
|
@@ -484,7 +564,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
484
564
|
* }))
|
|
485
565
|
* ```
|
|
486
566
|
*/
|
|
487
|
-
responseHeaders<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
567
|
+
responseHeaders<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
488
568
|
/**
|
|
489
569
|
* Link external documentation to this operation.
|
|
490
570
|
*
|
|
@@ -493,7 +573,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
493
573
|
* @param url - The URL to the external documentation.
|
|
494
574
|
* @param description - Optional short description of the external docs.
|
|
495
575
|
*/
|
|
496
|
-
externalDocs(url: string, description?: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
576
|
+
externalDocs(url: string, description?: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
497
577
|
/**
|
|
498
578
|
* Declare response links for OpenAPI spec generation.
|
|
499
579
|
*
|
|
@@ -514,7 +594,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
514
594
|
* })
|
|
515
595
|
* ```
|
|
516
596
|
*/
|
|
517
|
-
links(defs: Record<string, LinkDefinition<TResponse>>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
597
|
+
links(defs: Record<string, LinkDefinition<TResponse>>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
518
598
|
/**
|
|
519
599
|
* Declare callbacks for OpenAPI spec generation.
|
|
520
600
|
*
|
|
@@ -537,7 +617,51 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
|
|
|
537
617
|
* })
|
|
538
618
|
* ```
|
|
539
619
|
*/
|
|
540
|
-
callbacks(defs: Record<string, CallbackDefinition<TBody>>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
620
|
+
callbacks(defs: Record<string, CallbackDefinition<TBody>>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
|
|
621
|
+
/**
|
|
622
|
+
* Declare a cache group for this endpoint.
|
|
623
|
+
*
|
|
624
|
+
* Use on GET / query endpoints to group responses into a named cache.
|
|
625
|
+
* The client-side {@code cacheTags} middleware caches responses keyed
|
|
626
|
+
* by this tag and flushes matching entries when a mutation calls
|
|
627
|
+
* {@link clearsCacheTag}.
|
|
628
|
+
*
|
|
629
|
+
* @overload Simple tag (no properties — single cache entry).
|
|
630
|
+
* @overload Tag with property descriptors for fine-grained keys.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* // GET — responses cached under "todo" group, keyed by id
|
|
635
|
+
* endpoint.get('/api/todos/:id')
|
|
636
|
+
* .cacheTag('todo', p => ({
|
|
637
|
+
* id: p.params.id
|
|
638
|
+
* }))
|
|
639
|
+
* ```
|
|
640
|
+
*/
|
|
641
|
+
cacheTag(name: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
642
|
+
cacheTag(name: string, selector: (tree: CacheTagSelector<TParams, TBody, TQuery, THeaders>) => Record<string, unknown>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
643
|
+
/**
|
|
644
|
+
* Declare which cache groups are cleared when this mutation succeeds.
|
|
645
|
+
*
|
|
646
|
+
* Use on POST / PUT / PATCH / DELETE endpoints. When the mutation
|
|
647
|
+
* completes, the {@code cacheTags} client middleware invalidates all
|
|
648
|
+
* cache entries matching the declared tag names (prefix match).
|
|
649
|
+
*
|
|
650
|
+
* @overload Simple tag (clears all entries prefixed with the name).
|
|
651
|
+
* @overload Tag with property descriptors for targeted invalidation.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* ```ts
|
|
655
|
+
* // PATCH — clears "todo-list" and "todo:id=42" on success
|
|
656
|
+
* endpoint.patch('/api/todos/:id')
|
|
657
|
+
* .clearsCacheTag('todo-list')
|
|
658
|
+
* .clearsCacheTag('todo', p => ({
|
|
659
|
+
* id: p.params.id
|
|
660
|
+
* }))
|
|
661
|
+
* ```
|
|
662
|
+
*/
|
|
663
|
+
clearsCacheTag(name: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
664
|
+
clearsCacheTag(name: string, selector: (tree: CacheTagSelector<TParams, TBody, TQuery, THeaders>) => Record<string, unknown>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
|
|
541
665
|
}
|
|
542
666
|
/**
|
|
543
667
|
* Optional OpenAPI metadata fields accepted by `createEndpoint` / `createEndpoints`.
|
|
@@ -557,6 +681,14 @@ type ScopedEndpointFactoryMethods<TPrincipal, TRoles extends string = string> =
|
|
|
557
681
|
delete<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
558
682
|
head<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
559
683
|
options<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
684
|
+
post<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
685
|
+
put<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
686
|
+
patch<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
687
|
+
delete<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
688
|
+
head<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
689
|
+
options<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
|
|
690
|
+
/** Returns factory methods where all endpoints are public (no auth). */
|
|
691
|
+
public(): ScopedEndpointFactoryMethods<TPrincipal, TRoles>;
|
|
560
692
|
};
|
|
561
693
|
export type ScopedEndpointFactory<TRoles extends string = string> = ScopedEndpointFactoryMethods<undefined, TRoles> & {
|
|
562
694
|
/**
|
|
@@ -568,15 +700,17 @@ export type ScopedEndpointFactory<TRoles extends string = string> = ScopedEndpoi
|
|
|
568
700
|
*/
|
|
569
701
|
authorize<TSchema extends SchemaBuilder<any, any, any, any, any>>(principalSchema: TSchema, ...roles: TRoles[]): ScopedEndpointFactoryMethods<InferType<TSchema>, TRoles>;
|
|
570
702
|
authorize(...roles: TRoles[]): ScopedEndpointFactoryMethods<unknown, TRoles>;
|
|
703
|
+
/** Returns factory methods where all endpoints are public (no auth). */
|
|
704
|
+
public(): ScopedEndpointFactoryMethods<TRoles>;
|
|
571
705
|
};
|
|
572
706
|
type EndpointFactory<TRoles extends string = string> = {
|
|
573
|
-
get<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {},
|
|
574
|
-
post<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {},
|
|
575
|
-
put<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {},
|
|
576
|
-
patch<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {},
|
|
577
|
-
delete<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {},
|
|
578
|
-
head<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {},
|
|
579
|
-
options<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {},
|
|
707
|
+
get<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
|
|
708
|
+
post<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
|
|
709
|
+
put<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
|
|
710
|
+
patch<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
|
|
711
|
+
delete<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
|
|
712
|
+
head<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
|
|
713
|
+
options<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
|
|
580
714
|
resource(basePath: string): ScopedEndpointFactory<TRoles>;
|
|
581
715
|
subscription<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): SubscriptionBuilder<TParams, {}, {}, {}, undefined, TRoles>;
|
|
582
716
|
};
|
package/dist/Server.d.ts
CHANGED
package/dist/Subscription.d.ts
CHANGED
|
@@ -169,6 +169,13 @@ export declare class SubscriptionBuilder<TParams = {}, TQuery = {}, THeaders = {
|
|
|
169
169
|
*/
|
|
170
170
|
authorize<TSchema extends SchemaBuilder<any, any, any, any, any>>(principalSchema: TSchema, ...roles: TRoles[]): SubscriptionBuilder<TParams, TQuery, THeaders, TServices, InferType<TSchema>, TRoles, TIncoming, TOutgoing>;
|
|
171
171
|
authorize(...roles: TRoles[]): SubscriptionBuilder<TParams, TQuery, THeaders, TServices, unknown, TRoles, TIncoming, TOutgoing>;
|
|
172
|
+
/**
|
|
173
|
+
* Mark this subscription as public — no authentication required.
|
|
174
|
+
*
|
|
175
|
+
* Calling `.public()` sets `authRoles` to `null`, overriding any
|
|
176
|
+
* previously set authorization requirements.
|
|
177
|
+
*/
|
|
178
|
+
public(): SubscriptionBuilder<TParams, TQuery, THeaders, TServices, TPrincipal, TRoles, TIncoming, TOutgoing>;
|
|
172
179
|
/** Short, human-readable summary for documentation. */
|
|
173
180
|
summary(text: string): SubscriptionBuilder<TParams, TQuery, THeaders, TServices, TPrincipal, TRoles, TIncoming, TOutgoing>;
|
|
174
181
|
/** Longer description for documentation. Supports Markdown. */
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{object as P,SYMBOL_HAS_PROPERTIES as B,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as R}from"@cleverbrush/schema";function b(n){let e={};if(n.paramsSchema){let a=n.paramsSchema;typeof a.introspect=="function"&&a.introspect().objectSchema?e.params=a.introspect().objectSchema:a[B]===!0&&(e.params=a)}if(n.bodySchema){let a=n.bodySchema;(a[B]===!0||typeof a.introspect=="function")&&(e.body=a)}n.querySchema&&(e.query=n.querySchema),n.headerSchema&&(e.headers=n.headerSchema);let t=P(e);return P.getPropertiesFor(t)}function F(n){return n==null||typeof n!="object"?!1:typeof n[R]=="object"&&n[R]!==null}function x(n,e){let t={};for(let[a,s]of Object.entries(e)){if(!F(s))throw new Error(`Cache tag "${n}": property "${a}" is not a valid PropertyDescriptor. Make sure you select a leaf property from the tree (e.g. p.query.id, not p.query).`);let r=s[R];t[a]={getValue:i=>r.getValue(i)}}return{name:n,properties:t}}function J(n,e){let t=Object.entries(n.properties);if(t.length===0)return n.name;let a=[];for(let[s,r]of t.sort(([i],[y])=>i.localeCompare(y))){let i=r.getValue(e);i.success&&i.value!==void 0&&a.push(`${s}=${String(i.value)}`)}return a.length===0?n.name:`${n.name}:${a.join(",")}`}var f=Symbol.for("cleverbrush.tracked");function q(n,e){return{[f]:!0,id:n,data:e}}function X(n){return n!==null&&typeof n=="object"&&f in n&&n[f]===!0}var u=class n{#s;#a;#n;#i;#r;#o;#l;#t;#d;#h;#c;#u;#T;#y;constructor(e,t="/",a=null,s=null,r=null,i=null,y=null,d=null,l=null,h=null,c=[],m=null,S=!1,g=null){this.#s=e,this.#a=t,this.#n=a,this.#i=s,this.#r=r,this.#o=i,this.#l=y,this.#t=d,this.#d=l,this.#h=h,this.#c=c,this.#u=m,this.#T=S,this.#y=g}#e(e){return new n(e.basePath??this.#s,e.pathTemplate??this.#a,e.incomingSchema!==void 0?e.incomingSchema:this.#n,e.outgoingSchema!==void 0?e.outgoingSchema:this.#i,e.querySchema!==void 0?e.querySchema:this.#r,e.headerSchema!==void 0?e.headerSchema:this.#o,e.serviceSchemas!==void 0?e.serviceSchemas:this.#l,e.authRoles!==void 0?e.authRoles:this.#t,e.summary!==void 0?e.summary:this.#d,e.description!==void 0?e.description:this.#h,e.tags??this.#c,e.operationId!==void 0?e.operationId:this.#u,e.deprecated??this.#T,e.externalDocs!==void 0?e.externalDocs:this.#y)}incoming(e){return this.#e({incomingSchema:e})}outgoing(e){return this.#e({outgoingSchema:e})}query(e){return this.#e({querySchema:e})}headers(e){return this.#e({headerSchema:e})}inject(e){return this.#e({serviceSchemas:e})}authorize(...e){let t;e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?t=e.slice(1):t=e;let a=this.#t?[...this.#t,...t]:t;return this.#e({authRoles:a})}public(){return this.#e({authRoles:null})}summary(e){return this.#e({summary:e})}description(e){return this.#e({description:e})}tags(...e){return this.#e({tags:e})}operationId(e){return this.#e({operationId:e})}deprecated(){return this.#e({deprecated:!0})}externalDocs(e,t){return this.#e({externalDocs:{url:e,description:t}})}introspect(){return{protocol:"subscription",basePath:this.#s,pathTemplate:this.#a,incomingSchema:this.#n,outgoingSchema:this.#i,querySchema:this.#r,headerSchema:this.#o,serviceSchemas:this.#l,authRoles:this.#t,summary:this.#d,description:this.#h,tags:this.#c,operationId:this.#u,deprecated:this.#T,externalDocs:this.#y}}};function E(n,e){return new u(n,e??"/")}function k(n){return n instanceof u}function ne(n,e){let t=[],a=[];for(let s of Object.keys(n)){let r=n[s],i=e[s];for(let y of Object.keys(r)){let d=r[y],l=i[y],h=typeof l=="function"?l:l.handler,c=typeof l=="function"?void 0:l.middlewares;k(d)?a.push({endpoint:d,handler:h,middlewares:c}):t.push({endpoint:d,handler:h,middlewares:c})}}return{_entries:t,_subscriptions:a}}var p=class n{#s;#a;#n;#i;#r;#o;#l;#t;#d;#h;#c;#u;#T;#y;#e;#m;#S;#g;#R;#f;#P;#B;#b;#x;#p;constructor(e,t,a,s,r,i,y=null,d=null,l=null,h=null,c=[],m=null,S=!1,g=null,w=null,j=null,C=null,v=null,A=null,I=null,Q=null,D=null,K=null,M=null,U=[]){this.#s=e,this.#a=t,this.#n=a,this.#i=s,this.#r=r,this.#o=i,this.#l=y,this.#t=d,this.#d=l,this.#h=h,this.#c=c,this.#u=m,this.#T=S,this.#y=g,this.#e=w,this.#m=j,this.#S=C,this.#g=v,this.#R=A,this.#f=I,this.#P=Q,this.#B=D,this.#b=K,this.#x=M,this.#p=U}body(e){return new n(this.#s,this.#a,this.#n,e,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}query(e){return new n(this.#s,this.#a,this.#n,this.#i,e,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}headers(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,e,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}inject(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,e,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}authorize(...e){let t;e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?t=e.slice(1):t=e;let a=this.#t?[...this.#t,...t]:t;return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,a,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}public(){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,null,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}returns(e){let t=e!=null&&typeof e=="object"&&"introspect"in e?e:null;return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,t??this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}responses(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}summary(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,e,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}description(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,e,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}tags(...e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,e,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}operationId(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,e,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}deprecated(){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,!0,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}example(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,e,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}examples(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,e,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}producesFile(e,t){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,{contentType:e,description:t},this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}upload(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,{maxFileSize:e?.maxFileSize??10*1024*1024,allowedMimeTypes:e?.allowedMimeTypes,maxFileCount:e?.maxFileCount??10},this.#p)}get path(){let e=this.#a,t=this.#n,a;if(typeof t=="string")a=t;else{let{literals:s,segments:r}=t.introspect().templateDefinition,i="";for(let y=0;y<r.length;y++)i+=s[y]+`:${r[y].path}`;i+=s[r.length]??"",a=i}return a==="/"?e||"/":e+a}introspect(){return{method:this.#s,basePath:this.#a,pathTemplate:this.#n,bodySchema:this.#i,querySchema:this.#r,headerSchema:this.#o,serviceSchemas:this.#l,authRoles:this.#t,summary:this.#d,description:this.#h,tags:this.#c,operationId:this.#u,deprecated:this.#T,responseSchema:this.#y,responsesSchemas:this.#e,example:this.#m,examples:this.#S,producesFile:this.#g,produces:this.#R,responseHeaderSchema:this.#f,externalDocs:this.#P,links:this.#B,callbacks:this.#b,fileUpload:this.#x,cacheTags:this.#p}}produces(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,e,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}responseHeaders(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,e,this.#P,this.#B,this.#b,this.#x,this.#p)}externalDocs(e,t){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,{url:e,description:t},this.#B,this.#b,this.#x,this.#p)}links(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,e,this.#b,this.#x,this.#p)}callbacks(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,e,this.#x,this.#p)}cacheTag(e,t){return this.clearsCacheTag(e,t)}clearsCacheTag(e,t){if(!t)return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,[...this.#p,{name:e,properties:{}}]);let a=G(this.#n),s=b({paramsSchema:a,bodySchema:this.#i,querySchema:this.#r,headerSchema:this.#o}),r=t(s);if(typeof r!="object"||r===null)throw new Error(`Cache tag "${e}": selector must return an object with property descriptors (e.g. { id: p.query.id }).`);let i=x(e,r);return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,[...this.#p,i])}};function o(n,e,t,a,s){return new p(n,e,t??"/",null,null,null,null,a??null,s?.summary??null,s?.description??null,s?.tags??[],s?.operationId??null,s?.deprecated??!1,null,null,null,null,null,null,null)}function T(n,e){return{get:t=>o("GET",n,t,e),post:t=>o("POST",n,t,e),put:t=>o("PUT",n,t,e),patch:t=>o("PATCH",n,t,e),delete:t=>o("DELETE",n,t,e),head:t=>o("HEAD",n,t,e),options:t=>o("OPTIONS",n,t,e),public:()=>T(n,null)}}function z(n){return{...T(n,null),authorize(...e){let t;return e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?t=e.slice(1):t=e,T(n,t)},public(){return T(n,null)}}}function G(n){if(n&&typeof n!="string"&&typeof n.introspect=="function"){let e=n.introspect();if(e.objectSchema)return e.objectSchema}return null}function L(n){return H}var H={get:(n,e)=>o("GET",n,e),post:(n,e)=>o("POST",n,e),put:(n,e)=>o("PUT",n,e),patch:(n,e)=>o("PATCH",n,e),delete:(n,e)=>o("DELETE",n,e),head:(n,e)=>o("HEAD",n,e),options:(n,e)=>o("OPTIONS",n,e),resource:z,subscription:(n,e)=>E(n,e)};import{object as Y,parseString as $}from"@cleverbrush/schema";function O(n){let e=Y(n);return((t,...a)=>$(e,s=>s(t,...a)))}function V(n,...e){return n!=null&&Array.isArray(n.raw)?O({})(n):O(n??{})}function ie(n){for(let e of Object.values(n))Object.freeze(e);return Object.freeze(n)}function re(n,e){let t={};for(let a of Object.keys(n))t[a]={...n[a]};for(let a of Object.keys(e))Object.hasOwn(t,a)?t[a]={...t[a],...e[a]}:t[a]={...e[a]};for(let a of Object.values(t))Object.freeze(a);return Object.freeze(t)}function oe(n,...e){let t={};for(let a of e)t[a]=n[a],Object.freeze(t[a]);return Object.freeze(t)}function ye(n,...e){let t=new Set(e),a={};for(let s of Object.keys(n))t.has(s)||(a[s]=n[s],Object.freeze(a[s]));return Object.freeze(a)}export{b as a,x as b,J as c,q as d,X as e,u as f,ne as g,p as h,L as i,H as j,V as k,ie as l,re as m,oe as n,ye as o};
|
|
2
|
+
//# sourceMappingURL=chunk-BNRQFILU.js.map
|