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

Add nanoid-based ID generators: `generateId`, `generateAlphaNumericId`, `generateAlphaNumericLowerCaseId`, `generateAlphaId`, `generateAlphaLowerCaseId`, `generateAlphaUpperCaseId`. All accept an optional `size` parameter (default 21). Import from `@webiny/stdlib`.
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Contains:
- `CacheError` — abstract base error for all cache implementations. Subclass it for implementation-specific errors.
- `MemoryCacheFeature` — registers an in-memory `Cache` implementation in singleton scope.
- `AsyncMemoryCacheFeature` — registers an in-memory `AsyncCache` implementation in singleton scope.
- `generateId(size?)` — generates a URL-safe nanoid (default 21 chars). Also: `generateAlphaNumericId`, `generateAlphaNumericLowerCaseId`, `generateAlphaId`, `generateAlphaLowerCaseId`, `generateAlphaUpperCaseId` — each accepts an optional `size` parameter.

### `@webiny/stdlib/node`

Expand Down
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ The package is ESM-only and ships three subpath exports. Because each is a separ

## `@webiny/stdlib` — Common

| Feature | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| `Result` / `ResultAsync` | Typed success/failure values — synchronous and async |
| `BaseError` | Abstract base class for typed domain errors |
| `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/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) |
| `mdbid` | MongoDB-compatible ObjectId generator — [docs](src/common/utils/mdbid/README.md) |
| Feature | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `Result` / `ResultAsync` | Typed success/failure values — synchronous and async |
| `BaseError` | Abstract base class for typed domain errors |
| `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/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) |
| `mdbid` | MongoDB-compatible ObjectId generator — [docs](src/common/utils/mdbid/README.md) |
| `generateId` / `generateAlphaNumericId` / `generateAlphaLowerCaseId` / ... | Nanoid-based ID generators with configurable alphabets — [docs](src/common/utils/generateId/README.md) |

---

Expand Down
144 changes: 144 additions & 0 deletions __tests__/generateId.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { describe, it, expect } from "vitest";
import {
generateId,
generateAlphaNumericId,
generateAlphaNumericLowerCaseId,
generateAlphaId,
generateAlphaLowerCaseId,
generateAlphaUpperCaseId
} from "../src/common/utils/generateId/generateId.js";

const DEFAULT_SIZE = 21;

