sigilid 1.1.0 → 1.2.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/README.md CHANGED
@@ -24,10 +24,12 @@ const id = generateId(); // "K7gkJ_q3vR2nL8xH5eM0w"
24
24
  - **URL-safe by default** — 64-character alphabet: `A-Z a-z 0-9 _ -`
25
25
  - **Tree-shakeable** — subpath exports mean your bundle only includes what you import
26
26
  - **Zero runtime dependencies** — no third-party code in production output
27
- - **ESM-first with CJS compatibility** — works in modern Node, edge runtimes, and all major bundlers
27
+ - **Optional Node native fast path** — `sigilid/native` for Node-only throughput tuning
28
+ - **ESM-only** — works in modern Node, edge runtimes, and all major bundlers
28
29
  - **Strong TypeScript support** — strict types, branded ID types, precise inference
29
30
  - **Predictable behavior** — explicit errors on invalid input, no silent failures
30
- - **One package, six entrypoints** — `install sigilid`, then import only what you need
31
+ - **One package, seven entrypoints** — `install sigilid`, then import only what you need
32
+ - **Companion native addon package** — only needed when using `sigilid/native`
31
33
 
32
34
  ---
33
35
 
@@ -38,14 +40,14 @@ one never pulls in the others.
38
40
 
39
41
  | Import | Size |
40
42
  | -------------------- | ------ |
41
- | `sigilid` | ~348 B |
42
- | `sigilid/non-secure` | ~222 B |
43
- | `sigilid/prefix` | ~496 B |
44
- | `sigilid/typed` | ~486 B |
45
- | `sigilid/validate` | ~366 B |
46
- | `sigilid/alphabet` | ~381 B |
43
+ | `sigilid` | ~297 B |
44
+ | `sigilid/non-secure` | ~214 B |
45
+ | `sigilid/prefix` | ~385 B |
46
+ | `sigilid/typed` | ~398 B |
47
+ | `sigilid/validate` | ~360 B |
48
+ | `sigilid/alphabet` | ~380 B |
47
49
 
48
- Zero runtime dependencies. [Verified by size-limit on every PR.](link to workflow)
50
+ Zero runtime dependencies. [Verified by size-limit on every PR.](.github/workflows/size-limit.yml)
49
51
 
50
52
  ---
51
53
 
@@ -65,6 +67,14 @@ yarn add sigilid
65
67
 
66
68
  Node 20+ required. Works in all modern runtimes that expose the Web Crypto API (`globalThis.crypto`).
67
69
 
70
+ Optional native path:
71
+
72
+ ```bash
73
+ npm install sigilid @sigilid/native-addon
74
+ ```
75
+
76
+ Use `@sigilid/native-addon` only if you plan to import `sigilid/native`.
77
+
68
78
  ---
69
79
 
70
80
  ## Quick start
@@ -133,6 +143,30 @@ generateNonSecureId(8); // 8-character ID
133
143
 
134
144
  ---
135
145
 
146
+ ### `sigilid/native` — optional Node-only fast path
147
+
148
+ ```ts
149
+ import { generateDefault, generateId } from "sigilid/native";
150
+
151
+ generateDefault(); // 21-character secure ID
152
+ generateDefault(32); // 32-character secure ID
153
+
154
+ // Alias that mirrors the root naming
155
+ generateId(21);
156
+ ```
157
+
158
+ - Node-only entrypoint.
159
+ - Requires the companion addon package: `@sigilid/native-addon`.
160
+ - Uses secure randomness and the same default alphabet as `sigilid`.
161
+ - Throws a clear error if the addon is missing or the runtime is unsupported.
162
+ - Addon install tries prebuilt binaries first, then falls back to local `node-gyp` build.
163
+ - Publishing note: `sigilid` and `@sigilid/native-addon` are versioned/published separately.
164
+
165
+ If you want the broadest compatibility (browser, edge, and Node) stick to the root
166
+ `sigilid` import.
167
+
168
+ ---
169
+
136
170
  ### `sigilid/prefix` — prefixed IDs
137
171
 
