@cleverbrush/server 4.0.0 → 4.1.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 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;
@@ -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
- export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {}, THeaders = {}, TServices = {}, TPrincipal = undefined, TRoles extends string = string, TResponse = any, TResponses extends Record<number, any> = {}> {
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,8 @@ 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>;
361
407
  /**
362
408
  * Declare the response type for OpenAPI spec generation.
363
409
  *
@@ -365,8 +411,8 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
365
411
  * - `returns<T>()` — generic type only, no runtime schema
366
412
  * - `returns(schema)` — provides a schema for spec generation and type inference
367
413
  */
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>;
414
+ returns<T>(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, T, TResponses, TUpload>;
415
+ returns<TSchema extends SchemaBuilder<any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TSchema, TResponses, TUpload>;
370
416
  /**
371
417
  * Declare per-status-code response schemas for OpenAPI generation and
372
418
  * handler return-type enforcement.
@@ -389,17 +435,17 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
389
435
  * };
390
436
  * ```
391
437
  */
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>>;
438
+ 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
439
  /** Short, human-readable summary for OpenAPI operation objects. */
394
- summary(text: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
440
+ summary(text: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
395
441
  /** Longer description for OpenAPI operation objects. Supports Markdown. */
396
- description(text: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
442
+ description(text: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
397
443
  /** OpenAPI tags grouping this operation in generated documentation. */
398
- tags(...tags: string[]): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
444
+ tags(...tags: string[]): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
399
445
  /** A unique, stable identifier for this operation in OpenAPI spec. */
400
- operationId(id: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
446
+ operationId(id: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
401
447
  /** Mark this endpoint as deprecated in OpenAPI spec output. */
402
- deprecated(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
448
+ deprecated(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
403
449
  /**
404
450
  * Provide a single example value for the request body.
405
451
  *
@@ -408,7 +454,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
408
454
  *
409
455
  * @param value - An example request body value.
410
456
  */
411
- example(value: TBody): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
457
+ example(value: TBody): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
412
458
  /**
413
459
  * Provide named examples for the request body.
414
460
  *
@@ -421,7 +467,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
421
467
  summary?: string;
422
468
  description?: string;
423
469
  value: TBody;
424
- }>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
470
+ }>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
425
471
  /**
426
472
  * Declare that this endpoint produces a binary file response.
427
473
  *
@@ -431,7 +477,33 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
431
477
  * @param contentType - MIME type (default: `'application/octet-stream'`).
432
478
  * @param description - Optional response description for the spec.
433
479
  */
434
- producesFile(contentType?: string, description?: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
480
+ producesFile(contentType?: string, description?: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
481
+ /**
482
+ * Mark this endpoint as accepting `multipart/form-data` file uploads.
483
+ *
484
+ * When set, the server parses the request body with a streaming multipart
485
+ * parser instead of the default JSON deserializer. File fields are made
486
+ * available to the handler via `arg.files` (a `Record<string, FilePart>`),
487
+ * while non-file form fields are validated against the body schema and
488
+ * available via `arg.body`.
489
+ *
490
+ * @param options - Upload configuration (max file size, allowed MIME types, etc.).
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * const UploadAvatar = endpoint
495
+ * .post('/api/avatar')
496
+ * .upload({ maxFileSize: 2 * 1024 * 1024, allowedMimeTypes: ['image/*'] })
497
+ * .authorize(PrincipalSchema)
498
+ * .responses({ 200: AvatarSchema });
499
+ *
500
+ * const handler: Handler<typeof UploadAvatar> = async ({ files }) => {
501
+ * const avatar = files['avatar'];
502
+ * // avatar: { filename, mimeType, buffer, size }
503
+ * };
504
+ * ```
505
+ */
506
+ upload(options?: UploadOptions): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, true>;
435
507
  /**
436
508
  * The full path for this endpoint, combining `basePath` and `pathTemplate`.
437
509
  *
@@ -465,7 +537,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
465
537
  */
466
538
  produces(contentTypes: Record<string, {
467
539
  schema?: SchemaBuilder<any, any, any, any, any>;
468
- }>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
540
+ }>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
469
541
  /**
470
542
  * Declare response headers emitted by this endpoint.
471
543
  *
@@ -484,7 +556,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
484
556
  * }))
485
557
  * ```
486
558
  */
487
- responseHeaders<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
559
+ 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
560
  /**
489
561
  * Link external documentation to this operation.
490
562
  *
@@ -493,7 +565,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
493
565
  * @param url - The URL to the external documentation.
494
566
  * @param description - Optional short description of the external docs.
495
567
  */
496
- externalDocs(url: string, description?: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
568
+ externalDocs(url: string, description?: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
497
569
  /**
498
570
  * Declare response links for OpenAPI spec generation.
499
571
  *
@@ -514,7 +586,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
514
586
  * })
515
587
  * ```
516
588
  */
517
- links(defs: Record<string, LinkDefinition<TResponse>>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
589
+ links(defs: Record<string, LinkDefinition<TResponse>>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
518
590
  /**
519
591
  * Declare callbacks for OpenAPI spec generation.
520
592
  *
@@ -537,7 +609,51 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
537
609
  * })
538
610
  * ```
539
611
  */
540
- callbacks(defs: Record<string, CallbackDefinition<TBody>>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
612
+ callbacks(defs: Record<string, CallbackDefinition<TBody>>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
613
+ /**
614
+ * Declare a cache group for this endpoint.
615
+ *
616
+ * Use on GET / query endpoints to group responses into a named cache.
617
+ * The client-side {@code cacheTags} middleware caches responses keyed
618
+ * by this tag and flushes matching entries when a mutation calls
619
+ * {@link clearsCacheTag}.
620
+ *
621
+ * @overload Simple tag (no properties — single cache entry).
622
+ * @overload Tag with property descriptors for fine-grained keys.
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * // GET — responses cached under "todo" group, keyed by id
627
+ * endpoint.get('/api/todos/:id')
628
+ * .cacheTag('todo', p => ({
629
+ * id: p.params.id
630
+ * }))
631
+ * ```
632
+ */
633
+ cacheTag(name: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
634
+ cacheTag(name: string, selector: (tree: CacheTagSelector<TParams, TBody, TQuery, THeaders>) => Record<string, unknown>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
635
+ /**
636
+ * Declare which cache groups are cleared when this mutation succeeds.
637
+ *
638
+ * Use on POST / PUT / PATCH / DELETE endpoints. When the mutation
639
+ * completes, the {@code cacheTags} client middleware invalidates all
640
+ * cache entries matching the declared tag names (prefix match).
641
+ *
642
+ * @overload Simple tag (clears all entries prefixed with the name).
643
+ * @overload Tag with property descriptors for targeted invalidation.
644
+ *
645
+ * @example
646
+ * ```ts
647
+ * // PATCH — clears "todo-list" and "todo:id=42" on success
648
+ * endpoint.patch('/api/todos/:id')
649
+ * .clearsCacheTag('todo-list')
650
+ * .clearsCacheTag('todo', p => ({
651
+ * id: p.params.id
652
+ * }))
653
+ * ```
654
+ */
655
+ clearsCacheTag(name: string): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
656
+ clearsCacheTag(name: string, selector: (tree: CacheTagSelector<TParams, TBody, TQuery, THeaders>) => Record<string, unknown>): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses>;
541
657
  }
542
658
  /**
543
659
  * Optional OpenAPI metadata fields accepted by `createEndpoint` / `createEndpoints`.
@@ -557,6 +673,12 @@ type ScopedEndpointFactoryMethods<TPrincipal, TRoles extends string = string> =
557
673
  delete<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
558
674
  head<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
559
675
  options<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
676
+ post<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
677
+ put<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
678
+ patch<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
679
+ delete<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
680
+ head<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
681
+ options<TParams = {}>(pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams extends undefined ? {} : TParams, undefined, {}, {}, {}, TPrincipal, TRoles, any, {}>;
560
682
  };
561
683
  export type ScopedEndpointFactory<TRoles extends string = string> = ScopedEndpointFactoryMethods<undefined, TRoles> & {
562
684
  /**
@@ -570,13 +692,13 @@ export type ScopedEndpointFactory<TRoles extends string = string> = ScopedEndpoi
570
692
  authorize(...roles: TRoles[]): ScopedEndpointFactoryMethods<unknown, TRoles>;
571
693
  };
572
694
  type EndpointFactory<TRoles extends string = string> = {
573
- get<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, undefined, TRoles, any, {}>;
574
- post<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, undefined, TRoles, any, {}>;
575
- put<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, undefined, TRoles, any, {}>;
576
- patch<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, undefined, TRoles, any, {}>;
577
- delete<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, undefined, TRoles, any, {}>;
578
- head<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, undefined, TRoles, any, {}>;
579
- options<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, undefined, TRoles, any, {}>;
695
+ get<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
696
+ post<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
697
+ put<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
698
+ patch<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
699
+ delete<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
700
+ head<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
701
+ options<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): EndpointBuilder<TParams, undefined, {}, {}, {}, any, TRoles, any, {}>;
580
702
  resource(basePath: string): ScopedEndpointFactory<TRoles>;
581
703
  subscription<TParams = {}>(basePath: string, pathTemplate?: ParseStringSchemaBuilder<TParams, any, any, any, any>): SubscriptionBuilder<TParams, {}, {}, {}, undefined, TRoles>;
582
704
  };
package/dist/Server.d.ts CHANGED
@@ -46,6 +46,7 @@ export interface AuthorizationConfig {
46
46
  */
47
47
  export declare class ServerBuilder {
48
48
  #private;
49
+ constructor(options?: ServerOptions);
49
50
  /**
50
51
  * Configure the DI service collection.
51
52
  *
@@ -0,0 +1,2 @@
1
+ import{object as R,SYMBOL_HAS_PROPERTIES as P,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as g}from"@cleverbrush/schema";function B(n){let e={};if(n.paramsSchema){let t=n.paramsSchema;typeof t.introspect=="function"&&t.introspect().objectSchema?e.params=t.introspect().objectSchema:t[P]===!0&&(e.params=t)}if(n.bodySchema){let t=n.bodySchema;(t[P]===!0||typeof t.introspect=="function")&&(e.body=t)}n.querySchema&&(e.query=n.querySchema),n.headerSchema&&(e.headers=n.headerSchema);let a=R(e);return R.getPropertiesFor(a)}function F(n){return n==null||typeof n!="object"?!1:typeof n[g]=="object"&&n[g]!==null}function b(n,e){let a={};for(let[t,s]of Object.entries(e)){if(!F(s))throw new Error(`Cache tag "${n}": property "${t}" 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[g];a[t]={getValue:i=>r.getValue(i)}}return{name:n,properties:a}}function J(n,e){let a=Object.entries(n.properties);if(a.length===0)return n.name;let t=[];for(let[s,r]of a.sort(([i],[y])=>i.localeCompare(y))){let i=r.getValue(e);i.success&&i.value!==void 0&&t.push(`${s}=${String(i.value)}`)}return t.length===0?n.name:`${n.name}:${t.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;#t;#a;#i;#r;#o;#l;#n;#d;#h;#c;#u;#T;#y;constructor(e,a="/",t=null,s=null,r=null,i=null,y=null,d=null,l=null,h=null,c=[],p=null,m=!1,S=null){this.#s=e,this.#t=a,this.#a=t,this.#i=s,this.#r=r,this.#o=i,this.#l=y,this.#n=d,this.#d=l,this.#h=h,this.#c=c,this.#u=p,this.#T=m,this.#y=S}#e(e){return new n(e.basePath??this.#s,e.pathTemplate??this.#t,e.incomingSchema!==void 0?e.incomingSchema:this.#a,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.#n,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 a;e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?a=e.slice(1):a=e;let t=this.#n?[...this.#n,...a]:a;return this.#e({authRoles:t})}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,a){return this.#e({externalDocs:{url:e,description:a}})}introspect(){return{protocol:"subscription",basePath:this.#s,pathTemplate:this.#t,incomingSchema:this.#a,outgoingSchema:this.#i,querySchema:this.#r,headerSchema:this.#o,serviceSchemas:this.#l,authRoles:this.#n,summary:this.#d,description:this.#h,tags:this.#c,operationId:this.#u,deprecated:this.#T,externalDocs:this.#y}}};function x(n,e){return new u(n,e??"/")}function E(n){return n instanceof u}function ne(n,e){let a=[],t=[];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;E(d)?t.push({endpoint:d,handler:h,middlewares:c}):a.push({endpoint:d,handler:h,middlewares:c})}}return{_entries:a,_subscriptions:t}}var T=class n{#s;#t;#a;#i;#r;#o;#l;#n;#d;#h;#c;#u;#T;#y;#e;#m;#S;#g;#f;#R;#P;#B;#b;#x;#p;constructor(e,a,t,s,r,i,y=null,d=null,l=null,h=null,c=[],p=null,m=!1,S=null,w=null,j=null,C=null,v=null,A=null,I=null,D=null,Q=null,K=null,M=null,U=[]){this.#s=e,this.#t=a,this.#a=t,this.#i=s,this.#r=r,this.#o=i,this.#l=y,this.#n=d,this.#d=l,this.#h=h,this.#c=c,this.#u=p,this.#T=m,this.#y=S,this.#e=w,this.#m=j,this.#S=C,this.#g=v,this.#f=A,this.#R=I,this.#P=D,this.#B=Q,this.#b=K,this.#x=M,this.#p=U}body(e){return new n(this.#s,this.#t,this.#a,e,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}query(e){return new n(this.#s,this.#t,this.#a,this.#i,e,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}headers(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,e,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}inject(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,e,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}authorize(...e){let a;e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?a=e.slice(1):a=e;let t=this.#n?[...this.#n,...a]:a;return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}returns(e){let a=e!=null&&typeof e=="object"&&"introspect"in e?e:null;return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,a??this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}responses(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}summary(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,e,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}description(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,e,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}tags(...e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,e,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}operationId(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,e,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}deprecated(){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,!0,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}example(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,e,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}examples(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,e,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}producesFile(e,a){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,{contentType:e,description:a},this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}upload(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,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.#t,a=this.#a,t;if(typeof a=="string")t=a;else{let{literals:s,segments:r}=a.introspect().templateDefinition,i="";for(let y=0;y<r.length;y++)i+=s[y]+`:${r[y].path}`;i+=s[r.length]??"",t=i}return t==="/"?e||"/":e+t}introspect(){return{method:this.#s,basePath:this.#t,pathTemplate:this.#a,bodySchema:this.#i,querySchema:this.#r,headerSchema:this.#o,serviceSchemas:this.#l,authRoles:this.#n,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.#f,responseHeaderSchema:this.#R,externalDocs:this.#P,links:this.#B,callbacks:this.#b,fileUpload:this.#x,cacheTags:this.#p}}produces(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,e,this.#R,this.#P,this.#B,this.#b,this.#x,this.#p)}responseHeaders(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,e,this.#P,this.#B,this.#b,this.#x,this.#p)}externalDocs(e,a){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,{url:e,description:a},this.#B,this.#b,this.#x,this.#p)}links(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,e,this.#b,this.#x,this.#p)}callbacks(e){return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,e,this.#x,this.#p)}cacheTag(e,a){return this.clearsCacheTag(e,a)}clearsCacheTag(e,a){if(!a)return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,[...this.#p,{name:e,properties:{}}]);let t=G(this.#a),s=B({paramsSchema:t,bodySchema:this.#i,querySchema:this.#r,headerSchema:this.#o}),r=a(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=b(e,r);return new n(this.#s,this.#t,this.#a,this.#i,this.#r,this.#o,this.#l,this.#n,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#f,this.#R,this.#P,this.#B,this.#b,this.#x,[...this.#p,i])}};function o(n,e,a,t,s){return new T(n,e,a??"/",null,null,null,null,t??null,s?.summary??null,s?.description??null,s?.tags??[],s?.operationId??null,s?.deprecated??!1,null,null,null,null,null,null,null)}function k(n,e){return{get:a=>o("GET",n,a,e),post:a=>o("POST",n,a,e),put:a=>o("PUT",n,a,e),patch:a=>o("PATCH",n,a,e),delete:a=>o("DELETE",n,a,e),head:a=>o("HEAD",n,a,e),options:a=>o("OPTIONS",n,a,e)}}function z(n){return{...k(n,null),authorize(...e){let a;return e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?a=e.slice(1):a=e,k(n,a)}}}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)=>x(n,e)};import{object as Y,parseString as $}from"@cleverbrush/schema";function O(n){let e=Y(n);return((a,...t)=>$(e,s=>s(a,...t)))}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 a={};for(let t of Object.keys(n))a[t]={...n[t]};for(let t of Object.keys(e))Object.hasOwn(a,t)?a[t]={...a[t],...e[t]}:a[t]={...e[t]};for(let t of Object.values(a))Object.freeze(t);return Object.freeze(a)}function oe(n,...e){let a={};for(let t of e)a[t]=n[t],Object.freeze(a[t]);return Object.freeze(a)}function ye(n,...e){let a=new Set(e),t={};for(let s of Object.keys(n))a.has(s)||(t[s]=n[s],Object.freeze(t[s]));return Object.freeze(t)}export{B as a,b,J as c,q as d,X as e,u as f,ne as g,T 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-KDTWU6PS.js.map