@fraqjs/fraq 0.6.3 → 0.7.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
@@ -5679,6 +5679,7 @@ declare class Context {
5679
5679
  private readonly services;
5680
5680
  private readonly subContexts;
5681
5681
  private readonly parentEventForwarder?;
5682
+ private readonly timers;
5682
5683
  private readonly initialReconnectDelayMs;
5683
5684
  private readonly maxReconnectDelayMs;
5684
5685
  private readonly logHandler?;
@@ -5700,16 +5701,23 @@ declare class Context {
5700
5701
  tryResolve<T extends object>(service: ServiceClass<T>): T | undefined;
5701
5702
  isProvided<T extends object>(service: ServiceClass<T>): boolean;
5702
5703
  fork(name: string, filter?: Filter): Context;
5704
+ timeout(delayMs: number, callback: () => void | Promise<void>): NodeJS.Timeout;
5705
+ interval(intervalMs: number, callback: () => void | Promise<void>): NodeJS.Timeout;
5703
5706
  createSession(message: IncomingMessage): Session;
5704
5707
  start(): Promise<void>;
5705
5708
  stop(): Promise<void>;
5706
5709
  private startInternal;
5707
5710
  private getState;
5708
5711
  private stopInternal;
5712
+ private assertCanScheduleTimer;
5713
+ private runTimerCallback;
5714
+ private clearTimers;
5709
5715
  private stopEventStream;
5710
5716
  private acceptsParentEvent;
5711
5717
  private applyPlugins;
5718
+ private recursiveApplyPlugins;
5712
5719
  private startPlugins;
5720
+ private recursiveStartPlugins;
5713
5721
  private sortPlugins;
5714
5722
  private collectPendingProvidedServices;
5715
5723
  private areRequiredServicesAvailable;
package/dist/index.mjs CHANGED
@@ -382,6 +382,7 @@ var Context = class Context {
382
382
  services = /* @__PURE__ */ new Map();
383
383
  subContexts = [];
384
384
  parentEventForwarder;
385
+ timers = /* @__PURE__ */ new Set();
385
386
  initialReconnectDelayMs;
386
387
  maxReconnectDelayMs;
387
388
  logHandler;
@@ -466,6 +467,23 @@ and implement the dispose method to clean up resources when the context stops.
466
467
  this.subContexts.push(subContext);
467
468
  return subContext;
468
469
  }
470
+ timeout(delayMs, callback) {
471
+ this.assertCanScheduleTimer();
472
+ const timeout = setTimeout(() => {
473
+ this.timers.delete(timeout);
474
+ this.runTimerCallback(callback);
475
+ }, delayMs);
476
+ this.timers.add(timeout);
477
+ return timeout;
478
+ }
479
+ interval(intervalMs, callback) {
480
+ this.assertCanScheduleTimer();
481
+ const interval = setInterval(() => {
482
+ this.runTimerCallback(callback);
483
+ }, intervalMs);
484
+ this.timers.add(interval);
485
+ return interval;
486
+ }
469
487
  createSession(message) {
470
488
  return {
471
489
  raw: message,
@@ -508,10 +526,10 @@ and implement the dispose method to clean up resources when the context stops.
508
526
  }
509
527
  }
510
528
  async stop() {
511
- if (this.state === "idle" || this.state === "stopped") return;
529
+ if (this.state === "idle" && this.timers.size === 0 || this.state === "stopped") return;
512
530
  if (this.state === "starting") await this.startPromise;
513
531
  const stateAfterStart = this.getState();
514
- if (stateAfterStart === "idle" || stateAfterStart === "stopped") return;
532
+ if (stateAfterStart === "idle" && this.timers.size === 0 || stateAfterStart === "stopped") return;
515
533
  if (stateAfterStart === "stopping") {
516
534
  await this.stopPromise;
517
535
  return;
@@ -526,16 +544,16 @@ and implement the dispose method to clean up resources when the context stops.
526
544
  }
527
545
  }
528
546
  async startInternal() {
547
+ const appliedContextPlugins = [];
548
+ const startingContexts = [];
529
549
  try {
530
- const sortedPlugins = this.sortPlugins();
531
- await this.applyPlugins(sortedPlugins);
532
- await this.startPlugins(sortedPlugins);
550
+ await this.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
551
+ await this.recursiveStartPlugins(appliedContextPlugins);
533
552
  } catch (error) {
534
- this.state = "idle";
553
+ for (const context of startingContexts) if (context.state === "starting") context.state = "idle";
535
554
  throw error;
536
555
  }
537
- for (const subContext of this.subContexts) await subContext.start();
538
- this.state = "started";
556
+ for (const { context } of appliedContextPlugins) context.state = "started";
539
557
  if (!this.parent) this.eventStreamTask = this.runEventStream();
540
558
  }
541
559
  getState() {
@@ -543,12 +561,13 @@ and implement the dispose method to clean up resources when the context stops.
543
561
  }
544
562
  async stopInternal() {
545
563
  const errors = [];
546
- await this.stopEventStream(errors);
564
+ this.clearTimers();
547
565
  for (const subContext of [...this.subContexts].reverse()) try {
548
566
  await subContext.stop();
549
567
  } catch (error) {
550
568
  errors.push(error);
551
569
  }
570
+ await this.stopEventStream(errors);
552
571
  if (this.parent && this.parentEventForwarder) this.parent.eventBus.off("*", this.parentEventForwarder);
553
572
  for (const service of [...this.services.values()].reverse()) {
554
573
  if (!isDisposable(service)) continue;
@@ -561,6 +580,22 @@ and implement the dispose method to clean up resources when the context stops.
561
580
  if (errors.length === 1) throw errors[0];
562
581
  if (errors.length > 1) throw new AggregateError(errors, `Context "${this.name}" failed to stop cleanly.`);
563
582
  }
583
+ assertCanScheduleTimer() {
584
+ if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot schedule timers while it is stopping.`);
585
+ if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot schedule timers after it has stopped.`);
586
+ }
587
+ async runTimerCallback(callback) {
588
+ if (this.state === "stopping" || this.state === "stopped") return;
589
+ try {
590
+ await callback();
591
+ } catch (error) {
592
+ this.logger.error("Error handling timer callback", error);
593
+ }
594
+ }
595
+ clearTimers() {
596
+ for (const timer of this.timers) clearTimeout(timer);
597
+ this.timers.clear();
598
+ }
564
599
  async stopEventStream(errors) {
565
600
  this.resolveReconnectDelay();
566
601
  const subscription = this.eventSubscription;
@@ -591,6 +626,24 @@ and implement the dispose method to clean up resources when the context stops.
591
626
  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.`);
592
627
  }
593
628
  }
629
+ async recursiveApplyPlugins(appliedContextPlugins, startingContexts) {
630
+ if (this.state === "started") return;
631
+ if (this.state === "starting" && this.startPromise) {
632
+ await this.startPromise;
633
+ return;
634
+ }
635
+ if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot be started while it is stopping.`);
636
+ if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot be restarted after it has been stopped.`);
637
+ if (this.state === "idle") this.state = "starting";
638
+ startingContexts.push(this);
639
+ const sortedPlugins = this.sortPlugins();
640
+ await this.applyPlugins(sortedPlugins);
641
+ appliedContextPlugins.push({
642
+ context: this,
643
+ sortedPlugins
644
+ });
645
+ for (const subContext of this.subContexts) await subContext.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
646
+ }
594
647
  async startPlugins(sortedPlugins) {
595
648
  for (const installedPlugin of sortedPlugins) {
596
649
  const { plugin } = installedPlugin;
@@ -599,6 +652,9 @@ and implement the dispose method to clean up resources when the context stops.
599
652
  await plugin.start(this.getPluginContext(installedPlugin));
600
653
  }
601
654
  }
655
+ async recursiveStartPlugins(appliedContextPlugins) {
656
+ for (const { context, sortedPlugins } of appliedContextPlugins) await context.startPlugins(sortedPlugins);
657
+ }
602
658
  sortPlugins() {
603
659
  const providers = /* @__PURE__ */ new Map();
604
660
  for (const { plugin } of this.plugins) for (const service of plugin.provides ?? []) {
@@ -664,6 +720,10 @@ and implement the dispose method to clean up resources when the context stops.
664
720
  }
665
721
  createProxyContextForPlugin(plugin) {
666
722
  const proxyLogger = new Logger((message) => this.logHandler?.(message), plugin.name);
723
+ const proxyProvide = (service, instance) => {
724
+ this.provide(service, instance);
725
+ this.logger.debug(`Plugin ${plugin.name} provided ${service.name}`);
726
+ };
667
727
  let proxyInjections;
668
728
  if (plugin.inject) {
669
729
  proxyInjections = {};
@@ -673,10 +733,11 @@ and implement the dispose method to clean up resources when the context stops.
673
733
  proxyInjections ??= {};
674
734
  for (const [key, service] of Object.entries(plugin.optionalInject)) proxyInjections[key] = this.tryResolve(service);
675
735
  }
676
- return new Proxy(this, { get(target, prop) {
736
+ return new Proxy(this, { get(target, prop, receiver) {
677
737
  if (prop === "logger") return proxyLogger;
738
+ else if (prop === "provide") return proxyProvide;
678
739
  else if (proxyInjections && prop in proxyInjections) return proxyInjections[prop];
679
- else return target[prop];
740
+ else return Reflect.get(target, prop, receiver);
680
741
  } });
681
742
  }
682
743
  async runEventStream() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fraqjs/fraq",
3
3
  "type": "module",
4
- "version": "0.6.3",
4
+ "version": "0.7.0",
5
5
  "description": "Milky Bot framework",
6
6
  "files": [
7
7
  "dist"