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 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
+ [![CI](https://github.com/moritzmyrz/sigilid/actions/workflows/ci.yml/badge.svg)](https://github.com/moritzmyrz/sigilid/actions/workflows/ci.yml)
6
+ [![npm version](https://img.shields.io/npm/v/sigilid)](https://www.npmjs.com/package/sigilid)
7
+ [![license](https://img.shields.io/npm/l/sigilid)](./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 };
@@ -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"]}
@@ -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 };
@@ -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"]}