@smartdoca/plugin-sdk 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/dist/LICENSE +661 -0
- package/dist/LICENSING.md +57 -0
- package/dist/LICENSING.zh-CN.md +48 -0
- package/dist/ai.d.ts +27 -0
- package/dist/ai.js +2 -0
- package/dist/files.d.ts +3 -0
- package/dist/files.js +4 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +342 -0
- package/dist/knowledge.d.ts +3 -0
- package/dist/knowledge.js +3 -0
- package/dist/platform.d.ts +176 -0
- package/dist/platform.js +7 -0
- package/dist/search.d.ts +20 -0
- package/dist/search.js +2 -0
- package/dist/testing.d.ts +20 -0
- package/dist/testing.js +57 -0
- package/dist/web.d.ts +34 -0
- package/dist/web.js +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Licensing
|
|
2
|
+
|
|
3
|
+
[简体中文](LICENSING.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Doca uses a dual-licensing model for code owned by the Doca copyright holders.
|
|
6
|
+
|
|
7
|
+
## Open-source license
|
|
8
|
+
|
|
9
|
+
Unless a file or package says otherwise, Doca-owned code in this repository is
|
|
10
|
+
available under the GNU Affero General Public License version 3 only
|
|
11
|
+
(`AGPL-3.0-only`). The full license text is in [LICENSE](LICENSE).
|
|
12
|
+
|
|
13
|
+
The AGPL permits commercial use. When its conditions apply, it requires the
|
|
14
|
+
corresponding source to remain available under the AGPL, including for modified
|
|
15
|
+
versions offered to users over a network. “Open source” and “free of charge” are
|
|
16
|
+
not the same thing, and the AGPL obligations do not depend on whether an operator
|
|
17
|
+
charges money.
|
|
18
|
+
|
|
19
|
+
## Commercial license
|
|
20
|
+
|
|
21
|
+
Organizations that want to use Doca-owned code in a proprietary product without
|
|
22
|
+
the AGPL obligations may obtain a separate commercial license from the Doca
|
|
23
|
+
copyright holders. A commercial license exists only when both parties have
|
|
24
|
+
entered into a written agreement. This repository does not itself grant that
|
|
25
|
+
alternative license.
|
|
26
|
+
|
|
27
|
+
## Packages and editors
|
|
28
|
+
|
|
29
|
+
The same licensing direction applies to Doca-owned packages and editors,
|
|
30
|
+
including the Doca plugin SDK and the Slate, presentation, spreadsheet,
|
|
31
|
+
Markdown, and canvas editor packages. Each released npm artifact must:
|
|
32
|
+
|
|
33
|
+
- carry an accurate license field and license notice;
|
|
34
|
+
- link to the public source repository and the exact source tag for that version;
|
|
35
|
+
- include the build scripts and dependency lock needed to produce the artifact;
|
|
36
|
+
- preserve all notices and license obligations of third-party components.
|
|
37
|
+
|
|
38
|
+
The historical archives under `vendor/` are development and recovery inputs.
|
|
39
|
+
They are not the long-term public distribution channel. Before the public
|
|
40
|
+
release, active editor dependencies must be rebuilt from their public tagged
|
|
41
|
+
sources, published to npm, and consumed by Doca through registry versions.
|
|
42
|
+
|
|
43
|
+
## Third-party software and assets
|
|
44
|
+
|
|
45
|
+
The dual license covers only material for which the Doca copyright holders can
|
|
46
|
+
grant these rights. Third-party libraries, fonts, media, generated artifacts,
|
|
47
|
+
and other bundled material retain their own licenses. Their notices and source
|
|
48
|
+
or attribution requirements must be followed independently.
|
|
49
|
+
|
|
50
|
+
No trademark rights are granted by either the AGPL or this licensing summary.
|
|
51
|
+
|
|
52
|
+
## Contributions
|
|
53
|
+
|
|
54
|
+
Dual licensing requires the project to retain the rights needed to offer future
|
|
55
|
+
commercial licenses. See [CONTRIBUTING.md](CONTRIBUTING.md) before submitting
|
|
56
|
+
code. This document is a project policy summary, not a substitute for the
|
|
57
|
+
license text or legal advice.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# 许可说明
|
|
2
|
+
|
|
3
|
+
[English](LICENSING.md)
|
|
4
|
+
|
|
5
|
+
Doca 对其版权所有者拥有权利的代码采用双许可模式。
|
|
6
|
+
|
|
7
|
+
## 开源许可证
|
|
8
|
+
|
|
9
|
+
除非文件或包中另有说明,本仓库内由 Doca 版权所有者拥有的代码默认采用
|
|
10
|
+
GNU Affero General Public License version 3 only(`AGPL-3.0-only`)。完整条款见
|
|
11
|
+
[LICENSE](LICENSE)。
|
|
12
|
+
|
|
13
|
+
AGPL 允许商业使用。当其条件适用时,分发软件或将修改后的版本作为网络服务提供给
|
|
14
|
+
用户,需要继续按 AGPL 提供对应源码。是否收费并不是 AGPL 义务的判断条件,“开源”
|
|
15
|
+
也不等于“免费”。
|
|
16
|
+
|
|
17
|
+
## 商业许可证
|
|
18
|
+
|
|
19
|
+
希望在闭源产品中使用 Doca 自研代码、且不希望承担 AGPL 义务的组织,可以向 Doca
|
|
20
|
+
版权所有者申请单独的商业许可证。只有双方签署书面协议后,商业许可证才成立;本仓库
|
|
21
|
+
本身不授予该替代许可证。
|
|
22
|
+
|
|
23
|
+
## 子包与编辑器
|
|
24
|
+
|
|
25
|
+
同一许可方向适用于 Doca 自研的子包和编辑器,包括插件 SDK,以及 Slate 富文本、
|
|
26
|
+
演示文稿、表格、Markdown 和画板编辑器。每个正式发布的 npm 制品必须:
|
|
27
|
+
|
|
28
|
+
- 使用准确的许可证字段并携带许可证说明;
|
|
29
|
+
- 链接公开源码仓库以及与该版本精确对应的 Git tag;
|
|
30
|
+
- 提供生成该制品所需的构建脚本和依赖锁文件;
|
|
31
|
+
- 保留所有第三方组件要求的许可证及声明。
|
|
32
|
+
|
|
33
|
+
`vendor/` 中的历史压缩包是开发和恢复输入,不作为长期公开分发渠道。正式开源前,
|
|
34
|
+
当前使用的编辑器依赖应从公开且已打 tag 的源码重新构建并发布到 npm,Doca 再改为
|
|
35
|
+
使用 registry 版本。
|
|
36
|
+
|
|
37
|
+
## 第三方软件与素材
|
|
38
|
+
|
|
39
|
+
双许可只覆盖 Doca 版权所有者有权授予许可的内容。第三方库、字体、媒体、生成产物及
|
|
40
|
+
其他随附内容继续适用各自许可证,其声明、源码提供或署名要求需要独立履行。
|
|
41
|
+
|
|
42
|
+
AGPL 和本文均不授予商标使用权。
|
|
43
|
+
|
|
44
|
+
## 外部贡献
|
|
45
|
+
|
|
46
|
+
双许可要求项目持续保有提供商业许可证所需的权利。提交代码前请阅读
|
|
47
|
+
[CONTRIBUTING.zh-CN.md](CONTRIBUTING.zh-CN.md)。本文只是项目政策摘要,不能替代
|
|
48
|
+
许可证原文或专业法律意见。
|
package/dist/ai.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue } from "@smartdoca/plugin-contracts";
|
|
2
|
+
import type { PluginRequestContext } from "./platform.js";
|
|
3
|
+
export interface PluginAIToolContext extends PluginRequestContext {
|
|
4
|
+
readonly sessionId: string;
|
|
5
|
+
readonly turnId: string;
|
|
6
|
+
readonly jobId: string;
|
|
7
|
+
readonly callId: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PluginAITool {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly description: string;
|
|
12
|
+
readonly inputSchema: JsonObject;
|
|
13
|
+
readonly approval?: "never" | "policy" | "always";
|
|
14
|
+
execute(input: JsonObject, context: PluginAIToolContext): Promise<JsonValue>;
|
|
15
|
+
}
|
|
16
|
+
export interface PluginAISkill {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly content: string;
|
|
21
|
+
readonly formats: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
export interface AIServiceV1 {
|
|
24
|
+
registerTool(tool: PluginAITool): () => void;
|
|
25
|
+
registerSkill(skill: PluginAISkill): () => void;
|
|
26
|
+
}
|
|
27
|
+
export declare const aiServiceToken: import("@smartdoca/plugin-contracts").ServiceToken<AIServiceV1>;
|
package/dist/ai.js
ADDED
package/dist/files.d.ts
ADDED
package/dist/files.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type DispatchMode, type DispatchResult, type DocaContext, type DocaContributionRegistry, type DocaPluginPackage, type DocaSystemConfig, type DocaPlugin, type EffectCleanup, type EffectDisposer, type EventHandler, type EventToken, type JsonObject, type NonWaterfallDispatchMode, type PluginLifecycleContext, type PluginManifest, type ServiceToken, type ContributionPoint, type ContributionRecord, type WaterfallNext } from "@smartdoca/plugin-contracts";
|
|
2
|
+
import { Context as CordisContext, type Fiber as CordisFiber } from "@deepseek-ai/cordis";
|
|
3
|
+
export * from "@smartdoca/plugin-contracts";
|
|
4
|
+
export declare function plugin(packageName: string, options?: Omit<DocaPluginPackage, "package">): DocaPluginPackage;
|
|
5
|
+
export declare function defineDocaConfig(config: DocaSystemConfig): DocaSystemConfig;
|
|
6
|
+
export declare function defineService<T>(id: string): ServiceToken<T>;
|
|
7
|
+
export declare function defineEvent<Payload, Result = void, Mode extends DispatchMode = "emit">(id: string, mode?: Mode): EventToken<Payload, Result, Mode>;
|
|
8
|
+
export declare function defineContributionPoint<T>(id: string): ContributionPoint<T>;
|
|
9
|
+
interface ContextCore {
|
|
10
|
+
readonly eventModes: Map<string, DispatchMode>;
|
|
11
|
+
nextScopeId: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class Context implements DocaContext {
|
|
14
|
+
#private;
|
|
15
|
+
readonly scopeId: string;
|
|
16
|
+
constructor(scopeId?: string, internal?: {
|
|
17
|
+
native: CordisContext;
|
|
18
|
+
core: ContextCore;
|
|
19
|
+
fiber?: CordisFiber;
|
|
20
|
+
ownsFiber?: boolean;
|
|
21
|
+
});
|
|
22
|
+
get disposed(): boolean;
|
|
23
|
+
effect(setup: () => EffectCleanup | void): EffectDisposer;
|
|
24
|
+
effectAsync(setup: () => Promise<EffectCleanup | void>): Promise<EffectDisposer>;
|
|
25
|
+
provide<T>(token: ServiceToken<T>, value: T): EffectDisposer;
|
|
26
|
+
inject<T>(token: ServiceToken<T>): T;
|
|
27
|
+
injectOptional<T>(token: ServiceToken<T>): T | undefined;
|
|
28
|
+
has(token: ServiceToken<unknown>): boolean;
|
|
29
|
+
child(scopeId?: string): Context;
|
|
30
|
+
/**
|
|
31
|
+
* Create a Doca scope backed by a live Cordis plugin Fiber.
|
|
32
|
+
* PluginHost uses this for every registered plugin.
|
|
33
|
+
*/
|
|
34
|
+
createFiberScope(scopeId: string): Promise<Context>;
|
|
35
|
+
on<Payload, Result, Mode extends DispatchMode>(event: EventToken<Payload, Result, Mode>, handler: EventHandler<Payload, Result, Mode>): EffectDisposer;
|
|
36
|
+
dispatch<Payload, Result>(event: EventToken<Payload, Result, "waterfall">, payload: Payload, next: WaterfallNext<Result>): Result;
|
|
37
|
+
dispatch<Payload, Result, Mode extends NonWaterfallDispatchMode>(event: EventToken<Payload, Result, Mode>, payload: Payload): DispatchResult<Result, Mode>;
|
|
38
|
+
dispose(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export declare class ContributionStore {
|
|
41
|
+
#private;
|
|
42
|
+
forContext(context: DocaContext, pluginId: string): DocaContributionRegistry;
|
|
43
|
+
register<T>(context: DocaContext, pluginId: string, point: ContributionPoint<T>, id: string, value: T): EffectDisposer;
|
|
44
|
+
get<T>(point: ContributionPoint<T>, id: string): ContributionRecord<T> | undefined;
|
|
45
|
+
list<T>(point: ContributionPoint<T>): readonly ContributionRecord<T>[];
|
|
46
|
+
}
|
|
47
|
+
export declare class PluginContext<Config extends JsonObject = JsonObject> implements PluginLifecycleContext<Config> {
|
|
48
|
+
readonly context: DocaContext;
|
|
49
|
+
readonly manifest: PluginManifest;
|
|
50
|
+
readonly config: Config;
|
|
51
|
+
readonly contributions: DocaContributionRegistry;
|
|
52
|
+
constructor(context: DocaContext, manifest: PluginManifest, config: Config, contributions: DocaContributionRegistry);
|
|
53
|
+
get scopeId(): string;
|
|
54
|
+
get disposed(): boolean;
|
|
55
|
+
provide<T>(token: ServiceToken<T>, value: T): EffectDisposer;
|
|
56
|
+
inject<T>(token: ServiceToken<T>): T;
|
|
57
|
+
injectOptional<T>(token: ServiceToken<T>): T | undefined;
|
|
58
|
+
has(token: ServiceToken<unknown>): boolean;
|
|
59
|
+
effect(setup: () => EffectCleanup | void): EffectDisposer;
|
|
60
|
+
effectAsync(setup: () => Promise<EffectCleanup | void>): Promise<EffectDisposer>;
|
|
61
|
+
child(scopeId?: string): DocaContext;
|
|
62
|
+
on<Payload, Result, Mode extends DispatchMode>(event: EventToken<Payload, Result, Mode>, handler: EventHandler<Payload, Result, Mode>): EffectDisposer;
|
|
63
|
+
dispatch<Payload, Result>(event: EventToken<Payload, Result, "waterfall">, payload: Payload, next: WaterfallNext<Result>): Result;
|
|
64
|
+
dispatch<Payload, Result, Mode extends NonWaterfallDispatchMode>(event: EventToken<Payload, Result, Mode>, payload: Payload): DispatchResult<Result, Mode>;
|
|
65
|
+
dispose(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export declare function definePlugin<Config extends JsonObject = JsonObject>(plugin: DocaPlugin<Config>): DocaPlugin<Config>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { PluginContractError, validatePluginManifest, } from "@smartdoca/plugin-contracts";
|
|
2
|
+
import { Context as CordisContext, } from "@deepseek-ai/cordis";
|
|
3
|
+
export * from "@smartdoca/plugin-contracts";
|
|
4
|
+
const contractIdentifier = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
|
|
5
|
+
const contributionIdentifier = /^[A-Za-z0-9][A-Za-z0-9:./_-]*$/;
|
|
6
|
+
function assertIdentifier(value, kind) {
|
|
7
|
+
if (!contractIdentifier.test(value))
|
|
8
|
+
throw new PluginContractError("INVALID_MANIFEST", `${kind} must be a stable lowercase dotted or dashed identifier`, value || "$");
|
|
9
|
+
}
|
|
10
|
+
export function plugin(packageName, options = {}) {
|
|
11
|
+
if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(packageName))
|
|
12
|
+
throw new TypeError(`Invalid plugin package name: ${packageName}`);
|
|
13
|
+
const targets = options.targets
|
|
14
|
+
? [...new Set(options.targets)]
|
|
15
|
+
: undefined;
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
package: packageName,
|
|
18
|
+
...(options.enabled === undefined ? {} : { enabled: options.enabled }),
|
|
19
|
+
...(options.config === undefined
|
|
20
|
+
? {}
|
|
21
|
+
: { config: structuredClone(options.config) }),
|
|
22
|
+
...(targets === undefined ? {} : { targets: Object.freeze(targets) }),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function defineDocaConfig(config) {
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
const plugins = config.plugins.map((entry) => {
|
|
28
|
+
if (seen.has(entry.package))
|
|
29
|
+
throw new TypeError(`Duplicate plugin package: ${entry.package}`);
|
|
30
|
+
seen.add(entry.package);
|
|
31
|
+
return plugin(entry.package, entry);
|
|
32
|
+
});
|
|
33
|
+
return Object.freeze({ plugins: Object.freeze(plugins) });
|
|
34
|
+
}
|
|
35
|
+
export function defineService(id) {
|
|
36
|
+
assertIdentifier(id, "service identifier");
|
|
37
|
+
return Object.freeze({ id });
|
|
38
|
+
}
|
|
39
|
+
export function defineEvent(id, mode = "emit") {
|
|
40
|
+
assertIdentifier(id, "event identifier");
|
|
41
|
+
if (!["emit", "parallel", "serial", "bail", "waterfall"].includes(mode))
|
|
42
|
+
throw new PluginContractError("INVALID_MANIFEST", "unsupported dispatch mode", id);
|
|
43
|
+
return Object.freeze({ id, mode });
|
|
44
|
+
}
|
|
45
|
+
export function defineContributionPoint(id) {
|
|
46
|
+
assertIdentifier(id, "contribution point identifier");
|
|
47
|
+
return Object.freeze({ id });
|
|
48
|
+
}
|
|
49
|
+
export class Context {
|
|
50
|
+
scopeId;
|
|
51
|
+
#native;
|
|
52
|
+
#core;
|
|
53
|
+
#effects = [];
|
|
54
|
+
#fiber;
|
|
55
|
+
#ownsFiber;
|
|
56
|
+
#state = "active";
|
|
57
|
+
#disposePromise;
|
|
58
|
+
constructor(scopeId = "root", internal) {
|
|
59
|
+
this.scopeId = scopeId;
|
|
60
|
+
this.#native = internal?.native ?? new CordisContext();
|
|
61
|
+
this.#core = internal?.core ?? {
|
|
62
|
+
eventModes: new Map(),
|
|
63
|
+
nextScopeId: 0,
|
|
64
|
+
};
|
|
65
|
+
this.#fiber = internal?.fiber;
|
|
66
|
+
this.#ownsFiber = internal?.ownsFiber ?? false;
|
|
67
|
+
}
|
|
68
|
+
get disposed() {
|
|
69
|
+
return this.#state !== "active";
|
|
70
|
+
}
|
|
71
|
+
#assertActive() {
|
|
72
|
+
if (this.#state !== "active")
|
|
73
|
+
throw new PluginContractError("CONTEXT_DISPOSED", `context ${this.scopeId} is disposed`, this.scopeId);
|
|
74
|
+
}
|
|
75
|
+
#assertReadable() {
|
|
76
|
+
if (this.#state === "disposed")
|
|
77
|
+
throw new PluginContractError("CONTEXT_DISPOSED", `context ${this.scopeId} is disposed`, this.scopeId);
|
|
78
|
+
}
|
|
79
|
+
#track(dispose) {
|
|
80
|
+
this.#assertActive();
|
|
81
|
+
const record = { active: true, dispose };
|
|
82
|
+
this.#effects.push(record);
|
|
83
|
+
return async () => {
|
|
84
|
+
if (!record.active)
|
|
85
|
+
return;
|
|
86
|
+
record.active = false;
|
|
87
|
+
await record.dispose();
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
effect(setup) {
|
|
91
|
+
this.#assertActive();
|
|
92
|
+
const dispose = this.#native.fiber.effect(() => setup() ?? (() => { }), `doca.effect(${this.scopeId})`);
|
|
93
|
+
return this.#track(dispose);
|
|
94
|
+
}
|
|
95
|
+
async effectAsync(setup) {
|
|
96
|
+
this.#assertActive();
|
|
97
|
+
const cordisEffect = this.#native.fiber.effect(async () => (await setup()) ?? (() => { }), `doca.effectAsync(${this.scopeId})`);
|
|
98
|
+
const release = this.#track(cordisEffect);
|
|
99
|
+
await cordisEffect;
|
|
100
|
+
if (this.#state !== "active") {
|
|
101
|
+
await release();
|
|
102
|
+
throw new PluginContractError("CONTEXT_DISPOSED", `context ${this.scopeId} was disposed during async effect setup`, this.scopeId);
|
|
103
|
+
}
|
|
104
|
+
return release;
|
|
105
|
+
}
|
|
106
|
+
provide(token, value) {
|
|
107
|
+
this.#assertActive();
|
|
108
|
+
try {
|
|
109
|
+
return this.#track(this.#native.provide(token.id, value));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof Error &&
|
|
113
|
+
error.message.includes(`service "${token.id}" has been registered`))
|
|
114
|
+
throw new PluginContractError("DUPLICATE_PROVIDER", `service ${token.id} is already provided (${error.message})`, token.id);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
inject(token) {
|
|
119
|
+
this.#assertReadable();
|
|
120
|
+
const value = this.#native.get(token.id, true);
|
|
121
|
+
if (value === undefined)
|
|
122
|
+
throw new PluginContractError("MISSING_INJECTION", `required service ${token.id} is unavailable`, token.id);
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
injectOptional(token) {
|
|
126
|
+
this.#assertReadable();
|
|
127
|
+
return this.#native.get(token.id, true);
|
|
128
|
+
}
|
|
129
|
+
has(token) {
|
|
130
|
+
this.#assertReadable();
|
|
131
|
+
return this.#native.get(token.id, true) !== undefined;
|
|
132
|
+
}
|
|
133
|
+
child(scopeId) {
|
|
134
|
+
this.#assertActive();
|
|
135
|
+
const child = new Context(scopeId ?? `${this.scopeId}.${++this.#core.nextScopeId}`, {
|
|
136
|
+
native: this.#native.extend(),
|
|
137
|
+
core: this.#core,
|
|
138
|
+
fiber: this.#fiber,
|
|
139
|
+
});
|
|
140
|
+
this.effect(() => () => child.dispose());
|
|
141
|
+
return child;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Create a Doca scope backed by a live Cordis plugin Fiber.
|
|
145
|
+
* PluginHost uses this for every registered plugin.
|
|
146
|
+
*/
|
|
147
|
+
async createFiberScope(scopeId) {
|
|
148
|
+
this.#assertActive();
|
|
149
|
+
const runtime = {
|
|
150
|
+
name: `doca:${scopeId}`,
|
|
151
|
+
apply(_context) { },
|
|
152
|
+
};
|
|
153
|
+
const fiber = this.#native.plugin(runtime);
|
|
154
|
+
await fiber;
|
|
155
|
+
const child = new Context(scopeId, {
|
|
156
|
+
native: fiber.ctx,
|
|
157
|
+
core: this.#core,
|
|
158
|
+
fiber,
|
|
159
|
+
ownsFiber: true,
|
|
160
|
+
});
|
|
161
|
+
this.#track(() => child.dispose());
|
|
162
|
+
return child;
|
|
163
|
+
}
|
|
164
|
+
#assertEventMode(event) {
|
|
165
|
+
const current = this.#core.eventModes.get(event.id);
|
|
166
|
+
if (current && current !== event.mode)
|
|
167
|
+
throw new PluginContractError("INVALID_MANIFEST", `event ${event.id} was already defined with ${current} dispatch`, event.id);
|
|
168
|
+
this.#core.eventModes.set(event.id, event.mode);
|
|
169
|
+
}
|
|
170
|
+
on(event, handler) {
|
|
171
|
+
this.#assertActive();
|
|
172
|
+
this.#assertEventMode(event);
|
|
173
|
+
const dispose = this.#native.on(event.id, handler);
|
|
174
|
+
return this.#track(dispose);
|
|
175
|
+
}
|
|
176
|
+
dispatch(event, payload, next) {
|
|
177
|
+
this.#assertReadable();
|
|
178
|
+
this.#assertEventMode(event);
|
|
179
|
+
const native = this.#native;
|
|
180
|
+
if (event.mode === "waterfall") {
|
|
181
|
+
if (!next)
|
|
182
|
+
throw new PluginContractError("INVALID_MANIFEST", `waterfall event ${event.id} requires a next continuation`, event.id);
|
|
183
|
+
return native.waterfall(event.id, payload, next);
|
|
184
|
+
}
|
|
185
|
+
return native[event.mode](event.id, payload);
|
|
186
|
+
}
|
|
187
|
+
dispose() {
|
|
188
|
+
if (this.#disposePromise)
|
|
189
|
+
return this.#disposePromise;
|
|
190
|
+
this.#state = "disposing";
|
|
191
|
+
this.#disposePromise = (async () => {
|
|
192
|
+
const errors = [];
|
|
193
|
+
for (let index = this.#effects.length - 1; index >= 0; index--) {
|
|
194
|
+
const effect = this.#effects[index];
|
|
195
|
+
if (!effect.active)
|
|
196
|
+
continue;
|
|
197
|
+
effect.active = false;
|
|
198
|
+
try {
|
|
199
|
+
await effect.dispose();
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
errors.push(error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (this.#ownsFiber && this.#fiber) {
|
|
206
|
+
try {
|
|
207
|
+
await this.#fiber.dispose();
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
errors.push(error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
this.#state = "disposed";
|
|
214
|
+
if (errors.length === 1)
|
|
215
|
+
throw errors[0];
|
|
216
|
+
if (errors.length)
|
|
217
|
+
throw new AggregateError(errors, `Failed to dispose context ${this.scopeId}`);
|
|
218
|
+
})();
|
|
219
|
+
return this.#disposePromise;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export class ContributionStore {
|
|
223
|
+
#records = new Map();
|
|
224
|
+
forContext(context, pluginId) {
|
|
225
|
+
assertIdentifier(pluginId, "plugin identifier");
|
|
226
|
+
return new ScopedContributionRegistry(this, context, pluginId);
|
|
227
|
+
}
|
|
228
|
+
register(context, pluginId, point, id, value) {
|
|
229
|
+
assertIdentifier(point.id, "contribution point identifier");
|
|
230
|
+
if (!contributionIdentifier.test(id))
|
|
231
|
+
throw new PluginContractError("INVALID_MANIFEST", "contribution identifier contains unsupported characters", id || "$");
|
|
232
|
+
const key = `${point.id}\u0000${id}`;
|
|
233
|
+
const existing = this.#records.get(key);
|
|
234
|
+
if (existing)
|
|
235
|
+
throw new PluginContractError("CONTRIBUTION_COLLISION", `contribution ${point.id}/${id} is already registered by ${existing.pluginId}`, `${point.id}.${id}`);
|
|
236
|
+
const record = {
|
|
237
|
+
point: point.id,
|
|
238
|
+
id,
|
|
239
|
+
pluginId,
|
|
240
|
+
value,
|
|
241
|
+
};
|
|
242
|
+
this.#records.set(key, record);
|
|
243
|
+
try {
|
|
244
|
+
return context.effect(() => () => {
|
|
245
|
+
if (this.#records.get(key) === record)
|
|
246
|
+
this.#records.delete(key);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
this.#records.delete(key);
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
get(point, id) {
|
|
255
|
+
return this.#records.get(`${point.id}\u0000${id}`);
|
|
256
|
+
}
|
|
257
|
+
list(point) {
|
|
258
|
+
return [...this.#records.values()].filter((record) => record.point === point.id);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
class ScopedContributionRegistry {
|
|
262
|
+
store;
|
|
263
|
+
context;
|
|
264
|
+
pluginId;
|
|
265
|
+
constructor(store, context, pluginId) {
|
|
266
|
+
this.store = store;
|
|
267
|
+
this.context = context;
|
|
268
|
+
this.pluginId = pluginId;
|
|
269
|
+
}
|
|
270
|
+
register(point, id, value) {
|
|
271
|
+
return this.store.register(this.context, this.pluginId, point, id, value);
|
|
272
|
+
}
|
|
273
|
+
get(point, id) {
|
|
274
|
+
return this.store.get(point, id);
|
|
275
|
+
}
|
|
276
|
+
list(point) {
|
|
277
|
+
return this.store.list(point);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
export class PluginContext {
|
|
281
|
+
context;
|
|
282
|
+
manifest;
|
|
283
|
+
config;
|
|
284
|
+
contributions;
|
|
285
|
+
constructor(context, manifest, config, contributions) {
|
|
286
|
+
this.context = context;
|
|
287
|
+
this.manifest = manifest;
|
|
288
|
+
this.config = config;
|
|
289
|
+
this.contributions = contributions;
|
|
290
|
+
}
|
|
291
|
+
get scopeId() {
|
|
292
|
+
return this.context.scopeId;
|
|
293
|
+
}
|
|
294
|
+
get disposed() {
|
|
295
|
+
return this.context.disposed;
|
|
296
|
+
}
|
|
297
|
+
provide(token, value) {
|
|
298
|
+
return this.context.provide(token, value);
|
|
299
|
+
}
|
|
300
|
+
inject(token) {
|
|
301
|
+
return this.context.inject(token);
|
|
302
|
+
}
|
|
303
|
+
injectOptional(token) {
|
|
304
|
+
return this.context.injectOptional(token);
|
|
305
|
+
}
|
|
306
|
+
has(token) {
|
|
307
|
+
return this.context.has(token);
|
|
308
|
+
}
|
|
309
|
+
effect(setup) {
|
|
310
|
+
return this.context.effect(setup);
|
|
311
|
+
}
|
|
312
|
+
effectAsync(setup) {
|
|
313
|
+
return this.context.effectAsync(setup);
|
|
314
|
+
}
|
|
315
|
+
child(scopeId) {
|
|
316
|
+
return this.context.child(scopeId);
|
|
317
|
+
}
|
|
318
|
+
on(event, handler) {
|
|
319
|
+
return this.context.on(event, handler);
|
|
320
|
+
}
|
|
321
|
+
dispatch(event, payload, next) {
|
|
322
|
+
if (event.mode === "waterfall")
|
|
323
|
+
return this.context.dispatch(event, payload, next);
|
|
324
|
+
return this.context.dispatch(event, payload);
|
|
325
|
+
}
|
|
326
|
+
dispose() {
|
|
327
|
+
return this.context.dispose();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
export function definePlugin(plugin) {
|
|
331
|
+
const manifest = validatePluginManifest(plugin.manifest);
|
|
332
|
+
const required = plugin.injections?.required ?? [];
|
|
333
|
+
const optional = plugin.injections?.optional ?? [];
|
|
334
|
+
const seen = new Set();
|
|
335
|
+
for (const token of [...required, ...optional]) {
|
|
336
|
+
assertIdentifier(token.id, "service identifier");
|
|
337
|
+
if (seen.has(token.id))
|
|
338
|
+
throw new PluginContractError("INVALID_MANIFEST", `service ${token.id} is declared more than once`, `${manifest.id}.injections`);
|
|
339
|
+
seen.add(token.id);
|
|
340
|
+
}
|
|
341
|
+
return Object.freeze({ ...plugin, manifest });
|
|
342
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { KnowledgeSourceRegistryV1 } from "@smartdoca/knowledge-capability";
|
|
2
|
+
export * from "@smartdoca/knowledge-capability";
|
|
3
|
+
export declare const knowledgeSourceRegistryToken: import("@smartdoca/plugin-contracts").ServiceToken<Pick<KnowledgeSourceRegistryV1, "register">>;
|