New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

scru128

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

scru128 - npm Package Compare versions

Comparing version 0.2.3 to 0.2.4

123

dist/index.d.ts
/**
* SCRU128: Sortable, Clock and Random number-based Unique identifier
*
* @example
* ```javascript
* import { scru128 } from "scru128";
*
* console.log(scru128()); // e.g. "00PGHAJ3Q9VAJ7IU6PQBHBUAK4"
* console.log(scru128()); // e.g. "00PGHAJ3Q9VAJ7KU6PQ92NVBTV"
* ```
*
* @license Apache-2.0

@@ -8,7 +16,122 @@ * @copyright 2021 LiosK

*/
/** Unix time in milliseconds at 2020-01-01 00:00:00+00:00. */
export declare const TIMESTAMP_BIAS = 1577836800000;
/**
* Represents a SCRU128 ID generator and provides an interface to do more than
* just generate a string representation.
*
* @example
* ```javascript
* import { Generator } from "scru128";
*
* const g = new Generator();
* const x = g.generate();
* console.log(x.toString());
* console.log(BigInt(x.toHex()));
* ```
*/
export declare class Generator {
/** Timestamp at last generation. */
private tsLastGen;
/** Counter at last generation. */
private counter;
/** Timestamp at last renewal of perSecRandom. */
private tsLastSec;
/** Per-second random value at last generation. */
private perSecRandom;
/** Maximum number of checking `Date.now()` until clock goes forward. */
private nClockCheckMax;
/** Returns a `k`-bit (cryptographically strong) random unsigned integer. */
private getRandomBits;
/** Generates a new SCRU128 ID object. */
generate(): Scru128Id;
}
/**
* Represents a SCRU128 ID and provides converters to/from string and numbers.
*
* @example
* ```javascript
* import { Scru128Id } from "scru128";
*
* const x = Scru128Id.fromString("00Q1D9AB6DTJNLJ80SJ42SNJ4F");
* console.log(x.toString());
*
* const y = Scru128Id.fromHex(0xd05a952ccdecef5aa01c9904e5a115n.toString(16));
* console.log(BigInt(y.toHex()));
* ```
*/
export declare class Scru128Id {
readonly timestamp: number;
readonly counter: number;
readonly perSecRandom: number;
readonly perGenRandom: number;
/** Creates an object from field values. */
private constructor();
/**
* Creates an object from field values.
*
* @param timestamp - 44-bit millisecond timestamp field.
* @param counter - 28-bit per-millisecond counter field.
* @param perSecRandom - 24-bit per-second randomness field.
* @param perGenRandom - 32-bit per-generation randomness field.
* @throws RangeError if any argument is out of the range of each field.
* @category Conversion
*/
static fromFields(timestamp: number, counter: number, perSecRandom: number, perGenRandom: number): Scru128Id;
/**
* Creates an object from a 26-digit string representation.
*
* @throws SyntaxError if the argument is not a valid string representation.
* @category Conversion
*/
static fromString(value: string): Scru128Id;
/**
* Returns the 26-digit canonical string representation.
*
* @category Conversion
*/
toString(): string;
/**
* Creates an object from a 128-bit unsigned integer encoded in a hexadecimal
* string.
*
* @throws SyntaxError if the argument is not a hexadecimal string encoding a
* 128-bit unsigned integer.
* @category Conversion
*/
static fromHex(value: string): Scru128Id;
/**
* Returns the 128-bit unsigned integer representation as a 32-digit
* hexadecimal string prefixed with "0x".
*
* @category Conversion
*/
toHex(): string;
/** Represents `this` in JSON as a 26-digit canonical string. */
toJSON(): string;
/** Creates an object from `this`. */
clone(): Scru128Id;
/** Returns true if `this` is equivalent to `other`. */
equals(other: Scru128Id): boolean;
/**
* Returns a negative integer, zero, and positive integer if `this` is less
* than, equal to, and greater than `other`, respectively.
*/
compareTo(other: Scru128Id): number;
}
/**
* Generates a new SCRU128 ID encoded in a string.
*
* Use this function to quickly get a new SCRU128 ID as a string. Use
* [[Generator]] to do more.
*
* @returns 26-digit canonical string representation.
* @example
* ```javascript
* import { scru128 } from "scru128";
*
* const x = scru128();
* console.log(x);
* ```
*/
export declare const scru128: () => string;

