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/cute-words-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@webiny/stdlib": patch
---

feat(common): add uuid v4 generator with native randomUUID and getRandomValues fallback
940 changes: 0 additions & 940 deletions .yarn/releases/yarn-4.14.1.cjs

This file was deleted.

944 changes: 944 additions & 0 deletions .yarn/releases/yarn-4.16.0.cjs

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.14.1.cjs
approvedGitRepositories: []

enableScripts: false
npmMinimalAgeGate: 48h

nodeLinker: node-modules

npmMinimalAgeGate: 3d

npmPreapprovedPackages:
- "@webiny/di"

yarnPath: .yarn/releases/yarn-4.16.0.cjs
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ The package is ESM-only and ships three subpath exports. Because each is a separ
| `Logger` / `ConsoleLogger` / `ConsoleLoggerFeature` | Logging abstraction + console implementation — [docs](src/common/features/Logger/README.md) |
| `Cache` / `MemoryCacheFeature` | Synchronous key-value cache — [docs](src/common/features/Cache/README.md) |
| `AsyncCache` / `AsyncMemoryCacheFeature` | Async key-value cache — [docs](src/common/features/Cache/README.md) |
| `immutableGet` / `immutableSet` / `immutableDelete` / `mutableSet` / `mutableDelete` | Dot-notation get/set/delete on nested objects — [docs](src/common/utils/README.md#dotprop) |
| `toBoolean` / `isTruthy` / `isFalsy` | Semantic boolean coercion — [docs](src/common/utils/README.md#boolean) |
| `immutableGet` / `immutableSet` / `immutableDelete` / `mutableSet` / `mutableDelete` | Dot-notation get/set/delete on nested objects — [docs](src/common/utils/dotProp/README.md) |
| `toBoolean` / `isTruthy` / `isFalsy` | Semantic boolean coercion — [docs](src/common/utils/boolean/README.md) |
| `uuid` | RFC 4122 v4 UUID generator (native + fallback) — [docs](src/common/utils/uuid/README.md) |

---

Expand Down
2 changes: 1 addition & 1 deletion __tests__/boolean.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { toBoolean, isTruthy, isFalsy } from "../src/common/utils/boolean.js";
import { toBoolean, isTruthy, isFalsy } from "../src/common/utils/boolean/boolean.js";

describe("toBoolean", () => {
describe("strings — truthy set", () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/dotProp/immutableDelete.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { immutableDelete } from "../../src/common/utils/dotProp.js";
import { immutableDelete } from "../../src/common/utils/dotProp/dotProp.js";

describe("immutableDelete", () => {
it("returns a new object reference", () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/dotProp/immutableGet.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { immutableGet } from "../../src/common/utils/dotProp.js";
import { immutableGet } from "../../src/common/utils/dotProp/dotProp.js";

describe("immutableGet", () => {
it("gets a top-level property", () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/dotProp/immutableSet.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { immutableSet } from "../../src/common/utils/dotProp.js";
import { immutableSet } from "../../src/common/utils/dotProp/dotProp.js";

describe("immutableSet", () => {
it("returns a new object reference", () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/dotProp/mutableDelete.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { mutableDelete } from "../../src/common/utils/dotProp.js";
import { mutableDelete } from "../../src/common/utils/dotProp/dotProp.js";

describe("mutableDelete", () => {
it("removes a top-level property from the original object", () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/dotProp/mutableSet.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { mutableSet } from "../../src/common/utils/dotProp.js";
import { mutableSet } from "../../src/common/utils/dotProp/dotProp.js";

describe("mutableSet", () => {
it("returns the same object reference", () => {
Expand Down
77 changes: 77 additions & 0 deletions __tests__/uuid.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { uuid } from "../src/common/utils/uuid/uuid.js";

const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;

describe("uuid", () => {
it("returns a string in UUID v4 format", () => {
const id = uuid();
expect(id).toMatch(UUID_V4_REGEX);
});

it("generates unique values on successive calls", () => {
const ids = new Set(Array.from({ length: 1000 }, () => uuid()));
expect(ids.size).toBe(1000);
});

it("sets the version nibble to 4", () => {
const id = uuid();
expect(id[14]).toBe("4");
});

it("sets the variant bits to RFC 4122 (8, 9, a, or b)", () => {
for (let i = 0; i < 100; i++) {
const id = uuid();
expect(["8", "9", "a", "b"]).toContain(id[19]);
}
});

it("returns lowercase hex characters", () => {
const id = uuid();
const hexOnly = id.replace(/-/g, "");
expect(hexOnly).toBe(hexOnly.toLowerCase());
});

it("returns a 36-character string with hyphens at positions 8, 13, 18, 23", () => {
const id = uuid();
expect(id.length).toBe(36);
expect(id[8]).toBe("-");
expect(id[13]).toBe("-");
expect(id[18]).toBe("-");
expect(id[23]).toBe("-");
});

describe("fallback path", () => {
afterEach(() => {
vi.restoreAllMocks();
});

it("produces valid UUID v4 when crypto.randomUUID is unavailable", () => {
vi.spyOn(crypto, "randomUUID").mockImplementation(() => {
throw new Error("not available");
});
Object.defineProperty(crypto, "randomUUID", { value: undefined, configurable: true });

const id = uuid();
expect(id).toMatch(UUID_V4_REGEX);

Object.defineProperty(crypto, "randomUUID", {
value: globalThis.crypto.randomUUID,
configurable: true
});
});

it("generates unique values via the fallback path", () => {
const original = crypto.randomUUID;
Object.defineProperty(crypto, "randomUUID", { value: undefined, configurable: true });

const ids = new Set(Array.from({ length: 1000 }, () => uuid()));
expect(ids.size).toBe(1000);

Object.defineProperty(crypto, "randomUUID", {
value: original,
configurable: true
});
});
});
});
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@
"fast-glob": "^3.3.3",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"type-fest": "^5.6.0",
"type-fest": "^5.7.0",
"zod": "^4.4.3"
},
"devDependencies": {
"@changesets/cli": "^2.31.0",
"@types/node": ">=24",
"@typescript/native-preview": "^7.0.0-dev.20260522.1",
"@vitest/coverage-v8": "^4.1.7",
"adio": "^3.0.0",
"happy-dom": "^20.9.0",
"oxfmt": "^0.51.0",
"oxlint": "^1.66.0",
"vitest": "^4.1.7"
"@typescript/native-preview": "^7.0.0-dev.20260603.1",
"@vitest/coverage-v8": "^4.1.8",
"adio": "^3.0.1",
"happy-dom": "^20.10.1",
"oxfmt": "^0.53.0",
"oxlint": "^1.68.0",
"vitest": "^4.1.8"
},
"scripts": {
"clean": "rm -rf dist",
Expand All @@ -58,5 +58,6 @@
"check:imports": "adio",
"typecheck": "tsgo -p config/tsconfig.check.common.json && tsgo -p config/tsconfig.check.node.json && tsgo -p config/tsconfig.check.browser.json && tsgo -p config/tsconfig.check.scripts.json",
"full": "yarn check:imports && yarn clean && yarn format:fix && yarn lint:fix && yarn typecheck && yarn build && yarn test:coverage"
}
},
"packageManager": "yarn@4.16.0"
}
5 changes: 3 additions & 2 deletions src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ export {
AsyncMemoryCacheFeature
} from "./features/Cache/index.js";
export type { ICache, IAsyncCache } from "./features/Cache/index.js";
export { toBoolean, isTruthy, isFalsy } from "./utils/boolean.js";
export { toBoolean, isTruthy, isFalsy } from "./utils/boolean/index.js";
export {
immutableDelete,
immutableGet,
mutableDelete,
mutableSet,
immutableSet
} from "./utils/dotProp.js";
} from "./utils/dotProp/index.js";
export { uuid } from "./utils/uuid/index.js";
138 changes: 5 additions & 133 deletions src/common/utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,136 +2,8 @@

Standalone utility functions exported from `@webiny/stdlib`. No DI container required — import and call directly.

---

## dotProp

Immutable and mutable operations on nested objects via dot-notation paths. Wraps the [`dot-prop`](https://github.com/deoxxa/dot-prop) package with explicit immutable/mutable variants and `structuredClone`-based deep cloning.

### API

```ts
function immutableGet<T = unknown>(
object: Record<string, any> | null | undefined,
path: string,
defaultValue?: T
): T;
```

Gets the value at `path`. Clones `object` before reading; returns a clone of `defaultValue` when `object` is null or undefined.

```ts
function immutableSet<T extends Record<string, any>>(
object: T,
path: string,
value: unknown | ((current: any) => unknown)
): T;
```

Returns a deep clone of `object` with `value` set at `path`. Pass a function as `value` to compute the new value from the current one.

```ts
function immutableDelete<T extends Record<string, any>>(object: T, path: string): T;
function immutableDelete<T>(target: T[], index: number): T[];
```

Returns a deep clone with the property at `path` removed. When called on an array with a numeric index, splices the element out of the clone (the original array is unchanged).

```ts
function mutableSet<T extends Record<string, any>>(object: T, path: string, value: unknown): T;
```

Sets `value` at `path` on `object` in place. Returns `object`.

```ts
function mutableDelete<T extends Record<string, any>>(object: T, path: string): boolean;
function mutableDelete<T>(target: T[], index: number): boolean;
```

Removes the property at `path` from `object` in place. When called on an array with a numeric index, splices the element out. Returns `true` if the property/element existed, `false` otherwise.

### Usage

```ts
import {
immutableGet,
immutableSet,
immutableDelete,
mutableSet,
mutableDelete
} from "@webiny/stdlib";

const config = { server: { port: 3000, host: "localhost" } };

const port = immutableGet<number>(config, "server.port"); // 3000

const updated = immutableSet(config, "server.port", 4000);
// config.server.port is still 3000; updated.server.port is 4000

const doubled = immutableSet(config, "server.port", (v: number) => v * 2);
// doubled.server.port is 6000

const withoutHost = immutableDelete(config, "server.host");
// config still has host; withoutHost does not

mutableSet(config, "server.port", 5000); // mutates config
mutableDelete(config, "server.host"); // mutates config

// Array support — both delete functions accept a numeric index
const items = ["a", "b", "c"];

const without = immutableDelete(items, 1); // ["a", "c"] — items unchanged
mutableDelete(items, 0); // items is now ["b", "c"]
```

---

## boolean

Semantic boolean coercion with exact parity to the [`boolean`](https://www.npmjs.com/package/boolean) npm package. Converts environment variable strings, form values, and other stringly-typed inputs to `boolean` using a well-defined set of truthy tokens.

### API

```ts
function toBoolean(value: unknown): boolean;
```

Converts any value to `boolean`:

- **Strings** (case-insensitive, trimmed): `"true"`, `"t"`, `"yes"`, `"y"`, `"on"`, `"1"` → `true`; everything else → `false`
- **Numbers**: `1` → `true`; all other numbers → `false`
- **Booleans**: `.valueOf()` result
- **Anything else**: `false`

```ts
function isTruthy(value: unknown): boolean;
```

Readable alias for `toBoolean(value)`. Intended for use in array predicates.

```ts
function isFalsy(value: unknown): boolean;
```

Readable inverse: returns `!toBoolean(value)`.

### Usage

```ts
import { toBoolean, isTruthy, isFalsy } from "@webiny/stdlib";

toBoolean("yes"); // true
toBoolean("no"); // false
toBoolean("1"); // true
toBoolean("2"); // false
toBoolean(1); // true
toBoolean(0); // false
toBoolean(true); // true

const flags = ["on", "off", "true", "false"];
flags.filter(isTruthy); // ["on", "true"]
flags.filter(isFalsy); // ["off", "false"]

// Typical env var usage
const debug = isTruthy(process.env.DEBUG);
```
| Util | Description |
| ---------------------------- | ----------------------------------------------------------------------------- |
| [boolean](boolean/README.md) | Semantic boolean coercion (`toBoolean`, `isTruthy`, `isFalsy`) |
| [dotProp](dotProp/README.md) | Immutable and mutable get/set/delete on nested objects via dot-notation |
| [uuid](uuid/README.md) | RFC 4122 v4 UUID generator (native `randomUUID` + `getRandomValues` fallback) |
49 changes: 49 additions & 0 deletions src/common/utils/boolean/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# boolean

Semantic boolean coercion with exact parity to the [`boolean`](https://www.npmjs.com/package/boolean) npm package. Converts environment variable strings, form values, and other stringly-typed inputs to `boolean` using a well-defined set of truthy tokens.

## API

```ts
function toBoolean(value: unknown): boolean;
```

Converts any value to `boolean`:

- **Strings** (case-insensitive, trimmed): `"true"`, `"t"`, `"yes"`, `"y"`, `"on"`, `"1"` → `true`; everything else → `false`
- **Numbers**: `1` → `true`; all other numbers → `false`
- **Booleans**: `.valueOf()` result
- **Anything else**: `false`

```ts
function isTruthy(value: unknown): boolean;
```

Readable alias for `toBoolean(value)`. Intended for use in array predicates.

```ts
function isFalsy(value: unknown): boolean;
```

Readable inverse: returns `!toBoolean(value)`.

## Usage

```ts
import { toBoolean, isTruthy, isFalsy } from "@webiny/stdlib";

toBoolean("yes"); // true
toBoolean("no"); // false
toBoolean("1"); // true
toBoolean("2"); // false
toBoolean(1); // true
toBoolean(0); // false
toBoolean(true); // true

const flags = ["on", "off", "true", "false"];
flags.filter(isTruthy); // ["on", "true"]
flags.filter(isFalsy); // ["off", "false"]

// Typical env var usage
const debug = isTruthy(process.env.DEBUG);
```
Loading
Loading