@cleverbrush/server 4.4.3 → 5.0.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.
@@ -2,14 +2,16 @@ import type { InferType, ObjectSchemaBuilder, ParseStringSchemaBuilder, Property
2
2
  import { SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema';
3
3
  import type { ActionResult, ContentResult, FileResult, JsonResult, NoContentResult, RawResult, RedirectResult, StatusCodeResult, StreamResult } from './ActionResult.js';
4
4
  import type { CacheTagDefinition } from './CacheTag.js';
5
+ import type { EndpointOptions, RuntimeEndpointOptions } from './EndpointOptions.js';
5
6
  import type { RequestContext } from './RequestContext.js';
6
7
  import { type SubscriptionBuilder, type SubscriptionHandlerEntry } from './Subscription.js';
7
8
  import type { FilePart, Middleware, RejectedFile, UploadOptions } from './types.js';
9
+ import { type UploadConfiguration, type UploadContract, type UploadFiles, type UploadSchema } from './upload.js';
8
10
  type Simplify<T> = {
9
11
  [K in keyof T]: T[K];
10
12
  } & {};
11
13
  type HasKeys<T> = keyof T extends never ? false : true;
12
- type ActionContextParts<TParams, TBody, TQuery, THeaders, TPrincipal, TUpload extends boolean> = {
14
+ type ActionContextParts<TParams, TBody, TQuery, THeaders, TPrincipal, TUpload extends UploadContract> = {
13
15
  context: RequestContext;
14
16
  } & (HasKeys<TParams> extends true ? {
15
17
  params: TParams;
@@ -21,10 +23,12 @@ type ActionContextParts<TParams, TBody, TQuery, THeaders, TPrincipal, TUpload ex
21
23
  headers: THeaders;
22
24
  } : {}) & (TPrincipal extends undefined ? {} : {
23
25
  principal: TPrincipal;
24
- }) & (TUpload extends true ? {
26
+ }) & (TUpload extends false ? {} : TUpload extends true ? {
25
27
  files: Record<string, FilePart>;
26
28
  rejectedFiles?: RejectedFile[];
27
- } : {});
29
+ } : {
30
+ files: UploadFiles<TUpload>;
31
+ });
28
32
  /**
29
33
  * The fully-typed argument object passed to endpoint handlers.
30
34
  *
@@ -77,10 +81,9 @@ type AnySubscriptionBuilder = SubscriptionBuilder<any, any, any, any, any, any,
77
81
  * Either a bare handler function or an object with a `handler` function and
78
82
  * optional per-endpoint `middlewares`.
79
83
  */
80
- export type HandlerEntry<E> = Handler<E> | {
84
+ export type HandlerEntry<E> = Handler<E> | ({
81
85
  handler: Handler<E>;
82
- middlewares?: Middleware[];
83
- };
86
+ } & EndpointOptions<E>);
84
87
  /**
85
88
  * A compile-time complete mapping from an endpoint group structure to
86
89
  * handler entries. Every endpoint key in every group must have a
@@ -108,6 +111,10 @@ export interface HandlerMapping {
108
111
  endpoint: AnyEndpoint;
109
112
  handler: (...args: any[]) => any;
110
113
  middlewares?: Middleware[];
114
+ handlerErrorsMapped?: boolean;
115
+ prepare?: RuntimeEndpointOptions['prepare'];
116
+ idempotency?: RuntimeEndpointOptions['idempotency'];
117
+ errors?: RuntimeEndpointOptions['errors'];
111
118
  }>;
112
119
  /** @internal */
113
120
  readonly _subscriptions: ReadonlyArray<{
@@ -308,12 +315,14 @@ export interface EndpointMetadata {
308
315
  * The configuration controls max file size, allowed MIME types, etc.
309
316
  * @see `EndpointBuilder.upload()`
310
317
  */
311
- readonly fileUpload: UploadOptions | null;
318
+ readonly fileUpload: UploadConfiguration | null;
312
319
  /**
313
320
  * Cache tags declared via `.clearsCacheTag()`, providing tag-based cache
314
321
  * key computation for the client middleware.
315
322
  */
316
323
  readonly cacheTags: readonly CacheTagDefinition[];
324
+ /** Opt-in to bounded mutation response replay. */
325
+ readonly idempotent?: boolean;
317
326
  }
318
327
  /**
319
328
  * Immutable, fluent builder for HTTP endpoint definitions.
@@ -370,7 +379,7 @@ type CacheTagSelector<TParams, TBody, TQuery, THeaders> = {
370
379
  readonly query: [keyof TQuery] extends [never] ? Record<string, never> : TQuery extends Record<string, unknown> ? CacheTagPropertyTree<TQuery> : Record<string, never>;
371
380
  readonly headers: [keyof THeaders] extends [never] ? Record<string, never> : THeaders extends Record<string, unknown> ? CacheTagPropertyTree<THeaders> : Record<string, never>;
372
381
  };
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> {
382
+ 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 UploadContract = false> {
374
383
  #private;
375
384
  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, {
376
385
  summary?: string;
@@ -384,7 +393,13 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
384
393
  }> | null, responseHeaderSchema?: ObjectSchemaBuilder<any, any, any, any, any, any, any> | null, externalDocs?: {
385
394
  url: string;
386
395
  description?: string;
387
- } | null, links?: Record<string, LinkDefinition> | null, callbacks?: Record<string, CallbackDefinition> | null, fileUpload?: UploadOptions | null, cacheTags?: readonly CacheTagDefinition[]);
396
+ } | null, links?: Record<string, LinkDefinition> | null, callbacks?: Record<string, CallbackDefinition> | null, fileUpload?: UploadConfiguration | null, cacheTags?: readonly CacheTagDefinition[], idempotent?: boolean);
397
+ /**
398
+ * Enable optional X-Idempotency-Key replay for this mutation. The server
399
+ * registration must provide an explicit authorization scope. Retention is
400
+ * process-local; reusing a key asserts that the input is unchanged.
401
+ */
402
+ idempotent(): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
388
403
  /** Define the request body schema. Validation failures return 422 Problem Details. */
389
404
  body<TSchema extends SchemaBuilder<any, any, any, any, any>>(schema: TSchema): EndpointBuilder<TParams, TSchema, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, TUpload>;
390
405
  /** Define the query string schema (must be an object schema). Validation failures return 422. */
@@ -491,9 +506,11 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
491
506
  *
492
507
  * When set, the server parses the request body with a streaming multipart
493
508
  * 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`.
509
+ * available via `arg.files`. A schema of file() / array(file()) fields
510
+ * infers their names, cardinality and optionality. Without a schema,
511
+ * files remain a Record<string, FilePart>. Non-file fields use arg.body.
512
+ * File-only endpoints do not require a body schema. Typed uploads fail
513
+ * before the handler if their contract is invalid; limits produce 413.
497
514
  *
498
515
  * @param options - Upload configuration (max file size, allowed MIME types, etc.).
499
516
  *
@@ -511,6 +528,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
511
528
  * };
512
529
  * ```
513
530
  */
531
+ upload<S extends UploadSchema>(schema: S, options?: UploadOptions): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, S>;
514
532
  upload(options?: UploadOptions): EndpointBuilder<TParams, TBody, TQuery, THeaders, TServices, TPrincipal, TRoles, TResponse, TResponses, true>;
515
533
  /**
516
534
  * The full path for this endpoint, combining `basePath` and `pathTemplate`.
@@ -622,7 +640,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
622
640
  * Declare a cache group for this endpoint.
623
641
  *
624
642
  * Use on GET / query endpoints to group responses into a named cache.
625
- * The client-side {@code cacheTags} middleware caches responses keyed
643
+ * The client-side `cacheTags` middleware caches responses keyed
626
644
  * by this tag and flushes matching entries when a mutation calls
627
645
  * {@link clearsCacheTag}.
628
646
  *
@@ -644,7 +662,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
644
662
  * Declare which cache groups are cleared when this mutation succeeds.
645
663
  *
646
664
  * Use on POST / PUT / PATCH / DELETE endpoints. When the mutation
647
- * completes, the {@code cacheTags} client middleware invalidates all
665
+ * completes, the `cacheTags` client middleware invalidates all
648
666
  * cache entries matching the declared tag names (prefix match).
649
667
  *
650
668
  * @overload Simple tag (clears all entries prefixed with the name).
@@ -652,7 +670,7 @@ export declare class EndpointBuilder<TParams = {}, TBody = undefined, TQuery = {
652
670
  *
653
671
  * @example
654
672
  * ```ts
655
- * // PATCH — clears "todo-list" and "todo:id=42" on success
673
+ * // PATCH — clears "todo-list" / "todo" names and their computed ct2 keys
656
674
  * endpoint.patch('/api/todos/:id')
657
675
  * .clearsCacheTag('todo-list')
658
676
  * .clearsCacheTag('todo', p => ({
@@ -0,0 +1,31 @@
1
+ import type { ActionContext, Handler, ResponsesOf } from './Endpoint.js';
2
+ import type { ErrorMap, ErrorResponsesOf } from './ErrorMap.js';
3
+ import type { Middleware } from './types.js';
4
+ /** Identity components are serialized without ambiguous string concatenation. */
5
+ export type IdempotencyScope = string | readonly (string | number)[];
6
+ /** Per-endpoint, process-local replay limits. */
7
+ export interface EndpointIdempotencyLimits {
8
+ ttl?: number;
9
+ maxEntries?: number;
10
+ maxResponseBytes?: number;
11
+ }
12
+ /** Server-only callbacks, inferred from the endpoint's validated request and DI. */
13
+ export type EndpointOptions<E> = {
14
+ middlewares?: Middleware[];
15
+ /** Runs on every request, including replays; return the request used by scope and handler. */
16
+ prepare?: (...args: Parameters<Handler<E>>) => ActionContext<E> | Promise<ActionContext<E>>;
17
+ idempotency?: EndpointIdempotencyLimits & {
18
+ scope: (...args: Parameters<Handler<E>>) => IdempotencyScope | Promise<IdempotencyScope>;
19
+ };
20
+ errors?: keyof ResponsesOf<E> extends never ? never : ErrorMap<ErrorResponsesOf<E>>;
21
+ };
22
+ /** @internal Erased callback types retained through registration/composition. */
23
+ export interface RuntimeEndpointOptions {
24
+ /** @internal Handler invocation already owns its error policy. */
25
+ handlerErrorsMapped?: boolean;
26
+ prepare?: (...args: any[]) => any;
27
+ idempotency?: EndpointIdempotencyLimits & {
28
+ scope: (...args: any[]) => IdempotencyScope | Promise<IdempotencyScope>;
29
+ };
30
+ errors?: ErrorMap<any, any>;
31
+ }
@@ -0,0 +1,90 @@
1
+ import { type ContentResult, type FileResult, JsonResult, NoContentResult, type RawResult, type RedirectResult, StatusCodeResult, type StreamResult } from './ActionResult.js';
2
+ import type { EndpointBuilder, Handler, ResponsesOf } from './Endpoint.js';
3
+ /** Explicit, contract-checkable responses supported by error policies. */
4
+ export type ErrorResponse = JsonResult<number, unknown> | NoContentResult | StatusCodeResult<number>;
5
+ /** The declared JSON and bodyless responses of an HTTP endpoint. */
6
+ export type ErrorResponsesOf<E> = {
7
+ [K in keyof ResponsesOf<E> & number]: ResponsesOf<E>[K] extends null ? K extends 204 ? NoContentResult : StatusCodeResult<K> : JsonResult<K, ResponsesOf<E>[K]>;
8
+ }[keyof ResponsesOf<E> & number];
9
+ type ErrorConstructor<E extends Error> = abstract new (...args: any[]) => E;
10
+ declare const responseTypes: unique symbol;
11
+ type ResponseSignature<R> = R extends FileResult | StreamResult | ContentResult | RedirectResult | RawResult ? {
12
+ kind: 'unsupported';
13
+ } : R extends JsonResult<infer S, infer B> ? {
14
+ kind: 'json';
15
+ status: S;
16
+ body: B;
17
+ } : R extends StatusCodeResult<infer S> ? {
18
+ kind: 'empty';
19
+ status: S;
20
+ } : R extends NoContentResult ? {
21
+ kind: 'empty';
22
+ status: 204;
23
+ } : never;
24
+ type Rule = {
25
+ matches: (error: unknown) => boolean;
26
+ translate: (error: any) => ErrorResponse | Promise<ErrorResponse>;
27
+ };
28
+ /**
29
+ * Immutable, ordered exception translators. Create one with {@link errorMap}.
30
+ *
31
+ * Policies retain their exact response union until attached to an endpoint.
32
+ * They do not change that endpoint's responses, catch infrastructure errors,
33
+ * or automatically expose exception messages.
34
+ *
35
+ * @typeParam R - Union of all responses produced by this policy.
36
+ */
37
+ export declare class ErrorMap<R extends ErrorResponse = never, Signature = ResponseSignature<R>> {
38
+ #private;
39
+ /** @internal Retains status/body discrimination across exported policies. */
40
+ readonly [responseTypes]: Signature;
41
+ /** @internal Use {@link errorMap}. */
42
+ constructor(rules?: readonly Rule[]);
43
+ /**
44
+ * Return a new policy with a translator appended. First matching rule wins.
45
+ * Put subclass rules before base-class rules. Translators may be async;
46
+ * translator failures propagate without re-entering this policy.
47
+ *
48
+ * @param errorType - Error constructor matched using `instanceof`.
49
+ * @param translate - Application-owned conversion to an explicit response.
50
+ */
51
+ on<E extends Error, T extends ErrorResponse>(errorType: ErrorConstructor<E>, translate: (error: E) => T | Promise<T>): ErrorMap<R | T, Signature | ResponseSignature<T>>;
52
+ /**
53
+ * Translate a recognized exception or rethrow the original value unchanged.
54
+ * Usually invoked by {@link withErrors}, not directly by an application.
55
+ */
56
+ translate(error: unknown): Promise<R>;
57
+ }
58
+ /**
59
+ * Create an empty, immutable error policy. Unknown exceptions are rethrown.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const errors = errorMap().on(MissingItemError, () =>
64
+ * ActionResult.notFound({ message: 'Item not found' })
65
+ * );
66
+ * ```
67
+ */
68
+ export declare function errorMap(): ErrorMap<never>;
69
+ /**
70
+ * Wrap only a handler's invocation in an endpoint-checked error policy.
71
+ *
72
+ * Requires explicit `.responses()` declarations. Every possible translated
73
+ * status/body must fit the endpoint; file/raw/stream escape hatches are not
74
+ * accepted. Request and injected-service types are unchanged. Authentication,
75
+ * validation, middleware, DI and response serialization remain outside this
76
+ * wrapper and use the server's normal error handling.
77
+ *
78
+ * @param endpoint - Configured HTTP endpoint, the sole source of handler types.
79
+ * @param policy - Reusable policy compatible with the endpoint's responses.
80
+ * @param handler - Named or inline handler to wrap.
81
+ * @returns A handler suitable for `handle`, `mapHandlers`, or implementation scopes.
82
+ */
83
+ export declare function withErrors<E extends EndpointBuilder<any, any, any, any, any, any, any, any, any, any>>(endpoint: E, policy: keyof ResponsesOf<NoInfer<E>> extends never ? never : ErrorMap<ErrorResponsesOf<NoInfer<E>>>, handler: Handler<NoInfer<E>>): Handler<E>;
84
+ /** @internal Shared registration validation. */
85
+ export declare function validateErrorMap(endpoint: {
86
+ introspect(): {
87
+ responsesSchemas: unknown;
88
+ };
89
+ }): void;
90
+ export {};
@@ -0,0 +1,193 @@
1
+ import type { InferType, SchemaBuilder } from '@cleverbrush/schema';
2
+ import type { ApiContract, ApiGroup } from './contract.js';
3
+ import { type EndpointBuilder, type Handler, type HandlerMapping } from './Endpoint.js';
4
+ import type { EndpointOptions, RuntimeEndpointOptions } from './EndpointOptions.js';
5
+ import { type SubscriptionBuilder, type SubscriptionHandlerEntry } from './Subscription.js';
6
+ import type { Middleware } from './types.js';
7
+ type AnySchema = SchemaBuilder<any, any, any, any, any>;
8
+ type Services = Record<string, AnySchema>;
9
+ type AnyEndpoint = EndpointBuilder<any, any, any, any, any, any, any, any, any, any>;
10
+ type AnySubscription = SubscriptionBuilder<any, any, any, any, any, any, any, any>;
11
+ type Definition = AnyEndpoint | AnySubscription;
12
+ type Merge<A, B> = {
13
+ -readonly [K in keyof Required<A> as K extends keyof B ? never : K]: A[K];
14
+ } & {
15
+ -readonly [K in keyof Required<B>]: B[K];
16
+ };
17
+ /** Server-only enrichment shared by every operation in a scope. */
18
+ export interface ImplementationDefaults {
19
+ /** Merged with contract services; later same-name bindings win. */
20
+ readonly inject?: Services;
21
+ /** Explicitly require authentication, retaining any existing role requirements. */
22
+ readonly authorize?: AnySchema;
23
+ /** Replace inherited documentation tags when supplied. */
24
+ readonly tags?: readonly string[];
25
+ }
26
+ /** Per-operation overrides. Wire schemas and cache metadata are not configurable here. */
27
+ export interface ImplementationOperationOptions extends ImplementationDefaults {
28
+ /** Short documentation summary. */
29
+ readonly summary?: string;
30
+ /** Longer documentation description. */
31
+ readonly description?: string;
32
+ /** Stable OpenAPI/AsyncAPI operation identifier. */
33
+ readonly operationId?: string;
34
+ /** Mark deprecated; this cannot clear an inherited deprecation. */
35
+ readonly deprecated?: true;
36
+ }
37
+ /** Typed defaults and operation-specific server settings for a contract group. */
38
+ export interface ImplementationGroupOptions<G extends ApiGroup> extends ImplementationDefaults {
39
+ /** Keys must belong to the selected group. */
40
+ readonly operations?: {
41
+ readonly [K in keyof G]?: ImplementationOperationOptions;
42
+ };
43
+ }
44
+ type Injected<O> = O extends {
45
+ readonly inject: infer I extends Services;
46
+ } ? I : {};
47
+ type Principal<P, O> = O extends {
48
+ readonly authorize: infer S extends AnySchema;
49
+ } ? InferType<S> : P;
50
+ type OperationOptions<O, K extends PropertyKey> = O extends {
51
+ readonly operations: infer Operations;
52
+ } ? K extends keyof Operations ? Operations[K] : {} : {};
53
+ type Configured<E, Defaults, Options> = E extends EndpointBuilder<infer P, infer B, infer Q, infer H, infer S, infer A, infer Roles, infer R, infer Rs, infer U> ? EndpointBuilder<P, B, Q, H, Merge<Merge<S, Injected<Defaults>>, Injected<Options>>, Principal<Principal<A, Defaults>, Options>, Roles, R, Rs, U> : E extends SubscriptionBuilder<infer P, infer Q, infer H, infer S, infer A, infer Roles, infer I, infer O> ? SubscriptionBuilder<P, Q, H, Merge<Merge<S, Injected<Defaults>>, Injected<Options>>, Principal<Principal<A, Defaults>, Options>, Roles, I, O> : never;
54
+ type ConfiguredGroup<G extends ApiGroup, O> = {
55
+ readonly [K in keyof G]: Configured<G[K], O, OperationOptions<O, K>>;
56
+ };
57
+ /** Handler binding accepted by an implementation scope. Policies apply only to HTTP handlers. */
58
+ export type ImplementationHandlerEntry<E> = E extends AnySubscription ? SubscriptionHandlerEntry<E> & {
59
+ errors?: never;
60
+ } : E extends AnyEndpoint ? Handler<E> | ({
61
+ handler: Handler<E>;
62
+ } & EndpointOptions<E>) : never;
63
+ /** Complete, endpoint-specific handler bindings for one configured scope. */
64
+ export type ImplementationHandlers<G extends ApiGroup> = {
65
+ [K in keyof G]: ImplementationHandlerEntry<G[K]>;
66
+ };
67
+ type ExactKeys<Given, Expected> = Record<Exclude<keyof Given, keyof Expected>, never>;
68
+ type ExactOperations<O, G> = O extends {
69
+ readonly operations: infer Ops;
70
+ } ? {
71
+ readonly operations: ExactKeys<Ops, G>;
72
+ } : unknown;
73
+ type Entry = RuntimeEndpointOptions & {
74
+ readonly group: string;
75
+ readonly name: string;
76
+ readonly source: Definition;
77
+ readonly endpoint: Definition;
78
+ readonly handler: (...args: any[]) => any;
79
+ readonly middlewares?: Middleware[];
80
+ };
81
+ declare const moduleContract: unique symbol;
82
+ declare const moduleEntries: unique symbol;
83
+ /**
84
+ * Exportable, immutable set of bound operations. Its type retains the original
85
+ * contract slice, not just an erased array of registrations.
86
+ * Obtain modules with `scope.withHandlers()` or `implement(api).use(...)`.
87
+ */
88
+ export declare class ImplementationModule<C extends ApiContract> {
89
+ #private;
90
+ /** @internal Compile-time contract coverage. */
91
+ readonly [moduleContract]: C;
92
+ /** @internal Construct modules through an implementation scope. */
93
+ constructor(entries: readonly Entry[]);
94
+ /** @internal */
95
+ [moduleEntries](): readonly Entry[];
96
+ }
97
+ /**
98
+ * Configured, unbound operations from a single contract group.
99
+ * Export the scope from a configuration-only module, then use
100
+ * `Handler<typeof scope.endpoints.operation>` in separate handler files.
101
+ * No handler imports or registration side effects are needed here.
102
+ */
103
+ export declare class ImplementationScope<G extends string, Source extends ApiGroup, Endpoints extends ApiGroup> {
104
+ #private;
105
+ /** @internal Use `implement(api).group(...)`. */
106
+ constructor(group: G, source: Source, endpoints: Endpoints);
107
+ /** Configured endpoint builders: the source of handler request, DI and response types. */
108
+ get endpoints(): Endpoints;
109
+ /**
110
+ * Select operations for a smaller module without losing source identity.
111
+ * The composition root still checks coverage of the full contract.
112
+ */
113
+ pick<const K extends keyof Source & keyof Endpoints & string>(...names: K[]): ImplementationScope<G, Pick<Source, K>, Pick<Endpoints, K>>;
114
+ /**
115
+ * Bind exactly one handler for every operation in this scope.
116
+ * Named handlers and inline functions receive the same endpoint typing.
117
+ * Optional descriptors attach middleware and HTTP error policies.
118
+ */
119
+ withHandlers<const H extends ImplementationHandlers<Endpoints>>(handlers: H & ExactKeys<H, Endpoints>): ImplementationModule<{
120
+ [K in G]: Source;
121
+ }>;
122
+ }
123
+ type ContractOf<M> = M extends ImplementationModule<infer C> ? C : never;
124
+ type MergeContracts<A extends ApiContract, B extends ApiContract> = {
125
+ -readonly [G in keyof Required<A & B>]: G extends keyof A ? G extends keyof B ? A[G] & B[G] : A[G] : G extends keyof B ? B[G] : never;
126
+ };
127
+ type Joined<Bound extends ApiContract, Modules extends readonly ImplementationModule<any>[]> = Modules extends readonly [
128
+ infer Head,
129
+ ...infer Tail extends readonly ImplementationModule<any>[]
130
+ ] ? Joined<MergeContracts<Bound, ContractOf<Head>>, Tail> : Bound;
131
+ type Missing<C, B> = {
132
+ [G in keyof C & string]: `${G}.${Exclude<keyof C[G], G extends keyof B ? keyof B[G] : never> & string}`;
133
+ }[keyof C & string];
134
+ type Overlap<A, B> = {
135
+ [G in keyof A & keyof B & string]: `${G}.${keyof A[G] & keyof B[G] & string}`;
136
+ }[keyof A & keyof B & string];
137
+ type Incompatible<C, S> = {
138
+ [G in keyof S & string]: G extends keyof C ? {
139
+ [K in keyof S[G] & string]: K extends keyof C[G] ? S[G][K] extends C[G][K] ? never : `${G}.${K}` : `${G}.${K}`;
140
+ }[keyof S[G] & string] : `${G}.${keyof S[G] & string}`;
141
+ }[keyof S & string];
142
+ type CheckModules<C, B extends ApiContract, M extends readonly ImplementationModule<any>[]> = number extends M['length'] ? {
143
+ readonly implementationError: 'Use a tuple of modules with known operation keys';
144
+ } : M extends readonly [
145
+ infer Head,
146
+ ...infer Tail extends readonly ImplementationModule<any>[]
147
+ ] ? [
148
+ Overlap<B, ContractOf<Head>> | Incompatible<C, ContractOf<Head>>
149
+ ] extends [never] ? CheckModules<C, MergeContracts<B, ContractOf<Head>>, Tail> : {
150
+ readonly duplicateOrIncompatibleOperations: Overlap<B, ContractOf<Head>> | Incompatible<C, ContractOf<Head>>;
151
+ } : unknown;
152
+ /**
153
+ * Immutable composition root bound to a shared contract. Compose exported
154
+ * feature modules with `use`, then pass `complete()` to `server.handleAll`.
155
+ * Partial roots are themselves modules and can be composed in larger roots.
156
+ */
157
+ export declare class ApiImplementation<C extends ApiContract, Bound extends ApiContract = {}> extends ImplementationModule<Bound> {
158
+ #private;
159
+ /** @internal Use {@link implement}. */
160
+ constructor(contract: C, entries?: readonly Entry[]);
161
+ /**
162
+ * Create an unbound scope; does not register handlers or change this root.
163
+ * Services merge contract → group → operation. Only explicitly supplied
164
+ * documentation fields override inherited values. Authorization retains roles.
165
+ */
166
+ group<K extends keyof C & string, const O extends ImplementationGroupOptions<C[K]> = {}>(name: K, options?: O & ExactOperations<O, C[K]>): ImplementationScope<K, C[K], ConfiguredGroup<C[K], O>>;
167
+ /**
168
+ * Compose modules without erasing their operation coverage. Rejects duplicate
169
+ * and incompatible bindings, including runtime source-identity mismatches.
170
+ */
171
+ use<const M extends readonly ImplementationModule<any>[]>(...modules: M & CheckModules<C, Bound, M>): ApiImplementation<C, Joined<Bound, M>>;
172
+ /**
173
+ * Finalize only when every operation in the original contract is covered.
174
+ * Rechecks coverage at runtime for JavaScript callers and erased types.
175
+ * Returns the existing registration format; the server pipeline is unchanged.
176
+ */
177
+ complete(this: [Missing<C, Bound>] extends [never] ? ApiImplementation<C, Bound> : {
178
+ readonly missingOperations: Missing<C, Bound>;
179
+ }): HandlerMapping;
180
+ }
181
+ /**
182
+ * Start an immutable implementation bound to a shared API contract.
183
+ *
184
+ * @param contract - Complete shared contract, or an intentional audience slice.
185
+ * @example
186
+ * ```ts
187
+ * const todos = implement(api).group('todos', { inject: { db: DbToken } });
188
+ * const module = todos.withHandlers({ list: listHandler, create: createHandler });
189
+ * server.handleAll(implement(api).use(module).complete());
190
+ * ```
191
+ */
192
+ export declare function implement<C extends ApiContract>(contract: C): ApiImplementation<C>;
193
+ export {};
@@ -7,7 +7,7 @@ import type { IServiceProvider } from '@cleverbrush/di';
7
7
  */
8
8
  export declare const IRequestContext: import("@cleverbrush/schema").ExtendedObject<{
9
9
  method: import("@cleverbrush/schema").ExtendedString<string>;
10
- url: import("@cleverbrush/schema").ExtendedString<string>;
10
+ url: import("@cleverbrush/schema").AnySchemaBuilder<true, false, URL, false, {}, URL>;
11
11
  pathParams: import("@cleverbrush/schema").ExtendedRecord<import("@cleverbrush/schema").ExtendedString<string>, import("@cleverbrush/schema").ExtendedString<string>>;
12
12
  queryParams: import("@cleverbrush/schema").ExtendedRecord<import("@cleverbrush/schema").ExtendedString<string>, import("@cleverbrush/schema").ExtendedString<string>>;
13
13
  headers: import("@cleverbrush/schema").ExtendedRecord<import("@cleverbrush/schema").ExtendedString<string>, import("@cleverbrush/schema").ExtendedString<string>>;
@@ -60,7 +60,7 @@ export declare class RequestContext {
60
60
  /** The DI service provider scoped to this request. Set by the server before invoking the handler. */
61
61
  get services(): IServiceProvider | undefined;
62
62
  set services(value: IServiceProvider);
63
- /** Read and buffer the raw request body. Result is cached after the first call. */
63
+ /** Read once; concurrent readers share the same result or failure. */
64
64
  body(): Promise<Buffer>;
65
65
  /** Read, buffer, and JSON-parse the request body. Result is cached after the first call. */
66
66
  json(): Promise<unknown>;
package/dist/Server.d.ts CHANGED
@@ -2,7 +2,9 @@ import type { AuthenticationScheme } from '@cleverbrush/auth';
2
2
  import { PolicyBuilder } from '@cleverbrush/auth';
3
3
  import { ServiceCollection, type ServiceProvider } from '@cleverbrush/di';
4
4
  import { ContentNegotiator } from './ContentNegotiator.js';
5
+ import { type ServerCorsOptions } from './Cors.js';
5
6
  import type { EndpointBuilder, Handler, HandlerMapping } from './Endpoint.js';
7
+ import type { EndpointOptions } from './EndpointOptions.js';
6
8
  import { Router } from './Router.js';
7
9
  import type { ContentTypeHandler, EndpointRegistration, Middleware, ServerBatchingOptions, ServerOptions, SubscriptionRegistration } from './types.js';
8
10
  import type { WebhookDefinition } from './Webhook.js';
@@ -62,10 +64,18 @@ export declare class ServerBuilder {
62
64
  */
63
65
  services(configureFn: (svc: ServiceCollection) => void): this;
64
66
  /**
65
- * Add a global middleware that runs for every request.
66
- * Middleware is executed in the order it is added.
67
+ * Add middleware to the matched-request pipeline after authentication.
68
+ * Middleware is executed in the order it is added. CORS short-circuits
69
+ * and unmatched routes do not enter this pipeline.
67
70
  */
68
71
  use(middleware: Middleware): this;
72
+ /**
73
+ * Enable server-wide CORS before routing and authentication.
74
+ * Accepted preflights return 204 without running ordinary middleware.
75
+ * Requests with disallowed origins return 403 before handlers run.
76
+ * Configuration is validated and copied when the server starts listening.
77
+ */
78
+ useCors(options: ServerCorsOptions): this;
69
79
  /**
70
80
  * Register an additional content type handler for content negotiation.
71
81
  * JSON is registered by default.
@@ -73,8 +83,8 @@ export declare class ServerBuilder {
73
83
  contentType(handler: ContentTypeHandler): this;
74
84
  /**
75
85
  * Enable authentication with one or more schemes.
76
- * Registers a global middleware that authenticates every request and
77
- * sets `ctx.principal`.
86
+ * Registers middleware that authenticates protected requests and sets
87
+ * `ctx.principal`. CORS preflights are handled before this middleware.
78
88
  */
79
89
  useAuthentication(config: AuthenticationConfig): this;
80
90
  /**
@@ -88,7 +98,7 @@ export declare class ServerBuilder {
88
98
  * Enable the `GET /health` endpoint that returns `{ ok: true }` (200).
89
99
  * Useful for load balancer and container readiness probes.
90
100
  *
91
- * The path is available as {@link HEALTHCHECK_PATH}.
101
+ * The path is available as `HEALTHCHECK_PATH`.
92
102
  */
93
103
  withHealthcheck(): this;
94
104
  /**
@@ -120,9 +130,7 @@ export declare class ServerBuilder {
120
130
  * @param handler - The typed handler function.
121
131
  * @param options - Optional per-endpoint middleware.
122
132
  */
123
- handle<E extends EndpointBuilder<any, any, any, any, any, any, any, any, any>>(endpointDef: E, handler: Handler<E>, options?: {
124
- middlewares?: Middleware[];
125
- }): this;
133
+ handle<E extends EndpointBuilder<any, any, any, any, any, any, any, any, any>>(endpointDef: E, handler: Handler<E>, options?: EndpointOptions<E>): this;
126
134
  /**
127
135
  * Register all endpoints from a {@link HandlerMapping} created by
128
136
  * {@link mapHandlers}. This is the bulk equivalent of calling
@@ -171,7 +179,7 @@ export declare class ServerBuilder {
171
179
  }
172
180
  export declare class Server {
173
181
  #private;
174
- constructor(router: Router, serviceProvider: ServiceProvider, contentNegotiator: ContentNegotiator, globalMiddlewares: Middleware[], healthcheck?: boolean, hasSubscriptions?: boolean, batchConfig?: ServerBatchingOptions | null, maxBodySize?: number);
182
+ constructor(router: Router, serviceProvider: ServiceProvider, contentNegotiator: ContentNegotiator, globalMiddlewares: Middleware[], healthcheck?: boolean, hasSubscriptions?: boolean, batchConfig?: ServerBatchingOptions | null, maxBodySize?: number, corsOptions?: ServerCorsOptions);
175
183
  /**
176
184
  * Start listening. Called internally by `ServerBuilder.listen()` after
177
185
  * the server is fully configured.
@@ -81,6 +81,8 @@ export declare class VirtualServerResponse extends Writable {
81
81
  * Mirrors `ServerResponse.getHeader()`.
82
82
  */
83
83
  getHeader(name: string): string | undefined;
84
+ /** Return a snapshot of response headers, matching ServerResponse. */
85
+ getHeaders(): Record<string, string>;
84
86
  /**
85
87
  * Captures the body and signals the end of the response.
86
88
  *
@@ -0,0 +1,17 @@
1
+ import type { CacheTagDefinition } from './CacheTag.js';
2
+ type CacheRoot = Parameters<CacheTagDefinition['properties'][string]['getValue']>[0];
3
+ /**
4
+ * Compute a versioned, deterministic key from a tag and selected request values.
5
+ *
6
+ * Format: `ct2:` followed by JSON `[tagName, [[property, encodedValue], ...]]`.
7
+ * Properties and object keys are sorted by code units. Values are type-tagged;
8
+ * Dates retain millisecond precision. Missing/undefined selected properties are
9
+ * omitted. Even property-free tags use this format; base invalidation labels
10
+ * remain the original names. All cache writers/invalidators must use the same
11
+ * version and retire old entries when upgrading.
12
+ *
13
+ * @throws TypeError for cyclic values, invalid dates, functions, symbols,
14
+ * accessor properties, or objects other than arrays, dates and plain objects.
15
+ */
16
+ export declare function computeCacheKey(tag: CacheTagDefinition, root: CacheRoot): string;
17
+ export {};
@@ -0,0 +1,2 @@
1
+ function f(n,e){if(n===null)return["null"];switch(typeof n){case"undefined":return["undefined"];case"string":case"boolean":return[typeof n,n];case"number":return["number",Object.is(n,-0)?"-0":String(n)];case"bigint":return["bigint",String(n)];case"object":{if(e.has(n))throw new TypeError("Cache tag values must not contain cycles.");if(n instanceof Date){if(!Number.isFinite(n.getTime()))throw new TypeError("Cache tag dates must be valid.");return["date",n.toISOString()]}let t=Object.getPrototypeOf(n);if(!Array.isArray(n)&&t!==Object.prototype&&t!==null)throw new TypeError("Unsupported cache tag object.");if(Object.getOwnPropertySymbols(n).length>0)throw new TypeError("Cache tag values cannot have symbol keys.");e.add(n);try{if(Array.isArray(n)){if(Object.keys(n).some(a=>!/^(0|[1-9]\d*)$/.test(a)||Number(a)>=n.length))throw new TypeError("Cache tag arrays cannot have named properties.");return["array",Array.from({length:n.length},(a,s)=>{let i=Object.getOwnPropertyDescriptor(n,s);if(i&&!("value"in i))throw new TypeError("Cache tag values cannot contain getters.");return f(i?.value,e)})]}return["object",Object.keys(n).sort().map(a=>{let s=Object.getOwnPropertyDescriptor(n,a);if(!("value"in s))throw new TypeError("Cache tag values cannot contain getters.");return[a,f(s.value,e)]})]}finally{e.delete(n)}}default:throw new TypeError(`Unsupported cache tag value: ${typeof n}.`)}}function b(n,e){let t=[];for(let a of Object.keys(n.properties).sort()){let s=n.properties[a].getValue(e);s.success&&s.value!==void 0&&t.push([a,f(s.value,new Set)])}return`ct2:${JSON.stringify([n.name,t])}`}import{object as E,SYMBOL_HAS_PROPERTIES as x,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as R}from"@cleverbrush/schema";function w(n){let e={};if(n.paramsSchema){let a=n.paramsSchema;typeof a.introspect=="function"&&a.introspect().objectSchema?e.params=a.introspect().objectSchema:a[x]===!0&&(e.params=a)}if(n.bodySchema){let a=n.bodySchema;(a[x]===!0||typeof a.introspect=="function")&&(e.body=a)}n.querySchema&&(e.query=n.querySchema),n.headerSchema&&(e.headers=n.headerSchema);let t=E(e);return E.getPropertiesFor(t)}function L(n){return n==null||typeof n!="object"?!1:typeof n[R]=="object"&&n[R]!==null}function O(n,e){let t={};for(let[a,s]of Object.entries(e)){if(!L(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 i=s[R];t[a]={getValue:r=>i.getValue(r)}}return{name:n,properties:t}}var P=Symbol.for("cleverbrush.tracked");function N(n,e){return{[P]:!0,id:n,data:e}}function se(n){return n!==null&&typeof n=="object"&&P in n&&n[P]===!0}var p=class n{#a;#s;#n;#i;#r;#o;#l;#t;#d;#h;#c;#p;#u;#y;constructor(e,t="/",a=null,s=null,i=null,r=null,y=null,d=null,o=null,h=null,c=[],m=null,g=!1,S=null){this.#a=e,this.#s=t,this.#n=a,this.#i=s,this.#r=i,this.#o=r,this.#l=y,this.#t=d,this.#d=o,this.#h=h,this.#c=c,this.#p=m,this.#u=g,this.#y=S}#e(e){return new n(e.basePath??this.#a,e.pathTemplate??this.#s,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.#p,e.deprecated??this.#u,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.#a,pathTemplate:this.#s,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.#p,deprecated:this.#u,externalDocs:this.#y}}};function k(n,e){return new p(n,e??"/")}function C(n){return n instanceof p}import{any as $}from"@cleverbrush/schema";function Y(){return $().hasType().addValidator(n=>{let e=n!==null&&typeof n=="object"&&typeof n.filename=="string"&&typeof n.mimeType=="string"&&n.buffer instanceof Uint8Array&&n.size===n.buffer.byteLength;return{valid:e,errors:e?[]:[{message:"Expected an uploaded file"}]}}).withExtension("uploadFile",!0)}function H(n,e){if(!n)return;for(let s of["maxFileSize","maxFileCount","maxFieldSize","maxFieldCount","maxFieldNameSize","maxPartCount"]){let i=n[s];if(i!==void 0&&(!Number.isSafeInteger(i)||i<1))throw new TypeError(`${s} must be a positive safe integer`)}if(!n.schema)return;let t=n.schema.introspect();if(t.type!=="object"||t.isNullable||!t.isRequired||t.acceptUnknownProps)throw new TypeError("Upload schemas must be required, closed objects");let a=e?.introspect();if(a&&a.type!=="object")throw new TypeError("Multipart body schemas must be objects");for(let[s,i]of Object.entries(t.properties)){let r=i.introspect(),y=r.type==="array"?r.elementSchema?.introspect():r;if(!y?.extensions?.uploadFile||r.isNullable||y.isNullable||r.hasDefault||y.hasDefault||r.type==="array"&&!y.isRequired)throw new TypeError(`Upload field "${s}" must be file() or array(file())`);if(Object.hasOwn(a?.properties??{},s))throw new TypeError(`Multipart text and file fields overlap: ${s}`)}}function he(n,e){let t=[],a=[];for(let s of Object.keys(n)){let i=n[s],r=e[s];for(let y of Object.keys(i)){let d=i[y],o=r[y],h=typeof o=="function"?o:o.handler,c=typeof o=="function"?void 0:o.middlewares;C(d)?a.push({endpoint:d,handler:h,middlewares:c}):t.push({endpoint:d,handler:h,middlewares:c,handlerErrorsMapped:typeof o=="function"?!1:o.handlerErrorsMapped,prepare:typeof o=="function"?void 0:o.prepare,idempotency:typeof o=="function"?void 0:o.idempotency,errors:typeof o=="function"?void 0:o.errors})}}return{_entries:t,_subscriptions:a}}var T=class n{#a;#s;#n;#i;#r;#o;#l;#t;#d;#h;#c;#p;#u;#y;#e;#m;#g;#S;#f;#R;#P;#B;#b;#E;#T;#x;constructor(e,t,a,s,i,r,y=null,d=null,o=null,h=null,c=[],m=null,g=!1,S=null,v=null,U=null,I=null,D=null,Q=null,K=null,M=null,F=null,q=null,B=null,z=[],G=!1){H(B,s),this.#a=e,this.#s=t,this.#n=a,this.#i=s,this.#r=i,this.#o=r,this.#l=y,this.#t=d,this.#d=o,this.#h=h,this.#c=c,this.#p=m,this.#u=g,this.#y=S,this.#e=v,this.#m=U,this.#g=I,this.#S=D,this.#f=Q,this.#R=K,this.#P=M,this.#B=F,this.#b=q,this.#E=B,this.#T=z,this.#x=G}idempotent(){if(!["POST","PUT","PATCH","DELETE"].includes(this.#a.toUpperCase()))throw new TypeError("Idempotency requires a mutation endpoint");return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,!0)}body(e){return new n(this.#a,this.#s,this.#n,e,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}query(e){return new n(this.#a,this.#s,this.#n,this.#i,e,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}headers(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,e,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}inject(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,e,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}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.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,a,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}public(){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,null,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}returns(e){let t=e!=null&&typeof e=="object"&&"introspect"in e?e:null;return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,t??this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}responses(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}summary(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,e,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}description(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,e,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}tags(...e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,e,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}operationId(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,e,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}deprecated(){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,!0,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}example(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,e,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}examples(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,e,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}producesFile(e,t){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,{contentType:e,description:t},this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}upload(e,t){let a=e&&"introspect"in e?e:void 0,s=a?t:e;return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,{...s,allowedMimeTypes:s?.allowedMimeTypes?[...s.allowedMimeTypes]:void 0,maxFileSize:s?.maxFileSize??10*1024*1024,maxFileCount:s?.maxFileCount??10,schema:a},this.#T,this.#x)}get path(){let e=this.#s,t=this.#n,a;if(typeof t=="string")a=t;else{let{literals:s,segments:i}=t.introspect().templateDefinition,r="";for(let y=0;y<i.length;y++)r+=s[y]+`:${i[y].path}`;r+=s[i.length]??"",a=r}return a==="/"?e||"/":e+a}introspect(){return{method:this.#a,basePath:this.#s,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.#p,deprecated:this.#u,responseSchema:this.#y,responsesSchemas:this.#e,example:this.#m,examples:this.#g,producesFile:this.#S,produces:this.#f,responseHeaderSchema:this.#R,externalDocs:this.#P,links:this.#B,callbacks:this.#b,fileUpload:this.#E,cacheTags:this.#T,idempotent:this.#x}}produces(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,e,this.#R,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}responseHeaders(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,e,this.#P,this.#B,this.#b,this.#E,this.#T,this.#x)}externalDocs(e,t){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,{url:e,description:t},this.#B,this.#b,this.#E,this.#T,this.#x)}links(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,e,this.#b,this.#E,this.#T,this.#x)}callbacks(e){return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,e,this.#E,this.#T,this.#x)}cacheTag(e,t){return this.clearsCacheTag(e,t)}clearsCacheTag(e,t){if(!t)return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,[...this.#T,{name:e,properties:{}}],this.#x);let a=J(this.#n),s=w({paramsSchema:a,bodySchema:this.#i,querySchema:this.#r,headerSchema:this.#o}),i=t(s);if(typeof i!="object"||i===null)throw new Error(`Cache tag "${e}": selector must return an object with property descriptors (e.g. { id: p.query.id }).`);let r=O(e,i);return new n(this.#a,this.#s,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#p,this.#u,this.#y,this.#e,this.#m,this.#g,this.#S,this.#f,this.#R,this.#P,this.#B,this.#b,this.#E,[...this.#T,r],this.#x)}};function l(n,e,t,a,s){return new T(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 u(n,e){return{get:t=>l("GET",n,t,e),post:t=>l("POST",n,t,e),put:t=>l("PUT",n,t,e),patch:t=>l("PATCH",n,t,e),delete:t=>l("DELETE",n,t,e),head:t=>l("HEAD",n,t,e),options:t=>l("OPTIONS",n,t,e),public:()=>u(n,null)}}function V(n){return{...u(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,u(n,t)},public(){return u(n,null)}}}function J(n){if(n&&typeof n!="string"&&typeof n.introspect=="function"){let e=n.introspect();if(e.objectSchema)return e.objectSchema}return null}function W(n){return j}var j={get:(n,e)=>l("GET",n,e),post:(n,e)=>l("POST",n,e),put:(n,e)=>l("PUT",n,e),patch:(n,e)=>l("PATCH",n,e),delete:(n,e)=>l("DELETE",n,e),head:(n,e)=>l("HEAD",n,e),options:(n,e)=>l("OPTIONS",n,e),resource:V,subscription:(n,e)=>k(n,e)};import{object as X,parseString as Z}from"@cleverbrush/schema";function A(n){let e=X(n);return((t,...a)=>Z(e,s=>s(t,...a)))}function _(n,...e){return n!=null&&Array.isArray(n.raw)?A({})(n):A(n??{})}function Te(n){for(let e of Object.values(n))Object.freeze(e);return Object.freeze(n)}function me(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 ge(n,...e){let t={};for(let a of e)t[a]=n[a],Object.freeze(t[a]);return Object.freeze(t)}function Se(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,w as b,O as c,N as d,se as e,p as f,C as g,Y as h,he as i,T as j,W as k,j as l,_ as m,Te as n,me as o,ge as p,Se as q};
2
+ //# sourceMappingURL=chunk-UZECVBRI.js.map