140

dist/index.js

@@ -5,2 +5,10 @@ "use strict";

*
* @example
* ```javascript
* import { scru128 } from "scru128";
*
* console.log(scru128()); // e.g. "00PGHAJ3Q9VAJ7IU6PQBHBUAK4"
* console.log(scru128()); // e.g. "00PGHAJ3Q9VAJ7KU6PQ92NVBTV"
* ```
*
* @license Apache-2.0

@@ -11,8 +19,10 @@ * @copyright 2021 LiosK

Object.defineProperty(exports, "__esModule", { value: true });
exports._internal = exports.scru128 = void 0;
exports._internal = exports.scru128 = exports.Scru128Id = exports.Generator = exports.TIMESTAMP_BIAS = void 0;
const crypto_1 = require("crypto");
/** Unix time in milliseconds as at 2020-01-01 00:00:00+00:00. */
const TIMESTAMP_EPOCH = 1577836800000; // Date.UTC(2020, 0)
/** Unix time in milliseconds at 2020-01-01 00:00:00+00:00. */
exports.TIMESTAMP_BIAS = 1577836800000; // Date.UTC(2020, 0)
/** Maximum value of 28-bit counter field. */
const MAX_COUNTER = 268435455;
/** Leading zeros to polyfill padStart(n, "0") with slice(-n). */
const PAD_ZEROS = "0000000000000000";
/** Returns a random bit generator based on available cryptographic RNG. */

