@iskra-bun/web-kit 0.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +31 -0
  3. package/dist/chunk-POXNRNTC.js +51 -0
  4. package/dist/chunk-POXNRNTC.js.map +1 -0
  5. package/dist/index.d.ts +966 -0
  6. package/dist/index.js +2824 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/mailgun-Z46GZJNI.js +83 -0
  9. package/dist/mailgun-Z46GZJNI.js.map +1 -0
  10. package/dist/s3-7IG4ESFW.js +171 -0
  11. package/dist/s3-7IG4ESFW.js.map +1 -0
  12. package/dist/sendgrid-UK2GSBEF.js +43 -0
  13. package/dist/sendgrid-UK2GSBEF.js.map +1 -0
  14. package/dist/smtp-WJDLYKD5.js +50 -0
  15. package/dist/smtp-WJDLYKD5.js.map +1 -0
  16. package/package.json +74 -0
  17. package/src/driver.ts +55 -0
  18. package/src/errors.ts +66 -0
  19. package/src/features/api-key.ts +243 -0
  20. package/src/features/auth/better-auth-config.ts +160 -0
  21. package/src/features/auth/index.ts +229 -0
  22. package/src/features/auth/schema.ts +174 -0
  23. package/src/features/auth/types.ts +114 -0
  24. package/src/features/cache.ts +144 -0
  25. package/src/features/cors.ts +33 -0
  26. package/src/features/csrf.ts +94 -0
  27. package/src/features/db.ts +90 -0
  28. package/src/features/email/index.ts +103 -0
  29. package/src/features/email/providers/mailgun.ts +99 -0
  30. package/src/features/email/providers/sendgrid.ts +42 -0
  31. package/src/features/email/providers/smtp.ts +51 -0
  32. package/src/features/error-handler.ts +147 -0
  33. package/src/features/health.ts +94 -0
  34. package/src/features/json-schema-validation.ts +186 -0
  35. package/src/features/logger.ts +70 -0
  36. package/src/features/openapi.ts +107 -0
  37. package/src/features/permissions.ts +128 -0
  38. package/src/features/rate-limit.ts +173 -0
  39. package/src/features/request-id.ts +45 -0
  40. package/src/features/session.ts +322 -0
  41. package/src/features/storage/adapters/local.ts +133 -0
  42. package/src/features/storage/adapters/s3.ts +193 -0
  43. package/src/features/storage/base.ts +112 -0
  44. package/src/features/storage/index.ts +53 -0
  45. package/src/features/tracing.ts +49 -0
  46. package/src/features/upload/helper.ts +85 -0
  47. package/src/features/upload/index.ts +140 -0
  48. package/src/features/validation.ts +105 -0
  49. package/src/index.ts +29 -0
  50. package/src/kernel.ts +257 -0
  51. package/src/responses.ts +37 -0
  52. package/src/router.ts +31 -0
  53. package/src/server.ts +135 -0
  54. package/src/types.ts +272 -0
