@cdlab996/genid 1.3.0 → 1.4.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/README.md +227 -101
- package/README.zh-CN.md +377 -0
- package/dist/index.cjs +20 -13
- package/dist/index.d.cts +17 -3
- package/dist/index.d.mts +17 -3
- package/dist/index.mjs +20 -13
- package/package.json +7 -9
package/README.md
CHANGED
|
@@ -3,17 +3,19 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@cdlab996/genid)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
High-performance distributed unique ID generator based on the Snowflake algorithm, with drift mode and clock-rollback handling.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
[中文文档](./README.zh-CN.md)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- **时钟回拨处理** - 使用保留序列号优雅降级,不阻塞 ID 生成
|
|
12
|
-
- **灵活配置** - 支持自定义时间戳、节点 ID、序列号的位长度分配
|
|
13
|
-
- **ID 验证** - 支持严格/宽松模式校验 ID 有效性
|
|
14
|
-
- **运行监控** - 内置统计、解析和二进制格式化调试工具
|
|
10
|
+
## Features
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
- **Drift Algorithm** - Exceeds per-millisecond sequence limits under high concurrency for better throughput
|
|
13
|
+
- **Clock Rollback Handling** - Graceful degradation using reserved sequence numbers without blocking ID generation
|
|
14
|
+
- **Flexible Configuration** - Customize bit allocation for timestamp, worker ID, and sequence
|
|
15
|
+
- **ID Validation** - Strict and loose validation modes with `afterTime` support
|
|
16
|
+
- **Runtime Monitoring** - Built-in statistics, parsing, and binary formatting for debugging
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
21
|
# npm
|
|
@@ -23,25 +25,25 @@ npm install @cdlab996/genid
|
|
|
23
25
|
pnpm add @cdlab996/genid
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
##
|
|
28
|
+
## Quick Start
|
|
27
29
|
|
|
28
30
|
```typescript
|
|
29
31
|
import { GenidOptimized } from '@cdlab996/genid'
|
|
30
32
|
|
|
31
|
-
//
|
|
33
|
+
// Create an instance (use a different workerId for each worker/process)
|
|
32
34
|
const genid = new GenidOptimized({ workerId: 1 })
|
|
33
35
|
|
|
34
|
-
//
|
|
36
|
+
// Generate an ID
|
|
35
37
|
const id = genid.nextId()
|
|
36
38
|
|
|
37
|
-
//
|
|
39
|
+
// Batch generate
|
|
38
40
|
const ids = genid.nextBatch(1000)
|
|
39
41
|
|
|
40
|
-
//
|
|
42
|
+
// Parse an ID
|
|
41
43
|
const info = genid.parse(id)
|
|
42
44
|
// => { timestamp: Date, timestampMs: 1609459200000, workerId: 1, sequence: 42 }
|
|
43
45
|
|
|
44
|
-
//
|
|
46
|
+
// Validate an ID
|
|
45
47
|
genid.isValid(id) // true
|
|
46
48
|
```
|
|
47
49
|
|
|
@@ -49,48 +51,52 @@ genid.isValid(id) // true
|
|
|
49
51
|
|
|
50
52
|
### `new GenidOptimized(options)`
|
|
51
53
|
|
|
52
|
-
|
|
|
53
|
-
| ------------------- | ------------- |
|
|
54
|
-
| `workerId` | `number` |
|
|
55
|
-
| `method` | `GenidMethod` |
|
|
56
|
-
| `baseTime` | `number` |
|
|
57
|
-
| `workerIdBitLength` | `number` |
|
|
58
|
-
| `seqBitLength` | `number` |
|
|
59
|
-
| `maxSeqNumber` | `number` |
|
|
60
|
-
| `minSeqNumber` | `number` |
|
|
61
|
-
| `topOverCostCount` | `number` |
|
|
54
|
+
| Parameter | Type | Required | Default | Description |
|
|
55
|
+
| ------------------- | ------------- | :------: | ------------------ | --------------------------------------------------- |
|
|
56
|
+
| `workerId` | `number` | Yes | - | Worker node ID (0 to 2^workerIdBitLength-1) |
|
|
57
|
+
| `method` | `GenidMethod` | | `DRIFT` | Algorithm: `DRIFT` or `TRADITIONAL` |
|
|
58
|
+
| `baseTime` | `number` | | `1577836800000` | Base timestamp in ms (default: 2020-01-01) |
|
|
59
|
+
| `workerIdBitLength` | `number` | | `6` | Bit length for worker ID (1-15) |
|
|
60
|
+
| `seqBitLength` | `number` | | `6` | Bit length for sequence (3-21) |
|
|
61
|
+
| `maxSeqNumber` | `number` | | `2^seqBitLength-1` | Maximum sequence number |
|
|
62
|
+
| `minSeqNumber` | `number` | | `5` | Minimum sequence number (0-4 reserved for rollback) |
|
|
63
|
+
| `topOverCostCount` | `number` | | `2000` | Maximum drift count |
|
|
62
64
|
|
|
63
|
-
###
|
|
65
|
+
### Generating IDs
|
|
64
66
|
|
|
65
67
|
```typescript
|
|
66
|
-
genid.nextId()
|
|
67
|
-
genid.nextNumber()
|
|
68
|
-
genid.nextBigId()
|
|
69
|
-
genid.nextBatch(100)
|
|
70
|
-
genid.nextBatch(100, true) //
|
|
68
|
+
genid.nextId() // Returns number | bigint (auto-selects)
|
|
69
|
+
genid.nextNumber() // Returns number (throws if exceeds safe integer range)
|
|
70
|
+
genid.nextBigId() // Returns bigint
|
|
71
|
+
genid.nextBatch(100) // Batch generate 100 IDs
|
|
72
|
+
genid.nextBatch(100, true) // Batch generate 100 BigInt IDs
|
|
71
73
|
```
|
|
72
74
|
|
|
73
|
-
###
|
|
75
|
+
### Parsing & Validation
|
|
74
76
|
|
|
75
77
|
```typescript
|
|
76
|
-
//
|
|
78
|
+
// Parse an ID into its components
|
|
77
79
|
genid.parse(id)
|
|
78
80
|
// => { timestamp: Date, timestampMs: number, workerId: number, sequence: number }
|
|
79
81
|
|
|
80
|
-
//
|
|
81
|
-
genid.isValid(id)
|
|
82
|
-
genid.isValid(
|
|
83
|
-
|
|
82
|
+
// Loose validation: checks structural validity
|
|
83
|
+
genid.isValid(id) // true
|
|
84
|
+
genid.isValid('invalid') // false
|
|
85
|
+
|
|
86
|
+
// Strict validation: requires workerId to match the current instance
|
|
87
|
+
genid.isValid(id, true) // true (generated by this instance)
|
|
88
|
+
genid.isValid(otherId, true) // false (generated by another instance)
|
|
84
89
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
genid.isValid(
|
|
90
|
+
// Time-bound validation: reject IDs generated before a given time
|
|
91
|
+
const startupTime = Date.now()
|
|
92
|
+
genid.isValid(id, { afterTime: startupTime }) // true
|
|
93
|
+
genid.isValid(id, { strictWorkerId: true, afterTime: startupTime }) // combined
|
|
88
94
|
```
|
|
89
95
|
|
|
90
|
-
###
|
|
96
|
+
### Statistics & Configuration
|
|
91
97
|
|
|
92
98
|
```typescript
|
|
93
|
-
//
|
|
99
|
+
// Get runtime statistics
|
|
94
100
|
genid.getStats()
|
|
95
101
|
// => {
|
|
96
102
|
// totalGenerated: 1000,
|
|
@@ -101,7 +107,7 @@ genid.getStats()
|
|
|
101
107
|
// currentState: 'NORMAL' | 'OVER_COST'
|
|
102
108
|
// }
|
|
103
109
|
|
|
104
|
-
//
|
|
110
|
+
// Get current configuration
|
|
105
111
|
genid.getConfig()
|
|
106
112
|
// => {
|
|
107
113
|
// method: 'DRIFT',
|
|
@@ -116,24 +122,24 @@ genid.getConfig()
|
|
|
116
122
|
// sequenceBits: 6
|
|
117
123
|
// }
|
|
118
124
|
|
|
119
|
-
//
|
|
125
|
+
// Reset statistics
|
|
120
126
|
genid.resetStats()
|
|
121
127
|
```
|
|
122
128
|
|
|
123
|
-
###
|
|
129
|
+
### Debugging
|
|
124
130
|
|
|
125
131
|
```typescript
|
|
126
132
|
genid.formatBinary(id)
|
|
127
133
|
// ID: 123456789012345
|
|
128
134
|
// Binary (64-bit):
|
|
129
|
-
// 0000000000011010... -
|
|
130
|
-
// 000001 -
|
|
131
|
-
// 101010 -
|
|
135
|
+
// 0000000000011010... - Timestamp (52 bits) = 2025-10-17T...
|
|
136
|
+
// 000001 - Worker ID (6 bits) = 1
|
|
137
|
+
// 101010 - Sequence (6 bits) = 42
|
|
132
138
|
```
|
|
133
139
|
|
|
134
|
-
##
|
|
140
|
+
## Examples
|
|
135
141
|
|
|
136
|
-
###
|
|
142
|
+
### Custom Bit Allocation
|
|
137
143
|
|
|
138
144
|
```typescript
|
|
139
145
|
import { GenidOptimized, GenidMethod } from '@cdlab996/genid'
|
|
@@ -142,81 +148,81 @@ const genid = new GenidOptimized({
|
|
|
142
148
|
workerId: 1,
|
|
143
149
|
method: GenidMethod.TRADITIONAL,
|
|
144
150
|
baseTime: new Date('2024-01-01').valueOf(),
|
|
145
|
-
workerIdBitLength: 10, //
|
|
146
|
-
seqBitLength: 12, //
|
|
151
|
+
workerIdBitLength: 10, // Support 1024 nodes
|
|
152
|
+
seqBitLength: 12, // 4096 IDs per millisecond
|
|
147
153
|
topOverCostCount: 5000,
|
|
148
154
|
})
|
|
149
155
|
```
|
|
150
156
|
|
|
151
|
-
###
|
|
157
|
+
### Validating External IDs
|
|
152
158
|
|
|
153
159
|
```typescript
|
|
154
|
-
//
|
|
160
|
+
// Validate IDs from a database or API
|
|
155
161
|
const externalId = '123456789012345'
|
|
156
162
|
if (genid.isValid(externalId)) {
|
|
157
163
|
const info = genid.parse(externalId)
|
|
158
|
-
console.log('
|
|
159
|
-
console.log('
|
|
164
|
+
console.log('Generated at:', info.timestamp)
|
|
165
|
+
console.log('From worker:', info.workerId)
|
|
160
166
|
} else {
|
|
161
|
-
console.error('
|
|
167
|
+
console.error('Invalid ID')
|
|
162
168
|
}
|
|
163
169
|
```
|
|
164
170
|
|
|
165
|
-
###
|
|
171
|
+
### Performance Monitoring
|
|
166
172
|
|
|
167
173
|
```typescript
|
|
168
174
|
setInterval(() => {
|
|
169
175
|
const stats = genid.getStats()
|
|
170
|
-
console.log(
|
|
176
|
+
console.log(`Rate: ${stats.avgPerSecond} ID/s | Drift: ${stats.overCostCount} | Rollback: ${stats.turnBackCount}`)
|
|
171
177
|
}, 10000)
|
|
172
178
|
```
|
|
173
179
|
|
|
174
|
-
##
|
|
180
|
+
## Algorithm Modes
|
|
175
181
|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
| **DRIFT
|
|
179
|
-
| **TRADITIONAL**
|
|
182
|
+
| Mode | Description | Use Case |
|
|
183
|
+
| ------------------- | ------------------------------------------------------------------ | ----------------------------- |
|
|
184
|
+
| **DRIFT** (default) | Borrows future timestamps when sequence is exhausted; avoids waits | High-frequency ID generation |
|
|
185
|
+
| **TRADITIONAL** | Strictly increasing timestamps; waits for next ms on exhaustion | Strict time-ordering required |
|
|
180
186
|
|
|
181
|
-
##
|
|
187
|
+
## Architecture
|
|
182
188
|
|
|
183
|
-
### ID
|
|
189
|
+
### ID Structure (64-bit)
|
|
184
190
|
|
|
185
191
|
```
|
|
186
|
-
|------------
|
|
187
|
-
42-52 bits
|
|
192
|
+
|------------ Timestamp ------------|-- Worker ID --|-- Sequence --|
|
|
193
|
+
42-52 bits 1-15 bits 3-21 bits
|
|
188
194
|
```
|
|
189
195
|
|
|
190
|
-
|
|
196
|
+
Default: Timestamp 52 bits (~139 years) | Worker ID 6 bits (64 nodes) | Sequence 6 bits (59 IDs/ms)
|
|
191
197
|
|
|
192
|
-
|
|
198
|
+
Sequence values `0-4` are reserved for clock rollback; normal generation starts at `5`.
|
|
193
199
|
|
|
194
|
-
###
|
|
200
|
+
### Core Flow
|
|
195
201
|
|
|
196
202
|
```mermaid
|
|
197
203
|
graph TB
|
|
198
|
-
A[
|
|
204
|
+
A[Start ID Generation] --> B{In drift mode?}
|
|
199
205
|
|
|
200
|
-
B
|
|
201
|
-
B
|
|
206
|
+
B -->|No| C[Normal Path]
|
|
207
|
+
B -->|Yes| D[Drift Path]
|
|
202
208
|
|
|
203
|
-
C --> E{
|
|
204
|
-
E
|
|
205
|
-
E
|
|
206
|
-
E
|
|
209
|
+
C --> E{Check Clock}
|
|
210
|
+
E -->|Clock Rollback| F[Use Reserved Sequence 0-4]
|
|
211
|
+
E -->|Time Advanced| G[Reset Sequence]
|
|
212
|
+
E -->|Same Millisecond| H{Sequence Exhausted?}
|
|
207
213
|
|
|
208
|
-
H
|
|
209
|
-
H
|
|
214
|
+
H -->|No| I[Increment Sequence]
|
|
215
|
+
H -->|Yes| J[Enter Drift Mode, Timestamp+1]
|
|
210
216
|
|
|
211
|
-
D --> K{
|
|
212
|
-
K
|
|
213
|
-
K
|
|
214
|
-
K
|
|
217
|
+
D --> K{Check Time}
|
|
218
|
+
K -->|Time Caught Up| L[Exit Drift, Resume Normal]
|
|
219
|
+
K -->|Exceeded Max Drift| M[Wait for Next ms, Exit Drift]
|
|
220
|
+
K -->|Continue Drift| N{Sequence Exhausted?}
|
|
215
221
|
|
|
216
|
-
N
|
|
217
|
-
N
|
|
222
|
+
N -->|No| O[Use Current Sequence]
|
|
223
|
+
N -->|Yes| P[Timestamp+1, Reset Sequence]
|
|
218
224
|
|
|
219
|
-
F --> Q[
|
|
225
|
+
F --> Q[Assemble ID]
|
|
220
226
|
G --> Q
|
|
221
227
|
I --> Q
|
|
222
228
|
J --> Q
|
|
@@ -225,26 +231,146 @@ graph TB
|
|
|
225
231
|
O --> Q
|
|
226
232
|
P --> Q
|
|
227
233
|
|
|
228
|
-
Q --> R[
|
|
229
|
-
R --> S[
|
|
234
|
+
Q --> R[Update Statistics]
|
|
235
|
+
R --> S[Return ID]
|
|
230
236
|
```
|
|
231
237
|
|
|
232
|
-
##
|
|
238
|
+
## Performance
|
|
239
|
+
|
|
240
|
+
Throughput is determined by the number of available sequence slots per millisecond. Increasing `seqBitLength` scales linearly:
|
|
241
|
+
|
|
242
|
+
| seqBitLength | Slots/ms | Throughput |
|
|
243
|
+
| :-------------: | -------: | ------------------: |
|
|
244
|
+
| 3 | 3 | ~3,000 IDs/sec |
|
|
245
|
+
| 4 | 11 | ~11,000 IDs/sec |
|
|
246
|
+
| **6** (default) | **59** | **~58,000 IDs/sec** |
|
|
247
|
+
| 8 | 251 | ~247,000 IDs/sec |
|
|
248
|
+
| 10 | 1,019 | ~1,000,000 IDs/sec |
|
|
249
|
+
| 14 | 16,379 | ~4,500,000 IDs/sec |
|
|
250
|
+
|
|
251
|
+
> Measured on Node.js v22 (x64). Actual results vary by environment.
|
|
252
|
+
> Run `pnpm run benchmark` to probe your own machine.
|
|
253
|
+
|
|
254
|
+
| Metric | Value (default config) |
|
|
255
|
+
| ------------------------- | ---------------------: |
|
|
256
|
+
| Max worker nodes | 64 |
|
|
257
|
+
| Timestamp lifespan | ~139 years |
|
|
258
|
+
| P99 latency (single call) | < 1µs |
|
|
259
|
+
|
|
260
|
+
## Benchmark
|
|
261
|
+
|
|
262
|
+
A built-in probe script measures the actual capability of the current environment:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
pnpm run benchmark
|
|
266
|
+
```
|
|
233
267
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
-
|
|
237
|
-
|
|
238
|
-
|
|
268
|
+
It reports:
|
|
269
|
+
|
|
270
|
+
1. **Single-call throughput** — peak `nextId()` IDs/sec
|
|
271
|
+
2. **Batch throughput** — `nextBatch()` across different batch sizes
|
|
272
|
+
3. **Latency percentiles** — P50 / P95 / P99 / P99.9 / Max
|
|
273
|
+
4. **Algorithm comparison** — DRIFT vs TRADITIONAL side-by-side
|
|
274
|
+
5. **Throughput by seqBitLength** — how bit allocation affects throughput
|
|
275
|
+
6. **Memory footprint** — heap delta after 1M generations
|
|
276
|
+
7. **Recommended thresholds** — suggested safe values for test assertions (60% of peak)
|
|
277
|
+
|
|
278
|
+
<details>
|
|
279
|
+
<summary>Example output</summary>
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
station :: /app/projects/genid ‹main*› » pnpm run benchmark
|
|
283
|
+
|
|
284
|
+
> @cdlab996/genid@1.4.0 benchmark /app/projects/genid
|
|
285
|
+
> npx tsx scripts/benchmark.ts
|
|
286
|
+
|
|
287
|
+
============================================================
|
|
288
|
+
GenidOptimized — Environment Capability Probe
|
|
289
|
+
Node v22.22.0 | linux x64
|
|
290
|
+
Date: 2026-04-01T08:41:07.669Z
|
|
291
|
+
============================================================
|
|
292
|
+
|
|
293
|
+
────────────────────────────────────────────────────────────
|
|
294
|
+
1. Single-call throughput (nextId)
|
|
295
|
+
────────────────────────────────────────────────────────────
|
|
296
|
+
Duration: 3001ms
|
|
297
|
+
Generated: 226,073
|
|
298
|
+
Throughput: 75,332 IDs/sec
|
|
299
|
+
|
|
300
|
+
────────────────────────────────────────────────────────────
|
|
301
|
+
2. Batch throughput (nextBatch)
|
|
302
|
+
────────────────────────────────────────────────────────────
|
|
303
|
+
batch= 100 × 5000 => 61,665 IDs/sec
|
|
304
|
+
batch= 1,000 × 500 => 61,577 IDs/sec
|
|
305
|
+
batch= 10,000 × 50 => 61,716 IDs/sec
|
|
306
|
+
batch=100,000 × 5 => 61,644 IDs/sec
|
|
307
|
+
|
|
308
|
+
────────────────────────────────────────────────────────────
|
|
309
|
+
3. Single-call latency percentiles
|
|
310
|
+
────────────────────────────────────────────────────────────
|
|
311
|
+
Samples: 100,000
|
|
312
|
+
Avg: 0µs
|
|
313
|
+
P50: 0µs
|
|
314
|
+
P95: 1µs
|
|
315
|
+
P99: 1µs
|
|
316
|
+
P99.9: 1µs
|
|
317
|
+
Max: 364µs
|
|
318
|
+
|
|
319
|
+
────────────────────────────────────────────────────────────
|
|
320
|
+
4. Algorithm comparison (DRIFT vs TRADITIONAL)
|
|
321
|
+
────────────────────────────────────────────────────────────
|
|
322
|
+
DRIFT 58,296 IDs/sec drift=1
|
|
323
|
+
TRADITIONAL 59,000 IDs/sec drift=0
|
|
324
|
+
|
|
325
|
+
────────────────────────────────────────────────────────────
|
|
326
|
+
5. Throughput by sequence bit length
|
|
327
|
+
────────────────────────────────────────────────────────────
|
|
328
|
+
seqBits= 3 maxSeq= 7 => 2,986 IDs/sec drift=1
|
|
329
|
+
seqBits= 4 maxSeq= 15 => 10,884 IDs/sec drift=1
|
|
330
|
+
seqBits= 6 maxSeq= 63 => 58,240 IDs/sec drift=1
|
|
331
|
+
seqBits= 8 maxSeq= 255 => 247,804 IDs/sec drift=1
|
|
332
|
+
seqBits=10 maxSeq= 1023 => 1,008,930 IDs/sec drift=1
|
|
333
|
+
seqBits=14 maxSeq=16383 => 4,130,701 IDs/sec drift=0
|
|
334
|
+
|
|
335
|
+
────────────────────────────────────────────────────────────
|
|
336
|
+
6. Memory footprint
|
|
337
|
+
────────────────────────────────────────────────────────────
|
|
338
|
+
Generated: 1,000,000 IDs (not stored)
|
|
339
|
+
Heap delta: -6.27 MB
|
|
340
|
+
Note: Run with --expose-gc for accurate GC-forced measurement
|
|
341
|
+
|
|
342
|
+
────────────────────────────────────────────────────────────
|
|
343
|
+
Summary — Recommended test thresholds
|
|
344
|
+
────────────────────────────────────────────────────────────
|
|
345
|
+
Peak single-call: 75,332 IDs/sec
|
|
346
|
+
Peak batch: 61,716 IDs/sec
|
|
347
|
+
Suggested min threshold: 45,199 IDs/sec (60% of peak)
|
|
348
|
+
Suggested batch threshold: 37,029 IDs/sec (60% of peak)
|
|
349
|
+
Suggested P99 cap: 2µs (3× measured P99)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
</details>
|
|
353
|
+
|
|
354
|
+
## Development
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
pnpm install # Install dependencies
|
|
358
|
+
pnpm run build # Build (ESM + CJS)
|
|
359
|
+
pnpm run dev # Watch mode
|
|
360
|
+
pnpm run test # Run tests
|
|
361
|
+
pnpm run benchmark # Run environment capability probe
|
|
362
|
+
pnpm run typecheck # Type check
|
|
363
|
+
pnpm run lint # Lint (Biome)
|
|
364
|
+
pnpm run format # Format (Biome)
|
|
365
|
+
```
|
|
239
366
|
|
|
240
|
-
##
|
|
367
|
+
## Notes
|
|
241
368
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
| 时间戳可用时长(默认配置) | ~139 年 |
|
|
369
|
+
- Each worker/process must use a **unique workerId**
|
|
370
|
+
- Instances are **not thread-safe** — do not share across threads
|
|
371
|
+
- `workerIdBitLength + seqBitLength` must not exceed 22
|
|
372
|
+
- Sequence values 0-4 are reserved for clock-rollback handling
|
|
373
|
+
- When IDs exceed the JavaScript safe integer range (2^53-1), use `nextBigId()` or `nextId()` (auto-returns BigInt)
|
|
248
374
|
|
|
249
375
|
## License
|
|
250
376
|
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# @cdlab996/genid
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@cdlab996/genid)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
基于 Snowflake 算法的高性能分布式唯一 ID 生成器,支持漂移算法和时钟回拨处理。
|
|
7
|
+
|
|
8
|
+
[English](./README.md)
|
|
9
|
+
|
|
10
|
+
## 特性
|
|
11
|
+
|
|
12
|
+
- **漂移算法** - 高并发场景下突破每毫秒序列号上限,性能更优
|
|
13
|
+
- **时钟回拨处理** - 使用保留序列号优雅降级,不阻塞 ID 生成
|
|
14
|
+
- **灵活配置** - 支持自定义时间戳、节点 ID、序列号的位长度分配
|
|
15
|
+
- **ID 验证** - 支持严格/宽松模式校验,支持 `afterTime` 时间下限约束
|
|
16
|
+
- **运行监控** - 内置统计、解析和二进制格式化调试工具
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# npm
|
|
22
|
+
npm install @cdlab996/genid
|
|
23
|
+
|
|
24
|
+
# pnpm
|
|
25
|
+
pnpm add @cdlab996/genid
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { GenidOptimized } from '@cdlab996/genid'
|
|
32
|
+
|
|
33
|
+
// 创建实例(每个 Worker/进程使用不同的 workerId)
|
|
34
|
+
const genid = new GenidOptimized({ workerId: 1 })
|
|
35
|
+
|
|
36
|
+
// 生成 ID
|
|
37
|
+
const id = genid.nextId()
|
|
38
|
+
|
|
39
|
+
// 批量生成
|
|
40
|
+
const ids = genid.nextBatch(1000)
|
|
41
|
+
|
|
42
|
+
// 解析 ID
|
|
43
|
+
const info = genid.parse(id)
|
|
44
|
+
// => { timestamp: Date, timestampMs: 1609459200000, workerId: 1, sequence: 42 }
|
|
45
|
+
|
|
46
|
+
// 验证 ID
|
|
47
|
+
genid.isValid(id) // true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
### `new GenidOptimized(options)`
|
|
53
|
+
|
|
54
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
55
|
+
| ------------------- | ------------- | :---: | ------------------ | --------------------------------------------- |
|
|
56
|
+
| `workerId` | `number` | Yes | - | 工作节点 ID(0 ~ 2^workerIdBitLength-1) |
|
|
57
|
+
| `method` | `GenidMethod` | | `DRIFT` | 算法:`DRIFT`(漂移)或 `TRADITIONAL`(传统) |
|
|
58
|
+
| `baseTime` | `number` | | `1577836800000` | 起始时间戳,毫秒(默认 2020-01-01) |
|
|
59
|
+
| `workerIdBitLength` | `number` | | `6` | 节点 ID 位数(1-15) |
|
|
60
|
+
| `seqBitLength` | `number` | | `6` | 序列号位数(3-21) |
|
|
61
|
+
| `maxSeqNumber` | `number` | | `2^seqBitLength-1` | 最大序列号 |
|
|
62
|
+
| `minSeqNumber` | `number` | | `5` | 最小序列号(0-4 保留用于时钟回拨) |
|
|
63
|
+
| `topOverCostCount` | `number` | | `2000` | 最大漂移次数 |
|
|
64
|
+
|
|
65
|
+
### 生成 ID
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
genid.nextId() // 返回 number | bigint(自动选择)
|
|
69
|
+
genid.nextNumber() // 返回 number(超出安全整数范围抛错)
|
|
70
|
+
genid.nextBigId() // 返回 bigint
|
|
71
|
+
genid.nextBatch(100) // 批量生成 100 个 ID
|
|
72
|
+
genid.nextBatch(100, true) // 批量生成 100 个 BigInt ID
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 解析与验证
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// 解析 ID 的组成部分
|
|
79
|
+
genid.parse(id)
|
|
80
|
+
// => { timestamp: Date, timestampMs: number, workerId: number, sequence: number }
|
|
81
|
+
|
|
82
|
+
// 宽松验证:检查 ID 结构是否有效
|
|
83
|
+
genid.isValid(id) // true
|
|
84
|
+
genid.isValid('invalid') // false
|
|
85
|
+
|
|
86
|
+
// 严格验证:要求 workerId 匹配当前实例
|
|
87
|
+
genid.isValid(id, true) // true(本实例生成的 ID)
|
|
88
|
+
genid.isValid(otherId, true) // false(其他实例生成的 ID)
|
|
89
|
+
|
|
90
|
+
// 时间下限验证:拒绝早于指定时间生成的 ID
|
|
91
|
+
const startupTime = Date.now()
|
|
92
|
+
genid.isValid(id, { afterTime: startupTime }) // true
|
|
93
|
+
genid.isValid(id, { strictWorkerId: true, afterTime: startupTime }) // 组合使用
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 统计与配置
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// 获取运行统计
|
|
100
|
+
genid.getStats()
|
|
101
|
+
// => {
|
|
102
|
+
// totalGenerated: 1000,
|
|
103
|
+
// overCostCount: 10,
|
|
104
|
+
// turnBackCount: 2,
|
|
105
|
+
// uptimeMs: 60000,
|
|
106
|
+
// avgPerSecond: 16,
|
|
107
|
+
// currentState: 'NORMAL' | 'OVER_COST'
|
|
108
|
+
// }
|
|
109
|
+
|
|
110
|
+
// 获取当前配置
|
|
111
|
+
genid.getConfig()
|
|
112
|
+
// => {
|
|
113
|
+
// method: 'DRIFT',
|
|
114
|
+
// workerId: 1,
|
|
115
|
+
// workerIdRange: '0-63',
|
|
116
|
+
// sequenceRange: '5-63',
|
|
117
|
+
// maxSequence: 63,
|
|
118
|
+
// idsPerMillisecond: 59,
|
|
119
|
+
// baseTime: Date,
|
|
120
|
+
// timestampBits: 52,
|
|
121
|
+
// workerIdBits: 6,
|
|
122
|
+
// sequenceBits: 6
|
|
123
|
+
// }
|
|
124
|
+
|
|
125
|
+
// 重置统计
|
|
126
|
+
genid.resetStats()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 调试
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
genid.formatBinary(id)
|
|
133
|
+
// ID: 123456789012345
|
|
134
|
+
// Binary (64-bit):
|
|
135
|
+
// 0000000000011010... - Timestamp (52 bits) = 2025-10-17T...
|
|
136
|
+
// 000001 - Worker ID (6 bits) = 1
|
|
137
|
+
// 101010 - Sequence (6 bits) = 42
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 使用示例
|
|
141
|
+
|
|
142
|
+
### 自定义位分配
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { GenidOptimized, GenidMethod } from '@cdlab996/genid'
|
|
146
|
+
|
|
147
|
+
const genid = new GenidOptimized({
|
|
148
|
+
workerId: 1,
|
|
149
|
+
method: GenidMethod.TRADITIONAL,
|
|
150
|
+
baseTime: new Date('2024-01-01').valueOf(),
|
|
151
|
+
workerIdBitLength: 10, // 支持 1024 个节点
|
|
152
|
+
seqBitLength: 12, // 每毫秒 4096 个 ID
|
|
153
|
+
topOverCostCount: 5000,
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 验证外部 ID
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// 验证从数据库或 API 获取的 ID
|
|
161
|
+
const externalId = '123456789012345'
|
|
162
|
+
if (genid.isValid(externalId)) {
|
|
163
|
+
const info = genid.parse(externalId)
|
|
164
|
+
console.log('生成时间:', info.timestamp)
|
|
165
|
+
console.log('来自节点:', info.workerId)
|
|
166
|
+
} else {
|
|
167
|
+
console.error('无效 ID')
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 性能监控
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
setInterval(() => {
|
|
175
|
+
const stats = genid.getStats()
|
|
176
|
+
console.log(`速率: ${stats.avgPerSecond} ID/s | 漂移: ${stats.overCostCount} | 回拨: ${stats.turnBackCount}`)
|
|
177
|
+
}, 10000)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 算法模式
|
|
181
|
+
|
|
182
|
+
| 模式 | 说明 | 适用场景 |
|
|
183
|
+
| ----------------- | ---------------------------------------- | -------------------- |
|
|
184
|
+
| **DRIFT**(默认) | 序列号耗尽时借用未来时间戳,避免等待 | 高频 ID 生成、高并发 |
|
|
185
|
+
| **TRADITIONAL** | 严格按时间戳递增,序列号耗尽等待下一毫秒 | 对时间顺序严格要求 |
|
|
186
|
+
|
|
187
|
+
## 架构
|
|
188
|
+
|
|
189
|
+
### ID 结构(64-bit)
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
|------------ 时间戳 ------------|-- 工作节点 ID --|-- 序列号 --|
|
|
193
|
+
42-52 bits 1-15 bits 3-21 bits
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
默认配置:时间戳 52 bits(约 139 年)| 节点 ID 6 bits(64 个节点)| 序列号 6 bits(每毫秒 59 个 ID)
|
|
197
|
+
|
|
198
|
+
序列号 `0-4` 保留用于时钟回拨,正常使用从 `5` 开始。
|
|
199
|
+
|
|
200
|
+
### 核心流程
|
|
201
|
+
|
|
202
|
+
```mermaid
|
|
203
|
+
graph TB
|
|
204
|
+
A[开始生成 ID] --> B{是否处于漂移状态?}
|
|
205
|
+
|
|
206
|
+
B -->|否| C[正常路径]
|
|
207
|
+
B -->|是| D[漂移路径]
|
|
208
|
+
|
|
209
|
+
C --> E{检测时钟}
|
|
210
|
+
E -->|时钟回拨| F[使用保留序列号 0-4]
|
|
211
|
+
E -->|时间前进| G[重置序列号]
|
|
212
|
+
E -->|同一毫秒| H{序列号是否溢出?}
|
|
213
|
+
|
|
214
|
+
H -->|否| I[序列号+1 正常生成]
|
|
215
|
+
H -->|是| J[进入漂移状态 时间戳+1]
|
|
216
|
+
|
|
217
|
+
D --> K{检测时间}
|
|
218
|
+
K -->|时间追上| L[退出漂移 恢复正常]
|
|
219
|
+
K -->|超过最大漂移| M[等待下一毫秒 退出漂移]
|
|
220
|
+
K -->|继续漂移| N{序列号是否溢出?}
|
|
221
|
+
|
|
222
|
+
N -->|否| O[使用当前序列号]
|
|
223
|
+
N -->|是| P[时间戳+1 重置序列号]
|
|
224
|
+
|
|
225
|
+
F --> Q[计算 ID]
|
|
226
|
+
G --> Q
|
|
227
|
+
I --> Q
|
|
228
|
+
J --> Q
|
|
229
|
+
L --> Q
|
|
230
|
+
M --> Q
|
|
231
|
+
O --> Q
|
|
232
|
+
P --> Q
|
|
233
|
+
|
|
234
|
+
Q --> R[更新统计]
|
|
235
|
+
R --> S[返回 ID]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## 性能
|
|
239
|
+
|
|
240
|
+
吞吐量由每毫秒可用序列号槽位数决定,增大 `seqBitLength` 可线性提升:
|
|
241
|
+
|
|
242
|
+
| seqBitLength | 每毫秒槽位 | 吞吐量 |
|
|
243
|
+
| :-----------: | ---------: | ------------------: |
|
|
244
|
+
| 3 | 3 | ~3,000 IDs/sec |
|
|
245
|
+
| 4 | 11 | ~11,000 IDs/sec |
|
|
246
|
+
| **6**(默认) | **59** | **~58,000 IDs/sec** |
|
|
247
|
+
| 8 | 251 | ~247,000 IDs/sec |
|
|
248
|
+
| 10 | 1,019 | ~1,000,000 IDs/sec |
|
|
249
|
+
| 14 | 16,379 | ~4,500,000 IDs/sec |
|
|
250
|
+
|
|
251
|
+
> 基于 Node.js v22 (x64) 测量,实际结果因环境而异。
|
|
252
|
+
> 运行 `pnpm run benchmark` 可探测当前机器的实际能力。
|
|
253
|
+
|
|
254
|
+
| 指标 | 数值(默认配置) |
|
|
255
|
+
| -------------------- | ---------------: |
|
|
256
|
+
| 最大节点数 | 64 |
|
|
257
|
+
| 时间戳可用时长 | ~139 年 |
|
|
258
|
+
| P99 延迟(单次调用) | < 1µs |
|
|
259
|
+
|
|
260
|
+
## Benchmark
|
|
261
|
+
|
|
262
|
+
内置探测脚本,自动测量当前环境的实际性能上限:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
pnpm run benchmark
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
输出内容:
|
|
269
|
+
|
|
270
|
+
1. **单次调用吞吐量** — `nextId()` 峰值 IDs/sec
|
|
271
|
+
2. **批量吞吐量** — 不同批次大小的 `nextBatch()` 性能
|
|
272
|
+
3. **延迟分位数** — P50 / P95 / P99 / P99.9 / Max
|
|
273
|
+
4. **算法对比** — DRIFT vs TRADITIONAL 并排对比
|
|
274
|
+
5. **不同 seqBitLength 吞吐量** — 位分配对吞吐量的影响
|
|
275
|
+
6. **内存占用** — 生成 100 万 ID 后的堆内存变化
|
|
276
|
+
7. **推荐阈值** — 基于峰值 60% 给出的安全测试断言值
|
|
277
|
+
|
|
278
|
+
<details>
|
|
279
|
+
<summary>输出示例</summary>
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
station :: /app/projects/genid ‹main*› » pnpm run benchmark
|
|
283
|
+
|
|
284
|
+
> @cdlab996/genid@1.4.0 benchmark /app/projects/genid
|
|
285
|
+
> npx tsx scripts/benchmark.ts
|
|
286
|
+
|
|
287
|
+
============================================================
|
|
288
|
+
GenidOptimized — Environment Capability Probe
|
|
289
|
+
Node v22.22.0 | linux x64
|
|
290
|
+
Date: 2026-04-01T08:41:07.669Z
|
|
291
|
+
============================================================
|
|
292
|
+
|
|
293
|
+
────────────────────────────────────────────────────────────
|
|
294
|
+
1. Single-call throughput (nextId)
|
|
295
|
+
────────────────────────────────────────────────────────────
|
|
296
|
+
Duration: 3001ms
|
|
297
|
+
Generated: 226,073
|
|
298
|
+
Throughput: 75,332 IDs/sec
|
|
299
|
+
|
|
300
|
+
────────────────────────────────────────────────────────────
|
|
301
|
+
2. Batch throughput (nextBatch)
|
|
302
|
+
────────────────────────────────────────────────────────────
|
|
303
|
+
batch= 100 × 5000 => 61,665 IDs/sec
|
|
304
|
+
batch= 1,000 × 500 => 61,577 IDs/sec
|
|
305
|
+
batch= 10,000 × 50 => 61,716 IDs/sec
|
|
306
|
+
batch=100,000 × 5 => 61,644 IDs/sec
|
|
307
|
+
|
|
308
|
+
────────────────────────────────────────────────────────────
|
|
309
|
+
3. Single-call latency percentiles
|
|
310
|
+
────────────────────────────────────────────────────────────
|
|
311
|
+
Samples: 100,000
|
|
312
|
+
Avg: 0µs
|
|
313
|
+
P50: 0µs
|
|
314
|
+
P95: 1µs
|
|
315
|
+
P99: 1µs
|
|
316
|
+
P99.9: 1µs
|
|
317
|
+
Max: 364µs
|
|
318
|
+
|
|
319
|
+
────────────────────────────────────────────────────────────
|
|
320
|
+
4. Algorithm comparison (DRIFT vs TRADITIONAL)
|
|
321
|
+
────────────────────────────────────────────────────────────
|
|
322
|
+
DRIFT 58,296 IDs/sec drift=1
|
|
323
|
+
TRADITIONAL 59,000 IDs/sec drift=0
|
|
324
|
+
|
|
325
|
+
────────────────────────────────────────────────────────────
|
|
326
|
+
5. Throughput by sequence bit length
|
|
327
|
+
────────────────────────────────────────────────────────────
|
|
328
|
+
seqBits= 3 maxSeq= 7 => 2,986 IDs/sec drift=1
|
|
329
|
+
seqBits= 4 maxSeq= 15 => 10,884 IDs/sec drift=1
|
|
330
|
+
seqBits= 6 maxSeq= 63 => 58,240 IDs/sec drift=1
|
|
331
|
+
seqBits= 8 maxSeq= 255 => 247,804 IDs/sec drift=1
|
|
332
|
+
seqBits=10 maxSeq= 1023 => 1,008,930 IDs/sec drift=1
|
|
333
|
+
seqBits=14 maxSeq=16383 => 4,130,701 IDs/sec drift=0
|
|
334
|
+
|
|
335
|
+
────────────────────────────────────────────────────────────
|
|
336
|
+
6. Memory footprint
|
|
337
|
+
────────────────────────────────────────────────────────────
|
|
338
|
+
Generated: 1,000,000 IDs (not stored)
|
|
339
|
+
Heap delta: -6.27 MB
|
|
340
|
+
Note: Run with --expose-gc for accurate GC-forced measurement
|
|
341
|
+
|
|
342
|
+
────────────────────────────────────────────────────────────
|
|
343
|
+
Summary — Recommended test thresholds
|
|
344
|
+
────────────────────────────────────────────────────────────
|
|
345
|
+
Peak single-call: 75,332 IDs/sec
|
|
346
|
+
Peak batch: 61,716 IDs/sec
|
|
347
|
+
Suggested min threshold: 45,199 IDs/sec (60% of peak)
|
|
348
|
+
Suggested batch threshold: 37,029 IDs/sec (60% of peak)
|
|
349
|
+
Suggested P99 cap: 2µs (3× measured P99)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
</details>
|
|
353
|
+
|
|
354
|
+
## 开发
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
pnpm install # 安装依赖
|
|
358
|
+
pnpm run build # 构建(ESM + CJS)
|
|
359
|
+
pnpm run dev # 监听模式
|
|
360
|
+
pnpm run test # 运行测试
|
|
361
|
+
pnpm run benchmark # 运行环境性能探测
|
|
362
|
+
pnpm run typecheck # 类型检查
|
|
363
|
+
pnpm run lint # 代码检查(Biome)
|
|
364
|
+
pnpm run format # 代码格式化(Biome)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## 注意事项
|
|
368
|
+
|
|
369
|
+
- 每个 Worker/进程必须使用**不同的 workerId**
|
|
370
|
+
- 实例**非线程安全**,不要跨线程共享
|
|
371
|
+
- `workerIdBitLength + seqBitLength` 不能超过 22
|
|
372
|
+
- 序列号 0-4 保留用于时钟回拨处理
|
|
373
|
+
- 超出 JavaScript 安全整数范围(2^53-1)时,使用 `nextBigId()` 或 `nextId()`(自动返回 BigInt)
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
[MIT](./LICENSE) License © 2025-PRESENT [wudi](https://github.com/WuChenDi)
|
package/dist/index.cjs
CHANGED
|
@@ -15,16 +15,16 @@ function initConfig(options) {
|
|
|
15
15
|
const config = {
|
|
16
16
|
workerId: options.workerId,
|
|
17
17
|
method: options.method === GenidMethod.TRADITIONAL ? GenidMethod.TRADITIONAL : GenidMethod.DRIFT,
|
|
18
|
-
baseTime: options.baseTime && options.baseTime > 0 ? options.baseTime : (/* @__PURE__ */ new Date("2020-01-01")).valueOf(),
|
|
19
|
-
workerIdBitLength: options.workerIdBitLength && options.workerIdBitLength > 0 ? options.workerIdBitLength : 6,
|
|
20
|
-
seqBitLength: options.seqBitLength && options.seqBitLength > 0 ? options.seqBitLength : 6,
|
|
18
|
+
baseTime: options.baseTime != null && options.baseTime > 0 ? options.baseTime : (/* @__PURE__ */ new Date("2020-01-01")).valueOf(),
|
|
19
|
+
workerIdBitLength: options.workerIdBitLength != null && options.workerIdBitLength > 0 ? options.workerIdBitLength : 6,
|
|
20
|
+
seqBitLength: options.seqBitLength != null && options.seqBitLength > 0 ? options.seqBitLength : 6,
|
|
21
21
|
maxSeqNumber: 0,
|
|
22
22
|
minSeqNumber: 0,
|
|
23
23
|
topOverCostCount: 0
|
|
24
24
|
};
|
|
25
|
-
config.maxSeqNumber = options.maxSeqNumber && options.maxSeqNumber > 0 ? options.maxSeqNumber : (1 << config.seqBitLength) - 1;
|
|
26
|
-
config.minSeqNumber = options.minSeqNumber && options.minSeqNumber
|
|
27
|
-
config.topOverCostCount = options.topOverCostCount && options.topOverCostCount > 0 ? options.topOverCostCount : 2e3;
|
|
25
|
+
config.maxSeqNumber = options.maxSeqNumber != null && options.maxSeqNumber > 0 ? options.maxSeqNumber : (1 << config.seqBitLength) - 1;
|
|
26
|
+
config.minSeqNumber = options.minSeqNumber != null && options.minSeqNumber >= 5 ? options.minSeqNumber : 5;
|
|
27
|
+
config.topOverCostCount = options.topOverCostCount != null && options.topOverCostCount > 0 ? options.topOverCostCount : 2e3;
|
|
28
28
|
return config;
|
|
29
29
|
}
|
|
30
30
|
/** 校验配置合法性,不合法则抛出 Error */
|
|
@@ -308,10 +308,18 @@ var GenidOptimized = class {
|
|
|
308
308
|
}
|
|
309
309
|
/**
|
|
310
310
|
* 验证 ID 是否为当前配置下合法的 Snowflake ID
|
|
311
|
-
*
|
|
311
|
+
*
|
|
312
|
+
* @param options - 校验选项,或传 boolean 表示 strictWorkerId(向后兼容)
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* genid.isValid(id) // 宽松校验
|
|
316
|
+
* genid.isValid(id, true) // 要求 workerId 匹配
|
|
317
|
+
* genid.isValid(id, { strictWorkerId: true }) // 同上
|
|
318
|
+
* genid.isValid(id, { afterTime: startupTime }) // 要求 ID 生成时间晚于 startupTime
|
|
312
319
|
*/
|
|
313
|
-
isValid(id,
|
|
320
|
+
isValid(id, options = false) {
|
|
314
321
|
try {
|
|
322
|
+
const opts = typeof options === "boolean" ? { strictWorkerId: options } : options;
|
|
315
323
|
const idBigInt = BigInt(id);
|
|
316
324
|
if (idBigInt < 0n) return false;
|
|
317
325
|
if (idBigInt >= 18446744073709551616n) return false;
|
|
@@ -321,11 +329,10 @@ var GenidOptimized = class {
|
|
|
321
329
|
const sequence = idBigInt & (1n << this.seqBitLength) - 1n;
|
|
322
330
|
if (timestamp < this.baseTime) return false;
|
|
323
331
|
if (timestamp > BigInt(Date.now()) + 1000n) return false;
|
|
324
|
-
|
|
325
|
-
if (workerId
|
|
326
|
-
if (strictWorkerId && workerId !== this.workerId) return false;
|
|
327
|
-
|
|
328
|
-
if (sequence < 0n || sequence > maxSeq) return false;
|
|
332
|
+
if (opts.afterTime != null && timestamp < BigInt(opts.afterTime)) return false;
|
|
333
|
+
if (workerId > (1n << this.workerIdBitLength) - 1n) return false;
|
|
334
|
+
if (opts.strictWorkerId && workerId !== this.workerId) return false;
|
|
335
|
+
if (sequence > (1n << this.seqBitLength) - 1n) return false;
|
|
329
336
|
return true;
|
|
330
337
|
} catch {
|
|
331
338
|
return false;
|
package/dist/index.d.cts
CHANGED
|
@@ -64,6 +64,13 @@ interface StatsResult {
|
|
|
64
64
|
avgPerSecond: number;
|
|
65
65
|
currentState: 'OVER_COST' | 'NORMAL';
|
|
66
66
|
}
|
|
67
|
+
/** isValid 校验选项 */
|
|
68
|
+
interface ValidateOptions {
|
|
69
|
+
/** 为 true 时要求 workerId 匹配当前实例(默认:false) */
|
|
70
|
+
strictWorkerId?: boolean;
|
|
71
|
+
/** ID 的生成时间不得早于此时间戳/毫秒(默认:baseTime) */
|
|
72
|
+
afterTime?: number;
|
|
73
|
+
}
|
|
67
74
|
/** 配置信息 */
|
|
68
75
|
interface ConfigResult {
|
|
69
76
|
method: 'DRIFT' | 'TRADITIONAL';
|
|
@@ -154,11 +161,18 @@ declare class GenidOptimized {
|
|
|
154
161
|
getConfig(): ConfigResult;
|
|
155
162
|
/**
|
|
156
163
|
* 验证 ID 是否为当前配置下合法的 Snowflake ID
|
|
157
|
-
*
|
|
164
|
+
*
|
|
165
|
+
* @param options - 校验选项,或传 boolean 表示 strictWorkerId(向后兼容)
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* genid.isValid(id) // 宽松校验
|
|
169
|
+
* genid.isValid(id, true) // 要求 workerId 匹配
|
|
170
|
+
* genid.isValid(id, { strictWorkerId: true }) // 同上
|
|
171
|
+
* genid.isValid(id, { afterTime: startupTime }) // 要求 ID 生成时间晚于 startupTime
|
|
158
172
|
*/
|
|
159
|
-
isValid(id: number | bigint | string,
|
|
173
|
+
isValid(id: number | bigint | string, options?: boolean | ValidateOptions): boolean;
|
|
160
174
|
/** 将 ID 格式化为带标注的二进制字符串(调试用) */
|
|
161
175
|
formatBinary(id: number | bigint | string): string;
|
|
162
176
|
}
|
|
163
177
|
//#endregion
|
|
164
|
-
export { ConfigResult, GenidConfig, GenidMethod, GenidOptimized, GenidOptions, ParseResult, Stats, StatsResult };
|
|
178
|
+
export { ConfigResult, GenidConfig, GenidMethod, GenidOptimized, GenidOptions, ParseResult, Stats, StatsResult, ValidateOptions };
|
package/dist/index.d.mts
CHANGED
|
@@ -64,6 +64,13 @@ interface StatsResult {
|
|
|
64
64
|
avgPerSecond: number;
|
|
65
65
|
currentState: 'OVER_COST' | 'NORMAL';
|
|
66
66
|
}
|
|
67
|
+
/** isValid 校验选项 */
|
|
68
|
+
interface ValidateOptions {
|
|
69
|
+
/** 为 true 时要求 workerId 匹配当前实例(默认:false) */
|
|
70
|
+
strictWorkerId?: boolean;
|
|
71
|
+
/** ID 的生成时间不得早于此时间戳/毫秒(默认:baseTime) */
|
|
72
|
+
afterTime?: number;
|
|
73
|
+
}
|
|
67
74
|
/** 配置信息 */
|
|
68
75
|
interface ConfigResult {
|
|
69
76
|
method: 'DRIFT' | 'TRADITIONAL';
|
|
@@ -154,11 +161,18 @@ declare class GenidOptimized {
|
|
|
154
161
|
getConfig(): ConfigResult;
|
|
155
162
|
/**
|
|
156
163
|
* 验证 ID 是否为当前配置下合法的 Snowflake ID
|
|
157
|
-
*
|
|
164
|
+
*
|
|
165
|
+
* @param options - 校验选项,或传 boolean 表示 strictWorkerId(向后兼容)
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* genid.isValid(id) // 宽松校验
|
|
169
|
+
* genid.isValid(id, true) // 要求 workerId 匹配
|
|
170
|
+
* genid.isValid(id, { strictWorkerId: true }) // 同上
|
|
171
|
+
* genid.isValid(id, { afterTime: startupTime }) // 要求 ID 生成时间晚于 startupTime
|
|
158
172
|
*/
|
|
159
|
-
isValid(id: number | bigint | string,
|
|
173
|
+
isValid(id: number | bigint | string, options?: boolean | ValidateOptions): boolean;
|
|
160
174
|
/** 将 ID 格式化为带标注的二进制字符串(调试用) */
|
|
161
175
|
formatBinary(id: number | bigint | string): string;
|
|
162
176
|
}
|
|
163
177
|
//#endregion
|
|
164
|
-
export { ConfigResult, GenidConfig, GenidMethod, GenidOptimized, GenidOptions, ParseResult, Stats, StatsResult };
|
|
178
|
+
export { ConfigResult, GenidConfig, GenidMethod, GenidOptimized, GenidOptions, ParseResult, Stats, StatsResult, ValidateOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -14,16 +14,16 @@ function initConfig(options) {
|
|
|
14
14
|
const config = {
|
|
15
15
|
workerId: options.workerId,
|
|
16
16
|
method: options.method === GenidMethod.TRADITIONAL ? GenidMethod.TRADITIONAL : GenidMethod.DRIFT,
|
|
17
|
-
baseTime: options.baseTime && options.baseTime > 0 ? options.baseTime : (/* @__PURE__ */ new Date("2020-01-01")).valueOf(),
|
|
18
|
-
workerIdBitLength: options.workerIdBitLength && options.workerIdBitLength > 0 ? options.workerIdBitLength : 6,
|
|
19
|
-
seqBitLength: options.seqBitLength && options.seqBitLength > 0 ? options.seqBitLength : 6,
|
|
17
|
+
baseTime: options.baseTime != null && options.baseTime > 0 ? options.baseTime : (/* @__PURE__ */ new Date("2020-01-01")).valueOf(),
|
|
18
|
+
workerIdBitLength: options.workerIdBitLength != null && options.workerIdBitLength > 0 ? options.workerIdBitLength : 6,
|
|
19
|
+
seqBitLength: options.seqBitLength != null && options.seqBitLength > 0 ? options.seqBitLength : 6,
|
|
20
20
|
maxSeqNumber: 0,
|
|
21
21
|
minSeqNumber: 0,
|
|
22
22
|
topOverCostCount: 0
|
|
23
23
|
};
|
|
24
|
-
config.maxSeqNumber = options.maxSeqNumber && options.maxSeqNumber > 0 ? options.maxSeqNumber : (1 << config.seqBitLength) - 1;
|
|
25
|
-
config.minSeqNumber = options.minSeqNumber && options.minSeqNumber
|
|
26
|
-
config.topOverCostCount = options.topOverCostCount && options.topOverCostCount > 0 ? options.topOverCostCount : 2e3;
|
|
24
|
+
config.maxSeqNumber = options.maxSeqNumber != null && options.maxSeqNumber > 0 ? options.maxSeqNumber : (1 << config.seqBitLength) - 1;
|
|
25
|
+
config.minSeqNumber = options.minSeqNumber != null && options.minSeqNumber >= 5 ? options.minSeqNumber : 5;
|
|
26
|
+
config.topOverCostCount = options.topOverCostCount != null && options.topOverCostCount > 0 ? options.topOverCostCount : 2e3;
|
|
27
27
|
return config;
|
|
28
28
|
}
|
|
29
29
|
/** 校验配置合法性,不合法则抛出 Error */
|
|
@@ -307,10 +307,18 @@ var GenidOptimized = class {
|
|
|
307
307
|
}
|
|
308
308
|
/**
|
|
309
309
|
* 验证 ID 是否为当前配置下合法的 Snowflake ID
|
|
310
|
-
*
|
|
310
|
+
*
|
|
311
|
+
* @param options - 校验选项,或传 boolean 表示 strictWorkerId(向后兼容)
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* genid.isValid(id) // 宽松校验
|
|
315
|
+
* genid.isValid(id, true) // 要求 workerId 匹配
|
|
316
|
+
* genid.isValid(id, { strictWorkerId: true }) // 同上
|
|
317
|
+
* genid.isValid(id, { afterTime: startupTime }) // 要求 ID 生成时间晚于 startupTime
|
|
311
318
|
*/
|
|
312
|
-
isValid(id,
|
|
319
|
+
isValid(id, options = false) {
|
|
313
320
|
try {
|
|
321
|
+
const opts = typeof options === "boolean" ? { strictWorkerId: options } : options;
|
|
314
322
|
const idBigInt = BigInt(id);
|
|
315
323
|
if (idBigInt < 0n) return false;
|
|
316
324
|
if (idBigInt >= 18446744073709551616n) return false;
|
|
@@ -320,11 +328,10 @@ var GenidOptimized = class {
|
|
|
320
328
|
const sequence = idBigInt & (1n << this.seqBitLength) - 1n;
|
|
321
329
|
if (timestamp < this.baseTime) return false;
|
|
322
330
|
if (timestamp > BigInt(Date.now()) + 1000n) return false;
|
|
323
|
-
|
|
324
|
-
if (workerId
|
|
325
|
-
if (strictWorkerId && workerId !== this.workerId) return false;
|
|
326
|
-
|
|
327
|
-
if (sequence < 0n || sequence > maxSeq) return false;
|
|
331
|
+
if (opts.afterTime != null && timestamp < BigInt(opts.afterTime)) return false;
|
|
332
|
+
if (workerId > (1n << this.workerIdBitLength) - 1n) return false;
|
|
333
|
+
if (opts.strictWorkerId && workerId !== this.workerId) return false;
|
|
334
|
+
if (sequence > (1n << this.seqBitLength) - 1n) return false;
|
|
328
335
|
return true;
|
|
329
336
|
} catch {
|
|
330
337
|
return false;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdlab996/genid",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.4.0",
|
|
5
|
+
"description": "High-performance distributed unique ID generator based on the Snowflake algorithm",
|
|
6
6
|
"author": "wudi <wuchendi96@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"homepage": "https://github.com/WuChenDi",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
"node": ">=20"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@biomejs/biome": "^2.4.
|
|
50
|
+
"@biomejs/biome": "^2.4.10",
|
|
51
51
|
"@types/node": "^25",
|
|
52
|
-
"tsdown": "^0.21.
|
|
53
|
-
"typescript": "^
|
|
54
|
-
"vitest": "^4.1.
|
|
52
|
+
"tsdown": "^0.21.7",
|
|
53
|
+
"typescript": "^6",
|
|
54
|
+
"vitest": "^4.1.2"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public",
|
|
@@ -61,11 +61,9 @@
|
|
|
61
61
|
"build": "tsdown",
|
|
62
62
|
"dev": "tsdown --watch",
|
|
63
63
|
"test": "vitest --run",
|
|
64
|
-
"test:index": "vitest --run tests/index.test.ts",
|
|
65
|
-
"test:performance": "vitest --run tests/performance.test.ts",
|
|
66
|
-
"test:stress": "vitest --run tests/stress.test.ts",
|
|
67
64
|
"lint": "biome check",
|
|
68
65
|
"format": "biome format --write",
|
|
66
|
+
"benchmark": "npx tsx scripts/benchmark.ts",
|
|
69
67
|
"clean": "bash ./scripts/clean.sh",
|
|
70
68
|
"typecheck": "tsc --project tsconfig.json"
|
|
71
69
|
}
|