@fraqjs/fraq 0.12.1 → 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 +51 -7
- package/dist/index.mjs +79 -6
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -5674,18 +5674,42 @@ declare function createContextRouteActivationResolver(
|
|
|
5674
5674
|
): RouteActivationResolver;
|
|
5675
5675
|
//#endregion
|
|
5676
5676
|
//#region src/protocol/endpoint.d.ts
|
|
5677
|
+
type ApiEndpointName = keyof ApiEndpoints$1;
|
|
5677
5678
|
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
|
|
5678
5679
|
type AllOptional<T> = RequiredKeys<T> extends never ? true : false;
|
|
5679
|
-
type RawApiEndpoint<E extends
|
|
5680
|
+
type RawApiEndpoint<E extends ApiEndpointName> = {
|
|
5680
5681
|
request: ApiEndpoints$1[E]['request_ZodInput'] extends null ? null : ApiEndpoints$1[E]['request_ZodInput'];
|
|
5681
5682
|
response: ApiEndpoints$1[E]['response'] extends null ? null : ApiEndpoints$1[E]['response'];
|
|
5682
5683
|
};
|
|
5683
|
-
type
|
|
5684
|
-
|
|
5684
|
+
type ApiRequest<E extends ApiEndpointName> = RawApiEndpoint<E>['request'];
|
|
5685
|
+
type ApiParams<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
|
|
5686
|
+
? undefined
|
|
5685
5687
|
: AllOptional<RawApiEndpoint<E>['request']> extends true
|
|
5686
|
-
?
|
|
5687
|
-
:
|
|
5688
|
-
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> };
|
|
5689
5713
|
type EventMap = {
|
|
5690
5714
|
[K in Event['event_type']]: Extract<
|
|
5691
5715
|
Event,
|
|
@@ -5818,16 +5842,18 @@ interface ContextUrlOptions {
|
|
|
5818
5842
|
installEventSource?: boolean;
|
|
5819
5843
|
}
|
|
5820
5844
|
declare class Context {
|
|
5821
|
-
readonly client: MilkyClient;
|
|
5822
5845
|
readonly router: Router;
|
|
5823
5846
|
readonly logger: Logger;
|
|
5824
5847
|
readonly name: string;
|
|
5825
5848
|
readonly routeActivationResolver: RouteActivationResolver;
|
|
5849
|
+
readonly client: MilkyClient;
|
|
5850
|
+
private readonly baseClient;
|
|
5826
5851
|
private readonly parent?;
|
|
5827
5852
|
private readonly filter?;
|
|
5828
5853
|
private readonly eventBus;
|
|
5829
5854
|
private readonly plugins;
|
|
5830
5855
|
private readonly services;
|
|
5856
|
+
private readonly apiHookEntries;
|
|
5831
5857
|
private readonly subContexts;
|
|
5832
5858
|
private readonly eventSourceRuntimes;
|
|
5833
5859
|
private readonly parentEventForwarder?;
|
|
@@ -5845,6 +5871,8 @@ declare class Context {
|
|
|
5845
5871
|
...args: T
|
|
5846
5872
|
): void;
|
|
5847
5873
|
installEventSource(eventSource: MilkyEventSource): void;
|
|
5874
|
+
hookApi<E extends ApiEndpointName>(endpoint: E, hook: ApiHook<E>): () => void;
|
|
5875
|
+
hookApi(hook: AnyApiHook): () => void;
|
|
5848
5876
|
provide<T extends object>(service: ServiceClass<T>, instance: T): void;
|
|
5849
5877
|
resolve<T extends object>(service: ServiceClass<T>): T;
|
|
5850
5878
|
tryResolve<T extends object>(service: ServiceClass<T>): T | undefined;
|
|
@@ -5858,6 +5886,7 @@ declare class Context {
|
|
|
5858
5886
|
private startInternal;
|
|
5859
5887
|
private getState;
|
|
5860
5888
|
private stopInternal;
|
|
5889
|
+
private assertCanRegisterApiHook;
|
|
5861
5890
|
private assertCanScheduleTimer;
|
|
5862
5891
|
private runTimerCallback;
|
|
5863
5892
|
private clearTimers;
|
|
@@ -5879,6 +5908,11 @@ declare class Context {
|
|
|
5879
5908
|
private runEventSource;
|
|
5880
5909
|
private waitForReconnectDelay;
|
|
5881
5910
|
private resolveReconnectDelay;
|
|
5911
|
+
private createHookClient;
|
|
5912
|
+
private wrapAnyApiHook;
|
|
5913
|
+
private callHookedApi;
|
|
5914
|
+
private collectApiHooks;
|
|
5915
|
+
private callBaseApi;
|
|
5882
5916
|
static fromUrl(baseUrl: string | URL, options?: ContextOptions & ContextUrlOptions): Context;
|
|
5883
5917
|
static fromClient(client: MilkyClient, options?: ContextOptions): Context;
|
|
5884
5918
|
}
|
|
@@ -5924,7 +5958,17 @@ declare namespace seg {
|
|
|
5924
5958
|
}
|
|
5925
5959
|
//#endregion
|
|
5926
5960
|
export {
|
|
5961
|
+
AnyApiCall,
|
|
5962
|
+
AnyApiHook,
|
|
5963
|
+
AnyApiNext,
|
|
5964
|
+
ApiCall,
|
|
5965
|
+
ApiEndpointName,
|
|
5927
5966
|
ApiEndpoints,
|
|
5967
|
+
ApiHook,
|
|
5968
|
+
ApiNext,
|
|
5969
|
+
ApiParams,
|
|
5970
|
+
ApiRequest,
|
|
5971
|
+
ApiResponse,
|
|
5928
5972
|
Capturer,
|
|
5929
5973
|
Command,
|
|
5930
5974
|
CommandBuilder,
|
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",
|
|
@@ -820,16 +820,18 @@ function implementsESNextDisposable(service) {
|
|
|
820
820
|
const DEFAULT_INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
821
821
|
const DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
|
|
822
822
|
var Context = class Context {
|
|
823
|
-
client;
|
|
824
823
|
router = new Router();
|
|
825
824
|
logger;
|
|
826
825
|
name;
|
|
827
826
|
routeActivationResolver;
|
|
827
|
+
client;
|
|
828
|
+
baseClient;
|
|
828
829
|
parent;
|
|
829
830
|
filter;
|
|
830
831
|
eventBus = mitt();
|
|
831
832
|
plugins = [];
|
|
832
833
|
services = /* @__PURE__ */ new Map();
|
|
834
|
+
apiHookEntries = [];
|
|
833
835
|
subContexts = /* @__PURE__ */ new Map();
|
|
834
836
|
eventSourceRuntimes = /* @__PURE__ */ new Map();
|
|
835
837
|
parentEventForwarder;
|
|
@@ -840,8 +842,9 @@ var Context = class Context {
|
|
|
840
842
|
state = "idle";
|
|
841
843
|
startPromise;
|
|
842
844
|
stopPromise;
|
|
843
|
-
constructor(
|
|
844
|
-
this.
|
|
845
|
+
constructor(baseClient, options, name, parent, filter) {
|
|
846
|
+
this.baseClient = baseClient;
|
|
847
|
+
this.client = this.createHookClient();
|
|
845
848
|
this.initialReconnectDelayMs = options?.reconnect?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
|
|
846
849
|
this.maxReconnectDelayMs = options?.reconnect?.maxDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
|
|
847
850
|
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
@@ -892,6 +895,26 @@ var Context = class Context {
|
|
|
892
895
|
this.eventSourceRuntimes.set(eventSource, {});
|
|
893
896
|
if (this.state === "started") this.startEventSource(eventSource);
|
|
894
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
|
+
}
|
|
895
918
|
provide(service, instance) {
|
|
896
919
|
if (this.services.has(service)) throw new Error(`Service ${service.name} has already been provided in this context.`);
|
|
897
920
|
if (implementsESNextDisposable(instance) && !isDisposable(instance)) throw new Error(`
|
|
@@ -921,7 +944,7 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
921
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.`);
|
|
922
945
|
return this.subContexts.get(name);
|
|
923
946
|
}
|
|
924
|
-
const subContext = new Context(this.
|
|
947
|
+
const subContext = new Context(this.baseClient, void 0, name, this, filter);
|
|
925
948
|
this.subContexts.set(name, subContext);
|
|
926
949
|
return subContext;
|
|
927
950
|
}
|
|
@@ -1053,9 +1076,14 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1053
1076
|
errors.push(error);
|
|
1054
1077
|
}
|
|
1055
1078
|
}
|
|
1079
|
+
this.apiHookEntries.length = 0;
|
|
1056
1080
|
if (errors.length === 1) throw errors[0];
|
|
1057
1081
|
if (errors.length > 1) throw new AggregateError(errors, `Context "${this.name}" failed to stop cleanly.`);
|
|
1058
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
|
+
}
|
|
1059
1087
|
assertCanScheduleTimer() {
|
|
1060
1088
|
if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot schedule timers while it is stopping.`);
|
|
1061
1089
|
if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot schedule timers after it has stopped.`);
|
|
@@ -1290,8 +1318,53 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1290
1318
|
runtime.resolveReconnectTimer = void 0;
|
|
1291
1319
|
resolve?.();
|
|
1292
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
|
+
}
|
|
1293
1365
|
static fromUrl(baseUrl, options) {
|
|
1294
|
-
const
|
|
1366
|
+
const client = createMilkyClient(baseUrl, { accessToken: options?.accessToken });
|
|
1367
|
+
const context = new Context(client, options);
|
|
1295
1368
|
if (options?.installEventSource ?? true) context.installEventSource(createMilkyWebSocketEventSource(baseUrl, { accessToken: options?.accessToken }));
|
|
1296
1369
|
return context;
|
|
1297
1370
|
}
|