-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: split code into files and add tests
- Loading branch information
Showing
21 changed files
with
499 additions
and
217 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
indent_style = space | ||
indent_size = 2 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"git": { | ||
"changelog": "npx auto-changelog --stdout --commit-limit false -u --template https://raw.githubusercontent.com/release-it/release-it/master/templates/changelog-compact.hbs" | ||
}, | ||
"npm": { | ||
"publish": true | ||
}, | ||
"github": { | ||
"release": true | ||
}, | ||
"hooks": { | ||
"after:bump": "npx auto-changelog -p" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,61 @@ | ||
# id | ||
![Id image](https://github.com/the-minimal/id/blob/main/docs/the-minimal-id.jpg?raw=true) | ||
|
||
# Highlights | ||
|
||
- Small (~ 700 bytes) | ||
- Low runtime overhead | ||
- Cryptographically correct `random` | ||
- Multiple entropy sources | ||
- Fingerprint | ||
- Uses [@the-minimal/fingerprint](https://github.com/the-minimal/fingerprint) | ||
- Hashed using SHA-512 | ||
- Random 32 byte slice | ||
- Counter | ||
- Random start value | ||
- Maximum of `4_294_967_295` values | ||
- Timestamp | ||
- 64 byte timestamp with millisecond precision | ||
- Salt | ||
- 52 bytes of random 8-bit values | ||
- External data | ||
- Data not provided | ||
- 32 bytes of random 8-bit values | ||
- Data is provided | ||
- Hashed using SHA-512 | ||
- Random 32 byte slice | ||
- Variable length | ||
- Entropy sources are saved into 128 byte array | ||
- The byte array is hashed using SHA-512 | ||
- The hash is converted into Base36 string | ||
- The Base36 string is randomly sliced into desired length | ||
- Default length is 24 | ||
- Collision-resistant | ||
- `5.58e18` until 50% chance of collision | ||
- Uniform and URL-friendly output | ||
- `~0.005%` character variance | ||
- Async / Non-blocking | ||
- 100% test coverage | ||
|
||
# API | ||
|
||
## `init` | ||
|
||
Initializes a new instance of ID generator. | ||
|
||
```ts | ||
const createId8 = init({ length: 8 }); | ||
``` | ||
|
||
## `createId` | ||
|
||
Creates a random ID of length 24. | ||
|
||
It accepts optional external data which is used as another source of entropy. | ||
|
||
```ts | ||
const userId = createId(userEmail); | ||
``` | ||
|
||
# Credits | ||
|
||
This library is directly based on Cuid2. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { readdir } from "node:fs/promises"; | ||
import { file, gzipSync } from "bun"; | ||
|
||
(async () => { | ||
const outdir ="./dist"; | ||
const outFiles = await readdir(outdir); | ||
const filesLength = outFiles.length; | ||
|
||
for (let i = 0; i < filesLength; ++i) { | ||
const fileName = outFiles[i]; | ||
|
||
if(fileName.endsWith("js")) { | ||
const fileHandler = file(`${outdir}/${fileName}`); | ||
const arrBuffer = await fileHandler.arrayBuffer(); | ||
const gzip = gzipSync(arrBuffer); | ||
|
||
console.log(`${fileName} - ${gzip.byteLength} B`); | ||
} | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { asciiToArray } from "./index.js"; | ||
|
||
describe("asciiToArray", () => { | ||
it("should convert ASCII string to Uint8Array", () => { | ||
expect(asciiToArray("hello")).toEqual( | ||
Uint8Array.from([104, 101, 108, 108, 111]), | ||
); | ||
}); | ||
|
||
it("should throw if string is not valid ASCII", () => { | ||
expect(() => asciiToArray("你好")).toThrow(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* Transforms string into Uint8Array. | ||
* It also asserts that string is ASCII. | ||
* | ||
* @param {string} value - Value to be transformed | ||
* | ||
* @return {Uint8Array} Array containing char codes of the input value | ||
*/ | ||
export const asciiToArray = (value: string): Uint8Array => { | ||
const length = value.length; | ||
const buffer = new Uint8Array(length); | ||
|
||
for (let i = 0; i < length; ++i) { | ||
const code = value.charCodeAt(i); | ||
|
||
if (code > 0x7f) { | ||
throw Error("Value has to be ASCII string"); | ||
} | ||
|
||
buffer[i] = code; | ||
} | ||
|
||
return buffer; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export const BUFFER_SIZE = 128; | ||
|
||
export const COUNT_SIZE = 4; | ||
export const TIMESTAMP_SIZE = 8; | ||
export const SALT_SIZE = 52; | ||
export const FINGERPRINT_SIZE = 32; | ||
export const EXTERNAL_SIZE = 32; | ||
|
||
export const SALT_START = COUNT_SIZE + TIMESTAMP_SIZE; | ||
export const SALT_END = SALT_START + SALT_SIZE; | ||
|
||
export const FINGERPRINT_START = SALT_END; | ||
export const FINGERPRINT_END = FINGERPRINT_START + FINGERPRINT_SIZE; | ||
|
||
export const EXTERNAL_START = FINGERPRINT_END; | ||
export const EXTERNAL_END = EXTERNAL_START + EXTERNAL_SIZE; | ||
|
||
export const DEFAULT_LENGTH = 24; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { getRandomSlice } from "./index.js"; | ||
|
||
describe("getRandomSlice", () => { | ||
it("should return random slice from string", () => { | ||
const string = "abcdefghijklmnopqrstuvwxyz"; | ||
const slice = getRandomSlice(string, 6); | ||
|
||
expect(slice).toHaveLength(6); | ||
|
||
const start = string.indexOf(slice[0]); | ||
|
||
for (let i = 0; i < 6; ++i) { | ||
expect(slice[i]).toBe(string[start + i]); | ||
} | ||
}); | ||
|
||
it("should return random slice from Uint8Array", () => { | ||
const array = Uint8Array.from([ | ||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, | ||
22, 23, 24, 25, 26, | ||
]); | ||
const slice = getRandomSlice(array, 6); | ||
|
||
expect(slice).toHaveLength(6); | ||
|
||
const start = array.indexOf(slice[0]); | ||
|
||
for (let i = 0; i < 6; ++i) { | ||
expect(slice[i]).toBe(array[start + i]); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* Slices the original value at a random start position. | ||
* | ||
* @param {string | Uint8Array} value - Value to be sliced | ||
* @param {number} size - Desired size of the slice | ||
* | ||
* @return {string | Uint8Array} Sliced value | ||
*/ | ||
export const getRandomSlice = <$Value extends string | Uint8Array>( | ||
value: $Value, | ||
size: number, | ||
): $Value => { | ||
const position = new Uint8Array(1); | ||
crypto.getRandomValues(position); | ||
|
||
const start = Math.round((position[0] / 255) * (value.length - size)); | ||
|
||
return value.slice(start, start + size) as $Value; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { hashBufferToArray } from "./index.js"; | ||
|
||
describe("hashBufferToArray", () => { | ||
it("should hash BufferSource to Uint8Array", async () => { | ||
expect( | ||
await hashBufferToArray(Uint8Array.from([104, 101, 108, 108, 111])), | ||
).toEqual( | ||
Uint8Array.from([ | ||
155, 113, 210, 36, 189, 98, 243, 120, 93, 150, 212, 106, 211, 234, 61, | ||
115, 49, 155, 251, 194, 137, 12, 170, 218, 226, 223, 247, 37, 25, 103, | ||
60, 167, 35, 35, 195, 217, 155, 165, 193, 29, 124, 122, 204, 110, 20, | ||
184, 197, 218, 12, 70, 99, 71, 92, 46, 92, 58, 222, 244, 111, 115, 188, | ||
222, 192, 67, | ||
]), | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* Hashes buffer to Uint8Array using SHA-512 | ||
* | ||
* @param {BufferSource} buffer - Buffer to be hashed | ||
* | ||
* @return {Promise<Uint8Array>} Promise that resolves with Uint8Array | ||
*/ | ||
export const hashBufferToArray = async ( | ||
buffer: BufferSource, | ||
): Promise<Uint8Array> => { | ||
return new Uint8Array(await crypto.subtle.digest("SHA-512", buffer)); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { hashBufferToBase36 } from "./index.js"; | ||
|
||
describe("hashBufferToBase36", () => { | ||
it("should hash BufferSource to Base36", async () => { | ||
expect( | ||
await hashBufferToBase36(Uint8Array.from([104, 101, 108, 108, 111])), | ||
).toBe( | ||
"opx46l6bobogoghc7ym8wqtjw4ef6kvy1gu0i2tiatlhl6wfm5jj1wxb2ut8oeie2s35yqj08zqjmc1o945mstusp7rlzl4aber", | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { hashBufferToArray } from "../hashBufferToArray/index.js"; | ||
|
||
/** | ||
* Hashes buffer to base36 using SHA-512. | ||
* | ||
* @param {BufferSource} buffer - Buffer to be hashed | ||
* | ||
* @return {Promise<string>} Promise that resolves with base36 string | ||
*/ | ||
export const hashBufferToBase36 = async ( | ||
buffer: BufferSource, | ||
): Promise<string> => { | ||
const hashed = await hashBufferToArray(buffer); | ||
|
||
// append bytes to BigInt | ||
let bigint = BigInt(0); | ||
for (let i = 0; i < hashed.length; ++i) { | ||
bigint = (bigint << BigInt(8)) + BigInt(hashed[i]); | ||
} | ||
|
||
// return base36 converted from BigInt | ||
return bigint.toString(36); | ||
}; |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.