@parsrun/server 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.
- package/README.md +142 -0
- package/dist/app.d.ts +87 -0
- package/dist/app.js +59 -0
- package/dist/app.js.map +1 -0
- package/dist/context.d.ts +208 -0
- package/dist/context.js +23 -0
- package/dist/context.js.map +1 -0
- package/dist/health.d.ts +81 -0
- package/dist/health.js +112 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +2094 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +888 -0
- package/dist/middleware/index.js +880 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/module-loader.d.ts +125 -0
- package/dist/module-loader.js +309 -0
- package/dist/module-loader.js.map +1 -0
- package/dist/rbac.d.ts +171 -0
- package/dist/rbac.js +347 -0
- package/dist/rbac.js.map +1 -0
- package/dist/rls.d.ts +114 -0
- package/dist/rls.js +126 -0
- package/dist/rls.js.map +1 -0
- package/dist/utils/index.d.ts +262 -0
- package/dist/utils/index.js +193 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/validation/index.d.ts +118 -0
- package/dist/validation/index.js +245 -0
- package/dist/validation/index.js.map +1 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# @parsrun/server
|
|
2
|
+
|
|
3
|
+
Edge-compatible, multi-tenant server framework for Pars built on Hono.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Edge-First**: Cloudflare Workers, Deno Deploy, Vercel Edge
|
|
8
|
+
- **Multi-Tenant**: RLS (Row-Level Security), tenant context
|
|
9
|
+
- **RBAC**: Role-based access control
|
|
10
|
+
- **Modular**: Dynamic module loading
|
|
11
|
+
- **Health Checks**: Kubernetes-ready health endpoints
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @parsrun/server hono
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { createApp, createContext } from '@parsrun/server';
|
|
23
|
+
|
|
24
|
+
const app = createApp({
|
|
25
|
+
name: 'my-api',
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
app.get('/', (c) => c.json({ status: 'ok' }));
|
|
30
|
+
|
|
31
|
+
export default app;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API Overview
|
|
35
|
+
|
|
36
|
+
### App Creation
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { createApp } from '@parsrun/server';
|
|
40
|
+
|
|
41
|
+
const app = createApp({
|
|
42
|
+
name: 'my-api',
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
cors: true,
|
|
45
|
+
logging: true,
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Context & Middleware
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { createContext, withTenant } from '@parsrun/server/context';
|
|
53
|
+
|
|
54
|
+
// Tenant-aware context
|
|
55
|
+
app.use(withTenant());
|
|
56
|
+
|
|
57
|
+
app.get('/data', (c) => {
|
|
58
|
+
const tenantId = c.get('tenantId');
|
|
59
|
+
// ...
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Row-Level Security (RLS)
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { createRLS } from '@parsrun/server/rls';
|
|
67
|
+
|
|
68
|
+
const rls = createRLS({
|
|
69
|
+
tenantColumn: 'tenant_id',
|
|
70
|
+
userColumn: 'user_id',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Apply RLS to queries
|
|
74
|
+
const query = rls.apply(baseQuery, context);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### RBAC
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createRBAC, requireRole, requirePermission } from '@parsrun/server/rbac';
|
|
81
|
+
|
|
82
|
+
const rbac = createRBAC({
|
|
83
|
+
roles: {
|
|
84
|
+
admin: ['users:*', 'settings:*'],
|
|
85
|
+
member: ['users:read', 'content:*'],
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.get('/admin', requireRole('admin'), handler);
|
|
90
|
+
app.delete('/users/:id', requirePermission('users:delete'), handler);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Health Checks
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { createHealthRoutes } from '@parsrun/server/health';
|
|
97
|
+
|
|
98
|
+
app.route('/health', createHealthRoutes({
|
|
99
|
+
checks: {
|
|
100
|
+
database: async () => ({ status: 'ok' }),
|
|
101
|
+
cache: async () => ({ status: 'ok' }),
|
|
102
|
+
},
|
|
103
|
+
}));
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Module Loader
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { createModuleLoader } from '@parsrun/server/module-loader';
|
|
110
|
+
|
|
111
|
+
const loader = createModuleLoader({
|
|
112
|
+
modulesDir: './modules',
|
|
113
|
+
autoload: true,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await loader.loadModules(app);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Validation
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { validate, schemas } from '@parsrun/server/validation';
|
|
123
|
+
|
|
124
|
+
app.post('/users', validate(schemas.createUser), handler);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Exports
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { ... } from '@parsrun/server'; // Main exports
|
|
131
|
+
import { ... } from '@parsrun/server/app'; // App creation
|
|
132
|
+
import { ... } from '@parsrun/server/context'; // Context utilities
|
|
133
|
+
import { ... } from '@parsrun/server/rls'; // Row-level security
|
|
134
|
+
import { ... } from '@parsrun/server/rbac'; // Role-based access
|
|
135
|
+
import { ... } from '@parsrun/server/health'; // Health checks
|
|
136
|
+
import { ... } from '@parsrun/server/middleware'; // Middleware
|
|
137
|
+
import { ... } from '@parsrun/server/validation'; // Validation
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as hono from 'hono';
|
|
2
|
+
import { ServerConfig, HonoApp } from './context.js';
|
|
3
|
+
import '@parsrun/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extended server options
|
|
7
|
+
*/
|
|
8
|
+
interface CreateServerOptions extends ServerConfig {
|
|
9
|
+
/** Enable request logging */
|
|
10
|
+
logging?: boolean;
|
|
11
|
+
/** Enable request ID generation */
|
|
12
|
+
requestId?: boolean;
|
|
13
|
+
/** Base path for all routes */
|
|
14
|
+
basePath?: string;
|
|
15
|
+
/** Strict mode - trailing slashes matter */
|
|
16
|
+
strict?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a new Pars server instance
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const app = createServer({
|
|
24
|
+
* database: db,
|
|
25
|
+
* cors: { origin: '*' },
|
|
26
|
+
* logging: true,
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
30
|
+
*
|
|
31
|
+
* export default app;
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function createServer(options: CreateServerOptions): HonoApp;
|
|
35
|
+
/**
|
|
36
|
+
* Create a router (sub-app) with shared context
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const usersRouter = createRouter();
|
|
41
|
+
*
|
|
42
|
+
* usersRouter.get('/', async (c) => {
|
|
43
|
+
* const users = await getUsers(c.get('db'));
|
|
44
|
+
* return c.json(success(users));
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* usersRouter.post('/', async (c) => {
|
|
48
|
+
* // ...
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* app.route('/api/users', usersRouter);
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare function createRouter(): HonoApp;
|
|
55
|
+
/**
|
|
56
|
+
* Create a versioned API router
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const v1 = createVersionedRouter('v1');
|
|
61
|
+
* v1.get('/users', handler);
|
|
62
|
+
*
|
|
63
|
+
* app.route('/api', v1); // Results in /api/v1/users
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function createVersionedRouter(version: string): HonoApp;
|
|
67
|
+
/**
|
|
68
|
+
* Create a module router with prefix
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const inventoryModule = createModuleRouter('inventory', {
|
|
73
|
+
* routes: (router) => {
|
|
74
|
+
* router.get('/items', listItems);
|
|
75
|
+
* router.post('/items', createItem);
|
|
76
|
+
* },
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* app.route('/api', inventoryModule);
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
declare function createModuleRouter(moduleName: string, options: {
|
|
83
|
+
routes: (router: HonoApp) => void;
|
|
84
|
+
middleware?: Array<(c: hono.Context, next: () => Promise<void>) => Promise<Response | void>>;
|
|
85
|
+
}): HonoApp;
|
|
86
|
+
|
|
87
|
+
export { type CreateServerOptions, createModuleRouter, createRouter, createServer, createVersionedRouter };
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/app.ts
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { createLogger } from "@parsrun/core";
|
|
4
|
+
|
|
5
|
+
// src/context.ts
|
|
6
|
+
function generateRequestId() {
|
|
7
|
+
return crypto.randomUUID();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/app.ts
|
|
11
|
+
function createServer(options) {
|
|
12
|
+
const app = new Hono({
|
|
13
|
+
strict: options.strict ?? false
|
|
14
|
+
});
|
|
15
|
+
const logger = options.logger ?? createLogger({ name: "pars-server" });
|
|
16
|
+
app.use("*", async (c, next) => {
|
|
17
|
+
c.set("db", options.database);
|
|
18
|
+
c.set("config", options);
|
|
19
|
+
c.set("logger", logger);
|
|
20
|
+
c.set("enabledModules", /* @__PURE__ */ new Set());
|
|
21
|
+
c.set("cookiePrefix", options.cookiePrefix);
|
|
22
|
+
c.set("custom", options.custom ?? {});
|
|
23
|
+
if (options.requestId !== false) {
|
|
24
|
+
const requestId = c.req.header("x-request-id") ?? generateRequestId();
|
|
25
|
+
c.set("requestId", requestId);
|
|
26
|
+
c.header("x-request-id", requestId);
|
|
27
|
+
}
|
|
28
|
+
await next();
|
|
29
|
+
});
|
|
30
|
+
return app;
|
|
31
|
+
}
|
|
32
|
+
function createRouter() {
|
|
33
|
+
return new Hono();
|
|
34
|
+
}
|
|
35
|
+
function createVersionedRouter(version) {
|
|
36
|
+
const router = new Hono();
|
|
37
|
+
const versionedRouter = new Hono();
|
|
38
|
+
versionedRouter.route(`/${version}`, router);
|
|
39
|
+
return versionedRouter;
|
|
40
|
+
}
|
|
41
|
+
function createModuleRouter(moduleName, options) {
|
|
42
|
+
const moduleRouter = new Hono();
|
|
43
|
+
if (options.middleware) {
|
|
44
|
+
for (const mw of options.middleware) {
|
|
45
|
+
moduleRouter.use("*", mw);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
options.routes(moduleRouter);
|
|
49
|
+
const wrappedRouter = new Hono();
|
|
50
|
+
wrappedRouter.route(`/${moduleName}`, moduleRouter);
|
|
51
|
+
return wrappedRouter;
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
createModuleRouter,
|
|
55
|
+
createRouter,
|
|
56
|
+
createServer,
|
|
57
|
+
createVersionedRouter
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/app.ts","../src/context.ts"],"sourcesContent":["/**\n * @parsrun/server - App Factory\n * Create and configure Hono server instances\n */\n\nimport { Hono } from \"hono\";\nimport { createLogger } from \"@parsrun/core\";\nimport type {\n ServerConfig,\n ServerContextVariables,\n HonoApp,\n} from \"./context.js\";\nimport { generateRequestId } from \"./context.js\";\n\n/**\n * Extended server options\n */\nexport interface CreateServerOptions extends ServerConfig {\n /** Enable request logging */\n logging?: boolean;\n /** Enable request ID generation */\n requestId?: boolean;\n /** Base path for all routes */\n basePath?: string;\n /** Strict mode - trailing slashes matter */\n strict?: boolean;\n}\n\n/**\n * Create a new Pars server instance\n *\n * @example\n * ```typescript\n * const app = createServer({\n * database: db,\n * cors: { origin: '*' },\n * logging: true,\n * });\n *\n * app.get('/health', (c) => c.json({ status: 'ok' }));\n *\n * export default app;\n * ```\n */\nexport function createServer(options: CreateServerOptions): HonoApp {\n const app = new Hono<{ Variables: ServerContextVariables }>({\n strict: options.strict ?? false,\n });\n\n const logger = options.logger ?? createLogger({ name: \"pars-server\" });\n\n // Initialize context for all requests\n app.use(\"*\", async (c, next) => {\n // Set core context variables\n c.set(\"db\", options.database);\n c.set(\"config\", options);\n c.set(\"logger\", logger);\n c.set(\"enabledModules\", new Set<string>());\n c.set(\"cookiePrefix\", options.cookiePrefix);\n c.set(\"custom\", options.custom ?? {});\n\n // Generate request ID if enabled\n if (options.requestId !== false) {\n const requestId = c.req.header(\"x-request-id\") ?? generateRequestId();\n c.set(\"requestId\", requestId);\n c.header(\"x-request-id\", requestId);\n }\n\n await next();\n });\n\n return app;\n}\n\n/**\n * Create a router (sub-app) with shared context\n *\n * @example\n * ```typescript\n * const usersRouter = createRouter();\n *\n * usersRouter.get('/', async (c) => {\n * const users = await getUsers(c.get('db'));\n * return c.json(success(users));\n * });\n *\n * usersRouter.post('/', async (c) => {\n * // ...\n * });\n *\n * app.route('/api/users', usersRouter);\n * ```\n */\nexport function createRouter(): HonoApp {\n return new Hono<{ Variables: ServerContextVariables }>();\n}\n\n/**\n * Create a versioned API router\n *\n * @example\n * ```typescript\n * const v1 = createVersionedRouter('v1');\n * v1.get('/users', handler);\n *\n * app.route('/api', v1); // Results in /api/v1/users\n * ```\n */\nexport function createVersionedRouter(version: string): HonoApp {\n const router = new Hono<{ Variables: ServerContextVariables }>();\n\n // Add version to all routes\n const versionedRouter = new Hono<{ Variables: ServerContextVariables }>();\n versionedRouter.route(`/${version}`, router);\n\n return versionedRouter;\n}\n\n/**\n * Create a module router with prefix\n *\n * @example\n * ```typescript\n * const inventoryModule = createModuleRouter('inventory', {\n * routes: (router) => {\n * router.get('/items', listItems);\n * router.post('/items', createItem);\n * },\n * });\n *\n * app.route('/api', inventoryModule);\n * ```\n */\nexport function createModuleRouter(\n moduleName: string,\n options: {\n routes: (router: HonoApp) => void;\n middleware?: Array<(c: import(\"hono\").Context, next: () => Promise<void>) => Promise<Response | void>>;\n }\n): HonoApp {\n const moduleRouter = new Hono<{ Variables: ServerContextVariables }>();\n\n // Apply module-specific middleware\n if (options.middleware) {\n for (const mw of options.middleware) {\n moduleRouter.use(\"*\", mw);\n }\n }\n\n // Register routes\n options.routes(moduleRouter);\n\n // Wrap in module path\n const wrappedRouter = new Hono<{ Variables: ServerContextVariables }>();\n wrappedRouter.route(`/${moduleName}`, moduleRouter);\n\n return wrappedRouter;\n}\n","/**\n * @parsrun/server - Server Context\n * Type definitions for server context and configuration\n */\n\nimport type { Logger } from \"@parsrun/core\";\n\n/**\n * Database adapter interface\n * Implement this for your database (Drizzle, Prisma, etc.)\n */\nexport interface DatabaseAdapter {\n /** Execute raw SQL query */\n execute(sql: string): Promise<unknown>;\n /** Check connection */\n ping?(): Promise<boolean>;\n}\n\n/**\n * Module manifest for registering modules\n */\nexport interface ModuleManifest {\n /** Unique module name */\n name: string;\n /** Module version */\n version: string;\n /** Module description */\n description: string;\n /** Required permissions for this module */\n permissions: Record<string, string[]>;\n /** Module dependencies (other module names) */\n dependencies?: string[];\n /** Register routes for this module */\n registerRoutes: (app: HonoApp) => void;\n /** Called when module is enabled */\n onEnable?: () => Promise<void>;\n /** Called when module is disabled */\n onDisable?: () => Promise<void>;\n}\n\n/**\n * Server configuration\n */\nexport interface ServerConfig {\n /** Database adapter */\n database: DatabaseAdapter;\n /** CORS configuration */\n cors?: CorsConfig;\n /** Base path for API */\n basePath?: string;\n /** Cookie prefix for all cookies */\n cookiePrefix?: string;\n /** Logger instance */\n logger?: Logger;\n /** Custom context data */\n custom?: Record<string, unknown>;\n}\n\n/**\n * CORS configuration\n */\nexport interface CorsConfig {\n /** Allowed origins */\n origin: string | string[] | ((origin: string) => boolean);\n /** Allow credentials */\n credentials?: boolean;\n /** Allowed methods */\n methods?: string[];\n /** Allowed headers */\n allowedHeaders?: string[];\n /** Exposed headers */\n exposedHeaders?: string[];\n /** Max age in seconds */\n maxAge?: number;\n}\n\n/**\n * User information in context\n */\nexport interface ContextUser {\n id: string;\n email: string | undefined;\n tenantId: string | undefined;\n role: string | undefined;\n permissions: string[];\n}\n\n/**\n * Tenant information in context\n */\nexport interface ContextTenant {\n id: string;\n slug: string | undefined;\n name: string | undefined;\n status: string;\n}\n\n/**\n * Server context variables\n * Available in Hono context via c.get()\n */\nexport interface ServerContextVariables {\n /** Database adapter */\n db: DatabaseAdapter;\n /** Server configuration */\n config: ServerConfig;\n /** Enabled modules set */\n enabledModules: Set<string>;\n /** Current user (if authenticated) */\n user: ContextUser | undefined;\n /** Current tenant (if resolved) */\n tenant: ContextTenant | undefined;\n /** Request logger */\n logger: Logger;\n /** Request ID */\n requestId: string;\n /** Cookie prefix */\n cookiePrefix: string | undefined;\n /** Custom context data */\n custom: Record<string, unknown>;\n}\n\n/**\n * Hono app type with server context\n */\nexport type HonoApp = import(\"hono\").Hono<{ Variables: ServerContextVariables }>;\n\n/**\n * Hono context type with server context\n */\nexport type HonoContext = import(\"hono\").Context<{ Variables: ServerContextVariables }>;\n\n/**\n * Middleware next function\n */\nexport type HonoNext = () => Promise<void>;\n\n/**\n * Middleware function type\n */\nexport type Middleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Route handler function type\n */\nexport type RouteHandler = (c: HonoContext) => Promise<Response> | Response;\n\n/**\n * Permission check input\n */\nexport interface PermissionCheck {\n /** Resource name (e.g., \"users\", \"items\") */\n resource: string;\n /** Action name (e.g., \"read\", \"create\", \"update\", \"delete\") */\n action: string;\n /** Permission scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Permission definition\n */\nexport interface PermissionDefinition {\n /** Permission name (e.g., \"users:read\") */\n name: string;\n /** Resource part */\n resource: string;\n /** Action part */\n action: string;\n /** Description */\n description?: string;\n /** Scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Role definition\n */\nexport interface RoleDefinition {\n /** Role name */\n name: string;\n /** Display name */\n displayName?: string;\n /** Description */\n description?: string;\n /** Permissions assigned to this role */\n permissions: string[];\n /** Is this a system role */\n isSystem?: boolean;\n}\n\n/**\n * Standard API response structure\n */\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown> | undefined;\n };\n meta?: {\n page?: number | undefined;\n limit?: number | undefined;\n total?: number | undefined;\n requestId?: string | undefined;\n } | undefined;\n}\n\n/**\n * Create a success response\n */\nexport function success<T>(data: T, meta?: ApiResponse[\"meta\"]): ApiResponse<T> {\n return {\n success: true,\n data,\n meta: meta ?? undefined,\n };\n}\n\n/**\n * Create an error response\n */\nexport function error(\n code: string,\n message: string,\n details?: Record<string, unknown>\n): ApiResponse<never> {\n return {\n success: false,\n error: { code, message, details: details ?? undefined },\n };\n}\n\n/**\n * Generate a request ID\n */\nexport function generateRequestId(): string {\n return crypto.randomUUID();\n}\n"],"mappings":";AAKA,SAAS,YAAY;AACrB,SAAS,oBAAoB;;;ACwOtB,SAAS,oBAA4B;AAC1C,SAAO,OAAO,WAAW;AAC3B;;;ADpMO,SAAS,aAAa,SAAuC;AAClE,QAAM,MAAM,IAAI,KAA4C;AAAA,IAC1D,QAAQ,QAAQ,UAAU;AAAA,EAC5B,CAAC;AAED,QAAM,SAAS,QAAQ,UAAU,aAAa,EAAE,MAAM,cAAc,CAAC;AAGrE,MAAI,IAAI,KAAK,OAAO,GAAG,SAAS;AAE9B,MAAE,IAAI,MAAM,QAAQ,QAAQ;AAC5B,MAAE,IAAI,UAAU,OAAO;AACvB,MAAE,IAAI,UAAU,MAAM;AACtB,MAAE,IAAI,kBAAkB,oBAAI,IAAY,CAAC;AACzC,MAAE,IAAI,gBAAgB,QAAQ,YAAY;AAC1C,MAAE,IAAI,UAAU,QAAQ,UAAU,CAAC,CAAC;AAGpC,QAAI,QAAQ,cAAc,OAAO;AAC/B,YAAM,YAAY,EAAE,IAAI,OAAO,cAAc,KAAK,kBAAkB;AACpE,QAAE,IAAI,aAAa,SAAS;AAC5B,QAAE,OAAO,gBAAgB,SAAS;AAAA,IACpC;AAEA,UAAM,KAAK;AAAA,EACb,CAAC;AAED,SAAO;AACT;AAqBO,SAAS,eAAwB;AACtC,SAAO,IAAI,KAA4C;AACzD;AAaO,SAAS,sBAAsB,SAA0B;AAC9D,QAAM,SAAS,IAAI,KAA4C;AAG/D,QAAM,kBAAkB,IAAI,KAA4C;AACxE,kBAAgB,MAAM,IAAI,OAAO,IAAI,MAAM;AAE3C,SAAO;AACT;AAiBO,SAAS,mBACd,YACA,SAIS;AACT,QAAM,eAAe,IAAI,KAA4C;AAGrE,MAAI,QAAQ,YAAY;AACtB,eAAW,MAAM,QAAQ,YAAY;AACnC,mBAAa,IAAI,KAAK,EAAE;AAAA,IAC1B;AAAA,EACF;AAGA,UAAQ,OAAO,YAAY;AAG3B,QAAM,gBAAgB,IAAI,KAA4C;AACtE,gBAAc,MAAM,IAAI,UAAU,IAAI,YAAY;AAElD,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as hono from 'hono';
|
|
2
|
+
import { Logger } from '@parsrun/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Database adapter interface
|
|
6
|
+
* Implement this for your database (Drizzle, Prisma, etc.)
|
|
7
|
+
*/
|
|
8
|
+
interface DatabaseAdapter {
|
|
9
|
+
/** Execute raw SQL query */
|
|
10
|
+
execute(sql: string): Promise<unknown>;
|
|
11
|
+
/** Check connection */
|
|
12
|
+
ping?(): Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Module manifest for registering modules
|
|
16
|
+
*/
|
|
17
|
+
interface ModuleManifest {
|
|
18
|
+
/** Unique module name */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Module version */
|
|
21
|
+
version: string;
|
|
22
|
+
/** Module description */
|
|
23
|
+
description: string;
|
|
24
|
+
/** Required permissions for this module */
|
|
25
|
+
permissions: Record<string, string[]>;
|
|
26
|
+
/** Module dependencies (other module names) */
|
|
27
|
+
dependencies?: string[];
|
|
28
|
+
/** Register routes for this module */
|
|
29
|
+
registerRoutes: (app: HonoApp) => void;
|
|
30
|
+
/** Called when module is enabled */
|
|
31
|
+
onEnable?: () => Promise<void>;
|
|
32
|
+
/** Called when module is disabled */
|
|
33
|
+
onDisable?: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Server configuration
|
|
37
|
+
*/
|
|
38
|
+
interface ServerConfig {
|
|
39
|
+
/** Database adapter */
|
|
40
|
+
database: DatabaseAdapter;
|
|
41
|
+
/** CORS configuration */
|
|
42
|
+
cors?: CorsConfig;
|
|
43
|
+
/** Base path for API */
|
|
44
|
+
basePath?: string;
|
|
45
|
+
/** Cookie prefix for all cookies */
|
|
46
|
+
cookiePrefix?: string;
|
|
47
|
+
/** Logger instance */
|
|
48
|
+
logger?: Logger;
|
|
49
|
+
/** Custom context data */
|
|
50
|
+
custom?: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* CORS configuration
|
|
54
|
+
*/
|
|
55
|
+
interface CorsConfig {
|
|
56
|
+
/** Allowed origins */
|
|
57
|
+
origin: string | string[] | ((origin: string) => boolean);
|
|
58
|
+
/** Allow credentials */
|
|
59
|
+
credentials?: boolean;
|
|
60
|
+
/** Allowed methods */
|
|
61
|
+
methods?: string[];
|
|
62
|
+
/** Allowed headers */
|
|
63
|
+
allowedHeaders?: string[];
|
|
64
|
+
/** Exposed headers */
|
|
65
|
+
exposedHeaders?: string[];
|
|
66
|
+
/** Max age in seconds */
|
|
67
|
+
maxAge?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* User information in context
|
|
71
|
+
*/
|
|
72
|
+
interface ContextUser {
|
|
73
|
+
id: string;
|
|
74
|
+
email: string | undefined;
|
|
75
|
+
tenantId: string | undefined;
|
|
76
|
+
role: string | undefined;
|
|
77
|
+
permissions: string[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Tenant information in context
|
|
81
|
+
*/
|
|
82
|
+
interface ContextTenant {
|
|
83
|
+
id: string;
|
|
84
|
+
slug: string | undefined;
|
|
85
|
+
name: string | undefined;
|
|
86
|
+
status: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Server context variables
|
|
90
|
+
* Available in Hono context via c.get()
|
|
91
|
+
*/
|
|
92
|
+
interface ServerContextVariables {
|
|
93
|
+
/** Database adapter */
|
|
94
|
+
db: DatabaseAdapter;
|
|
95
|
+
/** Server configuration */
|
|
96
|
+
config: ServerConfig;
|
|
97
|
+
/** Enabled modules set */
|
|
98
|
+
enabledModules: Set<string>;
|
|
99
|
+
/** Current user (if authenticated) */
|
|
100
|
+
user: ContextUser | undefined;
|
|
101
|
+
/** Current tenant (if resolved) */
|
|
102
|
+
tenant: ContextTenant | undefined;
|
|
103
|
+
/** Request logger */
|
|
104
|
+
logger: Logger;
|
|
105
|
+
/** Request ID */
|
|
106
|
+
requestId: string;
|
|
107
|
+
/** Cookie prefix */
|
|
108
|
+
cookiePrefix: string | undefined;
|
|
109
|
+
/** Custom context data */
|
|
110
|
+
custom: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Hono app type with server context
|
|
114
|
+
*/
|
|
115
|
+
type HonoApp = hono.Hono<{
|
|
116
|
+
Variables: ServerContextVariables;
|
|
117
|
+
}>;
|
|
118
|
+
/**
|
|
119
|
+
* Hono context type with server context
|
|
120
|
+
*/
|
|
121
|
+
type HonoContext = hono.Context<{
|
|
122
|
+
Variables: ServerContextVariables;
|
|
123
|
+
}>;
|
|
124
|
+
/**
|
|
125
|
+
* Middleware next function
|
|
126
|
+
*/
|
|
127
|
+
type HonoNext = () => Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Middleware function type
|
|
130
|
+
*/
|
|
131
|
+
type Middleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;
|
|
132
|
+
/**
|
|
133
|
+
* Route handler function type
|
|
134
|
+
*/
|
|
135
|
+
type RouteHandler = (c: HonoContext) => Promise<Response> | Response;
|
|
136
|
+
/**
|
|
137
|
+
* Permission check input
|
|
138
|
+
*/
|
|
139
|
+
interface PermissionCheck {
|
|
140
|
+
/** Resource name (e.g., "users", "items") */
|
|
141
|
+
resource: string;
|
|
142
|
+
/** Action name (e.g., "read", "create", "update", "delete") */
|
|
143
|
+
action: string;
|
|
144
|
+
/** Permission scope */
|
|
145
|
+
scope?: "tenant" | "global" | "own";
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Permission definition
|
|
149
|
+
*/
|
|
150
|
+
interface PermissionDefinition {
|
|
151
|
+
/** Permission name (e.g., "users:read") */
|
|
152
|
+
name: string;
|
|
153
|
+
/** Resource part */
|
|
154
|
+
resource: string;
|
|
155
|
+
/** Action part */
|
|
156
|
+
action: string;
|
|
157
|
+
/** Description */
|
|
158
|
+
description?: string;
|
|
159
|
+
/** Scope */
|
|
160
|
+
scope?: "tenant" | "global" | "own";
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Role definition
|
|
164
|
+
*/
|
|
165
|
+
interface RoleDefinition {
|
|
166
|
+
/** Role name */
|
|
167
|
+
name: string;
|
|
168
|
+
/** Display name */
|
|
169
|
+
displayName?: string;
|
|
170
|
+
/** Description */
|
|
171
|
+
description?: string;
|
|
172
|
+
/** Permissions assigned to this role */
|
|
173
|
+
permissions: string[];
|
|
174
|
+
/** Is this a system role */
|
|
175
|
+
isSystem?: boolean;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Standard API response structure
|
|
179
|
+
*/
|
|
180
|
+
interface ApiResponse<T = unknown> {
|
|
181
|
+
success: boolean;
|
|
182
|
+
data?: T;
|
|
183
|
+
error?: {
|
|
184
|
+
code: string;
|
|
185
|
+
message: string;
|
|
186
|
+
details?: Record<string, unknown> | undefined;
|
|
187
|
+
};
|
|
188
|
+
meta?: {
|
|
189
|
+
page?: number | undefined;
|
|
190
|
+
limit?: number | undefined;
|
|
191
|
+
total?: number | undefined;
|
|
192
|
+
requestId?: string | undefined;
|
|
193
|
+
} | undefined;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Create a success response
|
|
197
|
+
*/
|
|
198
|
+
declare function success<T>(data: T, meta?: ApiResponse["meta"]): ApiResponse<T>;
|
|
199
|
+
/**
|
|
200
|
+
* Create an error response
|
|
201
|
+
*/
|
|
202
|
+
declare function error(code: string, message: string, details?: Record<string, unknown>): ApiResponse<never>;
|
|
203
|
+
/**
|
|
204
|
+
* Generate a request ID
|
|
205
|
+
*/
|
|
206
|
+
declare function generateRequestId(): string;
|
|
207
|
+
|
|
208
|
+
export { type ApiResponse, type ContextTenant, type ContextUser, type CorsConfig, type DatabaseAdapter, type HonoApp, type HonoContext, type HonoNext, type Middleware, type ModuleManifest, type PermissionCheck, type PermissionDefinition, type RoleDefinition, type RouteHandler, type ServerConfig, type ServerContextVariables, error, generateRequestId, success };
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
function success(data, meta) {
|
|
3
|
+
return {
|
|
4
|
+
success: true,
|
|
5
|
+
data,
|
|
6
|
+
meta: meta ?? void 0
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function error(code, message, details) {
|
|
10
|
+
return {
|
|
11
|
+
success: false,
|
|
12
|
+
error: { code, message, details: details ?? void 0 }
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function generateRequestId() {
|
|
16
|
+
return crypto.randomUUID();
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
error,
|
|
20
|
+
generateRequestId,
|
|
21
|
+
success
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts"],"sourcesContent":["/**\n * @parsrun/server - Server Context\n * Type definitions for server context and configuration\n */\n\nimport type { Logger } from \"@parsrun/core\";\n\n/**\n * Database adapter interface\n * Implement this for your database (Drizzle, Prisma, etc.)\n */\nexport interface DatabaseAdapter {\n /** Execute raw SQL query */\n execute(sql: string): Promise<unknown>;\n /** Check connection */\n ping?(): Promise<boolean>;\n}\n\n/**\n * Module manifest for registering modules\n */\nexport interface ModuleManifest {\n /** Unique module name */\n name: string;\n /** Module version */\n version: string;\n /** Module description */\n description: string;\n /** Required permissions for this module */\n permissions: Record<string, string[]>;\n /** Module dependencies (other module names) */\n dependencies?: string[];\n /** Register routes for this module */\n registerRoutes: (app: HonoApp) => void;\n /** Called when module is enabled */\n onEnable?: () => Promise<void>;\n /** Called when module is disabled */\n onDisable?: () => Promise<void>;\n}\n\n/**\n * Server configuration\n */\nexport interface ServerConfig {\n /** Database adapter */\n database: DatabaseAdapter;\n /** CORS configuration */\n cors?: CorsConfig;\n /** Base path for API */\n basePath?: string;\n /** Cookie prefix for all cookies */\n cookiePrefix?: string;\n /** Logger instance */\n logger?: Logger;\n /** Custom context data */\n custom?: Record<string, unknown>;\n}\n\n/**\n * CORS configuration\n */\nexport interface CorsConfig {\n /** Allowed origins */\n origin: string | string[] | ((origin: string) => boolean);\n /** Allow credentials */\n credentials?: boolean;\n /** Allowed methods */\n methods?: string[];\n /** Allowed headers */\n allowedHeaders?: string[];\n /** Exposed headers */\n exposedHeaders?: string[];\n /** Max age in seconds */\n maxAge?: number;\n}\n\n/**\n * User information in context\n */\nexport interface ContextUser {\n id: string;\n email: string | undefined;\n tenantId: string | undefined;\n role: string | undefined;\n permissions: string[];\n}\n\n/**\n * Tenant information in context\n */\nexport interface ContextTenant {\n id: string;\n slug: string | undefined;\n name: string | undefined;\n status: string;\n}\n\n/**\n * Server context variables\n * Available in Hono context via c.get()\n */\nexport interface ServerContextVariables {\n /** Database adapter */\n db: DatabaseAdapter;\n /** Server configuration */\n config: ServerConfig;\n /** Enabled modules set */\n enabledModules: Set<string>;\n /** Current user (if authenticated) */\n user: ContextUser | undefined;\n /** Current tenant (if resolved) */\n tenant: ContextTenant | undefined;\n /** Request logger */\n logger: Logger;\n /** Request ID */\n requestId: string;\n /** Cookie prefix */\n cookiePrefix: string | undefined;\n /** Custom context data */\n custom: Record<string, unknown>;\n}\n\n/**\n * Hono app type with server context\n */\nexport type HonoApp = import(\"hono\").Hono<{ Variables: ServerContextVariables }>;\n\n/**\n * Hono context type with server context\n */\nexport type HonoContext = import(\"hono\").Context<{ Variables: ServerContextVariables }>;\n\n/**\n * Middleware next function\n */\nexport type HonoNext = () => Promise<void>;\n\n/**\n * Middleware function type\n */\nexport type Middleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Route handler function type\n */\nexport type RouteHandler = (c: HonoContext) => Promise<Response> | Response;\n\n/**\n * Permission check input\n */\nexport interface PermissionCheck {\n /** Resource name (e.g., \"users\", \"items\") */\n resource: string;\n /** Action name (e.g., \"read\", \"create\", \"update\", \"delete\") */\n action: string;\n /** Permission scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Permission definition\n */\nexport interface PermissionDefinition {\n /** Permission name (e.g., \"users:read\") */\n name: string;\n /** Resource part */\n resource: string;\n /** Action part */\n action: string;\n /** Description */\n description?: string;\n /** Scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Role definition\n */\nexport interface RoleDefinition {\n /** Role name */\n name: string;\n /** Display name */\n displayName?: string;\n /** Description */\n description?: string;\n /** Permissions assigned to this role */\n permissions: string[];\n /** Is this a system role */\n isSystem?: boolean;\n}\n\n/**\n * Standard API response structure\n */\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown> | undefined;\n };\n meta?: {\n page?: number | undefined;\n limit?: number | undefined;\n total?: number | undefined;\n requestId?: string | undefined;\n } | undefined;\n}\n\n/**\n * Create a success response\n */\nexport function success<T>(data: T, meta?: ApiResponse[\"meta\"]): ApiResponse<T> {\n return {\n success: true,\n data,\n meta: meta ?? undefined,\n };\n}\n\n/**\n * Create an error response\n */\nexport function error(\n code: string,\n message: string,\n details?: Record<string, unknown>\n): ApiResponse<never> {\n return {\n success: false,\n error: { code, message, details: details ?? undefined },\n };\n}\n\n/**\n * Generate a request ID\n */\nexport function generateRequestId(): string {\n return crypto.randomUUID();\n}\n"],"mappings":";AAqNO,SAAS,QAAW,MAAS,MAA4C;AAC9E,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,MAAM,QAAQ;AAAA,EAChB;AACF;AAKO,SAAS,MACd,MACA,SACA,SACoB;AACpB,SAAO;AAAA,IACL,SAAS;AAAA,IACT,OAAO,EAAE,MAAM,SAAS,SAAS,WAAW,OAAU;AAAA,EACxD;AACF;AAKO,SAAS,oBAA4B;AAC1C,SAAO,OAAO,WAAW;AAC3B;","names":[]}
|
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
2
|
+
import * as hono from 'hono';
|
|
3
|
+
import { HonoApp, ServerContextVariables } from './context.js';
|
|
4
|
+
import '@parsrun/core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Health check status
|
|
8
|
+
*/
|
|
9
|
+
type HealthStatus = "healthy" | "degraded" | "unhealthy";
|
|
10
|
+
/**
|
|
11
|
+
* Health check result
|
|
12
|
+
*/
|
|
13
|
+
interface HealthCheckResult {
|
|
14
|
+
status: HealthStatus;
|
|
15
|
+
message?: string;
|
|
16
|
+
latency?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Health check function
|
|
20
|
+
*/
|
|
21
|
+
type HealthCheck = () => Promise<HealthCheckResult> | HealthCheckResult;
|
|
22
|
+
/**
|
|
23
|
+
* Health response
|
|
24
|
+
*/
|
|
25
|
+
interface HealthResponse {
|
|
26
|
+
status: HealthStatus;
|
|
27
|
+
timestamp: string;
|
|
28
|
+
uptime: number;
|
|
29
|
+
checks: Record<string, HealthCheckResult>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Health check options
|
|
33
|
+
*/
|
|
34
|
+
interface HealthCheckOptions {
|
|
35
|
+
/** Custom health checks */
|
|
36
|
+
checks?: Record<string, HealthCheck>;
|
|
37
|
+
/** Include detailed info (disable in production) */
|
|
38
|
+
detailed?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create health check router
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const health = createHealthRouter({
|
|
46
|
+
* checks: {
|
|
47
|
+
* redis: async () => {
|
|
48
|
+
* await redis.ping();
|
|
49
|
+
* return { status: 'healthy' };
|
|
50
|
+
* },
|
|
51
|
+
* external: async () => {
|
|
52
|
+
* const res = await fetch('https://api.example.com/health');
|
|
53
|
+
* return { status: res.ok ? 'healthy' : 'unhealthy' };
|
|
54
|
+
* },
|
|
55
|
+
* },
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* app.route('/health', health);
|
|
59
|
+
* // GET /health - Full health check
|
|
60
|
+
* // GET /health/live - Liveness probe
|
|
61
|
+
* // GET /health/ready - Readiness probe
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function createHealthRouter(options?: HealthCheckOptions): HonoApp;
|
|
65
|
+
/**
|
|
66
|
+
* Simple health endpoint handler
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* app.get('/health', healthHandler);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function healthHandler(c: hono.Context<{
|
|
74
|
+
Variables: ServerContextVariables;
|
|
75
|
+
}>): Promise<Response & hono.TypedResponse<{
|
|
76
|
+
status: string;
|
|
77
|
+
timestamp: string;
|
|
78
|
+
uptime: number;
|
|
79
|
+
}, hono_utils_http_status.ContentfulStatusCode, "json">>;
|
|
80
|
+
|
|
81
|
+
export { type HealthCheck, type HealthCheckOptions, type HealthCheckResult, type HealthResponse, type HealthStatus, createHealthRouter, healthHandler };
|