Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Perry Mitchell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+134
View File
@@ -0,0 +1,134 @@
# ulidx
> ULID generator for NodeJS and the browser
[![ulidx](https://img.shields.io/npm/v/ulidx?color=blue&label=ulidx&logo=npm&style=flat-square)](https://www.npmjs.com/package/ulidx) ![Tests status](https://github.com/perry-mitchell/ulidx/actions/workflows/test.yml/badge.svg) ![GitHub](https://img.shields.io/github/license/perry-mitchell/ulidx) ![Dependents (via libraries.io)](https://img.shields.io/librariesio/dependents/npm/ulidx)
ULID generator library, based off of the original [ulid](https://github.com/ulid/javascript) for NodeJS and the browser. ULIDs are Universally Unique Lexicographically Sortable Identifiers. This library adheres to [this specification](https://github.com/ulid/spec).
> The original [ulid](https://github.com/ulid/javascript) is no longer maintained, and has several outstanding compatibility-related issues that were never addressed. This library aims to address those and remain compatible in a larger range of environments.
## Installation
Install using npm by running: `npm install ulidx --save`.
`ulidx` provides types and is written entirely in Typescript. It provides both ESM and CommonJS outputs.
## Usage
Import `ulid` to generate new ULIDs:
```typescript
import { ulid } from "ulidx";
ulid(); // 01F7DKCVCVDZN1Z5Q4FWANHHCC
```
### Time seed
You can also provide a time seed which will consistently give you the same string for the time component.
> This is useful for migrating to ulid.
```typescript
ulid(1469918176385); // 01ARYZ6S41TSV4RRFFQ69G5FAV
```
### Monotonic ULID factory
To generate monotonically increasing ULIDs, create a monotonic counter using the factory:
```typescript
import { monotonicFactory } from "ulidx";
const ulid = monotonicFactory();
// Strict ordering for the same timestamp, by incrementing the least-significant random bit by 1
ulid(150000); // 000XAL6S41ACTAV9WEVGEMMVR8
ulid(150000); // 000XAL6S41ACTAV9WEVGEMMVR9
ulid(150000); // 000XAL6S41ACTAV9WEVGEMMVRA
ulid(150000); // 000XAL6S41ACTAV9WEVGEMMVRB
ulid(150000); // 000XAL6S41ACTAV9WEVGEMMVRC
// Even if a lower timestamp is passed (or generated), it will preserve sort order
ulid(100000); // 000XAL6S41ACTAV9WEVGEMMVRD
```
### Decode ULID Time
Import `decodeTime` to extract the timestamp embedded in a ULID:
```typescript
import { decodeTime } from "ulidx";
// Extract milliseconds since UNIX Epoch from ULID
decodeTime("01ARYZ6S41TSV4RRFFQ69G5FAV"); // 1469918176385
```
### Validate ULID
Import `isValid` to check if a string is a valid ULID:
```typescript
import { isValid } from "ulidx";
isValid("01ARYZ6S41TSV4RRFFQ69G5FAV"); // true
isValid("01ARYZ6S41TSV4RRFFQ69G5FA"); // false
```
### Crockford's Base32 (Typos tolerance and Hyphened ULIDs)
Import `fixULIDBase32` to fix typos and remove hyphens in a ULID:
```typescript
import { fixULIDBase32 } from "ulidx";
fixULIDBase32("oLARYZ6-S41TSV4RRF-FQ69G5FAV"); // 01ARYZ6S41TSV4RRFFQ69G5FAV
```
## Pseudo-Random Number Generation (PRNG)
`ulidx` will attempt to locate a suitable cryptographically-secure random number generator in the environment where it's loaded. On NodeJS this will be `crypto.randomBytes` and in the browser it will be `crypto.getRandomValues`.
`Math.random()` is **not supported**: The environment _must_ have a suitable crypto random number generator.
## Compatibility
`ulidx` is compatible with the following environments:
* NodeJS 16 and up
* Node REPL
* Browsers with working `crypto` / `msCrypto` libraries
* Web workers
* React-Native ¹
* Edge compute
* Cloudflare Workers ²
* Vercel Edge
¹ React-Native is supported if `crypto.getRandomValues()` is polyfilled. [`react-native-get-random-values`](https://github.com/LinusU/react-native-get-random-values) is one such library that should work well with `ulidx`. It should be imported before `ulidx` is used.
² `ulidx` is not _fully_ compatible with Cloudflare Workers due to their [problematic stance on getting the current time](https://developers.cloudflare.com/workers/learning/security-model#step-1-disallow-timers-and-multi-threading). It is recommended to only use monotonic factories in this runtime.
### Browser
`ulidx` provides browser bundles in both ESM and CommonJS varieties. Importing should be automatic, but you can import them directly:
* `dist/browser/index.js` - Browser ESM build
* `dist/browser/index.cjs` - Browser CommonJS build
Unlike version 1.x, these browser builds cannot simply be injected into the browser. They must be included in a build system of some kind, like Rollup or Webpack.
Note that you can use the Node-based builds in the browser if you use such an aforementioned tool, but you will need to stub `node:crypto` to do so. Consider the following example in Webpack using a plugin:
```javascript
{
// ...
plugins: [
new NormalModuleReplacementPlugin(/node:/, (resource) => {
resource.request = resource.request.replace(/^node:/, "");
})
]
// ...
}
```
+330
View File
@@ -0,0 +1,330 @@
'use strict';
function assertError(err) {
if (!isError(err)) {
throw new Error("Parameter was not an error");
}
}
function isError(err) {
return objectToString(err) === "[object Error]" || err instanceof Error;
}
function objectToString(obj) {
return Object.prototype.toString.call(obj);
}
function parseArguments(args) {
let options, shortMessage = "";
if (args.length === 0) {
options = {};
}
else if (isError(args[0])) {
options = {
cause: args[0]
};
shortMessage = args.slice(1).join(" ") || "";
}
else if (args[0] && typeof args[0] === "object") {
options = Object.assign({}, args[0]);
shortMessage = args.slice(1).join(" ") || "";
}
else if (typeof args[0] === "string") {
options = {};
shortMessage = shortMessage = args.join(" ") || "";
}
else {
throw new Error("Invalid arguments passed to Layerr");
}
return {
options,
shortMessage
};
}
class Layerr extends Error {
constructor(errorOptionsOrMessage, messageText) {
const args = [...arguments];
const { options, shortMessage } = parseArguments(args);
let message = shortMessage;
if (options.cause) {
message = `${message}: ${options.cause.message}`;
}
super(message);
this.message = message;
if (options.name && typeof options.name === "string") {
this.name = options.name;
}
else {
this.name = "Layerr";
}
if (options.cause) {
Object.defineProperty(this, "_cause", { value: options.cause });
}
Object.defineProperty(this, "_info", { value: {} });
if (options.info && typeof options.info === "object") {
Object.assign(this._info, options.info);
}
if (Error.captureStackTrace) {
const ctor = options.constructorOpt || this.constructor;
Error.captureStackTrace(this, ctor);
}
}
static cause(err) {
assertError(err);
if (!err._cause)
return null;
return isError(err._cause) ? err._cause : null;
}
static fullStack(err) {
assertError(err);
const cause = Layerr.cause(err);
if (cause) {
return `${err.stack}\ncaused by: ${Layerr.fullStack(cause)}`;
}
return err.stack;
}
static info(err) {
assertError(err);
const output = {};
const cause = Layerr.cause(err);
if (cause) {
Object.assign(output, Layerr.info(cause));
}
if (err._info) {
Object.assign(output, err._info);
}
return output;
}
cause() {
return Layerr.cause(this);
}
toString() {
let output = this.name || this.constructor.name || this.constructor.prototype.name;
if (this.message) {
output = `${output}: ${this.message}`;
}
return output;
}
}
// These values should NEVER change. The values are precisely for
// generating ULIDs.
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
const ENCODING_LEN = 32; // from ENCODING.length;
const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
const TIME_LEN = 10;
const RANDOM_LEN = 16;
const ERROR_INFO = Object.freeze({
source: "ulid"
});
function decodeTime(id) {
if (id.length !== TIME_LEN + RANDOM_LEN) {
throw new Layerr({
info: {
code: "DEC_TIME_MALFORMED",
...ERROR_INFO
}
}, "Malformed ULID");
}
const time = id
.substr(0, TIME_LEN)
.toUpperCase()
.split("")
.reverse()
.reduce((carry, char, index) => {
const encodingIndex = ENCODING.indexOf(char);
if (encodingIndex === -1) {
throw new Layerr({
info: {
code: "DEC_TIME_CHAR",
...ERROR_INFO
}
}, `Time decode error: Invalid character: ${char}`);
}
return (carry += encodingIndex * Math.pow(ENCODING_LEN, index));
}, 0);
if (time > TIME_MAX) {
throw new Layerr({
info: {
code: "DEC_TIME_CHAR",
...ERROR_INFO
}
}, `Malformed ULID: timestamp too large: ${time}`);
}
return time;
}
function detectPRNG(root) {
const rootLookup = root || detectRoot();
const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
(null);
if (typeof globalCrypto?.getRandomValues === "function") {
return () => {
const buffer = new Uint8Array(1);
globalCrypto.getRandomValues(buffer);
return buffer[0] / 0xff;
};
}
else if (typeof globalCrypto?.randomBytes === "function") {
return () => globalCrypto.randomBytes(1).readUInt8() / 0xff;
}
else ;
throw new Layerr({
info: {
code: "PRNG_DETECT",
...ERROR_INFO
}
}, "Failed to find a reliable PRNG");
}
function detectRoot() {
if (inWebWorker())
return self;
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
if (typeof globalThis !== "undefined") {
return globalThis;
}
return null;
}
function encodeRandom(len, prng) {
let str = "";
for (; len > 0; len--) {
str = randomChar(prng) + str;
}
return str;
}
function encodeTime(now, len) {
if (isNaN(now)) {
throw new Layerr({
info: {
code: "ENC_TIME_NAN",
...ERROR_INFO
}
}, `Time must be a number: ${now}`);
}
else if (now > TIME_MAX) {
throw new Layerr({
info: {
code: "ENC_TIME_SIZE_EXCEED",
...ERROR_INFO
}
}, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
}
else if (now < 0) {
throw new Layerr({
info: {
code: "ENC_TIME_NEG",
...ERROR_INFO
}
}, `Time must be positive: ${now}`);
}
else if (Number.isInteger(now) === false) {
throw new Layerr({
info: {
code: "ENC_TIME_TYPE",
...ERROR_INFO
}
}, `Time must be an integer: ${now}`);
}
let mod, str = "";
for (let currentLen = len; currentLen > 0; currentLen--) {
mod = now % ENCODING_LEN;
str = ENCODING.charAt(mod) + str;
now = (now - mod) / ENCODING_LEN;
}
return str;
}
/**
* Fix a ULID's Base32 encoding -
* i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
* hyphens are ignored during decoding.
* @param id
* @returns The cleaned up ULID
*/
function fixULIDBase32(id) {
return id.replace(/i/gi, "1").replace(/l/gi, "1").replace(/o/gi, "0").replace(/-/g, "");
}
function incrementBase32(str) {
let done = undefined, index = str.length, char, charIndex, output = str;
const maxCharIndex = ENCODING_LEN - 1;
while (!done && index-- >= 0) {
char = output[index];
charIndex = ENCODING.indexOf(char);
if (charIndex === -1) {
throw new Layerr({
info: {
code: "B32_INC_ENC",
...ERROR_INFO
}
}, "Incorrectly encoded string");
}
if (charIndex === maxCharIndex) {
output = replaceCharAt(output, index, ENCODING[0]);
continue;
}
done = replaceCharAt(output, index, ENCODING[charIndex + 1]);
}
if (typeof done === "string") {
return done;
}
throw new Layerr({
info: {
code: "B32_INC_INVALID",
...ERROR_INFO
}
}, "Failed incrementing string");
}
function inWebWorker() {
// @ts-ignore
return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
}
function isValid(id) {
return (typeof id === "string" &&
id.length === TIME_LEN + RANDOM_LEN &&
id
.toUpperCase()
.split("")
.every(char => ENCODING.indexOf(char) !== -1));
}
function monotonicFactory(prng) {
const currentPRNG = prng || detectPRNG();
let lastTime = 0, lastRandom;
return function _ulid(seedTime) {
const seed = isNaN(seedTime) ? Date.now() : seedTime;
if (seed <= lastTime) {
const incrementedRandom = (lastRandom = incrementBase32(lastRandom));
return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
}
lastTime = seed;
const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currentPRNG));
return encodeTime(seed, TIME_LEN) + newRandom;
};
}
function randomChar(prng) {
let rand = Math.floor(prng() * ENCODING_LEN);
if (rand === ENCODING_LEN) {
rand = ENCODING_LEN - 1;
}
return ENCODING.charAt(rand);
}
function replaceCharAt(str, index, char) {
if (index > str.length - 1) {
return str;
}
return str.substr(0, index) + char + str.substr(index + 1);
}
function ulid(seedTime, prng) {
const currentPRNG = prng || detectPRNG();
const seed = isNaN(seedTime) ? Date.now() : seedTime;
return encodeTime(seed, TIME_LEN) + encodeRandom(RANDOM_LEN, currentPRNG);
}
exports.decodeTime = decodeTime;
exports.detectPRNG = detectPRNG;
exports.encodeTime = encodeTime;
exports.fixULIDBase32 = fixULIDBase32;
exports.isValid = isValid;
exports.monotonicFactory = monotonicFactory;
exports.ulid = ulid;
+218
View File
@@ -0,0 +1,218 @@
import { Layerr } from 'layerr';
// These values should NEVER change. The values are precisely for
// generating ULIDs.
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
const ENCODING_LEN = 32; // from ENCODING.length;
const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
const TIME_LEN = 10;
const RANDOM_LEN = 16;
const ERROR_INFO = Object.freeze({
source: "ulid"
});
function decodeTime(id) {
if (id.length !== TIME_LEN + RANDOM_LEN) {
throw new Layerr({
info: {
code: "DEC_TIME_MALFORMED",
...ERROR_INFO
}
}, "Malformed ULID");
}
const time = id
.substr(0, TIME_LEN)
.toUpperCase()
.split("")
.reverse()
.reduce((carry, char, index) => {
const encodingIndex = ENCODING.indexOf(char);
if (encodingIndex === -1) {
throw new Layerr({
info: {
code: "DEC_TIME_CHAR",
...ERROR_INFO
}
}, `Time decode error: Invalid character: ${char}`);
}
return (carry += encodingIndex * Math.pow(ENCODING_LEN, index));
}, 0);
if (time > TIME_MAX) {
throw new Layerr({
info: {
code: "DEC_TIME_CHAR",
...ERROR_INFO
}
}, `Malformed ULID: timestamp too large: ${time}`);
}
return time;
}
function detectPRNG(root) {
const rootLookup = root || detectRoot();
const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
(null);
if (typeof globalCrypto?.getRandomValues === "function") {
return () => {
const buffer = new Uint8Array(1);
globalCrypto.getRandomValues(buffer);
return buffer[0] / 0xff;
};
}
else if (typeof globalCrypto?.randomBytes === "function") {
return () => globalCrypto.randomBytes(1).readUInt8() / 0xff;
}
else ;
throw new Layerr({
info: {
code: "PRNG_DETECT",
...ERROR_INFO
}
}, "Failed to find a reliable PRNG");
}
function detectRoot() {
if (inWebWorker())
return self;
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
if (typeof globalThis !== "undefined") {
return globalThis;
}
return null;
}
function encodeRandom(len, prng) {
let str = "";
for (; len > 0; len--) {
str = randomChar(prng) + str;
}
return str;
}
function encodeTime(now, len) {
if (isNaN(now)) {
throw new Layerr({
info: {
code: "ENC_TIME_NAN",
...ERROR_INFO
}
}, `Time must be a number: ${now}`);
}
else if (now > TIME_MAX) {
throw new Layerr({
info: {
code: "ENC_TIME_SIZE_EXCEED",
...ERROR_INFO
}
}, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
}
else if (now < 0) {
throw new Layerr({
info: {
code: "ENC_TIME_NEG",
...ERROR_INFO
}
}, `Time must be positive: ${now}`);
}
else if (Number.isInteger(now) === false) {
throw new Layerr({
info: {
code: "ENC_TIME_TYPE",
...ERROR_INFO
}
}, `Time must be an integer: ${now}`);
}
let mod, str = "";
for (let currentLen = len; currentLen > 0; currentLen--) {
mod = now % ENCODING_LEN;
str = ENCODING.charAt(mod) + str;
now = (now - mod) / ENCODING_LEN;
}
return str;
}
/**
* Fix a ULID's Base32 encoding -
* i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
* hyphens are ignored during decoding.
* @param id
* @returns The cleaned up ULID
*/
function fixULIDBase32(id) {
return id.replace(/i/gi, "1").replace(/l/gi, "1").replace(/o/gi, "0").replace(/-/g, "");
}
function incrementBase32(str) {
let done = undefined, index = str.length, char, charIndex, output = str;
const maxCharIndex = ENCODING_LEN - 1;
while (!done && index-- >= 0) {
char = output[index];
charIndex = ENCODING.indexOf(char);
if (charIndex === -1) {
throw new Layerr({
info: {
code: "B32_INC_ENC",
...ERROR_INFO
}
}, "Incorrectly encoded string");
}
if (charIndex === maxCharIndex) {
output = replaceCharAt(output, index, ENCODING[0]);
continue;
}
done = replaceCharAt(output, index, ENCODING[charIndex + 1]);
}
if (typeof done === "string") {
return done;
}
throw new Layerr({
info: {
code: "B32_INC_INVALID",
...ERROR_INFO
}
}, "Failed incrementing string");
}
function inWebWorker() {
// @ts-ignore
return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
}
function isValid(id) {
return (typeof id === "string" &&
id.length === TIME_LEN + RANDOM_LEN &&
id
.toUpperCase()
.split("")
.every(char => ENCODING.indexOf(char) !== -1));
}
function monotonicFactory(prng) {
const currentPRNG = prng || detectPRNG();
let lastTime = 0, lastRandom;
return function _ulid(seedTime) {
const seed = isNaN(seedTime) ? Date.now() : seedTime;
if (seed <= lastTime) {
const incrementedRandom = (lastRandom = incrementBase32(lastRandom));
return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
}
lastTime = seed;
const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currentPRNG));
return encodeTime(seed, TIME_LEN) + newRandom;
};
}
function randomChar(prng) {
let rand = Math.floor(prng() * ENCODING_LEN);
if (rand === ENCODING_LEN) {
rand = ENCODING_LEN - 1;
}
return ENCODING.charAt(rand);
}
function replaceCharAt(str, index, char) {
if (index > str.length - 1) {
return str;
}
return str.substr(0, index) + char + str.substr(index + 1);
}
function ulid(seedTime, prng) {
const currentPRNG = prng || detectPRNG();
const seed = isNaN(seedTime) ? Date.now() : seedTime;
return encodeTime(seed, TIME_LEN) + encodeRandom(RANDOM_LEN, currentPRNG);
}
export { decodeTime, detectPRNG, encodeTime, fixULIDBase32, isValid, monotonicFactory, ulid };
+2
View File
@@ -0,0 +1,2 @@
export { encodeTime, fixULIDBase32, decodeTime, detectPRNG, isValid, monotonicFactory, ulid } from "./ulid.js";
export * from "./types.js";
+334
View File
@@ -0,0 +1,334 @@
'use strict';
var crypto = require('node:crypto');
function assertError(err) {
if (!isError(err)) {
throw new Error("Parameter was not an error");
}
}
function isError(err) {
return objectToString(err) === "[object Error]" || err instanceof Error;
}
function objectToString(obj) {
return Object.prototype.toString.call(obj);
}
function parseArguments(args) {
let options, shortMessage = "";
if (args.length === 0) {
options = {};
}
else if (isError(args[0])) {
options = {
cause: args[0]
};
shortMessage = args.slice(1).join(" ") || "";
}
else if (args[0] && typeof args[0] === "object") {
options = Object.assign({}, args[0]);
shortMessage = args.slice(1).join(" ") || "";
}
else if (typeof args[0] === "string") {
options = {};
shortMessage = shortMessage = args.join(" ") || "";
}
else {
throw new Error("Invalid arguments passed to Layerr");
}
return {
options,
shortMessage
};
}
class Layerr extends Error {
constructor(errorOptionsOrMessage, messageText) {
const args = [...arguments];
const { options, shortMessage } = parseArguments(args);
let message = shortMessage;
if (options.cause) {
message = `${message}: ${options.cause.message}`;
}
super(message);
this.message = message;
if (options.name && typeof options.name === "string") {
this.name = options.name;
}
else {
this.name = "Layerr";
}
if (options.cause) {
Object.defineProperty(this, "_cause", { value: options.cause });
}
Object.defineProperty(this, "_info", { value: {} });
if (options.info && typeof options.info === "object") {
Object.assign(this._info, options.info);
}
if (Error.captureStackTrace) {
const ctor = options.constructorOpt || this.constructor;
Error.captureStackTrace(this, ctor);
}
}
static cause(err) {
assertError(err);
if (!err._cause)
return null;
return isError(err._cause) ? err._cause : null;
}
static fullStack(err) {
assertError(err);
const cause = Layerr.cause(err);
if (cause) {
return `${err.stack}\ncaused by: ${Layerr.fullStack(cause)}`;
}
return err.stack;
}
static info(err) {
assertError(err);
const output = {};
const cause = Layerr.cause(err);
if (cause) {
Object.assign(output, Layerr.info(cause));
}
if (err._info) {
Object.assign(output, err._info);
}
return output;
}
cause() {
return Layerr.cause(this);
}
toString() {
let output = this.name || this.constructor.name || this.constructor.prototype.name;
if (this.message) {
output = `${output}: ${this.message}`;
}
return output;
}
}
// These values should NEVER change. The values are precisely for
// generating ULIDs.
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
const ENCODING_LEN = 32; // from ENCODING.length;
const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
const TIME_LEN = 10;
const RANDOM_LEN = 16;
const ERROR_INFO = Object.freeze({
source: "ulid"
});
function decodeTime(id) {
if (id.length !== TIME_LEN + RANDOM_LEN) {
throw new Layerr({
info: {
code: "DEC_TIME_MALFORMED",
...ERROR_INFO
}
}, "Malformed ULID");
}
const time = id
.substr(0, TIME_LEN)
.toUpperCase()
.split("")
.reverse()
.reduce((carry, char, index) => {
const encodingIndex = ENCODING.indexOf(char);
if (encodingIndex === -1) {
throw new Layerr({
info: {
code: "DEC_TIME_CHAR",
...ERROR_INFO
}
}, `Time decode error: Invalid character: ${char}`);
}
return (carry += encodingIndex * Math.pow(ENCODING_LEN, index));
}, 0);
if (time > TIME_MAX) {
throw new Layerr({
info: {
code: "DEC_TIME_CHAR",
...ERROR_INFO
}
}, `Malformed ULID: timestamp too large: ${time}`);
}
return time;
}
function detectPRNG(root) {
const rootLookup = root || detectRoot();
const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
(typeof crypto !== "undefined" ? crypto : null);
if (typeof globalCrypto?.getRandomValues === "function") {
return () => {
const buffer = new Uint8Array(1);
globalCrypto.getRandomValues(buffer);
return buffer[0] / 0xff;
};
}
else if (typeof globalCrypto?.randomBytes === "function") {
return () => globalCrypto.randomBytes(1).readUInt8() / 0xff;
}
else if (crypto?.randomBytes) {
return () => crypto.randomBytes(1).readUInt8() / 0xff;
}
throw new Layerr({
info: {
code: "PRNG_DETECT",
...ERROR_INFO
}
}, "Failed to find a reliable PRNG");
}
function detectRoot() {
if (inWebWorker())
return self;
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
if (typeof globalThis !== "undefined") {
return globalThis;
}
return null;
}
function encodeRandom(len, prng) {
let str = "";
for (; len > 0; len--) {
str = randomChar(prng) + str;
}
return str;
}
function encodeTime(now, len) {
if (isNaN(now)) {
throw new Layerr({
info: {
code: "ENC_TIME_NAN",
...ERROR_INFO
}
}, `Time must be a number: ${now}`);
}
else if (now > TIME_MAX) {
throw new Layerr({
info: {
code: "ENC_TIME_SIZE_EXCEED",
...ERROR_INFO
}
}, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
}
else if (now < 0) {
throw new Layerr({
info: {
code: "ENC_TIME_NEG",
...ERROR_INFO
}
}, `Time must be positive: ${now}`);
}
else if (Number.isInteger(now) === false) {
throw new Layerr({
info: {
code: "ENC_TIME_TYPE",
...ERROR_INFO
}
}, `Time must be an integer: ${now}`);
}
let mod, str = "";
for (let currentLen = len; currentLen > 0; currentLen--) {
mod = now % ENCODING_LEN;
str = ENCODING.charAt(mod) + str;
now = (now - mod) / ENCODING_LEN;
}
return str;
}
/**
* Fix a ULID's Base32 encoding -
* i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
* hyphens are ignored during decoding.
* @param id
* @returns The cleaned up ULID
*/
function fixULIDBase32(id) {
return id.replace(/i/gi, "1").replace(/l/gi, "1").replace(/o/gi, "0").replace(/-/g, "");
}
function incrementBase32(str) {
let done = undefined, index = str.length, char, charIndex, output = str;
const maxCharIndex = ENCODING_LEN - 1;
while (!done && index-- >= 0) {
char = output[index];
charIndex = ENCODING.indexOf(char);
if (charIndex === -1) {
throw new Layerr({
info: {
code: "B32_INC_ENC",
...ERROR_INFO
}
}, "Incorrectly encoded string");
}
if (charIndex === maxCharIndex) {
output = replaceCharAt(output, index, ENCODING[0]);
continue;
}
done = replaceCharAt(output, index, ENCODING[charIndex + 1]);
}
if (typeof done === "string") {
return done;
}
throw new Layerr({
info: {
code: "B32_INC_INVALID",
...ERROR_INFO
}
}, "Failed incrementing string");
}
function inWebWorker() {
// @ts-ignore
return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
}
function isValid(id) {
return (typeof id === "string" &&
id.length === TIME_LEN + RANDOM_LEN &&
id
.toUpperCase()
.split("")
.every(char => ENCODING.indexOf(char) !== -1));
}
function monotonicFactory(prng) {
const currentPRNG = prng || detectPRNG();
let lastTime = 0, lastRandom;
return function _ulid(seedTime) {
const seed = isNaN(seedTime) ? Date.now() : seedTime;
if (seed <= lastTime) {
const incrementedRandom = (lastRandom = incrementBase32(lastRandom));
return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
}
lastTime = seed;
const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currentPRNG));
return encodeTime(seed, TIME_LEN) + newRandom;
};
}
function randomChar(prng) {
let rand = Math.floor(prng() * ENCODING_LEN);
if (rand === ENCODING_LEN) {
rand = ENCODING_LEN - 1;
}
return ENCODING.charAt(rand);
}
function replaceCharAt(str, index, char) {
if (index > str.length - 1) {
return str;
}
return str.substr(0, index) + char + str.substr(index + 1);
}
function ulid(seedTime, prng) {
const currentPRNG = prng || detectPRNG();
const seed = isNaN(seedTime) ? Date.now() : seedTime;
return encodeTime(seed, TIME_LEN) + encodeRandom(RANDOM_LEN, currentPRNG);
}
exports.decodeTime = decodeTime;
exports.detectPRNG = detectPRNG;
exports.encodeTime = encodeTime;
exports.fixULIDBase32 = fixULIDBase32;
exports.isValid = isValid;
exports.monotonicFactory = monotonicFactory;
exports.ulid = ulid;
+221
View File
@@ -0,0 +1,221 @@
import crypto from 'node:crypto';
import { Layerr } from 'layerr';
// These values should NEVER change. The values are precisely for
// generating ULIDs.
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
const ENCODING_LEN = 32; // from ENCODING.length;
const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
const TIME_LEN = 10;
const RANDOM_LEN = 16;
const ERROR_INFO = Object.freeze({
source: "ulid"
});
function decodeTime(id) {
if (id.length !== TIME_LEN + RANDOM_LEN) {
throw new Layerr({
info: {
code: "DEC_TIME_MALFORMED",
...ERROR_INFO
}
}, "Malformed ULID");
}
const time = id
.substr(0, TIME_LEN)
.toUpperCase()
.split("")
.reverse()
.reduce((carry, char, index) => {
const encodingIndex = ENCODING.indexOf(char);
if (encodingIndex === -1) {
throw new Layerr({
info: {
code: "DEC_TIME_CHAR",
...ERROR_INFO
}
}, `Time decode error: Invalid character: ${char}`);
}
return (carry += encodingIndex * Math.pow(ENCODING_LEN, index));
}, 0);
if (time > TIME_MAX) {
throw new Layerr({
info: {
code: "DEC_TIME_CHAR",
...ERROR_INFO
}
}, `Malformed ULID: timestamp too large: ${time}`);
}
return time;
}
function detectPRNG(root) {
const rootLookup = root || detectRoot();
const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
(typeof crypto !== "undefined" ? crypto : null);
if (typeof globalCrypto?.getRandomValues === "function") {
return () => {
const buffer = new Uint8Array(1);
globalCrypto.getRandomValues(buffer);
return buffer[0] / 0xff;
};
}
else if (typeof globalCrypto?.randomBytes === "function") {
return () => globalCrypto.randomBytes(1).readUInt8() / 0xff;
}
else if (crypto?.randomBytes) {
return () => crypto.randomBytes(1).readUInt8() / 0xff;
}
throw new Layerr({
info: {
code: "PRNG_DETECT",
...ERROR_INFO
}
}, "Failed to find a reliable PRNG");
}
function detectRoot() {
if (inWebWorker())
return self;
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
if (typeof globalThis !== "undefined") {
return globalThis;
}
return null;
}
function encodeRandom(len, prng) {
let str = "";
for (; len > 0; len--) {
str = randomChar(prng) + str;
}
return str;
}
function encodeTime(now, len) {
if (isNaN(now)) {
throw new Layerr({
info: {
code: "ENC_TIME_NAN",
...ERROR_INFO
}
}, `Time must be a number: ${now}`);
}
else if (now > TIME_MAX) {
throw new Layerr({
info: {
code: "ENC_TIME_SIZE_EXCEED",
...ERROR_INFO
}
}, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
}
else if (now < 0) {
throw new Layerr({
info: {
code: "ENC_TIME_NEG",
...ERROR_INFO
}
}, `Time must be positive: ${now}`);
}
else if (Number.isInteger(now) === false) {
throw new Layerr({
info: {
code: "ENC_TIME_TYPE",
...ERROR_INFO
}
}, `Time must be an integer: ${now}`);
}
let mod, str = "";
for (let currentLen = len; currentLen > 0; currentLen--) {
mod = now % ENCODING_LEN;
str = ENCODING.charAt(mod) + str;
now = (now - mod) / ENCODING_LEN;
}
return str;
}
/**
* Fix a ULID's Base32 encoding -
* i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
* hyphens are ignored during decoding.
* @param id
* @returns The cleaned up ULID
*/
function fixULIDBase32(id) {
return id.replace(/i/gi, "1").replace(/l/gi, "1").replace(/o/gi, "0").replace(/-/g, "");
}
function incrementBase32(str) {
let done = undefined, index = str.length, char, charIndex, output = str;
const maxCharIndex = ENCODING_LEN - 1;
while (!done && index-- >= 0) {
char = output[index];
charIndex = ENCODING.indexOf(char);
if (charIndex === -1) {
throw new Layerr({
info: {
code: "B32_INC_ENC",
...ERROR_INFO
}
}, "Incorrectly encoded string");
}
if (charIndex === maxCharIndex) {
output = replaceCharAt(output, index, ENCODING[0]);
continue;
}
done = replaceCharAt(output, index, ENCODING[charIndex + 1]);
}
if (typeof done === "string") {
return done;
}
throw new Layerr({
info: {
code: "B32_INC_INVALID",
...ERROR_INFO
}
}, "Failed incrementing string");
}
function inWebWorker() {
// @ts-ignore
return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
}
function isValid(id) {
return (typeof id === "string" &&
id.length === TIME_LEN + RANDOM_LEN &&
id
.toUpperCase()
.split("")
.every(char => ENCODING.indexOf(char) !== -1));
}
function monotonicFactory(prng) {
const currentPRNG = prng || detectPRNG();
let lastTime = 0, lastRandom;
return function _ulid(seedTime) {
const seed = isNaN(seedTime) ? Date.now() : seedTime;
if (seed <= lastTime) {
const incrementedRandom = (lastRandom = incrementBase32(lastRandom));
return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
}
lastTime = seed;
const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currentPRNG));
return encodeTime(seed, TIME_LEN) + newRandom;
};
}
function randomChar(prng) {
let rand = Math.floor(prng() * ENCODING_LEN);
if (rand === ENCODING_LEN) {
rand = ENCODING_LEN - 1;
}
return ENCODING.charAt(rand);
}
function replaceCharAt(str, index, char) {
if (index > str.length - 1) {
return str;
}
return str.substr(0, index) + char + str.substr(index + 1);
}
function ulid(seedTime, prng) {
const currentPRNG = prng || detectPRNG();
const seed = isNaN(seedTime) ? Date.now() : seedTime;
return encodeTime(seed, TIME_LEN) + encodeRandom(RANDOM_LEN, currentPRNG);
}
export { decodeTime, detectPRNG, encodeTime, fixULIDBase32, isValid, monotonicFactory, ulid };
+1
View File
@@ -0,0 +1 @@
export default undefined;
+3
View File
@@ -0,0 +1,3 @@
export type PRNG = () => number;
export type ULID = string;
export type ULIDFactory = (seedTime?: number) => ULID;
+19
View File
@@ -0,0 +1,19 @@
import { PRNG, ULID, ULIDFactory } from "./types.js";
export declare function decodeTime(id: string): number;
export declare function detectPRNG(root?: any): PRNG;
export declare function encodeRandom(len: number, prng: PRNG): string;
export declare function encodeTime(now: number, len: number): string;
/**
* Fix a ULID's Base32 encoding -
* i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
* hyphens are ignored during decoding.
* @param id
* @returns The cleaned up ULID
*/
export declare function fixULIDBase32(id: string): string;
export declare function incrementBase32(str: string): string;
export declare function isValid(id: string): boolean;
export declare function monotonicFactory(prng?: PRNG): ULIDFactory;
export declare function randomChar(prng: PRNG): string;
export declare function replaceCharAt(str: string, index: number, char: string): string;
export declare function ulid(seedTime?: number, prng?: PRNG): ULID;
+106
View File
@@ -0,0 +1,106 @@
{
"name": "ulidx",
"version": "2.1.0",
"description": "ULID generator for NodeJS and the browser",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"node": {
"import": "./dist/node/index.js",
"require": "./dist/node/index.cjs"
},
"browser": {
"import": "./dist/browser/index.js",
"require": "./dist/browser/index.cjs"
},
"worker": {
"import": "./dist/browser/index.js",
"require": "./dist/browser/index.cjs"
}
}
},
"main": "dist/node/index.cjs",
"module": "./dist/node/index.js",
"browser": {
"./dist/node/index.cjs": "./dist/browser/index.cjs",
"./dist/node/index.js": "./dist/browser/index.js"
},
"react-native": "./dist/browser/index.cjs",
"types": "dist/index.d.ts",
"scripts": {
"bench": "npm run build && node test/benchmark.js",
"build": "run-s clean build:node:cjs build:node:esm build:browser:cjs build:browser:esm build:types",
"build:browser:cjs": "FMT=cjs ENV=browser rollup -c --name ulidx",
"build:browser:esm": "FMT=esm ENV=browser rollup -c --name ulidx",
"build:node:cjs": "FMT=cjs ENV=node rollup -c",
"build:node:esm": "FMT=esm ENV=node rollup -c",
"build:types": "tsc -p tsconfig.dec.json --emitDeclarationOnly",
"clean": "rimraf dist",
"format": "prettier --write \"{{source,test}/**/*.{js,ts},rollup.config.js}\"",
"prepublishOnly": "npm run build",
"test": "npm run build && npm run test:specs && npm run test:format",
"test:format": "prettier --check \"{{source,test}/**/*.{js,ts},rollup.config.js}\"",
"test:specs": "run-s test:node:esm test:node:cjs test:browser:cjs",
"test:browser:cjs": "mochify ./test/browser-cjs/*.spec.cjs",
"test:node:cjs": "c8 --src ./dist/node/index.cjs --check-coverage --lines 60 --functions 60 --branches 65 --statements 60 mocha -t 10000 'test/node-cjs/**/*.spec.cjs'",
"test:node:esm": "c8 --src ./dist/node/index.js --check-coverage --lines 60 --functions 60 --branches 65 --statements 60 mocha -t 10000 'test/node-esm/**/*.spec.js'"
},
"files": [
"dist/**/*"
],
"lint-staged": {
"{{source,test}/**/*.{js,ts},rollup.config.js}": [
"prettier --write"
]
},
"engines": {
"node": ">=16"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/perry-mitchell/ulidx.git"
},
"keywords": [
"ulid",
"uuid",
"id",
"generator",
"guid"
],
"author": "Perry Mitchell <perry@perrymitchell.net>",
"license": "MIT",
"bugs": {
"url": "https://github.com/perry-mitchell/ulidx/issues"
},
"homepage": "https://github.com/perry-mitchell/ulidx#readme",
"devDependencies": {
"@rollup/plugin-alias": "^5.0.0",
"@rollup/plugin-commonjs": "^25.0.3",
"@rollup/plugin-node-resolve": "^15.1.0",
"@rollup/plugin-typescript": "^11.1.2",
"@types/node": "^20.4.4",
"benchmark": "^2.1.4",
"c8": "^8.0.0",
"chai": "^4.3.7",
"husky": "^4.3.8",
"lint-staged": "^13.2.3",
"mocha": "^10.2.0",
"mochify": "^9.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"rimraf": "^5.0.1",
"rollup": "^3.26.3",
"sinon": "^15.2.0",
"tslib": "^2.6.0",
"typescript": "^5.1.6"
},
"dependencies": {
"layerr": "^2.0.1"
}
}