@fraqjs/fraq 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/dist/index.d.mts +24 -11
- package/dist/index.mjs +66 -34
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,11 +23,8 @@ Fraq 需要在 Node.js 22+ 环境(或其他的支持 WebSocket API 的服务
|
|
|
23
23
|
```typescript
|
|
24
24
|
import { Context, msg, param } from '@fraqjs/fraq';
|
|
25
25
|
|
|
26
|
-
const ctx = Context.
|
|
27
|
-
|
|
28
|
-
baseUrl: '<替换成你的 Milky 协议端地址>',
|
|
29
|
-
// accessToken: '<如果你的 Milky 协议端配置了 token,请在这里添加>',
|
|
30
|
-
},
|
|
26
|
+
const ctx = Context.fromUrl('<替换成你的 Milky 协议端地址>', {
|
|
27
|
+
// accessToken: '<如果你的 Milky 协议端配置了 token,请在这里添加>',
|
|
31
28
|
});
|
|
32
29
|
|
|
33
30
|
ctx.router.command(
|
package/dist/index.d.mts
CHANGED
|
@@ -5396,21 +5396,27 @@ type EventMap = {
|
|
|
5396
5396
|
};
|
|
5397
5397
|
//#endregion
|
|
5398
5398
|
//#region src/protocol/client.d.ts
|
|
5399
|
+
interface MilkyEventSubscription {
|
|
5400
|
+
closed: Promise<void>;
|
|
5401
|
+
stop(): void | Promise<void>;
|
|
5402
|
+
}
|
|
5403
|
+
interface MilkyClient extends ApiEndpoints {
|
|
5404
|
+
startEvents(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
|
|
5405
|
+
}
|
|
5399
5406
|
declare class MilkyClientBase {
|
|
5400
5407
|
readonly baseUrl: string;
|
|
5401
5408
|
readonly wsBaseUrl: string;
|
|
5402
5409
|
private readonly accessToken?;
|
|
5403
5410
|
private readonly baseHeaders;
|
|
5404
5411
|
constructor(
|
|
5405
|
-
baseUrl: string,
|
|
5412
|
+
baseUrl: string | URL,
|
|
5406
5413
|
options?: {
|
|
5407
5414
|
accessToken?: string;
|
|
5408
5415
|
},
|
|
5409
5416
|
);
|
|
5410
5417
|
callApi(endpoint: string, params?: unknown): Promise<unknown>;
|
|
5411
|
-
|
|
5418
|
+
startEvents(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
|
|
5412
5419
|
}
|
|
5413
|
-
type MilkyClient = MilkyClientBase & ApiEndpoints;
|
|
5414
5420
|
declare function createMilkyClient(...params: ConstructorParameters<typeof MilkyClientBase>): MilkyClient;
|
|
5415
5421
|
//#endregion
|
|
5416
5422
|
//#region src/routing/tokenizer.d.ts
|
|
@@ -5568,19 +5574,22 @@ declare function definePlugin<T extends ParameterList>(plugin: Plugin<T>): Plugi
|
|
|
5568
5574
|
//#endregion
|
|
5569
5575
|
//#region src/core/context.d.ts
|
|
5570
5576
|
interface ContextOptions {
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
initialReconnectDelay?: number;
|
|
5575
|
-
maxReconnectDelay?: number;
|
|
5577
|
+
reconnect?: {
|
|
5578
|
+
initialDelayMs?: number;
|
|
5579
|
+
maxDelayMs?: number;
|
|
5576
5580
|
};
|
|
5577
5581
|
logHandler?: (message: LogMessage) => void;
|
|
5578
5582
|
}
|
|
5583
|
+
interface ContextUrlOptions {
|
|
5584
|
+
accessToken?: string;
|
|
5585
|
+
}
|
|
5579
5586
|
declare class Context {
|
|
5580
5587
|
readonly client: MilkyClient;
|
|
5581
5588
|
readonly router: Router;
|
|
5582
5589
|
readonly logger: Logger;
|
|
5590
|
+
readonly name: string;
|
|
5583
5591
|
private readonly parent?;
|
|
5592
|
+
private readonly filter?;
|
|
5584
5593
|
private readonly eventBus;
|
|
5585
5594
|
private readonly plugins;
|
|
5586
5595
|
private readonly services;
|
|
@@ -5596,16 +5605,18 @@ declare class Context {
|
|
|
5596
5605
|
resolve<T extends object>(service: ServiceClass<T>): T;
|
|
5597
5606
|
tryResolve<T extends object>(service: ServiceClass<T>): T | undefined;
|
|
5598
5607
|
isProvided<T extends object>(service: ServiceClass<T>): boolean;
|
|
5599
|
-
fork(filter?: Filter): Context;
|
|
5608
|
+
fork(name: string, filter?: Filter): Context;
|
|
5600
5609
|
start(): Promise<void>;
|
|
5610
|
+
private acceptsParentEvent;
|
|
5601
5611
|
private applyPlugins;
|
|
5602
5612
|
private sortPlugins;
|
|
5603
5613
|
private collectAvailableServices;
|
|
5604
5614
|
private createUnresolvablePluginError;
|
|
5605
5615
|
private createProxyContextForPlugin;
|
|
5606
|
-
private
|
|
5616
|
+
private runEventStream;
|
|
5607
5617
|
private createSession;
|
|
5608
|
-
static
|
|
5618
|
+
static fromUrl(baseUrl: string | URL, options?: ContextOptions & ContextUrlOptions): Context;
|
|
5619
|
+
static fromClient(client: MilkyClient, options?: ContextOptions): Context;
|
|
5609
5620
|
}
|
|
5610
5621
|
//#endregion
|
|
5611
5622
|
//#region src/protocol/segment.d.ts
|
|
@@ -5652,11 +5663,13 @@ export {
|
|
|
5652
5663
|
ApiEndpoints,
|
|
5653
5664
|
Context,
|
|
5654
5665
|
ContextOptions,
|
|
5666
|
+
ContextUrlOptions,
|
|
5655
5667
|
EventMap,
|
|
5656
5668
|
Filter,
|
|
5657
5669
|
LogMessage,
|
|
5658
5670
|
Logger,
|
|
5659
5671
|
MilkyClient,
|
|
5672
|
+
MilkyEventSubscription,
|
|
5660
5673
|
Parameter,
|
|
5661
5674
|
ParameterList,
|
|
5662
5675
|
Plugin,
|
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,8 @@ var MilkyClientBase = class {
|
|
|
6
6
|
accessToken;
|
|
7
7
|
baseHeaders;
|
|
8
8
|
constructor(baseUrl, options) {
|
|
9
|
-
|
|
9
|
+
const normalizedBaseUrl = baseUrl.toString();
|
|
10
|
+
this.baseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1) : normalizedBaseUrl;
|
|
10
11
|
this.wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
|
|
11
12
|
this.accessToken = options?.accessToken;
|
|
12
13
|
this.baseHeaders = { "Content-Type": "application/json" };
|
|
@@ -18,18 +19,41 @@ var MilkyClientBase = class {
|
|
|
18
19
|
body: JSON.stringify(params ?? {}),
|
|
19
20
|
headers: this.baseHeaders
|
|
20
21
|
});
|
|
21
|
-
if (!response.ok) throw new Error(`API call failed with status ${response.status}`);
|
|
22
|
+
if (!response.ok) throw new Error(`API call ${endpoint} failed with HTTP status ${response.status}`);
|
|
22
23
|
const json = await response.json();
|
|
23
|
-
if (json.status === "failed") throw new Error(`API call failed with retcode ${json.retcode}: ${json.message}`);
|
|
24
|
+
if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
|
|
24
25
|
return json.data;
|
|
25
26
|
}
|
|
26
|
-
async
|
|
27
|
+
async startEvents(onEvent) {
|
|
27
28
|
const ws = new WebSocket(`${this.wsBaseUrl}/event${this.accessToken ? `?access_token=${this.accessToken}` : ""}`);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
let closeSubscription = () => {};
|
|
30
|
+
const closed = new Promise((resolve, reject) => {
|
|
31
|
+
closeSubscription = (error) => {
|
|
32
|
+
if (error) reject(error);
|
|
33
|
+
else resolve();
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
ws.addEventListener("message", async (event) => {
|
|
37
|
+
try {
|
|
38
|
+
if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
|
|
39
|
+
await onEvent(JSON.parse(event.data));
|
|
40
|
+
} catch (error) {
|
|
41
|
+
closeSubscription(error);
|
|
42
|
+
ws.close();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
ws.addEventListener("open", () => resolve(), { once: true });
|
|
31
47
|
ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
32
48
|
});
|
|
49
|
+
ws.addEventListener("error", (event) => closeSubscription(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
50
|
+
ws.addEventListener("close", () => closeSubscription(), { once: true });
|
|
51
|
+
return {
|
|
52
|
+
closed,
|
|
53
|
+
stop() {
|
|
54
|
+
ws.close();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
33
57
|
}
|
|
34
58
|
};
|
|
35
59
|
function createMilkyClient(...params) {
|
|
@@ -288,8 +312,10 @@ const DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
|
|
|
288
312
|
var Context = class Context {
|
|
289
313
|
client;
|
|
290
314
|
router = new Router();
|
|
291
|
-
logger
|
|
315
|
+
logger;
|
|
316
|
+
name;
|
|
292
317
|
parent;
|
|
318
|
+
filter;
|
|
293
319
|
eventBus = mitt();
|
|
294
320
|
plugins = [];
|
|
295
321
|
services = /* @__PURE__ */ new Map();
|
|
@@ -298,24 +324,24 @@ var Context = class Context {
|
|
|
298
324
|
maxReconnectDelayMs;
|
|
299
325
|
logHandler;
|
|
300
326
|
isStarted = false;
|
|
301
|
-
constructor(client, options, parent, filter) {
|
|
327
|
+
constructor(client, options, name, parent, filter) {
|
|
302
328
|
this.client = client;
|
|
303
|
-
this.initialReconnectDelayMs = options?.
|
|
304
|
-
this.maxReconnectDelayMs = options?.
|
|
329
|
+
this.initialReconnectDelayMs = options?.reconnect?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
|
|
330
|
+
this.maxReconnectDelayMs = options?.reconnect?.maxDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
|
|
305
331
|
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
332
|
+
this.name = name ?? "root";
|
|
333
|
+
this.logger = new Logger((message) => this.logHandler?.(message), this.name);
|
|
306
334
|
this.parent = parent;
|
|
335
|
+
this.filter = filter;
|
|
307
336
|
parent?.eventBus.on("*", (type, event) => {
|
|
308
|
-
if (
|
|
309
|
-
const predicate = filter[type];
|
|
310
|
-
if (predicate && !predicate(event)) return;
|
|
311
|
-
}
|
|
337
|
+
if (!this.acceptsParentEvent(type, event)) return;
|
|
312
338
|
this.eventBus.emit(type, event);
|
|
313
339
|
});
|
|
314
340
|
this.eventBus.on("message_receive", async ({ data: message }) => {
|
|
315
341
|
try {
|
|
316
|
-
this.router.dispatch(this.createSession(message), message);
|
|
342
|
+
await this.router.dispatch(this.createSession(message), message);
|
|
317
343
|
} catch (error) {
|
|
318
|
-
|
|
344
|
+
this.logger.error(`Error routing command (scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
|
|
319
345
|
}
|
|
320
346
|
});
|
|
321
347
|
}
|
|
@@ -324,7 +350,7 @@ var Context = class Context {
|
|
|
324
350
|
try {
|
|
325
351
|
await handler(event);
|
|
326
352
|
} catch (error) {
|
|
327
|
-
|
|
353
|
+
this.logger.error(`Error handling event ${type}`, error);
|
|
328
354
|
}
|
|
329
355
|
});
|
|
330
356
|
}
|
|
@@ -350,8 +376,8 @@ var Context = class Context {
|
|
|
350
376
|
isProvided(service) {
|
|
351
377
|
return this.tryResolve(service) !== void 0;
|
|
352
378
|
}
|
|
353
|
-
fork(filter) {
|
|
354
|
-
const subContext = new Context(this.client, void 0, this, filter);
|
|
379
|
+
fork(name, filter) {
|
|
380
|
+
const subContext = new Context(this.client, void 0, name, this, filter);
|
|
355
381
|
this.subContexts.push(subContext);
|
|
356
382
|
return subContext;
|
|
357
383
|
}
|
|
@@ -360,13 +386,20 @@ var Context = class Context {
|
|
|
360
386
|
await this.applyPlugins();
|
|
361
387
|
for (const subContext of this.subContexts) await subContext.start();
|
|
362
388
|
this.isStarted = true;
|
|
363
|
-
if (!this.parent) this.
|
|
389
|
+
if (!this.parent) this.runEventStream();
|
|
390
|
+
}
|
|
391
|
+
acceptsParentEvent(type, event) {
|
|
392
|
+
if (!this.filter) return true;
|
|
393
|
+
const predicate = this.filter[type];
|
|
394
|
+
return predicate?.(event) === true;
|
|
364
395
|
}
|
|
365
396
|
async applyPlugins() {
|
|
366
397
|
for (const { plugin, args } of this.sortPlugins()) {
|
|
367
398
|
const providedBeforeApply = new Set(this.services.keys());
|
|
399
|
+
this.logger.info(`Applying plugin ${plugin.name}`);
|
|
368
400
|
await plugin.apply(this.createProxyContextForPlugin(plugin), ...args);
|
|
369
401
|
for (const service of plugin.provides ?? []) if (!this.services.has(service) || providedBeforeApply.has(service)) throw new Error(`${plugin.name} declares service ${getServiceName(service)} but did not provide it.`);
|
|
402
|
+
this.logger.debug(`Applied plugin ${plugin.name}`);
|
|
370
403
|
}
|
|
371
404
|
}
|
|
372
405
|
sortPlugins() {
|
|
@@ -417,25 +450,21 @@ var Context = class Context {
|
|
|
417
450
|
else return target[prop];
|
|
418
451
|
} });
|
|
419
452
|
}
|
|
420
|
-
async
|
|
453
|
+
async runEventStream() {
|
|
421
454
|
let reconnectDelay = this.initialReconnectDelayMs;
|
|
422
455
|
while (this.isStarted) {
|
|
423
456
|
try {
|
|
424
|
-
const
|
|
457
|
+
const subscription = await this.client.startEvents((event) => {
|
|
425
458
|
try {
|
|
426
|
-
|
|
427
|
-
const payload = JSON.parse(event.data);
|
|
428
|
-
this.eventBus.emit(payload.event_type, payload);
|
|
459
|
+
this.eventBus.emit(event.event_type, event);
|
|
429
460
|
} catch (error) {
|
|
430
|
-
|
|
461
|
+
this.logger.error("Error handling event stream event", error);
|
|
431
462
|
}
|
|
432
463
|
});
|
|
433
464
|
reconnectDelay = this.initialReconnectDelayMs;
|
|
434
|
-
await
|
|
435
|
-
ws.addEventListener("close", () => resolve(), { once: true });
|
|
436
|
-
});
|
|
465
|
+
await subscription.closed;
|
|
437
466
|
} catch (error) {
|
|
438
|
-
|
|
467
|
+
this.logger.error("Error connecting event stream", error);
|
|
439
468
|
}
|
|
440
469
|
await new Promise((resolve) => setTimeout(resolve, reconnectDelay));
|
|
441
470
|
reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
|
|
@@ -461,13 +490,16 @@ var Context = class Context {
|
|
|
461
490
|
break;
|
|
462
491
|
}
|
|
463
492
|
} catch (error) {
|
|
464
|
-
|
|
493
|
+
this.logger.error(`Error sending reply (source msg: scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
|
|
465
494
|
}
|
|
466
495
|
}
|
|
467
496
|
};
|
|
468
497
|
}
|
|
469
|
-
static
|
|
470
|
-
return new Context(createMilkyClient(
|
|
498
|
+
static fromUrl(baseUrl, options) {
|
|
499
|
+
return new Context(createMilkyClient(baseUrl, { accessToken: options?.accessToken }), options);
|
|
500
|
+
}
|
|
501
|
+
static fromClient(client, options) {
|
|
502
|
+
return new Context(client, options);
|
|
471
503
|
}
|
|
472
504
|
};
|
|
473
505
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraqjs/fraq",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Milky Bot framework",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"node": ">=22"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
|
-
"build": "tsdown &&
|
|
26
|
+
"build": "tsdown && tsx scripts/format-dts-comments.ts && biome format --vcs-enabled=false --write dist/index.d.mts",
|
|
27
27
|
"test": "tsx --test test/*.test.ts"
|
|
28
28
|
}
|
|
29
29
|
}
|