describe("generateId", () => {
it("returns a string of default length", () => {
const id = generateId();
expect(typeof id).toBe("string");
expect(id.length).toBe(DEFAULT_SIZE);
});

it("accepts a custom size", () => {
const id = generateId(10);
expect(id.length).toBe(10);
});

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

describe("generateAlphaNumericId", () => {
it("returns a string of default length", () => {
const id = generateAlphaNumericId();
expect(typeof id).toBe("string");
expect(id.length).toBe(DEFAULT_SIZE);
});

it("contains only alphanumeric characters", () => {
const id = generateAlphaNumericId();
expect(id).toMatch(/^[a-zA-Z0-9]+$/);
});

it("accepts a custom size", () => {
const id = generateAlphaNumericId(10);
expect(id.length).toBe(10);
});

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

describe("generateAlphaNumericLowerCaseId", () => {
it("returns a string of default length", () => {
const id = generateAlphaNumericLowerCaseId();
expect(typeof id).toBe("string");
expect(id.length).toBe(DEFAULT_SIZE);
});

it("contains only lowercase alphanumeric characters", () => {
const id = generateAlphaNumericLowerCaseId();
expect(id).toMatch(/^[a-z0-9]+$/);
});

it("accepts a custom size", () => {
const id = generateAlphaNumericLowerCaseId(10);
expect(id.length).toBe(10);
});

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

describe("generateAlphaId", () => {
it("returns a string of default length", () => {
const id = generateAlphaId();
expect(typeof id).toBe("string");
expect(id.length).toBe(DEFAULT_SIZE);
});

it("contains only alphabetic characters", () => {
const id = generateAlphaId();
expect(id).toMatch(/^[a-zA-Z]+$/);
});

it("accepts a custom size", () => {
const id = generateAlphaId(10);
expect(id.length).toBe(10);
});

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

describe("generateAlphaLowerCaseId", () => {
it("returns a string of default length", () => {
const id = generateAlphaLowerCaseId();
expect(typeof id).toBe("string");
expect(id.length).toBe(DEFAULT_SIZE);
});

it("contains only lowercase alphabetic characters", () => {
const id = generateAlphaLowerCaseId();
expect(id).toMatch(/^[a-z]+$/);
});

it("accepts a custom size", () => {
const id = generateAlphaLowerCaseId(10);
expect(id.length).toBe(10);
});

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

describe("generateAlphaUpperCaseId", () => {
it("returns a string of default length", () => {
const id = generateAlphaUpperCaseId();
expect(typeof id).toBe("string");
expect(id.length).toBe(DEFAULT_SIZE);
});

it("contains only uppercase alphabetic characters", () => {
const id = generateAlphaUpperCaseId();
expect(id).toMatch(/^[A-Z]+$/);
});

it("accepts a custom size", () => {
const id = generateAlphaUpperCaseId(10);
expect(id.length).toBe(10);
});

it("generates unique values on successive calls", () => {
const ids = new Set(Array.from({ length: 1000 }, () => generateAlphaUpperCaseId()));
expect(ids.size).toBe(1000);
});
});
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"bson-objectid": "^2.0.4",
"dot-prop": "^10.1.0",
"fast-glob": "^3.3.3",
"nanoid": "^5.1.11",
"nanoid-dictionary": "^5.0.0",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"type-fest": "^5.7.0",
Expand Down
8 changes: 8 additions & 0 deletions src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ export {
} from "./utils/dotProp/index.js";
export { uuid } from "./utils/uuid/index.js";
export { mdbid } from "./utils/mdbid/index.js";
export {
generateAlphaNumericId,
generateAlphaNumericLowerCaseId,
generateAlphaId,
generateAlphaLowerCaseId,
generateAlphaUpperCaseId,
generateId
} from "./utils/generateId/index.js";
13 changes: 7 additions & 6 deletions src/common/utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

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

| 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) |
| [mdbid](mdbid/README.md) | MongoDB-compatible ObjectId generator via `bson-objectid` |
| 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) |
| [mdbid](mdbid/README.md) | MongoDB-compatible ObjectId generator via `bson-objectid` |
| [generateId](generateId/README.md) | Nanoid-based ID generators with configurable alphabets and sizes |
33 changes: 33 additions & 0 deletions src/common/utils/generateId/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# generateId

Nanoid-based ID generators with configurable alphabets and sizes. Uses `nanoid` (v5) and `nanoid-dictionary` under the hood. All generators default to 21 characters and accept an optional `size` parameter.

## API

```ts
function generateId(size?: number): string;
function generateAlphaNumericId(size?: number): string;
function generateAlphaNumericLowerCaseId(size?: number): string;
function generateAlphaId(size?: number): string;
function generateAlphaLowerCaseId(size?: number): string;
function generateAlphaUpperCaseId(size?: number): string;
```

| Function | Alphabet | Example output |
| --------------------------------- | ------------------------ | ------------------------ |
| `generateId` | URL-safe (`A-Za-z0-9_-`) | `V1StGXR8_Z5jdHi6B-myT` |
| `generateAlphaNumericId` | `A-Z a-z 0-9` | `k3Bf9xQpWm7Yz2RtJhNcA` |
| `generateAlphaNumericLowerCaseId` | `a-z 0-9` | `m7k3xq9pw2yz5rtjh8nca` |
| `generateAlphaId` | `A-Z a-z` | `kBfxQpWmYzRtJhNcAeLsG` |
| `generateAlphaLowerCaseId` | `a-z` | `kbfxqpwmyzrtjhncaelsgd` |
| `generateAlphaUpperCaseId` | `A-Z` | `KBFXQPWMYZRTJHNCAELSGD` |

## Usage

```ts
import { generateId, generateAlphaNumericLowerCaseId } from "@webiny/stdlib";

const id = generateId(); // default 21 chars, URL-safe
const short = generateId(10); // custom size
const slug = generateAlphaNumericLowerCaseId(12); // lowercase alphanumeric
```
21 changes: 21 additions & 0 deletions src/common/utils/generateId/generateId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { customAlphabet, nanoid } from "nanoid";
import { alphanumeric, lowercase, numbers, uppercase } from "nanoid-dictionary";

const DEFAULT_SIZE = 21;

export const generateAlphaNumericId = customAlphabet(alphanumeric, DEFAULT_SIZE);

export const generateAlphaNumericLowerCaseId = customAlphabet(
`${lowercase}${numbers}`,
DEFAULT_SIZE
);

export const generateAlphaId = customAlphabet(`${lowercase}${uppercase}`, DEFAULT_SIZE);

export const generateAlphaLowerCaseId = customAlphabet(lowercase, DEFAULT_SIZE);

export const generateAlphaUpperCaseId = customAlphabet(uppercase, DEFAULT_SIZE);

export const generateId = (size = DEFAULT_SIZE): string => {
return nanoid(size);
};
8 changes: 8 additions & 0 deletions src/common/utils/generateId/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export {
generateAlphaNumericId,
generateAlphaNumericLowerCaseId,
generateAlphaId,
generateAlphaLowerCaseId,
generateAlphaUpperCaseId,
generateId
} from "./generateId.js";
18 changes: 18 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,8 @@ __metadata:
dot-prop: "npm:^10.1.0"
fast-glob: "npm:^3.3.3"
happy-dom: "npm:^20.10.1"
nanoid: "npm:^5.1.11"
nanoid-dictionary: "npm:^5.0.0"
oxfmt: "npm:^0.53.0"
oxlint: "npm:^1.68.0"
pino: "npm:^10.3.1"
Expand Down Expand Up @@ -2215,6 +2217,13 @@ __metadata:
languageName: node
linkType: hard

"nanoid-dictionary@npm:^5.0.0":
version: 5.0.0
resolution: "nanoid-dictionary@npm:5.0.0"
checksum: 10c0/b35de04523c0932b542254f28e4902e25f1fdfff3d46fc5a582f71594cc0cdaff2eb610a3f04f933da87c5c5f55301b7e9011b22329de3c98066ed601f5b4e2b
languageName: node
linkType: hard

"nanoid@npm:^3.3.12":
version: 3.3.12
resolution: "nanoid@npm:3.3.12"
Expand All @@ -2224,6 +2233,15 @@ __metadata:
languageName: node
linkType: hard

"nanoid@npm:^5.1.11":
version: 5.1.11
resolution: "nanoid@npm:5.1.11"
bin:
nanoid: bin/nanoid.js
checksum: 10c0/91580d18c29263ac0e871734f0d86e7f906f523f974d3c30fc65354ccf387ccffd606c2a6c28acc2977a3950146347e790ce9e3f514133a48995af5ccdb308ce
languageName: node
linkType: hard

"node-gyp@npm:latest":
version: 12.4.0
resolution: "node-gyp@npm:12.4.0"
Expand Down
Loading