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