@@ -43,2 +53,12 @@ const detectRng = () => {

* just generate a string representation.
*
* @example
* ```javascript
* import { Generator } from "scru128";
*
* const g = new Generator();
* const x = g.generate();
* console.log(x.toString());
* console.log(BigInt(x.toHex()));
* ```
*/

@@ -55,2 +75,4 @@ class Generator {

this.perSecRandom = 0;
/** Maximum number of checking `Date.now()` until clock goes forward. */
this.nClockCheckMax = 1000000;
/** Returns a `k`-bit (cryptographically strong) random unsigned integer. */

@@ -69,6 +91,6 @@ this.getRandomBits = detectRng();

// wait a moment until clock goes forward when counter overflows
let nTrials = 0;
let nClockCheck = 0;
while (tsNow <= this.tsLastGen) {
tsNow = Date.now();
if (++nTrials > 1000000) {
if (++nClockCheck > this.nClockCheckMax) {
console.warn("scru128: reset state as clock did not go forward");

@@ -87,7 +109,19 @@ this.tsLastSec = 0;

}
return Scru128Id.fromFields(this.tsLastGen - TIMESTAMP_EPOCH, this.counter, this.perSecRandom, this.getRandomBits(32));
return Scru128Id.fromFields(this.tsLastGen - exports.TIMESTAMP_BIAS, this.counter, this.perSecRandom, this.getRandomBits(32));
}
}
exports.Generator = Generator;
/**
* Represents a SCRU128 ID and provides converters to/from string and numbers.
*
* @example
* ```javascript
* import { Scru128Id } from "scru128";
*
* const x = Scru128Id.fromString("00Q1D9AB6DTJNLJ80SJ42SNJ4F");
* console.log(x.toString());
*
* const y = Scru128Id.fromHex(0xd05a952ccdecef5aa01c9904e5a115n.toString(16));
* console.log(BigInt(y.toHex()));
* ```
*/

@@ -123,2 +157,4 @@ class Scru128Id {

* @param perGenRandom - 32-bit per-generation randomness field.
* @throws RangeError if any argument is out of the range of each field.
* @category Conversion
*/

@@ -128,12 +164,8 @@ static fromFields(timestamp, counter, perSecRandom, perGenRandom) {

}
/** Returns the 26-digit canonical string representation. */
toString() {
const h48 = this.timestamp * 0x10 + (this.counter >> 24);
const m40 = (this.counter & 16777215) * 65536 + (this.perSecRandom >> 8);
const l40 = (this.perSecRandom & 0xff) * 4294967296 + this.perGenRandom;
return (("000000000" + h48.toString(32)).slice(-10) +
("0000000" + m40.toString(32)).slice(-8) +
("0000000" + l40.toString(32)).slice(-8)).toUpperCase();
}
/** Creates an object from a 26-digit string representation. */
/**
* Creates an object from a 26-digit string representation.
*
* @throws SyntaxError if the argument is not a valid string representation.
* @category Conversion
*/
static fromString(value) {

@@ -149,3 +181,67 @@ const m = value.match(/^([0-7][0-9A-V]{9})([0-9A-V]{8})([0-9A-V]{8})$/i);

}
/**
* Returns the 26-digit canonical string representation.
*
* @category Conversion
*/
toString() {
const h48 = this.timestamp * 0x10 + (this.counter >> 24);
const m40 = (this.counter & 16777215) * 65536 + (this.perSecRandom >> 8);
const l40 = (this.perSecRandom & 0xff) * 4294967296 + this.perGenRandom;
return ((PAD_ZEROS + h48.toString(32)).slice(-10) +
(PAD_ZEROS + m40.toString(32)).slice(-8) +
(PAD_ZEROS + l40.toString(32)).slice(-8)).toUpperCase();
}
/**
* Creates an object from a 128-bit unsigned integer encoded in a hexadecimal
* string.
*
* @throws SyntaxError if the argument is not a hexadecimal string encoding a
* 128-bit unsigned integer.
* @category Conversion
*/
static fromHex(value) {
const m = value.match(/^(?:0x)?0*(0|[1-9a-f][0-9a-f]*)$/i);
if (m === null || m[1].length > 32) {
throw new SyntaxError("invalid hexadecimal integer: " + value);
}
return new Scru128Id(parseInt(m[1].slice(-32, -21) || "0", 16), parseInt(m[1].slice(-21, -14) || "0", 16), parseInt(m[1].slice(-14, -8) || "0", 16), parseInt(m[1].slice(-8) || "0", 16));
}
/**
* Returns the 128-bit unsigned integer representation as a 32-digit
* hexadecimal string prefixed with "0x".
*
* @category Conversion
*/
toHex() {
return ("0x" +
(PAD_ZEROS + this.timestamp.toString(16)).slice(-11) +
(PAD_ZEROS + this.counter.toString(16)).slice(-7) +
(PAD_ZEROS + this.perSecRandom.toString(16)).slice(-6) +
(PAD_ZEROS + this.perGenRandom.toString(16)).slice(-8));
}
/** Represents `this` in JSON as a 26-digit canonical string. */
toJSON() {
return this.toString();
}
/** Creates an object from `this`. */
clone() {
return new Scru128Id(this.timestamp, this.counter, this.perSecRandom, this.perGenRandom);
}
/** Returns true if `this` is equivalent to `other`. */
equals(other) {
return this.compareTo(other) === 0;
}
/**
* Returns a negative integer, zero, and positive integer if `this` is less
* than, equal to, and greater than `other`, respectively.
*/
compareTo(other) {
return Math.sign(this.timestamp - other.timestamp ||
this.counter - other.counter ||
this.perSecRandom - other.perSecRandom ||
this.perGenRandom - other.perGenRandom);
}
}
exports.Scru128Id = Scru128Id;
const defaultGenerator = new Generator();

@@ -155,3 +251,13 @@ /**

*
* Use this function to quickly get a new SCRU128 ID as a string. Use
* [[Generator]] to do more.
*
* @returns 26-digit canonical string representation.
* @example
* ```javascript
* import { scru128 } from "scru128";
*
* const x = scru128();
* console.log(x);
* ```
*/

@@ -165,2 +271,2 @@ const scru128 = () => defaultGenerator.generate().toString();

*/
exports._internal = { Scru128Id, detectRng };
exports._internal = { detectRng };

8

package.json
{
"name": "scru128",
"version": "0.2.3",
"version": "0.2.4",
"description": "SCRU128: Sortable, Clock and Random number-based Unique identifier",

@@ -46,7 +46,7 @@ "main": "./dist/index.js",

"mocha": "^9.1.3",
"typedoc": "^0.22.5",
"typedoc": "^0.22.7",
"typescript": "^4.4.4",
"webpack": "^5.58.2",
"webpack-cli": "^4.9.0"
"webpack": "^5.60.0",
"webpack-cli": "^4.9.1"
}
}

@@ -9,3 +9,3 @@ # SCRU128: Sortable, Clock and Random number-based Unique identifier

- Sortable by generation time (as integer and as text)
- 26-character case-insensitive portable textual representation
- 26-digit case-insensitive portable textual representation
- 44-bit biased millisecond timestamp that ensures remaining life of 550 years

@@ -22,71 +22,9 @@ - Up to 268 million time-ordered but unpredictable unique IDs per millisecond

See [SCRU128 Specification] for details.
[uuid]: https://en.wikipedia.org/wiki/Universally_unique_identifier
[ulid]: https://github.com/ulid/spec
[ksuid]: https://github.com/segmentio/ksuid
[scru128 specification]: https://github.com/scru128/spec
## Design
A SCRU128 ID is a 128-bit unsigned integer consisting of four terms:
```
timestamp * 2^84 + counter * 2^56 + per_sec_random * 2^32 + per_gen_random
```
Where:
- `timestamp` is a 44-bit unix time in milliseconds biased by 50 years (i.e.
milliseconds elapsed since 2020-01-01 00:00:00+00:00, ignoring leap seconds).
- `counter` is a 28-bit counter incremented by one for each ID generated within
the same `timestamp` (reset to a random number every millisecond).
- `per_sec_random` is a 24-bit random number refreshed only once per second.
- `per_gen_random` is a 32-bit random number renewed per generation of a new ID.
This is essentially equivalent to allocating four unsigned integer fields to a
128-bit space as follows in a big-endian system, and thus it is easily
implemented with binary operations.
| Bit numbers | Field name | Size | Data type |
| ------------ | -------------- | ------- | ---------------- |
| Msb 0 - 43 | timestamp | 44 bits | Unsigned integer |
| Msb 44 - 71 | counter | 28 bits | Unsigned integer |
| Msb 72 - 95 | per_sec_random | 24 bits | Unsigned integer |
| Msb 96 - 127 | per_gen_random | 32 bits | Unsigned integer |
### Layered randomness
SCRU128 utilizes monotonic `counter` to guarantee the uniqueness of IDs with the
same `timestamp`; however, this mechanism does not ensure the uniqueness of IDs
generated by multiple generators that do not share a `counter` state. SCRU128
relies on random numbers to avoid such collisions.
For a given length of random bits, the greater the number of random numbers
generated, the higher the probability of collision. Therefore, SCRU128 gives
some random bits a longer life to reduce the number of random number generation
per a unit of time. As a result, even if each of multiple generators generates a
million IDs at the same millisecond, no collision will occur as long as the
random numbers generated only once per second (`per_sec_random`) differ.
That being said, the `per_sec_random` field is refreshed every second to prevent
potential attackers from using this field as a generator's fingerprint. Also,
the 32-bit `per_gen_random` field is reset to a new random number whenever an ID
is generated to make sure the adjacent IDs generated within the same `timestamp`
are not predictable.
## Textual representation
A SCRU128 ID is encoded in a string as a 128-bit unsigned integer denoted in the
radix of 32 using the digits of `[0-9A-V]`, with leading zeros added to form a
26-digit canonical representation. Converters for this simple base 32 notation
are widely available in many languages; even if not, it is easily implemented
with bitwise operations by translating each 5-bit group into one digit of
`[0-9A-V]`, from the least significant digit to the most. Since the three most
significant bits are mapped to one of `[0-7]`, any numeral greater than
`7VVVVVVVVVVVVVVVVVVVVVVVVV` is not a valid SCRU128 ID.
Note that this is different from some binary-to-text encodings referred to as
_base32_ or _base32hex_ (e.g. [RFC 4648]), which read and translate 5-bit groups
from the most significant one to the least.
[rfc 4648]: https://www.ietf.org/rfc/rfc4648.txt
## License

@@ -93,0 +31,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc