ocpp-ws-io 2.1.7 → 2.1.8

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.
@@ -2,7 +2,7 @@ import * as WebSocket from 'ws';
2
2
  import WebSocket__default, { WebSocket as WebSocket$1 } from 'ws';
3
3
  import * as node_https from 'node:https';
4
4
  import * as node_http from 'node:http';
5
- import { IncomingMessage } from 'node:http';
5
+ import { Server, IncomingMessage } from 'node:http';
6
6
  import { EventEmitter } from 'node:events';
7
7
  import { Duplex } from 'node:stream';
8
8
  import { TLSSocket } from 'node:tls';
@@ -4431,6 +4431,100 @@ type OCPPResponseType<P extends keyof OCPPMethodMap, M extends string> = P exten
4431
4431
  response: infer R;
4432
4432
  } ? R : never : never : never;
4433
4433
 
4434
+ /**
4435
+ * Compiled regex pattern for RegExp-based route fallback.
4436
+ * Only used when a user registers a RegExp pattern (not string patterns).
4437
+ * @internal
4438
+ */
4439
+ interface CompiledRegexPattern {
4440
+ regex: RegExp;
4441
+ paramNames: string[];
4442
+ }
4443
+ declare const OCPPRouter_base: new () => TypedEventEmitter<ServerEvents>;
4444
+ /**
4445
+ * OCPPRouter — An Express-like Connection dispatcher.
4446
+ * Isolated handler for a specific set of matching URL route patterns.
4447
+ *
4448
+ * String patterns are matched via radix trie (O(k) lookup, managed by OCPPServer).
4449
+ * RegExp patterns fall back to linear matching.
4450
+ */
4451
+ declare class OCPPRouter extends OCPPRouter_base {
4452
+ /** Raw registered patterns (strings and/or RegExp) for reference. */
4453
+ patterns: Array<string | RegExp>;
4454
+ /** Connection middlewares attached to this router. */
4455
+ middlewares: ConnectionMiddleware[];
4456
+ /** Auth callback for this route endpoint. */
4457
+ authCallback: AuthCallback<unknown> | null;
4458
+ /** Route-level CORS options. */
4459
+ _routeCORS?: CORSOptions;
4460
+ /** Route-level config overrides. */
4461
+ _routeConfig?: RouterConfig;
4462
+ /**
4463
+ * Compiled RegExp patterns for fallback linear matching.
4464
+ * Only populated when RegExp patterns are registered.
4465
+ * @internal
4466
+ */
4467
+ _regexPatterns: CompiledRegexPattern[];
4468
+ constructor(patterns?: Array<string | RegExp>, middlewares?: ConnectionMiddleware[]);
4469
+ /**
4470
+ * Appends URL paths or regular expressions to this router's match condition.
4471
+ * String patterns are stored for trie insertion by OCPPServer.
4472
+ * RegExp patterns are compiled for linear fallback matching.
4473
+ */
4474
+ route(...patterns: Array<string | RegExp>): this;
4475
+ /**
4476
+ * Appends connection middlewares to this router's execution chain.
4477
+ */
4478
+ use(...middlewares: ConnectionMiddleware[]): this;
4479
+ /**
4480
+ * Applies specific CORS rules to connections matching this router's paths.
4481
+ */
4482
+ cors(options: CORSOptions): this;
4483
+ /**
4484
+ * Overrides global connection settings (e.g. timeouts, protocols) for this router.
4485
+ */
4486
+ config(options: RouterConfig): this;
4487
+ /**
4488
+ * Registers an authentication and protocol-negotiation callback for this route endpoint.
4489
+ */
4490
+ auth<TSession = Record<string, unknown>>(callback: AuthCallback<TSession>): this;
4491
+ /**
4492
+ * Binds a version-specific OCPP message handler directly to all clients that match this route.
4493
+ *
4494
+ * @throws {Error} AT RUNTIME when a client connects, if a handler for this version and method is already registered for that client.
4495
+ */
4496
+ handle<V extends OCPPProtocol, M extends AllMethodNames<V>>(version: V, method: M, handler: (context: RouterHandlerContext<OCPPRequestType<V, M>>) => OCPPResponseType<V, M> | Promise<OCPPResponseType<V, M>>): this;
4497
+ /**
4498
+ * Binds a custom/extension message handler directly to all clients that match this route.
4499
+ *
4500
+ * @throws {Error} AT RUNTIME when a client connects, if a handler for this protocol and method is already registered for that client.
4501
+ */
4502
+ handle<S extends string>(version: S extends OCPPProtocol ? never : S, method: string, handler: (context: RouterHandlerContext<Record<string, any>>) => any): this;
4503
+ /**
4504
+ * Binds a message handler directly to all clients that match this route using the default protocol.
4505
+ *
4506
+ * @throws {Error} AT RUNTIME when a client connects, if a handler for this method is already registered for that client.
4507
+ */
4508
+ handle<M extends AllMethodNames<OCPPProtocol>>(method: M, handler: (context: RouterHandlerContext<OCPPRequestType<OCPPProtocol, M>>) => OCPPResponseType<OCPPProtocol, M> | Promise<OCPPResponseType<OCPPProtocol, M>>): this;
4509
+ /**
4510
+ * Binds a custom/extension method not in the typed map.
4511
+ *
4512
+ * @throws {Error} AT RUNTIME when a client connects, if a handler for this method is already registered for that client.
4513
+ */
4514
+ handle(method: string, handler: (context: RouterHandlerContext<Record<string, any>>) => any): this;
4515
+ /**
4516
+ * Binds a wildcard handler to all clients that match this route.
4517
+ *
4518
+ * @throws {Error} AT RUNTIME when a client connects, if a wildcard handler is already registered for that client.
4519
+ */
4520
+ handle(handler: RouterWildcardHandler): this;
4521
+ }
4522
+ /**
4523
+ * Creates a standalone, modular `OCPPRouter` instance that can be attached
4524
+ * to an `OCPPServer` later via `server.attachRouters()`.
4525
+ */
4526
+ declare function createRouter(...patterns: Array<string | RegExp>): OCPPRouter;
4527
+
4434
4528
  /**
4435
4529
  * Middleware handling for intercepting and modifying OCPP operations.
4436
4530
  *
@@ -4641,7 +4735,7 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4641
4735
  sendRaw(message: string): void;
4642
4736
  reconfigure(options: Partial<ClientOptions>): void;
4643
4737
  protected _attachWebsocket(ws: WebSocket__default): void;
4644
- private _onMessage;
4738
+ protected _onMessage(rawData: WebSocket__default.RawData, preParsed?: unknown): void;
4645
4739
  private _handleIncomingCall;
4646
4740
  private _handleCallResult;
4647
4741
  private _handleCallError;
@@ -4686,6 +4780,43 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4686
4780
  private _cleanup;
4687
4781
  }
4688
4782
 
4783
+ interface WorkerPoolOptions {
4784
+ /** Number of worker threads (default: Math.max(2, cpus - 2)) */
4785
+ poolSize?: number;
4786
+ /** Max pending parse jobs before rejecting (default: 10000) */
4787
+ maxQueueSize?: number;
4788
+ }
4789
+ interface ParseResult {
4790
+ message: unknown;
4791
+ validationError?: {
4792
+ schemaId: string;
4793
+ errors: string;
4794
+ };
4795
+ }
4796
+ declare class WorkerPool {
4797
+ private _workers;
4798
+ private _nextWorker;
4799
+ private _taskId;
4800
+ private _pending;
4801
+ private _maxQueueSize;
4802
+ private _terminated;
4803
+ constructor(options?: WorkerPoolOptions);
4804
+ /** Number of worker threads in the pool */
4805
+ get size(): number;
4806
+ /** Number of pending (unresolved) parse tasks */
4807
+ get pendingTasks(): number;
4808
+ /**
4809
+ * Send raw data to a worker for JSON parsing + optional validation.
4810
+ * Uses round-robin worker selection.
4811
+ */
4812
+ parse(data: Buffer | string, schemaInfo?: {
4813
+ protocol: string;
4814
+ schemas: Record<string, unknown>;
4815
+ }): Promise<ParseResult>;
4816
+ /** Gracefully terminate all workers */
4817
+ shutdown(): Promise<void>;
4818
+ }
4819
+
4689
4820
  /**
4690
4821
  * OCPPServerClient — A server-side client representation.
4691
4822
  *
@@ -4700,8 +4831,14 @@ declare class OCPPServerClient extends OCPPClient {
4700
4831
  handshake: HandshakeInfo;
4701
4832
  session: Record<string, any>;
4702
4833
  protocol?: string;
4834
+ /** Optional adaptive rate multiplier getter (from OCPPServer.AdaptiveLimiter) */
4835
+ adaptiveMultiplier?: () => number;
4836
+ /** Optional worker pool for off-thread JSON parsing */
4837
+ workerPool?: WorkerPool;
4703
4838
  });
