@eliware/snowflake 2.0.0 → 13.0.0

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.
package/AGENTS.md ADDED
@@ -0,0 +1,34 @@
1
+ # AGENTS.md
2
+
3
+ ## Project
4
+
5
+ Build a Snowflake ID library with Node.js 26.
6
+ Use native ESM.
7
+
8
+ ## Scope and boundaries
9
+
10
+ ## Layout
11
+
12
+ ## Development
13
+
14
+ ## Validation
15
+
16
+ Run `npm test`.
17
+ Run `npm run lint`.
18
+ Run `npm run audit`.
19
+ Run `npm run format`.
20
+ Run `npm run format:check`.
21
+
22
+ ## Security
23
+
24
+ ## Changes
25
+
26
+ ## Library
27
+
28
+ Run focused tests first during development.
29
+ Run one test file with `npm test -- tests/path/to/example.test.mjs`.
30
+ Run multiple test files with `npm test -- tests/path/to/first.test.mjs tests/path/to/second.test.mjs`.
31
+ Prevent repeated full-suite runs during development.
32
+ Run the full `npm test` suite for final validation before a commit, milestone, or release.
33
+
34
+ ## npm publication
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
- # MIT License
1
+ MIT License
2
2
 
3
- Copyright (c) 2025 Eli Sterling, eliware.org
3
+ Copyright (c) 2026 Eliware
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,101 +1,117 @@
1
- # @eliware/snowflake
1
+ # [![eliware.org](https://eliware.org/logos/brand.png)](https://discord.gg/M6aTR9eTwN)
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@eliware/snowflake.svg)](https://www.npmjs.com/package/@eliware/snowflake) [![build status](https://github.com/eliware/snowflake/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eliware/snowflake/actions)
3
+ ## @eliware/snowflake [![npm](https://img.shields.io/npm/v/@eliware/snowflake)](https://www.npmjs.com/package/@eliware/snowflake) [![License](https://img.shields.io/github/license/eliware/snowflake)](https://github.com/eliware/snowflake/blob/main/LICENSE) [![CI](https://github.com/eliware/snowflake/actions/workflows/ci.yaml/badge.svg)](https://github.com/eliware/snowflake/actions/workflows/ci.yaml)
4
4
 
5
- A production-ready, BigInt-safe Snowflake ID generator for Node.js. IDs are returned as decimal strings so they remain safe across JSON, databases, and JavaScript runtimes.
5
+ A Node.js Snowflake ID generator with BigInt support and a configurable epoch.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Features](#features)
10
+ - [Requirements](#requirements)
11
+ - [Setup](#setup)
12
+ - [Usage](#usage)
13
+ - [Development](#development)
14
+ - [Testing](#testing)
15
+ - [Troubleshooting](#troubleshooting)
16
+ - [Security](#security)
17
+ - [API](#api)
18
+ - [Packaging](#packaging)
19
+ - [Examples](#examples)
20
+ - [Support](#support)
21
+ - [Links](#links)
22
+ - [License](#license)
6
23
 
7
24
  ## Features
8
25
 
9
- - Custom epoch; the default is `365662380000n` (`1981-08-03T04:53:00Z`).
10
- - 42-bit millisecond timestamp, 5-bit worker ID, 5-bit process ID, and 12-bit sequence.
11
- - Clock rollback protection and sequence overflow handling.
12
- - Sequence overflow fails fast; callers can retry after the clock advances.
13
- - Configurable worker/process identity for distributed deployments.
14
- - Injectable clock for deterministic tests.
15
- - Range validation and timestamp-capacity checks.
16
- - Native ESM, TypeScript declarations, and no runtime dependencies.
26
+ Create time-ordered Snowflake IDs with a custom epoch and worker and process IDs.
27
+ Return decimal strings so JavaScript and JSON preserve each ID exactly.
28
+ The generator keeps IDs ordered when time decreases and reports sequence overflow.
17
29
 
18
30
  ## Requirements
19
31
 
20
- - Node.js 26 or newer
32
+ Use Node.js 26 or later.
33
+ Use one package instance in each Node.js process.
34
+
35
+ ## Setup
21
36
 
22
- ## Installation
37
+ Install the package from npm:
23
38
 
24
- ```bash
39
+ ```text
25
40
  npm install @eliware/snowflake
26
41
  ```
27
42
 
28
43
  ## Usage
29
44
 
45
+ Import the generator factory and set a unique worker and process ID pair:
46
+
30
47
  ```js
31
- import generate, { createSnowflakeGenerator } from '@eliware/snowflake';
48
+ import { createSnowflakeGenerator } from "@eliware/snowflake";
32
49
 
50
+ const generate = createSnowflakeGenerator({ workerId: 3, processId: 7 });
33
51
  console.log(generate());
34
-
35
- // Set a unique worker ID for each replica or node.
36
- const replicaGenerator = createSnowflakeGenerator({
37
- workerId: 3,
38
- processId: 7,
39
- });
40
- console.log(replicaGenerator());
41
52
  ```
42
53
 
43
- For Kubernetes or other distributed deployments, assign each replica a stable
44
- `workerId` and coordinate `processId` values so that no two active generators
45
- share the same worker/process pair. `processId` defaults to `process.pid % 32`,
46
- but should be explicitly configured when process identity must be coordinated
47
- across hosts.
54
+ Each call returns one decimal string.
48
55
 
49
- ## API
56
+ [Release notes](RELEASE_NOTES.md)
57
+
58
+ [Specifications](specs/README.md)
50
59
 
51
- `createSnowflakeGenerator(options?)` returns a function that generates decimal-string IDs.
60
+ ## Development
52
61
 
53
- | Option | Default | Description |
54
- | --- | --- | --- |
55
- | `epoch` | `365662380000n` | Unix epoch in milliseconds. |
56
- | `workerId` | `0` | Worker/node ID, from `0` to `31`. |
57
- | `processId` | `process.pid % 32` | Process ID, from `0` to `31`. |
58
- | `now` | `Date.now` | Injectable millisecond clock. |
62
+ [Documentation](docs/README.md)
63
+ See the [usage guide](docs/usage.md) for generator options and ID fields.
59
64
 
60
- The exports `generate` and `snowflake` are ready-to-use default generators;
61
- both refer to the same generator. `constants` exposes the bit layout and
62
- default epoch. IDs are unique only when the configured worker/process identity
63
- is unique and the system clock is not permanently stalled.
65
+ ## Testing
64
66
 
65
- ## TypeScript
67
+ Run `npm test` from a repository clone to run aggregate validation.
66
68
 
67
- TypeScript declarations are included for all public exports and options.
69
+ ## Troubleshooting
68
70
 
69
- ## Errors / Troubleshooting
71
+ Check the options and clock when the generator throws `RangeError`.
72
+ For sequence overflow, retry when the clock returns a timestamp greater than the last timestamp.
73
+ Read the [troubleshooting guide](docs/troubleshooting.md) for error causes and actions.
70
74
 
71
- Configuration values outside the supported ranges throw `RangeError`; a
72
- non-function clock throws `TypeError`; timestamps before the epoch or beyond
73
- Snowflake capacity throw `RangeError`. If more than 4096 IDs are requested in
74
- one millisecond, generation throws a `RangeError`; retry after the clock has
75
- advanced. Configure unique `workerId` and `processId` values across distributed
76
- generators.
75
+ ## Security
77
76
 
78
- ## Testing and linting
77
+ Use IDs as identifiers. Each ID contains its generation time and worker and process values.
78
+ Do not use IDs as secrets or authentication tokens.
79
+ Read the [usage guide](docs/usage.md) for ID fields and storage guidance.
79
80
 
80
- ```bash
81
- npm test
82
- npm run lint
83
- npm run typecheck
84
- npm run audit
85
- npm run pack
86
- ```
81
+ ## API
87
82
 
88
- ## Operations
83
+ Import `createSnowflakeGenerator` to create a generator with custom options.
84
+ Use `generate`, `snowflake`, or the default export for the default generator.
85
+ Read the [usage guide](docs/usage.md) for the API and option details.
89
86
 
90
- Use the same `epoch`, `workerId`, and `processId` configuration consistently
91
- for each generator instance. Persist IDs as decimal strings because Snowflake
92
- values can exceed JavaScript's safe integer range. The generator performs no
93
- filesystem or network I/O and does not require shutdown handling.
87
+ ## Packaging
94
88
 
95
- ## Security
89
+ The package uses native ESM and includes TypeScript declarations.
90
+ The package has no runtime dependencies.
91
+ Install it from [npm](https://www.npmjs.com/package/@eliware/snowflake).
92
+
93
+ ## Examples
94
+
95
+ [Run the native ESM example](examples/example.mjs).
96
+ [Examples](examples/README.md)
97
+
98
+ ## Support
99
+
100
+ For help, questions, or to chat with the author and community, visit:
101
+
102
+ [![Discord](https://eliware.org/logos/discord_96.png)](https://discord.gg/M6aTR9eTwN)[![eliware.org](https://eliware.org/logos/eliware_96.png)](https://discord.gg/M6aTR9eTwN)
103
+
104
+ **[eliware.org on Discord](https://discord.gg/M6aTR9eTwN)**
105
+
106
+ ## Links
96
107
 
97
- Snowflake IDs are unique identifiers, not secrets. Do not use them as authentication tokens or assume they conceal timestamp and deployment information.
108
+ - [Home Page](https://eliware.org)
109
+ - [GitHub Org](https://github.com/eliware)
110
+ - [GitHub Repo](https://github.com/eliware/snowflake)
111
+ - [Bug Reports](https://github.com/eliware/snowflake/issues)
112
+ - [npm](https://www.npmjs.com/package/@eliware/snowflake)
113
+ - [Discord](https://discord.gg/M6aTR9eTwN)
98
114
 
99
115
  ## License
100
116
 
101
- MIT © Eli Sterling, eliware.org
117
+ [MIT © Eliware](LICENSE)
package/RELEASE_NOTES.md CHANGED
@@ -1,57 +1,52 @@
1
1
  # Release Notes
2
2
 
3
- ## 2.0.0 — August 28, 2026
4
-
5
- - Adopted the shared `@eliware/test` harness for testing and linting with
6
- strict 100×4 coverage enforcement.
7
- - Modernized package metadata, documentation, package contents, and CI for
8
- Node.js `>=26`, including cross-platform validation and tag-only publishing.
9
- - Added production dependency auditing and repository-owned Knit validation.
10
- - Added fail-fast sequence-overflow handling and expanded BigInt, boundary, and
11
- rollback coverage.
12
- - Removed obsolete Jest-result ignore configuration and added shared project
13
- working rules.
14
- - Breaking: the standard `test` and `lint` scripts now delegate to
15
- `@eliware/test`.
16
-
17
- ## 1.1.5 — August 7, 2026
18
-
19
- - Aligned repository layout, scripts, CI, documentation, and package contents with Eliware library conventions.
20
- - Added TypeScript declaration checking and standardized package validation.
21
- - Moved tests under `tests/` and included a runnable `examples/` file in the package.
22
- - Removed the legacy `main` metadata from this ESM-only package.
23
- - Verification: tests, coverage, gap checks, lint, typecheck, smoke test, and package dry-run pass.
24
-
25
- ## 1.1.4 — August 7, 2026
3
+ ## 13.0.0 — 2026-10-19
4
+
5
+ ### Added
6
+
7
+ - Added the canonical npm publication workflow.
8
+ - Added full Git history to CI.
26
9
 
27
10
  ### Changed
28
11
 
29
- - Removed the unused `@eliware/common` runtime dependency.
30
- - Reduced the installed dependency tree and regenerated the npm lockfile.
31
- - Added manual GitHub Actions workflow dispatch support.
12
+ - Updated the package to version 13.0.0 and upgraded `@eliware/test` and npm dependencies.
13
+ - Aligned CI and npm publication workflows with the current templates and checkout action.
14
+ - Updated Knit deployment commands and the repository path.
15
+ - Updated specifications and repository guidance for Convention 13.
16
+ - Expanded user guides with API details, ID limits, timestamp decoding, and storage guidance.
17
+ - Documented the custom epoch for Discord timestamp decoding. It does not create official Discord IDs.
18
+ - Changed the invalid clock source and sequence overflow error messages.
19
+ - Reduced the root README and contributor guidance to convention-required content.
32
20
 
33
- ### Verification
21
+ ## 12.0.0 — 2026-10-12
22
+
23
+ ### Changed
34
24
 
35
- - npm audit: 0 vulnerabilities.
25
+ - Moved library code to `src/` and added matching tests under `tests/`.
26
+ - Added user guides, navigation indexes, repository rules, and a runnable example index.
27
+ - Expanded user guides with option defaults, ID fields, identity rules, limits, and error actions.
28
+ - Updated package metadata, version, contents, license, and dependency lock.
29
+ - Added repository ID metadata and v12 specification files.
30
+ - Updated ignore rules and removed obsolete Jest result settings.
31
+ - Updated validation scripts and the shared test harness to version 12.
32
+ - Replaced the CI workflow and added an npm publication workflow with provenance.
33
+ - Updated Knit commands and its repository path.
34
+ - Updated README and contributor rules to match the v12 conventions.
35
+ - Kept the public API and decimal string output unchanged.
36
36
 
37
- ## 1.1.2
37
+ ## 2.0.0 — 2026-08-28
38
38
 
39
39
  ### Added
40
40
 
41
- - Rebuilt the generator around a configurable, BigInt-safe Snowflake implementation.
42
- - Added the custom Eliware epoch: `365662380000n` (`1981-08-03T04:53:00Z`).
43
- - Added worker ID and process ID fields for distributed deployments.
44
- - Added clock rollback protection and sequence overflow handling.
45
- - Added injectable clocks, range validation, and timestamp-capacity validation.
46
- - Added TypeScript declarations for all public options and exports.
41
+ - Added fail-fast sequence overflow handling and expanded boundary coverage.
42
+ - Added TypeScript declarations for public exports and options.
47
43
 
48
44
  ### Changed
49
45
 
50
- - Removed CommonJS entrypoints and tests; the package is ESM-only.
51
- - Expanded documentation with deployment and bit-layout guidance.
52
- - Added complete generator behavior tests.
46
+ - Adopted `@eliware/test` for tests and lint checks.
47
+ - Updated package metadata, documentation, and CI for Node.js 26 or newer.
48
+ - Reorganized production modules under `src/` and tests under `tests/`.
53
49
 
54
- ### Verification
50
+ ### Breaking changes
55
51
 
56
- - Jest coverage: 100% statements, branches, functions, and lines.
57
- - Oxlint: 0 warnings and errors.
52
+ - Changed the `test` and `lint` scripts to use `@eliware/test`.
package/docs/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # Documentation
2
+
3
+ - [Usage](usage.md)
4
+ - [Troubleshooting](troubleshooting.md)
@@ -0,0 +1,48 @@
1
+ # Troubleshooting
2
+
3
+ ## Error reference
4
+
5
+ | Error | Cause | Action |
6
+ | --------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------- |
7
+ | `RangeError` for `workerId` or `processId` | The value is outside `0` through `31`. | Set an integer from `0` through `31`. |
8
+ | `TypeError` for `now` | The option is not a function. | Pass a function that returns Unix time in milliseconds. |
9
+ | `RangeError` for `epoch` | The epoch is outside the supported range. | Set the epoch from zero through the timestamp limit. |
10
+ | `RangeError` for invalid clock number | The clock returned a fractional or unsafe number. | Return a safe integer number or a bigint. |
11
+ | `RangeError` for time before epoch | The clock value is lower than `epoch`. | Set the correct epoch or set the clock source. |
12
+ | `RangeError` for timestamp capacity | Elapsed time is greater than the 42-bit limit. | Use a valid epoch for the required date range. |
13
+ | `RangeError` for sequence overflow | The generator created 4096 IDs at one timestamp. | Retry when the clock returns a greater timestamp. |
14
+ | `TypeError` or `RangeError` during conversion | An option or clock value cannot convert to `BigInt`. | Pass an integer number or bigint value. |
15
+
16
+ ## Sequence overflow
17
+
18
+ The generator does not wait for the clock.
19
+ It fails when one timestamp reaches the 4096-ID limit.
20
+ Retry when the clock returns a timestamp greater than the last timestamp.
21
+ If the clock stalls, check the system clock or the `now` function.
22
+
23
+ ## Duplicate IDs
24
+
25
+ Give each active generator a unique worker and process ID pair.
26
+ Use the same epoch for each generator in one application.
27
+ The default worker ID is `0`.
28
+ The default process ID is `process.pid % 32` and can repeat across hosts.
29
+ Set both IDs in deployment configuration when some hosts create IDs.
30
+
31
+ ## Invalid timestamps
32
+
33
+ The clock must return a safe integer number or a bigint.
34
+ The value must be Unix time in milliseconds.
35
+ The timestamp must be at or after the configured epoch.
36
+ The elapsed time must be 42 bits or less.
37
+ Check the clock and epoch values when timestamp validation fails.
38
+
39
+ ## Large ID values
40
+
41
+ IDs are decimal strings because JavaScript numbers cannot store every ID exactly.
42
+ Keep the string in JSON and in database fields that store exact decimal values.
43
+ Use `BigInt(id)` for exact integer operations.
44
+
45
+ ## Node.js version
46
+
47
+ Use Node.js 26 or later.
48
+ Check the active version with `node --version`.
package/docs/usage.md ADDED
@@ -0,0 +1,144 @@
1
+ # Use Snowflake IDs
2
+
3
+ ## Requirements
4
+
5
+ Use Node.js 26 or later.
6
+ Use one package instance in each Node.js process.
7
+
8
+ ## Install
9
+
10
+ Install the package from npm:
11
+
12
+ ```text
13
+ npm install @eliware/snowflake
14
+ ```
15
+
16
+ ## Create a generator
17
+
18
+ Import the generator factory and set a unique worker and process ID pair:
19
+
20
+ ```js
21
+ import { createSnowflakeGenerator } from "@eliware/snowflake";
22
+
23
+ const generate = createSnowflakeGenerator({ workerId: 3, processId: 7 });
24
+ const id = generate();
25
+ console.log(id);
26
+ ```
27
+
28
+ The command prints one decimal string. The value changes on each call.
29
+
30
+ ## Set generator options
31
+
32
+ Pass an options object to `createSnowflakeGenerator`.
33
+
34
+ | Option | Type | Default | Meaning |
35
+ | ----------- | ---------------- | ------------------ | ------------------------------------------------ |
36
+ | `epoch` | number or bigint | `365662380000n` | Unix epoch in milliseconds. |
37
+ | `workerId` | number or bigint | `0` | Worker value from `0` through `31`. |
38
+ | `processId` | number or bigint | `process.pid % 32` | Process value from `0` through `31`. |
39
+ | `now` | function | `Date.now` | Function that returns Unix time in milliseconds. |
40
+
41
+ `createSnowflakeGenerator(options)` returns a function that creates IDs.
42
+ Each call returns one decimal string.
43
+ Set `epoch` as a safe integer number or a bigint from zero through `(1n << 42n) - 1n`.
44
+ Set `workerId` and `processId` to integer values from `0` through `31`.
45
+ Use the same epoch for all generators in one application.
46
+
47
+ Give each active generator a unique pair of `workerId` and `processId` values.
48
+ Keep each pair unique while IDs from that pair remain in use.
49
+ Set `processId` when some hosts or processes use the same `workerId`.
50
+ The default process ID can repeat across hosts.
51
+
52
+ To align timestamp decoding with Discord, set `epoch` to `1420070400000n`.
53
+ This epoch matches Discord's timestamp epoch.
54
+ It does not create an official Discord resource ID.
55
+
56
+ ```js
57
+ const createDiscordTimeGenerator = createSnowflakeGenerator({
58
+ epoch: 1420070400000n,
59
+ workerId: 3,
60
+ processId: 7,
61
+ });
62
+ ```
63
+
64
+ ## Understand the ID fields
65
+
66
+ Each ID has a 42-bit timestamp, a 5-bit worker ID, a 5-bit process ID, and a 12-bit sequence.
67
+ The timestamp stores milliseconds since the configured epoch.
68
+ The sequence starts at zero when the timestamp changes.
69
+ The default epoch is `365662380000n`, or `1981-08-03T04:53:00Z`.
70
+ The timestamp field sorts before the worker, process, and sequence fields.
71
+
72
+ The generator combines the fields as follows:
73
+
74
+ ```text
75
+ ((time - epoch) << 22) | (workerId << 17) | (processId << 12) | sequence
76
+ ```
77
+
78
+ The maximum elapsed timestamp is `(1n << 42n) - 1n` milliseconds.
79
+ IDs can be greater than JavaScript's safe integer limit.
80
+
81
+ ## Use the default generator
82
+
83
+ Use `generate`, `snowflake`, or the default export to create IDs.
84
+ These exports refer to the same generator instance.
85
+ The default generator uses worker ID `0` and process ID `process.pid % 32`.
86
+ Create a custom generator to set a different identity, epoch, or clock.
87
+
88
+ The `constants` export has `epoch`, `sequenceBits`, `processBits`, and `workerBits` fields.
89
+ Each field has a `bigint` value.
90
+ The sequence, process, and worker fields use 12, 5, and 5 bits.
91
+ The `constants` object is frozen.
92
+
93
+ ## Decode an ID timestamp
94
+
95
+ Convert the ID string to a `bigint` before you read its timestamp field.
96
+ Move the value right by 22 bits to read elapsed milliseconds.
97
+ Add the configured epoch to get Unix time in milliseconds.
98
+ Convert that value to a JavaScript `Date` to get its ISO 8601 value.
99
+
100
+ ```js
101
+ const epoch = 365662380000n;
102
+ const timestamp = new Date(Number((BigInt(id) >> 22n) + epoch));
103
+ console.log(timestamp.toISOString());
104
+ ```
105
+
106
+ Use the same epoch that the generator used to create the ID.
107
+
108
+ ## Use a custom clock
109
+
110
+ Pass `now` to set the clock source.
111
+ Return a safe integer number or a bigint from that function.
112
+ The value must be Unix time in milliseconds.
113
+ Use this option for deterministic tests or a controlled clock source.
114
+
115
+ ## Time and sequence limits
116
+
117
+ The generator uses the last timestamp when the clock returns a lower value.
118
+ The generator sets the sequence to zero when the timestamp changes.
119
+ The generator increments the sequence while the timestamp stays the same.
120
+
121
+ Each generator can create 4096 IDs in one millisecond.
122
+ The generator throws `RangeError` after that limit.
123
+ Retry when the clock returns a timestamp greater than the last timestamp.
124
+ The generator throws `RangeError` for a timestamp before the epoch or greater than its 42-bit limit.
125
+
126
+ ## Store and share IDs
127
+
128
+ Keep each ID as a decimal string in JavaScript and JSON.
129
+ Do not convert an ID to `number`.
130
+ JSON cannot encode a `bigint` value without custom code.
131
+ Use a text field or an unsigned 64-bit field in a database.
132
+ Use `BigInt(id)` for exact integer math in JavaScript.
133
+
134
+ ## Run the example
135
+
136
+ Use Node.js 26 or later.
137
+ Run this command from the repository root:
138
+
139
+ ```text
140
+ node examples/example.mjs
141
+ ```
142
+
143
+ The command prints two decimal strings. The values depend on the current time and process ID.
144
+ The example writes no files.
@@ -0,0 +1,3 @@
1
+ # Examples
2
+
3
+ - [Native ESM example](example.mjs)
@@ -1,10 +1,10 @@
1
1
  // ESM example: example.mjs
2
- import generate, { createSnowflakeGenerator } from './index.mjs';
2
+ import generate, { createSnowflakeGenerator } from "@eliware/snowflake";
3
3
 
4
- console.log('Default generate():', generate());
4
+ console.log("Default generate():", generate());
5
5
 
6
6
  const genWorker1 = createSnowflakeGenerator({ workerId: 1 });
7
- console.log('Worker 1 generate():', genWorker1());
7
+ console.log("Worker 1 generate():", genWorker1());
8
8
 
9
9
  // Exported generator can be used anywhere in your app to create unique IDs
10
10
  export default generate;
package/package.json CHANGED
@@ -1,58 +1,109 @@
1
1
  {
2
- "name": "@eliware/snowflake",
3
- "type": "module",
4
- "version": "2.0.0",
5
- "description": "A minimal Node.js Snowflake ID generator (Discord-compatible). Provides BigInt-safe generators with configurable epoch and worker ID.",
6
- "engines": {
7
- "node": ">=26"
8
- },
9
- "types": "index.d.ts",
10
- "files": [
11
- "index.mjs",
12
- "index.d.ts",
13
- "README.md",
14
- "LICENSE",
15
- "RELEASE_NOTES.md",
16
- "examples"
2
+ "name": "@eliware/snowflake",
3
+ "version": "13.0.0",
4
+ "type": "module",
5
+ "description": "A Node.js Snowflake ID generator with BigInt support and a configurable epoch.",
6
+ "keywords": [
7
+ "snowflake",
8
+ "snowflake-id",
9
+ "id-generator",
10
+ "discord",
11
+ "unique-id",
12
+ "bigint",
13
+ "nodejs",
14
+ "esm"
15
+ ],
16
+ "private": false,
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "provenance": true
20
+ },
21
+ "main": "./src/index.mjs",
22
+ "files": [
23
+ "src/",
24
+ "docs/",
25
+ "README.md",
26
+ "AGENTS.md",
27
+ "LICENSE",
28
+ "RELEASE_NOTES.md",
29
+ "examples/",
30
+ "specs/"
31
+ ],
32
+ "scripts": {
33
+ "test": "eliware-test",
34
+ "lint": "eliware-test --lint",
35
+ "audit": "eliware-test --audit",
36
+ "format": "eliware-test --format",
37
+ "format:check": "eliware-test --format-check",
38
+ "typecheck": "tsc --project tsconfig.json",
39
+ "pack": "eliware-test --pack"
40
+ },
41
+ "engines": {
42
+ "node": ">=26"
43
+ },
44
+ "devDependencies": {
45
+ "@eliware/test": "^13.0.0",
46
+ "typescript": "^7.0.2"
47
+ },
48
+ "jest": {
49
+ "testEnvironment": "node",
50
+ "testMatch": [
51
+ "**/tests/**/*.test.mjs"
17
52
  ],
18
- "publishConfig": {
19
- "access": "public"
20
- },
21
- "exports": {
22
- ".": {
23
- "types": "./index.d.ts",
24
- "import": "./index.mjs"
25
- }
26
- },
27
- "scripts": {
28
- "test": "eliware-test",
29
- "lint": "eliware-test --lint",
30
- "audit": "npm audit --omit=dev --audit-level=moderate",
31
- "typecheck": "tsc --project tsconfig.json",
32
- "pack": "npm pack --dry-run"
33
- },
34
- "repository": {
35
- "type": "git",
36
- "url": "git+https://github.com/eliware/snowflake.git"
37
- },
38
- "keywords": [
39
- "snowflake",
40
- "snowflake-id",
41
- "id-generator",
42
- "discord",
43
- "unique-id",
44
- "bigint",
45
- "nodejs",
46
- "esm"
53
+ "collectCoverageFrom": [
54
+ "src/**/*.mjs"
47
55
  ],
48
- "author": "Eli Sterling, eliware.org <eli@eliware.org>",
49
- "license": "MIT",
50
- "bugs": {
51
- "url": "https://github.com/eliware/snowflake/issues"
52
- },
53
- "homepage": "https://github.com/eliware/snowflake#readme",
54
- "devDependencies": {
55
- "@eliware/test": "^2.0.0",
56
- "typescript": "^7.0.2"
56
+ "coverageReporters": [
57
+ "text",
58
+ "json-summary"
59
+ ],
60
+ "coverageThreshold": {
61
+ "global": {
62
+ "branches": 100,
63
+ "functions": 100,
64
+ "lines": 100,
65
+ "statements": 100
66
+ }
67
+ }
68
+ },
69
+ "prettier": {
70
+ "printWidth": 100,
71
+ "tabWidth": 2,
72
+ "useTabs": false,
73
+ "semi": true,
74
+ "singleQuote": false,
75
+ "quoteProps": "as-needed",
76
+ "jsxSingleQuote": false,
77
+ "trailingComma": "all",
78
+ "bracketSpacing": true,
79
+ "bracketSameLine": false,
80
+ "arrowParens": "always",
81
+ "proseWrap": "preserve",
82
+ "endOfLine": "lf"
83
+ },
84
+ "author": "Eliware <eliware@eliware.org>",
85
+ "license": "MIT",
86
+ "repository": {
87
+ "type": "git",
88
+ "url": "git+https://github.com/eliware/snowflake.git"
89
+ },
90
+ "homepage": "https://github.com/eliware/snowflake#readme",
91
+ "bugs": {
92
+ "url": "https://github.com/eliware/snowflake/issues"
93
+ },
94
+ "eliware": {
95
+ "id": "E-106",
96
+ "apply": [
97
+ "general",
98
+ "library",
99
+ "npm-published"
100
+ ]
101
+ },
102
+ "exports": {
103
+ ".": {
104
+ "types": "./src/index.d.ts",
105
+ "import": "./src/index.mjs"
57
106
  }
58
- }
107
+ },
108
+ "types": "./src/index.d.ts"
109
+ }
@@ -0,0 +1,5 @@
1
+ # Specifications
2
+
3
+ - [Directives](directives.yaml)
4
+ - [Out of scope](out-of-scope.yaml)
5
+ - [Requirements](requirements.yaml)
@@ -0,0 +1,23 @@
1
+ version: "13.0"
2
+ description: Defines behavior and limits for the Snowflake ID generator.
3
+ directives:
4
+ - id: E-106.0
5
+ dos:
6
+ - Create time-ordered Snowflake IDs.
7
+ - Return each ID as a decimal string.
8
+ - Use configurable epoch and identity values.
9
+ donts:
10
+ - Do not show generated IDs as secrets.
11
+ children:
12
+ - id: E-106.0.0
13
+ dos:
14
+ - Validate worker and process IDs before generation.
15
+ - Reject timestamps before the configured epoch.
16
+ donts:
17
+ - Do not use identity values outside the supported bit range.
18
+ - id: E-106.0.1
19
+ dos:
20
+ - Keep IDs unique when the clock returns a lower value.
21
+ - Report sequence overflow before an ID can repeat.
22
+ donts:
23
+ - Do not return an ID when the sequence overflows.
@@ -0,0 +1,10 @@
1
+ version: "13.0"
2
+ description: Defines work outside the Snowflake ID library scope.
3
+ directives:
4
+ - id: E-106.2
5
+ dos:
6
+ - Keep warehouse deployment procedures outside this library.
7
+ - Keep private release procedures outside this library.
8
+ - Keep host-specific operational commands outside this library.
9
+ donts:
10
+ - Do not store secrets in this repository.
@@ -0,0 +1,11 @@
1
+ version: "13.0"
2
+ description: Defines repository requirements for the Snowflake ID library.
3
+ directives:
4
+ - id: E-106.1
5
+ dos:
6
+ - Preserve the documented Snowflake ID behavior.
7
+ - Return IDs as decimal strings.
8
+ - Keep credentials and private data private.
9
+ - Pass repository validation before inspection.
10
+ donts:
11
+ - Do not use IDs as secrets or authentication tokens.
@@ -0,0 +1,15 @@
1
+ export const DEFAULT_EPOCH = 365662380000n;
2
+ export const SEQUENCE_BITS = 12n;
3
+ export const PROCESS_BITS = 5n;
4
+ export const WORKER_BITS = 5n;
5
+ export const PROCESS_MASK = (1n << PROCESS_BITS) - 1n;
6
+ export const WORKER_MASK = (1n << WORKER_BITS) - 1n;
7
+ export const SEQUENCE_MASK = (1n << SEQUENCE_BITS) - 1n;
8
+ export const MAX_TIMESTAMP = (1n << 42n) - 1n;
9
+
10
+ export const constants = Object.freeze({
11
+ epoch: DEFAULT_EPOCH,
12
+ sequenceBits: SEQUENCE_BITS,
13
+ processBits: PROCESS_BITS,
14
+ workerBits: WORKER_BITS,
15
+ });
@@ -0,0 +1,21 @@
1
+ import { DEFAULT_EPOCH, MAX_TIMESTAMP } from "./constants.mjs";
2
+ import { validateIdentity } from "./validation.mjs";
3
+
4
+ const defaultNow = () => Date.now();
5
+
6
+ export function validateGeneratorOptions({
7
+ epoch = DEFAULT_EPOCH,
8
+ workerId = 0,
9
+ processId = process.pid % 32,
10
+ now = defaultNow,
11
+ } = {}) {
12
+ if (typeof epoch === "number" && !Number.isSafeInteger(epoch))
13
+ throw new RangeError("epoch must be between 0 and the timestamp limit");
14
+ const baseEpoch = BigInt(epoch);
15
+ if (baseEpoch < 0n || baseEpoch > MAX_TIMESTAMP)
16
+ throw new RangeError("epoch must be between 0 and the timestamp limit");
17
+ const identity = validateIdentity(workerId, processId);
18
+ if (typeof now !== "function") throw new TypeError("clock source must be a function");
19
+
20
+ return { baseEpoch, ...identity, now };
21
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ MAX_TIMESTAMP,
3
+ PROCESS_BITS,
4
+ SEQUENCE_BITS,
5
+ SEQUENCE_MASK,
6
+ WORKER_BITS,
7
+ } from "./constants.mjs";
8
+ import { validateGeneratorOptions } from "./generator-options.mjs";
9
+
10
+ export function createGenerator(options = {}) {
11
+ const { baseEpoch, worker, processNumber, now } = validateGeneratorOptions(options);
12
+
13
+ let lastTimestamp = -1n;
14
+ let sequence = 0n;
15
+
16
+ function readTime() {
17
+ const clockValue = now();
18
+ if (typeof clockValue === "number" && !Number.isSafeInteger(clockValue))
19
+ throw new RangeError("current time must be a safe integer or bigint");
20
+ const timestamp = BigInt(clockValue);
21
+ if (timestamp < baseEpoch) throw new RangeError("current time must not be before epoch");
22
+ return timestamp;
23
+ }
24
+
25
+ return function generate() {
26
+ let timestamp = readTime();
27
+ if (timestamp < lastTimestamp) timestamp = lastTimestamp;
28
+ let nextSequence;
29
+ if (timestamp === lastTimestamp) {
30
+ if (sequence === SEQUENCE_MASK)
31
+ throw new RangeError(
32
+ "sequence overflow. Retry when the clock returns a greater timestamp.",
33
+ );
34
+ nextSequence = sequence + 1n;
35
+ } else {
36
+ nextSequence = 0n;
37
+ }
38
+ const elapsed = timestamp - baseEpoch;
39
+ if (elapsed > MAX_TIMESTAMP) throw new RangeError("timestamp exceeds Snowflake capacity");
40
+ lastTimestamp = timestamp;
41
+ sequence = nextSequence;
42
+ return (
43
+ (elapsed << (WORKER_BITS + PROCESS_BITS + SEQUENCE_BITS)) |
44
+ (worker << (PROCESS_BITS + SEQUENCE_BITS)) |
45
+ (processNumber << SEQUENCE_BITS) |
46
+ nextSequence
47
+ ).toString();
48
+ };
49
+ }
@@ -13,7 +13,7 @@ export interface SnowflakeConstants {
13
13
  }
14
14
 
15
15
  export function createSnowflakeGenerator(options?: SnowflakeGeneratorOptions): () => string;
16
- export const generate: () => string;
17
- export const snowflake: typeof generate;
18
- export const constants: SnowflakeConstants;
16
+ export declare const generate: () => string;
17
+ export declare const snowflake: typeof generate;
18
+ export declare const constants: SnowflakeConstants;
19
19
  export default generate;
package/src/index.mjs ADDED
@@ -0,0 +1,8 @@
1
+ import { constants } from "./constants.mjs";
2
+ import { createGenerator } from "./generator.mjs";
3
+
4
+ export const createSnowflakeGenerator = createGenerator;
5
+ export const generate = createGenerator();
6
+ export const snowflake = generate;
7
+ export { constants };
8
+ export default generate;
@@ -0,0 +1,16 @@
1
+ import { PROCESS_MASK, WORKER_MASK } from "./constants.mjs";
2
+
3
+ export function validatePart(value, name, mask) {
4
+ const parsed = BigInt(value);
5
+ if (parsed < 0n || parsed > mask) {
6
+ throw new RangeError(`${name} must be between 0 and ${mask}`);
7
+ }
8
+ return parsed;
9
+ }
10
+
11
+ export function validateIdentity(workerId, processId) {
12
+ return {
13
+ worker: validatePart(workerId, "workerId", WORKER_MASK),
14
+ processNumber: validatePart(processId, "processId", PROCESS_MASK),
15
+ };
16
+ }
package/index.mjs DELETED
@@ -1,85 +0,0 @@
1
- /**
2
- * BigInt-safe Snowflake ID generation.
3
- *
4
- * Layout (least significant bit first):
5
- * sequence: 12 bits
6
- * process: 5 bits
7
- * worker: 5 bits
8
- * timestamp: 42 bits
9
- */
10
- const DEFAULT_EPOCH = 365662380000n; // 1981-08-03T04:53:00.000Z (fixed EST input)
11
- const SEQUENCE_BITS = 12n;
12
- const PROCESS_BITS = 5n;
13
- const WORKER_BITS = 5n;
14
- const PROCESS_MASK = (1n << PROCESS_BITS) - 1n;
15
- const WORKER_MASK = (1n << WORKER_BITS) - 1n;
16
- const SEQUENCE_MASK = (1n << SEQUENCE_BITS) - 1n;
17
- const MAX_TIMESTAMP = (1n << 42n) - 1n;
18
-
19
- const defaultNow = () => Date.now();
20
-
21
- function validatePart(value, name, mask) {
22
- const parsed = BigInt(value);
23
- if (parsed < 0n || parsed > mask) {
24
- throw new RangeError(`${name} must be between 0 and ${mask}`);
25
- }
26
- return parsed;
27
- }
28
-
29
- /**
30
- * Create a generator. Configure a unique workerId per replica and processId
31
- * per process when generating IDs from multiple instances.
32
- */
33
- export function createSnowflakeGenerator({
34
- epoch = DEFAULT_EPOCH,
35
- workerId = 0,
36
- processId = process.pid % 32,
37
- now = defaultNow,
38
- } = {}) {
39
- const baseEpoch = BigInt(epoch);
40
- const worker = validatePart(workerId, 'workerId', WORKER_MASK);
41
- const processNumber = validatePart(processId, 'processId', PROCESS_MASK);
42
- if (typeof now !== 'function') throw new TypeError('now must be a function');
43
-
44
- let lastTimestamp = -1n;
45
- let sequence = 0n;
46
-
47
- function readTime() {
48
- const timestamp = BigInt(now());
49
- if (timestamp < baseEpoch) throw new RangeError('current time must not be before epoch');
50
- return timestamp;
51
- }
52
-
53
- return function generate() {
54
- let timestamp = readTime();
55
- if (timestamp < lastTimestamp) timestamp = lastTimestamp;
56
-
57
- if (timestamp === lastTimestamp) {
58
- sequence = (sequence + 1n) & SEQUENCE_MASK;
59
- if (sequence === 0n) {
60
- throw new RangeError('sequence overflow; clock must advance before retrying');
61
- }
62
- } else {
63
- sequence = 0n;
64
- }
65
-
66
- const elapsed = timestamp - baseEpoch;
67
- if (elapsed > MAX_TIMESTAMP) throw new RangeError('timestamp exceeds Snowflake capacity');
68
- lastTimestamp = timestamp;
69
- return ((elapsed << (WORKER_BITS + PROCESS_BITS + SEQUENCE_BITS)) |
70
- (worker << (PROCESS_BITS + SEQUENCE_BITS)) |
71
- (processNumber << SEQUENCE_BITS) |
72
- sequence).toString();
73
- };
74
- }
75
-
76
- export const generate = createSnowflakeGenerator();
77
- export const snowflake = generate;
78
- export const constants = Object.freeze({
79
- epoch: DEFAULT_EPOCH,
80
- sequenceBits: SEQUENCE_BITS,
81
- processBits: PROCESS_BITS,
82
- workerBits: WORKER_BITS,
83
- });
84
-
85
- export default generate;