snowflake-id-monotonic 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Princetoniño Piscos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # ❄️ Snowflake ID Generator (Monotonic)
2
+
3
+ [![npm version](https://img.shields.io/npm/v/snowflake-id-monotonic.svg?style=flat-square)](https://www.npmjs.com/package/snowflake-id-monotonic)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
6
+ [![Node.js](https://img.shields.io/badge/Node.js-≥16-green?style=flat-square&logo=node.js)](https://nodejs.org/)
7
+
8
+ A performancent, monotonic Snowflake ID generator for Node.js and TypeScript applications. Inspired by Twitter's original Snowflake algorithm, time-ordered, unique IDs across distributed systems with zero dependencies,
9
+
10
+ ## Features
11
+
12
+ - **Monotonic**: IDs are always increasing, even when system clock moves backward
13
+ - **Async Support**: Non-blocking async version for concurrent environments
14
+ - **Timezone Aware**: Optional for timezone-specific decoding
15
+ - **ID Decoding**: Extracts creation timestamp from Snowflake ID base to its offset
16
+ - **Resilient**: Returns "0" on errors instead of throwing
17
+ - **TypeScript Native**: Full type definitions included
18
+ - **Flexible Configuration**: Custom epochs, machine IDs
19
+
20
+ ## 📦 Installation
21
+
22
+ ```bash
23
+ npm install snowflake-id-monotonic
24
+ # or
25
+ yarn add snowflake-id-monotonic
26
+ # or
27
+ pnpm add snowflake-id-monotonic
28
+ ```
29
+
30
+ ## Basic Usage
31
+
32
+ ```js
33
+ import { Snowflake } from 'snowflake-id-monotonic';
34
+
35
+ // Create generator with default settings
36
+ const snowflake = new Snowflake();
37
+
38
+ // Generate ID (synchronous - fastest)
39
+ const id = snowflake.GenerateID();
40
+ console.log(id); // "130303618715750401"
41
+
42
+ // Generate ID (asynchronous - non-blocking)
43
+ const asyncId = await snowflake.GenerateIDAsync();
44
+ console.log(asyncId); // "130303618715750402"
45
+
46
+ // Decode ID back to creation timestamp
47
+ const timestamp = snowflake.decodetoTimestamp(id);
48
+ console.log(timestamp); // "1745109200000" (epoch milliseconds)
49
+
50
+ // Format timestamp as Date
51
+ console.log(new Date(Number(timestamp)));
52
+ // Output: 2025-04-19T10:33:20.000Z
53
+ ```
54
+
55
+ ## Custom Config
56
+
57
+ ```js
58
+ import { Snowflake } from 'snowflake-id-monotonic';
59
+
60
+ // Create generator with custom settings
61
+ const snowflake = new Snowflake({
62
+ mid: 42, // Machine ID (0-1023)
63
+ offset: "2025-01-01T00:00:00Z", // Custom epoch (ISO string)
64
+ // offset: new Date("2025-01-01"), // Or Date object
65
+ // offset: 1735689600000, // Or milliseconds
66
+ // now: () => Date.now(), // Custom time source
67
+ });
68
+
69
+ const id = snowflake.GenerateID();
70
+ console.log(id);
71
+ ```
72
+ ## 🎯 API Reference
73
+
74
+ __new Snowflake(options?)__ Creates a new Snowflake ID generator instance.
75
+
76
+ __GenerateID(): string__ Generates a unique Snowflake ID synchronously. Returns "0" on error.
77
+
78
+ __GenerateIDAsync(): Promise(string)__ Generates a unique Snowflake ID asynchronously. Returns "0" on error.
79
+
80
+ __decodetoTimestamp(id: string, offset?): string__ Decodes a Snowflake ID back to its creation timestamp (epoch milliseconds).
81
+
82
+ ## Distributed System Setup
83
+
84
+ ```js
85
+ // Server 1
86
+ const server1 = new Snowflake({ mid: 1 });
87
+
88
+ // Server 2
89
+ const server2 = new Snowflake({ mid: 2 });
90
+
91
+ // Server 3
92
+ const server3 = new Snowflake({ mid: 3 });
93
+
94
+ // Each generates unique, non-overlapping IDs
95
+ ```
96
+
97
+ ## Database Integration
98
+ ```js
99
+ import { Snowflake } from 'snowflake-id-monotonic';
100
+
101
+ const snowflake = new Snowflake({ mid: process.env.MACHINE_ID || 1 });
102
+
103
+ // Create user with Snowflake ID
104
+ async function createUser(userData) {
105
+ const userId = await snowflake.GenerateIDAsync();
106
+ const createdAt = Number(snowflake.decodetoTimestamp(userId));
107
+
108
+ await db.users.insert({
109
+ id: userId,
110
+ ...userData,
111
+ created_at: new Date(createdAt)
112
+ });
113
+
114
+ return userId;
115
+ }
116
+ ```
117
+
118
+ ## 📈 Performance
119
+
120
+ ### Benchmarks (Node.js v24, Acer Aspire 5 WIN11 Core i5-11th Gen 16gb ram 1tb ssd)
121
+
122
+ | Method | Rate (IDs/sec) | Time per ID | Relative Speed |
123
+ |--------|----------------|-------------|----------------|
124
+ | `GenerateID()` | ~1.2 million | ~0.0008ms | 1.52x faster |
125
+ | `GenerateIDAsync()` | ~800,000 | ~0.0012ms | Baseline |
126
+ | **Concurrent (100 instances)** | ~340,000 | ~0.0029ms | System-wide |
127
+
128
+ ### Performance Characteristics
129
+
130
+ 1. **Synchronous (`GenerateID()`)**
131
+ - **Best for**: Bulk generation, single-threaded workloads
132
+ - **Overhead**: Minimal, but may block event loop briefly
133
+ - **Use when**: Generating IDs in loops or batches
134
+
135
+ 2. **Asynchronous (`GenerateIDAsync()`)**
136
+ - **Best for**: High-concurrency, I/O-bound applications
137
+ - **Overhead**: ~52% slower due to Promise scheduling
138
+ - **Use when**: In async/await contexts, web servers, real-time systems
139
+
140
+ 3. **Memory Efficiency**
141
+ - Minimal memory footprint (< 1MB for 1M IDs)
142
+ - No significant GC pressure
143
+ - Suitable for serverless and memory-constrained environments
144
+
145
+ ## 🤝 Contributing
146
+ Every snowflake matters - and so does every contribution... Whether you're fixing a typo or adding a new feature...
147
+
148
+ How You Can Help:
149
+ - 🐛 Found a bug? File an issue with the snowflake ID that caused it!
150
+ - 💡 Have an idea? Suggest features that would make your snowfall heavier
151
+ - 📚 Better docs? Help others navigate the blizzard
152
+ - 🔧 Code contribution? Check our Contributing Guide to get started
153
+
154
+ ### Fun fact: Why 'snowflake'?
155
+ Because a snowflake's shape evolves as it journeys through the air, no two will ever be the same.
156
+ Even two flakes floating side by side will each be blown through different levels
157
+ of humidity and vapour to create a shape that is truly unique. -BBC UK
158
+
159
+ ### First Time? No Problem!
160
+ - Look for issues tagged good-first-issue ❄️ - perfect for your first contribution to an open source snowstorm!
161
+
162
+ ```bash
163
+ # Quick start for contributors
164
+ git clone https://github.com/piscode/snowflake-id-monotonic.git
165
+ cd snowflake-id-monotonic
166
+ npm install
167
+ npm test
168
+
169
+ benchmark:
170
+ - path\test> node benchmark.js
171
+ - path\test> node benchmark-batch.js
172
+ ```
173
+
174
+ ### The Originals
175
+ Twitter Engineering (2010) - For inventing the Snowflake concept that powers thousands of applications today.
176
+ Their brilliant 64-bit timestamp-based design revolutionized distributed ID generation.
177
+ _Dustin Rouillard_ - For the initial JavaScript implementation that made Snowflakes accessible
178
+
179
+ ## Special Thanks To:
180
+ - The Open Source Community - For being the atmosphere where ideas can collide and form beautiful patterns
181
+ - Early Adopters - For braving the first snowfall and reporting the icy patches
@@ -0,0 +1,51 @@
1
+ export type SnowflakeOffset = number | Date | string;
2
+
3
+ export interface SnowflakeOptions
4
+ {
5
+ /** Machine ID (0–1023). Defaults to 1. */
6
+ mid?: number;
7
+ /** Custom epoch (ms since unix epoch), Date, or ISO string. Defaults to 2025-01-01T00:00:00Z. */
8
+ offset?: SnowflakeOffset;
9
+ /** Override time source: either a function returning epoch ms, or a fixed epoch ms number. */
10
+ now?: (() => number) | number;
11
+ }
12
+
13
+ export declare class Snowflake
14
+ {
15
+ static DEFAULT_OFFSET: string;
16
+ static MIN_EPOCH_AGE_MS: number;
17
+
18
+ /** Sequence number (0–4095). */
19
+ seq: number;
20
+ /** Machine ID (0–1023). */
21
+ mid: number;
22
+ /** Custom epoch in ms. */
23
+ offset: number;
24
+ /** Time source. */
25
+ now: (() => number) | number;
26
+ /** Last timestamp used (epoch ms). */
27
+ lastTime: number;
28
+
29
+ constructor(options?: SnowflakeOptions);
30
+
31
+ /**
32
+ * Generates a unique, time-ordered Snowflake ID.
33
+ *
34
+ * Note: the current implementation returns `0` on error.
35
+ */
36
+ GenerateID(): string;
37
+
38
+ /**
39
+ * Generates a unique, time-ordered Snowflake ID (non-blocking version).
40
+ *
41
+ * Note: the current implementation returns `0` on error.
42
+ */
43
+ GenerateIDAsync(): Promise<string>;
44
+
45
+ /**
46
+ * Decodes a Snowflake ID back to its creation timestamp (epoch ms).
47
+ *
48
+ * Note: the current implementation returns `0` on error.
49
+ */
50
+ decodetoTimestamp(id: string, offset?: SnowflakeOffset): string;
51
+ }
package/dist/index.js ADDED
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __typeError = (msg) => {
4
+ throw TypeError(msg);
5
+ };
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
8
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
9
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
10
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
11
+
12
+ // src/index.js
13
+ var _Snowflake_static, resolveOffsetMs_fn, _Snowflake_instances, getCurrentTime_fn, waitNextMs_fn;
14
+ var _Snowflake = class _Snowflake {
15
+ /**
16
+ * @constructor
17
+ * @param {Object} options Configuration options
18
+ * @param {number} options.mid Machine ID (0–1023)
19
+ * @param {number|Date|string} options.offset Custom epoch (ms, Date, or ISO string)
20
+ * @description creates a new Snowflake ID generator instance
21
+ */
22
+ constructor(options) {
23
+ __privateAdd(this, _Snowflake_instances);
24
+ __publicField(this, "seq", 0);
25
+ __publicField(this, "mid");
26
+ __publicField(this, "offset");
27
+ __publicField(this, "now", Date.now);
28
+ __publicField(this, "lastTime", 0);
29
+ var _a;
30
+ this.mid = (options?.mid ?? 1) & 1023;
31
+ this.offset = __privateMethod(_a = _Snowflake, _Snowflake_static, resolveOffsetMs_fn).call(_a, options?.offset);
32
+ if (!Number.isFinite(this.offset))
33
+ throw new Error("Invalid offset: must be a valid epoch in milliseconds, or a parsable DEFAULT_OFFSET ISO string.");
34
+ this.now = options?.now ?? Date.now;
35
+ const nowValue = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
36
+ if (!Number.isFinite(nowValue))
37
+ throw new Error("Invalid now: must be a function returning epoch ms, or a finite number of epoch ms.");
38
+ if (this.offset > nowValue - _Snowflake.MIN_EPOCH_AGE_MS)
39
+ throw new Error("Invalid offset: custom epoch must not be in the future.");
40
+ }
41
+ /**
42
+ * @function GenerateID
43
+ * @description Generates a unique, time-ordered Snowflake ID
44
+ * Blocking version (synchronous); Fastest for single-threaded usage, may block the event loop briefly.
45
+ * @returns {string} The generated Snowflake ID as a string
46
+ */
47
+ GenerateID() {
48
+ let time = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
49
+ try {
50
+ if (time < this.lastTime)
51
+ time = this.lastTime;
52
+ if (time === this.lastTime) {
53
+ this.seq = this.seq + 1 & 4095;
54
+ if (this.seq === 0) {
55
+ while (__privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this) <= time)
56
+ time = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
57
+ }
58
+ } else {
59
+ this.seq = 0;
60
+ }
61
+ this.lastTime = time;
62
+ const timePart = BigInt(time - this.offset) << 22n;
63
+ const midPart = BigInt(this.mid) << 12n;
64
+ const seqPart = BigInt(this.seq);
65
+ return (timePart | midPart | seqPart).toString(10);
66
+ } catch (error) {
67
+ return "0";
68
+ }
69
+ }
70
+ /**
71
+ * @function GenerateIDAsync
72
+ * @description Non-blocking version (asynchronous); Uses async/await to yield control instead of blocking event loop.
73
+ * Slightly slower due to async overhead, but better for high-concurrency scenarios.
74
+ * @returns {Promise<string>} Promise that resolves to the generated Snowflake ID as a string
75
+ */
76
+ async GenerateIDAsync() {
77
+ let time = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
78
+ try {
79
+ if (time < this.lastTime)
80
+ time = this.lastTime;
81
+ if (time === this.lastTime) {
82
+ this.seq = this.seq + 1 & 4095;
83
+ if (this.seq === 0) {
84
+ await __privateMethod(this, _Snowflake_instances, waitNextMs_fn).call(this, time);
85
+ time = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
86
+ }
87
+ } else {
88
+ this.seq = 0;
89
+ }
90
+ this.lastTime = time;
91
+ const timePart = BigInt(time - this.offset) << 22n;
92
+ const midPart = BigInt(this.mid) << 12n;
93
+ const seqPart = BigInt(this.seq);
94
+ const id = timePart | midPart | seqPart;
95
+ return id.toString(10);
96
+ } catch (error) {
97
+ return "0";
98
+ }
99
+ }
100
+ /**
101
+ * @function decodetoTimestamp
102
+ * @param {string} id The Snowflake ID to decode
103
+ * @returns {string} The timestamp (epoch ms) when the ID was generated, or "0" on error
104
+ * @description decodes a Snowflake ID back to its creation timestamp (epoch ms)
105
+ */
106
+ decodetoTimestamp(id, offset = this.offset) {
107
+ var _a;
108
+ try {
109
+ const offsetMs = __privateMethod(_a = _Snowflake, _Snowflake_static, resolveOffsetMs_fn).call(_a, offset);
110
+ if (!Number.isFinite(offsetMs))
111
+ throw new Error("Invalid offset");
112
+ const timePart = BigInt(id) >> 22n;
113
+ const timestamp = timePart + BigInt(offsetMs);
114
+ const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
115
+ if (timestamp > maxSafe)
116
+ throw new Error("Timestamp exceeds MAX_SAFE_INTEGER");
117
+ return String(Number(timestamp));
118
+ } catch (error) {
119
+ return "0";
120
+ }
121
+ }
122
+ };
123
+ _Snowflake_static = new WeakSet();
124
+ resolveOffsetMs_fn = function(offset) {
125
+ if (typeof offset === "number")
126
+ return offset;
127
+ if (offset instanceof Date)
128
+ return offset.getTime();
129
+ const parsed = Date.parse(offset ?? _Snowflake.DEFAULT_OFFSET);
130
+ return Number.isFinite(parsed) ? parsed : NaN;
131
+ };
132
+ _Snowflake_instances = new WeakSet();
133
+ /**
134
+ * Retrieves the current timestamp in milliseconds adjusted by offset
135
+ * @returns {number} Current timestamp in milliseconds adjusted by offset
136
+ */
137
+ getCurrentTime_fn = function() {
138
+ if (typeof this.now === "function")
139
+ return this.now();
140
+ if (typeof this.now === "number")
141
+ return this.now;
142
+ return Date.now();
143
+ };
144
+ /**
145
+ * @function #waitNextMs
146
+ * @param {number} time The current timestamp in milliseconds
147
+ * @returns {Promise<void>} Promise that resolves when the next millisecond is reached
148
+ * @description Non-blocking wait until the next millisecond
149
+ */
150
+ waitNextMs_fn = function(time) {
151
+ return new Promise((resolve) => {
152
+ const check = () => {
153
+ if (__privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this) > time)
154
+ resolve();
155
+ else
156
+ setTimeout(check, 0);
157
+ };
158
+ check();
159
+ });
160
+ };
161
+ __privateAdd(_Snowflake, _Snowflake_static);
162
+ __publicField(_Snowflake, "DEFAULT_OFFSET", "2000-01-01T00:00:00Z");
163
+ __publicField(_Snowflake, "MIN_EPOCH_AGE_MS", 0);
164
+ var Snowflake = _Snowflake;
165
+ module.exports = { Snowflake };
package/dist/index.mjs ADDED
@@ -0,0 +1,173 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __typeError = (msg) => {
4
+ throw TypeError(msg);
5
+ };
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
11
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
12
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
13
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
14
+
15
+ // src/index.js
16
+ var require_index = __commonJS({
17
+ "src/index.js"(exports, module) {
18
+ var _Snowflake_static, resolveOffsetMs_fn, _Snowflake_instances, getCurrentTime_fn, waitNextMs_fn;
19
+ var _Snowflake = class _Snowflake {
20
+ /**
21
+ * @constructor
22
+ * @param {Object} options Configuration options
23
+ * @param {number} options.mid Machine ID (0–1023)
24
+ * @param {number|Date|string} options.offset Custom epoch (ms, Date, or ISO string)
25
+ * @description creates a new Snowflake ID generator instance
26
+ */
27
+ constructor(options) {
28
+ __privateAdd(this, _Snowflake_instances);
29
+ __publicField(this, "seq", 0);
30
+ __publicField(this, "mid");
31
+ __publicField(this, "offset");
32
+ __publicField(this, "now", Date.now);
33
+ __publicField(this, "lastTime", 0);
34
+ var _a;
35
+ this.mid = (options?.mid ?? 1) & 1023;
36
+ this.offset = __privateMethod(_a = _Snowflake, _Snowflake_static, resolveOffsetMs_fn).call(_a, options?.offset);
37
+ if (!Number.isFinite(this.offset))
38
+ throw new Error("Invalid offset: must be a valid epoch in milliseconds, or a parsable DEFAULT_OFFSET ISO string.");
39
+ this.now = options?.now ?? Date.now;
40
+ const nowValue = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
41
+ if (!Number.isFinite(nowValue))
42
+ throw new Error("Invalid now: must be a function returning epoch ms, or a finite number of epoch ms.");
43
+ if (this.offset > nowValue - _Snowflake.MIN_EPOCH_AGE_MS)
44
+ throw new Error("Invalid offset: custom epoch must not be in the future.");
45
+ }
46
+ /**
47
+ * @function GenerateID
48
+ * @description Generates a unique, time-ordered Snowflake ID
49
+ * Blocking version (synchronous); Fastest for single-threaded usage, may block the event loop briefly.
50
+ * @returns {string} The generated Snowflake ID as a string
51
+ */
52
+ GenerateID() {
53
+ let time = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
54
+ try {
55
+ if (time < this.lastTime)
56
+ time = this.lastTime;
57
+ if (time === this.lastTime) {
58
+ this.seq = this.seq + 1 & 4095;
59
+ if (this.seq === 0) {
60
+ while (__privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this) <= time)
61
+ time = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
62
+ }
63
+ } else {
64
+ this.seq = 0;
65
+ }
66
+ this.lastTime = time;
67
+ const timePart = BigInt(time - this.offset) << 22n;
68
+ const midPart = BigInt(this.mid) << 12n;
69
+ const seqPart = BigInt(this.seq);
70
+ return (timePart | midPart | seqPart).toString(10);
71
+ } catch (error) {
72
+ return "0";
73
+ }
74
+ }
75
+ /**
76
+ * @function GenerateIDAsync
77
+ * @description Non-blocking version (asynchronous); Uses async/await to yield control instead of blocking event loop.
78
+ * Slightly slower due to async overhead, but better for high-concurrency scenarios.
79
+ * @returns {Promise<string>} Promise that resolves to the generated Snowflake ID as a string
80
+ */
81
+ async GenerateIDAsync() {
82
+ let time = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
83
+ try {
84
+ if (time < this.lastTime)
85
+ time = this.lastTime;
86
+ if (time === this.lastTime) {
87
+ this.seq = this.seq + 1 & 4095;
88
+ if (this.seq === 0) {
89
+ await __privateMethod(this, _Snowflake_instances, waitNextMs_fn).call(this, time);
90
+ time = __privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this);
91
+ }
92
+ } else {
93
+ this.seq = 0;
94
+ }
95
+ this.lastTime = time;
96
+ const timePart = BigInt(time - this.offset) << 22n;
97
+ const midPart = BigInt(this.mid) << 12n;
98
+ const seqPart = BigInt(this.seq);
99
+ const id = timePart | midPart | seqPart;
100
+ return id.toString(10);
101
+ } catch (error) {
102
+ return "0";
103
+ }
104
+ }
105
+ /**
106
+ * @function decodetoTimestamp
107
+ * @param {string} id The Snowflake ID to decode
108
+ * @returns {string} The timestamp (epoch ms) when the ID was generated, or "0" on error
109
+ * @description decodes a Snowflake ID back to its creation timestamp (epoch ms)
110
+ */
111
+ decodetoTimestamp(id, offset = this.offset) {
112
+ var _a;
113
+ try {
114
+ const offsetMs = __privateMethod(_a = _Snowflake, _Snowflake_static, resolveOffsetMs_fn).call(_a, offset);
115
+ if (!Number.isFinite(offsetMs))
116
+ throw new Error("Invalid offset");
117
+ const timePart = BigInt(id) >> 22n;
118
+ const timestamp = timePart + BigInt(offsetMs);
119
+ const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
120
+ if (timestamp > maxSafe)
121
+ throw new Error("Timestamp exceeds MAX_SAFE_INTEGER");
122
+ return String(Number(timestamp));
123
+ } catch (error) {
124
+ return "0";
125
+ }
126
+ }
127
+ };
128
+ _Snowflake_static = new WeakSet();
129
+ resolveOffsetMs_fn = function(offset) {
130
+ if (typeof offset === "number")
131
+ return offset;
132
+ if (offset instanceof Date)
133
+ return offset.getTime();
134
+ const parsed = Date.parse(offset ?? _Snowflake.DEFAULT_OFFSET);
135
+ return Number.isFinite(parsed) ? parsed : NaN;
136
+ };
137
+ _Snowflake_instances = new WeakSet();
138
+ /**
139
+ * Retrieves the current timestamp in milliseconds adjusted by offset
140
+ * @returns {number} Current timestamp in milliseconds adjusted by offset
141
+ */
142
+ getCurrentTime_fn = function() {
143
+ if (typeof this.now === "function")
144
+ return this.now();
145
+ if (typeof this.now === "number")
146
+ return this.now;
147
+ return Date.now();
148
+ };
149
+ /**
150
+ * @function #waitNextMs
151
+ * @param {number} time The current timestamp in milliseconds
152
+ * @returns {Promise<void>} Promise that resolves when the next millisecond is reached
153
+ * @description Non-blocking wait until the next millisecond
154
+ */
155
+ waitNextMs_fn = function(time) {
156
+ return new Promise((resolve) => {
157
+ const check = () => {
158
+ if (__privateMethod(this, _Snowflake_instances, getCurrentTime_fn).call(this) > time)
159
+ resolve();
160
+ else
161
+ setTimeout(check, 0);
162
+ };
163
+ check();
164
+ });
165
+ };
166
+ __privateAdd(_Snowflake, _Snowflake_static);
167
+ __publicField(_Snowflake, "DEFAULT_OFFSET", "2000-01-01T00:00:00Z");
168
+ __publicField(_Snowflake, "MIN_EPOCH_AGE_MS", 0);
169
+ var Snowflake = _Snowflake;
170
+ module.exports = { Snowflake };
171
+ }
172
+ });
173
+ export default require_index();
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "snowflake-id-monotonic",
3
+ "version": "1.0.0",
4
+ "description": "A tiny module to generate monotonic time-based 64-bit unique IDs and decode them back to their creation timestamp, inspired by Twitter Snowflake.",
5
+ "keywords": [
6
+ "snowflake",
7
+ "snowflake-id",
8
+ "snowflake-id-generator",
9
+ "monotonic-id",
10
+ "unique-id",
11
+ "id-generator",
12
+ "distributed-id",
13
+ "time-based-id"
14
+ ],
15
+ "homepage": "https://github.com/piscodev/snowflake-id-monotonic#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/piscodev/snowflake-id-monotonic/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/piscodev/snowflake-id-monotonic.git"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Princetoniño Piscos",
25
+ "type": "commonjs",
26
+
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "require": "./dist/index.js",
34
+ "import": "./dist/index.mjs"
35
+ }
36
+ },
37
+
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+
44
+ "scripts": {
45
+ "build": "node scripts/clean-dist.cjs && tsup src/index.js --format cjs,esm --out-dir dist && node scripts/copy-types.cjs",
46
+ "typecheck": "tsc -p tsconfig.json --noEmit",
47
+ "test": "npm run typecheck",
48
+ "prepublish": "npm run build && npm test"
49
+ },
50
+
51
+ "devDependencies": {
52
+ "tsup": "^8.5.1",
53
+ "typescript": "^5.9.3"
54
+ }
55
+ }