Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/legal-windows-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@webiny/stdlib": patch
---

Add `createProcessEnv()` and `createBrowserEnv()` factory functions for using Env implementations outside of DI.
4 changes: 2 additions & 2 deletions __tests__/browser/Env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { ConsoleLoggerFeature } from "../../src/common/features/Logger/feature.j
import { Env } from "../../src/common/features/Env/index.js";
import { BrowserEnvFeature } from "../../src/browser/features/BrowserEnv/index.js";

function makeContainer(vars?: Record<string, string>): Container {
function makeContainer(vars: Record<string, string> = {}): Container {
const container = new Container();
container.registerInstance(ConsoleLoggerConfig, {
getConfig: () => ({ logLevel: "error" as const })
});
ConsoleLoggerFeature.register(container);
BrowserEnvFeature.register(container, vars ? { variables: vars } : undefined);
BrowserEnvFeature.register(container, { variables: vars });
return container;
}

Expand Down
8 changes: 8 additions & 0 deletions src/browser/features/BrowserEnv/BrowserEnv.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { Env } from "~/common/features/Env/abstractions/Env.js";
import { toBoolean } from "~/common/utils/boolean/index.js";

export interface CreateBrowserEnvParams {
variables: Record<string, string>;
}

export class BrowserEnv implements Env.Interface {
private readonly variables: Record<string, string>;

Expand Down Expand Up @@ -70,3 +74,7 @@ export class BrowserEnv implements Env.Interface {
return toBoolean(raw);
}
}

export function createBrowserEnv(params: CreateBrowserEnvParams): Env.Interface {
return new BrowserEnv(params.variables);
}
10 changes: 10 additions & 0 deletions src/browser/features/BrowserEnv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ BrowserEnvFeature.register(container, {
});
const env = container.resolve(Env);
```

### Without DI

```ts
import { createBrowserEnv } from "@webiny/stdlib/browser";

const env = createBrowserEnv({
variables: { API_URL: "https://api.example.com" }
});
```
6 changes: 3 additions & 3 deletions src/browser/features/BrowserEnv/feature.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { createFeature } from "~/common/index.js";
import { Env } from "~/common/features/Env/abstractions/Env.js";
import { BrowserEnv } from "./BrowserEnv.js";
import { createBrowserEnv } from "./BrowserEnv.js";

export interface BrowserEnvFeatureParams {
variables: Record<string, string>;
}

export const BrowserEnvFeature = createFeature<BrowserEnvFeatureParams | undefined>({
export const BrowserEnvFeature = createFeature<BrowserEnvFeatureParams>({
name: "Browser/BrowserEnvFeature",
register(container, params) {
container.registerInstance(Env, new BrowserEnv(params?.variables || {}));
container.registerInstance(Env, createBrowserEnv(params));
}
});
1 change: 1 addition & 0 deletions src/browser/features/BrowserEnv/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { BrowserEnvFeature } from "./feature.js";
export { createBrowserEnv, type CreateBrowserEnvParams } from "./BrowserEnv.js";
6 changes: 5 additions & 1 deletion src/browser/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export { BrowserEnvFeature } from "./features/BrowserEnv/index.js";
export {
BrowserEnvFeature,
createBrowserEnv,
type CreateBrowserEnvParams
} from "./features/BrowserEnv/index.js";
export {
LocalStorageCacheFeature,
createLocalStorageCache,
Expand Down
29 changes: 19 additions & 10 deletions src/node/features/ProcessEnv/ProcessEnv.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import { Env as EnvAbstraction } from "~/common/features/Env/abstractions/Env.js";
import { toBoolean } from "~/common/utils/boolean/index.js";

export interface CreateProcessEnvParams {
variables?: Record<string, string | undefined>;
}

class ProcessEnvImpl implements EnvAbstraction.Interface {
private readonly variables: Record<string, string | undefined>;

public constructor(variables: Record<string, string | undefined>) {
this.variables = variables;
}

getString(key: string): string | undefined;
getString(key: string, defaultValue: string): string;
getString(key: string, defaultValue?: string): string | undefined {
const value = process.env[key];
const value = this.variables[key];
if (value === undefined) {
return defaultValue;
}
return value;
}

getStringOrThrow(key: string): string {
const value = process.env[key];
const value = this.variables[key];
if (value === undefined) {
throw new Error(`Environment variable "${key}" is not set.`);
}
Expand All @@ -23,7 +33,7 @@ class ProcessEnvImpl implements EnvAbstraction.Interface {
getNumber(key: string): number | undefined;
getNumber(key: string, defaultValue: number): number;
getNumber(key: string, defaultValue?: number): number | undefined {
const raw = process.env[key];
const raw = this.variables[key];
if (raw === undefined || raw === "") {
return defaultValue;
}
Expand All @@ -35,7 +45,7 @@ class ProcessEnvImpl implements EnvAbstraction.Interface {
}

getNumberOrThrow(key: string): number {
const raw = process.env[key];
const raw = this.variables[key];
if (raw === undefined || raw === "") {
throw new Error(`Environment variable "${key}" is not set.`);
}
Expand All @@ -49,23 +59,22 @@ class ProcessEnvImpl implements EnvAbstraction.Interface {
getBoolean(key: string): boolean | undefined;
getBoolean(key: string, defaultValue: boolean): boolean;
getBoolean(key: string, defaultValue?: boolean): boolean | undefined {
const raw = process.env[key];
const raw = this.variables[key];
if (raw === undefined) {
return defaultValue;
}
return toBoolean(raw);
}

getBooleanOrThrow(key: string): boolean {
const raw = process.env[key];
const raw = this.variables[key];
if (raw === undefined) {
throw new Error(`Environment variable "${key}" is not set.`);
}
return toBoolean(raw);
}
}

export const ProcessEnv = EnvAbstraction.createImplementation({
implementation: ProcessEnvImpl,
dependencies: []
});
export function createProcessEnv(params?: CreateProcessEnvParams): EnvAbstraction.Interface {
return new ProcessEnvImpl(params?.variables ?? process.env);
}
24 changes: 23 additions & 1 deletion src/node/features/ProcessEnv/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ProcessEnv

Node.js implementation of the `Env` abstraction, backed by `process.env`. Provides typed access to environment variables with `getString`, `getNumber`, and `getBoolean` families.
Node.js implementation of the `Env` abstraction, backed by `process.env` by default. Accepts an optional `variables` record to override the source. Provides typed access to environment variables with `getString`, `getNumber`, and `getBoolean` families.

See [Env README](../../common/features/Env/README.md) for the full interface and usage.

Expand All @@ -17,3 +17,25 @@ const env = container.resolve(Env);

const port = env.getNumber("PORT", 3000);
```

### With custom variables (DI)

```ts
ProcessEnvFeature.register(container, {
variables: { ...process.env, MY_VAR: "overridden" }
});
```

### Without DI

```ts
import { createProcessEnv } from "@webiny/stdlib/node";

// Defaults to process.env
const env = createProcessEnv();

// With custom variables
const env = createProcessEnv({
variables: { ...process.env, MY_VAR: "overridden" }
});
```
13 changes: 9 additions & 4 deletions src/node/features/ProcessEnv/feature.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { createFeature } from "~/common/index.js";
import { ProcessEnv } from "./ProcessEnv.js";
import { Env } from "~/common/features/Env/abstractions/Env.js";
import { createProcessEnv } from "./ProcessEnv.js";

export const ProcessEnvFeature = createFeature({
export interface ProcessEnvFeatureParams {
variables?: Record<string, string | undefined>;
}

export const ProcessEnvFeature = createFeature<ProcessEnvFeatureParams | void>({
name: "Node/ProcessEnvFeature",
register(container) {
container.register(ProcessEnv).inSingletonScope();
register(container, params) {
container.registerInstance(Env, createProcessEnv(params || undefined));
}
});
3 changes: 2 additions & 1 deletion src/node/features/ProcessEnv/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { ProcessEnvFeature } from "./feature.js";
export { ProcessEnvFeature, type ProcessEnvFeatureParams } from "./feature.js";
export { createProcessEnv, type CreateProcessEnvParams } from "./ProcessEnv.js";
7 changes: 6 additions & 1 deletion src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ export {
type CreatePackageJsonFileToolParams,
PackageJsonFile
} from "./features/PackageJsonFileTool/index.js";
export { ProcessEnvFeature } from "./features/ProcessEnv/index.js";
export {
ProcessEnvFeature,
type ProcessEnvFeatureParams,
createProcessEnv,
type CreateProcessEnvParams
} from "./features/ProcessEnv/index.js";
export {
HashFolderTool,
HashFolderToolFeature,
Expand Down
Loading