@fraqjs/fraq 0.3.2 → 0.4.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 CHANGED
@@ -5540,23 +5540,26 @@ declare namespace filter {
5540
5540
  }
5541
5541
  //#endregion
5542
5542
  //#region src/core/logging.d.ts
5543
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
5543
5544
  interface LogMessage {
5544
- level: 'debug' | 'info' | 'warn' | 'error';
5545
+ level: LogLevel;
5545
5546
  module: string;
5546
5547
  message: string;
5547
5548
  error?: unknown;
5548
5549
  time: number;
5549
5550
  }
5551
+ type LogHandler = (message: LogMessage) => void;
5550
5552
  declare class Logger {
5551
5553
  private readonly logHandler;
5552
5554
  private readonly module;
5553
- constructor(logHandler: (message: LogMessage) => void, module: string);
5555
+ constructor(logHandler: LogHandler, module: string);
5554
5556
  private log;
5555
5557
  debug(message: string): void;
5556
5558
  info(message: string): void;
5557
5559
  warn(message: string, error?: unknown): void;
5558
5560
  error(message: string, error?: unknown): void;
5559
5561
  }
5562
+ declare function combineLogHandlers(...handlers: LogHandler[]): LogHandler;
5560
5563
  //#endregion
5561
5564
  //#region src/core/service.d.ts
5562
5565
  /** biome-ignore-all lint/suspicious/noExplicitAny: Service constructors may accept any arguments. */
@@ -5564,13 +5567,31 @@ type ServiceClass<T extends object = object> = abstract new (...args: any[]) =>
5564
5567
  //#endregion
5565
5568
  //#region src/core/plugin.d.ts
5566
5569
  type ParameterList = Array<any>;
