sigilid 0.1.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/LICENSE +7 -0
- package/README.md +37 -0
- package/dist/alphabet.cjs +82 -0
- package/dist/alphabet.cjs.map +1 -0
- package/dist/alphabet.d.cts +34 -0
- package/dist/alphabet.d.ts +34 -0
- package/dist/alphabet.js +79 -0
- package/dist/alphabet.js.map +1 -0
- package/dist/index.cjs +51 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/non-secure.cjs +31 -0
- package/dist/non-secure.cjs.map +1 -0
- package/dist/non-secure.d.cts +19 -0
- package/dist/non-secure.d.ts +19 -0
- package/dist/non-secure.js +29 -0
- package/dist/non-secure.js.map +1 -0
- package/dist/prefix.cjs +72 -0
- package/dist/prefix.cjs.map +1 -0
- package/dist/prefix.d.cts +33 -0
- package/dist/prefix.d.ts +33 -0
- package/dist/prefix.js +69 -0
- package/dist/prefix.js.map +1 -0
- package/dist/typed.cjs +70 -0
- package/dist/typed.cjs.map +1 -0
- package/dist/typed.d.cts +50 -0
- package/dist/typed.d.ts +50 -0
- package/dist/typed.js +67 -0
- package/dist/typed.js.map +1 -0
- package/dist/validate.cjs +45 -0
- package/dist/validate.cjs.map +1 -0
- package/dist/validate.d.cts +46 -0
- package/dist/validate.d.ts +46 -0
- package/dist/validate.js +41 -0
- package/dist/validate.js.map +1 -0
- package/package.json +89 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Moritz Andrè Myrseth
|
|
2
|
+
|
|
3
|
+
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:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# sigilid
|
|
2
|
+
|
|
3
|
+
**A tiny, tree-shakeable ID toolkit for TypeScript apps.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/moritzmyrz/sigilid/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/sigilid)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
Zero runtime dependencies. ESM-first with CJS compatibility. Strong TypeScript support.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { generateId } from "sigilid";
|
|
13
|
+
generateId(); // "K7gkJ_q3vR2nL8xH5eM0w"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
For full documentation, see the [repository README](https://github.com/sigilid/sigilid#readme).
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install sigilid
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Subpath exports
|
|
25
|
+
|
|
26
|
+
| Import | Description |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `sigilid` | Secure random ID generator |
|
|
29
|
+
| `sigilid/non-secure` | Math.random-based generator |
|
|
30
|
+
| `sigilid/prefix` | Prefixed IDs (`usr_abc123`) |
|
|
31
|
+
| `sigilid/typed` | Branded TypeScript ID types |
|
|
32
|
+
| `sigilid/validate` | Validation helpers |
|
|
33
|
+
| `sigilid/alphabet` | Custom alphabet factory |
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/internal/alphabet.ts
|
|
4
|
+
var MIN_ALPHABET_SIZE = 2;
|
|
5
|
+
var MAX_ALPHABET_SIZE = 256;
|
|
6
|
+
function validateAlphabetString(alphabet) {
|
|
7
|
+
if (typeof alphabet !== "string") {
|
|
8
|
+
throw new TypeError("Alphabet must be a string");
|
|
9
|
+
}
|
|
10
|
+
if (alphabet.length < MIN_ALPHABET_SIZE) {
|
|
11
|
+
throw new RangeError(
|
|
12
|
+
`Alphabet must have at least ${MIN_ALPHABET_SIZE} characters, got ${alphabet.length}`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
if (alphabet.length > MAX_ALPHABET_SIZE) {
|
|
16
|
+
throw new RangeError(
|
|
17
|
+
`Alphabet must have at most ${MAX_ALPHABET_SIZE} characters, got ${alphabet.length}`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
for (const char of alphabet) {
|
|
22
|
+
if (seen.has(char)) {
|
|
23
|
+
throw new TypeError(`Alphabet contains duplicate character: "${char}"`);
|
|
24
|
+
}
|
|
25
|
+
seen.add(char);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function generateFromAlphabet(alphabet, length, getBytes) {
|
|
29
|
+
const alphabetSize = alphabet.length;
|
|
30
|
+
let mask = 1;
|
|
31
|
+
while (mask < alphabetSize) mask = mask << 1 | 1;
|
|
32
|
+
const bufferMultiplier = Math.ceil(1.6 * mask * length / alphabetSize);
|
|
33
|
+
let result = "";
|
|
34
|
+
while (result.length < length) {
|
|
35
|
+
const bytes = getBytes(bufferMultiplier);
|
|
36
|
+
for (let i = 0; i < bytes.length && result.length < length; i++) {
|
|
37
|
+
const byte = bytes[i] & mask;
|
|
38
|
+
if (byte < alphabetSize) {
|
|
39
|
+
result += alphabet[byte];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/internal/assert.ts
|
|
47
|
+
var MIN_LENGTH = 1;
|
|
48
|
+
var MAX_LENGTH = 255;
|
|
49
|
+
function assertLength(length) {
|
|
50
|
+
if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {
|
|
51
|
+
throw new RangeError(
|
|
52
|
+
`ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/internal/random.ts
|
|
58
|
+
function randomBytes(count) {
|
|
59
|
+
const bytes = new Uint8Array(count);
|
|
60
|
+
crypto.getRandomValues(bytes);
|
|
61
|
+
return bytes;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/alphabet.ts
|
|
65
|
+
var DEFAULT_LENGTH = 21;
|
|
66
|
+
function validateAlphabet(alphabet) {
|
|
67
|
+
validateAlphabetString(alphabet);
|
|
68
|
+
}
|
|
69
|
+
function createAlphabet(alphabet) {
|
|
70
|
+
validateAlphabetString(alphabet);
|
|
71
|
+
return {
|
|
72
|
+
generate(length = DEFAULT_LENGTH) {
|
|
73
|
+
assertLength(length);
|
|
74
|
+
return generateFromAlphabet(alphabet, length, randomBytes);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
exports.createAlphabet = createAlphabet;
|
|
80
|
+
exports.validateAlphabet = validateAlphabet;
|
|
81
|
+
//# sourceMappingURL=alphabet.cjs.map
|
|
82
|
+
//# sourceMappingURL=alphabet.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/alphabet.ts","../src/internal/assert.ts","../src/internal/random.ts","../src/alphabet.ts"],"names":[],"mappings":"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","file":"alphabet.cjs","sourcesContent":["const MIN_ALPHABET_SIZE = 2;\nconst MAX_ALPHABET_SIZE = 256;\n\nexport function validateAlphabetString(alphabet: string): void {\n if (typeof alphabet !== \"string\") {\n throw new TypeError(\"Alphabet must be a string\");\n }\n if (alphabet.length < MIN_ALPHABET_SIZE) {\n throw new RangeError(\n `Alphabet must have at least ${MIN_ALPHABET_SIZE} characters, got ${alphabet.length}`,\n );\n }\n if (alphabet.length > MAX_ALPHABET_SIZE) {\n throw new RangeError(\n `Alphabet must have at most ${MAX_ALPHABET_SIZE} characters, got ${alphabet.length}`,\n );\n }\n const seen = new Set<string>();\n for (const char of alphabet) {\n if (seen.has(char)) {\n throw new TypeError(`Alphabet contains duplicate character: \"${char}\"`);\n }\n seen.add(char);\n }\n}\n\n/**\n * Generates an ID of `length` characters drawn from `alphabet` using\n * rejection sampling to avoid modulo bias.\n *\n * The caller is responsible for providing valid `alphabet` and `length` values.\n * Use `validateAlphabetString` and `assertLength` before calling this.\n */\nexport function generateFromAlphabet(\n alphabet: string,\n length: number,\n getBytes: (count: number) => Uint8Array,\n): string {\n const alphabetSize = alphabet.length;\n\n // Find the smallest bitmask >= alphabetSize to reduce rejection rate.\n let mask = 1;\n while (mask < alphabetSize) mask = (mask << 1) | 1;\n\n // Overshoot buffer size to reduce round-trips. Most cases complete in one pass.\n const bufferMultiplier = Math.ceil((1.6 * mask * length) / alphabetSize);\n let result = \"\";\n\n while (result.length < length) {\n const bytes = getBytes(bufferMultiplier);\n for (let i = 0; i < bytes.length && result.length < length; i++) {\n const byte = (bytes[i] as number) & mask;\n if (byte < alphabetSize) {\n result += alphabet[byte];\n }\n }\n }\n\n return result;\n}\n","const MIN_LENGTH = 1;\nconst MAX_LENGTH = 255;\n\nexport function assertLength(length: number): void {\n if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {\n throw new RangeError(\n `ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`,\n );\n }\n}\n\nexport function assertPrefix(prefix: string): void {\n if (typeof prefix !== \"string\" || prefix.length === 0) {\n throw new TypeError(\"Prefix must be a non-empty string\");\n }\n if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(prefix)) {\n throw new TypeError(\"Prefix must start with a letter and contain only letters and digits\");\n }\n}\n","// Returns a Uint8Array of `count` cryptographically secure random bytes.\n// Works in Node 18+, all modern browsers, and edge runtimes that expose `crypto`.\nexport function randomBytes(count: number): Uint8Array {\n const bytes = new Uint8Array(count);\n crypto.getRandomValues(bytes);\n return bytes;\n}\n","import { generateFromAlphabet, validateAlphabetString } from \"./internal/alphabet.js\";\nimport { assertLength } from \"./internal/assert.js\";\nimport { randomBytes } from \"./internal/random.js\";\n\nconst DEFAULT_LENGTH = 21;\n\n/**\n * Validates a custom alphabet string.\n *\n * Throws if the alphabet:\n * - is not a string\n * - has fewer than 2 characters\n * - has more than 256 characters\n * - contains duplicate characters\n *\n * @example\n * import { validateAlphabet } from \"sigilid/alphabet\";\n * validateAlphabet(\"abc123\"); // ok\n * validateAlphabet(\"aab\"); // throws: duplicate character\n */\nexport function validateAlphabet(alphabet: string): void {\n validateAlphabetString(alphabet);\n}\n\n/**\n * Creates a secure ID generator bound to a custom alphabet.\n *\n * The alphabet is validated once at creation time. The returned object\n * has a single `generate(length?)` method.\n *\n * @param alphabet - A string of unique characters to draw from.\n * @returns An object with a `generate(length?: number): string` method.\n *\n * @example\n * import { createAlphabet } from \"sigilid/alphabet\";\n * const hex = createAlphabet(\"0123456789abcdef\");\n * hex.generate(32); // \"3f2a8c1d...\" (32 hex characters)\n */\nexport function createAlphabet(alphabet: string): { generate(length?: number): string } {\n validateAlphabetString(alphabet);\n return {\n generate(length: number = DEFAULT_LENGTH): string {\n assertLength(length);\n return generateFromAlphabet(alphabet, length, randomBytes);\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates a custom alphabet string.
|
|
3
|
+
*
|
|
4
|
+
* Throws if the alphabet:
|
|
5
|
+
* - is not a string
|
|
6
|
+
* - has fewer than 2 characters
|
|
7
|
+
* - has more than 256 characters
|
|
8
|
+
* - contains duplicate characters
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { validateAlphabet } from "sigilid/alphabet";
|
|
12
|
+
* validateAlphabet("abc123"); // ok
|
|
13
|
+
* validateAlphabet("aab"); // throws: duplicate character
|
|
14
|
+
*/
|
|
15
|
+
declare function validateAlphabet(alphabet: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a secure ID generator bound to a custom alphabet.
|
|
18
|
+
*
|
|
19
|
+
* The alphabet is validated once at creation time. The returned object
|
|
20
|
+
* has a single `generate(length?)` method.
|
|
21
|
+
*
|
|
22
|
+
* @param alphabet - A string of unique characters to draw from.
|
|
23
|
+
* @returns An object with a `generate(length?: number): string` method.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* import { createAlphabet } from "sigilid/alphabet";
|
|
27
|
+
* const hex = createAlphabet("0123456789abcdef");
|
|
28
|
+
* hex.generate(32); // "3f2a8c1d..." (32 hex characters)
|
|
29
|
+
*/
|
|
30
|
+
declare function createAlphabet(alphabet: string): {
|
|
31
|
+
generate(length?: number): string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { createAlphabet, validateAlphabet };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates a custom alphabet string.
|
|
3
|
+
*
|
|
4
|
+
* Throws if the alphabet:
|
|
5
|
+
* - is not a string
|
|
6
|
+
* - has fewer than 2 characters
|
|
7
|
+
* - has more than 256 characters
|
|
8
|
+
* - contains duplicate characters
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { validateAlphabet } from "sigilid/alphabet";
|
|
12
|
+
* validateAlphabet("abc123"); // ok
|
|
13
|
+
* validateAlphabet("aab"); // throws: duplicate character
|
|
14
|
+
*/
|
|
15
|
+
declare function validateAlphabet(alphabet: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a secure ID generator bound to a custom alphabet.
|
|
18
|
+
*
|
|
19
|
+
* The alphabet is validated once at creation time. The returned object
|
|
20
|
+
* has a single `generate(length?)` method.
|
|
21
|
+
*
|
|
22
|
+
* @param alphabet - A string of unique characters to draw from.
|
|
23
|
+
* @returns An object with a `generate(length?: number): string` method.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* import { createAlphabet } from "sigilid/alphabet";
|
|
27
|
+
* const hex = createAlphabet("0123456789abcdef");
|
|
28
|
+
* hex.generate(32); // "3f2a8c1d..." (32 hex characters)
|
|
29
|
+
*/
|
|
30
|
+
declare function createAlphabet(alphabet: string): {
|
|
31
|
+
generate(length?: number): string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { createAlphabet, validateAlphabet };
|
package/dist/alphabet.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/internal/alphabet.ts
|
|
2
|
+
var MIN_ALPHABET_SIZE = 2;
|
|
3
|
+
var MAX_ALPHABET_SIZE = 256;
|
|
4
|
+
function validateAlphabetString(alphabet) {
|
|
5
|
+
if (typeof alphabet !== "string") {
|
|
6
|
+
throw new TypeError("Alphabet must be a string");
|
|
7
|
+
}
|
|
8
|
+
if (alphabet.length < MIN_ALPHABET_SIZE) {
|
|
9
|
+
throw new RangeError(
|
|
10
|
+
`Alphabet must have at least ${MIN_ALPHABET_SIZE} characters, got ${alphabet.length}`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
if (alphabet.length > MAX_ALPHABET_SIZE) {
|
|
14
|
+
throw new RangeError(
|
|
15
|
+
`Alphabet must have at most ${MAX_ALPHABET_SIZE} characters, got ${alphabet.length}`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19
|
+
for (const char of alphabet) {
|
|
20
|
+
if (seen.has(char)) {
|
|
21
|
+
throw new TypeError(`Alphabet contains duplicate character: "${char}"`);
|
|
22
|
+
}
|
|
23
|
+
seen.add(char);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function generateFromAlphabet(alphabet, length, getBytes) {
|
|
27
|
+
const alphabetSize = alphabet.length;
|
|
28
|
+
let mask = 1;
|
|
29
|
+
while (mask < alphabetSize) mask = mask << 1 | 1;
|
|
30
|
+
const bufferMultiplier = Math.ceil(1.6 * mask * length / alphabetSize);
|
|
31
|
+
let result = "";
|
|
32
|
+
while (result.length < length) {
|
|
33
|
+
const bytes = getBytes(bufferMultiplier);
|
|
34
|
+
for (let i = 0; i < bytes.length && result.length < length; i++) {
|
|
35
|
+
const byte = bytes[i] & mask;
|
|
36
|
+
if (byte < alphabetSize) {
|
|
37
|
+
result += alphabet[byte];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/internal/assert.ts
|
|
45
|
+
var MIN_LENGTH = 1;
|
|
46
|
+
var MAX_LENGTH = 255;
|
|
47
|
+
function assertLength(length) {
|
|
48
|
+
if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {
|
|
49
|
+
throw new RangeError(
|
|
50
|
+
`ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/internal/random.ts
|
|
56
|
+
function randomBytes(count) {
|
|
57
|
+
const bytes = new Uint8Array(count);
|
|
58
|
+
crypto.getRandomValues(bytes);
|
|
59
|
+
return bytes;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/alphabet.ts
|
|
63
|
+
var DEFAULT_LENGTH = 21;
|
|
64
|
+
function validateAlphabet(alphabet) {
|
|
65
|
+
validateAlphabetString(alphabet);
|
|
66
|
+
}
|
|
67
|
+
function createAlphabet(alphabet) {
|
|
68
|
+
validateAlphabetString(alphabet);
|
|
69
|
+
return {
|
|
70
|
+
generate(length = DEFAULT_LENGTH) {
|
|
71
|
+
assertLength(length);
|
|
72
|
+
return generateFromAlphabet(alphabet, length, randomBytes);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { createAlphabet, validateAlphabet };
|
|
78
|
+
//# sourceMappingURL=alphabet.js.map
|
|
79
|
+
//# sourceMappingURL=alphabet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/alphabet.ts","../src/internal/assert.ts","../src/internal/random.ts","../src/alphabet.ts"],"names":[],"mappings":"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","file":"alphabet.js","sourcesContent":["const MIN_ALPHABET_SIZE = 2;\nconst MAX_ALPHABET_SIZE = 256;\n\nexport function validateAlphabetString(alphabet: string): void {\n if (typeof alphabet !== \"string\") {\n throw new TypeError(\"Alphabet must be a string\");\n }\n if (alphabet.length < MIN_ALPHABET_SIZE) {\n throw new RangeError(\n `Alphabet must have at least ${MIN_ALPHABET_SIZE} characters, got ${alphabet.length}`,\n );\n }\n if (alphabet.length > MAX_ALPHABET_SIZE) {\n throw new RangeError(\n `Alphabet must have at most ${MAX_ALPHABET_SIZE} characters, got ${alphabet.length}`,\n );\n }\n const seen = new Set<string>();\n for (const char of alphabet) {\n if (seen.has(char)) {\n throw new TypeError(`Alphabet contains duplicate character: \"${char}\"`);\n }\n seen.add(char);\n }\n}\n\n/**\n * Generates an ID of `length` characters drawn from `alphabet` using\n * rejection sampling to avoid modulo bias.\n *\n * The caller is responsible for providing valid `alphabet` and `length` values.\n * Use `validateAlphabetString` and `assertLength` before calling this.\n */\nexport function generateFromAlphabet(\n alphabet: string,\n length: number,\n getBytes: (count: number) => Uint8Array,\n): string {\n const alphabetSize = alphabet.length;\n\n // Find the smallest bitmask >= alphabetSize to reduce rejection rate.\n let mask = 1;\n while (mask < alphabetSize) mask = (mask << 1) | 1;\n\n // Overshoot buffer size to reduce round-trips. Most cases complete in one pass.\n const bufferMultiplier = Math.ceil((1.6 * mask * length) / alphabetSize);\n let result = \"\";\n\n while (result.length < length) {\n const bytes = getBytes(bufferMultiplier);\n for (let i = 0; i < bytes.length && result.length < length; i++) {\n const byte = (bytes[i] as number) & mask;\n if (byte < alphabetSize) {\n result += alphabet[byte];\n }\n }\n }\n\n return result;\n}\n","const MIN_LENGTH = 1;\nconst MAX_LENGTH = 255;\n\nexport function assertLength(length: number): void {\n if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {\n throw new RangeError(\n `ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`,\n );\n }\n}\n\nexport function assertPrefix(prefix: string): void {\n if (typeof prefix !== \"string\" || prefix.length === 0) {\n throw new TypeError(\"Prefix must be a non-empty string\");\n }\n if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(prefix)) {\n throw new TypeError(\"Prefix must start with a letter and contain only letters and digits\");\n }\n}\n","// Returns a Uint8Array of `count` cryptographically secure random bytes.\n// Works in Node 18+, all modern browsers, and edge runtimes that expose `crypto`.\nexport function randomBytes(count: number): Uint8Array {\n const bytes = new Uint8Array(count);\n crypto.getRandomValues(bytes);\n return bytes;\n}\n","import { generateFromAlphabet, validateAlphabetString } from \"./internal/alphabet.js\";\nimport { assertLength } from \"./internal/assert.js\";\nimport { randomBytes } from \"./internal/random.js\";\n\nconst DEFAULT_LENGTH = 21;\n\n/**\n * Validates a custom alphabet string.\n *\n * Throws if the alphabet:\n * - is not a string\n * - has fewer than 2 characters\n * - has more than 256 characters\n * - contains duplicate characters\n *\n * @example\n * import { validateAlphabet } from \"sigilid/alphabet\";\n * validateAlphabet(\"abc123\"); // ok\n * validateAlphabet(\"aab\"); // throws: duplicate character\n */\nexport function validateAlphabet(alphabet: string): void {\n validateAlphabetString(alphabet);\n}\n\n/**\n * Creates a secure ID generator bound to a custom alphabet.\n *\n * The alphabet is validated once at creation time. The returned object\n * has a single `generate(length?)` method.\n *\n * @param alphabet - A string of unique characters to draw from.\n * @returns An object with a `generate(length?: number): string` method.\n *\n * @example\n * import { createAlphabet } from \"sigilid/alphabet\";\n * const hex = createAlphabet(\"0123456789abcdef\");\n * hex.generate(32); // \"3f2a8c1d...\" (32 hex characters)\n */\nexport function createAlphabet(alphabet: string): { generate(length?: number): string } {\n validateAlphabetString(alphabet);\n return {\n generate(length: number = DEFAULT_LENGTH): string {\n assertLength(length);\n return generateFromAlphabet(alphabet, length, randomBytes);\n },\n };\n}\n"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/internal/alphabet.ts
|
|
4
|
+
function generateFromAlphabet(alphabet, length, getBytes) {
|
|
5
|
+
const alphabetSize = alphabet.length;
|
|
6
|
+
let mask = 1;
|
|
7
|
+
while (mask < alphabetSize) mask = mask << 1 | 1;
|
|
8
|
+
const bufferMultiplier = Math.ceil(1.6 * mask * length / alphabetSize);
|
|
9
|
+
let result = "";
|
|
10
|
+
while (result.length < length) {
|
|
11
|
+
const bytes = getBytes(bufferMultiplier);
|
|
12
|
+
for (let i = 0; i < bytes.length && result.length < length; i++) {
|
|
13
|
+
const byte = bytes[i] & mask;
|
|
14
|
+
if (byte < alphabetSize) {
|
|
15
|
+
result += alphabet[byte];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/internal/assert.ts
|
|
23
|
+
var MIN_LENGTH = 1;
|
|
24
|
+
var MAX_LENGTH = 255;
|
|
25
|
+
function assertLength(length) {
|
|
26
|
+
if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {
|
|
27
|
+
throw new RangeError(
|
|
28
|
+
`ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/internal/random.ts
|
|
34
|
+
function randomBytes(count) {
|
|
35
|
+
const bytes = new Uint8Array(count);
|
|
36
|
+
crypto.getRandomValues(bytes);
|
|
37
|
+
return bytes;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/index.ts
|
|
41
|
+
var DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
42
|
+
var DEFAULT_LENGTH = 21;
|
|
43
|
+
function generateId(length = DEFAULT_LENGTH) {
|
|
44
|
+
assertLength(length);
|
|
45
|
+
return generateFromAlphabet(DEFAULT_ALPHABET, length, randomBytes);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
exports.DEFAULT_ALPHABET = DEFAULT_ALPHABET;
|
|
49
|
+
exports.generateId = generateId;
|
|
50
|
+
//# sourceMappingURL=index.cjs.map
|
|
51
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/alphabet.ts","../src/internal/assert.ts","../src/internal/random.ts","../src/index.ts"],"names":[],"mappings":";;;AAiCO,SAAS,oBAAA,CACd,QAAA,EACA,MAAA,EACA,QAAA,EACQ;AACR,EAAA,MAAM,eAAe,QAAA,CAAS,MAAA;AAG9B,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,OAAO,IAAA,GAAO,YAAA,EAAc,IAAA,GAAQ,IAAA,IAAQ,CAAA,GAAK,CAAA;AAGjD,EAAA,MAAM,mBAAmB,IAAA,CAAK,IAAA,CAAM,GAAA,GAAM,IAAA,GAAO,SAAU,YAAY,CAAA;AACvE,EAAA,IAAI,MAAA,GAAS,EAAA;AAEb,EAAA,OAAO,MAAA,CAAO,SAAS,MAAA,EAAQ;AAC7B,IAAA,MAAM,KAAA,GAAQ,SAAS,gBAAgB,CAAA;AACvC,IAAA,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,GAAI,KAAA,CAAM,UAAU,MAAA,CAAO,MAAA,GAAS,QAAQ,CAAA,EAAA,EAAK;AAC/D,MAAA,MAAM,IAAA,GAAQ,KAAA,CAAM,CAAC,CAAA,GAAe,IAAA;AACpC,MAAA,IAAI,OAAO,YAAA,EAAc;AACvB,QAAA,MAAA,IAAU,SAAS,IAAI,CAAA;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;AC3DA,IAAM,UAAA,GAAa,CAAA;AACnB,IAAM,UAAA,GAAa,GAAA;AAEZ,SAAS,aAAa,MAAA,EAAsB;AACjD,EAAA,IAAI,CAAC,OAAO,SAAA,CAAU,MAAM,KAAK,MAAA,GAAS,UAAA,IAAc,SAAS,UAAA,EAAY;AAC3E,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,qCAAA,EAAwC,UAAU,CAAA,KAAA,EAAQ,UAAU,SAAS,MAAM,CAAA;AAAA,KACrF;AAAA,EACF;AACF;;;ACPO,SAAS,YAAY,KAAA,EAA2B;AACrD,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,KAAK,CAAA;AAClC,EAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAC5B,EAAA,OAAO,KAAA;AACT;;;ACEO,IAAM,gBAAA,GAAmB;AAEhC,IAAM,cAAA,GAAiB,EAAA;AAYhB,SAAS,UAAA,CAAW,SAAiB,cAAA,EAAwB;AAClE,EAAA,YAAA,CAAa,MAAM,CAAA;AACnB,EAAA,OAAO,oBAAA,CAAqB,gBAAA,EAAkB,MAAA,EAAQ,WAAW,CAAA;AACnE","file":"index.cjs","sourcesContent":["const MIN_ALPHABET_SIZE = 2;\nconst MAX_ALPHABET_SIZE = 256;\n\nexport function validateAlphabetString(alphabet: string): void {\n if (typeof alphabet !== \"string\") {\n throw new TypeError(\"Alphabet must be a string\");\n }\n if (alphabet.length < MIN_ALPHABET_SIZE) {\n throw new RangeError(\n `Alphabet must have at least ${MIN_ALPHABET_SIZE} characters, got ${alphabet.length}`,\n );\n }\n if (alphabet.length > MAX_ALPHABET_SIZE) {\n throw new RangeError(\n `Alphabet must have at most ${MAX_ALPHABET_SIZE} characters, got ${alphabet.length}`,\n );\n }\n const seen = new Set<string>();\n for (const char of alphabet) {\n if (seen.has(char)) {\n throw new TypeError(`Alphabet contains duplicate character: \"${char}\"`);\n }\n seen.add(char);\n }\n}\n\n/**\n * Generates an ID of `length` characters drawn from `alphabet` using\n * rejection sampling to avoid modulo bias.\n *\n * The caller is responsible for providing valid `alphabet` and `length` values.\n * Use `validateAlphabetString` and `assertLength` before calling this.\n */\nexport function generateFromAlphabet(\n alphabet: string,\n length: number,\n getBytes: (count: number) => Uint8Array,\n): string {\n const alphabetSize = alphabet.length;\n\n // Find the smallest bitmask >= alphabetSize to reduce rejection rate.\n let mask = 1;\n while (mask < alphabetSize) mask = (mask << 1) | 1;\n\n // Overshoot buffer size to reduce round-trips. Most cases complete in one pass.\n const bufferMultiplier = Math.ceil((1.6 * mask * length) / alphabetSize);\n let result = \"\";\n\n while (result.length < length) {\n const bytes = getBytes(bufferMultiplier);\n for (let i = 0; i < bytes.length && result.length < length; i++) {\n const byte = (bytes[i] as number) & mask;\n if (byte < alphabetSize) {\n result += alphabet[byte];\n }\n }\n }\n\n return result;\n}\n","const MIN_LENGTH = 1;\nconst MAX_LENGTH = 255;\n\nexport function assertLength(length: number): void {\n if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {\n throw new RangeError(\n `ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`,\n );\n }\n}\n\nexport function assertPrefix(prefix: string): void {\n if (typeof prefix !== \"string\" || prefix.length === 0) {\n throw new TypeError(\"Prefix must be a non-empty string\");\n }\n if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(prefix)) {\n throw new TypeError(\"Prefix must start with a letter and contain only letters and digits\");\n }\n}\n","// Returns a Uint8Array of `count` cryptographically secure random bytes.\n// Works in Node 18+, all modern browsers, and edge runtimes that expose `crypto`.\nexport function randomBytes(count: number): Uint8Array {\n const bytes = new Uint8Array(count);\n crypto.getRandomValues(bytes);\n return bytes;\n}\n","import { generateFromAlphabet } from \"./internal/alphabet.js\";\nimport { assertLength } from \"./internal/assert.js\";\nimport { randomBytes } from \"./internal/random.js\";\n\n/**\n * URL-safe alphabet used by default. 64 characters: A-Z, a-z, 0-9, _, -.\n * Chosen to be safe in URLs, filenames, and most log formats without encoding.\n */\nexport const DEFAULT_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-\";\n\nconst DEFAULT_LENGTH = 21;\n\n/**\n * Generates a cryptographically secure random ID.\n *\n * @param length - Number of characters (1–255). Defaults to 21.\n * @returns A random URL-safe string of the requested length.\n *\n * @example\n * import { generateId } from \"sigilid\";\n * const id = generateId(); // \"K7gkJ_q3vR2nL8xH5eM0w\"\n */\nexport function generateId(length: number = DEFAULT_LENGTH): string {\n assertLength(length);\n return generateFromAlphabet(DEFAULT_ALPHABET, length, randomBytes);\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL-safe alphabet used by default. 64 characters: A-Z, a-z, 0-9, _, -.
|
|
3
|
+
* Chosen to be safe in URLs, filenames, and most log formats without encoding.
|
|
4
|
+
*/
|
|
5
|
+
declare const DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
6
|
+
/**
|
|
7
|
+
* Generates a cryptographically secure random ID.
|
|
8
|
+
*
|
|
9
|
+
* @param length - Number of characters (1–255). Defaults to 21.
|
|
10
|
+
* @returns A random URL-safe string of the requested length.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import { generateId } from "sigilid";
|
|
14
|
+
* const id = generateId(); // "K7gkJ_q3vR2nL8xH5eM0w"
|
|
15
|
+
*/
|
|
16
|
+
declare function generateId(length?: number): string;
|
|
17
|
+
|
|
18
|
+
export { DEFAULT_ALPHABET, generateId };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL-safe alphabet used by default. 64 characters: A-Z, a-z, 0-9, _, -.
|
|
3
|
+
* Chosen to be safe in URLs, filenames, and most log formats without encoding.
|
|
4
|
+
*/
|
|
5
|
+
declare const DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
6
|
+
/**
|
|
7
|
+
* Generates a cryptographically secure random ID.
|
|
8
|
+
*
|
|
9
|
+
* @param length - Number of characters (1–255). Defaults to 21.
|
|
10
|
+
* @returns A random URL-safe string of the requested length.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import { generateId } from "sigilid";
|
|
14
|
+
* const id = generateId(); // "K7gkJ_q3vR2nL8xH5eM0w"
|
|
15
|
+
*/
|
|
16
|
+
declare function generateId(length?: number): string;
|
|
17
|
+
|
|
18
|
+
export { DEFAULT_ALPHABET, generateId };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/internal/alphabet.ts
|
|
2
|
+
function generateFromAlphabet(alphabet, length, getBytes) {
|
|
3
|
+
const alphabetSize = alphabet.length;
|
|
4
|
+
let mask = 1;
|
|
5
|
+
while (mask < alphabetSize) mask = mask << 1 | 1;
|
|
6
|
+
const bufferMultiplier = Math.ceil(1.6 * mask * length / alphabetSize);
|
|
7
|
+
let result = "";
|
|
8
|
+
while (result.length < length) {
|
|
9
|
+
const bytes = getBytes(bufferMultiplier);
|
|
10
|
+
for (let i = 0; i < bytes.length && result.length < length; i++) {
|
|
11
|
+
const byte = bytes[i] & mask;
|
|
12
|
+
if (byte < alphabetSize) {
|
|
13
|
+
result += alphabet[byte];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/internal/assert.ts
|
|
21
|
+
var MIN_LENGTH = 1;
|
|
22
|
+
var MAX_LENGTH = 255;
|
|
23
|
+
function assertLength(length) {
|
|
24
|
+
if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {
|
|
25
|
+
throw new RangeError(
|
|
26
|
+
`ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/internal/random.ts
|
|
32
|
+
function randomBytes(count) {
|
|
33
|
+
const bytes = new Uint8Array(count);
|
|
34
|
+
crypto.getRandomValues(bytes);
|
|
35
|
+
return bytes;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/index.ts
|
|
39
|
+
var DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
40
|
+
var DEFAULT_LENGTH = 21;
|
|
41
|
+
function generateId(length = DEFAULT_LENGTH) {
|
|
42
|
+
assertLength(length);
|
|
43
|
+
return generateFromAlphabet(DEFAULT_ALPHABET, length, randomBytes);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { DEFAULT_ALPHABET, generateId };
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/alphabet.ts","../src/internal/assert.ts","../src/internal/random.ts","../src/index.ts"],"names":[],"mappings":";AAiCO,SAAS,oBAAA,CACd,QAAA,EACA,MAAA,EACA,QAAA,EACQ;AACR,EAAA,MAAM,eAAe,QAAA,CAAS,MAAA;AAG9B,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,OAAO,IAAA,GAAO,YAAA,EAAc,IAAA,GAAQ,IAAA,IAAQ,CAAA,GAAK,CAAA;AAGjD,EAAA,MAAM,mBAAmB,IAAA,CAAK,IAAA,CAAM,GAAA,GAAM,IAAA,GAAO,SAAU,YAAY,CAAA;AACvE,EAAA,IAAI,MAAA,GAAS,EAAA;AAEb,EAAA,OAAO,MAAA,CAAO,SAAS,MAAA,EAAQ;AAC7B,IAAA,MAAM,KAAA,GAAQ,SAAS,gBAAgB,CAAA;AACvC,IAAA,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,GAAI,KAAA,CAAM,UAAU,MAAA,CAAO,MAAA,GAAS,QAAQ,CAAA,EAAA,EAAK;AAC/D,MAAA,MAAM,IAAA,GAAQ,KAAA,CAAM,CAAC,CAAA,GAAe,IAAA;AACpC,MAAA,IAAI,OAAO,YAAA,EAAc;AACvB,QAAA,MAAA,IAAU,SAAS,IAAI,CAAA;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;AC3DA,IAAM,UAAA,GAAa,CAAA;AACnB,IAAM,UAAA,GAAa,GAAA;AAEZ,SAAS,aAAa,MAAA,EAAsB;AACjD,EAAA,IAAI,CAAC,OAAO,SAAA,CAAU,MAAM,KAAK,MAAA,GAAS,UAAA,IAAc,SAAS,UAAA,EAAY;AAC3E,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,qCAAA,EAAwC,UAAU,CAAA,KAAA,EAAQ,UAAU,SAAS,MAAM,CAAA;AAAA,KACrF;AAAA,EACF;AACF;;;ACPO,SAAS,YAAY,KAAA,EAA2B;AACrD,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,KAAK,CAAA;AAClC,EAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAC5B,EAAA,OAAO,KAAA;AACT;;;ACEO,IAAM,gBAAA,GAAmB;AAEhC,IAAM,cAAA,GAAiB,EAAA;AAYhB,SAAS,UAAA,CAAW,SAAiB,cAAA,EAAwB;AAClE,EAAA,YAAA,CAAa,MAAM,CAAA;AACnB,EAAA,OAAO,oBAAA,CAAqB,gBAAA,EAAkB,MAAA,EAAQ,WAAW,CAAA;AACnE","file":"index.js","sourcesContent":["const MIN_ALPHABET_SIZE = 2;\nconst MAX_ALPHABET_SIZE = 256;\n\nexport function validateAlphabetString(alphabet: string): void {\n if (typeof alphabet !== \"string\") {\n throw new TypeError(\"Alphabet must be a string\");\n }\n if (alphabet.length < MIN_ALPHABET_SIZE) {\n throw new RangeError(\n `Alphabet must have at least ${MIN_ALPHABET_SIZE} characters, got ${alphabet.length}`,\n );\n }\n if (alphabet.length > MAX_ALPHABET_SIZE) {\n throw new RangeError(\n `Alphabet must have at most ${MAX_ALPHABET_SIZE} characters, got ${alphabet.length}`,\n );\n }\n const seen = new Set<string>();\n for (const char of alphabet) {\n if (seen.has(char)) {\n throw new TypeError(`Alphabet contains duplicate character: \"${char}\"`);\n }\n seen.add(char);\n }\n}\n\n/**\n * Generates an ID of `length` characters drawn from `alphabet` using\n * rejection sampling to avoid modulo bias.\n *\n * The caller is responsible for providing valid `alphabet` and `length` values.\n * Use `validateAlphabetString` and `assertLength` before calling this.\n */\nexport function generateFromAlphabet(\n alphabet: string,\n length: number,\n getBytes: (count: number) => Uint8Array,\n): string {\n const alphabetSize = alphabet.length;\n\n // Find the smallest bitmask >= alphabetSize to reduce rejection rate.\n let mask = 1;\n while (mask < alphabetSize) mask = (mask << 1) | 1;\n\n // Overshoot buffer size to reduce round-trips. Most cases complete in one pass.\n const bufferMultiplier = Math.ceil((1.6 * mask * length) / alphabetSize);\n let result = \"\";\n\n while (result.length < length) {\n const bytes = getBytes(bufferMultiplier);\n for (let i = 0; i < bytes.length && result.length < length; i++) {\n const byte = (bytes[i] as number) & mask;\n if (byte < alphabetSize) {\n result += alphabet[byte];\n }\n }\n }\n\n return result;\n}\n","const MIN_LENGTH = 1;\nconst MAX_LENGTH = 255;\n\nexport function assertLength(length: number): void {\n if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {\n throw new RangeError(\n `ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`,\n );\n }\n}\n\nexport function assertPrefix(prefix: string): void {\n if (typeof prefix !== \"string\" || prefix.length === 0) {\n throw new TypeError(\"Prefix must be a non-empty string\");\n }\n if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(prefix)) {\n throw new TypeError(\"Prefix must start with a letter and contain only letters and digits\");\n }\n}\n","// Returns a Uint8Array of `count` cryptographically secure random bytes.\n// Works in Node 18+, all modern browsers, and edge runtimes that expose `crypto`.\nexport function randomBytes(count: number): Uint8Array {\n const bytes = new Uint8Array(count);\n crypto.getRandomValues(bytes);\n return bytes;\n}\n","import { generateFromAlphabet } from \"./internal/alphabet.js\";\nimport { assertLength } from \"./internal/assert.js\";\nimport { randomBytes } from \"./internal/random.js\";\n\n/**\n * URL-safe alphabet used by default. 64 characters: A-Z, a-z, 0-9, _, -.\n * Chosen to be safe in URLs, filenames, and most log formats without encoding.\n */\nexport const DEFAULT_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-\";\n\nconst DEFAULT_LENGTH = 21;\n\n/**\n * Generates a cryptographically secure random ID.\n *\n * @param length - Number of characters (1–255). Defaults to 21.\n * @returns A random URL-safe string of the requested length.\n *\n * @example\n * import { generateId } from \"sigilid\";\n * const id = generateId(); // \"K7gkJ_q3vR2nL8xH5eM0w\"\n */\nexport function generateId(length: number = DEFAULT_LENGTH): string {\n assertLength(length);\n return generateFromAlphabet(DEFAULT_ALPHABET, length, randomBytes);\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/internal/assert.ts
|
|
4
|
+
var MIN_LENGTH = 1;
|
|
5
|
+
var MAX_LENGTH = 255;
|
|
6
|
+
function assertLength(length) {
|
|
7
|
+
if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {
|
|
8
|
+
throw new RangeError(
|
|
9
|
+
`ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/index.ts
|
|
15
|
+
var DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
16
|
+
|
|
17
|
+
// src/non-secure.ts
|
|
18
|
+
var DEFAULT_LENGTH = 21;
|
|
19
|
+
function generateNonSecureId(length = DEFAULT_LENGTH) {
|
|
20
|
+
assertLength(length);
|
|
21
|
+
let result = "";
|
|
22
|
+
const size = DEFAULT_ALPHABET.length;
|
|
23
|
+
for (let i = 0; i < length; i++) {
|
|
24
|
+
result += DEFAULT_ALPHABET[Math.floor(Math.random() * size)];
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
exports.generateNonSecureId = generateNonSecureId;
|
|
30
|
+
//# sourceMappingURL=non-secure.cjs.map
|
|
31
|
+
//# sourceMappingURL=non-secure.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/assert.ts","../src/index.ts","../src/non-secure.ts"],"names":[],"mappings":";;;AAAA,IAAM,UAAA,GAAa,CAAA;AACnB,IAAM,UAAA,GAAa,GAAA;AAEZ,SAAS,aAAa,MAAA,EAAsB;AACjD,EAAA,IAAI,CAAC,OAAO,SAAA,CAAU,MAAM,KAAK,MAAA,GAAS,UAAA,IAAc,SAAS,UAAA,EAAY;AAC3E,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,qCAAA,EAAwC,UAAU,CAAA,KAAA,EAAQ,UAAU,SAAS,MAAM,CAAA;AAAA,KACrF;AAAA,EACF;AACF;;;ACDO,IAAM,gBAAA,GAAmB,kEAAA;;;ACLhC,IAAM,cAAA,GAAiB,EAAA;AAkBhB,SAAS,mBAAA,CAAoB,SAAiB,cAAA,EAAwB;AAC3E,EAAA,YAAA,CAAa,MAAM,CAAA;AACnB,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,MAAM,OAAO,gBAAA,CAAiB,MAAA;AAC9B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,MAAA,IAAU,iBAAiB,IAAA,CAAK,KAAA,CAAM,KAAK,MAAA,EAAO,GAAI,IAAI,CAAC,CAAA;AAAA,EAC7D;AACA,EAAA,OAAO,MAAA;AACT","file":"non-secure.cjs","sourcesContent":["const MIN_LENGTH = 1;\nconst MAX_LENGTH = 255;\n\nexport function assertLength(length: number): void {\n if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {\n throw new RangeError(\n `ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`,\n );\n }\n}\n\nexport function assertPrefix(prefix: string): void {\n if (typeof prefix !== \"string\" || prefix.length === 0) {\n throw new TypeError(\"Prefix must be a non-empty string\");\n }\n if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(prefix)) {\n throw new TypeError(\"Prefix must start with a letter and contain only letters and digits\");\n }\n}\n","import { generateFromAlphabet } from \"./internal/alphabet.js\";\nimport { assertLength } from \"./internal/assert.js\";\nimport { randomBytes } from \"./internal/random.js\";\n\n/**\n * URL-safe alphabet used by default. 64 characters: A-Z, a-z, 0-9, _, -.\n * Chosen to be safe in URLs, filenames, and most log formats without encoding.\n */\nexport const DEFAULT_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-\";\n\nconst DEFAULT_LENGTH = 21;\n\n/**\n * Generates a cryptographically secure random ID.\n *\n * @param length - Number of characters (1–255). Defaults to 21.\n * @returns A random URL-safe string of the requested length.\n *\n * @example\n * import { generateId } from \"sigilid\";\n * const id = generateId(); // \"K7gkJ_q3vR2nL8xH5eM0w\"\n */\nexport function generateId(length: number = DEFAULT_LENGTH): string {\n assertLength(length);\n return generateFromAlphabet(DEFAULT_ALPHABET, length, randomBytes);\n}\n","import { DEFAULT_ALPHABET } from \"./index.js\";\nimport { assertLength } from \"./internal/assert.js\";\n\nconst DEFAULT_LENGTH = 21;\n\n/**\n * Generates a non-cryptographic random ID using `Math.random`.\n *\n * **Not suitable for security-sensitive contexts** such as tokens, session\n * identifiers, or secrets. Use `generateId` from `\"sigilid\"` when in doubt.\n *\n * Useful for cases where performance matters more than entropy quality, such\n * as temporary keys in test fixtures or non-sensitive local identifiers.\n *\n * @param length - Number of characters (1–255). Defaults to 21.\n * @returns A random URL-safe string of the requested length.\n *\n * @example\n * import { generateNonSecureId } from \"sigilid/non-secure\";\n * const id = generateNonSecureId(); // \"a5Fq2J8mXkR9vL3nP0eHw\"\n */\nexport function generateNonSecureId(length: number = DEFAULT_LENGTH): string {\n assertLength(length);\n let result = \"\";\n const size = DEFAULT_ALPHABET.length;\n for (let i = 0; i < length; i++) {\n result += DEFAULT_ALPHABET[Math.floor(Math.random() * size)];\n }\n return result;\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a non-cryptographic random ID using `Math.random`.
|
|
3
|
+
*
|
|
4
|
+
* **Not suitable for security-sensitive contexts** such as tokens, session
|
|
5
|
+
* identifiers, or secrets. Use `generateId` from `"sigilid"` when in doubt.
|
|
6
|
+
*
|
|
7
|
+
* Useful for cases where performance matters more than entropy quality, such
|
|
8
|
+
* as temporary keys in test fixtures or non-sensitive local identifiers.
|
|
9
|
+
*
|
|
10
|
+
* @param length - Number of characters (1–255). Defaults to 21.
|
|
11
|
+
* @returns A random URL-safe string of the requested length.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import { generateNonSecureId } from "sigilid/non-secure";
|
|
15
|
+
* const id = generateNonSecureId(); // "a5Fq2J8mXkR9vL3nP0eHw"
|
|
16
|
+
*/
|
|
17
|
+
declare function generateNonSecureId(length?: number): string;
|
|
18
|
+
|
|
19
|
+
export { generateNonSecureId };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a non-cryptographic random ID using `Math.random`.
|
|
3
|
+
*
|
|
4
|
+
* **Not suitable for security-sensitive contexts** such as tokens, session
|
|
5
|
+
* identifiers, or secrets. Use `generateId` from `"sigilid"` when in doubt.
|
|
6
|
+
*
|
|
7
|
+
* Useful for cases where performance matters more than entropy quality, such
|
|
8
|
+
* as temporary keys in test fixtures or non-sensitive local identifiers.
|
|
9
|
+
*
|
|
10
|
+
* @param length - Number of characters (1–255). Defaults to 21.
|
|
11
|
+
* @returns A random URL-safe string of the requested length.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import { generateNonSecureId } from "sigilid/non-secure";
|
|
15
|
+
* const id = generateNonSecureId(); // "a5Fq2J8mXkR9vL3nP0eHw"
|
|
16
|
+
*/
|
|
17
|
+
declare function generateNonSecureId(length?: number): string;
|
|
18
|
+
|
|
19
|
+
export { generateNonSecureId };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/internal/assert.ts
|
|
2
|
+
var MIN_LENGTH = 1;
|
|
3
|
+
var MAX_LENGTH = 255;
|
|
4
|
+
function assertLength(length) {
|
|
5
|
+
if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {
|
|
6
|
+
throw new RangeError(
|
|
7
|
+
`ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/index.ts
|
|
13
|
+
var DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
14
|
+
|
|
15
|
+
// src/non-secure.ts
|
|
16
|
+
var DEFAULT_LENGTH = 21;
|
|
17
|
+
function generateNonSecureId(length = DEFAULT_LENGTH) {
|
|
18
|
+
assertLength(length);
|
|
19
|
+
let result = "";
|
|
20
|
+
const size = DEFAULT_ALPHABET.length;
|
|
21
|
+
for (let i = 0; i < length; i++) {
|
|
22
|
+
result += DEFAULT_ALPHABET[Math.floor(Math.random() * size)];
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { generateNonSecureId };
|
|
28
|
+
//# sourceMappingURL=non-secure.js.map
|
|
29
|
+
//# sourceMappingURL=non-secure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/assert.ts","../src/index.ts","../src/non-secure.ts"],"names":[],"mappings":";AAAA,IAAM,UAAA,GAAa,CAAA;AACnB,IAAM,UAAA,GAAa,GAAA;AAEZ,SAAS,aAAa,MAAA,EAAsB;AACjD,EAAA,IAAI,CAAC,OAAO,SAAA,CAAU,MAAM,KAAK,MAAA,GAAS,UAAA,IAAc,SAAS,UAAA,EAAY;AAC3E,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,qCAAA,EAAwC,UAAU,CAAA,KAAA,EAAQ,UAAU,SAAS,MAAM,CAAA;AAAA,KACrF;AAAA,EACF;AACF;;;ACDO,IAAM,gBAAA,GAAmB,kEAAA;;;ACLhC,IAAM,cAAA,GAAiB,EAAA;AAkBhB,SAAS,mBAAA,CAAoB,SAAiB,cAAA,EAAwB;AAC3E,EAAA,YAAA,CAAa,MAAM,CAAA;AACnB,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,MAAM,OAAO,gBAAA,CAAiB,MAAA;AAC9B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,MAAA,IAAU,iBAAiB,IAAA,CAAK,KAAA,CAAM,KAAK,MAAA,EAAO,GAAI,IAAI,CAAC,CAAA;AAAA,EAC7D;AACA,EAAA,OAAO,MAAA;AACT","file":"non-secure.js","sourcesContent":["const MIN_LENGTH = 1;\nconst MAX_LENGTH = 255;\n\nexport function assertLength(length: number): void {\n if (!Number.isInteger(length) || length < MIN_LENGTH || length > MAX_LENGTH) {\n throw new RangeError(\n `ID length must be an integer between ${MIN_LENGTH} and ${MAX_LENGTH}, got ${length}`,\n );\n }\n}\n\nexport function assertPrefix(prefix: string): void {\n if (typeof prefix !== \"string\" || prefix.length === 0) {\n throw new TypeError(\"Prefix must be a non-empty string\");\n }\n if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(prefix)) {\n throw new TypeError(\"Prefix must start with a letter and contain only letters and digits\");\n }\n}\n","import { generateFromAlphabet } from \"./internal/alphabet.js\";\nimport { assertLength } from \"./internal/assert.js\";\nimport { randomBytes } from \"./internal/random.js\";\n\n/**\n * URL-safe alphabet used by default. 64 characters: A-Z, a-z, 0-9, _, -.\n * Chosen to be safe in URLs, filenames, and most log formats without encoding.\n */\nexport const DEFAULT_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-\";\n\nconst DEFAULT_LENGTH = 21;\n\n/**\n * Generates a cryptographically secure random ID.\n *\n * @param length - Number of characters (1–255). Defaults to 21.\n * @returns A random URL-safe string of the requested length.\n *\n * @example\n * import { generateId } from \"sigilid\";\n * const id = generateId(); // \"K7gkJ_q3vR2nL8xH5eM0w\"\n */\nexport function generateId(length: number = DEFAULT_LENGTH): string {\n assertLength(length);\n return generateFromAlphabet(DEFAULT_ALPHABET, length, randomBytes);\n}\n","import { DEFAULT_ALPHABET } from \"./index.js\";\nimport { assertLength } from \"./internal/assert.js\";\n\nconst DEFAULT_LENGTH = 21;\n\n/**\n * Generates a non-cryptographic random ID using `Math.random`.\n *\n * **Not suitable for security-sensitive contexts** such as tokens, session\n * identifiers, or secrets. Use `generateId` from `\"sigilid\"` when in doubt.\n *\n * Useful for cases where performance matters more than entropy quality, such\n * as temporary keys in test fixtures or non-sensitive local identifiers.\n *\n * @param length - Number of characters (1–255). Defaults to 21.\n * @returns A random URL-safe string of the requested length.\n *\n * @example\n * import { generateNonSecureId } from \"sigilid/non-secure\";\n * const id = generateNonSecureId(); // \"a5Fq2J8mXkR9vL3nP0eHw\"\n */\nexport function generateNonSecureId(length: number = DEFAULT_LENGTH): string {\n assertLength(length);\n let result = \"\";\n const size = DEFAULT_ALPHABET.length;\n for (let i = 0; i < length; i++) {\n result += DEFAULT_ALPHABET[Math.floor(Math.random() * size)];\n }\n return result;\n}\n"]}
|