@eliware/snowflake 1.1.5 → 12.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,54 @@
1
+ # AGENTS.md
2
+
3
+ ## Project
4
+
5
+ Build a Snowflake ID library with Node.js 26. Use native ESM.
6
+
7
+ ## Scope and boundaries
8
+
9
+ Keep this package focused on Snowflake ID generation.
10
+ Do not use validation to publish, deploy, or change external systems.
11
+
12
+ ## Layout
13
+
14
+ Keep implementation modules under `src/` and matching tests under `tests/`.
15
+ Keep user guides under `docs/`, examples under `examples/`, and rules under `specs/`.
16
+
17
+ ## Development
18
+
19
+ Keep each module focused on one purpose.
20
+ Keep TypeScript declarations beside their matching ESM modules.
21
+
22
+ ## Validation
23
+
24
+ Use Node.js 26 and native ESM.
25
+ Run `npm test` for aggregate validation.
26
+ Run `npm run lint` for lint checks.
27
+ Run `npm run audit` for dependency checks.
28
+ Run `npm run format` to format files.
29
+ Run `npm run format:check` to check file format.
30
+ Run `npm run typecheck` to check TypeScript declarations.
31
+ Run `npm run pack` to check package contents.
32
+
33
+ ## Security
34
+
35
+ Protect credentials, tokens, secrets, private data, and machine-specific values.
36
+ Treat Snowflake IDs as identifiers, not secrets.
37
+
38
+ ## Changes
39
+
40
+ Keep docs current and direct.
41
+ Follow the Eliware Operations approval process for release changes.
42
+ Do not treat validation as release approval.
43
+
44
+ ## Library
45
+
46
+ Export the public generator API from `src/index.mjs`.
47
+ Return each generated ID as a decimal string.
48
+ Keep declarations in `src/` beside their matching ESM modules.
49
+
50
+ ## npm publication
51
+
52
+ Publish the `@eliware/snowflake` package only after release approval.
53
+ Use `package.json.version` as the package version.
54
+ Run package validation before publication review.
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,81 +1,112 @@
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
- - Configurable worker/process identity for distributed deployments.
13
- - Injectable clock for deterministic tests.
14
- - Range validation and timestamp-capacity checks.
15
- - Native ESM, TypeScript declarations, and no runtime dependencies.
26
+ Create time-ordered IDs with a custom epoch and unique worker and process IDs.
27
+ The IDs use the Discord Snowflake bit layout.
28
+ Receive decimal strings that preserve every ID bit in JavaScript and JSON.
29
+ The generator handles clock rollback and reports sequence overflow.
16
30
 
17
31
  ## Requirements
18
32
 
19
- - Node.js 26 or newer
33
+ Use Node.js 26 or later.
34
+ Assign a unique worker and process ID pair to each active generator.
20
35
 
21
- ## Installation
36
+ ## Setup
22
37
 
23
- ```bash
38
+ Install the package:
39
+
40
+ ```text
24
41
  npm install @eliware/snowflake
25
42
  ```
26
43
 
27
44
  ## Usage
28
45
 
46
+ Create a generator with a unique identity:
47
+
29
48
  ```js
30
- import generate, { createSnowflakeGenerator } from '@eliware/snowflake';
49
+ import { createSnowflakeGenerator } from "@eliware/snowflake";
31
50
 
51
+ const generate = createSnowflakeGenerator({ workerId: 3, processId: 7 });
32
52
  console.log(generate());
