@syllm/brickly-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.
@@ -0,0 +1,64 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { Readable, Writable } from 'node:stream';
3
+ import type { BppMessage } from './protocol';
4
+ export interface BppTransportOptions {
5
+ /** Brick id,用于生成请求 id 前缀与 runtime.ready 身份。 */
6
+ brickId: string;
7
+ /** stdin 流,默认 process.stdin。测试时可注入 mock。 */
8
+ stdin?: Readable;
9
+ /** stdout 流,默认 process.stdout。 */
10
+ stdout?: Writable;
11
+ /** stderr 流,默认 process.stderr。SDK 仅用于日志,不写协议。 */
12
+ stderr?: Writable;
13
+ /**
14
+ * 宿主管道断开时是否触发 host-disconnect。
15
+ * 默认:使用真实 process.stdin 时开启;测试/自定义 stdin 默认关闭。
16
+ */
17
+ exitOnHostDisconnect?: boolean;
18
+ }
19
+ /**
20
+ * 底层 BPP 传输层:负责 stdin 行读取、stdout 行写入、host.* 请求-响应路由与 id 分配。
21
+ * 不感知 command / event 业务语义,那部分由上层 BricklyRuntime(api.ts)实现。
22
+ *
23
+ * 事件:
24
+ * - 'message' (msg) 收到任意一条 BPP 消息(含 host.result/host.error 等)
25
+ * - 'command' (msg) 仅 command.invoke / command.cancel
26
+ * - 'event' (msg) 仅 event.notify
27
+ * - 'shutdown' () 收到 runtime.shutdown
28
+ * - 'error' (err) 解析失败 / 内部异常
29
+ */
30
+ export declare class BppTransport extends EventEmitter {
31
+ readonly brickId: string;
32
+ private readonly stdin;
33
+ private readonly stdout;
34
+ private readonly stderr;
35
+ private readonly exitOnHostDisconnect;
36
+ private buffer;
37
+ private idCounter;
38
+ private started;
39
+ private pending;
40
+ constructor(opts: BppTransportOptions);
41
+ /** 启动 stdin 监听。重复调用安全。 */
42
+ start(): void;
43
+ /** 停止监听并拒掉所有 pending host 调用。 */
44
+ stop(reason?: string): void;
45
+ /** 写一条 BPP 消息到 stdout。线程安全(Node 单线程,单次 write 是原子的)。 */
46
+ send(message: BppMessage): void;
47
+ /** 写 stderr 日志。stdout 永远只写协议;Brick 身份由宿主日志中心附加。 */
48
+ log(...parts: unknown[]): void;
49
+ /**
50
+ * 发起一个需要等待 host.result/host.error 回复的 host.* 调用。
51
+ * 入参省略 id(SDK 自动分配),返回 result 字段。
52
+ */
53
+ hostCall<T = unknown>(msg: Omit<Extract<BppMessage, {
54
+ id: string;
55
+ }>, 'id'> & {
56
+ type: string;
57
+ }): Promise<T>;
58
+ nextHostId(): string;
59
+ private nextId;
60
+ private handleChunk;
61
+ private handleInputClosed;
62
+ private handleInputError;
63
+ private dispatch;
64
+ }
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BppTransport = void 0;
4
+ const node_events_1 = require("node:events");
5
+ const errors_1 = require("./errors");
6
+ /**
7
+ * 底层 BPP 传输层:负责 stdin 行读取、stdout 行写入、host.* 请求-响应路由与 id 分配。
8
+ * 不感知 command / event 业务语义,那部分由上层 BricklyRuntime(api.ts)实现。
9
+ *
10
+ * 事件:
11
+ * - 'message' (msg) 收到任意一条 BPP 消息(含 host.result/host.error 等)
12
+ * - 'command' (msg) 仅 command.invoke / command.cancel
13
+ * - 'event' (msg) 仅 event.notify
14
+ * - 'shutdown' () 收到 runtime.shutdown
15
+ * - 'error' (err) 解析失败 / 内部异常
16
+ */
17
+ class BppTransport extends node_events_1.EventEmitter {
18
+ brickId;
19
+ stdin;
20
+ stdout;
21
+ stderr;
22
+ exitOnHostDisconnect;
23
+ buffer = '';
24
+ idCounter = 0;
25
+ started = false;
26
+ pending = new Map();
27
+ constructor(opts) {
28
+ super();
29
+ this.brickId = opts.brickId;
30
+ this.stdin = opts.stdin ?? process.stdin;
31
+ this.stdout = opts.stdout ?? process.stdout;
32
+ this.stderr = opts.stderr ?? process.stderr;
33
+ this.exitOnHostDisconnect = opts.exitOnHostDisconnect ?? !opts.stdin;
34
+ }
35
+ /** 启动 stdin 监听。重复调用安全。 */
36
+ start() {
37
+ if (this.started)
38
+ return;
39
+ this.started = true;
40
+ if (typeof this.stdin.setEncoding ===
41
+ 'function') {
42
+ ;
43
+ this.stdin.setEncoding('utf8');
44
+ }
45
+ this.stdin.on('data', this.handleChunk);
46
+ this.stdin.on('end', this.handleInputClosed);
47
+ this.stdin.on('close', this.handleInputClosed);
48
+ this.stdin.on('error', this.handleInputError);
49
+ }
50
+ /** 停止监听并拒掉所有 pending host 调用。 */
51
+ stop(reason = 'transport stopped') {
52
+ if (!this.started)
53
+ return;
54
+ this.started = false;
55
+ this.stdin.off?.('data', this.handleChunk);
56
+ this.stdin.off?.('end', this.handleInputClosed);
57
+ this.stdin.off?.('close', this.handleInputClosed);
58
+ this.stdin.off?.('error', this.handleInputError);
59
+ for (const [, waiter] of this.pending) {
60
+ waiter.reject(new errors_1.BppError('PROCESS_EXITED', reason));
61
+ }
62
+ this.pending.clear();
63
+ }
64
+ /** 写一条 BPP 消息到 stdout。线程安全(Node 单线程,单次 write 是原子的)。 */
65
+ send(message) {
66
+ const line = JSON.stringify(message) + '\n';
67
+ this.stdout.write(line);
68
+ }
69
+ /** 写 stderr 日志。stdout 永远只写协议;Brick 身份由宿主日志中心附加。 */
70
+ log(...parts) {
71
+ const line = `${parts.map((p) => (typeof p === 'string' ? p : JSON.stringify(p))).join(' ')}\n`;
72
+ this.stderr.write(line);
73
+ }
74
+ /**
75
+ * 发起一个需要等待 host.result/host.error 回复的 host.* 调用。
76
+ * 入参省略 id(SDK 自动分配),返回 result 字段。
77
+ */
78
+ hostCall(msg) {
79
+ const id = this.nextId();
80
+ return new Promise((resolve, reject) => {
81
+ this.pending.set(id, {
82
+ resolve: (v) => resolve(v),
83
+ reject
84
+ });
85
+ this.send({ ...msg, id });
86
+ });
87
+ }
88
+ nextHostId() {
89
+ return this.nextId();
90
+ }
91
+ nextId() {
92
+ this.idCounter += 1;
93
+ return `${this.brickId}-${process.pid}-${this.idCounter}`;
94
+ }
95
+ handleChunk = (chunk) => {
96
+ this.buffer += typeof chunk === 'string' ? chunk : chunk.toString('utf8');
97
+ const lines = this.buffer.split(/\r?\n/);
98
+ this.buffer = lines.pop() ?? '';
99
+ for (const line of lines) {
100
+ if (!line.trim())
101
+ continue;
102
+ let msg;
103
+ try {
104
+ msg = JSON.parse(line);
105
+ }
106
+ catch (err) {
107
+ this.emit('error', new errors_1.BppError('PROTOCOL_ERROR', `Invalid JSON line: ${line}`, { cause: String(err) }));
108
+ continue;
109
+ }
110
+ this.dispatch(msg);
111
+ }
112
+ };
113
+ handleInputClosed = () => {
114
+ this.emit('end');
115
+ if (this.exitOnHostDisconnect) {
116
+ this.emit('host-disconnect', new errors_1.BppError('PROCESS_EXITED', 'host pipe closed'));
117
+ }
118
+ this.stop('host pipe closed');
119
+ };
120
+ handleInputError = (error) => {
121
+ this.emit('error', error);
122
+ if (this.exitOnHostDisconnect) {
123
+ this.emit('host-disconnect', new errors_1.BppError('PROCESS_EXITED', 'host pipe error', { error }));
124
+ }
125
+ this.stop('host pipe error');
126
+ };
127
+ dispatch(msg) {
128
+ // 优先处理 host.* 应答(请求-响应配对)
129
+ if (msg.type === 'host.invoke.progress' ||
130
+ msg.type === 'host.invoke.chunk' ||
131
+ msg.type === 'host.invoke.output') {
132
+ this.emit('message', msg);
133
+ return;
134
+ }
135
+ if (msg.type === 'host.result') {
136
+ const waiter = this.pending.get(msg.id);
137
+ if (waiter) {
138
+ this.pending.delete(msg.id);
139
+ waiter.resolve(msg.result);
140
+ return;
141
+ }
142
+ this.emit('message', msg);
143
+ return;
144
+ }
145
+ if (msg.type === 'host.error') {
146
+ const waiter = this.pending.get(msg.id);
147
+ if (waiter) {
148
+ this.pending.delete(msg.id);
149
+ waiter.reject((0, errors_1.payloadToError)(msg.error));
150
+ return;
151
+ }
152
+ this.emit('message', msg);
153
+ return;
154
+ }
155
+ // 心跳由 transport 自动处理
156
+ if (msg.type === 'runtime.ping') {
157
+ this.send({ type: 'runtime.pong', id: msg.id });
158
+ return;
159
+ }
160
+ if (msg.type === 'runtime.shutdown') {
161
+ this.emit('shutdown');
162
+ return;
163
+ }
164
+ if (msg.type === 'event.notify') {
165
+ this.emit('event', msg);
166
+ this.emit('message', msg);
167
+ return;
168
+ }
169
+ if (msg.type === 'command.invoke' || msg.type === 'command.cancel') {
170
+ this.emit('command', msg);
171
+ this.emit('message', msg);
172
+ return;
173
+ }
174
+ this.emit('message', msg);
175
+ }
176
+ }
177
+ exports.BppTransport = BppTransport;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const typegen_1 = require("./typegen");
5
+ function main(argv) {
6
+ const options = parseArgs(argv);
7
+ const result = (0, typegen_1.generateDependencyTypes)(options);
8
+ for (const warning of result.warnings) {
9
+ console.warn(`! ${warning}`);
10
+ }
11
+ console.log(`✓ generated ${result.commandCount} command wrapper(s) for ${result.dependencyCount} dependency Brick(s)`);
12
+ console.log(` out: ${result.outDir}`);
13
+ console.log(` sdk module: ${result.sdkModule}`);
14
+ }
15
+ function parseArgs(argv) {
16
+ const options = {};
17
+ for (let index = 0; index < argv.length; index += 1) {
18
+ const arg = argv[index];
19
+ if (arg === '--help' || arg === '-h') {
20
+ printHelp();
21
+ process.exit(0);
22
+ }
23
+ const value = argv[index + 1];
24
+ if (!value || value.startsWith('-')) {
25
+ throw new Error(`参数 ${arg} 缺少值。`);
26
+ }
27
+ index += 1;
28
+ switch (arg) {
29
+ case '--manifest':
30
+ options.manifestPath = value;
31
+ break;
32
+ case '--brick-root':
33
+ options.brickRoot = value;
34
+ break;
35
+ case '--bricks-dir':
36
+ options.bricksDir = value;
37
+ break;
38
+ case '--out':
39
+ options.outDir = value;
40
+ break;
41
+ case '--sdk-module':
42
+ options.sdkModule = value;
43
+ break;
44
+ default:
45
+ throw new Error(`未知参数:${arg}`);
46
+ }
47
+ }
48
+ return options;
49
+ }
50
+ function printHelp() {
51
+ console.log(`Usage: brickly-typegen [options]
52
+
53
+ Options:
54
+ --manifest <path> 当前 Brick 的 manifest.json,默认 ./manifest.json
55
+ --brick-root <path> 当前 Brick 根目录,默认 manifest 所在目录
56
+ --bricks-dir <path> 所有 Brick 所在目录,默认当前 Brick 根目录的父目录
57
+ --out <path> 输出目录,默认 runtime/node/_generated/deps
58
+ --sdk-module <name> CommandMap 扩展的 SDK 模块名,默认自动推导
59
+ -h, --help 显示帮助
60
+ `);
61
+ }
62
+ try {
63
+ main(process.argv.slice(2));
64
+ }
65
+ catch (error) {
66
+ console.error(error instanceof Error ? error.message : String(error));
67
+ process.exit(1);
68
+ }
@@ -0,0 +1,24 @@
1
+ export interface TypegenOptions {
2
+ /** 当前 Brick 的 manifest.json 路径。默认读取 cwd/manifest.json。 */
3
+ manifestPath?: string;
4
+ /** Brick 根目录。默认取 manifest 所在目录。 */
5
+ brickRoot?: string;
6
+ /** 所有 Brick 所在目录。默认取当前 Brick 根目录的父目录。 */
7
+ bricksDir?: string;
8
+ /** 已加载依赖 Brick 的真实 manifest 路径索引;优先级高于 bricksDir/<brickId>/manifest.json。 */
9
+ dependencyManifestPaths?: Record<string, string>;
10
+ /** 输出目录。默认写到 runtime/node/_generated/deps。 */
11
+ outDir?: string;
12
+ /** 生成 CommandMap 扩展时使用的 SDK 模块名。默认自动推导。 */
13
+ sdkModule?: string;
14
+ }
15
+ export interface TypegenResult {
16
+ manifestPath: string;
17
+ outDir: string;
18
+ sdkModule: string;
19
+ dependencyCount: number;
20
+ commandCount: number;
21
+ files: string[];
22
+ warnings: string[];
23
+ }
24
+ export declare function generateDependencyTypes(options?: TypegenOptions): TypegenResult;