138
172
  ```ts
@@ -333,6 +367,34 @@ similar. The subpath ecosystem is where `sigilid` earns its place.
333
367
  If you are targeting an environment without Web Crypto, use `sigilid/non-secure`
334
368
  with the understanding that `Math.random` is not cryptographically safe.
335
369
 
370
+ `sigilid/native` is a separate Node-only path. It depends on the companion addon
371
+ package and is not intended for browsers or edge runtimes.
372
+
373
+ ---
374
+
375
+ ## Benchmarking
376
+
377
+ For local performance checks, run:
378
+
379
+ ```bash
380
+ npm ci
381
+ npm run build
382
+ npm run bench
383
+ ```
384
+
385
+ Native vs JS benchmark:
386
+
387
+ ```bash
388
+ npm run build:native-addon
389
+ npm run bench:native
390
+ ```
391
+
392
+ Build prebuilt binaries for publishing the addon:
393
+
394
+ ```bash
395
+ npm run prebuild:native-addon
396
+ ```
397
+
336
398
  ---
337
399
 
338
400
  ## Package exports
@@ -340,13 +402,14 @@ with the understanding that `Math.random` is not cryptographically safe.
340
402
  | Import | Entry file | Description |
341
403
  | -------------------- | -------------------- | ---------------------------------- |
342
404
  | `sigilid` | `dist/index.js` | Secure root generator |
405
+ | `sigilid/native` | `dist/native.js` | Optional Node-only native fast path |
343
406
  | `sigilid/non-secure` | `dist/non-secure.js` | Math.random-based generator |
344
407
  | `sigilid/prefix` | `dist/prefix.js` | Prefixed ID helpers |
345
408
  | `sigilid/typed` | `dist/typed.js` | Branded types and typed generators |
346
409
  | `sigilid/validate` | `dist/validate.js` | Validation helpers |
347
410
  | `sigilid/alphabet` | `dist/alphabet.js` | Custom alphabet factory |
348
411
 
349
- All exports are available as ESM (`.js`) and CommonJS (`.cjs`) with TypeScript declarations (`.d.ts`).
412
+ All exports are ESM (`.js`) with TypeScript declarations (`.d.ts`). Node.js 20+ required.
350
413
 
351
414
  ---
352
415
 
@@ -363,8 +426,8 @@ decisions and constraints contributors should keep in mind.
363
426
  ## Release and versioning
364
427
 
365
428
  `sigilid` uses [Semantic Versioning](https://semver.org/). Breaking API changes
366
- will bump the major version. Releases are managed with
367
- [Changesets](https://github.com/changesets/changesets).
429
+ will bump the major version. Releases are cut from GitHub — bump the version in
430
+ `package.json`, tag the release, and the publish workflow handles the rest.
368
431
 
369
432
  ---
370
433
 
package/dist/alphabet.js CHANGED
@@ -1 +1 @@
1
- function i(t){if(typeof t!="string")throw new TypeError("alphabet must be a string");if(t.length<2)throw new RangeError("alphabet must have at least 2 characters");if(t.length>256)throw new RangeError("alphabet must have at most 256 characters");if(new Set(t).size!==t.length)throw new TypeError("alphabet has duplicate characters")}function g(t,r,m){let o=t.length,e=1;for(;e<o;)e=e<<1|1;let c=Math.ceil(1.6*e*r/o),n="";for(;n.length<r;){let s=m(c);for(let a=0;a<s.length&&n.length<r;a++){let u=s[a]&e;u<o&&(n+=t[u]);}}return n}function h(t){if(!Number.isInteger(t)||t<1||t>255)throw new RangeError(`length must be 1\u2013255, got ${t}`)}function l(t){let r=new Uint8Array(t);return globalThis.crypto.getRandomValues(r),r}var p=21;function v(t){i(t);}function E(t){return i(t),{generate(r=p){return h(r),g(t,r,l)}}}export{E as createAlphabet,v as validateAlphabet};
1
+ function a(t){if(typeof t!="string")throw new TypeError("alphabet must be a string");if(t.length<2)throw new RangeError("alphabet must have at least 2 characters");if(t.length>256)throw new RangeError("alphabet must have at most 256 characters");if(new Set(t).size!==t.length)throw new TypeError("alphabet has duplicate characters")}function g(t,r,c){let o=t.length,e=1;for(;e<o;)e=e<<1|1;let l=Math.ceil(1.6*e*r/o),n="";for(;n.length<r;){let s=c(l);for(let i=0;i<s.length&&n.length<r;i++){let u=s[i]&e;u<o&&(n+=t[u]);}}return n}function h(t){if(!Number.isInteger(t)||t<1||t>255)throw new RangeError(`length must be 1\u2013255, got ${t}`)}function m(t){let r=new Uint8Array(t);return crypto.getRandomValues(r),r}var p=21;function v(t){a(t);}function E(t){return a(t),{generate(r=p){return h(r),g(t,r,m)}}}export{E as createAlphabet,v as validateAlphabet};
@@ -0,0 +1,7 @@
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
+ export { DEFAULT_ALPHABET as D };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,4 @@
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_-";
1
+ export { D as DEFAULT_ALPHABET } from './constants-CFnCjbhy.js';
6
2
 
7
3
  /**
8
4
  * Generates a cryptographically secure random ID.
@@ -16,4 +12,4 @@ declare const DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst
16
12
  */
17
13
  declare function generateId(length?: number): string;
18
14
 
19
- export { DEFAULT_ALPHABET, generateId };
15
+ export { generateId };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function m(t,r,p){let o=t.length,e=1;for(;e<o;)e=e<<1|1;let g=Math.ceil(1.6*e*r/o),n="";for(;n.length<r;){let i=p(g);for(let a=0;a<i.length&&n.length<r;a++){let s=i[a]&e;s<o&&(n+=t[s]);}}return n}function u(t){if(!Number.isInteger(t)||t<1||t>255)throw new RangeError(`length must be 1\u2013255, got ${t}`)}var h="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";function c(t){let r=new Uint8Array(t);return globalThis.crypto.getRandomValues(r),r}var f=21;function x(t=f){return u(t),m(h,t,c)}export{h as DEFAULT_ALPHABET,x as generateId};
1
+ function s(t){if(!Number.isInteger(t)||t<1||t>255)throw new RangeError(`length must be 1\u2013255, got ${t}`)}var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";var n=1024,i=new Uint8Array(n),m=e,r=n;function a(t){r+t>n&&(crypto.getRandomValues(i),r=0);let o="",f=r+t;for(;r<f;)o+=m[i[r++]&63];return o}function w(t=21){return s(t),a(t)}export{e as DEFAULT_ALPHABET,w as generateId};
@@ -0,0 +1,6 @@
1
+ export { D as DEFAULT_ALPHABET } from './constants-CFnCjbhy.js';
2
+
3
+ declare function generateDefault(length?: number): string;
4
+ declare function generateId(length?: number): string;
5
+
6
+ export { generateDefault, generateId };
package/dist/native.js ADDED
@@ -0,0 +1 @@
1
+ import {createRequire}from'module';function n(e){if(!Number.isInteger(e)||e<1||e>255)throw new RangeError(`length must be 1\u2013255, got ${e}`)}var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";var d=createRequire(import.meta.url),u="@sigilid/native-addon";function f(){return typeof process<"u"&&typeof process.versions?.node=="string"}function o(e=u){if(!f())throw new Error("sigilid/native is only supported in Node.js.");try{let t=d(e);if(typeof t.generateDefault!="function")throw new TypeError(`${e} does not export generateDefault(length).`);return t}catch(t){let i=`sigilid/native failed to load ${e}. Install it with "npm install ${e}" and ensure native builds are available.`,r=new Error(i);throw t instanceof Error&&Object.defineProperty(r,"cause",{value:t,enumerable:false,configurable:true,writable:true}),r}}var l=o();function p(e=21){return n(e),l.generateDefault(e)}function h(e=21){return p(e)}export{a as DEFAULT_ALPHABET,p as generateDefault,h as generateId};
@@ -1 +1 @@
1
- function o(t){if(!Number.isInteger(t)||t<1||t>255)throw new RangeError(`length must be 1\u2013255, got ${t}`)}var r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";var i=21;function c(t=i){o(t);let e="",s=r.length;for(let n=0;n<t;n++)e+=r[Math.floor(Math.random()*s)];return e}export{c as generateNonSecureId};
1
+ function n(r){if(!Number.isInteger(r)||r<1||r>255)throw new RangeError(`length must be 1\u2013255, got ${r}`)}var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";var i=21;function f(r=i){n(r);let t="";for(let e=0;e<r;e++)t+=o[Math.random()*64|0];return t}export{f as generateNonSecureId};
package/dist/prefix.js CHANGED
@@ -1 +1 @@
1
- function a(t,r,e){let i=t.length,n=1;for(;n<i;)n=n<<1|1;let b=Math.ceil(1.6*n*r/i),o="";for(;o.length<r;){let h=e(b);for(let s=0;s<h.length&&o.length<r;s++){let f=h[s]&n;f<i&&(o+=t[f]);}}return o}function u(t){if(!Number.isInteger(t)||t<1||t>255)throw new RangeError(`length must be 1\u2013255, got ${t}`)}function c(t){if(typeof t!="string"||t.length===0)throw new TypeError("prefix must be a non-empty string");if(!/^[a-zA-Z][a-zA-Z0-9]*$/.test(t))throw new TypeError("prefix must start with a letter and contain only alphanumeric characters")}var m="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";function g(t){let r=new Uint8Array(t);return globalThis.crypto.getRandomValues(r),r}var p=21,l="_";function P(t,r=p){c(t),u(r);let e=a(m,r,g);return `${t}${l}${e}`}function $(t,r=p){return c(t),u(r),()=>{let e=a(m,r,g);return `${t}${l}${e}`}}export{$ as createPrefixedGenerator,P as generatePrefixedId};
1
+ function n(r){if(!Number.isInteger(r)||r<1||r>255)throw new RangeError(`length must be 1\u2013255, got ${r}`)}function o(r){if(typeof r!="string"||r.length===0)throw new TypeError("prefix must be a non-empty string");if(!/^[a-z][a-z\d]*$/i.test(r))throw new TypeError("prefix must start with a letter and contain only alphanumeric characters")}var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";var s=1024,u=new Uint8Array(s),c=a,e=s;function i(r){e+r>s&&(crypto.getRandomValues(u),e=0);let t="",f=e+r;for(;e<f;)t+=c[u[e++]&63];return t}function x(r,t=21){return o(r),n(t),`${r}_${i(t)}`}function E(r,t=21){return o(r),n(t),()=>`${r}_${i(t)}`}export{E as createPrefixedGenerator,x as generatePrefixedId};
package/dist/typed.js CHANGED
@@ -1 +1 @@
1
- function c(t,r,n){let o=t.length,e=1;for(;e<o;)e=e<<1|1;let h=Math.ceil(1.6*e*r/o),s="";for(;s.length<r;){let i=n(h);for(let a=0;a<i.length&&s.length<r;a++){let u=i[a]&e;u<o&&(s+=t[u]);}}return s}function f(t){if(!Number.isInteger(t)||t<1||t>255)throw new RangeError(`length must be 1\u2013255, got ${t}`)}function m(t){if(typeof t!="string"||t.length===0)throw new TypeError("prefix must be a non-empty string");if(!/^[a-zA-Z][a-zA-Z0-9]*$/.test(t))throw new TypeError("prefix must start with a letter and contain only alphanumeric characters")}var g="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";function d(t){let r=new Uint8Array(t);return globalThis.crypto.getRandomValues(r),r}var p=21,l="_";function B(t){return t}function I(t,r=p){return t!==void 0&&m(t),f(r),()=>{let n=c(g,r,d);return t!==void 0?`${t}${l}${n}`:n}}export{B as castId,I as createTypedGenerator};
1
+ function s(t){if(!Number.isInteger(t)||t<1||t>255)throw new RangeError(`length must be 1\u2013255, got ${t}`)}function a(t){if(typeof t!="string"||t.length===0)throw new TypeError("prefix must be a non-empty string");if(!/^[a-z][a-z\d]*$/i.test(t))throw new TypeError("prefix must start with a letter and contain only alphanumeric characters")}var i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";var o=1024,d=new Uint8Array(o),u=i,e=o;function f(t){e+t>o&&(crypto.getRandomValues(d),e=0);let r="",n=e+t;for(;e<n;)r+=u[d[e++]&63];return r}function l(t){return t}function w(t,r=21){return t!==void 0&&a(t),s(r),()=>{let n=f(r);return t!==void 0?`${t}_${n}`:n}}export{l as castId,w as createTypedGenerator};
package/dist/validate.js CHANGED
@@ -1 +1 @@
1
- var r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";function a(t,e={}){if(typeof t!="string"||t.length===0)return false;if(e.alphabet!==void 0&&e.alphabet.length===0)throw new TypeError("ValidationOptions.alphabet must not be empty");let n=t;if(e.prefix!==void 0){let i=`${e.prefix}_`;if(!t.startsWith(i))return false;n=t.slice(i.length);}if(e.length!==void 0&&n.length!==e.length||n.length===0)return false;if(e.alphabet!==void 0){let i=new Set(e.alphabet);for(let o of n)if(!i.has(o))return false}return true}function l(t,e={}){let n={alphabet:r,...e};return a(t,n)}function s(t,e={}){if(!l(t,e)){let n=t.length>8?`${t.slice(0,8)}\u2026`:t;throw new TypeError(`Invalid ID: "${n}"`)}}function g(t,e={}){return s(t,e),t}export{s as assertValidId,l as isValidId,g as parseId};
1
+ var r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";function a(t,e={}){if(typeof t!="string"||t.length===0)return false;if(e.alphabet!==void 0&&e.alphabet.length===0)throw new TypeError("alphabet option must not be empty");let n=t;if(e.prefix!==void 0){let i=`${e.prefix}_`;if(!t.startsWith(i))return false;n=t.slice(i.length);}if(e.length!==void 0&&n.length!==e.length||n.length===0)return false;if(e.alphabet!==void 0){let i=new Set(e.alphabet);for(let o of n)if(!i.has(o))return false}return true}function l(t,e={}){let n={alphabet:r,...e};return a(t,n)}function s(t,e={}){if(!l(t,e)){let n=t.length>8?`${t.slice(0,8)}\u2026`:t;throw new TypeError(`Invalid ID: "${n}"`)}}function g(t,e={}){return s(t,e),t}export{s as assertValidId,l as isValidId,g as parseId};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigilid",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A tiny, tree-shakeable ID toolkit for TypeScript apps. Secure core with optional subpath exports for prefixed IDs, typed IDs, validation, and custom alphabets.",
5
5
  "license": "MIT",
6
6
  "author": "moritzmyrz",
@@ -38,6 +38,10 @@
38
38
  "types": "./dist/index.d.ts",
39
39
  "import": "./dist/index.js"
40
40
  },
41
+ "./native": {
42
+ "types": "./dist/native.d.ts",
43
+ "node": "./dist/native.js"
44
+ },
41
45
  "./non-secure": {
42
46
  "types": "./dist/non-secure.d.ts",
43
47
  "import": "./dist/non-secure.js"
@@ -60,9 +64,17 @@
60
64
  },
61
65
  "./package.json": "./package.json"
62
66
  },
63
- "files": ["dist", "README.md", "LICENSE"],
67
+ "files": [
68
+ "dist",
69
+ "README.md",
70
+ "LICENSE"
71
+ ],
64
72
  "scripts": {
65
73
  "build": "tsup",
74
+ "build:native-addon": "npm --prefix packages/native-addon run build",
75
+ "prebuild:native-addon": "npm --prefix packages/native-addon run prebuild",
76
+ "release:check": "npm run lint && npm run typecheck && npm run test && npm run build",
77
+ "release:pack": "npm pack && npm pack ./packages/native-addon",
66
78
  "test": "vitest run",
67
79
  "test:watch": "vitest",
68
80
  "test:coverage": "vitest run --coverage",
@@ -73,41 +85,44 @@
73
85
  "clean": "rm -rf dist *.tsbuildinfo",
74
86
  "verify": "npm run lint && npm run typecheck && npm run test && npm run build",
75
87
  "playground": "tsx tools/playground.ts",
76
- "bench": "tsx tools/benchmark.ts"
88
+ "bench": "tsx tools/benchmark.ts",
89
+ "bench:native": "tsx tools/benchmark-native.ts"
77
90
  },
78
91
  "size-limit": [
79
92
  {
80
93
  "path": "dist/index.js",
81
94
  "brotli": true,
82
- "limit": "350 B"
95
+ "limit": "265 B"
83
96
  },
84
97
  {
85
98
  "path": "dist/non-secure.js",
86
99
  "brotli": true,
87
- "limit": "250 B"
100
+ "limit": "245 B"
88
101
  },
89
102
  {
90
103
  "path": "dist/prefix.js",
91
104
  "brotli": true,
92
- "limit": "500 B"
105
+ "limit": "385 B"
93
106
  },
94
107
  {
95
108
  "path": "dist/typed.js",
96
109
  "brotli": true,
97
- "limit": "500 B"
110
+ "limit": "420 B"
98
111
  },
99
112
  {
100
113
  "path": "dist/validate.js",
101
114
  "brotli": true,
102
- "limit": "400 B"
115
+ "limit": "395 B"
103
116
  },
104
117
  {
105
118
  "path": "dist/alphabet.js",
106
119
  "brotli": true,
107
- "limit": "400 B"
120
+ "limit": "415 B"
108
121
  }
109
122
  ],
110
123
  "devDependencies": {
124
+ "@sigilid/native-addon": "file:packages/native-addon",
125
+ "@types/node": "^24.5.2",
111
126
  "@biomejs/biome": "^1.9.4",
112
127
  "@size-limit/preset-small-lib": "^12.0.1",
113
128
  "@vitest/coverage-v8": "^3.0.7",