33
-
34
- // Set a unique worker ID for each replica or node.
35
- const replicaGenerator = createSnowflakeGenerator({
36
- workerId: 3,
37
- processId: 7,
38
- });
39
- console.log(replicaGenerator());
40
53
  ```
41
54
 
42
- For Kubernetes, assign each replica a stable `workerId` from configuration. `processId` defaults to `process.pid % 32`, but should be explicitly configured when process identity must be coordinated across hosts.
55
+ See [release notes](RELEASE_NOTES.md) for changes by version.
56
+ See the [usage guide](docs/usage.md) for options, ID fields, and limits.
57
+
58
+ ## Development
59
+
60
+ Read [user documentation](docs/README.md), [examples](examples/README.md), and [specifications](specs/README.md).
61
+
62
+ ## Testing
63
+
64
+ Run `npm test` for all required checks.
65
+ Run `npm run lint`, `npm run format:check`, `npm run audit`, `npm run typecheck`, or `npm run pack` for focused checks.
66
+
67
+ ## Troubleshooting
68
+
69
+ See [troubleshooting](docs/troubleshooting.md) for error causes and recovery steps.
70
+
71
+ ## Security
72
+
73
+ Treat IDs as identifiers, not secrets.
74
+ IDs reveal timestamp and deployment data.
75
+ Do not use IDs as authentication tokens.
43
76
 
44
77
  ## API
45
78
 
46
- `createSnowflakeGenerator(options?)` returns a function that generates decimal-string IDs.
79
+ `createSnowflakeGenerator(options)` returns a function that creates decimal-string IDs.
80
+ Options set the epoch, worker ID, process ID, and clock.
81
+ The default generator is `generate`, `snowflake`, and the default export.
82
+ The `constants` export provides the default epoch and bit sizes.
47
83
 
48
- | Option | Default | Description |
49
- | --- | --- | --- |
50
- | `epoch` | `365662380000n` | Unix epoch in milliseconds. |
51
- | `workerId` | `0` | Worker/node ID, from `0` to `31`. |
52
- | `processId` | `process.pid % 32` | Process ID, from `0` to `31`. |
53
- | `now` | `Date.now` | Injectable millisecond clock. |
84
+ ## Packaging
54
85
 
55
- The exports `generate` and `snowflake` are ready-to-use default generators. `constants` exposes the bit layout and default epoch.
86
+ The package provides native ESM code and TypeScript declarations.
87
+ It has no runtime dependencies.
56
88
 
57
- ## TypeScript
89
+ ## Examples
58
90
 
59
- TypeScript declarations are included for all public exports and options.
91
+ Run the [native ESM example](examples/example.mjs) with Node.js 26 or later.
60
92
 
61
- ## Errors / Troubleshooting
93
+ ## Support
62
94
 
63
- Configuration values outside the supported ranges throw `RangeError`; a non-function clock throws `TypeError`; timestamps before the epoch or beyond Snowflake capacity throw `RangeError`. Configure unique `workerId` and `processId` values across distributed generators.
95
+ For help, questions, or to chat with the author and community, visit:
64
96
 
65
- ## Testing and linting
97
+ [![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)
66
98
 
67
- ```bash
68
- npm test
69
- npm run test:gaps
70
- npm run lint
71
- npm run typecheck
72
- npm run pack
73
- ```
99
+ **[eliware.org on Discord](https://discord.gg/M6aTR9eTwN)**
74
100
 
75
- ## Security
101
+ ## Links
76
102
 
77
- Snowflake IDs are unique identifiers, not secrets. Do not use them as authentication tokens or assume they conceal timestamp and deployment information.
103
+ - [Home Page](https://eliware.org)
104
+ - [GitHub Org](https://github.com/eliware)
105
+ - [GitHub Repo](https://github.com/eliware/snowflake)
106
+ - [Bug Reports](https://github.com/eliware/snowflake/issues)
107
+ - [npm](https://www.npmjs.com/package/@eliware/snowflake)
108
+ - [Discord](https://discord.gg/M6aTR9eTwN)
78
109
 
79
110
  ## License
80
111
 
81
- MIT © Eli Sterling, eliware.org
112
+ [MIT © Eliware](LICENSE)
@@ -0,0 +1,65 @@
1
+ # Release Notes
2
+
3
+ ## 12.0.0 — 2026-10-12
4
+
5
+ ### Changed
6
+
7
+ - Moved library code to `src/` and added matching tests under `tests/`.
8
+ - Added user guides, navigation indexes, repository rules, and a runnable example index.
9
+ - Expanded user guides with option defaults, ID fields, identity rules, limits, and error actions.
10
+ - Updated package metadata, version, contents, license, and dependency lock.
11
+ - Added repository ID metadata and v12 specification files.
12
+ - Updated ignore rules and removed obsolete Jest result settings.
13
+ - Updated validation scripts and the shared test harness to version 12.
14
+ - Replaced the CI workflow and added an npm publication workflow with provenance.
15
+ - Updated Knit commands and its repository path.
16
+ - Updated README and contributor rules to match the v12 conventions.
17
+ - Kept the public API and decimal string output unchanged.
18
+
19
+ ## 2.0.0 — 2026-08-28
20
+
21
+ ### Added
22
+
23
+ - Added fail-fast sequence overflow handling and expanded boundary coverage.
24
+ - Added TypeScript declarations for public exports and options.
25
+
26
+ ### Changed
27
+
28
+ - Adopted `@eliware/test` for tests and lint checks.
29
+ - Updated package metadata, documentation, and CI for Node.js 26 or newer.
30
+ - Reorganized production modules under `src/` and tests under `tests/`.
31
+
32
+ ### Breaking changes
33
+
34
+ - Changed the `test` and `lint` scripts to use `@eliware/test`.
35
+
36
+ ## 1.1.5 — 2026-08-07
37
+
38
+ ### Changed
39
+
40
+ - Aligned the repository layout, scripts, documentation, and package contents.
41
+ - Added TypeScript declaration checks and package validation.
42
+ - Moved tests under `tests/` and added a runnable native ESM example.
43
+ - Removed the legacy CommonJS package entry point.
44
+
45
+ ## 1.1.4 — 2026-08-07
46
+
47
+ ### Changed
48
+
49
+ - Removed the unused `@eliware/common` runtime dependency.
50
+ - Added manual GitHub Actions workflow dispatch support.
51
+
52
+ ## 1.1.2 — 2026-08-06
53
+
54
+ ### Added
55
+
56
+ - Added a configurable BigInt-safe Snowflake ID generator.
57
+ - Added the Eliware epoch, worker IDs, and process IDs.
58
+ - Added clock rollback protection and sequence overflow handling.
59
+ - Added injectable clocks, range checks, and timestamp capacity checks.
60
+ - Added TypeScript declarations for public options and exports.
61
+
62
+ ### Changed
63
+
64
+ - Removed CommonJS entry points and tests. The package now uses native ESM.
65
+ - Expanded deployment and bit layout guidance.
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 fix 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. | Wait until the clock moves past the last timestamp, then retry. |
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
+ Wait until the clock moves past the last timestamp before you retry.
21
+ If the clock stalls, check the system clock or the `now` function.
22
+
23
+ ## Duplicate IDs
24
+
25
+ Assign a unique worker and process ID pair to each active generator.
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 through deployment configuration when several 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 fit in 42 bits.
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 represent every ID exactly.
42
+ Keep the string in JSON and in database fields that preserve 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,104 @@
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 program 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
+ Use the same epoch for all generators that create IDs for one application.
42
+ Set the epoch from zero through the timestamp limit.
43
+ Pass it as a safe integer number or a bigint.
44
+ Set `epoch` to `1420070400000n` to decode timestamps with Discord's epoch.
45
+ Assign a unique worker and process ID pair to each active generator.
46
+ Set `processId` when several hosts or processes share worker IDs.
47
+ The default process ID can repeat across hosts.
48
+
49
+ ## Understand the ID fields
50
+
51
+ Each ID has a 42-bit timestamp, a 5-bit worker ID, a 5-bit process ID, and a 12-bit sequence.
52
+ The timestamp stores milliseconds since the configured epoch.
53
+ The sequence starts at zero when the timestamp advances.
54
+ The default epoch is `365662380000n`, or `1981-08-03T04:53:00Z`.
55
+
56
+ The generator combines the fields as follows:
57
+
58
+ ```text
59
+ ((time - epoch) << 22) | (workerId << 17) | (processId << 12) | sequence
60
+ ```
61
+
62
+ The maximum elapsed timestamp is `(1n << 42n) - 1n` milliseconds.
63
+ IDs can exceed JavaScript's safe integer range.
64
+
65
+ ## Use the default generator
66
+
67
+ Use `generate`, `snowflake`, or the default export to create IDs.
68
+ These exports refer to the same generator instance.
69
+ The default generator uses worker ID `0` and process ID `process.pid % 32`.
70
+ Create a custom generator when you need a different identity, epoch, or clock.
71
+
72
+ ## Use a custom clock
73
+
74
+ Pass `now` to set the clock source.
75
+ Return a safe integer number or a bigint from that function.
76
+ The value must be Unix time in milliseconds.
77
+ Use this option for deterministic tests or a controlled clock source.
78
+
79
+ ## Handle time and sequence limits
80
+
81
+ The generator uses the last timestamp when the clock moves backward.
82
+ It increments the sequence while that timestamp remains active.
83
+ Each generator can create up to 4096 IDs for one millisecond.
84
+ The next call throws `RangeError` until the clock advances past the last timestamp.
85
+ Wait for the clock to advance before retry.
86
+ The generator throws `RangeError` for a timestamp before the epoch or beyond its 42-bit limit.
87
+
88
+ ## Store and transfer IDs
89
+
90
+ Keep each ID as a decimal string in JavaScript and JSON.
91
+ Do not convert an ID to `number`.
92
+ Use a text field or an unsigned 64-bit field in a database.
93
+ Use `BigInt(id)` when you need exact integer math in JavaScript.
94
+
95
+ ## Run the example
96
+
97
+ Use Node.js 26 or later. Run this command from the repository root:
98
+
99
+ ```text
100
+ node examples/example.mjs
101
+ ```
102
+
103
+ The command prints two decimal strings. The values depend on the current time and process ID.
104
+ You do not need cleanup.
@@ -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": "1.1.5",
5
- "description": "A minimal Node.js Snowflake ID generator (Discord-compatible). Provides BigInt-safe generators with configurable epoch and worker ID.",
6
- "types": "index.d.ts",
7
- "files": [
8
- "index.mjs",
9
- "index.d.ts",
10
- "README.md",
11
- "LICENSE",
12
- "examples"
2
+ "name": "@eliware/snowflake",
3
+ "version": "12.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": "^12.0.0",
46
+ "typescript": "^7.0.2"
47
+ },
48
+ "jest": {
49
+ "testEnvironment": "node",
50
+ "testMatch": [
51
+ "**/tests/**/*.test.mjs"
13
52
  ],
14
- "exports": {
15
- ".": {
16
- "types": "./index.d.ts",
17
- "import": "./index.mjs"
18
- }
19
- },
20
- "scripts": {
21
- "test": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --detectOpenHandles --silent --coverage",
22
- "test:gaps": "npm test 2>&1 | grep '|' | grep -vF '| 100 | 100 | 100 | 100 |'",
23
- "lint": "npx oxlint .",
24
- "typecheck": "tsc --project tsconfig.json",
25
- "pack": "npm pack --dry-run"
26
- },
27
- "repository": {
28
- "type": "git",
29
- "url": "git+https://github.com/eliware/snowflake.git"
30
- },
31
- "keywords": [
32
- "snowflake",
33
- "snowflake-id",
34
- "id-generator",
35
- "discord",
36
- "unique-id",
37
- "bigint",
38
- "nodejs",
39
- "esm"
53
+ "collectCoverageFrom": [
54
+ "src/**/*.mjs"
40
55
  ],
41
- "author": "Eli Sterling, eliware.org <eli@eliware.org>",
42
- "license": "MIT",
43
- "bugs": {
44
- "url": "https://github.com/eliware/snowflake/issues"
45
- },
46
- "homepage": "https://github.com/eliware/snowflake#readme",
47
- "devDependencies": {
48
- "jest": "^30.4.2",
49
- "typescript": "^7.0.2"
50
- },
51
- "jest": {
52
- "testEnvironment": "node",
53
- "testMatch": [
54
- "**/?(*.)+(spec|test).[jt]s?(x)",
55
- "**/?(*.)+(spec|test).mjs"
56
- ]
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,25 @@
1
+ version: "12.0"
2
+ description: Defines behavior and limits for the Snowflake ID generator.
3
+ requires: []
4
+ conflicts: []
5
+ directives:
6
+ - id: E-106.0
7
+ dos:
8
+ - Generate time-ordered Snowflake IDs.
9
+ - Return each ID as a decimal string.
10
+ - Use configurable epoch and identity values.
11
+ donts:
12
+ - Do not present generated IDs as secrets.
13
+ children:
14
+ - id: E-106.0.0
15
+ dos:
16
+ - Validate worker and process IDs before generation.
17
+ - Reject timestamps before the configured epoch.
18
+ donts:
19
+ - Do not permit identity values outside the supported bit range.
20
+ - id: E-106.0.1
21
+ dos:
22
+ - Protect generation from clock rollback.
23
+ - Report sequence overflow before an ID can repeat.
24
+ donts:
25
+ - Do not return an ID when the sequence overflows.
@@ -0,0 +1,12 @@
1
+ version: "12.0"
2
+ description: Defines work outside the Snowflake ID library scope.
3
+ requires: []
4
+ conflicts: []
5
+ directives:
6
+ - id: E-106.2
7
+ dos:
8
+ - Keep warehouse deployment procedures outside this library.
9
+ - Keep private release procedures outside this library.
10
+ - Keep host-specific operational commands outside this library.
11
+ donts:
12
+ - Do not store secrets in this repository.
@@ -0,0 +1,13 @@
1
+ version: "12.0"
2
+ description: Defines repository requirements for the Snowflake ID library.
3
+ requires: []
4
+ conflicts: []
5
+ directives:
6
+ - id: E-106.1
7
+ dos:
8
+ - Preserve the documented Snowflake ID behavior.
9
+ - Return IDs as decimal strings.
10
+ - Protect credentials and private data.
11
+ - Pass the repository validation commands before review.
12
+ donts:
13
+ - 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("now must be a function");
19
+
20
+ return { baseEpoch, ...identity, now };
21
+ }
@@ -0,0 +1,47 @@
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("sequence overflow; clock must advance before retrying");
32
+ nextSequence = sequence + 1n;
33
+ } else {
34
+ nextSequence = 0n;
35
+ }
36
+ const elapsed = timestamp - baseEpoch;
37
+ if (elapsed > MAX_TIMESTAMP) throw new RangeError("timestamp exceeds Snowflake capacity");
38
+ lastTimestamp = timestamp;
39
+ sequence = nextSequence;
40
+ return (
41
+ (elapsed << (WORKER_BITS + PROCESS_BITS + SEQUENCE_BITS)) |
42
+ (worker << (PROCESS_BITS + SEQUENCE_BITS)) |
43
+ (processNumber << SEQUENCE_BITS) |
44
+ nextSequence
45
+ ).toString();
46
+ };
47
+ }
@@ -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
- do timestamp = readTime(); while (timestamp <= lastTimestamp);
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;