5567
- interface Plugin<T extends ParameterList> {
5570
+ type Injection = Record<string, ServiceClass>;
5571
+ type InjectedServices<I extends Injection | undefined> = I extends Injection
5572
+ ? { [K in keyof I]: InstanceType<I[K]> }
5573
+ : {};
5574
+ type OptionalInjectedServices<I extends Injection | undefined> = I extends Injection
5575
+ ? { [K in keyof I]: InstanceType<I[K]> | undefined }
5576
+ : {};
5577
+ interface Plugin<
5578
+ T extends ParameterList,
5579
+ I extends Injection | undefined,
5580
+ OI extends Injection | undefined = undefined,
5581
+ > {
5568
5582
  name: string;
5569
5583
  requires?: readonly ServiceClass[];
5584
+ inject?: I;
5585
+ optionalRequires?: readonly ServiceClass[];
5586
+ optionalInject?: OI;
5570
5587
  provides?: readonly ServiceClass[];
5571
- apply(ctx: Context, ...args: T): void | Promise<void>;
5588
+ apply(ctx: Context & InjectedServices<I> & OptionalInjectedServices<OI>, ...args: T): void | Promise<void>;
5572
5589
  }
5573
- declare function definePlugin<T extends ParameterList>(plugin: Plugin<T>): Plugin<T>;
5590
+ declare function definePlugin<
5591
+ T extends ParameterList,
5592
+ I extends Injection | undefined,
5593
+ OI extends Injection | undefined,
5594
+ >(plugin: Plugin<T, I, OI>): Plugin<T, I, OI>;
5574
5595
  //#endregion
5575
5596
  //#region src/core/context.d.ts
5576
5597
  interface ContextOptions {
@@ -5578,7 +5599,7 @@ interface ContextOptions {
5578
5599
  initialDelayMs?: number;
5579
5600
  maxDelayMs?: number;
5580
5601
  };
5581
- logHandler?: (message: LogMessage) => void;
5602
+ logHandler?: LogHandler;
5582
5603
  }
5583
5604
  interface ContextUrlOptions {
5584
5605
  accessToken?: string;
@@ -5600,7 +5621,10 @@ declare class Context {
5600
5621
  private isStarted;
5601
5622
  private constructor();
5602
5623
  on<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void | Promise<void>): void;
5603
- install<T extends ParameterList>(plugin: Plugin<T>, ...args: T): void;
5624
+ install<T extends ParameterList, I extends Injection | undefined, OI extends Injection | undefined>(
5625
+ plugin: Plugin<T, I, OI>,
5626
+ ...args: T
5627
+ ): void;
5604
5628
  provide<T extends object>(service: ServiceClass<T>, instance: T): void;
5605
5629
  resolve<T extends object>(service: ServiceClass<T>): T;
5606
5630
  tryResolve<T extends object>(service: ServiceClass<T>): T | undefined;
@@ -5610,6 +5634,9 @@ declare class Context {
5610
5634
  private acceptsParentEvent;
5611
5635
  private applyPlugins;
5612
5636
  private sortPlugins;
5637
+ private collectPendingProvidedServices;
5638
+ private areRequiredServicesAvailable;
5639
+ private areOptionalServicesReady;
5613
5640
  private collectAvailableServices;
5614
5641
  private createUnresolvablePluginError;
5615
5642
  private createProxyContextForPlugin;
@@ -5666,6 +5693,9 @@ export {
5666
5693
  ContextUrlOptions,
5667
5694
  EventMap,
5668
5695
  Filter,
5696
+ Injection,
5697
+ LogHandler,
5698
+ LogLevel,
5669
5699
  LogMessage,
5670
5700
  Logger,
5671
5701
  MilkyClient,
@@ -5676,6 +5706,7 @@ export {
5676
5706
  Router,
5677
5707
  type ServiceClass,
5678
5708
  Session,
5709
+ combineLogHandlers,
5679
5710
  createMilkyClient,
5680
5711
  definePlugin,
5681
5712
  filter,
package/dist/index.mjs CHANGED
@@ -300,6 +300,11 @@ var Logger = class {
300
300
  this.log("error", message, error);
301
301
  }
302
302
  };
303
+ function combineLogHandlers(...handlers) {
304
+ return (message) => {
305
+ for (const handler of handlers) handler(message);
306
+ };
307
+ }
303
308
  //#endregion
304
309
  //#region src/core/service.ts
305
310
  function getServiceName(service) {
@@ -414,7 +419,10 @@ var Context = class Context {
414
419
  const available = /* @__PURE__ */ new Set();
415
420
  for (const service of this.collectAvailableServices()) available.add(service);
416
421
  while (pending.length > 0) {
417
- const nextIndex = pending.findIndex(({ plugin }) => (plugin.requires ?? []).every((service) => available.has(service)));
422
+ const availableByPendingPlugins = this.collectPendingProvidedServices(pending);
423
+ const requiredReadyIndex = pending.findIndex(({ plugin }) => this.areRequiredServicesAvailable(plugin, available));
424
+ const optionalReadyIndex = pending.findIndex(({ plugin }) => this.areRequiredServicesAvailable(plugin, available) && this.areOptionalServicesReady(plugin, available, availableByPendingPlugins));
425
+ const nextIndex = optionalReadyIndex === -1 ? requiredReadyIndex : optionalReadyIndex;
418
426
  if (nextIndex === -1) throw this.createUnresolvablePluginError(pending, available);
419
427
  const [next] = pending.splice(nextIndex, 1);
420
428
  sorted.push(next);
@@ -422,6 +430,21 @@ var Context = class Context {
422
430
  }
423
431
  return sorted;
424
432
  }
433
+ collectPendingProvidedServices(pending) {
434
+ const providedServices = /* @__PURE__ */ new Map();
435
+ for (const installedPlugin of pending) for (const service of installedPlugin.plugin.provides ?? []) providedServices.set(service, installedPlugin);
436
+ return providedServices;
437
+ }
438
+ areRequiredServicesAvailable(plugin, available) {
439
+ return (plugin.requires ?? []).every((service) => available.has(service));
440
+ }
441
+ areOptionalServicesReady(plugin, available, pendingProviders) {
442
+ return (plugin.optionalRequires ?? []).every((service) => {
443
+ if (available.has(service)) return true;
444
+ const pendingProvider = pendingProviders.get(service);
445
+ return pendingProvider === void 0 || pendingProvider.plugin === plugin;
446
+ });
447
+ }
425
448
  collectAvailableServices() {
426
449
  const services = [...this.services.keys()];
427
450
  if (this.parent) services.push(...this.parent.collectAvailableServices());
@@ -445,15 +468,27 @@ var Context = class Context {
445
468
  }
446
469
  createProxyContextForPlugin(plugin) {
447
470
  const proxyLogger = new Logger((message) => this.logHandler?.(message), plugin.name);
471
+ let proxyInjections;
472
+ if (plugin.inject) {
473
+ proxyInjections = {};
474
+ for (const [key, service] of Object.entries(plugin.inject)) proxyInjections[key] = this.resolve(service);
475
+ }
476
+ if (plugin.optionalInject) {
477
+ proxyInjections ??= {};
478
+ for (const [key, service] of Object.entries(plugin.optionalInject)) proxyInjections[key] = this.tryResolve(service);
479
+ }
448
480
  return new Proxy(this, { get(target, prop) {
449
481
  if (prop === "logger") return proxyLogger;
482
+ else if (proxyInjections && prop in proxyInjections) return proxyInjections[prop];
450
483
  else return target[prop];
451
484
  } });
452
485
  }
453
486
  async runEventStream() {
454
487
  let reconnectDelay = this.initialReconnectDelayMs;
488
+ let reconnectAttempt = 1;
455
489
  while (this.isStarted) {
456
490
  try {
491
+ this.logger.debug(`Connecting event stream (attempt=${reconnectAttempt})`);
457
492
  const subscription = await this.client.startEvents((event) => {
458
493
  try {
459
494
  this.eventBus.emit(event.event_type, event);
@@ -461,13 +496,17 @@ var Context = class Context {
461
496
  this.logger.error("Error handling event stream event", error);
462
497
  }
463
498
  });
499
+ this.logger.info("Event stream connected");
464
500
  reconnectDelay = this.initialReconnectDelayMs;
501
+ reconnectAttempt = 1;
465
502
  await subscription.closed;
503
+ this.logger.warn(`Event stream disconnected; reconnecting in ${reconnectDelay}ms`);
466
504
  } catch (error) {
467
- this.logger.error("Error connecting event stream", error);
505
+ this.logger.error(`Error connecting event stream; reconnecting in ${reconnectDelay}ms`, error);
468
506
  }
469
507
  await new Promise((resolve) => setTimeout(resolve, reconnectDelay));
470
508
  reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
509
+ reconnectAttempt += 1;
471
510
  }
472
511
  }
473
512
  createSession(message) {
@@ -604,6 +643,14 @@ let filter;
604
643
  //#endregion
605
644
  //#region src/core/plugin.ts
606
645
  function definePlugin(plugin) {
646
+ if (plugin.inject) {
647
+ if (plugin.requires) throw new Error(`Plugin "${plugin.name}" cannot have both "requires" and "inject" properties.`);
648
+ plugin.requires = Object.values(plugin.inject);
649
+ }
650
+ if (plugin.optionalInject) {
651
+ if (plugin.optionalRequires) throw new Error(`Plugin "${plugin.name}" cannot have both "optionalRequires" and "optionalInject" properties.`);
652
+ plugin.optionalRequires = Object.values(plugin.optionalInject);
653
+ }
607
654
  return plugin;
608
655
  }
609
656
  //#endregion
@@ -866,4 +913,4 @@ let param;
866
913
  _param.segment = segment;
867
914
  })(param || (param = {}));
868
915
  //#endregion
869
- export { Context, Logger, Parameter, Router, createMilkyClient, definePlugin, filter, milkyPackageVersion, milkyVersion, msg, param, seg };
916
+ export { Context, Logger, Parameter, Router, combineLogHandlers, createMilkyClient, definePlugin, filter, milkyPackageVersion, milkyVersion, msg, param, seg };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fraqjs/fraq",
3
3
  "type": "module",
4
- "version": "0.3.2",
4
+ "version": "0.4.0",
5
5
  "description": "Milky Bot framework",
6
6
  "files": [
7
7
  "dist"