@venizia/ignis-docs 0.2.0 → 0.2.1-1
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 +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +8 -4
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +247 -153
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +58 -218
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +24 -23
|
@@ -1,272 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: UID
|
|
3
|
+
description: Two ID generators - time-sortable Snowflake IDs, and short random IDs a human can read back
|
|
4
|
+
difficulty: beginner
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# UID
|
|
2
8
|
|
|
3
|
-
|
|
9
|
+
Two generators, and they answer different questions.
|
|
10
|
+
|
|
11
|
+
| Helper | Reads back as | Pick it when |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `SnowflakeUidHelper` | Timestamp, worker ID, sequence | The ID is a primary key and you want insert order for free |
|
|
14
|
+
| `OpaqueUidHelper` | Nothing | The ID leaves your system - a human reads it, or a stranger sees it |
|
|
4
15
|
|
|
5
|
-
|
|
16
|
+
A Snowflake ID is transparent by design. `parseId()` gives back the exact millisecond it was minted and the worker that minted it. That is what you want in a log, and what you do not want printed on an invoice a customer keeps.
|
|
6
17
|
|
|
7
|
-
|
|
8
|
-
|------|-------|
|
|
9
|
-
| **Package** | `@venizia/ignis-helpers` |
|
|
10
|
-
| **Class** | `SnowflakeUidHelper` |
|
|
11
|
-
| **Extends** | `BaseHelper` |
|
|
12
|
-
| **Runtimes** | Both |
|
|
18
|
+
An opaque ID gives nothing back. You trade the ordering for that, and for a short ID a human can read out loud.
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
## In one example
|
|
15
21
|
|
|
16
22
|
```typescript
|
|
17
|
-
import { SnowflakeUidHelper
|
|
23
|
+
import { SnowflakeUidHelper } from '@venizia/ignis-helpers';
|
|
18
24
|
|
|
19
|
-
//
|
|
20
|
-
|
|
25
|
+
const generator = new SnowflakeUidHelper(); // workerId: 199, epoch: 2025-01-01 UTC
|
|
26
|
+
const id = generator.nextId();
|
|
27
|
+
// => e.g. "9du1sJXO88"
|
|
21
28
|
```
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| Component | Bits | Range | Purpose |
|
|
26
|
-
|-----------|------|-------|---------|
|
|
27
|
-
| Timestamp | 48 | ~8,919 years | Time since epoch |
|
|
28
|
-
| Worker ID | 10 | 0--1023 | Unique worker identifier |
|
|
29
|
-
| Sequence | 12 | 0--4095 | IDs per millisecond |
|
|
30
|
-
|
|
31
|
-
#### Key Specifications
|
|
32
|
-
|
|
33
|
-
| Spec | Value |
|
|
34
|
-
|------|-------|
|
|
35
|
-
| Base62 Output | 10--12 characters |
|
|
36
|
-
| Throughput | 4,096,000 IDs/second/worker |
|
|
37
|
-
| Max Workers | 1024 |
|
|
38
|
-
| Lifespan | Until ~10,944 AD |
|
|
39
|
-
| Default Epoch | 2025-01-01 00:00:00 UTC |
|
|
30
|
+
Use one `SnowflakeUidHelper` instance per worker process. `nextId()` returns a URL-safe Base62 string, and it sorts in the same order the IDs were generated.
|
|
40
31
|
|
|
41
|
-
##
|
|
32
|
+
## How it works
|
|
42
33
|
|
|
43
|
-
`
|
|
34
|
+
- **The ID packs three fields into 70 bits.** `nextSnowflake()` shifts and OR's together a timestamp, a worker ID, and a sequence - see the bit layout below.
|
|
35
|
+
- **Sorting the ID sorts by generation time.** This holds for the raw `bigint`, and for the Base62 string.
|
|
36
|
+
- **The sequence resets every millisecond, per worker.** It increments within the millisecond and wraps at 4096.
|
|
37
|
+
- **A wrap forces a busy-wait for the next millisecond.** That caps throughput at 4,096,000 IDs per second per worker.
|
|
38
|
+
- **A small backward clock drift busy-waits.** Up to 100ms (`MAX_CLOCK_BACKWARD_MS`), the generator waits for the clock to catch up, and logs a warning.
|
|
39
|
+
- **A larger drift throws instead.** IGNIS refuses to risk generating a duplicate ID.
|
|
40
|
+
- **`encodeBase62` / `decodeBase62` are a bigint <-> string codec.** They don't know about the Snowflake layout.
|
|
41
|
+
- **`parseId()` layers on top of the codec.** It calls `decodeBase62()`, then the three `extract*` calls, using the same instance's `epoch` and bit-shift constants.
|
|
42
|
+
- **An expiry warning logs once the epoch nears its 48-bit limit.** The timestamp field overflows about 8,919 years after the configured epoch. A warning starts logging 10 years before that.
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
import { SnowflakeUidHelper } from '@venizia/ignis-helpers';
|
|
44
|
+
**Snowflake layout (70 bits)**
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
workerId: 123,
|
|
54
|
-
epoch: BigInt(1735689600000),
|
|
55
|
-
});
|
|
56
|
-
```
|
|
46
|
+
| Component | Bits | Range | Purpose |
|
|
47
|
+
|-----------|------|-------|---------|
|
|
48
|
+
| Timestamp | 48 | ~8,919 years | Milliseconds since `epoch` |
|
|
49
|
+
| Worker ID | 10 | 0-1023 | Set per instance, must be unique across a deployment |
|
|
50
|
+
| Sequence | 12 | 0-4095 | IDs generated within the same millisecond |
|
|
57
51
|
|
|
58
|
-
|
|
52
|
+
**`IIdGeneratorOptions`**
|
|
59
53
|
|
|
60
54
|
| Option | Type | Default | Description |
|
|
61
55
|
|--------|------|---------|-------------|
|
|
62
|
-
| `workerId` | `number` | `199` |
|
|
63
|
-
| `epoch` | `bigint` | `BigInt(1735689600000)`
|
|
56
|
+
| `workerId` | `number` | `199` | Integer, `0`-`1023` |
|
|
57
|
+
| `epoch` | `bigint` | `BigInt(1735689600000)` (2025-01-01 00:00:00 UTC) | Positive, must be in the past |
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
> Use unique worker IDs in distributed systems to prevent ID collisions, and keep the epoch consistent across all instances to ensure proper ID ordering.
|
|
59
|
+
## Common tasks
|
|
67
60
|
|
|
68
|
-
|
|
61
|
+
### Generate an ID
|
|
62
|
+
|
|
63
|
+
`nextId()` is the common case - a Base62 string, 10-12 characters. `nextSnowflake()` returns the raw `bigint`, for arithmetic or bit-level access.
|
|
69
64
|
|
|
70
65
|
```typescript
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
SnowflakeConfig.DEFAULT_EPOCH // BigInt(1735689600000) - 2025-01-01 00:00:00 UTC
|
|
74
|
-
SnowflakeConfig.MAX_WORKER_ID // 1023n
|
|
75
|
-
SnowflakeConfig.MAX_SEQUENCE // 4095n
|
|
76
|
-
SnowflakeConfig.TIMESTAMP_SHIFT // 22n
|
|
77
|
-
SnowflakeConfig.WORKER_ID_SHIFT // 12n
|
|
78
|
-
|
|
79
|
-
// Bit widths
|
|
80
|
-
SnowflakeConfig.TIMESTAMP_BITS // 48n
|
|
81
|
-
SnowflakeConfig.WORKER_ID_BITS // 10n
|
|
82
|
-
SnowflakeConfig.SEQUENCE_BITS // 12n
|
|
83
|
-
|
|
84
|
-
// Safety thresholds
|
|
85
|
-
SnowflakeConfig.MAX_CLOCK_BACKWARD_MS // 100n (busy-wait tolerance)
|
|
86
|
-
SnowflakeConfig.MAX_TIMESTAMP_MS // (1n << 48n) - 1n (~8,919 years)
|
|
87
|
-
SnowflakeConfig.WARNING_THRESHOLD_MS // ~8,909 years (warns 10 years before expiry)
|
|
66
|
+
const id = generator.nextId(); // "9du1sJXO88"
|
|
67
|
+
const raw = generator.nextSnowflake(); // 130546360012247045n
|
|
88
68
|
```
|
|
89
69
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
### Generating IDs
|
|
93
|
-
|
|
94
|
-
Generate unique Snowflake IDs as either compact Base62 strings or raw bigints.
|
|
70
|
+
### Parse an ID back into its components
|
|
95
71
|
|
|
96
72
|
```typescript
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// => e.g., "9du1sJXO88"
|
|
100
|
-
|
|
101
|
-
// Generate raw Snowflake ID as bigint
|
|
102
|
-
const snowflakeId = generator.nextSnowflake();
|
|
103
|
-
// => e.g., 130546360012247045n
|
|
73
|
+
const parsed = generator.parseId('9du1sJXO88');
|
|
74
|
+
// => { raw: 130546360012247045n, timestamp: Date, workerId: 199, sequence: 0 }
|
|
104
75
|
```
|
|
105
76
|
|
|
106
|
-
|
|
107
|
-
> Use `nextId()` for most cases -- it returns a compact Base62 string (10--12 chars) that works well as database primary keys and URL-safe identifiers. Use `nextSnowflake()` only when you need the raw bigint for arithmetic or bit-level operations.
|
|
77
|
+
### Extract a single component from a raw ID
|
|
108
78
|
|
|
109
|
-
|
|
79
|
+
```typescript
|
|
80
|
+
const timestamp = generator.extractTimestamp(raw);
|
|
81
|
+
const workerId = generator.extractWorkerId(raw);
|
|
82
|
+
const sequence = generator.extractSequence(raw);
|
|
83
|
+
```
|
|
110
84
|
|
|
111
|
-
|
|
112
|
-
- Drifts up to 100ms are handled automatically by busy-waiting until the clock catches up.
|
|
113
|
-
- Drifts exceeding 100ms throw an error to protect ID uniqueness.
|
|
114
|
-
- A warning is logged whenever any backward clock movement is detected.
|
|
85
|
+
### Run one generator per worker in a distributed deployment
|
|
115
86
|
|
|
116
|
-
|
|
87
|
+
Give each process a unique `workerId`. Keep `epoch` identical across every process, so ID ordering stays meaningful.
|
|
117
88
|
|
|
118
|
-
|
|
89
|
+
```typescript
|
|
90
|
+
const generator = new SnowflakeUidHelper({ workerId: Number(process.env.WORKER_ID) });
|
|
91
|
+
```
|
|
119
92
|
|
|
120
|
-
### Base62
|
|
93
|
+
### Decode Base62 defensively
|
|
121
94
|
|
|
122
|
-
|
|
95
|
+
`decodeBase62()` throws on any character outside `0-9A-Za-z` - including a leading space or a URL-decoded `+`.
|
|
123
96
|
|
|
124
97
|
```typescript
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const decoded = generator.decodeBase62('9du1sJXO88');
|
|
131
|
-
// => 130546360012247045n
|
|
98
|
+
try {
|
|
99
|
+
const raw = generator.decodeBase62(userSuppliedId);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// not a valid Base62 ID
|
|
102
|
+
}
|
|
132
103
|
```
|
|
133
104
|
|
|
134
|
-
|
|
135
|
-
> `decodeBase62()` throws if the input contains characters outside the Base62 alphabet (`0-9`, `A-Z`, `a-z`). Watch for URL-encoded strings or accidental whitespace.
|
|
136
|
-
|
|
137
|
-
### Parsing IDs
|
|
105
|
+
## OpaqueUidHelper
|
|
138
106
|
|
|
139
|
-
|
|
107
|
+
```typescript
|
|
108
|
+
import { OpaqueUidHelper } from '@venizia/ignis-helpers';
|
|
140
109
|
|
|
141
|
-
|
|
110
|
+
const generator = new OpaqueUidHelper({
|
|
111
|
+
prefix: { enable: true, value: 'INV' },
|
|
112
|
+
delimiter: { enable: true, value: '-' },
|
|
113
|
+
length: 6,
|
|
114
|
+
});
|
|
142
115
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// => {
|
|
146
|
-
// raw: 130546360012247045n,
|
|
147
|
-
// timestamp: Date,
|
|
148
|
-
// workerId: 199,
|
|
149
|
-
// sequence: 0
|
|
150
|
-
// }
|
|
116
|
+
generator.nextId();
|
|
117
|
+
// => e.g. "INV-7K2MQ9"
|
|
151
118
|
```
|
|
152
119
|
|
|
153
|
-
|
|
120
|
+
`length` counts the body only. The prefix and the delimiter sit in front of it, so that ID is 10 characters.
|
|
154
121
|
|
|
155
|
-
|
|
156
|
-
interface ISnowflakeParsedId {
|
|
157
|
-
raw: bigint;
|
|
158
|
-
timestamp: Date;
|
|
159
|
-
workerId: number;
|
|
160
|
-
sequence: number;
|
|
161
|
-
}
|
|
162
|
-
```
|
|
122
|
+
### Options
|
|
163
123
|
|
|
164
|
-
|
|
124
|
+
| Option | Type | Default | Meaning |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `prefix` | `{ enable, value? }` | disabled | Leads the ID. `value` survives being disabled, so you can toggle it back on |
|
|
127
|
+
| `delimiter` | `{ enable, value? }` | disabled, `_` | Separates prefix from body. Requires a prefix |
|
|
128
|
+
| `length` | `number` | `6` | Characters in the body |
|
|
129
|
+
| `caseForm` | `'upper' \| 'lower' \| 'mixed'` | `'upper'` | Folds the alphabet before sampling |
|
|
130
|
+
| `alphabet` | `string` | `UidAlphabets.CROCKFORD` | The characters to draw from |
|
|
131
|
+
| `exclude` | `string` | `''` | Removed from `alphabet` |
|
|
165
132
|
|
|
166
|
-
|
|
167
|
-
const snowflakeId = generator.nextSnowflake();
|
|
133
|
+
Four alphabets ship with it:
|
|
168
134
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
135
|
+
| Alphabet | Size | Drops | Use for |
|
|
136
|
+
|---|---|---|---|
|
|
137
|
+
| `CROCKFORD` | 32 | Lowercase, `I` `L` `O` `U` | IDs a human reads, types, or says aloud |
|
|
138
|
+
| `BASE58` | 58 | `0` `O` `I` `l` | IDs in a URL or a log |
|
|
139
|
+
| `NO_VOWEL` | 50 | `BASE58` plus every vowel | Customer-facing IDs - no word can form |
|
|
140
|
+
| `BASE62` | 62 | Nothing | Machines only |
|
|
172
141
|
|
|
173
|
-
|
|
174
|
-
const workerId = generator.extractWorkerId(snowflakeId);
|
|
175
|
-
// => 199
|
|
142
|
+
`getAlphabet()` returns the set actually in use, after the case fold and every exclusion. That is what `length` is measured against, so read it before calculating how many IDs a length affords you.
|
|
176
143
|
|
|
177
|
-
|
|
178
|
-
const sequence = generator.extractSequence(snowflakeId);
|
|
179
|
-
// => 0-4095
|
|
144
|
+
### Six characters needs a unique index
|
|
180
145
|
|
|
181
|
-
|
|
182
|
-
const currentWorkerId = generator.getWorkerId();
|
|
183
|
-
// => 199
|
|
184
|
-
```
|
|
146
|
+
Six Crockford characters is 2^30 combinations, the size of an airline record locator. That works for airlines because of three things, not because 2^30 is large:
|
|
185
147
|
|
|
186
|
-
|
|
148
|
+
1. **Scoped.** A record locator is unique per carrier, not worldwide.
|
|
149
|
+
2. **Recycled.** The airline reuses it once the trip is over.
|
|
150
|
+
3. **Retried.** The reservation system regenerates on conflict.
|
|
187
151
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
| `nextId` | `nextId(): string` | Generate a Base62-encoded Snowflake ID (10--12 chars) |
|
|
191
|
-
| `nextSnowflake` | `nextSnowflake(): bigint` | Generate a raw 70-bit Snowflake ID |
|
|
192
|
-
| `encodeBase62` | `encodeBase62(num: bigint): string` | Encode a bigint to Base62 string |
|
|
193
|
-
| `decodeBase62` | `decodeBase62(str: string): bigint` | Decode a Base62 string to bigint |
|
|
194
|
-
| `parseId` | `parseId(base62Id: string): ISnowflakeParsedId` | Parse a Base62 ID into its components |
|
|
195
|
-
| `extractTimestamp` | `extractTimestamp(id: bigint): Date` | Extract the timestamp from a raw Snowflake ID |
|
|
196
|
-
| `extractWorkerId` | `extractWorkerId(id: bigint): number` | Extract the worker ID from a raw Snowflake ID |
|
|
197
|
-
| `extractSequence` | `extractSequence(id: bigint): number` | Extract the sequence number from a raw Snowflake ID |
|
|
198
|
-
| `getWorkerId` | `getWorkerId(): number` | Get the current instance's worker ID |
|
|
152
|
+
> [!WARNING]
|
|
153
|
+
> At the default length, a 1% chance of at least one collision arrives at roughly 4,600 IDs in one space, and 50% at roughly 38,000.
|
|
199
154
|
|
|
200
|
-
|
|
155
|
+
A `prefix` PARTITIONS the space. `INV-7K2MQ9` can never collide with `CUS-7K2MQ9`, so ten entity types get ten separate spaces instead of sharing one. It does not make an ID unique inside its own space - within `INV`, the numbers above still hold.
|
|
201
156
|
|
|
202
|
-
|
|
157
|
+
So do both. Give each entity type its own `prefix`, and put the column behind a unique index.
|
|
203
158
|
|
|
204
|
-
|
|
159
|
+
### Reject an ID you already hold
|
|
205
160
|
|
|
206
|
-
|
|
161
|
+
`nextId()` takes a callback. Return `true` to accept the ID, `false` to draw another.
|
|
207
162
|
|
|
208
163
|
```typescript
|
|
209
|
-
|
|
210
|
-
new
|
|
164
|
+
const issued = new Set<string>();
|
|
165
|
+
const generator = new OpaqueUidHelper({ length: 8 });
|
|
211
166
|
|
|
212
|
-
|
|
213
|
-
|
|
167
|
+
const id = generator.nextId({
|
|
168
|
+
isAvailable: candidate => !issued.has(candidate),
|
|
169
|
+
});
|
|
170
|
+
issued.add(id);
|
|
214
171
|
```
|
|
215
172
|
|
|
216
|
-
|
|
173
|
+
Every argument is optional, including the options object itself - `nextId()` alone is the common case.
|
|
217
174
|
|
|
218
|
-
|
|
175
|
+
**`nextId()` options**
|
|
219
176
|
|
|
220
|
-
|
|
177
|
+
| Option | Type | Default | Meaning |
|
|
178
|
+
|---|---|---|---|
|
|
179
|
+
| `prefix` | `string` | The configured prefix | Replaces it for this call |
|
|
180
|
+
| `isAvailable` | `(id: string) => ValueOrPromise<boolean>` | none | `true` accepts, `false` draws again |
|
|
181
|
+
| `maxAttempts` | `number` | `10` | Draws before it throws. Requires `isAvailable` |
|
|
182
|
+
|
|
183
|
+
The callback sees the finished ID, prefix and delimiter included - the string the column stores.
|
|
184
|
+
|
|
185
|
+
**The callback's return type decides `nextId`'s.** A synchronous check keeps the call synchronous, so no promise is allocated. An asynchronous one makes it a promise:
|
|
221
186
|
|
|
222
187
|
```typescript
|
|
223
|
-
|
|
224
|
-
|
|
188
|
+
const id = generator.nextId({ isAvailable: candidate => !issued.has(candidate) });
|
|
189
|
+
// string
|
|
225
190
|
|
|
226
|
-
|
|
227
|
-
|
|
191
|
+
const id = await generator.nextId({
|
|
192
|
+
isAvailable: async candidate => !(await invoiceRepository.exists(candidate)),
|
|
193
|
+
});
|
|
194
|
+
// Promise<string>
|
|
228
195
|
```
|
|
229
196
|
|
|
230
|
-
|
|
197
|
+
> [!WARNING]
|
|
198
|
+
> An asynchronous check does not make an ID safe. Against a database it is a read and then a write with a gap between them, and another request can take the ID inside that gap. `isAvailable` lowers the collision rate; the unique index is what removes it.
|
|
231
199
|
|
|
232
|
-
|
|
200
|
+
`maxAttempts` bounds the redraws. Repeated rejection means the space is full, and the helper throws with the length and alphabet size rather than looping.
|
|
233
201
|
|
|
234
|
-
|
|
202
|
+
### Regenerate on conflict, never check first
|
|
235
203
|
|
|
236
|
-
|
|
204
|
+
Insert and catch the violation. Checking whether an ID exists and then inserting it leaves a window where another request takes the same ID between the two statements.
|
|
237
205
|
|
|
238
|
-
|
|
206
|
+
```typescript
|
|
207
|
+
import { executeWithRetry, OpaqueUidHelper } from '@venizia/ignis-helpers';
|
|
239
208
|
|
|
240
|
-
|
|
209
|
+
const generator = new OpaqueUidHelper({
|
|
210
|
+
prefix: { enable: true, value: 'INV' },
|
|
211
|
+
delimiter: { enable: true, value: '-' },
|
|
212
|
+
});
|
|
241
213
|
|
|
242
|
-
|
|
214
|
+
const invoice = await executeWithRetry({
|
|
215
|
+
operation: 'createInvoice',
|
|
216
|
+
maxAttempts: 5,
|
|
217
|
+
// A fresh ID per attempt - retrying with the same one would collide forever.
|
|
218
|
+
execution: () => invoiceRepository.create({ data: { id: generator.nextId(), total } }),
|
|
219
|
+
shouldRetry: context => isUniqueViolation(context.error),
|
|
220
|
+
});
|
|
221
|
+
```
|
|
243
222
|
|
|
244
|
-
|
|
223
|
+
Five attempts is generous. At the default length a second collision on the same insert has a probability in the millionths.
|
|
245
224
|
|
|
246
|
-
|
|
225
|
+
Or raise `length` - every extra character multiplies the space by 32.
|
|
247
226
|
|
|
248
|
-
|
|
249
|
-
// Wrong
|
|
250
|
-
generator.decodeBase62('9du1sJ+O88'); // '+' is not Base62
|
|
251
|
-
generator.decodeBase62(' 9du1sJXO88'); // leading space
|
|
227
|
+
### What the constructor refuses
|
|
252
228
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
229
|
+
Each of these fails loudly at construction, because each one fails silently at runtime:
|
|
230
|
+
|
|
231
|
+
| Configuration | Why it is refused |
|
|
232
|
+
|---|---|
|
|
233
|
+
| `delimiter` enabled, `prefix` disabled | The delimiter would lead the ID and separate nothing |
|
|
234
|
+
| A delimiter character that is in the alphabet | The ID could not be split back into prefix and body |
|
|
235
|
+
| A `prefix` that disagrees with `caseForm` | A lowercase prefix on an uppercase ID defeats the reason for choosing one case |
|
|
236
|
+
| `caseForm` folding a two-case alphabet | Uppercasing `BASE58` brings `I` and `O` back - the pair it drops so `1` and `0` stay readable |
|
|
237
|
+
| An alphabet under 16 characters | `exclude` has eaten it |
|
|
238
|
+
| `maxAttempts` passed without `isAvailable` | Nothing rejects a draw, so the retry it configures cannot happen |
|
|
239
|
+
|
|
240
|
+
### Randomness
|
|
241
|
+
|
|
242
|
+
`OpaqueUidHelper` draws from `crypto.getRandomValues`, and rejects samples that fall outside the alphabet rather than wrapping them. A `byte % 58` would favour the first 24 characters by about 1.5%, forever, and no caller could see it.
|
|
256
243
|
|
|
257
|
-
|
|
244
|
+
Unlike `crypto.randomUUID`, `getRandomValues` works outside a secure context. The generator runs on a plain-http origin and inside a browser Worker.
|
|
258
245
|
|
|
259
|
-
|
|
260
|
-
- [Models](/guides/core-concepts/persistent/models) - Using UIDs as primary keys
|
|
261
|
-
- [Services](/guides/core-concepts/services) - Generating unique IDs in services
|
|
246
|
+
## See also
|
|
262
247
|
|
|
263
|
-
-
|
|
264
|
-
|
|
265
|
-
|
|
248
|
+
- [Models](/guides/core-concepts/persistent/models) - using UIDs as primary keys
|
|
249
|
+
- [Services](/guides/core-concepts/services) - generating unique IDs in services
|
|
250
|
+
- [Helpers Overview](/extensions/helpers/) - all available helpers
|
|
251
|
+
- [Crypto Helper](/extensions/helpers/crypto/) - cryptographic random values
|
|
252
|
+
- [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) - the algorithm this helper implements
|
|
266
253
|
|
|
267
|
-
|
|
268
|
-
- [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) - Snowflake algorithm explained
|
|
269
|
-
- [Base62 Encoding](https://en.wikipedia.org/wiki/Base62) - Base62 encoding overview
|
|
254
|
+
**Files:**
|
|
270
255
|
|
|
271
|
-
-
|
|
272
|
-
|
|
256
|
+
- [`packages/helpers/src/modules/uid/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/uid/helper.ts) - `SnowflakeUidHelper`, `SnowflakeConfig`
|
|
257
|
+
- [`packages/helpers/src/modules/uid/opaque.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/uid/opaque.ts) - `OpaqueUidHelper`
|
|
258
|
+
- [`packages/helpers/src/modules/uid/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/uid/common/constants.ts) - `UidAlphabets`, `UidCaseForms`
|
|
259
|
+
- [`packages/helpers/src/modules/uid/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/uid/index.ts) - module barrel
|