@fraqjs/fraq 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +56 -10
- package/dist/index.mjs +92 -10
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -5547,6 +5547,7 @@ type RouteActivation =
|
|
|
5547
5547
|
type: 'prefix';
|
|
5548
5548
|
prefix: string;
|
|
5549
5549
|
};
|
|
5550
|
+
type RouteActivationInput = RouteActivation | readonly RouteActivation[];
|
|
5550
5551
|
type RouteDescriptor =
|
|
5551
5552
|
| {
|
|
5552
5553
|
type: 'command';
|
|
@@ -5560,7 +5561,7 @@ type RouteDescriptor =
|
|
|
5560
5561
|
path: readonly string[];
|
|
5561
5562
|
meta?: RouteMeta;
|
|
5562
5563
|
};
|
|
5563
|
-
type RouteActivationResolver = (route: RouteDescriptor, session: Session) =>
|
|
5564
|
+
type RouteActivationResolver = (route: RouteDescriptor, session: Session) => RouteActivationInput | undefined;
|
|
5564
5565
|
declare const defaultRouteActivationResolver: RouteActivationResolver;
|
|
5565
5566
|
type RouteEntry =
|
|
5566
5567
|
| {
|
|
@@ -5642,9 +5643,9 @@ declare class Router {
|
|
|
5642
5643
|
//#endregion
|
|
5643
5644
|
//#region src/core/activation.d.ts
|
|
5644
5645
|
type ContextRouteActivationByScene = Partial<
|
|
5645
|
-
Record<IncomingMessage['message_scene'] | 'default',
|
|
5646
|
+
Record<IncomingMessage['message_scene'] | 'default', RouteActivationInput>
|
|
5646
5647
|
>;
|
|
5647
|
-
type ContextRouteActivation =
|
|
5648
|
+
type ContextRouteActivation = RouteActivationInput | ContextRouteActivationByScene;
|
|
5648
5649
|
interface ContextRouteActivationMatcher {
|
|
5649
5650
|
type?: RouteDescriptor['type'] | readonly RouteDescriptor['type'][];
|
|
5650
5651
|
plugin?: string | readonly string[];
|
|
@@ -5673,18 +5674,42 @@ declare function createContextRouteActivationResolver(
|
|
|
5673
5674
|
): RouteActivationResolver;
|
|
5674
5675
|
//#endregion
|
|
5675
5676
|
//#region src/protocol/endpoint.d.ts
|
|
5677
|
+
type ApiEndpointName = keyof ApiEndpoints$1;
|
|
5676
5678
|
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
|
|
5677
5679
|
type AllOptional<T> = RequiredKeys<T> extends never ? true : false;
|
|
5678
|
-
type RawApiEndpoint<E extends
|
|
5680
|
+
type RawApiEndpoint<E extends ApiEndpointName> = {
|
|
5679
5681
|
request: ApiEndpoints$1[E]['request_ZodInput'] extends null ? null : ApiEndpoints$1[E]['request_ZodInput'];
|
|
5680
5682
|
response: ApiEndpoints$1[E]['response'] extends null ? null : ApiEndpoints$1[E]['response'];
|
|
5681
5683
|
};
|
|
5682
|
-
type
|
|
5683
|
-
|
|
5684
|
+
type ApiRequest<E extends ApiEndpointName> = RawApiEndpoint<E>['request'];
|
|
5685
|
+
type ApiParams<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
|
|
5686
|
+
? undefined
|
|
5684
5687
|
: AllOptional<RawApiEndpoint<E>['request']> extends true
|
|
5685
|
-
?
|
|
5686
|
-
:
|
|
5687
|
-
type
|
|
5688
|
+
? RawApiEndpoint<E>['request'] | undefined
|
|
5689
|
+
: RawApiEndpoint<E>['request'];
|
|
5690
|
+
type ApiResponse<E extends ApiEndpointName> = RawApiEndpoint<E>['response'];
|
|
5691
|
+
interface ApiCall<E extends ApiEndpointName = ApiEndpointName> {
|
|
5692
|
+
endpoint: E;
|
|
5693
|
+
params: ApiParams<E>;
|
|
5694
|
+
}
|
|
5695
|
+
type ApiNext<E extends ApiEndpointName> =
|
|
5696
|
+
undefined extends ApiParams<E>
|
|
5697
|
+
? (params?: ApiParams<E>) => Promise<ApiResponse<E>>
|
|
5698
|
+
: (params: ApiParams<E>) => Promise<ApiResponse<E>>;
|
|
5699
|
+
type ApiHook<E extends ApiEndpointName> = (
|
|
5700
|
+
params: ApiParams<E>,
|
|
5701
|
+
next: ApiNext<E>,
|
|
5702
|
+
call: ApiCall<E>,
|
|
5703
|
+
) => ApiResponse<E> | Promise<ApiResponse<E>>;
|
|
5704
|
+
type AnyApiCall = { [E in ApiEndpointName]: ApiCall<E> }[ApiEndpointName];
|
|
5705
|
+
type AnyApiNext = (params?: unknown) => Promise<unknown>;
|
|
5706
|
+
type AnyApiHook = (call: AnyApiCall, next: AnyApiNext) => unknown | Promise<unknown>;
|
|
5707
|
+
type ApiEndpointFunction<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
|
|
5708
|
+
? () => Promise<ApiResponse<E>>
|
|
5709
|
+
: AllOptional<RawApiEndpoint<E>['request']> extends true
|
|
5710
|
+
? (params?: RawApiEndpoint<E>['request']) => Promise<ApiResponse<E>>
|
|
5711
|
+
: (params: RawApiEndpoint<E>['request']) => Promise<ApiResponse<E>>;
|
|
5712
|
+
type ApiEndpoints = { [E in ApiEndpointName]: ApiEndpointFunction<E> };
|
|
5688
5713
|
type EventMap = {
|
|
5689
5714
|
[K in Event['event_type']]: Extract<
|
|
5690
5715
|
Event,
|
|
@@ -5817,16 +5842,18 @@ interface ContextUrlOptions {
|
|
|
5817
5842
|
installEventSource?: boolean;
|
|
5818
5843
|
}
|
|
5819
5844
|
declare class Context {
|
|
5820
|
-
readonly client: MilkyClient;
|
|
5821
5845
|
readonly router: Router;
|
|
5822
5846
|
readonly logger: Logger;
|
|
5823
5847
|
readonly name: string;
|
|
5824
5848
|
readonly routeActivationResolver: RouteActivationResolver;
|
|
5849
|
+
readonly client: MilkyClient;
|
|
5850
|
+
private readonly baseClient;
|
|
5825
5851
|
private readonly parent?;
|
|
5826
5852
|
private readonly filter?;
|
|
5827
5853
|
private readonly eventBus;
|
|
5828
5854
|
private readonly plugins;
|
|
5829
5855
|
private readonly services;
|
|
5856
|
+
private readonly apiHookEntries;
|
|
5830
5857
|
private readonly subContexts;
|
|
5831
5858
|
private readonly eventSourceRuntimes;
|
|
5832
5859
|
private readonly parentEventForwarder?;
|
|
@@ -5844,6 +5871,8 @@ declare class Context {
|
|
|
5844
5871
|
...args: T
|
|
5845
5872
|
): void;
|
|
5846
5873
|
installEventSource(eventSource: MilkyEventSource): void;
|
|
5874
|
+
hookApi<E extends ApiEndpointName>(endpoint: E, hook: ApiHook<E>): () => void;
|
|
5875
|
+
hookApi(hook: AnyApiHook): () => void;
|
|
5847
5876
|
provide<T extends object>(service: ServiceClass<T>, instance: T): void;
|
|
5848
5877
|
resolve<T extends object>(service: ServiceClass<T>): T;
|
|
5849
5878
|
tryResolve<T extends object>(service: ServiceClass<T>): T | undefined;
|
|
@@ -5857,6 +5886,7 @@ declare class Context {
|
|
|
5857
5886
|
private startInternal;
|
|
5858
5887
|
private getState;
|
|
5859
5888
|
private stopInternal;
|
|
5889
|
+
private assertCanRegisterApiHook;
|
|
5860
5890
|
private assertCanScheduleTimer;
|
|
5861
5891
|
private runTimerCallback;
|
|
5862
5892
|
private clearTimers;
|
|
@@ -5878,6 +5908,11 @@ declare class Context {
|
|
|
5878
5908
|
private runEventSource;
|
|
5879
5909
|
private waitForReconnectDelay;
|
|
5880
5910
|
private resolveReconnectDelay;
|
|
5911
|
+
private createHookClient;
|
|
5912
|
+
private wrapAnyApiHook;
|
|
5913
|
+
private callHookedApi;
|
|
5914
|
+
private collectApiHooks;
|
|
5915
|
+
private callBaseApi;
|
|
5881
5916
|
static fromUrl(baseUrl: string | URL, options?: ContextOptions & ContextUrlOptions): Context;
|
|
5882
5917
|
static fromClient(client: MilkyClient, options?: ContextOptions): Context;
|
|
5883
5918
|
}
|
|
@@ -5923,7 +5958,17 @@ declare namespace seg {
|
|
|
5923
5958
|
}
|
|
5924
5959
|
//#endregion
|
|
5925
5960
|
export {
|
|
5961
|
+
AnyApiCall,
|
|
5962
|
+
AnyApiHook,
|
|
5963
|
+
AnyApiNext,
|
|
5964
|
+
ApiCall,
|
|
5965
|
+
ApiEndpointName,
|
|
5926
5966
|
ApiEndpoints,
|
|
5967
|
+
ApiHook,
|
|
5968
|
+
ApiNext,
|
|
5969
|
+
ApiParams,
|
|
5970
|
+
ApiRequest,
|
|
5971
|
+
ApiResponse,
|
|
5927
5972
|
Capturer,
|
|
5928
5973
|
Command,
|
|
5929
5974
|
CommandBuilder,
|
|
@@ -5956,6 +6001,7 @@ export {
|
|
|
5956
6001
|
Plugin,
|
|
5957
6002
|
RawPattern,
|
|
5958
6003
|
RouteActivation,
|
|
6004
|
+
RouteActivationInput,
|
|
5959
6005
|
RouteActivationResolver,
|
|
5960
6006
|
RouteBranch,
|
|
5961
6007
|
RouteDescriptor,
|
package/dist/index.mjs
CHANGED
|
@@ -75,7 +75,7 @@ function uniqueTags(...tagLists) {
|
|
|
75
75
|
}
|
|
76
76
|
//#endregion
|
|
77
77
|
//#region src/routing/tokenizer.ts
|
|
78
|
-
const WHITESPACE_CHARS = new Set([
|
|
78
|
+
const WHITESPACE_CHARS = /* @__PURE__ */ new Set([
|
|
79
79
|
" ",
|
|
80
80
|
" ",
|
|
81
81
|
"\n",
|
|
@@ -316,7 +316,7 @@ var Router = class Router {
|
|
|
316
316
|
for (const branch of this.branchesFrom(session, [], { includeHidden: true })) {
|
|
317
317
|
const descriptor = this.describeBranch(branch);
|
|
318
318
|
const activations = this.activationResolver(descriptor, session) ?? DEFAULT_ACTIVATIONS;
|
|
319
|
-
for (const activation of activations) {
|
|
319
|
+
for (const activation of routeActivationInputs(activations)) {
|
|
320
320
|
const tokenizer = new Tokenizer(message.segments);
|
|
321
321
|
if (!this.consumeActivation(tokenizer, activation, session)) continue;
|
|
322
322
|
const match = this.matchBranch(branch, tokenizer, activation);
|
|
@@ -485,6 +485,12 @@ var Router = class Router {
|
|
|
485
485
|
return params;
|
|
486
486
|
}
|
|
487
487
|
};
|
|
488
|
+
function routeActivationInputs(activations) {
|
|
489
|
+
return isRouteActivationArray(activations) ? activations : [activations];
|
|
490
|
+
}
|
|
491
|
+
function isRouteActivationArray(activations) {
|
|
492
|
+
return Array.isArray(activations);
|
|
493
|
+
}
|
|
488
494
|
//#endregion
|
|
489
495
|
//#region src/core/activation.ts
|
|
490
496
|
function createContextRouteActivationResolver(routing, fallback = defaultRouteActivationResolver) {
|
|
@@ -516,11 +522,14 @@ function isContextRouteActivationRuleArray(rules) {
|
|
|
516
522
|
return Array.isArray(rules);
|
|
517
523
|
}
|
|
518
524
|
function resolveContextRouteActivation(activation, scene) {
|
|
519
|
-
if (
|
|
525
|
+
if (isRouteActivationInput(activation)) return activation;
|
|
520
526
|
return activation[scene] ?? activation.default;
|
|
521
527
|
}
|
|
522
|
-
function
|
|
523
|
-
return Array.isArray(activation);
|
|
528
|
+
function isRouteActivationInput(activation) {
|
|
529
|
+
return Array.isArray(activation) || isRouteActivation(activation);
|
|
530
|
+
}
|
|
531
|
+
function isRouteActivation(activation) {
|
|
532
|
+
return "type" in activation;
|
|
524
533
|
}
|
|
525
534
|
function matchesContextRouteActivationRule(route, session, rule) {
|
|
526
535
|
const match = rule.match;
|
|
@@ -811,16 +820,18 @@ function implementsESNextDisposable(service) {
|
|
|
811
820
|
const DEFAULT_INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
812
821
|
const DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
|
|
813
822
|
var Context = class Context {
|
|
814
|
-
client;
|
|
815
823
|
router = new Router();
|
|
816
824
|
logger;
|
|
817
825
|
name;
|
|
818
826
|
routeActivationResolver;
|
|
827
|
+
client;
|
|
828
|
+
baseClient;
|
|
819
829
|
parent;
|
|
820
830
|
filter;
|
|
821
831
|
eventBus = mitt();
|
|
822
832
|
plugins = [];
|
|
823
833
|
services = /* @__PURE__ */ new Map();
|
|
834
|
+
apiHookEntries = [];
|
|
824
835
|
subContexts = /* @__PURE__ */ new Map();
|
|
825
836
|
eventSourceRuntimes = /* @__PURE__ */ new Map();
|
|
826
837
|
parentEventForwarder;
|
|
@@ -831,8 +842,9 @@ var Context = class Context {
|
|
|
831
842
|
state = "idle";
|
|
832
843
|
startPromise;
|
|
833
844
|
stopPromise;
|
|
834
|
-
constructor(
|
|
835
|
-
this.
|
|
845
|
+
constructor(baseClient, options, name, parent, filter) {
|
|
846
|
+
this.baseClient = baseClient;
|
|
847
|
+
this.client = this.createHookClient();
|
|
836
848
|
this.initialReconnectDelayMs = options?.reconnect?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
|
|
837
849
|
this.maxReconnectDelayMs = options?.reconnect?.maxDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
|
|
838
850
|
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
@@ -883,6 +895,26 @@ var Context = class Context {
|
|
|
883
895
|
this.eventSourceRuntimes.set(eventSource, {});
|
|
884
896
|
if (this.state === "started") this.startEventSource(eventSource);
|
|
885
897
|
}
|
|
898
|
+
hookApi(endpointOrHook, hook) {
|
|
899
|
+
this.assertCanRegisterApiHook();
|
|
900
|
+
let entry;
|
|
901
|
+
if (typeof endpointOrHook === "function") entry = { hook: this.wrapAnyApiHook(endpointOrHook) };
|
|
902
|
+
else {
|
|
903
|
+
if (!hook) throw new Error(`API hook for endpoint ${endpointOrHook} is missing a handler.`);
|
|
904
|
+
entry = {
|
|
905
|
+
endpoint: endpointOrHook,
|
|
906
|
+
hook
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
this.apiHookEntries.push(entry);
|
|
910
|
+
let disposed = false;
|
|
911
|
+
return () => {
|
|
912
|
+
if (disposed) return;
|
|
913
|
+
disposed = true;
|
|
914
|
+
const index = this.apiHookEntries.indexOf(entry);
|
|
915
|
+
if (index !== -1) this.apiHookEntries.splice(index, 1);
|
|
916
|
+
};
|
|
917
|
+
}
|
|
886
918
|
provide(service, instance) {
|
|
887
919
|
if (this.services.has(service)) throw new Error(`Service ${service.name} has already been provided in this context.`);
|
|
888
920
|
if (implementsESNextDisposable(instance) && !isDisposable(instance)) throw new Error(`
|
|
@@ -912,7 +944,7 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
912
944
|
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.`);
|
|
913
945
|
return this.subContexts.get(name);
|
|
914
946
|
}
|
|
915
|
-
const subContext = new Context(this.
|
|
947
|
+
const subContext = new Context(this.baseClient, void 0, name, this, filter);
|
|
916
948
|
this.subContexts.set(name, subContext);
|
|
917
949
|
return subContext;
|
|
918
950
|
}
|
|
@@ -1044,9 +1076,14 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1044
1076
|
errors.push(error);
|
|
1045
1077
|
}
|
|
1046
1078
|
}
|
|
1079
|
+
this.apiHookEntries.length = 0;
|
|
1047
1080
|
if (errors.length === 1) throw errors[0];
|
|
1048
1081
|
if (errors.length > 1) throw new AggregateError(errors, `Context "${this.name}" failed to stop cleanly.`);
|
|
1049
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
|
+
}
|
|
1050
1087
|
assertCanScheduleTimer() {
|
|
1051
1088
|
if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot schedule timers while it is stopping.`);
|
|
1052
1089
|
if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot schedule timers after it has stopped.`);
|
|
@@ -1281,8 +1318,53 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1281
1318
|
runtime.resolveReconnectTimer = void 0;
|
|
1282
1319
|
resolve?.();
|
|
1283
1320
|
}
|
|
1321
|
+
createHookClient() {
|
|
1322
|
+
return new Proxy(this.baseClient, { get: (target, prop, receiver) => {
|
|
1323
|
+
if (typeof prop === "string" && prop.includes("_")) return (params) => this.callHookedApi(prop, params);
|
|
1324
|
+
return Reflect.get(target, prop, receiver);
|
|
1325
|
+
} });
|
|
1326
|
+
}
|
|
1327
|
+
wrapAnyApiHook(hook) {
|
|
1328
|
+
return (params, next, call) => {
|
|
1329
|
+
return hook(call, (nextParams = params) => next(nextParams));
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
async callHookedApi(endpoint, params) {
|
|
1333
|
+
const hooks = this.collectApiHooks(endpoint);
|
|
1334
|
+
const dispatch = async (index, currentParams) => {
|
|
1335
|
+
const hook = hooks[index];
|
|
1336
|
+
if (!hook) return await this.callBaseApi(endpoint, currentParams);
|
|
1337
|
+
let nextCalled = false;
|
|
1338
|
+
return await hook(currentParams, async (nextParams = currentParams) => {
|
|
1339
|
+
if (nextCalled) throw new Error(`API hook for endpoint ${endpoint} called next() multiple times.`);
|
|
1340
|
+
nextCalled = true;
|
|
1341
|
+
return await dispatch(index + 1, nextParams);
|
|
1342
|
+
}, {
|
|
1343
|
+
endpoint,
|
|
1344
|
+
params: currentParams
|
|
1345
|
+
});
|
|
1346
|
+
};
|
|
1347
|
+
return await dispatch(0, params);
|
|
1348
|
+
}
|
|
1349
|
+
collectApiHooks(endpoint) {
|
|
1350
|
+
const hooks = [];
|
|
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;
|
|
1357
|
+
}
|
|
1358
|
+
async callBaseApi(endpoint, params) {
|
|
1359
|
+
const callApi = this.baseClient.callApi;
|
|
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);
|
|
1364
|
+
}
|
|
1284
1365
|
static fromUrl(baseUrl, options) {
|
|
1285
|
-
const
|
|
1366
|
+
const client = createMilkyClient(baseUrl, { accessToken: options?.accessToken });
|
|
1367
|
+
const context = new Context(client, options);
|
|
1286
1368
|
if (options?.installEventSource ?? true) context.installEventSource(createMilkyWebSocketEventSource(baseUrl, { accessToken: options?.accessToken }));
|
|
1287
1369
|
return context;
|
|
1288
1370
|
}
|