@omni-api/core 0.0.1
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 +92 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +372 -0
- package/dist/index.js.map +1 -0
- package/dist/registry-ZWfxJhSP.d.ts +263 -0
- package/dist/utils/index.d.ts +171 -0
- package/dist/utils/index.js +221 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @omni/core
|
|
2
|
+
|
|
3
|
+
OmniAPI 的核心包:定义 Procedure / 组合 Middleware / 注册到 Registry / 通过 runProcedure 执行。
|
|
4
|
+
|
|
5
|
+
> 本包不绑定任何具体协议(HTTP / MCP),所有出口由独立的 Adapter 包负责。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @omni/core zod
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 5 分钟速览
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import {
|
|
18
|
+
defineProcedure,
|
|
19
|
+
createRouter,
|
|
20
|
+
Registry,
|
|
21
|
+
runByName,
|
|
22
|
+
createContext,
|
|
23
|
+
} from '@omni/core';
|
|
24
|
+
|
|
25
|
+
// 1. 定义一个 Procedure
|
|
26
|
+
const createOrder = defineProcedure({
|
|
27
|
+
name: 'order.create',
|
|
28
|
+
description: '创建一笔订单',
|
|
29
|
+
input: z.object({ sku: z.string(), qty: z.number().int().positive() }),
|
|
30
|
+
output: z.object({ orderId: z.string() }),
|
|
31
|
+
meta: {
|
|
32
|
+
http: { method: 'POST', path: '/orders' },
|
|
33
|
+
mcp: { category: 'commerce', requireConfirmation: true },
|
|
34
|
+
},
|
|
35
|
+
handler: async ({ input }) => ({ orderId: `O_${input.sku}` }),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// 2. 注册到 Registry
|
|
39
|
+
const registry = new Registry();
|
|
40
|
+
registry.registerRouter(
|
|
41
|
+
createRouter({
|
|
42
|
+
namespace: 'order',
|
|
43
|
+
procedures: [createOrder],
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// 3. 调用(任何 Adapter 内部都是这么调的)
|
|
48
|
+
const result = await runByName(
|
|
49
|
+
registry,
|
|
50
|
+
'order.create',
|
|
51
|
+
{ sku: 'X1', qty: 1 },
|
|
52
|
+
createContext({ source: 'test' }),
|
|
53
|
+
);
|
|
54
|
+
console.log(result); // { orderId: 'O_X1' }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 导出 LLM 工具 JSON(逃生出口)
|
|
58
|
+
|
|
59
|
+
给不支持 MCP 的老 Agent 平台用:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { exportToolsJSON } from '@omni/core/utils';
|
|
63
|
+
|
|
64
|
+
const openaiTools = exportToolsJSON(registry, { format: 'openai' });
|
|
65
|
+
const claudeTools = exportToolsJSON(registry, { format: 'anthropic' });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API
|
|
69
|
+
|
|
70
|
+
### 核心
|
|
71
|
+
- `defineProcedure(def)` - 定义一个 Procedure
|
|
72
|
+
- `createRouter(def)` - 把多个 Procedure 组合 + 共享中间件
|
|
73
|
+
- `Registry` - 全局注册中心
|
|
74
|
+
- `runProcedure(proc, input, ctx, options?)` - 直接执行
|
|
75
|
+
- `runByName(registry, name, input, ctx, options?)` - 通过 Registry 执行
|
|
76
|
+
|
|
77
|
+
### 工具
|
|
78
|
+
- `compose(middlewares)` - 组合中间件链
|
|
79
|
+
- `createContext(partial)` - 创建 Context(测试 / Adapter 用)
|
|
80
|
+
- `getNamespace(p)` - 取 procedure 的 namespace
|
|
81
|
+
|
|
82
|
+
### 错误
|
|
83
|
+
- `OmniError` 及子类:`ValidationError` / `UnauthorizedError` / `ForbiddenError` / `NotFoundError` / `ConflictError` / `RateLimitError` / `InternalError`
|
|
84
|
+
- `toOmniError(err)` - 把任意错误规范化为 OmniError
|
|
85
|
+
|
|
86
|
+
### `@omni/core/utils`
|
|
87
|
+
- `zodToJsonSchema(schema)` - Zod → JSON Schema
|
|
88
|
+
- `exportToolsJSON(registry, opts)` - 导出 LLM 工具数组(OpenAI / Anthropic / Generic)
|
|
89
|
+
|
|
90
|
+
## 设计文档
|
|
91
|
+
|
|
92
|
+
详见根目录 `docs/`。
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { M as Middleware, C as Context, R as Registry, P as Procedure } from './registry-ZWfxJhSP.js';
|
|
2
|
+
export { A as AdapterName, a as AnyProcedure, b as ContextSource, L as ListFilter, c as Logger, d as ProcedureDef, e as ProcedureMeta, f as RegistryOptions, g as Router, h as RouterDef, U as UserPrincipal, i as compose, j as consoleLogger, k as createContext, l as createRouter, m as defineProcedure, n as getNamespace } from './registry-ZWfxJhSP.js';
|
|
3
|
+
import { ZodTypeAny, z } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OmniAPI 错误体系
|
|
7
|
+
*
|
|
8
|
+
* 业务原则:
|
|
9
|
+
* - 业务代码应抛出这些类的实例,而不是字符串或原生 Error
|
|
10
|
+
* - 各 Adapter 负责把 OmniError 映射为协议层错误(HTTP status / MCP error code 等)
|
|
11
|
+
*/
|
|
12
|
+
interface OmniErrorOptions {
|
|
13
|
+
/** 业务错误码(机器可读,如 "ORDER_NOT_FOUND") */
|
|
14
|
+
code?: string;
|
|
15
|
+
/** 附加详情(建议是可序列化的对象) */
|
|
16
|
+
details?: unknown;
|
|
17
|
+
/** 原始错误(用于错误链追溯) */
|
|
18
|
+
cause?: unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 框架根错误。所有可预期的业务/协议错误都应继承此类。
|
|
22
|
+
*/
|
|
23
|
+
declare class OmniError extends Error {
|
|
24
|
+
/** 业务错误码 */
|
|
25
|
+
readonly code: string;
|
|
26
|
+
/** 默认 HTTP 状态码(Adapter 可覆盖) */
|
|
27
|
+
readonly status: number;
|
|
28
|
+
/** 附加详情 */
|
|
29
|
+
readonly details?: unknown;
|
|
30
|
+
constructor(message: string, options?: OmniErrorOptions & {
|
|
31
|
+
status?: number;
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* 序列化为可对外暴露的 JSON 结构。
|
|
35
|
+
*
|
|
36
|
+
* @param options.production - true 时对 5xx 错误做安全脱敏:
|
|
37
|
+
* - message 替换为通用文案
|
|
38
|
+
* - details 隐藏(避免泄漏 SQL / 文件路径 / 堆栈)
|
|
39
|
+
* 4xx 错误(业务错误)仍保留 message + details,方便客户端处理。
|
|
40
|
+
*/
|
|
41
|
+
toJSON(options?: {
|
|
42
|
+
production?: boolean;
|
|
43
|
+
}): {
|
|
44
|
+
code: string;
|
|
45
|
+
message: string;
|
|
46
|
+
details?: unknown;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** 输入校验失败 */
|
|
50
|
+
declare class ValidationError extends OmniError {
|
|
51
|
+
constructor(message?: string, options?: OmniErrorOptions);
|
|
52
|
+
}
|
|
53
|
+
/** 未认证 */
|
|
54
|
+
declare class UnauthorizedError extends OmniError {
|
|
55
|
+
constructor(message?: string, options?: OmniErrorOptions);
|
|
56
|
+
}
|
|
57
|
+
/** 已认证但无权限 */
|
|
58
|
+
declare class ForbiddenError extends OmniError {
|
|
59
|
+
constructor(message?: string, options?: OmniErrorOptions);
|
|
60
|
+
}
|
|
61
|
+
/** 资源不存在 */
|
|
62
|
+
declare class NotFoundError extends OmniError {
|
|
63
|
+
constructor(message?: string, options?: OmniErrorOptions);
|
|
64
|
+
}
|
|
65
|
+
/** 资源冲突(如重复创建) */
|
|
66
|
+
declare class ConflictError extends OmniError {
|
|
67
|
+
constructor(message?: string, options?: OmniErrorOptions);
|
|
68
|
+
}
|
|
69
|
+
/** 触发限流 */
|
|
70
|
+
declare class RateLimitError extends OmniError {
|
|
71
|
+
readonly retryAfterMs?: number;
|
|
72
|
+
constructor(message?: string, options?: OmniErrorOptions & {
|
|
73
|
+
retryAfterMs?: number;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** 服务端内部错误(兜底) */
|
|
77
|
+
declare class InternalError extends OmniError {
|
|
78
|
+
constructor(message?: string, options?: OmniErrorOptions);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 把任意 thrown 值规范化为 OmniError。
|
|
82
|
+
* 用于 Adapter 在最外层兜底,避免业务漏抛原生 Error 时信息丢失。
|
|
83
|
+
*/
|
|
84
|
+
declare function toOmniError(err: unknown): OmniError;
|
|
85
|
+
|
|
86
|
+
interface RunOptions {
|
|
87
|
+
/**
|
|
88
|
+
* 是否在 handler 执行后校验 output。
|
|
89
|
+
* 默认仅在 NODE_ENV !== 'production' 时校验,避免生产环境性能损耗。
|
|
90
|
+
*/
|
|
91
|
+
validateOutput?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* 显式注入的中间件链。
|
|
94
|
+
*
|
|
95
|
+
* - 不传 → 使用 procedure.middleware(直接调用场景,例如测试)
|
|
96
|
+
* - 传入 → 通常由 Adapter 从 Registry.getMiddleware(name) 取得(合并了 Router 共享中间件)
|
|
97
|
+
*/
|
|
98
|
+
middleware?: ReadonlyArray<Middleware<Context>>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 执行一个 Procedure —— 整个框架的"心脏"。
|
|
102
|
+
*
|
|
103
|
+
* 流程:
|
|
104
|
+
* 1. 用 input schema 校验 rawInput(失败 → ValidationError)
|
|
105
|
+
* 2. 组合中间件链
|
|
106
|
+
* 3. 链尾调用 handler
|
|
107
|
+
* 4. 可选:用 output schema 校验返回值(开发期默认开)
|
|
108
|
+
* 5. 任何抛错统一规范化为 OmniError
|
|
109
|
+
*/
|
|
110
|
+
declare function runProcedure<Input extends ZodTypeAny, Output extends ZodTypeAny, C extends Context = Context>(procedure: Procedure<Input, Output, C>, rawInput: unknown, ctx: C, options?: RunOptions): Promise<z.infer<Output>>;
|
|
111
|
+
/**
|
|
112
|
+
* 通过 Registry 调用 Procedure(按 name 查找并使用合并后的中间件链)。
|
|
113
|
+
* Adapter 通常调用这个函数,而非直接 runProcedure。
|
|
114
|
+
*/
|
|
115
|
+
declare function runByName(registry: Registry, name: string, rawInput: unknown, ctx: Context, options?: Omit<RunOptions, 'middleware'>): Promise<unknown>;
|
|
116
|
+
|
|
117
|
+
export { ConflictError, Context, ForbiddenError, InternalError, Middleware, NotFoundError, OmniError, type OmniErrorOptions, Procedure, RateLimitError, Registry, type RunOptions, UnauthorizedError, ValidationError, runByName, runProcedure, toOmniError };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/errors.ts
|
|
12
|
+
var errors_exports = {};
|
|
13
|
+
__export(errors_exports, {
|
|
14
|
+
ConflictError: () => ConflictError,
|
|
15
|
+
ForbiddenError: () => ForbiddenError,
|
|
16
|
+
InternalError: () => InternalError,
|
|
17
|
+
NotFoundError: () => NotFoundError,
|
|
18
|
+
OmniError: () => OmniError,
|
|
19
|
+
RateLimitError: () => RateLimitError,
|
|
20
|
+
UnauthorizedError: () => UnauthorizedError,
|
|
21
|
+
ValidationError: () => ValidationError,
|
|
22
|
+
toOmniError: () => toOmniError
|
|
23
|
+
});
|
|
24
|
+
function toOmniError(err) {
|
|
25
|
+
if (err instanceof OmniError) return err;
|
|
26
|
+
if (err instanceof Error) {
|
|
27
|
+
return new InternalError(err.message, { cause: err });
|
|
28
|
+
}
|
|
29
|
+
return new InternalError(typeof err === "string" ? err : "Unknown error", { cause: err });
|
|
30
|
+
}
|
|
31
|
+
var OmniError, ValidationError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, RateLimitError, InternalError;
|
|
32
|
+
var init_errors = __esm({
|
|
33
|
+
"src/errors.ts"() {
|
|
34
|
+
OmniError = class extends Error {
|
|
35
|
+
/** 业务错误码 */
|
|
36
|
+
code;
|
|
37
|
+
/** 默认 HTTP 状态码(Adapter 可覆盖) */
|
|
38
|
+
status;
|
|
39
|
+
/** 附加详情 */
|
|
40
|
+
details;
|
|
41
|
+
constructor(message, options = {}) {
|
|
42
|
+
super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
43
|
+
this.name = new.target.name;
|
|
44
|
+
this.code = options.code ?? "INTERNAL";
|
|
45
|
+
this.status = options.status ?? 500;
|
|
46
|
+
this.details = options.details;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 序列化为可对外暴露的 JSON 结构。
|
|
50
|
+
*
|
|
51
|
+
* @param options.production - true 时对 5xx 错误做安全脱敏:
|
|
52
|
+
* - message 替换为通用文案
|
|
53
|
+
* - details 隐藏(避免泄漏 SQL / 文件路径 / 堆栈)
|
|
54
|
+
* 4xx 错误(业务错误)仍保留 message + details,方便客户端处理。
|
|
55
|
+
*/
|
|
56
|
+
toJSON(options = {}) {
|
|
57
|
+
const isServerError = this.status >= 500;
|
|
58
|
+
if (options.production && isServerError) {
|
|
59
|
+
return {
|
|
60
|
+
code: this.code,
|
|
61
|
+
message: "Internal server error"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
code: this.code,
|
|
66
|
+
message: this.message,
|
|
67
|
+
...this.details !== void 0 ? { details: this.details } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
ValidationError = class extends OmniError {
|
|
72
|
+
constructor(message = "Validation failed", options = {}) {
|
|
73
|
+
super(message, { code: "VALIDATION_ERROR", status: 400, ...options });
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
UnauthorizedError = class extends OmniError {
|
|
77
|
+
constructor(message = "Unauthorized", options = {}) {
|
|
78
|
+
super(message, { code: "UNAUTHORIZED", status: 401, ...options });
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
ForbiddenError = class extends OmniError {
|
|
82
|
+
constructor(message = "Forbidden", options = {}) {
|
|
83
|
+
super(message, { code: "FORBIDDEN", status: 403, ...options });
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
NotFoundError = class extends OmniError {
|
|
87
|
+
constructor(message = "Not found", options = {}) {
|
|
88
|
+
super(message, { code: "NOT_FOUND", status: 404, ...options });
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
ConflictError = class extends OmniError {
|
|
92
|
+
constructor(message = "Conflict", options = {}) {
|
|
93
|
+
super(message, { code: "CONFLICT", status: 409, ...options });
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
RateLimitError = class extends OmniError {
|
|
97
|
+
retryAfterMs;
|
|
98
|
+
constructor(message = "Too many requests", options = {}) {
|
|
99
|
+
super(message, { code: "RATE_LIMIT", status: 429, ...options });
|
|
100
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
InternalError = class extends OmniError {
|
|
104
|
+
constructor(message = "Internal server error", options = {}) {
|
|
105
|
+
super(message, { code: "INTERNAL", status: 500, ...options });
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// src/index.ts
|
|
112
|
+
init_errors();
|
|
113
|
+
|
|
114
|
+
// src/context.ts
|
|
115
|
+
var consoleLogger = createConsoleLogger();
|
|
116
|
+
function createConsoleLogger(bindings = {}) {
|
|
117
|
+
const fmt = (msg, meta) => {
|
|
118
|
+
const merged = { ...bindings, ...meta };
|
|
119
|
+
const tail = Object.keys(merged).length > 0 ? ` ${JSON.stringify(merged)}` : "";
|
|
120
|
+
return `${msg}${tail}`;
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
debug: (msg, meta) => console.debug(fmt(msg, meta)),
|
|
124
|
+
info: (msg, meta) => console.info(fmt(msg, meta)),
|
|
125
|
+
warn: (msg, meta) => console.warn(fmt(msg, meta)),
|
|
126
|
+
error: (msg, meta) => console.error(fmt(msg, meta)),
|
|
127
|
+
child: (extra) => createConsoleLogger({ ...bindings, ...extra })
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function createContext(partial) {
|
|
131
|
+
return {
|
|
132
|
+
requestId: partial.requestId ?? generateRequestId(),
|
|
133
|
+
user: partial.user,
|
|
134
|
+
logger: partial.logger ?? consoleLogger,
|
|
135
|
+
state: partial.state ?? /* @__PURE__ */ Object.create(null),
|
|
136
|
+
signal: partial.signal ?? new AbortController().signal,
|
|
137
|
+
raw: partial.raw,
|
|
138
|
+
source: partial.source
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function generateRequestId() {
|
|
142
|
+
const ts = Date.now().toString(36);
|
|
143
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
144
|
+
return `req_${ts}_${rand}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/middleware.ts
|
|
148
|
+
function compose(middlewares) {
|
|
149
|
+
const mws = middlewares.slice();
|
|
150
|
+
return function composed(ctx, finalHandler) {
|
|
151
|
+
let lastIndex = -1;
|
|
152
|
+
const dispatch = (i) => {
|
|
153
|
+
if (i <= lastIndex) {
|
|
154
|
+
return Promise.reject(new Error("next() called multiple times in middleware"));
|
|
155
|
+
}
|
|
156
|
+
lastIndex = i;
|
|
157
|
+
const fn = mws[i];
|
|
158
|
+
if (!fn) {
|
|
159
|
+
return Promise.resolve().then(finalHandler);
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
return Promise.resolve(fn(ctx, () => dispatch(i + 1)));
|
|
163
|
+
} catch (err) {
|
|
164
|
+
return Promise.reject(err);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return dispatch(0);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/procedure.ts
|
|
172
|
+
var NAME_PATTERN = /^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)*$/;
|
|
173
|
+
function defineProcedure(def) {
|
|
174
|
+
if (!def.name || typeof def.name !== "string") {
|
|
175
|
+
throw new Error("defineProcedure: `name` is required and must be a string");
|
|
176
|
+
}
|
|
177
|
+
if (!NAME_PATTERN.test(def.name)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`defineProcedure: invalid name "${def.name}". Expected pattern like "order.create" (lowercase, dot-separated)`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (!def.input || typeof def.input.parse !== "function") {
|
|
183
|
+
throw new Error(`defineProcedure(${def.name}): \`input\` must be a Zod schema`);
|
|
184
|
+
}
|
|
185
|
+
if (!def.output || typeof def.output.parse !== "function") {
|
|
186
|
+
throw new Error(`defineProcedure(${def.name}): \`output\` must be a Zod schema`);
|
|
187
|
+
}
|
|
188
|
+
if (typeof def.handler !== "function") {
|
|
189
|
+
throw new Error(`defineProcedure(${def.name}): \`handler\` must be a function`);
|
|
190
|
+
}
|
|
191
|
+
if (!def.description || typeof def.description !== "string") {
|
|
192
|
+
throw new Error(`defineProcedure(${def.name}): \`description\` is required`);
|
|
193
|
+
}
|
|
194
|
+
const procedure = {
|
|
195
|
+
name: def.name,
|
|
196
|
+
summary: def.summary,
|
|
197
|
+
description: def.description,
|
|
198
|
+
input: def.input,
|
|
199
|
+
output: def.output,
|
|
200
|
+
handler: def.handler,
|
|
201
|
+
meta: Object.freeze({ ...def.meta ?? {} }),
|
|
202
|
+
middleware: Object.freeze([...def.middleware ?? []]),
|
|
203
|
+
tags: Object.freeze([...def.tags ?? []])
|
|
204
|
+
};
|
|
205
|
+
return Object.freeze(procedure);
|
|
206
|
+
}
|
|
207
|
+
function getNamespace(p) {
|
|
208
|
+
const idx = p.name.indexOf(".");
|
|
209
|
+
return idx >= 0 ? p.name.slice(0, idx) : p.name;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/router.ts
|
|
213
|
+
function createRouter(def) {
|
|
214
|
+
if (!Array.isArray(def.procedures)) {
|
|
215
|
+
throw new Error("createRouter: `procedures` must be an array");
|
|
216
|
+
}
|
|
217
|
+
if (def.namespace) {
|
|
218
|
+
const ns = def.namespace;
|
|
219
|
+
for (const p of def.procedures) {
|
|
220
|
+
if (p.name !== ns && !p.name.startsWith(`${ns}.`)) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`createRouter: procedure "${p.name}" does not belong to namespace "${ns}"`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return Object.freeze({
|
|
228
|
+
namespace: def.namespace,
|
|
229
|
+
middleware: Object.freeze([...def.middleware ?? []]),
|
|
230
|
+
procedures: Object.freeze([...def.procedures])
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/registry.ts
|
|
235
|
+
var Registry = class {
|
|
236
|
+
entries = /* @__PURE__ */ new Map();
|
|
237
|
+
globalMiddleware;
|
|
238
|
+
constructor(options = {}) {
|
|
239
|
+
this.globalMiddleware = Object.freeze([...options.globalMiddleware ?? []]);
|
|
240
|
+
}
|
|
241
|
+
/** 注册单个 Procedure */
|
|
242
|
+
register(procedure) {
|
|
243
|
+
if (this.entries.has(procedure.name)) {
|
|
244
|
+
throw new Error(`Registry: duplicate procedure name "${procedure.name}"`);
|
|
245
|
+
}
|
|
246
|
+
this.entries.set(procedure.name, {
|
|
247
|
+
procedure,
|
|
248
|
+
middleware: Object.freeze([...this.globalMiddleware, ...procedure.middleware])
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/** 注册一个 Router,会把 router.middleware 前置合并到每个 procedure */
|
|
252
|
+
registerRouter(router) {
|
|
253
|
+
for (const p of router.procedures) {
|
|
254
|
+
if (this.entries.has(p.name)) {
|
|
255
|
+
throw new Error(`Registry: duplicate procedure name "${p.name}"`);
|
|
256
|
+
}
|
|
257
|
+
this.entries.set(p.name, {
|
|
258
|
+
procedure: p,
|
|
259
|
+
middleware: Object.freeze([
|
|
260
|
+
...this.globalMiddleware,
|
|
261
|
+
...router.middleware,
|
|
262
|
+
...p.middleware
|
|
263
|
+
])
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** 按 name 查找 */
|
|
268
|
+
get(name) {
|
|
269
|
+
return this.entries.get(name)?.procedure;
|
|
270
|
+
}
|
|
271
|
+
/** 取某个 procedure 的最终中间件链(router 共享 + procedure 自有) */
|
|
272
|
+
getMiddleware(name) {
|
|
273
|
+
return this.entries.get(name)?.middleware ?? [];
|
|
274
|
+
}
|
|
275
|
+
/** 是否存在 */
|
|
276
|
+
has(name) {
|
|
277
|
+
return this.entries.has(name);
|
|
278
|
+
}
|
|
279
|
+
/** 总数 */
|
|
280
|
+
get size() {
|
|
281
|
+
return this.entries.size;
|
|
282
|
+
}
|
|
283
|
+
/** 列出全部(可选过滤) */
|
|
284
|
+
list(filter) {
|
|
285
|
+
const all = Array.from(this.entries.values(), (e) => e.procedure);
|
|
286
|
+
if (!filter) return all;
|
|
287
|
+
return all.filter((p) => {
|
|
288
|
+
if (filter.namespace && !p.name.startsWith(`${filter.namespace}.`) && p.name !== filter.namespace) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
292
|
+
const tagSet = new Set(p.tags);
|
|
293
|
+
for (const t of filter.tags) if (!tagSet.has(t)) return false;
|
|
294
|
+
}
|
|
295
|
+
return true;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 列出某个 Adapter 应该暴露的 Procedure。
|
|
300
|
+
*
|
|
301
|
+
* 规则:
|
|
302
|
+
* - meta[adapter].expose === false → 排除
|
|
303
|
+
* - 否则 → 包含(默认暴露)
|
|
304
|
+
*
|
|
305
|
+
* 注:CLI 等"白名单式"出口可由该 adapter 自己再过滤一遍。
|
|
306
|
+
*/
|
|
307
|
+
listFor(adapter) {
|
|
308
|
+
return this.list().filter((p) => {
|
|
309
|
+
const cfg = p.meta[adapter];
|
|
310
|
+
if (cfg && cfg.expose === false) return false;
|
|
311
|
+
return true;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/** 清空(测试用) */
|
|
315
|
+
clear() {
|
|
316
|
+
this.entries.clear();
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/runner.ts
|
|
321
|
+
init_errors();
|
|
322
|
+
async function runProcedure(procedure, rawInput, ctx, options = {}) {
|
|
323
|
+
let input;
|
|
324
|
+
const parsed = procedure.input.safeParse(rawInput);
|
|
325
|
+
if (!parsed.success) {
|
|
326
|
+
throw new ValidationError(`Invalid input for "${procedure.name}"`, {
|
|
327
|
+
details: parsed.error.flatten(),
|
|
328
|
+
cause: parsed.error
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
input = parsed.data;
|
|
332
|
+
if (!ctx.procedure || ctx.procedure.name !== procedure.name) {
|
|
333
|
+
ctx.procedure = { name: procedure.name };
|
|
334
|
+
}
|
|
335
|
+
ctx.state["__input__"] = input;
|
|
336
|
+
const mws = options.middleware ?? procedure.middleware;
|
|
337
|
+
const exec = compose(mws);
|
|
338
|
+
let output;
|
|
339
|
+
try {
|
|
340
|
+
const result = await exec(ctx, async () => procedure.handler({ input, ctx }));
|
|
341
|
+
output = result;
|
|
342
|
+
} catch (err) {
|
|
343
|
+
throw toOmniError(err);
|
|
344
|
+
}
|
|
345
|
+
const shouldValidate = options.validateOutput ?? process.env.NODE_ENV !== "production";
|
|
346
|
+
if (shouldValidate) {
|
|
347
|
+
const outParsed = procedure.output.safeParse(output);
|
|
348
|
+
if (!outParsed.success) {
|
|
349
|
+
throw new ValidationError(`Invalid output for "${procedure.name}"`, {
|
|
350
|
+
details: outParsed.error.flatten(),
|
|
351
|
+
cause: outParsed.error
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return outParsed.data;
|
|
355
|
+
}
|
|
356
|
+
return output;
|
|
357
|
+
}
|
|
358
|
+
async function runByName(registry, name, rawInput, ctx, options = {}) {
|
|
359
|
+
const proc = registry.get(name);
|
|
360
|
+
if (!proc) {
|
|
361
|
+
const { NotFoundError: NotFoundError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
362
|
+
throw new NotFoundError2(`Procedure "${name}" not found`);
|
|
363
|
+
}
|
|
364
|
+
return runProcedure(proc, rawInput, ctx, {
|
|
365
|
+
...options,
|
|
366
|
+
middleware: registry.getMiddleware(name)
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export { ConflictError, ForbiddenError, InternalError, NotFoundError, OmniError, RateLimitError, Registry, UnauthorizedError, ValidationError, compose, consoleLogger, createContext, createRouter, defineProcedure, getNamespace, runByName, runProcedure, toOmniError };
|
|
371
|
+
//# sourceMappingURL=index.js.map
|
|
372
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/index.ts","../src/context.ts","../src/middleware.ts","../src/procedure.ts","../src/router.ts","../src/registry.ts","../src/runner.ts"],"names":["NotFoundError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * OmniAPI 错误体系\n *\n * 业务原则:\n * - 业务代码应抛出这些类的实例,而不是字符串或原生 Error\n * - 各 Adapter 负责把 OmniError 映射为协议层错误(HTTP status / MCP error code 等)\n */\n\nexport interface OmniErrorOptions {\n /** 业务错误码(机器可读,如 \"ORDER_NOT_FOUND\") */\n code?: string;\n /** 附加详情(建议是可序列化的对象) */\n details?: unknown;\n /** 原始错误(用于错误链追溯) */\n cause?: unknown;\n}\n\n/**\n * 框架根错误。所有可预期的业务/协议错误都应继承此类。\n */\nexport class OmniError extends Error {\n /** 业务错误码 */\n public readonly code: string;\n /** 默认 HTTP 状态码(Adapter 可覆盖) */\n public readonly status: number;\n /** 附加详情 */\n public readonly details?: unknown;\n\n public constructor(\n message: string,\n options: OmniErrorOptions & { status?: number } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = new.target.name;\n this.code = options.code ?? 'INTERNAL';\n this.status = options.status ?? 500;\n this.details = options.details;\n }\n\n /**\n * 序列化为可对外暴露的 JSON 结构。\n *\n * @param options.production - true 时对 5xx 错误做安全脱敏:\n * - message 替换为通用文案\n * - details 隐藏(避免泄漏 SQL / 文件路径 / 堆栈)\n * 4xx 错误(业务错误)仍保留 message + details,方便客户端处理。\n */\n public toJSON(options: { production?: boolean } = {}): { code: string; message: string; details?: unknown } {\n const isServerError = this.status >= 500;\n if (options.production && isServerError) {\n return {\n code: this.code,\n message: 'Internal server error',\n };\n }\n return {\n code: this.code,\n message: this.message,\n ...(this.details !== undefined ? { details: this.details } : {}),\n };\n }\n}\n\n/** 输入校验失败 */\nexport class ValidationError extends OmniError {\n public constructor(message = 'Validation failed', options: OmniErrorOptions = {}) {\n super(message, { code: 'VALIDATION_ERROR', status: 400, ...options });\n }\n}\n\n/** 未认证 */\nexport class UnauthorizedError extends OmniError {\n public constructor(message = 'Unauthorized', options: OmniErrorOptions = {}) {\n super(message, { code: 'UNAUTHORIZED', status: 401, ...options });\n }\n}\n\n/** 已认证但无权限 */\nexport class ForbiddenError extends OmniError {\n public constructor(message = 'Forbidden', options: OmniErrorOptions = {}) {\n super(message, { code: 'FORBIDDEN', status: 403, ...options });\n }\n}\n\n/** 资源不存在 */\nexport class NotFoundError extends OmniError {\n public constructor(message = 'Not found', options: OmniErrorOptions = {}) {\n super(message, { code: 'NOT_FOUND', status: 404, ...options });\n }\n}\n\n/** 资源冲突(如重复创建) */\nexport class ConflictError extends OmniError {\n public constructor(message = 'Conflict', options: OmniErrorOptions = {}) {\n super(message, { code: 'CONFLICT', status: 409, ...options });\n }\n}\n\n/** 触发限流 */\nexport class RateLimitError extends OmniError {\n public readonly retryAfterMs?: number;\n public constructor(\n message = 'Too many requests',\n options: OmniErrorOptions & { retryAfterMs?: number } = {},\n ) {\n super(message, { code: 'RATE_LIMIT', status: 429, ...options });\n this.retryAfterMs = options.retryAfterMs;\n }\n}\n\n/** 服务端内部错误(兜底) */\nexport class InternalError extends OmniError {\n public constructor(message = 'Internal server error', options: OmniErrorOptions = {}) {\n super(message, { code: 'INTERNAL', status: 500, ...options });\n }\n}\n\n/**\n * 把任意 thrown 值规范化为 OmniError。\n * 用于 Adapter 在最外层兜底,避免业务漏抛原生 Error 时信息丢失。\n */\nexport function toOmniError(err: unknown): OmniError {\n if (err instanceof OmniError) return err;\n if (err instanceof Error) {\n return new InternalError(err.message, { cause: err });\n }\n return new InternalError(typeof err === 'string' ? err : 'Unknown error', { cause: err });\n}\n","// === 错误体系 ===\nexport {\n OmniError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n RateLimitError,\n InternalError,\n toOmniError,\n type OmniErrorOptions,\n} from './errors.js';\n\n// === 上下文 ===\nexport {\n createContext,\n consoleLogger,\n type Context,\n type ContextSource,\n type Logger,\n type UserPrincipal,\n} from './context.js';\n\n// === 中间件 ===\nexport { compose, type Middleware } from './middleware.js';\n\n// === Procedure ===\nexport {\n defineProcedure,\n getNamespace,\n type Procedure,\n type ProcedureDef,\n type ProcedureMeta,\n} from './procedure.js';\n\n// === Router ===\nexport { createRouter, type Router, type RouterDef, type AnyProcedure } from './router.js';\n\n// === Registry ===\nexport { Registry, type AdapterName, type ListFilter, type RegistryOptions } from './registry.js';\n\n// === Runner(执行器,整个框架心脏) ===\nexport { runProcedure, runByName, type RunOptions } from './runner.js';\n","/**\n * 用户主体(Principal)\n *\n * 由各 Adapter 的鉴权中间件填充到 Context.user。\n * 业务层只需要相信这个字段为 true,无需关心鉴权方式。\n */\nexport interface UserPrincipal {\n /** 全局唯一用户 ID */\n id: string;\n /** 角色,自由字符串数组(如 ['admin','op']) */\n roles?: string[];\n /** 其他自定义字段 */\n [key: string]: unknown;\n}\n\n/** 极简 Logger 接口,避免强绑定具体库;插件可注入 pino/winston 适配 */\nexport interface Logger {\n debug(msg: string, meta?: Record<string, unknown>): void;\n info(msg: string, meta?: Record<string, unknown>): void;\n warn(msg: string, meta?: Record<string, unknown>): void;\n error(msg: string, meta?: Record<string, unknown>): void;\n child(bindings: Record<string, unknown>): Logger;\n}\n\n/**\n * 调用来源标识\n *\n * - http: 来自 HTTP Adapter\n * - mcp: 来自 MCP Adapter\n * - test: 单元测试中的直接调用\n */\nexport type ContextSource = 'http' | 'mcp' | 'test' | (string & {});\n\n/**\n * 请求级上下文。\n *\n * 中间件链 / handler 都拿到同一个 Context 实例。\n * raw 字段是 Adapter 的私有数据(如 Fastify request/reply),业务原则上不应直接使用。\n */\nexport interface Context {\n /** 调用来源 */\n source: ContextSource;\n /** 请求级唯一 ID */\n requestId: string;\n /** 用户身份(鉴权后填充) */\n user?: UserPrincipal;\n /** 请求级 logger */\n logger: Logger;\n /** 中间件之间传值的临时槽位 */\n state: Record<string, unknown>;\n /** 取消信号 */\n signal: AbortSignal;\n /** Adapter 私有上下文,类型由各 Adapter 自行声明合并 */\n raw?: unknown;\n /**\n * 当前正在执行的 procedure 引用(由 runProcedure 在执行前注入)。\n * 中间件可据此区分场景,例如限流按 procedure name 隔离。\n * 调用方一般不需要主动传,runProcedure 会负责设置。\n */\n procedure?: { readonly name: string };\n}\n\n/** 控制台 Logger,作为默认 / 测试用 fallback */\nexport const consoleLogger: Logger = createConsoleLogger();\n\nfunction createConsoleLogger(bindings: Record<string, unknown> = {}): Logger {\n const fmt = (msg: string, meta?: Record<string, unknown>): string => {\n const merged = { ...bindings, ...meta };\n const tail = Object.keys(merged).length > 0 ? ` ${JSON.stringify(merged)}` : '';\n return `${msg}${tail}`;\n };\n return {\n debug: (msg, meta) => console.debug(fmt(msg, meta)),\n info: (msg, meta) => console.info(fmt(msg, meta)),\n warn: (msg, meta) => console.warn(fmt(msg, meta)),\n error: (msg, meta) => console.error(fmt(msg, meta)),\n child: (extra) => createConsoleLogger({ ...bindings, ...extra }),\n };\n}\n\n/**\n * 创建一个测试 / Adapter 默认用的 Context。\n *\n * 各 Adapter 一般会基于此函数再补充自己的 raw 字段。\n */\nexport function createContext(partial: Partial<Context> & { source: ContextSource }): Context {\n return {\n requestId: partial.requestId ?? generateRequestId(),\n user: partial.user,\n logger: partial.logger ?? consoleLogger,\n state: partial.state ?? Object.create(null),\n signal: partial.signal ?? new AbortController().signal,\n raw: partial.raw,\n source: partial.source,\n };\n}\n\n/** 生成简单的请求 ID(不依赖外部库;生产建议用 ulid/uuid) */\nfunction generateRequestId(): string {\n const ts = Date.now().toString(36);\n const rand = Math.random().toString(36).slice(2, 10);\n return `req_${ts}_${rand}`;\n}\n","import type { Context } from './context.js';\n\n/**\n * 中间件函数签名(类 Koa onion 模型)\n *\n * - 必须 await next() 才能让后续中间件 / handler 执行\n * - 可在 next() 前后做任何事(鉴权、计时、错误转换…)\n * - 抛出的错误会被 runProcedure 捕获并规范化\n */\nexport type Middleware<C extends Context = Context> = (\n ctx: C,\n next: () => Promise<unknown>,\n) => Promise<unknown>;\n\n/**\n * 把多个中间件合并为单个执行函数。\n *\n * 调用 `compose(mws)(ctx, finalHandler)` 会按 mws 顺序进入、反向退出,\n * 最终调用 finalHandler 并返回其结果。\n *\n * 实现要点:\n * 1. 每个中间件 next 只能调用一次(防止重复 await)\n * 2. finalHandler 的返回值会原样向上传递\n */\nexport function compose<C extends Context>(\n middlewares: ReadonlyArray<Middleware<C>>,\n): (ctx: C, finalHandler: () => Promise<unknown>) => Promise<unknown> {\n // 防御:拷贝一份,避免外部修改影响执行\n const mws = middlewares.slice();\n\n return function composed(ctx: C, finalHandler: () => Promise<unknown>): Promise<unknown> {\n let lastIndex = -1;\n\n const dispatch = (i: number): Promise<unknown> => {\n if (i <= lastIndex) {\n return Promise.reject(new Error('next() called multiple times in middleware'));\n }\n lastIndex = i;\n\n const fn = mws[i];\n if (!fn) {\n // 已穿透所有中间件,调用最终 handler\n return Promise.resolve().then(finalHandler);\n }\n try {\n return Promise.resolve(fn(ctx, () => dispatch(i + 1)));\n } catch (err) {\n return Promise.reject(err);\n }\n };\n\n return dispatch(0);\n };\n}\n","import type { ZodTypeAny, z } from 'zod';\nimport type { Context } from './context.js';\nimport type { Middleware } from './middleware.js';\n\n/**\n * Procedure 元信息 —— 仅声明真实出口(HTTP / MCP)。\n *\n * 各 Adapter 只读自己关心的那块;插件可通过自定义 key 扩展。\n */\nexport interface ProcedureMeta {\n /** HTTP 出口配置;不写则用默认约定(按 name 派生路径,按动词推断 method) */\n http?: {\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n /** 默认 /<namespace>/<action>,如 order.create → /order/create */\n path?: string;\n /** 成功状态码,默认 200 */\n status?: number;\n /** 是否暴露到 HTTP,默认 true */\n expose?: boolean;\n };\n\n /** MCP 出口配置 */\n mcp?: {\n /** 是否暴露到 MCP,默认 true */\n expose?: boolean;\n /** 在 MCP Client 中的分组(如 commerce / admin) */\n category?: string;\n /** 是否在执行前要求 MCP Host 用户确认(destructive 操作建议 true) */\n requireConfirmation?: boolean;\n /** 危险等级:拼接进 description 头部,帮助 LLM 谨慎使用 */\n dangerLevel?: 'read' | 'write' | 'destructive';\n /** 给 LLM 的额外示例,会拼接进 description */\n examples?: ReadonlyArray<{ input: unknown; when: string; output?: unknown }>;\n /** 别名:拼接进 description,提升 LLM 召回 */\n aliases?: ReadonlyArray<string>;\n };\n\n /** 插件自由扩展用的自定义元信息 */\n [key: string]: unknown;\n}\n\n/** Procedure 定义(用户传给 defineProcedure 的形态) */\nexport interface ProcedureDef<\n Input extends ZodTypeAny,\n Output extends ZodTypeAny,\n C extends Context = Context,\n> {\n /** 全局唯一名,建议 namespace.action 格式:order.create */\n name: string;\n /** 单行简述(用于列表 / table) */\n summary?: string;\n /** 详细描述:作为 OpenAPI / MCP description 的主体 */\n description: string;\n /** 输入 schema */\n input: Input;\n /** 输出 schema */\n output: Output;\n /** 多端元信息 */\n meta?: ProcedureMeta;\n /** 中间件链(按声明顺序进入、反向退出) */\n middleware?: ReadonlyArray<Middleware<C>>;\n /** 业务逻辑 */\n handler: (params: { input: z.infer<Input>; ctx: C }) => Promise<z.infer<Output>> | z.infer<Output>;\n /** 标签,用于分组、按需暴露 */\n tags?: ReadonlyArray<string>;\n}\n\n/** 冻结后的 Procedure 实例 */\nexport interface Procedure<\n Input extends ZodTypeAny = ZodTypeAny,\n Output extends ZodTypeAny = ZodTypeAny,\n C extends Context = Context,\n> extends Required<Pick<ProcedureDef<Input, Output, C>, 'name' | 'description' | 'input' | 'output' | 'handler'>> {\n readonly summary?: string;\n readonly meta: Readonly<ProcedureMeta>;\n readonly middleware: ReadonlyArray<Middleware<C>>;\n readonly tags: ReadonlyArray<string>;\n}\n\n/** name 合法性:小写字母 + 数字 + 点 + 下划线 + 连字符;点用作 namespace 分隔 */\nconst NAME_PATTERN = /^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)*$/;\n\n/**\n * 定义一个 Procedure。\n *\n * 工厂的价值:\n * 1. 类型推导起点:I/O 由 input/output 自动推出,业务无需手写泛型\n * 2. 声明期校验:name 合法性、必要字段\n * 3. 不可变保证:返回 frozen 对象,防止运行时被改写\n */\nexport function defineProcedure<\n Input extends ZodTypeAny,\n Output extends ZodTypeAny,\n C extends Context = Context,\n>(def: ProcedureDef<Input, Output, C>): Procedure<Input, Output, C> {\n if (!def.name || typeof def.name !== 'string') {\n throw new Error('defineProcedure: `name` is required and must be a string');\n }\n if (!NAME_PATTERN.test(def.name)) {\n throw new Error(\n `defineProcedure: invalid name \"${def.name}\". Expected pattern like \"order.create\" (lowercase, dot-separated)`,\n );\n }\n if (!def.input || typeof def.input.parse !== 'function') {\n throw new Error(`defineProcedure(${def.name}): \\`input\\` must be a Zod schema`);\n }\n if (!def.output || typeof def.output.parse !== 'function') {\n throw new Error(`defineProcedure(${def.name}): \\`output\\` must be a Zod schema`);\n }\n if (typeof def.handler !== 'function') {\n throw new Error(`defineProcedure(${def.name}): \\`handler\\` must be a function`);\n }\n if (!def.description || typeof def.description !== 'string') {\n throw new Error(`defineProcedure(${def.name}): \\`description\\` is required`);\n }\n\n const procedure: Procedure<Input, Output, C> = {\n name: def.name,\n summary: def.summary,\n description: def.description,\n input: def.input,\n output: def.output,\n handler: def.handler,\n meta: Object.freeze({ ...(def.meta ?? {}) }),\n middleware: Object.freeze([...(def.middleware ?? [])]),\n tags: Object.freeze([...(def.tags ?? [])]),\n };\n\n return Object.freeze(procedure);\n}\n\n/** 取 Procedure 的 namespace(\"order.create\" → \"order\";无点则返回整个 name) */\nexport function getNamespace(p: Pick<Procedure, 'name'>): string {\n const idx = p.name.indexOf('.');\n return idx >= 0 ? p.name.slice(0, idx) : p.name;\n}\n","import type { ZodTypeAny } from 'zod';\nimport type { Context } from './context.js';\nimport type { Middleware } from './middleware.js';\nimport type { Procedure } from './procedure.js';\n\n/**\n * 任意 Procedure(输入输出类型不限)。\n *\n * 用 `Procedure<any, any>` 而非 `Procedure<ZodTypeAny, ZodTypeAny>` 来获得协变行为,\n * 让具体 procedure(带窄 schema 类型)能被赋值到 router.procedures 数组里。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyProcedure = Procedure<any, any>;\n\n/**\n * Router:Procedure 的批量组合 + 共享 middleware 的语法糖。\n *\n * 不引入新的运行时概念 —— Registry 注册时会把 Router 拍平为 Procedure 列表,\n * 并把 router.middleware 前置到每个 procedure.middleware 之前。\n */\nexport interface Router<C extends Context = Context> {\n /** 命名空间(可选) */\n readonly namespace?: string;\n /** 该组下所有 procedure 共享的中间件 */\n readonly middleware: ReadonlyArray<Middleware<C>>;\n /** 组内的 procedure 列表 */\n readonly procedures: ReadonlyArray<AnyProcedure>;\n}\n\nexport interface RouterDef<C extends Context = Context> {\n namespace?: string;\n middleware?: ReadonlyArray<Middleware<C>>;\n procedures: ReadonlyArray<AnyProcedure>;\n}\n\nexport function createRouter<C extends Context = Context>(def: RouterDef<C>): Router<C> {\n if (!Array.isArray(def.procedures)) {\n throw new Error('createRouter: `procedures` must be an array');\n }\n\n // namespace 校验:开发期就发现命名混乱\n if (def.namespace) {\n const ns = def.namespace;\n for (const p of def.procedures) {\n if (p.name !== ns && !p.name.startsWith(`${ns}.`)) {\n throw new Error(\n `createRouter: procedure \"${p.name}\" does not belong to namespace \"${ns}\"`,\n );\n }\n }\n }\n\n return Object.freeze({\n namespace: def.namespace,\n middleware: Object.freeze([...(def.middleware ?? [])]),\n procedures: Object.freeze([...def.procedures]),\n });\n}\n\n// 防止 unused(仅类型导入引用)\nexport type _ZodAnyAlias = ZodTypeAny;\n","import type { Procedure } from './procedure.js';\nimport type { Router, AnyProcedure } from './router.js';\nimport type { Middleware } from './middleware.js';\nimport type { Context } from './context.js';\n\n/** Adapter 名称的常量集合(也允许字符串扩展) */\nexport type AdapterName = 'http' | 'mcp' | (string & {});\n\n/** Registry 内部存储的 procedure 条目(已合并 router 的共享中间件) */\ninterface RegistryEntry {\n procedure: AnyProcedure;\n /** 该 procedure 的最终中间件链(router.middleware + procedure.middleware) */\n middleware: ReadonlyArray<Middleware<Context>>;\n}\n\nexport interface ListFilter {\n /** 按 namespace 精确过滤 */\n namespace?: string;\n /** 必须包含所有给定 tag */\n tags?: ReadonlyArray<string>;\n}\n\nexport interface RegistryOptions {\n /**\n * 全局前置中间件 —— 在每个 procedure 的 router.middleware + procedure.middleware 之前。\n *\n * 典型用途:attachDataSource / observability.middleware / 全局 audit。\n *\n * 顺序:全局 → router → procedure\n */\n globalMiddleware?: ReadonlyArray<Middleware<Context>>;\n}\n\n/**\n * 全局注册中心:所有 Adapter 共享的单一事实源。\n *\n * 职责:\n * - 收集 Procedure / Router\n * - 校验 name 唯一性\n * - 按 Adapter 维度(meta.<adapter>.expose)过滤可暴露集合\n * - 提供基于 namespace / tag 的查询\n * - 维护\"全局 → router → procedure\"的中间件叠加\n */\nexport class Registry {\n private readonly entries = new Map<string, RegistryEntry>();\n private readonly globalMiddleware: ReadonlyArray<Middleware<Context>>;\n\n public constructor(options: RegistryOptions = {}) {\n this.globalMiddleware = Object.freeze([...(options.globalMiddleware ?? [])]);\n }\n\n /** 注册单个 Procedure */\n public register(procedure: AnyProcedure): void {\n if (this.entries.has(procedure.name)) {\n throw new Error(`Registry: duplicate procedure name \"${procedure.name}\"`);\n }\n this.entries.set(procedure.name, {\n procedure,\n middleware: Object.freeze([...this.globalMiddleware, ...procedure.middleware]),\n });\n }\n\n /** 注册一个 Router,会把 router.middleware 前置合并到每个 procedure */\n public registerRouter(router: Router): void {\n for (const p of router.procedures) {\n if (this.entries.has(p.name)) {\n throw new Error(`Registry: duplicate procedure name \"${p.name}\"`);\n }\n this.entries.set(p.name, {\n procedure: p,\n middleware: Object.freeze([\n ...this.globalMiddleware,\n ...router.middleware,\n ...p.middleware,\n ]),\n });\n }\n }\n\n /** 按 name 查找 */\n public get(name: string): AnyProcedure | undefined {\n return this.entries.get(name)?.procedure;\n }\n\n /** 取某个 procedure 的最终中间件链(router 共享 + procedure 自有) */\n public getMiddleware(name: string): ReadonlyArray<Middleware<Context>> {\n return this.entries.get(name)?.middleware ?? [];\n }\n\n /** 是否存在 */\n public has(name: string): boolean {\n return this.entries.has(name);\n }\n\n /** 总数 */\n public get size(): number {\n return this.entries.size;\n }\n\n /** 列出全部(可选过滤) */\n public list(filter?: ListFilter): AnyProcedure[] {\n const all = Array.from(this.entries.values(), (e) => e.procedure);\n if (!filter) return all;\n return all.filter((p) => {\n if (filter.namespace && !p.name.startsWith(`${filter.namespace}.`) && p.name !== filter.namespace) {\n return false;\n }\n if (filter.tags && filter.tags.length > 0) {\n const tagSet = new Set(p.tags);\n for (const t of filter.tags) if (!tagSet.has(t)) return false;\n }\n return true;\n });\n }\n\n /**\n * 列出某个 Adapter 应该暴露的 Procedure。\n *\n * 规则:\n * - meta[adapter].expose === false → 排除\n * - 否则 → 包含(默认暴露)\n *\n * 注:CLI 等\"白名单式\"出口可由该 adapter 自己再过滤一遍。\n */\n public listFor(adapter: AdapterName): AnyProcedure[] {\n return this.list().filter((p) => {\n const cfg = (p.meta as Record<string, unknown>)[adapter] as\n | { expose?: boolean }\n | undefined;\n if (cfg && cfg.expose === false) return false;\n return true;\n });\n }\n\n /** 清空(测试用) */\n public clear(): void {\n this.entries.clear();\n }\n}\n","import type { ZodTypeAny, z } from 'zod';\nimport type { Context } from './context.js';\nimport type { Middleware } from './middleware.js';\nimport type { Procedure } from './procedure.js';\nimport type { Registry } from './registry.js';\nimport { compose } from './middleware.js';\nimport { ValidationError, toOmniError } from './errors.js';\n\nexport interface RunOptions {\n /**\n * 是否在 handler 执行后校验 output。\n * 默认仅在 NODE_ENV !== 'production' 时校验,避免生产环境性能损耗。\n */\n validateOutput?: boolean;\n /**\n * 显式注入的中间件链。\n *\n * - 不传 → 使用 procedure.middleware(直接调用场景,例如测试)\n * - 传入 → 通常由 Adapter 从 Registry.getMiddleware(name) 取得(合并了 Router 共享中间件)\n */\n middleware?: ReadonlyArray<Middleware<Context>>;\n}\n\n/**\n * 执行一个 Procedure —— 整个框架的\"心脏\"。\n *\n * 流程:\n * 1. 用 input schema 校验 rawInput(失败 → ValidationError)\n * 2. 组合中间件链\n * 3. 链尾调用 handler\n * 4. 可选:用 output schema 校验返回值(开发期默认开)\n * 5. 任何抛错统一规范化为 OmniError\n */\nexport async function runProcedure<\n Input extends ZodTypeAny,\n Output extends ZodTypeAny,\n C extends Context = Context,\n>(\n procedure: Procedure<Input, Output, C>,\n rawInput: unknown,\n ctx: C,\n options: RunOptions = {},\n): Promise<z.infer<Output>> {\n // 1. 输入校验\n let input: z.infer<Input>;\n const parsed = procedure.input.safeParse(rawInput);\n if (!parsed.success) {\n throw new ValidationError(`Invalid input for \"${procedure.name}\"`, {\n details: parsed.error.flatten(),\n cause: parsed.error,\n });\n }\n input = parsed.data;\n\n // 2. 注入 procedure 引用,让中间件可感知当前执行的 procedure\n // 使用 Object.assign 直接修改 ctx,保证中间件能读到(避免代理开销)\n if (!ctx.procedure || ctx.procedure.name !== procedure.name) {\n (ctx as { procedure?: { name: string } }).procedure = { name: procedure.name };\n }\n // 把校验后的 input 也写到 ctx.state 里(中间件可读,audit / cache key 等场景需要)\n ctx.state['__input__'] = input;\n\n // 3. 中间件链\n const mws = (options.middleware ?? procedure.middleware) as ReadonlyArray<Middleware<C>>;\n const exec = compose(mws);\n\n // 4. 真正执行\n let output: z.infer<Output>;\n try {\n const result = await exec(ctx, async () => procedure.handler({ input, ctx }));\n output = result as z.infer<Output>;\n } catch (err) {\n throw toOmniError(err);\n }\n\n // 5. 输出校验(开发期默认开)\n const shouldValidate =\n options.validateOutput ?? process.env.NODE_ENV !== 'production';\n if (shouldValidate) {\n const outParsed = procedure.output.safeParse(output);\n if (!outParsed.success) {\n throw new ValidationError(`Invalid output for \"${procedure.name}\"`, {\n details: outParsed.error.flatten(),\n cause: outParsed.error,\n });\n }\n return outParsed.data;\n }\n\n return output;\n}\n\n/**\n * 通过 Registry 调用 Procedure(按 name 查找并使用合并后的中间件链)。\n * Adapter 通常调用这个函数,而非直接 runProcedure。\n */\nexport async function runByName(\n registry: Registry,\n name: string,\n rawInput: unknown,\n ctx: Context,\n options: Omit<RunOptions, 'middleware'> = {},\n): Promise<unknown> {\n const proc = registry.get(name);\n if (!proc) {\n const { NotFoundError } = await import('./errors.js');\n throw new NotFoundError(`Procedure \"${name}\" not found`);\n }\n return runProcedure(proc, rawInput, ctx, {\n ...options,\n middleware: registry.getMiddleware(name),\n });\n}\n"]}
|