@sc-voice/tools 3.34.0 → 3.35.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/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Assert } from './src/js/assert.mjs';
2
- export const JS = { Assert,
2
+ import UUIDV7 from './src/js/uuidv7.mjs';
3
+ export const JS = { Assert, UUIDV7,
3
4
  }
4
5
 
5
6
  import { Activation } from './src/math/activation.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sc-voice/tools",
3
- "version": "3.34.0",
3
+ "version": "3.35.0",
4
4
  "description": "Utilities for SC-Voice",
5
5
  "main": "index.mjs",
6
6
  "files": [
@@ -11,9 +11,8 @@
11
11
  "src"
12
12
  ],
13
13
  "scripts": {
14
- "test": "mocha --inline-diffs --recursive",
15
- "test:test": "mocha --config test/mocha-config.json --recursive --watch",
16
- "test:one": "mocha --config test/mocha-config.json --recursive"
14
+ "test": "vitest run --config test/vitest.config.mjs",
15
+ "test:test": "vitest --config test/vitest.config.mjs"
17
16
  },
18
17
  "repository": {
19
18
  "type": "git",
@@ -30,14 +29,13 @@
30
29
  "bugs": {
31
30
  "url": "https://github.com/sc-voice/nx-scv/issues"
32
31
  },
33
- "homepage": "https://github.com/sc-voice/nx-scv/packages/tools/#readme",
32
+ "homepage": "https://github.com/sc-voice/nx-scv/pkg/tools/#readme",
34
33
  "devDependencies": {
35
34
  "@biomejs/biome": "1.9.4",
36
35
  "avro-js": "^1.12.0",
37
36
  "deepl-node": "^1.15.0",
38
37
  "eslint": "^9.17.0",
39
- "mocha": "^11.0.1",
40
- "should": "^13.2.3"
38
+ "@sc-voice/vitest": "^4.0.0"
41
39
  },
42
40
  "dependencies": {
43
41
  "uuid": "^11.1.0"
@@ -0,0 +1,171 @@
1
+ import { randomBytes } from 'crypto';
2
+
3
+ // ============================================================================
4
+ // MonotonicityState - Ensures successive UUIDs are always strictly increasing
5
+ // ============================================================================
6
+
7
+ class MonotonicityState {
8
+ constructor() {
9
+ this.previousTimestamp = 0n;
10
+ this.sequence = 0;
11
+ this.offset = 0n;
12
+ }
13
+
14
+ nextMillisWithSequence(timeIntervalMs) {
15
+ // Convert to BigInt if needed
16
+ const currentTime = BigInt(Math.floor(timeIntervalMs));
17
+ let currentMillis = currentTime + this.offset;
18
+
19
+ if (this.previousTimestamp === currentMillis) {
20
+ // Same millisecond: increment sequence counter
21
+ this.sequence += 1; // Don't mask yet - check for overflow
22
+ } else if (currentMillis < this.previousTimestamp) {
23
+ // Clock went backward: increment sequence and adjust offset
24
+ this.sequence += 1;
25
+ this.offset = this.previousTimestamp - currentMillis;
26
+ currentMillis = this.previousTimestamp;
27
+ } else {
28
+ // Time advanced: reset sequence
29
+ this.offset = 0n;
30
+ this.sequence = 0;
31
+ }
32
+
33
+ // If sequence overflows 12 bits, increment time and reset sequence
34
+ if (this.sequence > 0xFFF) {
35
+ this.sequence = 0;
36
+ currentMillis += 1n;
37
+ }
38
+
39
+ // Now apply 12-bit mask for the returned value
40
+ const maskedSequence = this.sequence & 0xFFF;
41
+
42
+ this.previousTimestamp = currentMillis;
43
+ return { millis: currentMillis, sequence: maskedSequence };
44
+ }
45
+ }
46
+
47
+ // Global monotonicity state (thread-safe via single JS event loop)
48
+ const monotonicityState = new MonotonicityState();
49
+
50
+ // ============================================================================
51
+ // UUIDV7 Class
52
+ // ============================================================================
53
+
54
+ class UUIDV7 {
55
+ /**
56
+ * Creates a monotonically increasing UUIDV7.
57
+ * Two successive calls will always produce different, strictly increasing UUIDs.
58
+ *
59
+ * @returns {string} UUID in format: 019cc8b2-e91e-7000-8669-15ac4a704757
60
+ */
61
+ static create() {
62
+ const now = Date.now(); // milliseconds since epoch
63
+ const { millis, sequence } = monotonicityState.nextMillisWithSequence(now);
64
+
65
+ // Generate random bytes for the UUID
66
+ const randomBuf = randomBytes(16); // Get 16 random bytes
67
+
68
+ // Build UUID bytes (16 bytes total)
69
+ const uuid = Buffer.alloc(16);
70
+
71
+ // Bytes 0-5: timestamp in milliseconds (48 bits)
72
+ // Convert BigInt to bytes in big-endian
73
+ const millisBigInt = millis;
74
+ uuid[0] = Number((millisBigInt >> 40n) & 0xFFn);
75
+ uuid[1] = Number((millisBigInt >> 32n) & 0xFFn);
76
+ uuid[2] = Number((millisBigInt >> 24n) & 0xFFn);
77
+ uuid[3] = Number((millisBigInt >> 16n) & 0xFFn);
78
+ uuid[4] = Number((millisBigInt >> 8n) & 0xFFn);
79
+ uuid[5] = Number(millisBigInt & 0xFFn);
80
+
81
+ // Bytes 6-8: 12-bit sequence counter + version + variant
82
+ // Place 12-bit sequence as UInt16 big-endian in bytes 6-7, then apply version/variant masks
83
+ // Sequence is 12 bits: bit11-bit0, represented as 0x0000-0x0FFF
84
+ // When converted to big-endian bytes:
85
+ // bytes[6] = (sequence >> 8) & 0xFF (contains bits 11-8 in lower nibble, upper bits are 0)
86
+ // bytes[7] = sequence & 0xFF (contains bits 7-0)
87
+ uuid[6] = ((sequence >> 8) & 0x0F) | 0x70; // Upper 4 bits of sequence + version 0x7
88
+ uuid[7] = sequence & 0xFF; // Lower 8 bits of sequence
89
+
90
+ // Bytes 9-15: random data
91
+ randomBuf.copy(uuid, 9, 0, 7); // Copy 7 bytes from randomBuf[0:7] to uuid[9:16]
92
+
93
+ // Byte 8: variant (2 bits) + remaining random bits
94
+ uuid[8] = (randomBuf[7] & 0x3F) | 0x80; // Keep lower 6 bits of random + variant 0b10
95
+
96
+ return bytesToUuidString(uuid);
97
+ }
98
+
99
+ /**
100
+ * Compare two UUIDs lexicographically.
101
+ * Returns: -1 if a < b, 0 if a === b, 1 if a > b
102
+ *
103
+ * @param {string} a - UUID
104
+ * @param {string} b - UUID
105
+ * @returns {number} -1, 0, or 1
106
+ */
107
+ static compare(a, b) {
108
+ const aBytes = typeof a === 'string' ? uuidStringToBytes(a) : a;
109
+ const bBytes = typeof b === 'string' ? uuidStringToBytes(b) : b;
110
+
111
+ for (let i = 0; i < 16; i++) {
112
+ if (aBytes[i] < bBytes[i]) return -1;
113
+ if (aBytes[i] > bBytes[i]) return 1;
114
+ }
115
+ return 0;
116
+ }
117
+
118
+ /**
119
+ * Check if UUID a < UUID b
120
+ *
121
+ * @param {string} a - UUID
122
+ * @param {string} b - UUID
123
+ * @returns {boolean}
124
+ */
125
+ static isLessThan(a, b) {
126
+ return UUIDV7.compare(a, b) < 0;
127
+ }
128
+
129
+ /**
130
+ * Check if UUID a > UUID b
131
+ *
132
+ * @param {string} a - UUID
133
+ * @param {string} b - UUID
134
+ * @returns {boolean}
135
+ */
136
+ static isGreaterThan(a, b) {
137
+ return UUIDV7.compare(a, b) > 0;
138
+ }
139
+ }
140
+
141
+ export default UUIDV7;
142
+
143
+ // ============================================================================
144
+ // UUID String Formatting
145
+ // ============================================================================
146
+
147
+ /**
148
+ * Convert UUID bytes to standard string format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
149
+ */
150
+ function bytesToUuidString(bytes) {
151
+ const hex = bytes.toString('hex');
152
+ return [
153
+ hex.slice(0, 8),
154
+ hex.slice(8, 12),
155
+ hex.slice(12, 16),
156
+ hex.slice(16, 20),
157
+ hex.slice(20),
158
+ ].join('-');
159
+ }
160
+
161
+ /**
162
+ * Convert UUID string to bytes.
163
+ *
164
+ * @param {string} uuidString - UUID in format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
165
+ * @returns {Buffer} 16 bytes
166
+ */
167
+ function uuidStringToBytes(uuidString) {
168
+ const hex = uuidString.replace(/-/g, '');
169
+ return Buffer.from(hex, 'hex');
170
+ }
171
+