@fraqjs/fraq 0.13.0 → 0.13.2
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/index.d.mts +7 -41
- package/dist/index.mjs +590 -465
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -5542,6 +5542,7 @@ type RouteActivation =
|
|
|
5542
5542
|
}
|
|
5543
5543
|
| {
|
|
5544
5544
|
type: 'mention';
|
|
5545
|
+
prefix?: string;
|
|
5545
5546
|
}
|
|
5546
5547
|
| {
|
|
5547
5548
|
type: 'prefix';
|
|
@@ -5828,7 +5829,7 @@ declare function definePlugin<
|
|
|
5828
5829
|
OI extends Injection | undefined,
|
|
5829
5830
|
>(plugin: Plugin<T, I, OI>): Plugin<T, I, OI>;
|
|
5830
5831
|
//#endregion
|
|
5831
|
-
//#region src/core/context.d.ts
|
|
5832
|
+
//#region src/core/context/index.d.ts
|
|
5832
5833
|
interface ContextOptions {
|
|
5833
5834
|
reconnect?: {
|
|
5834
5835
|
initialDelayMs?: number;
|
|
@@ -5851,19 +5852,14 @@ declare class Context {
|
|
|
5851
5852
|
private readonly parent?;
|
|
5852
5853
|
private readonly filter?;
|
|
5853
5854
|
private readonly eventBus;
|
|
5854
|
-
private readonly plugins;
|
|
5855
|
-
private readonly services;
|
|
5856
|
-
private readonly apiHookEntries;
|
|
5857
5855
|
private readonly subContexts;
|
|
5858
|
-
private readonly eventSourceRuntimes;
|
|
5859
5856
|
private readonly parentEventForwarder?;
|
|
5860
|
-
private readonly timers;
|
|
5861
|
-
private readonly initialReconnectDelayMs;
|
|
5862
|
-
private readonly maxReconnectDelayMs;
|
|
5863
5857
|
private readonly logHandler?;
|
|
5864
|
-
private
|
|
5865
|
-
private
|
|
5866
|
-
private
|
|
5858
|
+
private readonly plugins;
|
|
5859
|
+
private readonly apiHooks;
|
|
5860
|
+
private readonly eventSources;
|
|
5861
|
+
private readonly timers;
|
|
5862
|
+
private readonly lifecycle;
|
|
5867
5863
|
private constructor();
|
|
5868
5864
|
on<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void | Promise<void>): () => void;
|
|
5869
5865
|
install<T extends ParameterList, I extends Injection | undefined, OI extends Injection | undefined>(
|
|
@@ -5883,36 +5879,6 @@ declare class Context {
|
|
|
5883
5879
|
createSession(selfId: number, message: IncomingMessage): Session;
|
|
5884
5880
|
start(): Promise<void>;
|
|
5885
5881
|
stop(): Promise<void>;
|
|
5886
|
-
private startInternal;
|
|
5887
|
-
private getState;
|
|
5888
|
-
private stopInternal;
|
|
5889
|
-
private assertCanRegisterApiHook;
|
|
5890
|
-
private assertCanScheduleTimer;
|
|
5891
|
-
private runTimerCallback;
|
|
5892
|
-
private clearTimers;
|
|
5893
|
-
private stopEventSources;
|
|
5894
|
-
private acceptsParentEvent;
|
|
5895
|
-
private applyPlugins;
|
|
5896
|
-
private recursiveApplyPlugins;
|
|
5897
|
-
private startPlugins;
|
|
5898
|
-
private recursiveStartPlugins;
|
|
5899
|
-
private sortPlugins;
|
|
5900
|
-
private collectPendingProvidedServices;
|
|
5901
|
-
private areRequiredServicesAvailable;
|
|
5902
|
-
private areOptionalServicesReady;
|
|
5903
|
-
private collectAvailableServices;
|
|
5904
|
-
private createUnresolvablePluginError;
|
|
5905
|
-
private getPluginContext;
|
|
5906
|
-
private createProxyContextForPlugin;
|
|
5907
|
-
private startEventSource;
|
|
5908
|
-
private runEventSource;
|
|
5909
|
-
private waitForReconnectDelay;
|
|
5910
|
-
private resolveReconnectDelay;
|
|
5911
|
-
private createHookClient;
|
|
5912
|
-
private wrapAnyApiHook;
|
|
5913
|
-
private callHookedApi;
|
|
5914
|
-
private collectApiHooks;
|
|
5915
|
-
private callBaseApi;
|
|
5916
5882
|
static fromUrl(baseUrl: string | URL, options?: ContextOptions & ContextUrlOptions): Context;
|
|
5917
5883
|
static fromClient(client: MilkyClient, options?: ContextOptions): Context;
|
|
5918
5884
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -314,12 +314,16 @@ var Router = class Router {
|
|
|
314
314
|
}
|
|
315
315
|
match(session, message) {
|
|
316
316
|
for (const branch of this.branchesFrom(session, [], { includeHidden: true })) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
317
|
+
const literalActivationIndex = branch.type === "rawPattern" ? Object.values(branch.rawPattern.pattern).findIndex((parameter) => parameter.capturer.typeInstruction.type === "literal") : -1;
|
|
318
|
+
let activationInputs = DEFAULT_ACTIVATIONS;
|
|
319
|
+
if (branch.type === "command" || literalActivationIndex !== -1) {
|
|
320
|
+
const descriptor = this.describeBranch(branch);
|
|
321
|
+
const activations = this.activationResolver(descriptor, session) ?? DEFAULT_ACTIVATIONS;
|
|
322
|
+
activationInputs = Array.isArray(activations) ? activations : [activations];
|
|
323
|
+
}
|
|
324
|
+
for (const activation of activationInputs) {
|
|
320
325
|
const tokenizer = new Tokenizer(message.segments);
|
|
321
|
-
|
|
322
|
-
const match = this.matchBranch(branch, tokenizer, activation);
|
|
326
|
+
const match = this.matchBranch(branch, tokenizer, activation, session, literalActivationIndex);
|
|
323
327
|
if (match !== void 0) return match;
|
|
324
328
|
}
|
|
325
329
|
}
|
|
@@ -362,15 +366,18 @@ var Router = class Router {
|
|
|
362
366
|
consumeActivation(tokenizer, activation, session) {
|
|
363
367
|
switch (activation.type) {
|
|
364
368
|
case "direct": return true;
|
|
365
|
-
case "mention":
|
|
369
|
+
case "mention":
|
|
370
|
+
if (!tokenizer.consumeMention(session.selfId)) return false;
|
|
371
|
+
return activation.prefix === void 0 || tokenizer.consumeTextPrefix(activation.prefix);
|
|
366
372
|
case "prefix": return tokenizer.consumeTextPrefix(activation.prefix);
|
|
367
373
|
}
|
|
368
374
|
}
|
|
369
|
-
matchBranch(branch, tokenizer, activation) {
|
|
375
|
+
matchBranch(branch, tokenizer, activation, session, literalActivationIndex) {
|
|
376
|
+
if (branch.type === "command" && !this.consumeActivation(tokenizer, activation, session)) return;
|
|
370
377
|
if (!this.matchPath(branch.path, tokenizer)) return;
|
|
371
378
|
switch (branch.type) {
|
|
372
379
|
case "command": return this.matchCommand(branch.command, tokenizer, branch.path, activation);
|
|
373
|
-
case "rawPattern": return this.matchRawPattern(branch.rawPattern, tokenizer, branch.path, activation);
|
|
380
|
+
case "rawPattern": return this.matchRawPattern(branch.rawPattern, tokenizer, branch.path, activation, session, literalActivationIndex);
|
|
374
381
|
}
|
|
375
382
|
}
|
|
376
383
|
matchPath(path, tokenizer) {
|
|
@@ -395,9 +402,15 @@ var Router = class Router {
|
|
|
395
402
|
activation
|
|
396
403
|
};
|
|
397
404
|
}
|
|
398
|
-
matchRawPattern(rawPattern, tokenizer, path, activation) {
|
|
399
|
-
const params =
|
|
400
|
-
|
|
405
|
+
matchRawPattern(rawPattern, tokenizer, path, activation, session, literalActivationIndex) {
|
|
406
|
+
const params = {};
|
|
407
|
+
for (const [index, [name, parameter]] of Object.entries(rawPattern.pattern).entries()) {
|
|
408
|
+
if (index === literalActivationIndex && !this.consumeActivation(tokenizer, activation, session)) return;
|
|
409
|
+
const value = parameter.capturer.capture(tokenizer);
|
|
410
|
+
if (value === void 0) return;
|
|
411
|
+
params[name] = value;
|
|
412
|
+
}
|
|
413
|
+
if (tokenizer.hasNext()) return;
|
|
401
414
|
return {
|
|
402
415
|
type: "rawPattern",
|
|
403
416
|
path: [...path],
|
|
@@ -465,7 +478,6 @@ var Router = class Router {
|
|
|
465
478
|
validatePattern(pattern, options) {
|
|
466
479
|
const entries = Object.entries(pattern);
|
|
467
480
|
if (options?.rawPattern && entries.length === 0) throw new Error("Raw pattern must have at least one parameter.");
|
|
468
|
-
if (options?.rawPattern && entries[0]?.[1].capturer.typeInstruction.type === "literal") throw new Error("The first parameter of a raw pattern cannot be a literal, as it would conflict with command patterns.");
|
|
469
481
|
const catchAllEntryIndex = entries.findIndex(([, parameter]) => {
|
|
470
482
|
return parameter.capturer.typeInstruction.type === "catchAll";
|
|
471
483
|
});
|
|
@@ -485,12 +497,6 @@ var Router = class Router {
|
|
|
485
497
|
return params;
|
|
486
498
|
}
|
|
487
499
|
};
|
|
488
|
-
function routeActivationInputs(activations) {
|
|
489
|
-
return isRouteActivationArray(activations) ? activations : [activations];
|
|
490
|
-
}
|
|
491
|
-
function isRouteActivationArray(activations) {
|
|
492
|
-
return Array.isArray(activations);
|
|
493
|
-
}
|
|
494
500
|
//#endregion
|
|
495
501
|
//#region src/core/activation.ts
|
|
496
502
|
function createContextRouteActivationResolver(routing, fallback = defaultRouteActivationResolver) {
|
|
@@ -502,7 +508,8 @@ function createContextRouteActivationResolver(routing, fallback = defaultRouteAc
|
|
|
502
508
|
}
|
|
503
509
|
function compileContextRouteActivationConfig(config, fallback) {
|
|
504
510
|
return (route, session) => {
|
|
505
|
-
|
|
511
|
+
const rules = config.rules === void 0 ? [] : Array.isArray(config.rules) ? config.rules : [config.rules];
|
|
512
|
+
for (const rule of rules) {
|
|
506
513
|
if (!matchesContextRouteActivationRule(route, session, rule)) continue;
|
|
507
514
|
const activation = resolveContextRouteActivation(rule.activation, session.raw.message_scene);
|
|
508
515
|
if (activation !== void 0) return activation;
|
|
@@ -514,22 +521,10 @@ function compileContextRouteActivationConfig(config, fallback) {
|
|
|
514
521
|
return fallback(route, session);
|
|
515
522
|
};
|
|
516
523
|
}
|
|
517
|
-
function contextRouteActivationRules(rules) {
|
|
518
|
-
if (rules === void 0) return [];
|
|
519
|
-
return isContextRouteActivationRuleArray(rules) ? rules : [rules];
|
|
520
|
-
}
|
|
521
|
-
function isContextRouteActivationRuleArray(rules) {
|
|
522
|
-
return Array.isArray(rules);
|
|
523
|
-
}
|
|
524
524
|
function resolveContextRouteActivation(activation, scene) {
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
function isRouteActivationInput(activation) {
|
|
529
|
-
return Array.isArray(activation) || isRouteActivation(activation);
|
|
530
|
-
}
|
|
531
|
-
function isRouteActivation(activation) {
|
|
532
|
-
return "type" in activation;
|
|
525
|
+
if (Array.isArray(activation) || "type" in activation) return activation;
|
|
526
|
+
const activationByScene = activation;
|
|
527
|
+
return activationByScene[scene] ?? activationByScene.default;
|
|
533
528
|
}
|
|
534
529
|
function matchesContextRouteActivationRule(route, session, rule) {
|
|
535
530
|
const match = rule.match;
|
|
@@ -808,97 +803,29 @@ function combineLogHandlers(...handlers) {
|
|
|
808
803
|
};
|
|
809
804
|
}
|
|
810
805
|
//#endregion
|
|
811
|
-
//#region src/core/
|
|
812
|
-
|
|
813
|
-
return "dispose" in service && typeof service.dispose === "function";
|
|
814
|
-
}
|
|
815
|
-
function implementsESNextDisposable(service) {
|
|
816
|
-
return Symbol.dispose in service && typeof service[Symbol.dispose] === "function";
|
|
817
|
-
}
|
|
818
|
-
//#endregion
|
|
819
|
-
//#region src/core/context.ts
|
|
820
|
-
const DEFAULT_INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
821
|
-
const DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
|
|
822
|
-
var Context = class Context {
|
|
823
|
-
router = new Router();
|
|
824
|
-
logger;
|
|
825
|
-
name;
|
|
826
|
-
routeActivationResolver;
|
|
827
|
-
client;
|
|
806
|
+
//#region src/core/context/api-hooks.ts
|
|
807
|
+
var ApiHookRegistry = class {
|
|
828
808
|
baseClient;
|
|
829
809
|
parent;
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
subContexts = /* @__PURE__ */ new Map();
|
|
836
|
-
eventSourceRuntimes = /* @__PURE__ */ new Map();
|
|
837
|
-
parentEventForwarder;
|
|
838
|
-
timers = /* @__PURE__ */ new Set();
|
|
839
|
-
initialReconnectDelayMs;
|
|
840
|
-
maxReconnectDelayMs;
|
|
841
|
-
logHandler;
|
|
842
|
-
state = "idle";
|
|
843
|
-
startPromise;
|
|
844
|
-
stopPromise;
|
|
845
|
-
constructor(baseClient, options, name, parent, filter) {
|
|
810
|
+
contextName;
|
|
811
|
+
getState;
|
|
812
|
+
client;
|
|
813
|
+
entries = [];
|
|
814
|
+
constructor(baseClient, parent, contextName, getState) {
|
|
846
815
|
this.baseClient = baseClient;
|
|
847
|
-
this.client = this.createHookClient();
|
|
848
|
-
this.initialReconnectDelayMs = options?.reconnect?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
|
|
849
|
-
this.maxReconnectDelayMs = options?.reconnect?.maxDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
|
|
850
|
-
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
851
|
-
this.name = name ?? "root";
|
|
852
|
-
this.logger = new Logger((message) => this.logHandler?.(message), `context:${this.name}`);
|
|
853
|
-
this.routeActivationResolver = createContextRouteActivationResolver(options?.routing, parent?.routeActivationResolver);
|
|
854
|
-
this.router.setActivationResolver(this.routeActivationResolver);
|
|
855
816
|
this.parent = parent;
|
|
856
|
-
this.
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
if (!this.acceptsParentEvent(type, event)) return;
|
|
860
|
-
this.eventBus.emit(type, event);
|
|
861
|
-
};
|
|
862
|
-
parent.eventBus.on("*", this.parentEventForwarder);
|
|
863
|
-
}
|
|
864
|
-
this.eventBus.on("message_receive", async ({ self_id, data: message }) => {
|
|
865
|
-
try {
|
|
866
|
-
if (this.state === "stopping" || this.state === "stopped") return;
|
|
867
|
-
await this.router.dispatch(this.createSession(self_id, message), message);
|
|
868
|
-
} catch (error) {
|
|
869
|
-
this.logger.error(`Error routing command (scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
|
|
870
|
-
}
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
on(type, handler) {
|
|
874
|
-
const wrappedHandler = async (event) => {
|
|
875
|
-
try {
|
|
876
|
-
if (this.state === "stopping" || this.state === "stopped") return;
|
|
877
|
-
await handler(event);
|
|
878
|
-
} catch (error) {
|
|
879
|
-
this.logger.error(`Error handling event ${type}`, error);
|
|
880
|
-
}
|
|
881
|
-
};
|
|
882
|
-
this.eventBus.on(type, wrappedHandler);
|
|
883
|
-
return () => {
|
|
884
|
-
this.eventBus.off(type, wrappedHandler);
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
install(plugin, ...args) {
|
|
888
|
-
this.plugins.push({
|
|
889
|
-
plugin,
|
|
890
|
-
args
|
|
891
|
-
});
|
|
892
|
-
}
|
|
893
|
-
installEventSource(eventSource) {
|
|
894
|
-
if (this.eventSourceRuntimes.has(eventSource)) return;
|
|
895
|
-
this.eventSourceRuntimes.set(eventSource, {});
|
|
896
|
-
if (this.state === "started") this.startEventSource(eventSource);
|
|
817
|
+
this.contextName = contextName;
|
|
818
|
+
this.getState = getState;
|
|
819
|
+
this.client = this.createHookClient();
|
|
897
820
|
}
|
|
898
|
-
|
|
899
|
-
this.
|
|
821
|
+
register(endpointOrHook, hook) {
|
|
822
|
+
const state = this.getState();
|
|
823
|
+
if (state === "stopping") throw new Error(`Context "${this.contextName}" cannot register API hooks while it is stopping.`);
|
|
824
|
+
if (state === "stopped") throw new Error(`Context "${this.contextName}" cannot register API hooks after it has stopped.`);
|
|
900
825
|
let entry;
|
|
901
|
-
if (typeof endpointOrHook === "function") entry = { hook:
|
|
826
|
+
if (typeof endpointOrHook === "function") entry = { hook: (params, next, call) => {
|
|
827
|
+
return endpointOrHook(call, (nextParams = params) => next(nextParams));
|
|
828
|
+
} };
|
|
902
829
|
else {
|
|
903
830
|
if (!hook) throw new Error(`API hook for endpoint ${endpointOrHook} is missing a handler.`);
|
|
904
831
|
entry = {
|
|
@@ -906,213 +833,98 @@ var Context = class Context {
|
|
|
906
833
|
hook
|
|
907
834
|
};
|
|
908
835
|
}
|
|
909
|
-
this.
|
|
836
|
+
this.entries.push(entry);
|
|
910
837
|
let disposed = false;
|
|
911
838
|
return () => {
|
|
912
839
|
if (disposed) return;
|
|
913
840
|
disposed = true;
|
|
914
|
-
const index = this.
|
|
915
|
-
if (index !== -1) this.
|
|
841
|
+
const index = this.entries.indexOf(entry);
|
|
842
|
+
if (index !== -1) this.entries.splice(index, 1);
|
|
916
843
|
};
|
|
917
844
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
if (implementsESNextDisposable(instance) && !isDisposable(instance)) throw new Error(`
|
|
921
|
-
Service ${service.name} implements ESNext Disposable but not Fraq Disposable.
|
|
922
|
-
Please explicitly import the interface like this:
|
|
923
|
-
|
|
924
|
-
import type { Disposable } from '@fraqjs/fraq';
|
|
925
|
-
|
|
926
|
-
and implement the dispose method to clean up resources when the context stops.
|
|
927
|
-
`.trim());
|
|
928
|
-
this.services.set(service, instance);
|
|
929
|
-
}
|
|
930
|
-
resolve(service) {
|
|
931
|
-
const instance = this.tryResolve(service);
|
|
932
|
-
if (instance === void 0) throw new Error(`Service ${service.name} has not been provided.`);
|
|
933
|
-
return instance;
|
|
845
|
+
clear() {
|
|
846
|
+
this.entries.length = 0;
|
|
934
847
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
848
|
+
createHookClient() {
|
|
849
|
+
return new Proxy(this.baseClient, { get: (target, prop, receiver) => {
|
|
850
|
+
if (typeof prop === "string" && prop.includes("_")) return (params) => this.callHookedApi(prop, params);
|
|
851
|
+
return Reflect.get(target, prop, receiver);
|
|
852
|
+
} });
|
|
938
853
|
}
|
|
939
|
-
|
|
940
|
-
|
|
854
|
+
async callHookedApi(endpoint, params) {
|
|
855
|
+
const hooks = this.collectApiHooks(endpoint);
|
|
856
|
+
const dispatch = async (index, currentParams) => {
|
|
857
|
+
const hook = hooks[index];
|
|
858
|
+
if (!hook) return await this.callBaseApi(endpoint, currentParams);
|
|
859
|
+
let nextCalled = false;
|
|
860
|
+
return await hook(currentParams, async (nextParams = currentParams) => {
|
|
861
|
+
if (nextCalled) throw new Error(`API hook for endpoint ${endpoint} called next() multiple times.`);
|
|
862
|
+
nextCalled = true;
|
|
863
|
+
return await dispatch(index + 1, nextParams);
|
|
864
|
+
}, {
|
|
865
|
+
endpoint,
|
|
866
|
+
params: currentParams
|
|
867
|
+
});
|
|
868
|
+
};
|
|
869
|
+
return await dispatch(0, params);
|
|
941
870
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
871
|
+
collectApiHooks(endpoint) {
|
|
872
|
+
const hooks = [];
|
|
873
|
+
let registry = this;
|
|
874
|
+
while (registry) {
|
|
875
|
+
for (const entry of registry.entries.toReversed()) if (entry.endpoint === void 0 || entry.endpoint === endpoint) hooks.push(entry.hook);
|
|
876
|
+
registry = registry.parent;
|
|
946
877
|
}
|
|
947
|
-
|
|
948
|
-
this.subContexts.set(name, subContext);
|
|
949
|
-
return subContext;
|
|
878
|
+
return hooks;
|
|
950
879
|
}
|
|
951
|
-
|
|
952
|
-
this.
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
this.timers.add(timeout);
|
|
958
|
-
return timeout;
|
|
880
|
+
async callBaseApi(endpoint, params) {
|
|
881
|
+
const callApi = this.baseClient.callApi;
|
|
882
|
+
if (typeof callApi === "function") return await callApi.call(this.baseClient, endpoint, params);
|
|
883
|
+
const method = this.baseClient[endpoint];
|
|
884
|
+
if (typeof method !== "function") throw new Error(`Milky client does not implement API endpoint ${endpoint}.`);
|
|
885
|
+
return await method.call(this.baseClient, params);
|
|
959
886
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
887
|
+
};
|
|
888
|
+
//#endregion
|
|
889
|
+
//#region src/core/context/event-sources.ts
|
|
890
|
+
const DEFAULT_INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
891
|
+
const DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
|
|
892
|
+
var EventSourceRegistry = class {
|
|
893
|
+
logger;
|
|
894
|
+
getState;
|
|
895
|
+
emit;
|
|
896
|
+
runtimes = /* @__PURE__ */ new Map();
|
|
897
|
+
initialReconnectDelayMs;
|
|
898
|
+
maxReconnectDelayMs;
|
|
899
|
+
constructor(options, logger, getState, emit) {
|
|
900
|
+
this.logger = logger;
|
|
901
|
+
this.getState = getState;
|
|
902
|
+
this.emit = emit;
|
|
903
|
+
this.initialReconnectDelayMs = options?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
|
|
904
|
+
this.maxReconnectDelayMs = options?.maxDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
|
|
967
905
|
}
|
|
968
|
-
|
|
969
|
-
return
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
reply: async (textOrSegments, options) => {
|
|
973
|
-
const actualSegments = [];
|
|
974
|
-
if (typeof textOrSegments === "string") actualSegments.push({
|
|
975
|
-
type: "text",
|
|
976
|
-
data: { text: textOrSegments }
|
|
977
|
-
});
|
|
978
|
-
else actualSegments.push(...textOrSegments);
|
|
979
|
-
if (options?.withMention && message.message_scene === "group") actualSegments.unshift(seg.mention(message.sender_id));
|
|
980
|
-
if (options?.withQuote) actualSegments.unshift(seg.reply(message.message_seq));
|
|
981
|
-
switch (message.message_scene) {
|
|
982
|
-
case "friend": {
|
|
983
|
-
const { message_seq } = await this.client.send_private_message({
|
|
984
|
-
user_id: message.peer_id,
|
|
985
|
-
message: actualSegments
|
|
986
|
-
});
|
|
987
|
-
return { messageSeq: message_seq };
|
|
988
|
-
}
|
|
989
|
-
case "group": {
|
|
990
|
-
const { message_seq } = await this.client.send_group_message({
|
|
991
|
-
group_id: message.peer_id,
|
|
992
|
-
message: actualSegments
|
|
993
|
-
});
|
|
994
|
-
return { messageSeq: message_seq };
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
return { messageSeq: 0 };
|
|
998
|
-
},
|
|
999
|
-
reaction: async (type, reactionId) => {
|
|
1000
|
-
if (message.message_scene === "group") await this.client.send_group_message_reaction({
|
|
1001
|
-
group_id: message.peer_id,
|
|
1002
|
-
message_seq: message.message_seq,
|
|
1003
|
-
reaction_type: type,
|
|
1004
|
-
reaction: reactionId
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
906
|
+
install(eventSource) {
|
|
907
|
+
if (this.runtimes.has(eventSource)) return;
|
|
908
|
+
this.runtimes.set(eventSource, {});
|
|
909
|
+
if (this.getState() === "started") this.startEventSource(eventSource);
|
|
1008
910
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
if (this.state === "starting") {
|
|
1012
|
-
await this.startPromise;
|
|
1013
|
-
return;
|
|
1014
|
-
}
|
|
1015
|
-
if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot be started while it is stopping.`);
|
|
1016
|
-
if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot be restarted after it has been stopped.`);
|
|
1017
|
-
this.state = "starting";
|
|
1018
|
-
this.startPromise = this.startInternal();
|
|
1019
|
-
try {
|
|
1020
|
-
await this.startPromise;
|
|
1021
|
-
} finally {
|
|
1022
|
-
this.startPromise = void 0;
|
|
1023
|
-
}
|
|
911
|
+
startAll() {
|
|
912
|
+
for (const eventSource of this.runtimes.keys()) this.startEventSource(eventSource);
|
|
1024
913
|
}
|
|
1025
914
|
async stop() {
|
|
1026
|
-
if (this.state === "idle" && this.timers.size === 0 || this.state === "stopped") return;
|
|
1027
|
-
if (this.state === "starting") await this.startPromise;
|
|
1028
|
-
const stateAfterStart = this.getState();
|
|
1029
|
-
if (stateAfterStart === "idle" && this.timers.size === 0 || stateAfterStart === "stopped") return;
|
|
1030
|
-
if (stateAfterStart === "stopping") {
|
|
1031
|
-
await this.stopPromise;
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
this.state = "stopping";
|
|
1035
|
-
this.stopPromise = this.stopInternal();
|
|
1036
|
-
try {
|
|
1037
|
-
await this.stopPromise;
|
|
1038
|
-
} finally {
|
|
1039
|
-
this.state = "stopped";
|
|
1040
|
-
this.stopPromise = void 0;
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
async startInternal() {
|
|
1044
|
-
const appliedContextPlugins = [];
|
|
1045
|
-
const startingContexts = [];
|
|
1046
|
-
try {
|
|
1047
|
-
await this.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
|
|
1048
|
-
await this.recursiveStartPlugins(appliedContextPlugins);
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
for (const context of startingContexts) if (context.state === "starting") context.state = "idle";
|
|
1051
|
-
throw error;
|
|
1052
|
-
}
|
|
1053
|
-
for (const { context } of appliedContextPlugins) {
|
|
1054
|
-
context.state = "started";
|
|
1055
|
-
for (const eventSource of context.eventSourceRuntimes.keys()) context.startEventSource(eventSource);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
getState() {
|
|
1059
|
-
return this.state;
|
|
1060
|
-
}
|
|
1061
|
-
async stopInternal() {
|
|
1062
915
|
const errors = [];
|
|
1063
|
-
this.
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
errors.push(error);
|
|
1068
|
-
}
|
|
1069
|
-
await this.stopEventSources(errors);
|
|
1070
|
-
if (this.parent && this.parentEventForwarder) this.parent.eventBus.off("*", this.parentEventForwarder);
|
|
1071
|
-
for (const service of [...this.services.values()].reverse()) {
|
|
1072
|
-
if (!isDisposable(service)) continue;
|
|
916
|
+
for (const runtime of this.runtimes.values()) {
|
|
917
|
+
this.resolveReconnectDelay(runtime);
|
|
918
|
+
const subscription = runtime.subscription;
|
|
919
|
+
if (!subscription) continue;
|
|
1073
920
|
try {
|
|
1074
|
-
await
|
|
921
|
+
await subscription.stop();
|
|
1075
922
|
} catch (error) {
|
|
1076
923
|
errors.push(error);
|
|
1077
924
|
}
|
|
925
|
+
runtime.subscription = void 0;
|
|
1078
926
|
}
|
|
1079
|
-
this.
|
|
1080
|
-
if (errors.length === 1) throw errors[0];
|
|
1081
|
-
if (errors.length > 1) throw new AggregateError(errors, `Context "${this.name}" failed to stop cleanly.`);
|
|
1082
|
-
}
|
|
1083
|
-
assertCanRegisterApiHook() {
|
|
1084
|
-
if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot register API hooks while it is stopping.`);
|
|
1085
|
-
if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot register API hooks after it has stopped.`);
|
|
1086
|
-
}
|
|
1087
|
-
assertCanScheduleTimer() {
|
|
1088
|
-
if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot schedule timers while it is stopping.`);
|
|
1089
|
-
if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot schedule timers after it has stopped.`);
|
|
1090
|
-
}
|
|
1091
|
-
async runTimerCallback(callback) {
|
|
1092
|
-
if (this.state === "stopping" || this.state === "stopped") return;
|
|
1093
|
-
try {
|
|
1094
|
-
await callback();
|
|
1095
|
-
} catch (error) {
|
|
1096
|
-
this.logger.error("Error handling timer callback", error);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
clearTimers() {
|
|
1100
|
-
for (const timer of this.timers) clearTimeout(timer);
|
|
1101
|
-
this.timers.clear();
|
|
1102
|
-
}
|
|
1103
|
-
async stopEventSources(errors) {
|
|
1104
|
-
for (const [_, runtime] of this.eventSourceRuntimes) {
|
|
1105
|
-
this.resolveReconnectDelay(runtime);
|
|
1106
|
-
const subscription = runtime.subscription;
|
|
1107
|
-
if (!subscription) continue;
|
|
1108
|
-
try {
|
|
1109
|
-
await subscription.stop();
|
|
1110
|
-
} catch (error) {
|
|
1111
|
-
errors.push(error);
|
|
1112
|
-
}
|
|
1113
|
-
runtime.subscription = void 0;
|
|
1114
|
-
}
|
|
1115
|
-
for (const runtime of this.eventSourceRuntimes.values()) {
|
|
927
|
+
for (const runtime of this.runtimes.values()) {
|
|
1116
928
|
if (!runtime.task) continue;
|
|
1117
929
|
try {
|
|
1118
930
|
await runtime.task;
|
|
@@ -1122,13 +934,259 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1122
934
|
runtime.task = void 0;
|
|
1123
935
|
}
|
|
1124
936
|
}
|
|
937
|
+
return errors;
|
|
938
|
+
}
|
|
939
|
+
startEventSource(eventSource) {
|
|
940
|
+
const runtime = this.runtimes.get(eventSource);
|
|
941
|
+
if (!runtime) return;
|
|
942
|
+
runtime.task = this.runEventSource(eventSource, runtime);
|
|
943
|
+
}
|
|
944
|
+
async runEventSource(eventSource, runtime) {
|
|
945
|
+
let reconnectDelay = this.initialReconnectDelayMs;
|
|
946
|
+
let reconnectAttempt = 1;
|
|
947
|
+
while (this.getState() === "started") {
|
|
948
|
+
try {
|
|
949
|
+
this.logger.debug(`Connecting ${eventSource.name ?? "event source"} (attempt=${reconnectAttempt})`);
|
|
950
|
+
const subscription = await eventSource.start((event) => {
|
|
951
|
+
try {
|
|
952
|
+
if (this.getState() !== "started") return;
|
|
953
|
+
this.emit(event);
|
|
954
|
+
} catch (error) {
|
|
955
|
+
this.logger.error("Error handling event stream event", error);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
if (this.getState() !== "started") {
|
|
959
|
+
await subscription.stop();
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
runtime.subscription = subscription;
|
|
963
|
+
this.logger.info(`${eventSource.name ?? "Event source"} connected`);
|
|
964
|
+
reconnectDelay = this.initialReconnectDelayMs;
|
|
965
|
+
reconnectAttempt = 1;
|
|
966
|
+
await subscription.closed;
|
|
967
|
+
if (runtime.subscription === subscription) runtime.subscription = void 0;
|
|
968
|
+
if (this.getState() !== "started") break;
|
|
969
|
+
this.logger.warn(`${eventSource.name ?? "Event source"} disconnected; reconnecting in ${reconnectDelay}ms`);
|
|
970
|
+
} catch (error) {
|
|
971
|
+
if (this.getState() !== "started") break;
|
|
972
|
+
this.logger.error(`Error connecting ${eventSource.name ?? "event source"}; reconnecting in ${reconnectDelay}ms`, error);
|
|
973
|
+
}
|
|
974
|
+
await this.waitForReconnectDelay(runtime, reconnectDelay);
|
|
975
|
+
reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
|
|
976
|
+
reconnectAttempt += 1;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
waitForReconnectDelay(runtime, delay) {
|
|
980
|
+
return new Promise((resolve) => {
|
|
981
|
+
runtime.resolveReconnectTimer = resolve;
|
|
982
|
+
runtime.reconnectTimer = setTimeout(() => {
|
|
983
|
+
runtime.reconnectTimer = void 0;
|
|
984
|
+
runtime.resolveReconnectTimer = void 0;
|
|
985
|
+
resolve();
|
|
986
|
+
}, delay);
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
resolveReconnectDelay(runtime) {
|
|
990
|
+
if (runtime.reconnectTimer) {
|
|
991
|
+
clearTimeout(runtime.reconnectTimer);
|
|
992
|
+
runtime.reconnectTimer = void 0;
|
|
993
|
+
}
|
|
994
|
+
const resolve = runtime.resolveReconnectTimer;
|
|
995
|
+
runtime.resolveReconnectTimer = void 0;
|
|
996
|
+
resolve?.();
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
//#endregion
|
|
1000
|
+
//#region src/core/context/lifecycle.ts
|
|
1001
|
+
var LifecycleManager = class {
|
|
1002
|
+
contextName;
|
|
1003
|
+
plugins;
|
|
1004
|
+
timers;
|
|
1005
|
+
eventSources;
|
|
1006
|
+
apiHooks;
|
|
1007
|
+
detachParentEvents;
|
|
1008
|
+
children = /* @__PURE__ */ new Set();
|
|
1009
|
+
currentState = "idle";
|
|
1010
|
+
startPromise;
|
|
1011
|
+
stopPromise;
|
|
1012
|
+
constructor(contextName, plugins, timers, eventSources, apiHooks, detachParentEvents) {
|
|
1013
|
+
this.contextName = contextName;
|
|
1014
|
+
this.plugins = plugins;
|
|
1015
|
+
this.timers = timers;
|
|
1016
|
+
this.eventSources = eventSources;
|
|
1017
|
+
this.apiHooks = apiHooks;
|
|
1018
|
+
this.detachParentEvents = detachParentEvents;
|
|
1019
|
+
}
|
|
1020
|
+
get state() {
|
|
1021
|
+
return this.currentState;
|
|
1022
|
+
}
|
|
1023
|
+
addChild(child) {
|
|
1024
|
+
this.children.add(child);
|
|
1025
|
+
}
|
|
1026
|
+
async start() {
|
|
1027
|
+
if (this.currentState === "started") return;
|
|
1028
|
+
if (this.currentState === "starting") {
|
|
1029
|
+
await this.startPromise;
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (this.currentState === "stopping") throw new Error(`Context "${this.contextName}" cannot be started while it is stopping.`);
|
|
1033
|
+
if (this.currentState === "stopped") throw new Error(`Context "${this.contextName}" cannot be restarted after it has been stopped.`);
|
|
1034
|
+
this.currentState = "starting";
|
|
1035
|
+
this.startPromise = this.startInternal();
|
|
1036
|
+
try {
|
|
1037
|
+
await this.startPromise;
|
|
1038
|
+
} finally {
|
|
1039
|
+
this.startPromise = void 0;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
async stop() {
|
|
1043
|
+
if (this.currentState === "idle" && !this.timers.hasTimers || this.currentState === "stopped") return;
|
|
1044
|
+
let stateAfterStart = this.currentState;
|
|
1045
|
+
if (stateAfterStart === "starting") {
|
|
1046
|
+
await this.startPromise;
|
|
1047
|
+
stateAfterStart = this.currentState;
|
|
1048
|
+
}
|
|
1049
|
+
if (stateAfterStart === "idle" && !this.timers.hasTimers || stateAfterStart === "stopped") return;
|
|
1050
|
+
if (stateAfterStart === "stopping") {
|
|
1051
|
+
await this.stopPromise;
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
this.currentState = "stopping";
|
|
1055
|
+
this.stopPromise = this.stopInternal();
|
|
1056
|
+
try {
|
|
1057
|
+
await this.stopPromise;
|
|
1058
|
+
} finally {
|
|
1059
|
+
this.currentState = "stopped";
|
|
1060
|
+
this.stopPromise = void 0;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
async startInternal() {
|
|
1064
|
+
const appliedContextPlugins = [];
|
|
1065
|
+
const startingContexts = [];
|
|
1066
|
+
try {
|
|
1067
|
+
await this.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
|
|
1068
|
+
for (const { lifecycle, sortedPlugins } of appliedContextPlugins) await lifecycle.plugins.start(sortedPlugins);
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
for (const lifecycle of startingContexts) if (lifecycle.currentState === "starting") lifecycle.currentState = "idle";
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
for (const { lifecycle } of appliedContextPlugins) {
|
|
1074
|
+
lifecycle.currentState = "started";
|
|
1075
|
+
lifecycle.eventSources.startAll();
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
async stopInternal() {
|
|
1079
|
+
const errors = [];
|
|
1080
|
+
this.timers.clear();
|
|
1081
|
+
for (const child of [...this.children].reverse()) try {
|
|
1082
|
+
await child.stop();
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
errors.push(error);
|
|
1085
|
+
}
|
|
1086
|
+
errors.push(...await this.eventSources.stop());
|
|
1087
|
+
this.detachParentEvents();
|
|
1088
|
+
errors.push(...await this.plugins.disposeServices());
|
|
1089
|
+
this.apiHooks.clear();
|
|
1090
|
+
if (errors.length === 1) throw errors[0];
|
|
1091
|
+
if (errors.length > 1) throw new AggregateError(errors, `Context "${this.contextName}" failed to stop cleanly.`);
|
|
1092
|
+
}
|
|
1093
|
+
async recursiveApplyPlugins(appliedContextPlugins, startingContexts) {
|
|
1094
|
+
if (this.currentState === "started") return;
|
|
1095
|
+
if (this.currentState === "starting" && this.startPromise) {
|
|
1096
|
+
await this.startPromise;
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
if (this.currentState === "stopping") throw new Error(`Context "${this.contextName}" cannot be started while it is stopping.`);
|
|
1100
|
+
if (this.currentState === "stopped") throw new Error(`Context "${this.contextName}" cannot be restarted after it has been stopped.`);
|
|
1101
|
+
if (this.currentState === "idle") this.currentState = "starting";
|
|
1102
|
+
startingContexts.push(this);
|
|
1103
|
+
const sortedPlugins = await this.plugins.apply();
|
|
1104
|
+
appliedContextPlugins.push({
|
|
1105
|
+
lifecycle: this,
|
|
1106
|
+
sortedPlugins
|
|
1107
|
+
});
|
|
1108
|
+
for (const child of this.children) await child.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
//#endregion
|
|
1112
|
+
//#region src/core/service.ts
|
|
1113
|
+
function isDisposable(service) {
|
|
1114
|
+
return "dispose" in service && typeof service.dispose === "function";
|
|
1115
|
+
}
|
|
1116
|
+
function implementsESNextDisposable(service) {
|
|
1117
|
+
return Symbol.dispose in service && typeof service[Symbol.dispose] === "function";
|
|
1118
|
+
}
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region src/core/context/plugins.ts
|
|
1121
|
+
function areRequiredServicesAvailable(plugin, available) {
|
|
1122
|
+
return (plugin.requires ?? []).every((service) => available.has(service));
|
|
1123
|
+
}
|
|
1124
|
+
function areOptionalServicesReady(plugin, available, pendingProviders) {
|
|
1125
|
+
return (plugin.optionalRequires ?? []).every((service) => {
|
|
1126
|
+
if (available.has(service)) return true;
|
|
1127
|
+
const pendingProvider = pendingProviders.get(service);
|
|
1128
|
+
return pendingProvider === void 0 || pendingProvider.plugin === plugin;
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
function createUnresolvablePluginError(pending, available) {
|
|
1132
|
+
const missingRequirements = /* @__PURE__ */ new Map();
|
|
1133
|
+
const pendingProviders = /* @__PURE__ */ new Set();
|
|
1134
|
+
for (const { plugin } of pending) for (const service of plugin.provides ?? []) pendingProviders.add(service);
|
|
1135
|
+
for (const { plugin } of pending) for (const service of plugin.requires ?? []) if (!available.has(service)) {
|
|
1136
|
+
const plugins = missingRequirements.get(service) ?? [];
|
|
1137
|
+
plugins.push(plugin);
|
|
1138
|
+
missingRequirements.set(service, plugins);
|
|
1139
|
+
}
|
|
1140
|
+
const lines = [...missingRequirements].map(([service, plugins]) => {
|
|
1141
|
+
const dependents = plugins.map((plugin) => plugin.name).join(", ");
|
|
1142
|
+
const reason = pendingProviders.has(service) ? "blocked by a dependency cycle" : "no installed plugin provides it";
|
|
1143
|
+
return `${service.name} required by ${dependents} (${reason})`;
|
|
1144
|
+
});
|
|
1145
|
+
return /* @__PURE__ */ new Error(`Unable to resolve plugin service dependencies: ${lines.join("; ")}.`);
|
|
1146
|
+
}
|
|
1147
|
+
var PluginRegistry = class {
|
|
1148
|
+
context;
|
|
1149
|
+
parent;
|
|
1150
|
+
logHandler;
|
|
1151
|
+
plugins = [];
|
|
1152
|
+
services = /* @__PURE__ */ new Map();
|
|
1153
|
+
constructor(context, parent, logHandler) {
|
|
1154
|
+
this.context = context;
|
|
1155
|
+
this.parent = parent;
|
|
1156
|
+
this.logHandler = logHandler;
|
|
1157
|
+
}
|
|
1158
|
+
install(plugin, ...args) {
|
|
1159
|
+
this.plugins.push({
|
|
1160
|
+
plugin,
|
|
1161
|
+
args
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
provide(service, instance) {
|
|
1165
|
+
if (this.services.has(service)) throw new Error(`Service ${service.name} has already been provided in this context.`);
|
|
1166
|
+
if (implementsESNextDisposable(instance) && !isDisposable(instance)) throw new Error(`
|
|
1167
|
+
Service ${service.name} implements ESNext Disposable but not Fraq Disposable.
|
|
1168
|
+
Please explicitly import the interface like this:
|
|
1169
|
+
|
|
1170
|
+
import type { Disposable } from '@fraqjs/fraq';
|
|
1171
|
+
|
|
1172
|
+
and implement the dispose method to clean up resources when the context stops.
|
|
1173
|
+
`.trim());
|
|
1174
|
+
this.services.set(service, instance);
|
|
1125
1175
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
return
|
|
1176
|
+
resolve(service) {
|
|
1177
|
+
const instance = this.tryResolve(service);
|
|
1178
|
+
if (instance === void 0) throw new Error(`Service ${service.name} has not been provided.`);
|
|
1179
|
+
return instance;
|
|
1130
1180
|
}
|
|
1131
|
-
|
|
1181
|
+
tryResolve(service) {
|
|
1182
|
+
if (this.services.has(service)) return this.services.get(service);
|
|
1183
|
+
return this.parent?.tryResolve(service);
|
|
1184
|
+
}
|
|
1185
|
+
isProvided(service) {
|
|
1186
|
+
return this.tryResolve(service) !== void 0;
|
|
1187
|
+
}
|
|
1188
|
+
async apply() {
|
|
1189
|
+
const sortedPlugins = this.sortPlugins();
|
|
1132
1190
|
for (const installedPlugin of sortedPlugins) {
|
|
1133
1191
|
const { plugin, args } = installedPlugin;
|
|
1134
1192
|
const providedBeforeApply = new Set(this.services.keys());
|
|
@@ -1140,39 +1198,31 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1140
1198
|
if (plugin.provides) for (const service of plugin.provides) providedServices.push(service.name);
|
|
1141
1199
|
if (requiredServices.length > 0) applyingMessage += `, requires: [${requiredServices.join(", ")}]`;
|
|
1142
1200
|
if (providedServices.length > 0) applyingMessage += `, provides: [${providedServices.join(", ")}]`;
|
|
1143
|
-
this.logger.info(applyingMessage);
|
|
1201
|
+
this.context.logger.info(applyingMessage);
|
|
1144
1202
|
await plugin.apply(this.getPluginContext(installedPlugin), ...args);
|
|
1145
1203
|
for (const service of plugin.provides ?? []) if (!this.services.has(service) || providedBeforeApply.has(service)) throw new Error(`${plugin.name} declares service ${service.name} but did not provide it.`);
|
|
1146
1204
|
}
|
|
1205
|
+
return sortedPlugins;
|
|
1147
1206
|
}
|
|
1148
|
-
async
|
|
1149
|
-
if (this.state === "started") return;
|
|
1150
|
-
if (this.state === "starting" && this.startPromise) {
|
|
1151
|
-
await this.startPromise;
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot be started while it is stopping.`);
|
|
1155
|
-
if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot be restarted after it has been stopped.`);
|
|
1156
|
-
if (this.state === "idle") this.state = "starting";
|
|
1157
|
-
startingContexts.push(this);
|
|
1158
|
-
const sortedPlugins = this.sortPlugins();
|
|
1159
|
-
await this.applyPlugins(sortedPlugins);
|
|
1160
|
-
appliedContextPlugins.push({
|
|
1161
|
-
context: this,
|
|
1162
|
-
sortedPlugins
|
|
1163
|
-
});
|
|
1164
|
-
for (const subContext of this.subContexts.values()) await subContext.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
|
|
1165
|
-
}
|
|
1166
|
-
async startPlugins(sortedPlugins) {
|
|
1207
|
+
async start(sortedPlugins) {
|
|
1167
1208
|
for (const installedPlugin of sortedPlugins) {
|
|
1168
1209
|
const { plugin } = installedPlugin;
|
|
1169
1210
|
if (!plugin.start) continue;
|
|
1170
|
-
this.logger.debug(`Plugin ${plugin.name} is starting...`);
|
|
1211
|
+
this.context.logger.debug(`Plugin ${plugin.name} is starting...`);
|
|
1171
1212
|
await plugin.start(this.getPluginContext(installedPlugin));
|
|
1172
1213
|
}
|
|
1173
1214
|
}
|
|
1174
|
-
async
|
|
1175
|
-
|
|
1215
|
+
async disposeServices() {
|
|
1216
|
+
const errors = [];
|
|
1217
|
+
for (const service of [...this.services.values()].reverse()) {
|
|
1218
|
+
if (!isDisposable(service)) continue;
|
|
1219
|
+
try {
|
|
1220
|
+
await service.dispose();
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
errors.push(error);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return errors;
|
|
1176
1226
|
}
|
|
1177
1227
|
sortPlugins() {
|
|
1178
1228
|
const providers = /* @__PURE__ */ new Map();
|
|
@@ -1183,64 +1233,33 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1183
1233
|
}
|
|
1184
1234
|
const pending = [...this.plugins];
|
|
1185
1235
|
const sorted = [];
|
|
1186
|
-
const available =
|
|
1187
|
-
for (const service of this.collectAvailableServices()) available.add(service);
|
|
1236
|
+
const available = new Set(this.collectAvailableServices());
|
|
1188
1237
|
while (pending.length > 0) {
|
|
1189
|
-
const availableByPendingPlugins =
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1238
|
+
const availableByPendingPlugins = /* @__PURE__ */ new Map();
|
|
1239
|
+
for (const installedPlugin of pending) for (const service of installedPlugin.plugin.provides ?? []) availableByPendingPlugins.set(service, installedPlugin);
|
|
1240
|
+
const requiredReadyIndex = pending.findIndex(({ plugin }) => areRequiredServicesAvailable(plugin, available));
|
|
1241
|
+
const optionalReadyIndex = pending.findIndex(({ plugin }) => areRequiredServicesAvailable(plugin, available) && areOptionalServicesReady(plugin, available, availableByPendingPlugins));
|
|
1192
1242
|
const nextIndex = optionalReadyIndex === -1 ? requiredReadyIndex : optionalReadyIndex;
|
|
1193
|
-
if (nextIndex === -1) throw
|
|
1243
|
+
if (nextIndex === -1) throw createUnresolvablePluginError(pending, available);
|
|
1194
1244
|
const [next] = pending.splice(nextIndex, 1);
|
|
1195
1245
|
sorted.push(next);
|
|
1196
1246
|
for (const service of next.plugin.provides ?? []) available.add(service);
|
|
1197
1247
|
}
|
|
1198
1248
|
return sorted;
|
|
1199
1249
|
}
|
|
1200
|
-
collectPendingProvidedServices(pending) {
|
|
1201
|
-
const providedServices = /* @__PURE__ */ new Map();
|
|
1202
|
-
for (const installedPlugin of pending) for (const service of installedPlugin.plugin.provides ?? []) providedServices.set(service, installedPlugin);
|
|
1203
|
-
return providedServices;
|
|
1204
|
-
}
|
|
1205
|
-
areRequiredServicesAvailable(plugin, available) {
|
|
1206
|
-
return (plugin.requires ?? []).every((service) => available.has(service));
|
|
1207
|
-
}
|
|
1208
|
-
areOptionalServicesReady(plugin, available, pendingProviders) {
|
|
1209
|
-
return (plugin.optionalRequires ?? []).every((service) => {
|
|
1210
|
-
if (available.has(service)) return true;
|
|
1211
|
-
const pendingProvider = pendingProviders.get(service);
|
|
1212
|
-
return pendingProvider === void 0 || pendingProvider.plugin === plugin;
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
1250
|
collectAvailableServices() {
|
|
1216
1251
|
const services = [...this.services.keys()];
|
|
1217
1252
|
if (this.parent) services.push(...this.parent.collectAvailableServices());
|
|
1218
1253
|
return services;
|
|
1219
1254
|
}
|
|
1220
|
-
createUnresolvablePluginError(pending, available) {
|
|
1221
|
-
const missingRequirements = /* @__PURE__ */ new Map();
|
|
1222
|
-
const pendingProviders = /* @__PURE__ */ new Set();
|
|
1223
|
-
for (const { plugin } of pending) for (const service of plugin.provides ?? []) pendingProviders.add(service);
|
|
1224
|
-
for (const { plugin } of pending) for (const service of plugin.requires ?? []) if (!available.has(service)) {
|
|
1225
|
-
const plugins = missingRequirements.get(service) ?? [];
|
|
1226
|
-
plugins.push(plugin);
|
|
1227
|
-
missingRequirements.set(service, plugins);
|
|
1228
|
-
}
|
|
1229
|
-
const lines = [...missingRequirements].map(([service, plugins]) => {
|
|
1230
|
-
const dependents = plugins.map((plugin) => plugin.name).join(", ");
|
|
1231
|
-
const reason = pendingProviders.has(service) ? "blocked by a dependency cycle" : "no installed plugin provides it";
|
|
1232
|
-
return `${service.name} required by ${dependents} (${reason})`;
|
|
1233
|
-
});
|
|
1234
|
-
return /* @__PURE__ */ new Error(`Unable to resolve plugin service dependencies: ${lines.join("; ")}.`);
|
|
1235
|
-
}
|
|
1236
1255
|
getPluginContext(installedPlugin) {
|
|
1237
1256
|
installedPlugin.proxy ??= this.createProxyContextForPlugin(installedPlugin.plugin);
|
|
1238
1257
|
return installedPlugin.proxy;
|
|
1239
1258
|
}
|
|
1240
1259
|
createProxyContextForPlugin(plugin) {
|
|
1241
|
-
const proxyLogger = new Logger((message) => this.logHandler?.(message), `plugin:${this.name ? `${this.name}/` : ""}${plugin.name}`);
|
|
1242
|
-
const proxyRouter = this.router.withMeta({
|
|
1243
|
-
context: this.name,
|
|
1260
|
+
const proxyLogger = new Logger((message) => this.logHandler?.(message), `plugin:${this.context.name ? `${this.context.name}/` : ""}${plugin.name}`);
|
|
1261
|
+
const proxyRouter = this.context.router.withMeta({
|
|
1262
|
+
context: this.context.name,
|
|
1244
1263
|
plugin: plugin.name
|
|
1245
1264
|
});
|
|
1246
1265
|
let proxyInjections;
|
|
@@ -1252,115 +1271,221 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1252
1271
|
proxyInjections ??= {};
|
|
1253
1272
|
for (const [key, service] of Object.entries(plugin.optionalInject)) proxyInjections[key] = this.tryResolve(service);
|
|
1254
1273
|
}
|
|
1255
|
-
return new Proxy(this, { get(target, prop, receiver) {
|
|
1274
|
+
return new Proxy(this.context, { get(target, prop, receiver) {
|
|
1256
1275
|
if (prop === "logger") return proxyLogger;
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1276
|
+
if (prop === "router") return proxyRouter;
|
|
1277
|
+
if (proxyInjections && prop in proxyInjections) return proxyInjections[prop];
|
|
1278
|
+
return Reflect.get(target, prop, receiver);
|
|
1260
1279
|
} });
|
|
1261
1280
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1281
|
+
};
|
|
1282
|
+
//#endregion
|
|
1283
|
+
//#region src/core/context/timers.ts
|
|
1284
|
+
var TimerRegistry = class {
|
|
1285
|
+
contextName;
|
|
1286
|
+
logger;
|
|
1287
|
+
getState;
|
|
1288
|
+
timers = /* @__PURE__ */ new Set();
|
|
1289
|
+
constructor(contextName, logger, getState) {
|
|
1290
|
+
this.contextName = contextName;
|
|
1291
|
+
this.logger = logger;
|
|
1292
|
+
this.getState = getState;
|
|
1266
1293
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1294
|
+
get hasTimers() {
|
|
1295
|
+
return this.timers.size > 0;
|
|
1296
|
+
}
|
|
1297
|
+
timeout(delayMs, callback) {
|
|
1298
|
+
this.assertCanScheduleTimer();
|
|
1299
|
+
const timeout = setTimeout(() => {
|
|
1300
|
+
this.timers.delete(timeout);
|
|
1301
|
+
this.runTimerCallback(callback);
|
|
1302
|
+
}, delayMs);
|
|
1303
|
+
this.timers.add(timeout);
|
|
1304
|
+
return timeout;
|
|
1305
|
+
}
|
|
1306
|
+
interval(intervalMs, callback) {
|
|
1307
|
+
this.assertCanScheduleTimer();
|
|
1308
|
+
const interval = setInterval(() => {
|
|
1309
|
+
this.runTimerCallback(callback);
|
|
1310
|
+
}, intervalMs);
|
|
1311
|
+
this.timers.add(interval);
|
|
1312
|
+
return interval;
|
|
1313
|
+
}
|
|
1314
|
+
clear() {
|
|
1315
|
+
for (const timer of this.timers) clearTimeout(timer);
|
|
1316
|
+
this.timers.clear();
|
|
1317
|
+
}
|
|
1318
|
+
assertCanScheduleTimer() {
|
|
1319
|
+
const state = this.getState();
|
|
1320
|
+
if (state === "stopping") throw new Error(`Context "${this.contextName}" cannot schedule timers while it is stopping.`);
|
|
1321
|
+
if (state === "stopped") throw new Error(`Context "${this.contextName}" cannot schedule timers after it has stopped.`);
|
|
1322
|
+
}
|
|
1323
|
+
async runTimerCallback(callback) {
|
|
1324
|
+
const state = this.getState();
|
|
1325
|
+
if (state === "stopping" || state === "stopped") return;
|
|
1326
|
+
try {
|
|
1327
|
+
await callback();
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
this.logger.error("Error handling timer callback", error);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
//#endregion
|
|
1334
|
+
//#region src/core/context/index.ts
|
|
1335
|
+
var Context = class Context {
|
|
1336
|
+
router = new Router();
|
|
1337
|
+
logger;
|
|
1338
|
+
name;
|
|
1339
|
+
routeActivationResolver;
|
|
1340
|
+
client;
|
|
1341
|
+
baseClient;
|
|
1342
|
+
parent;
|
|
1343
|
+
filter;
|
|
1344
|
+
eventBus = mitt();
|
|
1345
|
+
subContexts = /* @__PURE__ */ new Map();
|
|
1346
|
+
parentEventForwarder;
|
|
1347
|
+
logHandler;
|
|
1348
|
+
plugins;
|
|
1349
|
+
apiHooks;
|
|
1350
|
+
eventSources;
|
|
1351
|
+
timers;
|
|
1352
|
+
lifecycle;
|
|
1353
|
+
constructor(baseClient, options, name, parent, filter) {
|
|
1354
|
+
this.baseClient = baseClient;
|
|
1355
|
+
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
1356
|
+
this.name = name ?? "root";
|
|
1357
|
+
this.logger = new Logger((message) => this.logHandler?.(message), `context:${this.name}`);
|
|
1358
|
+
this.routeActivationResolver = createContextRouteActivationResolver(options?.routing, parent?.routeActivationResolver);
|
|
1359
|
+
this.router.setActivationResolver(this.routeActivationResolver);
|
|
1360
|
+
this.parent = parent;
|
|
1361
|
+
this.filter = filter;
|
|
1362
|
+
if (parent) {
|
|
1363
|
+
this.parentEventForwarder = (type, event) => {
|
|
1364
|
+
if (this.filter) {
|
|
1365
|
+
const predicate = this.filter[type];
|
|
1366
|
+
if (predicate?.(event) !== true) return;
|
|
1284
1367
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1368
|
+
this.eventBus.emit(type, event);
|
|
1369
|
+
};
|
|
1370
|
+
parent.eventBus.on("*", this.parentEventForwarder);
|
|
1371
|
+
}
|
|
1372
|
+
const getState = () => this.lifecycle.state;
|
|
1373
|
+
this.plugins = new PluginRegistry(this, parent?.plugins, this.logHandler);
|
|
1374
|
+
this.apiHooks = new ApiHookRegistry(baseClient, parent?.apiHooks, this.name, getState);
|
|
1375
|
+
this.client = this.apiHooks.client;
|
|
1376
|
+
this.timers = new TimerRegistry(this.name, this.logger, getState);
|
|
1377
|
+
this.eventSources = new EventSourceRegistry(options?.reconnect, this.logger, getState, (event) => {
|
|
1378
|
+
this.eventBus.emit(event.event_type, event);
|
|
1379
|
+
});
|
|
1380
|
+
this.lifecycle = new LifecycleManager(this.name, this.plugins, this.timers, this.eventSources, this.apiHooks, () => {
|
|
1381
|
+
if (this.parent && this.parentEventForwarder) this.parent.eventBus.off("*", this.parentEventForwarder);
|
|
1382
|
+
});
|
|
1383
|
+
parent?.lifecycle.addChild(this.lifecycle);
|
|
1384
|
+
this.eventBus.on("message_receive", async ({ self_id, data: message }) => {
|
|
1385
|
+
try {
|
|
1386
|
+
if (this.lifecycle.state === "stopping" || this.lifecycle.state === "stopped") return;
|
|
1387
|
+
await this.router.dispatch(this.createSession(self_id, message), message);
|
|
1293
1388
|
} catch (error) {
|
|
1294
|
-
|
|
1295
|
-
this.logger.error(`Error connecting ${eventSource.name ?? "event source"}; reconnecting in ${reconnectDelay}ms`, error);
|
|
1389
|
+
this.logger.error(`Error routing command (scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
|
|
1296
1390
|
}
|
|
1297
|
-
await this.waitForReconnectDelay(runtime, reconnectDelay);
|
|
1298
|
-
reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
|
|
1299
|
-
reconnectAttempt += 1;
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
waitForReconnectDelay(runtime, delay) {
|
|
1303
|
-
return new Promise((resolve) => {
|
|
1304
|
-
runtime.resolveReconnectTimer = resolve;
|
|
1305
|
-
runtime.reconnectTimer = setTimeout(() => {
|
|
1306
|
-
runtime.reconnectTimer = void 0;
|
|
1307
|
-
runtime.resolveReconnectTimer = void 0;
|
|
1308
|
-
resolve();
|
|
1309
|
-
}, delay);
|
|
1310
1391
|
});
|
|
1311
1392
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1393
|
+
on(type, handler) {
|
|
1394
|
+
const wrappedHandler = async (event) => {
|
|
1395
|
+
try {
|
|
1396
|
+
if (this.lifecycle.state === "stopping" || this.lifecycle.state === "stopped") return;
|
|
1397
|
+
await handler(event);
|
|
1398
|
+
} catch (error) {
|
|
1399
|
+
this.logger.error(`Error handling event ${type}`, error);
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
this.eventBus.on(type, wrappedHandler);
|
|
1403
|
+
return () => {
|
|
1404
|
+
this.eventBus.off(type, wrappedHandler);
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
install(plugin, ...args) {
|
|
1408
|
+
this.plugins.install(plugin, ...args);
|
|
1409
|
+
}
|
|
1410
|
+
installEventSource(eventSource) {
|
|
1411
|
+
this.eventSources.install(eventSource);
|
|
1412
|
+
}
|
|
1413
|
+
hookApi(endpointOrHook, hook) {
|
|
1414
|
+
return this.apiHooks.register(endpointOrHook, hook);
|
|
1415
|
+
}
|
|
1416
|
+
provide(service, instance) {
|
|
1417
|
+
this.plugins.provide(service, instance);
|
|
1418
|
+
}
|
|
1419
|
+
resolve(service) {
|
|
1420
|
+
return this.plugins.resolve(service);
|
|
1421
|
+
}
|
|
1422
|
+
tryResolve(service) {
|
|
1423
|
+
return this.plugins.tryResolve(service);
|
|
1424
|
+
}
|
|
1425
|
+
isProvided(service) {
|
|
1426
|
+
return this.plugins.isProvided(service);
|
|
1427
|
+
}
|
|
1428
|
+
fork(name, filter) {
|
|
1429
|
+
if (this.subContexts.has(name)) {
|
|
1430
|
+
if (filter) throw new Error(`Sub context "${name}" already exists, so the provided filter cannot be applied. Please use fork('${name}') without a filter to get the existing subcontext.`);
|
|
1431
|
+
return this.subContexts.get(name);
|
|
1316
1432
|
}
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
|
|
1433
|
+
const subContext = new Context(this.baseClient, void 0, name, this, filter);
|
|
1434
|
+
this.subContexts.set(name, subContext);
|
|
1435
|
+
return subContext;
|
|
1320
1436
|
}
|
|
1321
|
-
|
|
1322
|
-
return
|
|
1323
|
-
if (typeof prop === "string" && prop.includes("_")) return (params) => this.callHookedApi(prop, params);
|
|
1324
|
-
return Reflect.get(target, prop, receiver);
|
|
1325
|
-
} });
|
|
1437
|
+
timeout(delayMs, callback) {
|
|
1438
|
+
return this.timers.timeout(delayMs, callback);
|
|
1326
1439
|
}
|
|
1327
|
-
|
|
1328
|
-
return (
|
|
1329
|
-
return hook(call, (nextParams = params) => next(nextParams));
|
|
1330
|
-
};
|
|
1440
|
+
interval(intervalMs, callback) {
|
|
1441
|
+
return this.timers.interval(intervalMs, callback);
|
|
1331
1442
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1443
|
+
createSession(selfId, message) {
|
|
1444
|
+
return {
|
|
1445
|
+
selfId,
|
|
1446
|
+
raw: message,
|
|
1447
|
+
reply: async (textOrSegments, options) => {
|
|
1448
|
+
const actualSegments = [];
|
|
1449
|
+
if (typeof textOrSegments === "string") actualSegments.push({
|
|
1450
|
+
type: "text",
|
|
1451
|
+
data: { text: textOrSegments }
|
|
1452
|
+
});
|
|
1453
|
+
else actualSegments.push(...textOrSegments);
|
|
1454
|
+
if (options?.withMention && message.message_scene === "group") actualSegments.unshift(seg.mention(message.sender_id));
|
|
1455
|
+
if (options?.withQuote) actualSegments.unshift(seg.reply(message.message_seq));
|
|
1456
|
+
switch (message.message_scene) {
|
|
1457
|
+
case "friend": {
|
|
1458
|
+
const { message_seq } = await this.client.send_private_message({
|
|
1459
|
+
user_id: message.peer_id,
|
|
1460
|
+
message: actualSegments
|
|
1461
|
+
});
|
|
1462
|
+
return { messageSeq: message_seq };
|
|
1463
|
+
}
|
|
1464
|
+
case "group": {
|
|
1465
|
+
const { message_seq } = await this.client.send_group_message({
|
|
1466
|
+
group_id: message.peer_id,
|
|
1467
|
+
message: actualSegments
|
|
1468
|
+
});
|
|
1469
|
+
return { messageSeq: message_seq };
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return { messageSeq: 0 };
|
|
1473
|
+
},
|
|
1474
|
+
reaction: async (type, reactionId) => {
|
|
1475
|
+
if (message.message_scene === "group") await this.client.send_group_message_reaction({
|
|
1476
|
+
group_id: message.peer_id,
|
|
1477
|
+
message_seq: message.message_seq,
|
|
1478
|
+
reaction_type: type,
|
|
1479
|
+
reaction: reactionId
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1346
1482
|
};
|
|
1347
|
-
return await dispatch(0, params);
|
|
1348
1483
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
let context = this;
|
|
1352
|
-
while (context) {
|
|
1353
|
-
for (const entry of context.apiHookEntries.toReversed()) if (entry.endpoint === void 0 || entry.endpoint === endpoint) hooks.push(entry.hook);
|
|
1354
|
-
context = context.parent;
|
|
1355
|
-
}
|
|
1356
|
-
return hooks;
|
|
1484
|
+
async start() {
|
|
1485
|
+
await this.lifecycle.start();
|
|
1357
1486
|
}
|
|
1358
|
-
async
|
|
1359
|
-
|
|
1360
|
-
if (typeof callApi === "function") return await callApi.call(this.baseClient, endpoint, params);
|
|
1361
|
-
const method = this.baseClient[endpoint];
|
|
1362
|
-
if (typeof method !== "function") throw new Error(`Milky client does not implement API endpoint ${endpoint}.`);
|
|
1363
|
-
return await method.call(this.baseClient, params);
|
|
1487
|
+
async stop() {
|
|
1488
|
+
await this.lifecycle.stop();
|
|
1364
1489
|
}
|
|
1365
1490
|
static fromUrl(baseUrl, options) {
|
|
1366
1491
|
const client = createMilkyClient(baseUrl, { accessToken: options?.accessToken });
|