4704
4839
  private _rateLimits;
4840
+ private _adaptiveMultiplier;
4841
+ private _workerPool;
4705
4842
  private _checkRateLimit;
4706
4843
  private _attachServerWebsocket;
4707
4844
  private _handleRateLimitExceeded;
@@ -4732,6 +4869,240 @@ declare class OCPPServerClient extends OCPPClient {
4732
4869
  }>;
4733
4870
  }
4734
4871
 
4872
+ declare const OCPPServer_base: new () => TypedEventEmitter<ServerEvents>;
4873
+ /**
4874
+ * OCPPServer — A typed WebSocket RPC server for OCPP communication.
4875
+ *
4876
+ * Supports all 3 OCPP Security Profiles:
4877
+ * - Profile 1: Basic Auth over unsecured WS
4878
+ * - Profile 2: TLS + Basic Auth (HTTPS server)
4879
+ * - Profile 3: Mutual TLS (HTTPS server with requestCert)
4880
+ */
4881
+ declare class OCPPServer extends OCPPServer_base {
4882
+ private _options;
4883
+ /** Radix trie for O(k) route matching (string patterns). */
4884
+ private _trie;
4885
+ /** Global middleware routers (server.use() with no patterns — catch-all). */
4886
+ private _globalMiddlewareRouters;
4887
+ /** Routers with RegExp patterns (fallback linear scan). */
4888
+ private _regexRouters;
4889
+ private _clients;
4890
+ private _clientsByIdentity;
4891
+ private _httpServers;
4892
+ private _wss;
4893
+ private _state;
4894
+ private _adapter;
4895
+ private _httpServerAbortControllers;
4896
+ private _logger;
4897
+ private _globalCORS?;
4898
+ private _connectionBuckets;
4899
+ private _adaptiveLimiter;
4900
+ private _plugins;
4901
+ private _workerPool;
4902
+ private readonly _nodeId;
4903
+ private _sessions;
4904
+ private _gcInterval;
4905
+ private readonly _sessionTimeoutMs;
4906
+ constructor(options?: ServerOptions);
4907
+ get log(): LoggerLikeNotOptional;
4908
+ /**
4909
+ * Returns a readonly set of all currently connected OCPPServerClient instances.
4910
+ */
4911
+ get clients(): ReadonlySet<OCPPServerClient>;
4912
+ /**
4913
+ * Returns the current server state (OPEN, CLOSING, CLOSED).
4914
+ */
4915
+ get state(): "OPEN" | "CLOSING" | "CLOSED";
4916
+ /**
4917
+ * Returns current node observability statistics
4918
+ * (e.g. connected socket count, tracked memory sessions, and process CPU/Memory usage).
4919
+ * Fully compatible with Loki/Prometheus node metric ingestion.
4920
+ */
4921
+ stats(): OCPPServerStats;
4922
+ /**
4923
+ * Returns observability statistics from the active Event Adapter (e.g. Redis).
4924
+ * Useful for tracking consumer backlog and enabling Horizontal Pod Autoscaling.
4925
+ */
4926
+ adapterMetrics(): Promise<Record<string, unknown> | null>;
4927
+ /**
4928
+ * Synchronously returns the OCPPServerClient instance if the specific identity
4929
+ * is connected to THIS local server node.
4930
+ * Note: In a clustered environment, clients connected to other nodes will NOT be returned here.
4931
+ *
4932
+ * @param identity The client identity (username/station ID)
4933
+ */
4934
+ getLocalClient(identity: string): OCPPServerClient | undefined;
4935
+ /**
4936
+ * Synchronously checks if the specific identity is connected to THIS local server node.
4937
+ * Note: In a clustered environment, this will return false if the client is connected to another node.
4938
+ *
4939
+ * @param identity The client identity (username/station ID)
4940
+ */
4941
+ hasLocalClient(identity: string): boolean;
4942
+ /**
4943
+ * Asynchronously checks if the specific identity is connected to the server.
4944
+ * In a single-node setup, this checks the local connections.
4945
+ * In a clustered setup (with a pub/sub adapter), this will also check the global presence registry
4946
+ * to see if the client is connected to ANY node in the cluster.
4947
+ *
4948
+ * @param identity The client identity (username/station ID)
4949
+ */
4950
+ isClientConnected(identity: string): Promise<boolean>;
4951
+ /**
4952
+ * Applies global CORS rules to all incoming connections before routing.
4953
+ */
4954
+ cors(options: CORSOptions): this;
4955
+ /**
4956
+ * Registers a new routing dispatcher for multiplexing connections.
4957
+ * `server.route("/api/:tenant").use(middleware).auth(cb).on("client", ...)`
4958
+ */
4959
+ route(...patterns: Array<string | RegExp>): OCPPRouter;
4960
+ /**
4961
+ * Attaches one or more standalone modular routers created via `createRouter()`.
4962
+ * This is useful for separating route definitions across different files.
4963
+ */
4964
+ attachRouters(...routers: OCPPRouter[]): this;
4965
+ /**
4966
+ * Registers one or more plugins for server lifecycle hooks.
4967
+ * Plugins are called in registration order for all lifecycle events.
4968
+ *
4969
+ * @example Single plugin
4970
+ * ```ts
4971
+ * server.plugin(metricsPlugin);
4972
+ * ```
4973
+ *
4974
+ * @example Multiple plugins
4975
+ * ```ts
4976
+ * server.plugin(metricsPlugin, loggingPlugin, otelPlugin);
4977
+ * ```
4978
+ */
4979
+ plugin(...plugins: OCPPPlugin[]): this;
4980
+ /**
4981
+ * Registers middleware chain(s) as a wildcard/catch-all router.
4982
+ *
4983
+ * @example
4984
+ * ```ts
4985
+ * server.use(myMiddleware).route("/api").on("client", ...);
4986
+ * ```
4987
+ */
4988
+ use(...middlewares: ConnectionMiddleware[]): OCPPRouter;
4989
+ /**
4990
+ * Registers a top-level auth handler, returning a router to attach `.on()` or `.use()`.
4991
+ */
4992
+ auth<TSession = Record<string, unknown>>(callback: AuthCallback<TSession>): OCPPRouter;
4993
+ /**
4994
+ * Routes a router into the appropriate internal structure:
4995
+ * - String patterns → radix trie (O(k) lookup)
4996
+ * - RegExp patterns → linear fallback array
4997
+ * - No patterns → global middleware (catch-all)
4998
+ * @internal
4999
+ */
5000
+ private _registerRouter;
5001
+ listen(port?: number, host?: string, options?: ListenOptions): Promise<Server>;
5002
+ /**
5003
+ * Hot-reloads the TLS certificate on all active HTTPS servers without
5004
+ * dropping any existing WebSocket connections.
5005
+ *
5006
+ * **When to use:** Call this whenever your TLS certificate is renewed —
5007
+ * for example, after a Let's Encrypt auto-renewal (every ~90 days).
5008
+ * Without this, you would need to restart the Node.js process to pick up
5009
+ * the new certificate, disconnecting all connected charging stations.
5010
+ *
5011
+ * **How to use:**
5012
+ * ```ts
5013
+ * server.updateTLS({ cert: newCert, key: newKey });
5014
+ * ```
5015
+ *
5016
+ * **Optional:** Only relevant if you are terminating TLS directly in Node.js
5017
+ * (i.e. `SecurityProfile.TLS_BASIC_AUTH` or `TLS_CLIENT_CERT`). If you are
5018
+ * running behind a reverse proxy (Nginx, AWS ALB, etc.) that handles TLS,
5019
+ * you do not need this method — just rotate the cert on the proxy.
5020
+ *
5021
+ * @throws If the server is not using a TLS Security Profile.
5022
+ */
5023
+ updateTLS(tlsOpts: TLSOptions): void;
5024
+ get handleUpgrade(): (req: IncomingMessage, socket: Duplex, head: Buffer) => Promise<void>;
5025
+ /**
5026
+ * Core upgrade handler. Follows a strict pipeline:
5027
+ *
5028
+ * 1. Validate socket readyState & upgrade header
5029
+ * 2. Parse URL → identity + endpoint
5030
+ * 3. Enable TCP Keep-Alive
5031
+ * 4. Parse & negotiate subprotocols
5032
+ * 5. Parse Basic Auth (via modular parseBasicAuth)
5033
+ * 6. Extract TLS client certificate (Profile 3)
5034
+ * 7. Build HandshakeInfo
5035
+ * 8. Run auth callback with AbortController + handshake timeout
5036
+ * 9. Complete WebSocket upgrade
5037
+ * 10. Create OCPPServerClient
5038
+ */
5039
+ private _handleUpgrade;
5040
+ private _updateSessionActivity;
5041
+ close(options?: CloseOptions): Promise<void>;
5042
+ reconfigure(options: Partial<ServerOptions>): void;
5043
+ /**
5044
+ * Send a request to a specific client (local or remote).
5045
+ *
5046
+ * 1. Checks local clients.
5047
+ * 2. Checks Presence Registry -> Unicast.
5048
+ * 3. Fallback: Broadcast.
5049
+ */
5050
+ /**
5051
+ * Send a request to a specific client (local or remote).
5052
+ *
5053
+ * 1. Checks local clients.
5054
+ * 2. Checks Presence Registry -> Unicast.
5055
+ * 3. Fallback: Error (Client not found).
5056
+ */
5057
+ sendToClient<V extends OCPPProtocol, M extends AllMethodNames<V>>(identity: string, version: V, method: M, params: OCPPRequestType<V, M>, options?: CallOptions): Promise<OCPPResponseType<V, M> | undefined>;
5058
+ sendToClient<M extends AllMethodNames<any>>(identity: string, method: M, params: OCPPRequestType<any, M>, options?: CallOptions): Promise<OCPPResponseType<any, M> | undefined>;
5059
+ sendToClient<_T = any>(identity: string, method: string, params: Record<string, any>, options?: CallOptions): Promise<any | undefined>;
5060
+ safeSendToClient<V extends OCPPProtocol, M extends AllMethodNames<V>>(identity: string, version: V, method: M, params: OCPPRequestType<V, M>, options?: CallOptions): Promise<OCPPResponseType<V, M> | undefined>;
5061
+ safeSendToClient<M extends AllMethodNames<any>>(identity: string, method: M, params: OCPPRequestType<any, M>, options?: CallOptions): Promise<OCPPResponseType<any, M> | undefined>;
5062
+ safeSendToClient<_T = any>(identity: string, method: string, params: Record<string, any>, options?: CallOptions): Promise<any | undefined>;
5063
+ /**
5064
+ * Pipeline multiple calls to a single client into a concurrent batch.
5065
+ * Useful for reconnection warm-up (e.g. GetConfiguration, ChangeAvailability, etc.)
5066
+ * where sequential calls would add unnecessary round-trip latency.
5067
+ *
5068
+ * @param identity The client identity to send calls to
5069
+ * @param calls Array of { method, params, options? } to execute concurrently
5070
+ * @returns Array of results in the same order as the calls array.
5071
+ * Each element is the call result, or `undefined` if that individual call failed.
5072
+ *
5073
+ * @example
5074
+ * ```ts
5075
+ * const results = await server.sendBatch('CP-101', [
5076
+ * { method: 'GetConfiguration', params: { key: ['MeterInterval'] } },
5077
+ * { method: 'ChangeAvailability', params: { type: 'Operative' } },
5078
+ * { method: 'TriggerMessage', params: { requestedMessage: 'StatusNotification' } },
5079
+ * ]);
5080
+ * ```
5081
+ */
5082
+ sendBatch(identity: string, calls: Array<{
5083
+ method: string;
5084
+ params: Record<string, unknown>;
5085
+ options?: CallOptions;
5086
+ }>): Promise<Array<unknown | undefined>>;
5087
+ setAdapter(adapter: EventAdapterInterface): Promise<void>;
5088
+ private _onBroadcast;
5089
+ private _onUnicast;
5090
+ publish(channel: string, data: unknown): Promise<void>;
5091
+ broadcast<V extends AllMethodNames<any>>(method: V, params: OCPPRequestType<any, V>): Promise<void>;
5092
+ /**
5093
+ * Send a specific method & params to a list of specific clients efficiently.
5094
+ * This leverages adapter pipelining (e.g. Redis .pipeline()) to minimize network overhead
5095
+ * when communicating with thousands of nodes simultaneously.
5096
+ *
5097
+ * @param identities Array of target client identities
5098
+ * @param method The OCPP method to send
5099
+ * @param params The request parameters
5100
+ * @param options Call options
5101
+ */
5102
+ broadcastBatch<V extends AllMethodNames<any>>(identities: string[], method: V, params: OCPPRequestType<any, V>, options?: CallOptions): Promise<void>;
5103
+ private _buildCompressionConfig;
5104
+ }
5105
+
4735
5106
  interface ValidatorSchema {
4736
5107
  $schema?: string;
4737
5108
  $id?: string;
@@ -5068,6 +5439,26 @@ interface ClientOptions {
5068
5439
  * Oldest messages are dropped when exceeded. (default: 100)
5069
5440
  */
5070
5441
  offlineQueueMaxSize?: number;
5442
+ /**
5443
+ * Enable WebSocket `permessage-deflate` compression.
5444
+ * Reduces bandwidth by ~80% for JSON payloads at the cost of ~0.2ms CPU per message.
5445
+ * - `true` → sensible defaults (threshold: 1024, level: 6)
5446
+ * - `object` → fine-tuned configuration
5447
+ * (default: false)
5448
+ */
5449
+ compression?: boolean | CompressionOptions;
5450
+ }
5451
+ interface CompressionOptions {
5452
+ /** Minimum payload size in bytes to compress (default: 1024) */
5453
+ threshold?: number;
5454
+ /** zlib compression level 1 (fastest) to 9 (smallest) (default: 6) */
5455
+ level?: number;
5456
+ /** zlib memory level 1–9 (default: 8) */
5457
+ memLevel?: number;
5458
+ /** Server does not retain deflate context between messages (default: true — saves ~120KB/conn) */
5459
+ serverNoContextTakeover?: boolean;
5460
+ /** Client does not retain deflate context between messages (default: true) */
5461
+ clientNoContextTakeover?: boolean;
5071
5462
  }
5072
5463
  interface RateLimitOptions {
5073
5464
  /** Maximum number of messages allowed within the window */
@@ -5090,6 +5481,27 @@ interface RateLimitOptions {
5090
5481
  limit: number;
5091
5482
  windowMs: number;
5092
5483
  }>;
5484
+ /**
5485
+ * Enable adaptive rate limiting based on CPU/memory pressure.
5486
+ * When enabled, the token refill rate is automatically reduced under
5487
+ * high load and restored after a cooldown period. (default: false)
5488
+ */
5489
+ adaptive?: boolean;
5490
+ /**
5491
+ * CPU usage percent threshold to begin throttling.
5492
+ * Applies only when `adaptive` is true. (default: 80)
5493
+ */
5494
+ cpuThresholdPercent?: number;
5495
+ /**
5496
+ * Heap usage percent threshold to begin throttling.
5497
+ * Applies only when `adaptive` is true. (default: 85)
5498
+ */
5499
+ memThresholdPercent?: number;
5500
+ /**
5501
+ * Time (ms) both CPU and memory must stay below their thresholds
5502
+ * before restoring the original rate. (default: 5000)
5503
+ */
5504
+ cooldownMs?: number;
5093
5505
  }
5094
5506
  interface RouterConfig {
5095
5507
  /** Accepted OCPP subprotocols (e.g. ["ocpp1.6"]) */
@@ -5189,6 +5601,26 @@ interface ServerOptionsBase {
5189
5601
  * (default: 65536 / 64KB — sufficient for any standard OCPP message)
5190
5602
  */
5191
5603
  maxPayloadBytes?: number;
5604
+ /**
5605
+ * Enable worker thread pool for JSON parsing (+ optional AJV validation).
5606
+ * Offloads CPU-heavy work to worker threads, keeping the main event loop free.
5607
+ * Recommended for 10k+ concurrent connections. (default: false)
5608
+ *
5609
+ * - `true` → uses default pool size: `Math.max(2, os.cpus() - 2)`
5610
+ * - `{ poolSize, maxQueueSize }` → fine-tuned pool configuration
5611
+ */
5612
+ workerThreads?: boolean | {
5613
+ poolSize?: number;
5614
+ maxQueueSize?: number;
5615
+ };
5616
+ /**
5617
+ * Enable WebSocket `permessage-deflate` compression.
5618
+ * Reduces bandwidth by ~80% for JSON payloads at the cost of ~0.2ms CPU per message.
5619
+ * - `true` → sensible defaults (threshold: 1024, level: 6)
5620
+ * - `object` → fine-tuned configuration
5621
+ * (default: false)
5622
+ */
5623
+ compression?: boolean | CompressionOptions;
5192
5624
  }
5193
5625
  /** When strictMode is enabled, protocols MUST be specified */
5194
5626
  interface StrictServerOptions extends ServerOptionsBase {
@@ -5339,6 +5771,39 @@ interface EventAdapterInterface {
5339
5771
  }[]): Promise<void>;
5340
5772
  metrics?(): Promise<Record<string, unknown>>;
5341
5773
  }
5774
+ /**
5775
+ * Plugin interface for extending OCPPServer functionality.
5776
+ *
5777
+ * Plugins provide a unified way to hook into server lifecycle events
5778
+ * without modifying core internals. Useful for:
5779
+ * - Observability (OpenTelemetry, Prometheus)
5780
+ * - Custom adapters and integrations
5781
+ * - Auditing and compliance
5782
+ *
5783
+ * @example
5784
+ * ```ts
5785
+ * const myPlugin: OCPPPlugin = {
5786
+ * name: 'my-plugin',
5787
+ * onInit(server) { console.log('Plugin initialized'); },
5788
+ * onConnection(client) { console.log(`${client.identity} connected`); },
5789
+ * onDisconnect(client) { console.log(`${client.identity} disconnected`); },
5790
+ * onClose() { console.log('Server shutting down'); },
5791
+ * };
5792
+ * server.plugin(myPlugin);
5793
+ * ```
5794
+ */
5795
+ interface OCPPPlugin {
5796
+ /** Unique plugin name (used for logging and deduplication) */
5797
+ name: string;
5798
+ /** Called when the plugin is registered via server.plugin(plugin) */
5799
+ onInit?(server: OCPPServer): void | Promise<void>;
5800
+ /** Called for each new client connection after auth succeeds */
5801
+ onConnection?(client: OCPPServerClient): void | Promise<void>;
5802
+ /** Called when a client disconnects */
5803
+ onDisconnect?(client: OCPPServerClient, code: number, reason: string): void;
5804
+ /** Called during server.close() for plugin cleanup */
5805
+ onClose?(): void | Promise<void>;
5806
+ }
5342
5807
  declare const NOREPLY: unique symbol;
5343
5808
  type MiddlewareContext = {
5344
5809
  type: "incoming_call";
@@ -5384,90 +5849,4 @@ interface AuthContext<TSession = Record<string, unknown>> extends BaseConnection
5384
5849
  }
5385
5850
  type ConnectionMiddleware = (ctx: ConnectionContext) => Promise<void> | void;
5386
5851
 
5387
- interface RedisLikeClient {
5388
- publish(channel: string, message: string): Promise<number | unknown | undefined>;
5389
- subscribe(channel: string, ...args: unknown[]): Promise<unknown | undefined>;
5390
- unsubscribe(channel: string, ...args: unknown[]): Promise<unknown | undefined>;
5391
- on?(event: "message", callback: (channel: string, message: string) => void): unknown;
5392
- disconnect?(): Promise<void> | void;
5393
- quit?(): Promise<unknown> | undefined;
5394
- isOpen?: boolean;
5395
- }
5396
-
5397
- interface RedisAdapterOptions {
5398
- /** Redis client for publishing */
5399
- pubClient: RedisLikeClient;
5400
- /** Redis client for subscribing (must be a separate connection) */
5401
- subClient: RedisLikeClient;
5402
- /** Redis client for blocking stream operations (recommended for reliability) */
5403
- blockingClient?: RedisLikeClient;
5404
- /** Optional key prefix for channels (default: 'ocpp-ws-io:') */
5405
- prefix?: string;
5406
- /** StreamMaxLen for trimming (default: 1000) */
5407
- streamMaxLen?: number;
5408
- /**
5409
- * TTL in seconds for ephemeral stream keys (default: 300).
5410
- * Prevents abandoned channel keys from leaking memory in Redis.
5411
- */
5412
- streamTtlSeconds?: number;
5413
- /**
5414
- * Presence TTL in seconds (default: 300).
5415
- * Used for batch presence heartbeat pipeline.
5416
- */
5417
- presenceTtlSeconds?: number;
5418
- }
5419
- /**
5420
- * Redis adapter for cross-process event distribution.
5421
- *
5422
- * Supports `ioredis` and `node-redis` (v4+).
5423
- * Uses Redis Streams for reliable unicast (node-to-node) and Pub/Sub for broadcast.
5424
- */
5425
- declare class RedisAdapter implements EventAdapterInterface {
5426
- private _driver;
5427
- private _prefix;
5428
- private _streamMaxLen;
5429
- private _streamTtlSeconds;
5430
- private _presenceTtlSeconds;
5431
- private _handlers;
5432
- private _streamOffsets;
5433
- private _streams;
5434
- private _polling;
5435
- private _closed;
5436
- private _sequenceCounters;
5437
- private _unsubError?;
5438
- private _unsubReconnect?;
5439
- private _presenceCache;
5440
- constructor(options: RedisAdapterOptions);
5441
- publish(channel: string, data: unknown): Promise<void>;
5442
- publishBatch(messages: {
5443
- channel: string;
5444
- data: unknown;
5445
- }[]): Promise<void>;
5446
- subscribe(channel: string, handler: (data: unknown) => void): Promise<void>;
5447
- unsubscribe(channel: string): Promise<void>;
5448
- disconnect(): Promise<void>;
5449
- private _handleMessage;
5450
- private _ensurePolling;
5451
- private _pollLoop;
5452
- setPresence(identity: string, nodeId: string, ttl: number): Promise<void>;
5453
- getPresence(identity: string): Promise<string | null>;
5454
- getPresenceBatch(identities: string[]): Promise<(string | null)[]>;
5455
- removePresence(identity: string): Promise<void>;
5456
- metrics(): Promise<Record<string, unknown>>;
5457
- /**
5458
- * Set multiple presence entries in a single Redis pipeline.
5459
- * Reduces N network round-trips to 1 for bulk presence updates.
5460
- */
5461
- setPresenceBatch(entries: {
5462
- identity: string;
5463
- nodeId: string;
5464
- ttl?: number;
5465
- }[]): Promise<void>;
5466
- /**
5467
- * Re-syncs all cached presence entries to Redis after a reconnection.
5468
- * Called automatically when the Redis client reconnects.
5469
- */
5470
- private _rehydratePresence;
5471
- }
5472
-
5473
- export { createValidator as $, type AuthCallback as A, MiddlewareStack as B, type ConnectionMiddleware as C, type OCPP16Methods as D, type EventAdapterInterface as E, type OCPP201Methods as F, type OCPP21Methods as G, type HandlerContext as H, type OCPPCall as I, type OCPPCallError as J, type OCPPCallResult as K, type LoggerLike as L, type MiddlewareFunction as M, NOREPLY as N, type OCPPProtocol as O, OCPPClient as P, type OCPPMessage as Q, type RouterConfig as R, type ServerEvents as S, type TypedEventEmitter as T, type OCPPMethodMap as U, Validator as V, type OCPPProtocolKey as W, RedisAdapter as X, SecurityProfile as Y, type SessionData as Z, type WildcardHandler as _, type LoggingConfig as a, type RedisAdapterOptions as a0, type MiddlewareContext as b, type CORSOptions as c, type AllMethodNames as d, type RouterHandlerContext as e, type OCPPRequestType as f, type OCPPResponseType as g, type RouterWildcardHandler as h, type ServerOptions as i, type LoggerLikeNotOptional as j, OCPPServerClient as k, type OCPPServerStats as l, type ListenOptions as m, type TLSOptions as n, type CloseOptions as o, type CallOptions as p, type AnyOCPPProtocol as q, type AuthAccept as r, type CallHandler as s, type ClientEvents as t, type ClientOptions as u, type ConnectionContext as v, ConnectionState as w, type HandshakeInfo as x, MessageType as y, type MiddlewareNext as z };
5852
+ export { createRouter as $, type AuthCallback as A, type OCPPMessage as B, type ConnectionMiddleware as C, type OCPPMethodMap as D, type EventAdapterInterface as E, type OCPPProtocol as F, type OCPPProtocolKey as G, type HandlerContext as H, type OCPPRequestType as I, type OCPPResponseType as J, OCPPRouter as K, type LoggerLike as L, type MiddlewareFunction as M, NOREPLY as N, type OCPPPlugin as O, OCPPServer as P, OCPPServerClient as Q, type RateLimitOptions as R, type RouterConfig as S, SecurityProfile as T, type ServerEvents as U, Validator as V, type ServerOptions as W, type SessionData as X, type TLSOptions as Y, type TypedEventEmitter as Z, type WildcardHandler as _, type LoggingConfig as a, createValidator as a0, type MiddlewareContext as b, type AllMethodNames as c, type AnyOCPPProtocol as d, type AuthAccept as e, type CORSOptions as f, type CallHandler as g, type CallOptions as h, type ClientEvents as i, type ClientOptions as j, type CloseOptions as k, type CompressionOptions as l, type ConnectionContext as m, ConnectionState as n, type HandshakeInfo as o, type ListenOptions as p, MessageType as q, type MiddlewareNext as r, MiddlewareStack as s, type OCPP16Methods as t, type OCPP201Methods as u, type OCPP21Methods as v, type OCPPCall as w, type OCPPCallError as x, type OCPPCallResult as y, OCPPClient as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocpp-ws-io",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "description": "Type-safe OCPP 1.6 & 2.0.1 WebSocket RPC client & server. Build EV charging CSMS platforms with Redis clustering, strict validation, and DDoS protection.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,6 +29,11 @@
29
29
  "types": "./dist/logger.d.ts",
30
30
  "import": "./dist/logger.mjs",
31
31
  "require": "./dist/logger.js"
32
+ },
33
+ "./plugins": {
34
+ "types": "./dist/plugins.d.ts",
35
+ "import": "./dist/plugins.mjs",
36
+ "require": "./dist/plugins.js"
32
37
  }
33
38
  },
34
39
  "scripts": {
@@ -79,8 +84,7 @@
79
84
  "url": "https://github.com/rohittiwari-dev/ocpp-ws-io/issues"
80
85
  },
81
86
  "files": [
82
- "dist",
83
- "assets"
87
+ "dist"
84
88
  ],
85
89
  "license": "MIT",
86
90
  "dependencies": {