yaxa-svelte 2.1.0 → 2.3.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.
@@ -0,0 +1,14 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ import type { YaxaEndpointConfig } from './types';
3
+ /**
4
+ * Wraps SvelteKit +server.ts request handlers into an opinionated,
5
+ * declarative, type-safe execution pipeline with Guards, Multi-Engine
6
+ * Validation, and RFC 7807 Problem Details.
7
+ */
8
+ export declare function yaxaEndpoint<TBodySchema = any, TQuerySchema = any, TParamsSchema = any, THeadersSchema = any, TResult = any>(config: YaxaEndpointConfig<TBodySchema, TQuerySchema, TParamsSchema, THeadersSchema, TResult>): RequestHandler & {
9
+ _yaxaConfig: typeof config;
10
+ };
11
+ export * from './types';
12
+ export * from './problem';
13
+ export * from './guards';
14
+ export * from './validate';
@@ -0,0 +1,242 @@
1
+ import { isProblemDetails, problemResponse } from './problem';
2
+ import { validateData } from './validate';
3
+ import { globalApiRegistry } from '../openapi/registry';
4
+ /**
5
+ * Parses URLSearchParams into a plain key-value object.
6
+ * Duplicate keys are parsed into arrays.
7
+ */
8
+ function parseSearchParamsToObject(searchParams) {
9
+ const result = {};
10
+ for (const key of searchParams.keys()) {
11
+ const allValues = searchParams.getAll(key);
12
+ if (allValues.length > 1) {
13
+ result[key] = allValues;
14
+ }
15
+ else if (allValues.length === 1) {
16
+ result[key] = allValues[0];
17
+ }
18
+ }
19
+ return result;
20
+ }
21
+ /**
22
+ * Safely parses request body based on Content-Type.
23
+ */
24
+ async function parseRequestBody(request) {
25
+ const contentType = request.headers.get('content-type') || '';
26
+ if (contentType.includes('application/json')) {
27
+ try {
28
+ const text = await request.text();
29
+ if (!text.trim()) {
30
+ return { ok: true, body: undefined };
31
+ }
32
+ return { ok: true, body: JSON.parse(text) };
33
+ }
34
+ catch {
35
+ return {
36
+ ok: false,
37
+ response: problemResponse({
38
+ status: 400,
39
+ title: 'Bad Request',
40
+ detail: 'Malformed JSON payload in request body.',
41
+ type: 'https://yaxa.dev/errors/malformed-json'
42
+ })
43
+ };
44
+ }
45
+ }
46
+ if (contentType.includes('application/x-www-form-urlencoded') ||
47
+ contentType.includes('multipart/form-data')) {
48
+ try {
49
+ const formData = await request.formData();
50
+ const formObj = {};
51
+ formData.forEach((val, key) => {
52
+ if (key in formObj) {
53
+ if (Array.isArray(formObj[key])) {
54
+ formObj[key].push(val);
55
+ }
56
+ else {
57
+ formObj[key] = [formObj[key], val];
58
+ }
59
+ }
60
+ else {
61
+ formObj[key] = val;
62
+ }
63
+ });
64
+ return { ok: true, body: formObj };
65
+ }
66
+ catch {
67
+ return {
68
+ ok: false,
69
+ response: problemResponse({
70
+ status: 400,
71
+ title: 'Bad Request',
72
+ detail: 'Malformed form-data in request body.'
73
+ })
74
+ };
75
+ }
76
+ }
77
+ // Plain text or other payloads
78
+ try {
79
+ const text = await request.text();
80
+ return { ok: true, body: text ? text : undefined };
81
+ }
82
+ catch {
83
+ return { ok: true, body: undefined };
84
+ }
85
+ }
86
+ /**
87
+ * Wraps SvelteKit +server.ts request handlers into an opinionated,
88
+ * declarative, type-safe execution pipeline with Guards, Multi-Engine
89
+ * Validation, and RFC 7807 Problem Details.
90
+ */
91
+ export function yaxaEndpoint(config) {
92
+ const handler = async (event) => {
93
+ const { request, url, locals, params } = event;
94
+ // 1. Execute Declarative Guards in Series
95
+ if (config.guards && config.guards.length > 0) {
96
+ for (const guard of config.guards) {
97
+ const result = await guard(event);
98
+ if (result instanceof Response) {
99
+ return result;
100
+ }
101
+ if (isProblemDetails(result)) {
102
+ return problemResponse(result);
103
+ }
104
+ if (result === false) {
105
+ return problemResponse({
106
+ status: 403,
107
+ title: 'Forbidden',
108
+ detail: 'Access denied by endpoint guard.'
109
+ });
110
+ }
111
+ }
112
+ }
113
+ // 2. Parse Raw Inputs
114
+ const rawParams = params || {};
115
+ const rawQuery = parseSearchParamsToObject(url.searchParams);
116
+ const rawHeaders = {};
117
+ request.headers.forEach((val, key) => {
118
+ rawHeaders[key.toLowerCase()] = val;
119
+ });
120
+ let rawBody;
121
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
122
+ const parsed = await parseRequestBody(request);
123
+ if (!parsed.ok) {
124
+ return parsed.response;
125
+ }
126
+ rawBody = parsed.body;
127
+ }
128
+ // 3. Multi-Engine Schema Validation (Standard Schema, Zod, etc.)
129
+ const allInvalidParams = [];
130
+ let validatedBody = rawBody;
131
+ let validatedQuery = rawQuery;
132
+ let validatedParams = rawParams;
133
+ let validatedHeaders = rawHeaders;
134
+ if (config.schema) {
135
+ if (config.schema.params) {
136
+ const res = await validateData(config.schema.params, rawParams, 'params');
137
+ if (res.success)
138
+ validatedParams = res.data;
139
+ else
140
+ allInvalidParams.push(...res.invalidParams);
141
+ }
142
+ if (config.schema.query) {
143
+ const res = await validateData(config.schema.query, rawQuery, 'query');
144
+ if (res.success)
145
+ validatedQuery = res.data;
146
+ else
147
+ allInvalidParams.push(...res.invalidParams);
148
+ }
149
+ if (config.schema.headers) {
150
+ const res = await validateData(config.schema.headers, rawHeaders, 'headers');
151
+ if (res.success)
152
+ validatedHeaders = res.data;
153
+ else
154
+ allInvalidParams.push(...res.invalidParams);
155
+ }
156
+ if (config.schema.body && request.method !== 'GET' && request.method !== 'HEAD') {
157
+ const res = await validateData(config.schema.body, rawBody, 'body');
158
+ if (res.success)
159
+ validatedBody = res.data;
160
+ else
161
+ allInvalidParams.push(...res.invalidParams);
162
+ }
163
+ }
164
+ if (allInvalidParams.length > 0) {
165
+ return problemResponse({
166
+ status: 422,
167
+ title: 'Unprocessable Entity',
168
+ detail: `Validation failed for ${allInvalidParams.length} parameter(s).`,
169
+ type: 'https://yaxa.dev/errors/validation-failed',
170
+ invalidParams: allInvalidParams
171
+ });
172
+ }
173
+ // 4. Construct Rich Endpoint Context
174
+ const context = {
175
+ event,
176
+ request,
177
+ url,
178
+ locals,
179
+ body: validatedBody,
180
+ query: validatedQuery,
181
+ params: validatedParams,
182
+ headers: validatedHeaders,
183
+ user: locals?.user || null,
184
+ session: locals?.session || null,
185
+ orgId: locals?.orgId || null
186
+ };
187
+ // 5. Execute Handler wrapped in Interceptors & Error Boundary
188
+ const runHandler = async () => {
189
+ const defaultStatus = config.status || 200;
190
+ let result;
191
+ try {
192
+ result = await config.handle(context);
193
+ }
194
+ catch (err) {
195
+ // Allow SvelteKit redirects and HTTP errors to bubble up
196
+ if (err && typeof err === 'object' && ('status' in err || 'location' in err)) {
197
+ throw err;
198
+ }
199
+ if (err instanceof Response) {
200
+ return err;
201
+ }
202
+ console.error('[Yaxa Endpoint Error]', err);
203
+ return problemResponse({
204
+ status: 500,
205
+ title: 'Internal Server Error',
206
+ detail: 'An unexpected internal error occurred.',
207
+ type: 'https://yaxa.dev/errors/internal-server-error'
208
+ });
209
+ }
210
+ // 6. Response Auto-Serialization
211
+ if (result instanceof Response) {
212
+ return result;
213
+ }
214
+ if (result === undefined || result === null) {
215
+ return new Response(null, { status: 204 });
216
+ }
217
+ if (typeof result === 'string') {
218
+ return new Response(result, {
219
+ status: defaultStatus,
220
+ headers: { 'content-type': 'text/plain; charset=utf-8' }
221
+ });
222
+ }
223
+ return Response.json(result, { status: defaultStatus });
224
+ };
225
+ // 7. Apply Interceptors (if any)
226
+ if (config.interceptors && config.interceptors.length > 0) {
227
+ const pipeline = config.interceptors.reduceRight((next, interceptor) => () => interceptor(context, next), runHandler);
228
+ return pipeline();
229
+ }
230
+ return runHandler();
231
+ };
232
+ // Attach configuration metadata for runtime reflection and OpenAPI generation (Milestone 3)
233
+ handler._yaxaConfig = config;
234
+ if (config.path && config.method) {
235
+ globalApiRegistry.register(config.path, { [config.method]: handler });
236
+ }
237
+ return handler;
238
+ }
239
+ export * from './types';
240
+ export * from './problem';
241
+ export * from './guards';
242
+ export * from './validate';
@@ -0,0 +1,26 @@
1
+ import type { ProblemDetails } from './types';
2
+ export interface CreateProblemOptions {
3
+ status: number;
4
+ title?: string;
5
+ detail?: string;
6
+ type?: string;
7
+ instance?: string;
8
+ invalidParams?: Array<{
9
+ name: string;
10
+ reason: string;
11
+ }>;
12
+ headers?: HeadersInit;
13
+ [key: string]: unknown;
14
+ }
15
+ /**
16
+ * Creates an RFC 7807 Problem Details object.
17
+ */
18
+ export declare function createProblemDetails(options: CreateProblemOptions): ProblemDetails;
19
+ /**
20
+ * Creates a standard SvelteKit Response formatted with application/problem+json.
21
+ */
22
+ export declare function problemResponse(options: CreateProblemOptions): Response;
23
+ /**
24
+ * Type-guard checking if an object conforms to ProblemDetails.
25
+ */
26
+ export declare function isProblemDetails(value: unknown): value is ProblemDetails;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Standard HTTP error status code to human-readable RFC 7807 titles.
3
+ */
4
+ const STATUS_TITLES = {
5
+ 400: 'Bad Request',
6
+ 401: 'Unauthorized',
7
+ 403: 'Forbidden',
8
+ 404: 'Not Found',
9
+ 405: 'Method Not Allowed',
10
+ 409: 'Conflict',
11
+ 422: 'Unprocessable Entity',
12
+ 429: 'Too Many Requests',
13
+ 500: 'Internal Server Error',
14
+ 502: 'Bad Gateway',
15
+ 503: 'Service Unavailable',
16
+ 504: 'Gateway Timeout'
17
+ };
18
+ /**
19
+ * Creates an RFC 7807 Problem Details object.
20
+ */
21
+ export function createProblemDetails(options) {
22
+ const status = options.status || 500;
23
+ const title = options.title || STATUS_TITLES[status] || 'Internal Error';
24
+ const type = options.type || `https://httpstatuses.com/${status}`;
25
+ const problem = {
26
+ type,
27
+ title,
28
+ status
29
+ };
30
+ for (const [key, value] of Object.entries(options)) {
31
+ if (key !== 'headers' && key !== 'status' && key !== 'title' && key !== 'type') {
32
+ problem[key] = value;
33
+ }
34
+ }
35
+ return problem;
36
+ }
37
+ /**
38
+ * Creates a standard SvelteKit Response formatted with application/problem+json.
39
+ */
40
+ export function problemResponse(options) {
41
+ const status = options.status || 500;
42
+ const problem = createProblemDetails(options);
43
+ const customHeaders = new Headers(options.headers || {});
44
+ if (!customHeaders.has('content-type')) {
45
+ customHeaders.set('content-type', 'application/problem+json; charset=utf-8');
46
+ }
47
+ return new Response(JSON.stringify(problem), {
48
+ status,
49
+ headers: customHeaders
50
+ });
51
+ }
52
+ /**
53
+ * Type-guard checking if an object conforms to ProblemDetails.
54
+ */
55
+ export function isProblemDetails(value) {
56
+ return (typeof value === 'object' &&
57
+ value !== null &&
58
+ 'status' in value &&
59
+ typeof value.status === 'number' &&
60
+ 'title' in value &&
61
+ 'type' in value);
62
+ }
@@ -0,0 +1,151 @@
1
+ import type { RequestEvent } from '@sveltejs/kit';
2
+ /**
3
+ * Standard RFC 7807 Problem Details representation.
4
+ * @see https://datatracker.ietf.org/doc/html/rfc7807
5
+ */
6
+ export interface ProblemDetails {
7
+ type: string;
8
+ title: string;
9
+ status: number;
10
+ detail?: string;
11
+ instance?: string;
12
+ invalidParams?: Array<{
13
+ name: string;
14
+ reason: string;
15
+ }>;
16
+ [key: string]: unknown;
17
+ }
18
+ /**
19
+ * Generic guard return value.
20
+ * - Return `Response` or `ProblemDetails`: Immediately stops pipeline and sends HTTP response.
21
+ * - Return `void` or `undefined` or `true`: Guard passed; continues to next step.
22
+ */
23
+ export type GuardResult = Response | ProblemDetails | void | undefined | boolean;
24
+ /**
25
+ * Guard function signature.
26
+ */
27
+ export type Guard = (event: RequestEvent) => Promise<GuardResult> | GuardResult;
28
+ /**
29
+ * Standard Schema or validator representation.
30
+ * Supports:
31
+ * 1. Standard Schema v1 (`~standard` property, e.g. Zod v3.24+, Zod v4, Valibot, ArkType)
32
+ * 2. `.safeParse()` (Zod, Yup, etc.)
33
+ * 3. Functional validator: `(data: unknown) => { success: boolean; data?: T; error?: any }`
34
+ */
35
+ export type AnyValidator<T = any> = {
36
+ '~standard': {
37
+ validate: (value: unknown) => Promise<{
38
+ value?: T;
39
+ issues?: ReadonlyArray<any>;
40
+ }> | {
41
+ value?: T;
42
+ issues?: ReadonlyArray<any>;
43
+ };
44
+ };
45
+ } | {
46
+ safeParse: (value: unknown) => {
47
+ success: true;
48
+ data: T;
49
+ } | {
50
+ success: false;
51
+ error: any;
52
+ };
53
+ } | {
54
+ parse: (value: unknown) => T;
55
+ } | ((value: unknown) => {
56
+ success: boolean;
57
+ data?: T;
58
+ error?: any;
59
+ });
60
+ /**
61
+ * Infer output type from a validator.
62
+ */
63
+ export type InferValidatorOutput<V> = V extends {
64
+ '~standard': {
65
+ validate: (...args: any[]) => Promise<{
66
+ value?: infer T;
67
+ }> | {
68
+ value?: infer T;
69
+ };
70
+ };
71
+ } ? T : V extends {
72
+ safeParse: (...args: any[]) => {
73
+ success: true;
74
+ data: infer T;
75
+ } | {
76
+ success: false;
77
+ error: any;
78
+ };
79
+ } ? T : V extends {
80
+ parse: (...args: any[]) => infer T;
81
+ } ? T : V extends (value: unknown) => {
82
+ success: true;
83
+ data: infer T;
84
+ } ? T : any;
85
+ export interface YaxaEndpointSchemaConfig<TBody = any, TQuery = any, TParams = any, THeaders = any> {
86
+ body?: TBody;
87
+ query?: TQuery;
88
+ params?: TParams;
89
+ headers?: THeaders;
90
+ }
91
+ /**
92
+ * Rich context passed to business handler in yaxaEndpoint.
93
+ */
94
+ export interface YaxaEndpointContext<TBody = any, TQuery = any, TParams = any, THeaders = any> {
95
+ event: RequestEvent;
96
+ request: Request;
97
+ url: URL;
98
+ locals: App.Locals;
99
+ body: TBody;
100
+ query: TQuery;
101
+ params: TParams;
102
+ headers: THeaders;
103
+ user: any;
104
+ session: any;
105
+ orgId: string | null;
106
+ }
107
+ /**
108
+ * Interceptor function wrapping handler execution.
109
+ */
110
+ export type YaxaInterceptor = (context: YaxaEndpointContext, next: () => Promise<Response>) => Promise<Response>;
111
+ /**
112
+ * Endpoint configuration passed to yaxaEndpoint().
113
+ */
114
+ export interface YaxaEndpointConfig<TBodySchema = any, TQuerySchema = any, TParamsSchema = any, THeadersSchema = any, TResult = any> {
115
+ /**
116
+ * Sequence of declarative guards executed before handler body.
117
+ * (e.g. requireAuth(), requireRole(['admin']), rateLimit({ points: 10 }))
118
+ */
119
+ guards?: Guard[];
120
+ /**
121
+ * Schemas for input payload validation (body, query string, path params, headers).
122
+ */
123
+ schema?: YaxaEndpointSchemaConfig<TBodySchema, TQuerySchema, TParamsSchema, THeadersSchema>;
124
+ /**
125
+ * Optional status code to return for successful non-Response return values.
126
+ * Defaults to 200 (or 201 for POST when returning created object if desired).
127
+ */
128
+ status?: number;
129
+ /**
130
+ * Optional response interceptors (e.g. audit logging, timing headers).
131
+ */
132
+ interceptors?: YaxaInterceptor[];
133
+ /**
134
+ * OpenAPI metadata annotations (used by Milestone 3 auto-docs).
135
+ */
136
+ summary?: string;
137
+ description?: string;
138
+ tags?: string[];
139
+ deprecated?: boolean;
140
+ operationId?: string;
141
+ path?: string;
142
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | string;
143
+ responses?: Record<number, {
144
+ description: string;
145
+ schema?: any;
146
+ }>;
147
+ /**
148
+ * Core business logic handler.
149
+ */
150
+ handle: (context: YaxaEndpointContext<InferValidatorOutput<TBodySchema>, InferValidatorOutput<TQuerySchema>, InferValidatorOutput<TParamsSchema>, InferValidatorOutput<THeadersSchema>>) => Promise<TResult> | TResult;
151
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ export interface ValidationSuccess<T> {
2
+ success: true;
3
+ data: T;
4
+ }
5
+ export interface ValidationFailure {
6
+ success: false;
7
+ invalidParams: Array<{
8
+ name: string;
9
+ reason: string;
10
+ }>;
11
+ rawError?: any;
12
+ }
13
+ export type ValidationOutcome<T> = ValidationSuccess<T> | ValidationFailure;
14
+ /**
15
+ * Validates arbitrary input data against any supported validator:
16
+ * 1. Standard Schema v1 (`~standard` property, e.g. Zod v3.24+, Zod v4, Valibot, ArkType)
17
+ * 2. `.safeParse()` (Zod, Yup, etc.)
18
+ * 3. `.parse()`
19
+ * 4. Custom validation function: `(data) => { success, data, error }`
20
+ */
21
+ export declare function validateData<T = any>(validator: any, input: unknown, scopePrefix?: string): Promise<ValidationOutcome<T>>;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Validates arbitrary input data against any supported validator:
3
+ * 1. Standard Schema v1 (`~standard` property, e.g. Zod v3.24+, Zod v4, Valibot, ArkType)
4
+ * 2. `.safeParse()` (Zod, Yup, etc.)
5
+ * 3. `.parse()`
6
+ * 4. Custom validation function: `(data) => { success, data, error }`
7
+ */
8
+ export async function validateData(validator, input, scopePrefix = '') {
9
+ if (!validator) {
10
+ return { success: true, data: input };
11
+ }
12
+ // 1. Standard Schema v1 (~standard specification)
13
+ if (typeof validator === 'object' && validator !== null && '~standard' in validator) {
14
+ try {
15
+ const std = validator['~standard'];
16
+ const result = await std.validate(input);
17
+ if (result &&
18
+ 'issues' in result &&
19
+ Array.isArray(result.issues) &&
20
+ result.issues.length > 0) {
21
+ const invalidParams = result.issues.map((issue) => {
22
+ let pathStr = '';
23
+ if (Array.isArray(issue.path)) {
24
+ pathStr = issue.path
25
+ .map((seg) => typeof seg === 'object' && seg !== null && 'key' in seg ? seg.key : String(seg))
26
+ .join('.');
27
+ }
28
+ const name = scopePrefix
29
+ ? pathStr
30
+ ? `${scopePrefix}.${pathStr}`
31
+ : scopePrefix
32
+ : pathStr || 'value';
33
+ return {
34
+ name,
35
+ reason: issue.message || 'Invalid value'
36
+ };
37
+ });
38
+ return { success: false, invalidParams, rawError: result.issues };
39
+ }
40
+ return { success: true, data: result.value };
41
+ }
42
+ catch (err) {
43
+ return {
44
+ success: false,
45
+ invalidParams: [
46
+ { name: scopePrefix || 'value', reason: err?.message || 'Validation failed' }
47
+ ],
48
+ rawError: err
49
+ };
50
+ }
51
+ }
52
+ // 2. Standard .safeParse() (Zod, etc.)
53
+ if (typeof validator === 'object' &&
54
+ validator !== null &&
55
+ typeof validator.safeParse === 'function') {
56
+ const parsed = validator.safeParse(input);
57
+ if (parsed.success) {
58
+ return { success: true, data: parsed.data };
59
+ }
60
+ // ZodError or similar
61
+ const issues = parsed.error?.issues || parsed.error?.errors || [];
62
+ const invalidParams = issues.length > 0
63
+ ? issues.map((issue) => {
64
+ const pathStr = Array.isArray(issue.path) ? issue.path.join('.') : '';
65
+ const name = scopePrefix
66
+ ? pathStr
67
+ ? `${scopePrefix}.${pathStr}`
68
+ : scopePrefix
69
+ : pathStr || 'value';
70
+ return {
71
+ name,
72
+ reason: issue.message || 'Validation failed'
73
+ };
74
+ })
75
+ : [{ name: scopePrefix || 'value', reason: parsed.error?.message || 'Validation failed' }];
76
+ return { success: false, invalidParams, rawError: parsed.error };
77
+ }
78
+ // 3. Functional Validator
79
+ if (typeof validator === 'function') {
80
+ try {
81
+ const res = validator(input);
82
+ if (res && typeof res === 'object' && 'success' in res) {
83
+ if (res.success) {
84
+ return { success: true, data: (res.data !== undefined ? res.data : input) };
85
+ }
86
+ const reason = res.error?.message || (typeof res.error === 'string' ? res.error : 'Validation failed');
87
+ return {
88
+ success: false,
89
+ invalidParams: [{ name: scopePrefix || 'value', reason }],
90
+ rawError: res.error
91
+ };
92
+ }
93
+ // If function returned parsed data without error
94
+ return { success: true, data: res };
95
+ }
96
+ catch (err) {
97
+ return {
98
+ success: false,
99
+ invalidParams: [
100
+ { name: scopePrefix || 'value', reason: err?.message || 'Validation failed' }
101
+ ],
102
+ rawError: err
103
+ };
104
+ }
105
+ }
106
+ // 4. Standard .parse() throwing validator
107
+ if (typeof validator === 'object' &&
108
+ validator !== null &&
109
+ typeof validator.parse === 'function') {
110
+ try {
111
+ const data = validator.parse(input);
112
+ return { success: true, data: data };
113
+ }
114
+ catch (err) {
115
+ const issues = err?.issues || err?.errors || [];
116
+ const invalidParams = issues.length > 0
117
+ ? issues.map((issue) => ({
118
+ name: scopePrefix
119
+ ? `${scopePrefix}.${issue.path?.join('.')}`
120
+ : issue.path?.join('.') || 'value',
121
+ reason: issue.message || 'Validation failed'
122
+ }))
123
+ : [{ name: scopePrefix || 'value', reason: err?.message || 'Validation failed' }];
124
+ return { success: false, invalidParams, rawError: err };
125
+ }
126
+ }
127
+ return { success: true, data: input };
128
+ }
@@ -22,3 +22,7 @@ export { getResendClient, sendMagicLinkEmail, sendWelcomeEmail } from './email/i
22
22
  export type { ResendEmailConfig, SendMagicLinkOptions, SendWelcomeEmailOptions } from './email/index';
23
23
  export { getS3Client, createPresignedUploadUrl, createS3UploadHandler } from './storage/index';
24
24
  export type { S3StorageConfig, PresignedUploadOptions, PresignedUploadResult, S3UploadHandlerOptions } from './storage/index';
25
+ export { yaxaEndpoint, createProblemDetails, problemResponse, isProblemDetails, requireAuth, requireRole, rateLimit, composeGuards, validateData } from './endpoint/index';
26
+ export type { YaxaEndpointConfig, YaxaEndpointContext, YaxaEndpointSchemaConfig, YaxaInterceptor, Guard, GuardResult, ProblemDetails, CreateProblemOptions, RequireAuthOptions, RequireRoleOptions, RateLimitOptions, AnyValidator, InferValidatorOutput, ValidationOutcome, ValidationSuccess, ValidationFailure } from './endpoint/index';
27
+ export { generateOpenApiSpec, createApiDocsHandler, createApiRegistry, globalApiRegistry, convertToJsonSchema } from './openapi/index';
28
+ export type { OpenApiSpec31, OpenApiOperation, OpenApiParameter, OpenApiRequestBody, OpenApiResponse, GenerateOpenApiOptions, ApiDocsOptions, ApiDocsTheme, EndpointLike, EndpointMap } from './openapi/index';
@@ -18,3 +18,7 @@ export { getPolarClient, createPolarCheckout, createPolarCustomerPortalSession,
18
18
  export { getResendClient, sendMagicLinkEmail, sendWelcomeEmail } from './email/index';
19
19
  // Cloud Storage (AWS S3 & Cloudflare R2)
20
20
  export { getS3Client, createPresignedUploadUrl, createS3UploadHandler } from './storage/index';
21
+ // Endpoint Pipeline, Guards & RFC 7807 Validation (Milestone 1)
22
+ export { yaxaEndpoint, createProblemDetails, problemResponse, isProblemDetails, requireAuth, requireRole, rateLimit, composeGuards, validateData } from './endpoint/index';
23
+ // Auto-Generated OpenAPI 3.1 & Interactive Docs (Milestone 3)
24
+ export { generateOpenApiSpec, createApiDocsHandler, createApiRegistry, globalApiRegistry, convertToJsonSchema } from './openapi/index';
@@ -0,0 +1,5 @@
1
+ import type { GenerateOpenApiOptions, OpenApiSpec31 } from './types';
2
+ /**
3
+ * Generates an OpenAPI 3.1.0 document from registered yaxaEndpoints.
4
+ */
5
+ export declare function generateOpenApiSpec(options?: GenerateOpenApiOptions): OpenApiSpec31;