From 564c71aaefd5ea7307828264c7c43026a256545a Mon Sep 17 00:00:00 2001 From: Exelo Date: Sat, 7 Mar 2026 08:35:07 +0100 Subject: [PATCH] feat: add loader given env to config registry --- e2e/game/client/main.ts | 15 +++--- example/pong-network/client/main.ts | 11 ++++- example/pong-network/server/main.ts | 6 ++- .../contexts/executions/init.context.ts | 6 +++ .../common/src/options/types/options.type.ts | 2 + packages/config/README.md | 46 +++++++++++++------ packages/config/src/index.ts | 1 + .../src/transformers/boolean.transformer.ts | 9 ++++ packages/config/src/transformers/index.ts | 1 + .../application/application-options.type.ts | 1 - .../src/application/nanoforge-application.ts | 1 - packages/core/src/core/core.ts | 2 +- .../src/client.network.library.ts | 22 ++++----- .../src/config.client.network.ts | 12 +++-- .../test/client.network.library.spec.ts | 26 +++++------ .../src/config.server.network.ts | 12 ++--- .../src/server.network.library.ts | 32 ++++++------- .../test/server.network.library.spec.ts | 30 ++++++------ 18 files changed, 143 insertions(+), 92 deletions(-) create mode 100644 packages/config/src/transformers/boolean.transformer.ts create mode 100644 packages/config/src/transformers/index.ts diff --git a/e2e/game/client/main.ts b/e2e/game/client/main.ts index 1e9ca74d..0f2057d1 100644 --- a/e2e/game/client/main.ts +++ b/e2e/game/client/main.ts @@ -14,11 +14,6 @@ import { exampleSystem } from "./systems/example.system"; export async function main(options: IRunOptions) { const app = NanoforgeFactory.createClient({ tickRate: 60, - environment: { - serverAddress: "127.0.0.1", - serverTcpPort: "4445", - serverUdpPort: "4444", - }, }); const assetManager = new AssetManagerLibrary(); @@ -37,7 +32,15 @@ export async function main(options: IRunOptions) { app.useNetwork(network); app.use(Symbol("music"), music); - await app.init(options); + await app.init({ + ...options, + env: { + ...options.env, + SERVER_ADDRESS: "127.0.0.1", + SERVER_TCP_PORT: "4445", + SERVER_UDP_PORT: "4444", + }, + }); const registry = ecs.registry; diff --git a/example/pong-network/client/main.ts b/example/pong-network/client/main.ts index 07b4f8b4..427482e2 100644 --- a/example/pong-network/client/main.ts +++ b/example/pong-network/client/main.ts @@ -12,7 +12,6 @@ import { controlPlayer, draw, move, packetHandler } from "./systems"; export const app = NanoforgeFactory.createClient({ tickRate: 60, - environment: { serverTcpPort: "4445", serverUdpPort: "4444", serverAddress: "127.0.0.1" }, }); export const layer = new Layer(); @@ -30,7 +29,15 @@ export const main = async (options: IRunOptions) => { app.useAssetManager(assetManager); app.useInput(input); - await app.init(options); + await app.init({ + ...options, + env: { + ...options.env, + SERVER_TCP_PORT: "4445", + SERVER_UDP_PORT: "4444", + SERVER_ADDRESS: "127.0.0.1", + }, + }); const registry = ecsLibrary.registry; diff --git a/example/pong-network/server/main.ts b/example/pong-network/server/main.ts index ed6e3451..fe4f024d 100644 --- a/example/pong-network/server/main.ts +++ b/example/pong-network/server/main.ts @@ -9,7 +9,6 @@ import { bounce, move, packetHandler } from "./systems"; export const app = NanoforgeFactory.createServer({ tickRate: 60, - environment: { listeningTcpPort: "4445", listeningUdpPort: "4444" }, }); export const main = async (options: IRunOptions) => { @@ -21,7 +20,10 @@ export const main = async (options: IRunOptions) => { app.useNetwork(network); app.useAssetManager(assetManager); - await app.init(options); + await app.init({ + ...options, + env: { ...options.env, LISTENING_TCP_PORT: "4445", LISTENING_UDP_PORT: "4444" }, + }); const registry = ecsLibrary.registry; diff --git a/packages/common/src/context/contexts/executions/init.context.ts b/packages/common/src/context/contexts/executions/init.context.ts index ad85de82..ae356788 100644 --- a/packages/common/src/context/contexts/executions/init.context.ts +++ b/packages/common/src/context/contexts/executions/init.context.ts @@ -6,6 +6,7 @@ import { BaseContext } from "./base.context"; export class InitContext extends BaseContext { private readonly _canvas: IRunClientOptions["canvas"] | undefined; private readonly _files: IRunOptions["files"]; + private readonly _env: Record; private readonly _config: IConfigRegistry; constructor( @@ -18,6 +19,7 @@ export class InitContext extends BaseContext { this._canvas = (options as IRunClientOptions)["canvas"]; this._files = options.files; + this._env = options.env; this._config = configRegistry; } @@ -29,6 +31,10 @@ export class InitContext extends BaseContext { return this._files; } + get env(): Record { + return this._env; + } + get config(): IConfigRegistry { return this._config; } diff --git a/packages/common/src/options/types/options.type.ts b/packages/common/src/options/types/options.type.ts index 9a5d967b..3ed47674 100644 --- a/packages/common/src/options/types/options.type.ts +++ b/packages/common/src/options/types/options.type.ts @@ -3,8 +3,10 @@ export type IRunOptions = IRunClientOptions | IRunServerOptions; export interface IRunClientOptions { canvas: HTMLCanvasElement; files: Map; + env: Record; } export interface IRunServerOptions { files: Map; + env: Record; } diff --git a/packages/config/README.md b/packages/config/README.md index 0ee4bd7f..b48191ce 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -31,7 +31,25 @@ bun add @nanoforge-dev/config ## Warning -This library is of exclusive usage for other libraries. To put variables in the environment to allow libraries to use it through this config library, you must put it in factory options : +This library is of exclusive usage for other libraries. To put variables in the environment to allow libraries to use it through this config library, you must put it in `.env` file at the root of your project (or in your environment) : + +```dotenv +NANOFORGE_CLIENT_SERVER_TCP_PORT=4445 +NANOFORGE_CLIENT_SERVER_UDP_PORT=4444 +NANOFORGE_CLIENT_SERVER_ADDRESS=127.0.0.1 +``` + +You must prefix your variables according to this table : + +| Prefix | Availability | +| :------------------ | :----------------------- | +| `NANOFORGE_CLIENT_` | Available in client only | +| `NANOFORGE_SERVER_` | Available in server only | +| `NANOFORGE_` | Available in both apps | + +⚠️ Prefixs are removed in libs + +You can also put it in init options (every value must be string or undefined) : ```ts import { type IRunOptions } from "@nanoforge-dev/common"; @@ -39,19 +57,21 @@ import { NanoforgeFactory } from "@nanoforge-dev/core"; import { NetworkLibrary } from "@nanoforge-dev/network"; export async function main(options: IRunOptions) { - const app = NanoforgeFactory.createClient({ - environment: { - serverTcpPort: "4445", - serverUdpPort: "4444", - serverAddress: "127.0.0.1", - }, - }); + const app = NanoforgeFactory.createClient(); const network = new NetworkLibrary(); app.useNetwork(network); - await app.init(options); + await app.init({ + ...options, + env: { + ...options.env, + SERVER_TCP_PORT: "4445", + SERVER_UDP_PORT: "4444", + SERVER_ADDRESS: "127.0.0.1", + }, + }); await app.run(); } @@ -92,23 +112,23 @@ export class ClientConfigNetwork { @Expose() @IsOptional() @IsPort() - serverTcpPort?: string; + SERVER_TCP_PORT?: string; @Expose() @IsOptional() @IsPort() - serverUdpPort?: string; + SERVER_UDP_PORT?: string; // This var must be ip address or fqdn (it cannot be undefined) @Expose() @IsIpOrFQDN() - serverAddress?: string; + SERVER_ADDRESS?: string; // This var must be a byte length between 2 and 64. It can be undefined as it as a default value. @Expose() @Default("PACKET_END") @IsByteLength(2, 64) - magicValue!: string; + MAGIC_VALUE!: string; } ``` diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 9ae754b9..eccc35fa 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,3 +1,4 @@ export * from "./exports"; export * from "./default"; +export * from "./transformers"; export * from "./validators"; diff --git a/packages/config/src/transformers/boolean.transformer.ts b/packages/config/src/transformers/boolean.transformer.ts new file mode 100644 index 00000000..fad1a052 --- /dev/null +++ b/packages/config/src/transformers/boolean.transformer.ts @@ -0,0 +1,9 @@ +import { Transform } from "class-transformer"; + +const transformStringToBoolean = ({ value }: { value: string }) => { + if (value === "true") return true; + if (value === "false") return false; + return undefined; +}; + +export const TransformToBoolean = () => Transform(transformStringToBoolean); diff --git a/packages/config/src/transformers/index.ts b/packages/config/src/transformers/index.ts new file mode 100644 index 00000000..77363e3a --- /dev/null +++ b/packages/config/src/transformers/index.ts @@ -0,0 +1 @@ +export { TransformToBoolean } from "./boolean.transformer"; diff --git a/packages/core/src/application/application-options.type.ts b/packages/core/src/application/application-options.type.ts index 41a53830..57ecc833 100644 --- a/packages/core/src/application/application-options.type.ts +++ b/packages/core/src/application/application-options.type.ts @@ -1,4 +1,3 @@ export interface IApplicationOptions { tickRate: number; - environment: Record; } diff --git a/packages/core/src/application/nanoforge-application.ts b/packages/core/src/application/nanoforge-application.ts index 04d0ce22..7a5fa520 100644 --- a/packages/core/src/application/nanoforge-application.ts +++ b/packages/core/src/application/nanoforge-application.ts @@ -22,7 +22,6 @@ export abstract class NanoforgeApplication { this._options = { tickRate: 60, - environment: {}, ...(options ?? {}), }; } diff --git a/packages/core/src/core/core.ts b/packages/core/src/core/core.ts index 78ad4986..1badedcf 100644 --- a/packages/core/src/core/core.ts +++ b/packages/core/src/core/core.ts @@ -30,7 +30,7 @@ export class Core { public async init(options: IRunOptions, appOptions: IApplicationOptions): Promise { this.options = appOptions; - this._configRegistry = new ConfigRegistry(appOptions.environment); + this._configRegistry = new ConfigRegistry(options.env); await this.runInit(this.getInitContext(options)); } diff --git a/packages/network-client/src/client.network.library.ts b/packages/network-client/src/client.network.library.ts index 3afbd03f..76c98189 100644 --- a/packages/network-client/src/client.network.library.ts +++ b/packages/network-client/src/client.network.library.ts @@ -18,26 +18,26 @@ export class NetworkClientLibrary extends BaseNetworkLibrary { public override async __init(context: InitContext): Promise { const config: ClientConfigNetwork = await context.config.registerConfig(ClientConfigNetwork); - if (config.serverTcpPort === undefined && config.serverUdpPort === undefined) { + if (config.SERVER_TCP_PORT === undefined && config.SERVER_UDP_PORT === undefined) { throw new NfConfigException("No server port specified to connect", this.__name); } - if (config.serverTcpPort !== undefined) { + if (config.SERVER_TCP_PORT !== undefined) { this.tcp = new TCPClient( - +config.serverTcpPort, - config.serverAddress, - config.magicValue, - config.wss, + +config.SERVER_TCP_PORT, + config.SERVER_ADDRESS, + config.MAGIC_VALUE, + config.WSS, ); await this.tcp.connect(); } - if (config.serverUdpPort !== undefined) { + if (config.SERVER_UDP_PORT !== undefined) { this.udp = new UDPClient( - +config.serverUdpPort, - config.serverAddress, - config.magicValue, - config.wss, + +config.SERVER_UDP_PORT, + config.SERVER_ADDRESS, + config.MAGIC_VALUE, + config.WSS, ); await this.udp.connect(); } diff --git a/packages/network-client/src/config.client.network.ts b/packages/network-client/src/config.client.network.ts index 31b9baa4..cbbf1044 100644 --- a/packages/network-client/src/config.client.network.ts +++ b/packages/network-client/src/config.client.network.ts @@ -6,30 +6,32 @@ import { IsIpOrFQDN, IsOptional, IsPort, + TransformToBoolean, } from "@nanoforge-dev/config"; export class ClientConfigNetwork { @Expose() @IsOptional() @IsPort() - serverTcpPort?: string; + SERVER_TCP_PORT?: string; @Expose() @IsOptional() @IsPort() - serverUdpPort?: string; + SERVER_UDP_PORT?: string; @Expose() @IsIpOrFQDN() - serverAddress!: string; + SERVER_ADDRESS!: string; @Expose() @Default("PACKET_END") @IsByteLength(2, 64) - magicValue!: string; + MAGIC_VALUE!: string; @Expose() + @TransformToBoolean() @IsBoolean() @Default(false) - wss!: boolean; + WSS!: boolean; } diff --git a/packages/network-client/test/client.network.library.spec.ts b/packages/network-client/test/client.network.library.spec.ts index 57bbf971..cede52a9 100644 --- a/packages/network-client/test/client.network.library.spec.ts +++ b/packages/network-client/test/client.network.library.spec.ts @@ -60,17 +60,17 @@ describe("NetworkClientLibrary", () => { describe("config validation", () => { it("should throw NfConfigException when neither TCP nor UDP port is provided", async () => { - const ctx = makeContext({ serverAddress: "127.0.0.1", magicValue: "END" }); + const ctx = makeContext({ SERVER_ADDRESS: "127.0.0.1", MAGIC_VALUE: "END" }); await expect(new NetworkClientLibrary().__init(ctx)).rejects.toThrow(NfConfigException); }); }); describe("initialization", () => { - it("should initialize a TCP client when only serverTcpPort is provided", async () => { + it("should initialize a TCP client when only SERVER_TCP_PORT is provided", async () => { const ctx = makeContext({ - serverTcpPort: "8080", - serverAddress: "127.0.0.1", - magicValue: "END", + SERVER_TCP_PORT: "8080", + SERVER_ADDRESS: "127.0.0.1", + MAGIC_VALUE: "END", }); const lib = new NetworkClientLibrary(); await lib.__init(ctx); @@ -78,11 +78,11 @@ describe("NetworkClientLibrary", () => { expect(lib.udp).toBeUndefined(); }); - it("should initialize a UDP client when only serverUdpPort is provided", async () => { + it("should initialize a UDP client when only SERVER_UDP_PORT is provided", async () => { const ctx = makeContext({ - serverUdpPort: "8081", - serverAddress: "127.0.0.1", - magicValue: "END", + SERVER_UDP_PORT: "8081", + SERVER_ADDRESS: "127.0.0.1", + MAGIC_VALUE: "END", }); const lib = new NetworkClientLibrary(); await lib.__init(ctx); @@ -92,10 +92,10 @@ describe("NetworkClientLibrary", () => { it("should initialize both TCP and UDP clients when both ports are provided", async () => { const ctx = makeContext({ - serverTcpPort: "8080", - serverUdpPort: "8081", - serverAddress: "127.0.0.1", - magicValue: "END", + SERVER_TCP_PORT: "8080", + SERVER_UDP_PORT: "8081", + SERVER_ADDRESS: "127.0.0.1", + MAGIC_VALUE: "END", }); const lib = new NetworkClientLibrary(); await lib.__init(ctx); diff --git a/packages/network-server/src/config.server.network.ts b/packages/network-server/src/config.server.network.ts index 2e2f9d33..13923997 100644 --- a/packages/network-server/src/config.server.network.ts +++ b/packages/network-server/src/config.server.network.ts @@ -12,30 +12,30 @@ export class ServerConfigNetwork { @Expose() @IsOptional() @IsPort() - listeningUdpPort?: string; + LISTENING_TCP_PORT?: string; @Expose() @IsOptional() @IsPort() - listeningTcpPort?: string; + LISTENING_UDP_PORT?: string; @Expose() @Default("0.0.0.0") @IsIpOrFQDN() - listeningInterface!: string; + LISTENING_INTERFACE!: string; @Expose() @Default("PACKET_END") @IsByteLength(2, 64) - magicValue!: string; + MAGIC_VALUE!: string; @Expose() @IsString() @IsOptional() - cert?: string; + WSS_CERT?: string; @Expose() @IsString() @IsOptional() - key?: string; + WSS_KEY?: string; } diff --git a/packages/network-server/src/server.network.library.ts b/packages/network-server/src/server.network.library.ts index a7d76ff9..c7514b84 100644 --- a/packages/network-server/src/server.network.library.ts +++ b/packages/network-server/src/server.network.library.ts @@ -18,38 +18,38 @@ export class NetworkServerLibrary extends BaseNetworkLibrary { public override async __init(context: InitContext): Promise { const config: ServerConfigNetwork = await context.config.registerConfig(ServerConfigNetwork); - if (config.listeningInterface === undefined) { + if (config.LISTENING_INTERFACE === undefined) { throw new NfConfigException("No listenning address provided", this.__name); } - if (config.listeningUdpPort === undefined && config.listeningTcpPort === undefined) { + if (config.LISTENING_TCP_PORT === undefined && config.LISTENING_UDP_PORT === undefined) { throw new NfConfigException("No listenning port specified", this.__name); } if ( - (config.cert !== undefined && config.key === undefined) || - (config.cert === undefined && config.key !== undefined) + (config.WSS_CERT !== undefined && config.WSS_KEY === undefined) || + (config.WSS_CERT === undefined && config.WSS_KEY !== undefined) ) { throw new NfConfigException("Both cert and key must be provided together", this.__name); } - if (config.listeningTcpPort !== undefined) { + if (config.LISTENING_TCP_PORT !== undefined) { this.tcp = new TCPServer( - +config.listeningTcpPort, - config.listeningInterface, - config.magicValue, - config.cert, - config.key, + +config.LISTENING_TCP_PORT, + config.LISTENING_INTERFACE, + config.MAGIC_VALUE, + config.WSS_CERT, + config.WSS_KEY, ); this.tcp.listen(); } - if (config.listeningUdpPort !== undefined) { + if (config.LISTENING_UDP_PORT !== undefined) { this.udp = new UDPServer( - +config.listeningUdpPort, - config.listeningInterface, - config.magicValue, - config.cert, - config.key, + +config.LISTENING_UDP_PORT, + config.LISTENING_INTERFACE, + config.MAGIC_VALUE, + config.WSS_CERT, + config.WSS_KEY, ); this.udp.listen(); } diff --git a/packages/network-server/test/server.network.library.spec.ts b/packages/network-server/test/server.network.library.spec.ts index 77d1848a..4a0f1a84 100644 --- a/packages/network-server/test/server.network.library.spec.ts +++ b/packages/network-server/test/server.network.library.spec.ts @@ -37,23 +37,23 @@ describe("NetworkServerLibrary", () => { }); describe("config validation", () => { - it("should throw NfConfigException when listeningInterface is not provided", async () => { - const ctx = makeContext({ listeningTcpPort: "9000", magicValue: "END" }); + it("should throw NfConfigException when LISTENING_INTERFACE is not provided", async () => { + const ctx = makeContext({ LISTENING_TCP_PORT: "9000", MAGIC_VALUE: "END" }); await expect(new NetworkServerLibrary().__init(ctx)).rejects.toThrow(NfConfigException); }); it("should throw NfConfigException when neither TCP nor UDP port is provided", async () => { - const ctx = makeContext({ listeningInterface: "0.0.0.0", magicValue: "END" }); + const ctx = makeContext({ LISTENING_INTERFACE: "0.0.0.0", MAGIC_VALUE: "END" }); await expect(new NetworkServerLibrary().__init(ctx)).rejects.toThrow(NfConfigException); }); }); describe("initialization", () => { - it("should initialize a TCP server when only listeningTcpPort is provided", async () => { + it("should initialize a TCP server when only LISTENING_TCP_PORT is provided", async () => { const ctx = makeContext({ - listeningTcpPort: "9000", - listeningInterface: "0.0.0.0", - magicValue: "END", + LISTENING_TCP_PORT: "9000", + LISTENING_INTERFACE: "0.0.0.0", + MAGIC_VALUE: "END", }); const lib = new NetworkServerLibrary(); await lib.__init(ctx); @@ -61,11 +61,11 @@ describe("NetworkServerLibrary", () => { expect(lib.udp).toBeUndefined(); }); - it("should initialize a UDP server when only listeningUdpPort is provided", async () => { + it("should initialize a UDP server when only LISTENING_UDP_PORT is provided", async () => { const ctx = makeContext({ - listeningUdpPort: "9001", - listeningInterface: "0.0.0.0", - magicValue: "END", + LISTENING_UDP_PORT: "9001", + LISTENING_INTERFACE: "0.0.0.0", + MAGIC_VALUE: "END", }); const lib = new NetworkServerLibrary(); await lib.__init(ctx); @@ -75,10 +75,10 @@ describe("NetworkServerLibrary", () => { it("should initialize both TCP and UDP servers when both ports are provided", async () => { const ctx = makeContext({ - listeningTcpPort: "9000", - listeningUdpPort: "9001", - listeningInterface: "0.0.0.0", - magicValue: "END", + LISTENING_TCP_PORT: "9000", + LISTENING_UDP_PORT: "9001", + LISTENING_INTERFACE: "0.0.0.0", + MAGIC_VALUE: "END", }); const lib = new NetworkServerLibrary(); await lib.__init(ctx);