@notchjs/core 0.2.2 → 0.3.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.
@@ -1,5 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
+ /// <reference types="node" />
3
4
  import * as http from 'node:http';
4
5
  import * as https from 'node:https';
5
6
  import type { HttpAdapter } from '@notchjs/types';
@@ -10,6 +11,8 @@ export declare class Application {
10
11
  private readonly adapter;
11
12
  private readonly hooks;
12
13
  private readonly _environment;
14
+ private readonly activeSignals;
15
+ private cleanupRef?;
13
16
  private isInitialized;
14
17
  private isListening;
15
18
  constructor(adapter: HttpAdapter, hooks: HookCollector, environment: ApplicationEnvironment);
@@ -23,6 +26,9 @@ export declare class Application {
23
26
  listen(port: string | number, hostname: string, callback?: () => void): Promise<any>;
24
27
  close(signal?: string): Promise<void>;
25
28
  getUrl(): Promise<string>;
29
+ protected shutdownOnSignal(signals?: (NodeJS.Signals | string)[]): void;
30
+ protected listenToSignals(signals: string[]): void;
31
+ protected unsubscribeFromSignals(): void;
26
32
  protected dispose(): Promise<void>;
27
33
  private formatAddress;
28
34
  private getProtocol;
@@ -3,8 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Application = void 0;
4
4
  const node_os_1 = require("node:os");
5
5
  const notions_1 = require("@hemjs/notions");
6
+ const iterare_1 = require("iterare");
6
7
  class Application {
7
8
  constructor(adapter, hooks, environment) {
9
+ this.activeSignals = new Array();
8
10
  this.isInitialized = false;
9
11
  this.isListening = false;
10
12
  this.adapter = adapter;
@@ -20,11 +22,11 @@ class Application {
20
22
  return this;
21
23
  }
22
24
  const startAt = process.hrtime.bigint();
23
- await this.hooks.addStartupHook();
25
+ await this.hooks.onStartup();
24
26
  this.isInitialized = true;
25
27
  const finishedAt = process.hrtime.bigint();
26
- const elapsed = (Number(finishedAt - startAt) / 1e9).toFixed(3);
27
- this.environment.log?.info(`Application started in ${elapsed} seconds.`);
28
+ const elapsedTime = (Number(finishedAt - startAt) / 1e9).toFixed(3);
29
+ this.environment.log?.info(`Application started in ${elapsedTime} seconds`);
28
30
  return this;
29
31
  }
30
32
  getHttpAdapter() {
@@ -43,6 +45,9 @@ class Application {
43
45
  async listen(port, ...args) {
44
46
  if (!this.isInitialized)
45
47
  await this.init();
48
+ if (this.environment.config?.shutdown?.enabled === true) {
49
+ this.shutdownOnSignal(this.environment.config?.shutdown?.signals);
50
+ }
46
51
  return new Promise((resolve, reject) => {
47
52
  const errorHandler = (e) => {
48
53
  this.environment.log?.error(e?.toString?.());
@@ -71,13 +76,14 @@ class Application {
71
76
  }
72
77
  async close(signal) {
73
78
  await this.dispose();
74
- await this.hooks.addShutdownHook(signal);
79
+ await this.hooks.onShutdown(signal);
80
+ this.unsubscribeFromSignals();
75
81
  this.isListening = false;
76
82
  }
77
83
  async getUrl() {
78
84
  return new Promise((resolve, reject) => {
79
85
  if (!this.isListening) {
80
- this.environment.log?.error('Ensure app.listen() is called before attempting to get the URL with app.getUrl().');
86
+ this.environment.log?.error('app.listen() must be called before app.getUrl()');
81
87
  reject('Server not listening!');
82
88
  return;
83
89
  }
@@ -85,6 +91,46 @@ class Application {
85
91
  resolve(this.formatAddress(address));
86
92
  });
87
93
  }
94
+ shutdownOnSignal(signals = []) {
95
+ signals = (0, iterare_1.iterate)(Array.from(new Set(signals)))
96
+ .map((signal) => signal.toString().toUpperCase().trim())
97
+ .filter((signal) => !this.activeSignals.includes(signal))
98
+ .toArray();
99
+ this.listenToSignals(signals);
100
+ }
101
+ listenToSignals(signals) {
102
+ let receivedSignal = false;
103
+ const doCleanup = async (signal) => {
104
+ try {
105
+ if (receivedSignal) {
106
+ return;
107
+ }
108
+ receivedSignal = true;
109
+ this.environment.log?.info(`Received ${signal}. Shutdown initiated...`);
110
+ await this.dispose();
111
+ await this.hooks.onShutdown(signal);
112
+ signals.forEach((sig) => process.removeListener(sig, doCleanup));
113
+ process.kill(process.pid, signal);
114
+ }
115
+ catch (err) {
116
+ this.environment.log?.error(err.stack);
117
+ process.exit(1);
118
+ }
119
+ };
120
+ this.cleanupRef = doCleanup;
121
+ signals.forEach((signal) => {
122
+ this.activeSignals.push(signal);
123
+ process.on(signal, doCleanup);
124
+ });
125
+ }
126
+ unsubscribeFromSignals() {
127
+ if (!this.cleanupRef) {
128
+ return;
129
+ }
130
+ this.activeSignals.forEach((signal) => {
131
+ process.removeListener(signal, this.cleanupRef);
132
+ });
133
+ }
88
134
  async dispose() {
89
135
  this.adapter && (await this.adapter.close());
90
136
  }
@@ -3,8 +3,8 @@ import type { HookFactory } from './hook-factory';
3
3
  export declare class HookCollector {
4
4
  private readonly records;
5
5
  constructor(factory: HookFactory, hooks?: HookProvider[]);
6
- addStartupHook(): Promise<void>;
7
- addShutdownHook(signal?: string): Promise<void>;
6
+ onStartup(): Promise<void>;
7
+ onShutdown(signal?: string): Promise<void>;
8
8
  private runStartupHook;
9
9
  private runShutdownHook;
10
10
  private isStartupHook;
@@ -7,10 +7,10 @@ class HookCollector {
7
7
  constructor(factory, hooks = []) {
8
8
  this.records = factory.prepare(hooks);
9
9
  }
10
- async addStartupHook() {
10
+ async onStartup() {
11
11
  await Promise.all(this.runStartupHook(this.records));
12
12
  }
13
- async addShutdownHook(signal) {
13
+ async onShutdown(signal) {
14
14
  await Promise.all(this.runShutdownHook(this.records, signal));
15
15
  }
16
16
  runStartupHook(records) {
@@ -1,5 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import type { TlsOptions } from 'node:tls';
3
+ import type { ShutdownOptions } from './shutdown-options';
3
4
  export interface ApplicationConfig {
4
5
  tls?: TlsOptions;
6
+ shutdown?: ShutdownOptions;
5
7
  }
@@ -0,0 +1,7 @@
1
+ /// <reference types="node" />
2
+ export interface ShutdownOptions {
3
+ enabled?: boolean;
4
+ signals?: Array<NodeJS.Signals | string>;
5
+ graceful?: boolean;
6
+ grace?: number;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -57,7 +57,7 @@ class NotchModule {
57
57
  useFactory: (container) => {
58
58
  const httpAdapterHost = container.get(http_adapter_host_1.HttpAdapterHost.name);
59
59
  if (!httpAdapterHost.httpAdapter) {
60
- throw new Error('HTTP adapter missing');
60
+ throw new Error('HTTP adapter not found. Please register a compatible HTTP adapter with the "HTTP_ADAPTER" token.');
61
61
  }
62
62
  const config = container.has('config')
63
63
  ? container.get('config')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notchjs/core",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "A dependency injection based web framework (Node.js)",
5
5
  "license": "MIT",
6
6
  "author": "Augustus Kamau",
@@ -18,13 +18,13 @@
18
18
  "test": "jest --detectOpenHandles"
19
19
  },
20
20
  "dependencies": {
21
- "@notchjs/util": "^0.2.1",
21
+ "@notchjs/util": "^0.3.0",
22
22
  "iterare": "1.2.1",
23
23
  "tslib": "2.6.2"
24
24
  },
25
25
  "devDependencies": {
26
- "@notchjs/express": "^0.2.2",
27
- "@notchjs/types": "^0.2.1"
26
+ "@notchjs/express": "^0.3.0",
27
+ "@notchjs/types": "^0.3.0"
28
28
  },
29
29
  "homepage": "https://github.com/notchjs/notch",
30
30
  "bugs": {
@@ -40,5 +40,5 @@
40
40
  "publishConfig": {
41
41
  "access": "public"
42
42
  },
43
- "gitHead": "e9c3e51d2bcb4d0cf46594a0649266c541ea2598"
43
+ "gitHead": "da7036186e7a4cf72cdc91c2122c1b89f224af0f"
44
44
  }