@aalis/api-hooks 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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ace Nyan <ace@acenyan.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @aalis/api-hooks
|
|
2
|
+
|
|
3
|
+
钩子契约:`hooks` 服务描述符、中间件签名 `MiddlewareFn` / `MiddlewareNext`、提供者契约 `HookRegistry` 与扩展点 `HookContextMap`。不含实现,默认提供者是 `@aalis/plugin-hooks`。
|
|
4
|
+
|
|
5
|
+
## 角色
|
|
6
|
+
|
|
7
|
+
- `hooks`:服务描述符。插件在 `uses` 里声明后得到绑定门面:`middleware(hook, fn)` 登记中间件,随本次激活撤回;`run(hook, data, defaultAction?, opts?)` 驱动一条链,返回 `false` 表示被截停。
|
|
8
|
+
- `HookContextMap`:空接口,由各领域 `-api` 包经 declaration merging 注入「钩子名 → 中间件上下文」。
|
|
9
|
+
- 没有提供者时 `run` 返回被拒的 Promise,不执行默认动作。
|
|
10
|
+
|
|
11
|
+
## 顺序
|
|
12
|
+
|
|
13
|
+
链内按登记顺序执行。登记序是每条登记自带的序号,提供者按它排链:以更高优先级换上另一个提供者时,各插件的登记整批重挂,链序不变。提供者重启时,依赖它的插件随之重启并重新登记,链序即重新激活的次序,与冷启动相同。相位内的 handler 应与顺序无关。
|
|
14
|
+
|
|
15
|
+
自己实现提供者时,`register` 须按 `order` 升序插入。
|
|
16
|
+
|
|
17
|
+
## 运行中换提供者的已知现状
|
|
18
|
+
|
|
19
|
+
以更高优先级上线第二个钩子提供者,或在服务页切换 hooks 偏好时,有两种情形会让截停者暂时缺席:
|
|
20
|
+
|
|
21
|
+
- 换人那一刻正在执行的链会跳过已移到新提供者的中间件。被跳过的若是截停者,默认动作照样执行。
|
|
22
|
+
- 胜者在容器里同步换人,各插件的登记要逐个重挂到新提供者上。重挂完成前新发起的链,只能看到已经搬过来的中间件。
|
|
23
|
+
|
|
24
|
+
两种情形都只发生在运行中换提供者的那一刻。第一方部署只有 `@aalis/plugin-hooks` 一个提供者,不会遇到。
|
|
25
|
+
|
|
26
|
+
## 安装
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add @aalis/api-hooks
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 使用
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { hooks } from '@aalis/api-hooks';
|
|
36
|
+
|
|
37
|
+
// uses: { hooks }
|
|
38
|
+
hooks.middleware('agent:llm:before', async (data, next) => {
|
|
39
|
+
// 改 data 后交棒;不调 next 即截停
|
|
40
|
+
await next();
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
扩展钩子键:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
declare module '@aalis/api-hooks' {
|
|
48
|
+
interface HookContextMap {
|
|
49
|
+
'schedule:before': { jobId: string };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** 钩子扩展点(空接口;由各 -api 包经 declaration merging 注入「钩子名 → 中间件上下文」) */
|
|
2
|
+
export interface HookContextMap {
|
|
3
|
+
}
|
|
4
|
+
/** 中间件 next:把控制交给下一个中间件或默认动作 */
|
|
5
|
+
export type MiddlewareNext = () => Promise<void>;
|
|
6
|
+
/** 中间件:可改 data(引用传递),调 next() 继续,不调即截停(含默认动作) */
|
|
7
|
+
export type MiddlewareFn<T> = (data: T, next: MiddlewareNext) => Promise<void>;
|
|
8
|
+
/** `run` 的选项 */
|
|
9
|
+
export interface RunOptions {
|
|
10
|
+
/** 广播型相位:某 handler 返回却没调 next() 时点名告警(其后的注入会被静默丢弃) */
|
|
11
|
+
warnOnStall?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 提供者契约(钩子登记表本体)。插件不直接调用:登记经 {@link hooks} 的绑定门面,
|
|
15
|
+
* 它盖上本激活 id、随激活撤回。
|
|
16
|
+
*/
|
|
17
|
+
export interface HookRegistry {
|
|
18
|
+
/**
|
|
19
|
+
* 按 order 升序插入该钩子链。order 是门面在登记时分配的登记序(进程内全部 api-hooks 副本共用一个计数器,单调递增),换提供者重挂时原样带过来,
|
|
20
|
+
* 提供者据它还原原来的交错次序。contextId 供卡链告警点名。返回退订(幂等,只撤这一条)
|
|
21
|
+
*/
|
|
22
|
+
register(hook: string, fn: MiddlewareFn<unknown>, contextId: string, order: number): () => void;
|
|
23
|
+
/** 驱动一条钩子链;true = 走到底(含执行了默认动作或本就没有 handler),false = 被截停 */
|
|
24
|
+
run(hook: string, data: unknown, defaultAction?: () => Promise<void>, opts?: RunOptions): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
/** `hooks` 的绑定接口 */
|
|
27
|
+
export interface Hooks {
|
|
28
|
+
/** 注册中间件(洋葱模型,按登记顺序);返回退订,随这次激活撤回 */
|
|
29
|
+
middleware<K extends string & keyof HookContextMap>(hook: K, fn: MiddlewareFn<HookContextMap[K]>): () => void;
|
|
30
|
+
/** 驱动一条钩子链;返回 false 表示被某个中间件截停 */
|
|
31
|
+
run<K extends string & keyof HookContextMap>(hook: K, data: HookContextMap[K], defaultAction?: () => Promise<void>, opts?: RunOptions): Promise<boolean>;
|
|
32
|
+
}
|
|
33
|
+
export declare const hooks: import("@aalis/core").ServiceDescriptor<HookRegistry, Hooks>;
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,iEAAiE;AACjE,MAAM,WAAW,cAAc;CAAG;AAElC,gCAAgC;AAChC,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjD,iDAAiD;AACjD,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/E,gBAAgB;AAChB,MAAM,WAAW,UAAU;IACzB,sDAAsD;IACtD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC;IAChG,4DAA4D;IAC5D,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5G;AAED,oBAAoB;AACpB,MAAM,WAAW,KAAK;IACpB,qCAAqC;IACrC,UAAU,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,cAAc,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IAC9G,kCAAkC;IAClC,GAAG,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,cAAc,EACzC,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAWD,eAAO,MAAM,KAAK,8DAYhB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// @aalis/api-hooks — 钩子契约
|
|
3
|
+
//
|
|
4
|
+
// 每个钩子键代表一个语义清晰的生命周期事件(如 inbound:command / agent:llm:before),
|
|
5
|
+
// handler 在事件内部按**登记顺序**串行执行洋葱模型(next 交棒)。不用数字 priority:
|
|
6
|
+
// 相位之间的次序由调度方显式表达;相位内部的 handler 应顺序无关,或由相位拥有方约定。
|
|
7
|
+
// 登记序是每条登记自带的序号,提供者按它排链:换提供者时各账本整批重挂,链序不变;
|
|
8
|
+
// 提供者重启时依赖方随之重启、重新登记,链序即重新激活的次序(与冷启动相同)。
|
|
9
|
+
// 默认提供者:@aalis/plugin-hooks。没有提供者时 run 返回被拒的 Promise(不执行默认动作)。
|
|
10
|
+
//
|
|
11
|
+
// 钩子键由各 -api 包经 declaration merging 注入 HookContextMap:
|
|
12
|
+
// declare module '@aalis/api-hooks' {
|
|
13
|
+
// interface HookContextMap { 'schedule:before': { jobId: string } }
|
|
14
|
+
// }
|
|
15
|
+
// ============================================================
|
|
16
|
+
import { defineService } from '@aalis/core';
|
|
17
|
+
/**
|
|
18
|
+
* 登记序计数器:进程内全部 api-hooks 副本共用一个(经全局符号表取同一个对象),登记即取号。
|
|
19
|
+
* 契约包可能装了两份,各自从 0 计数的话序号不可比,提供者按序排链就会把后登记的排到前面。
|
|
20
|
+
*/
|
|
21
|
+
const ORDER = Symbol.for('@aalis/api-hooks.order');
|
|
22
|
+
const shared = globalThis;
|
|
23
|
+
shared[ORDER] ??= { n: 0 };
|
|
24
|
+
const orderCounter = shared[ORDER];
|
|
25
|
+
export const hooks = defineService('hooks', port => {
|
|
26
|
+
// 每次登记一个独立账目:同一激活可在同一钩子上挂多个 handler,没有「同键替换」。登记序全局唯一,
|
|
27
|
+
// 用作账本键不会撞;键带钩子名,关闭后登记的告警能点名
|
|
28
|
+
const ledger = port.registrar({
|
|
29
|
+
key: ({ hook, order }) => `${hook}#${order}`,
|
|
30
|
+
register: (registry, { hook, fn, order }) => registry.register(hook, fn, port.id, order),
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
middleware: (hook, fn) => ledger.add({ hook, fn: fn, order: ++orderCounter.n }),
|
|
34
|
+
// async:没有提供者时以被拒的 Promise 传出,不在调用点同步抛(与提供者 run 的异步口径一致)
|
|
35
|
+
run: async (hook, data, defaultAction, opts) => port.require().run(hook, data, defaultAction, opts),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,0BAA0B;AAC1B,EAAE;AACF,8DAA8D;AAC9D,yDAAyD;AACzD,iDAAiD;AACjD,2CAA2C;AAC3C,yCAAyC;AACzC,+DAA+D;AAC/D,EAAE;AACF,uDAAuD;AACvD,wCAAwC;AACxC,wEAAwE;AACxE,MAAM;AACN,+DAA+D;AAE/D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA4C5C;;;GAGG;AACH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AACnD,MAAM,MAAM,GAAG,UAAuD,CAAC;AACvE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAC3B,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;AAEnC,MAAM,CAAC,MAAM,KAAK,GAAG,aAAa,CAAsB,OAAO,EAAE,IAAI,CAAC,EAAE;IACtE,sDAAsD;IACtD,6BAA6B;IAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAA6D;QACxF,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE;QAC5C,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC;KACzF,CAAC,CAAC;IACH,OAAO;QACL,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAA2B,EAAE,KAAK,EAAE,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;QACxG,yDAAyD;QACzD,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC;KACpG,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aalis/api-hooks",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/AalisLabs/Aalis.git",
|
|
8
|
+
"directory": "packages/api-hooks"
|
|
9
|
+
},
|
|
10
|
+
"author": "Ace Nyan <ace@acenyan.com>",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/AalisLabs/Aalis/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/AalisLabs/Aalis#readme",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"typescript": "^5.7.0",
|
|
24
|
+
"@aalis/core": "0.18.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@aalis/core": ">=0.18.0 <1.0.0"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"aalis",
|
|
31
|
+
"aalis-api"
|
|
32
|
+
],
|
|
33
|
+
"description": "钩子契约:hooks 服务描述符、中间件签名与 HookContextMap 扩展点(默认提供者 @aalis/plugin-hooks)",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"dev": "tsc --watch"
|
|
37
|
+
}
|
|
38
|
+
}
|