@@ -0,0 +1,112 @@
1
+ import fs from "node:fs/promises";
2
+
3
+ export interface StorageConfig {
4
+ adapter: "local" | "minio" | "s3";
5
+ basePath?: string; // For local storage
6
+ connection?: {
7
+ endpoint?: string;
8
+ accessKey?: string;
9
+ secretKey?: string;
10
+ bucket?: string;
11
+ region?: string;
12
+ useSSL?: boolean;
13
+ };
14
+ }
15
+
16
+ export interface StorageFile {
17
+ name: string;
18
+ path: string;
19
+ size: number;
20
+ mimeType?: string;
21
+ lastModified?: Date;
22
+ url?: string;
23
+ }
24
+
25
+ export interface PutOptions {
26
+ contentType?: string;
27
+ metadata?: Record<string, string>;
28
+ public?: boolean;
29
+ }
30
+
31
+ export interface StorageAdapter {
32
+ connect(): Promise<void>;
33
+ disconnect(): Promise<void>;
34
+ put(path: string, data: Uint8Array | Buffer | ReadableStream, options?: PutOptions): Promise<StorageFile>;
35
+ get(path: string): Promise<Uint8Array | null>;
36
+ // getStream(path: string): Promise<ReadableStream | null>;
37
+ // Node streams are different, sticking to Buffer/Uint8Array for simplicity or using generic Stream
38
+ delete(path: string): Promise<void>;
39
+ exists(path: string): Promise<boolean>;
40
+ list(prefix?: string): Promise<StorageFile[]>;
41
+ url(path: string, expiresIn?: number): Promise<string>;
42
+ copy(from: string, to: string): Promise<void>;
43
+ move(from: string, to: string): Promise<void>;
44
+ isDirectory(path: string): Promise<boolean>;
45
+ }
46
+
47
+ export abstract class BaseStorageAdapter implements StorageAdapter {
48
+ protected connected = false;
49
+
50
+ abstract connect(): Promise<void>;
51
+ abstract disconnect(): Promise<void>;
52
+ abstract put(path: string, data: Uint8Array | Buffer | ReadableStream, options?: PutOptions): Promise<StorageFile>;
53
+ abstract get(path: string): Promise<Uint8Array | null>;
54
+
55
+ abstract delete(path: string): Promise<void>;
56
+ abstract exists(path: string): Promise<boolean>;
57
+ abstract list(prefix?: string): Promise<StorageFile[]>;
58
+ abstract url(path: string, expiresIn?: number): Promise<string>;
59
+ abstract isDirectory(path: string): Promise<boolean>;
60
+
61
+ isConnected(): boolean {
62
+ return this.connected;
63
+ }
64
+
65
+ protected ensureConnected(): void {
66
+ if (!this.connected) {
67
+ throw new Error("Storage not connected. Call connect() first.");
68
+ }
69
+ }
70
+
71
+ async copy(from: string, to: string): Promise<void> {
72
+ const data = await this.get(from);
73
+ if (!data) {
74
+ throw new Error(`Source file not found: ${from}`);
75
+ }
76
+ await this.put(to, data);
77
+ }
78
+
79
+ async move(from: string, to: string): Promise<void> {
80
+ await this.copy(from, to);
81
+ await this.delete(from);
82
+ }
83
+
84
+ protected generateFileName(originalName: string): string {
85
+ const ext = originalName.split(".").pop();
86
+ const timestamp = Date.now();
87
+ const random = Math.random().toString(36).substring(2, 15);
88
+ return `${timestamp}-${random}.${ext}`;
89
+ }
90
+
91
+ protected sanitizePath(path: string): string {
92
+ return path
93
+ .replace(/^\/+/, "")
94
+ .replace(/\/+/g, "/")
95
+ .replace(/\\/g, "/");
96
+ }
97
+
98
+ protected getMimeType(filename: string): string {
99
+ const ext = filename.split(".").pop()?.toLowerCase();
100
+ const mimeTypes: Record<string, string> = {
101
+ jpg: "image/jpeg",
102
+ jpeg: "image/jpeg",
103
+ png: "image/png",
104
+ gif: "image/gif",
105
+ pdf: "application/pdf",
106
+ txt: "text/plain",
107
+ json: "application/json",
108
+ zip: "application/zip",
109
+ };
110
+ return mimeTypes[ext || ""] || "application/octet-stream";
111
+ }
112
+ }
@@ -0,0 +1,53 @@
1
+ import type { Feature } from "../../types";
2
+ import type { Kernel } from "../../kernel";
3
+ import type { StorageConfig } from "./base";
4
+ import { BaseStorageAdapter } from "./base";
5
+ import type { Context, Next } from "hono";
6
+ import { LocalStorageAdapter } from "./adapters/local";
7
+
8
+ export { BaseStorageAdapter, LocalStorageAdapter };
9
+ export type { StorageConfig, StorageFile } from "./base";
10
+
11
+ declare module "hono" {
12
+ interface ContextVariableMap {
13
+ storage: BaseStorageAdapter;
14
+ }
15
+ }
16
+
17
+ export class StorageFeature implements Feature {
18
+ name = "storage";
19
+ private adapter?: BaseStorageAdapter;
20
+
21
+ constructor(private config: StorageConfig) { }
22
+
23
+ async initialize(kernel: Kernel): Promise<void> {
24
+ if (this.config.adapter === "local") {
25
+ this.adapter = new LocalStorageAdapter(this.config);
26
+ } else if (this.config.adapter === "s3" || this.config.adapter === "minio") {
27
+ const { S3StorageAdapter } = await import("./adapters/s3");
28
+ this.adapter = new S3StorageAdapter(this.config);
29
+ } else {
30
+ throw new Error(`Adapter ${this.config.adapter} not supported.`);
31
+ }
32
+
33
+ await this.adapter.connect();
34
+
35
+ const app = kernel.getApp();
36
+ app.use("*", async (c: Context, next: Next) => {
37
+ c.set("storage", this.adapter!);
38
+ await next();
39
+ });
40
+
41
+ console.log(`✅ StorageFeature initialized - ${this.config.adapter}`);
42
+ }
43
+
44
+ async shutdown(): Promise<void> {
45
+ if (this.adapter) {
46
+ await this.adapter.disconnect();
47
+ }
48
+ }
49
+
50
+ getAdapter(): BaseStorageAdapter | undefined {
51
+ return this.adapter;
52
+ }
53
+ }
@@ -0,0 +1,49 @@
1
+ import type { Feature } from "../types";
2
+ import type { Kernel } from "../kernel";
3
+ import { httpInstrumentationMiddleware } from "@hono/otel";
4
+
5
+ export interface OtelTracingConfig {
6
+ serviceName: string;
7
+ serviceVersion?: string;
8
+ captureRequestHeaders?: string[];
9
+ captureResponseHeaders?: string[];
10
+ tracerProvider?: any;
11
+ meterProvider?: any;
12
+ tracer?: any;
13
+ spanNameFactory?: (c: any) => string;
14
+ getTime?: () => number;
15
+ }
16
+
17
+ export class OtelTracingFeature implements Feature {
18
+ name = "otel-tracing";
19
+ private config: OtelTracingConfig;
20
+
21
+ constructor(config: OtelTracingConfig) {
22
+ if (!config.serviceName) {
23
+ throw new Error("serviceName is required for OtelTracingFeature");
24
+ }
25
+ this.config = config;
26
+ }
27
+
28
+ async initialize(kernel: Kernel): Promise<void> {
29
+ const app = kernel.getApp();
30
+
31
+ const instrumentationConfig: any = {
32
+ serviceName: this.config.serviceName,
33
+ };
34
+
35
+ if (this.config.serviceVersion) instrumentationConfig.serviceVersion = this.config.serviceVersion;
36
+ if (this.config.captureRequestHeaders) instrumentationConfig.captureRequestHeaders = this.config.captureRequestHeaders;
37
+ if (this.config.captureResponseHeaders) instrumentationConfig.captureResponseHeaders = this.config.captureResponseHeaders;
38
+ if (this.config.tracerProvider) instrumentationConfig.tracerProvider = this.config.tracerProvider;
39
+ if (this.config.meterProvider) instrumentationConfig.meterProvider = this.config.meterProvider;
40
+ if (this.config.tracer) instrumentationConfig.tracer = this.config.tracer;
41
+ if (this.config.spanNameFactory) instrumentationConfig.spanNameFactory = this.config.spanNameFactory;
42
+ if (this.config.getTime) instrumentationConfig.getTime = this.config.getTime;
43
+
44
+ // Register Otel middleware
45
+ app.use("*", httpInstrumentationMiddleware(instrumentationConfig));
46
+
47
+ console.log("✅ OpenTelemetry tracing feature initialized");
48
+ }
49
+ }
@@ -0,0 +1,85 @@
1
+ import type { StorageFeature } from "../storage";
2
+ import type { BaseStorageAdapter, PutOptions } from "../storage/base";
3
+
4
+ export interface UploadOptions {
5
+ metadata?: Record<string, string>;
6
+ contentType?: string;
7
+ }
8
+
9
+ export interface UploadResult {
10
+ path: string;
11
+ filename: string;
12
+ size: number;
13
+ uploadedAt: Date;
14
+ }
15
+
16
+ export class UploadHelper {
17
+ private basePath: string;
18
+
19
+ constructor(private storage: BaseStorageAdapter, private projectName: string) {
20
+ this.basePath = `${projectName}/`;
21
+ }
22
+
23
+ getBasePath(): string {
24
+ return this.basePath;
25
+ }
26
+
27
+ private buildPath(filename: string, subfolder?: string): string {
28
+ if (subfolder) {
29
+ const normalized = subfolder.replace(/^\/+|\/+$/g, "");
30
+ return `${this.basePath}${normalized}/${filename}`;
31
+ }
32
+ return `${this.basePath}${filename}`;
33
+ }
34
+
35
+ async upload(filename: string, data: Uint8Array | string, subfolder?: string, options?: UploadOptions): Promise<UploadResult> {
36
+ const fullPath = this.buildPath(filename, subfolder);
37
+ const fileData = typeof data === "string" ? new TextEncoder().encode(data) : data;
38
+
39
+ const putOptions: PutOptions | undefined = options ? {
40
+ contentType: options.contentType,
41
+ metadata: options.metadata
42
+ } : undefined;
43
+
44
+ await this.storage.put(fullPath, fileData, putOptions);
45
+
46
+ return {
47
+ path: fullPath,
48
+ filename,
49
+ size: fileData.length,
50
+ uploadedAt: new Date()
51
+ };
52
+ }
53
+
54
+ // uploadFromRequest: Not implementing raw request reading here to avoid Node/Bun specific request stream issues unless necessary
55
+ // or implementing simplified Version
56
+ async uploadFromRequest(request: Request, fieldName: string, subfolder?: string, options?: UploadOptions): Promise<UploadResult> {
57
+ const formData = await request.formData();
58
+ const file = formData.get(fieldName);
59
+ if (!file || !(file instanceof File)) throw new Error(`No file found in field: ${fieldName}`);
60
+
61
+ const data = new Uint8Array(await file.arrayBuffer());
62
+ const opts = { ...options, contentType: options?.contentType || file.type };
63
+ return this.upload(file.name, data, subfolder, opts);
64
+ }
65
+
66
+ async list(subfolder?: string) {
67
+ const prefix = subfolder ? `${this.basePath}${subfolder.replace(/^\/+|\/+$/g, "")}/` : this.basePath;
68
+ return await this.storage.list(prefix);
69
+ }
70
+
71
+ async get(filename: string, subfolder?: string) {
72
+ const fullPath = this.buildPath(filename, subfolder);
73
+ return await this.storage.get(fullPath);
74
+ }
75
+
76
+ async delete(filename: string, subfolder?: string) {
77
+ const fullPath = this.buildPath(filename, subfolder);
78
+ await this.storage.delete(fullPath);
79
+ }
80
+
81
+ async getUrl(filename: string, subfolder?: string, expiresIn?: number) {
82
+ const fullPath = this.buildPath(filename, subfolder);
83
+ return await this.storage.url(fullPath, expiresIn);
84
+ }
85
+ }
@@ -0,0 +1,140 @@
1
+ import type { Feature, UploadConfig } from "../../types";
2
+ import type { Kernel } from "../../kernel";
3
+ import type { Hono, Context, Next } from "hono";
4
+ import { UploadHelper } from "./helper";
5
+ import type { StorageFeature } from "../storage";
6
+
7
+ declare module "hono" {
8
+ interface ContextVariableMap {
9
+ upload: UploadHelper;
10
+ }
11
+ }
12
+
13
+ export class UploadFeature implements Feature {
14
+ name = "upload";
15
+ dependencies = ["storage"];
16
+ private helper?: UploadHelper;
17
+ private config: Required<UploadConfig>;
18
+
19
+ constructor(config: UploadConfig) {
20
+ this.config = {
21
+ projectName: config.projectName,
22
+ maxFileSize: config.maxFileSize || 10 * 1024 * 1024,
23
+ allowedExtensions: config.allowedExtensions || [],
24
+ exposeRoutes: config.exposeRoutes || false,
25
+ routePrefix: config.routePrefix || "/upload"
26
+ };
27
+ }
28
+
29
+ async initialize(kernel: Kernel): Promise<void> {
30
+ const storageFeature = kernel.getFeature("storage") as unknown as StorageFeature;
31
+ if (!storageFeature) throw new Error("Upload feature requires storage feature");
32
+ const storage = storageFeature.getAdapter();
33
+ if (!storage) throw new Error("Storage adapter not ready");
34
+
35
+ this.helper = new UploadHelper(storage, this.config.projectName);
36
+
37
+ const app = kernel.getApp();
38
+ app.use("*", async (c: Context, next: Next) => {
39
+ c.set("upload", this.helper!);
40
+ await next();
41
+ });
42
+
43
+ console.log(`✅ Upload feature initialized: ${this.config.projectName}`);
44
+ }
45
+
46
+ routes(app: Hono) {
47
+ if (!this.config.exposeRoutes) return;
48
+
49
+ const prefix = this.config.routePrefix;
50
+
51
+ // POST — upload a file
52
+ app.post(`${prefix}`, async (c) => {
53
+ const upload = c.get("upload");
54
+ try {
55
+ const subfolder = c.req.query("subfolder");
56
+ const formData = await c.req.formData();
57
+ const file = formData.get("file");
58
+ if (!file || !(file instanceof File)) return c.json({ error: "No file" }, 400);
59
+
60
+ if (file.size > this.config.maxFileSize) return c.json({ error: "File too large" }, 400);
61
+ if (this.config.allowedExtensions.length > 0) {
62
+ if (!this.config.allowedExtensions.some(e => file.name.toLowerCase().endsWith(e))) {
63
+ return c.json({ error: "Invalid extension" }, 400);
64
+ }
65
+ }
66
+
67
+ const data = new Uint8Array(await file.arrayBuffer());
68
+ const result = await upload.upload(file.name, data, subfolder, { contentType: file.type });
69
+ return c.json({ success: true, ...result });
70
+
71
+ } catch (e: any) {
72
+ return c.json({ error: e.message }, 500);
73
+ }
74
+ });
75
+
76
+ // GET — list files
77
+ app.get(`${prefix}`, async (c) => {
78
+ const upload = c.get("upload");
79
+ try {
80
+ const subfolder = c.req.query("subfolder");
81
+ const files = await upload.list(subfolder);
82
+ return c.json({ success: true, files });
83
+ } catch (e: any) {
84
+ return c.json({ error: e.message }, 500);
85
+ }
86
+ });
87
+
88
+ // GET — download a file
89
+ app.get(`${prefix}/*`, async (c) => {
90
+ const upload = c.get("upload");
91
+ try {
92
+ const filePath = c.req.path.replace(`${prefix}/`, "");
93
+ const segments = filePath.split("/");
94
+ const filename = segments.pop() || "";
95
+ const subfolder = segments.length > 0 ? segments.join("/") : undefined;
96
+
97
+ const data = await upload.get(filename, subfolder);
98
+ if (!data) {
99
+ return c.json({ error: "File not found" }, 404);
100
+ }
101
+
102
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
103
+ const mimeTypes: Record<string, string> = {
104
+ jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif",
105
+ pdf: "application/pdf", txt: "text/plain", json: "application/json", zip: "application/zip",
106
+ };
107
+ const contentType = mimeTypes[ext] || "application/octet-stream";
108
+
109
+ const body = new Uint8Array(data.length);
110
+ body.set(data);
111
+
112
+ return new Response(body, {
113
+ headers: {
114
+ "Content-Type": contentType,
115
+ "Content-Disposition": `inline; filename="${filename}"`,
116
+ "Content-Length": String(data.length),
117
+ },
118
+ });
119
+ } catch (e: any) {
120
+ return c.json({ error: e.message }, 500);
121
+ }
122
+ });
123
+
124
+ // DELETE — delete a file
125
+ app.delete(`${prefix}/*`, async (c) => {
126
+ const upload = c.get("upload");
127
+ try {
128
+ const filePath = c.req.path.replace(`${prefix}/`, "");
129
+ const segments = filePath.split("/");
130
+ const filename = segments.pop() || "";
131
+ const subfolder = segments.length > 0 ? segments.join("/") : undefined;
132
+
133
+ await upload.delete(filename, subfolder);
134
+ return new Response(null, { status: 204 });
135
+ } catch (e: any) {
136
+ return c.json({ error: e.message }, 500);
137
+ }
138
+ });
139
+ }
140
+ }
@@ -0,0 +1,105 @@
1
+ import type { Feature } from "../types";
2
+ import type { Kernel } from "../kernel";
3
+ import type { Context, Next, Hono, Handler } from "hono";
4
+ import { z } from "zod";
5
+ import { ErrorCodes, errorResponse } from "../responses";
6
+
7
+ // Extend Hono Context to include valid() method
8
+ declare module "hono" {
9
+ interface Context {
10
+ valid<T = any>(): T;
11
+ }
12
+ }
13
+
14
+ // Extend Hono to include *Validated methods if possible,
15
+ // or simpler: just provide middleware factory.
16
+ // Monkey patching Hono class is tricky in strict TS if types aren't augmented in global/module scope correctly.
17
+ // But we can try to follow kollective's approach if we augment module "hono".
18
+
19
+ export interface ValidationSchema {
20
+ params?: z.ZodSchema<any>;
21
+ query?: z.ZodSchema<any>;
22
+ body?: z.ZodSchema<any>;
23
+ }
24
+
25
+ export interface ValidationOptions {
26
+ logErrors?: boolean;
27
+ status?: number;
28
+ }
29
+
30
+ function createValidationMiddleware(schema: ValidationSchema, options: ValidationOptions = {}) {
31
+ const { logErrors = true, status = 400 } = options;
32
+
33
+ return async (c: Context, next: Next) => {
34
+ try {
35
+ const validated: Record<string, any> = {};
36
+
37
+ if (schema.params) {
38
+ const parsed = schema.params.safeParse(c.req.param());
39
+ if (!parsed.success) {
40
+ return c.json(errorResponse("Invalid route params", ErrorCodes.VALIDATION_ERROR, parsed.error.flatten()), status as any);
41
+ }
42
+ validated.params = parsed.data;
43
+ }
44
+
45
+ if (schema.query) {
46
+ const parsed = schema.query.safeParse(c.req.query());
47
+ if (!parsed.success) {
48
+ return c.json(errorResponse("Invalid query params", ErrorCodes.VALIDATION_ERROR, parsed.error.flatten()), status as any);
49
+ }
50
+ validated.query = parsed.data;
51
+ }
52
+
53
+ if (schema.body) {
54
+ let data: unknown = {};
55
+ const contentType = c.req.header("content-type") || "";
56
+ if (contentType.includes("application/json")) {
57
+ data = await c.req.json().catch(() => ({}));
58
+ } else if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
59
+ data = await c.req.parseBody();
60
+ }
61
+
62
+ const parsed = schema.body.safeParse(data);
63
+ if (!parsed.success) {
64
+ return c.json(errorResponse("Invalid body", ErrorCodes.VALIDATION_ERROR, parsed.error.flatten()), status as any);
65
+ }
66
+ validated.body = parsed.data;
67
+ }
68
+
69
+ // Attach to context
70
+ (c as any).valid = () => validated;
71
+
72
+ await next();
73
+ } catch (err) {
74
+ if (logErrors) console.error("Validation error:", err);
75
+ return c.json(errorResponse("Validation middleware failed", ErrorCodes.INTERNAL_ERROR), 500);
76
+ }
77
+ };
78
+ }
79
+
80
+ // Helper to monkey patch Hono
81
+ function extendHonoWithValidation(app: Hono) {
82
+ const methods = ["get", "post", "put", "delete"] as const;
83
+ for (const method of methods) {
84
+ (app as any)[`${method}Validated`] = function (path: string, schema: ValidationSchema, handler: Handler, options?: ValidationOptions) {
85
+ const middleware = createValidationMiddleware(schema, options);
86
+ (this as any)[method](path, middleware, handler);
87
+ return this;
88
+ };
89
+ }
90
+ }
91
+
92
+ export class ValidationFeature implements Feature {
93
+ name = "validation";
94
+
95
+ async initialize(kernel: Kernel): Promise<void> {
96
+ const app = kernel.getApp();
97
+ extendHonoWithValidation(app);
98
+
99
+ // Add context helper if not already present via middleware factory logic
100
+ // The middleware factory adds .valid() to the SPECIFIC request context
101
+ console.log("✅ Validation feature initialized");
102
+ }
103
+ }
104
+
105
+ export { createValidationMiddleware };
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ export * from "./types";
2
+ export * from "./kernel";
3
+ export * from "./driver";
4
+ export * from "./server";
5
+ export * from "./router";
6
+
7
+ // Features
8
+ export * from "./features/cors";
9
+ export * from "./features/error-handler";
10
+ export * from "./features/health";
11
+ export * from "./features/logger";
12
+ export * from "./features/request-id";
13
+ export * from "./features/storage";
14
+ export * from "./features/db";
15
+ export * from "./features/cache";
16
+ export * from "./features/session";
17
+ export * from "./features/auth";
18
+ export * from "./features/rate-limit";
19
+ export * from "./features/permissions";
20
+ export * from "./features/api-key";
21
+ export * from "./features/csrf";
22
+ export * from "./features/validation";
23
+ export * from "./features/json-schema-validation";
24
+ export * from "./features/openapi";
25
+ export * from "./features/upload";
26
+ export * from "./features/tracing";
27
+ export * from "./features/email";
28
+ export * from "./responses";
29
+ export * from "./errors";