Skip to content

Commit c61dfb3

Browse files
committed
Merge branch 'dingosky' into main
2 parents a649de2 + e812c04 commit c61dfb3

34 files changed

+9879
-5575
lines changed

.eslintrc.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ rules:
3636
- newlines-between: always
3737
alphabetize:
3838
order: asc
39+
max-len:
40+
- error
41+
- code: 120
42+
ignoreUrls: true
43+
ignoreStrings: true
44+
ignoreTemplateLiterals: true
45+
ignoreRegExpLiterals: true
3946
semi: 0
4047
sort-imports:
4148
- error

.github/workflows/ci.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
8+
jobs:
9+
build-test:
10+
name: build+test (${{ matrix.os }} / node@${{ matrix.node }})
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest, macos-latest, windows-latest]
16+
node: [20]
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: ${{ matrix.node }}
25+
cache: 'yarn'
26+
27+
- name: Enable Corepack (Yarn)
28+
run: corepack enable
29+
30+
- name: Install dependencies
31+
run: yarn install --immutable
32+
33+
- name: Build
34+
run: yarn build
35+
36+
- name: Node ESM import smoke
37+
run: node --input-type=module -e "import { generate, puid } from './build/module/index.mjs'; console.log(typeof generate==='function' && typeof puid==='function' ? 'ok' : 'bad')"
38+
39+
- name: Test
40+
run: yarn test:unit
41+
42+
esm-example:
43+
name: esm example (ubuntu)
44+
runs-on: ubuntu-latest
45+
steps:
46+
- name: Checkout
47+
uses: actions/checkout@v4
48+
49+
- name: Setup Node.js
50+
uses: actions/setup-node@v4
51+
with:
52+
node-version: '20'
53+
54+
- name: Enable Corepack (Yarn)
55+
run: corepack enable
56+
57+
- name: Install deps and build package
58+
run: |
59+
yarn install --immutable
60+
yarn build
61+
62+
- name: Pack tarball
63+
run: npm pack --json > pack.json
64+
65+
- name: Install tarball into example
66+
run: |
67+
TARBALL=$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('pack.json','utf8'))[0].filename)")
68+
cd examples/node-esm
69+
npm i --no-audit --no-fund ../../$TARBALL
70+
71+
- name: Run example
72+
run: node examples/node-esm/index.mjs
73+

.github/workflows/release.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
publish:
13+
name: publish to npm with provenance
14+
runs-on: ubuntu-latest
15+
permissions:
16+
id-token: write # required for provenance
17+
contents: read
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Setup Node.js
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: '20'
26+
registry-url: 'https://registry.npmjs.org'
27+
28+
- name: Enable Corepack
29+
run: corepack enable
30+
31+
- name: Install dependencies
32+
run: yarn install --immutable
33+
34+
- name: Build
35+
run: yarn build
36+
37+
- name: Unit tests
38+
run: yarn test:unit
39+
40+
- name: Publish
41+
env:
42+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
43+
run: npm publish --provenance --access public
44+

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@ coverage
88
package-lock.json
99
puid-js-*.tgz
1010
*~
11-
.yarn
11+
.yarn/*
12+
!.yarn/releases
13+
!.yarn/releases/**
1214
timing
15+
scripts/build/*.js
16+
scripts/build/*.mjs
17+
!scripts/build/mjsify.cjs

.vscode/settings.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,12 @@
44
"typescript.enablePromptUseWorkspaceTsdk": true,
55
"files.exclude": {
66
"**/.nyc_output": true
7-
}
8-
}
7+
},
8+
"cSpell.words": [
9+
"alphanum",
10+
"bitauth",
11+
"Crockford",
12+
"libauth",
13+
"Puid"
14+
]
15+
}

.yarn/releases/yarn-4.9.4.cjs

Lines changed: 942 additions & 0 deletions
Large diffs are not rendered by default.

.yarnrc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
nodeLinker: node-modules
2+
yarnPath: .yarn/releases/yarn-4.9.4.cjs

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# Changelog
22

3+
## v2.0.0 (2025-08-27)
4+
5+
### Breaking
6+
7+
- Raise Node.js engines requirement to >=18
8+
- Introduce exports map and ESM/browser export conditions; deep imports may break depending on consumers' bundlers/resolvers
9+
10+
### Features
11+
12+
- New browser-friendly entry point (`import { puid } from 'puid-js/web'`)
13+
- Add `generate(config?)` convenience function
14+
15+
### Fixes
16+
17+
- Browser-safe ERE computation via new `byteLength` helper (uses TextEncoder in browsers)
18+
- Correct ESM `.mjs` and browser export conditions
19+
20+
### Performance
21+
22+
- Reduce allocations on the power-of-two path in `bits` by replacing map with a preallocated loop
23+
- Cache `bitShifts` per charset to avoid recomputation
24+
25+
### Refactor/Types
26+
27+
- Narrow `EntropyFunction` to a discriminated union and remove casts in `fillEntropy`
28+
- Improve Web Crypto detection and binding
29+
- Freeze `puid.info` for safer consumption
30+
31+
### Build/Tooling/Docs
32+
33+
- Pin Yarn 4.9.4 and add CI/release workflows
34+
- Raise TypeScript targets, enable strict typing, add subpath exports and npm metadata
35+
- Add tinybench micro-benchmark and Node ESM consumer example
36+
- Documentation: add "Migrating from UUID v4" section
37+
338
## v1.3.1 (2023-08-11)
439

540
### Fixes

README.md

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ randId()
2828
- [Overkill and Under Specify](#Overkill)
2929
- [Efficiencies](#Efficiencies)
3030
- [tl;dr](#tl;dr)
31+
- [Migrating from UUID v4](#UUIDv4Migration)
3132

3233
## <a name="Overview"></a>Overview
3334

@@ -45,7 +46,7 @@ Random string generation can be thought of as a _transformation_ of some random
4546

4647
What characters are used in the ID?
4748

48-
> `puid-js` provides 16 pre-defined character sets, as well as allows custom characters, including Unicode
49+
> `puid-js` provides 19 pre-defined character sets, as well as allows custom characters, including Unicode
4950
5051
3. **ID randomness**
5152

@@ -55,9 +56,66 @@ Random string generation can be thought of as a _transformation_ of some random
5556
5657
[TOC](#TOC)
5758

59+
### <a name="UUIDv4Migration"></a>Migrating from UUID v4
60+
61+
- UUID v4 has 122 bits of entropy (36 chars with hyphens; 32 hex chars without). Default `puid-js` IDs are ~132 bits in 22 URL/file-safe chars.
62+
63+
Replace uuidv4() one-off
64+
65+
```js
66+
// before
67+
import { v4 as uuidv4 } from 'uuid'
68+
const id = uuidv4()
69+
70+
// after
71+
import { generate, Chars } from 'puid-js'
72+
// ≈132 bits, 22 chars, URL/file-safe
73+
const id = generate({ chars: Chars.Safe64 })
74+
75+
// hex-like (32 chars, 128 bits)
76+
const hexId = generate({ bits: 128, chars: Chars.HexUpper })
77+
```
78+
79+
Use a generator in hot paths
80+
81+
```js
82+
import { puid, Chars } from 'puid-js'
83+
84+
// explicit bits (≈ UUID v4 or better)
85+
const { generator: id128 } = puid({ bits: 128, chars: Chars.Safe64 })
86+
87+
// or size by total/risk (10M IDs, 1e-12 repeat risk)
88+
const { generator: sized } = puid({ total: 1e7, risk: 1e12, chars: Chars.Safe64 })
89+
90+
const id = id128()
91+
```
92+
93+
Browser
94+
95+
```js
96+
import { generate, Chars } from 'puid-js/web'
97+
const id = generate({ chars: Chars.Safe64 })
98+
```
99+
100+
Error handling for generate()
101+
102+
```js
103+
try {
104+
generate({ total: 1000 }) // invalid: missing risk
105+
} catch (err) {
106+
// handle invalid config
107+
}
108+
```
109+
110+
Notes
111+
- If your DB/validators assume UUID format/length, update to accept generic ID strings. Default Safe64 is 22 chars; HexUpper at 128 bits is 32 chars.
112+
- Charset guidance: Safe64 (shortest URL/file-safe), Hex/HexUpper (compat), Safe32/WordSafe32 (human-friendlier).
113+
114+
And remember, you rarely need the 122-bytes of entropy provided by UUID, and you certainly never need the inefficiency of the string representation!
115+
58116
### <a name="Usage"></a>Usage
59117

60-
Creating a random ID generator using `puid-js` is a simple as:
118+
Creating a random ID generator using `puid-js` is as simple as:
61119

62120
```js
63121
const { puid } = require('puid-js')
@@ -67,6 +125,28 @@ randId()
67125
// => 'fxgA7EO_YklcUnrPenF284'
68126
```
69127

128+
Convenience: one-off generate
129+
130+
```js
131+
import { generate, Chars } from 'puid-js' // or: const { generate, Chars } = require('puid-js')
132+
133+
const id = generate() // defaults (128 bits, Safe64, secure entropy)
134+
const token = generate({ bits: 256, chars: Chars.HexUpper })
135+
136+
// generate() throws on invalid config — use try/catch if you pass dynamic input
137+
try {
138+
generate({ total: 1000 })
139+
} catch (err) {
140+
console.error('Invalid config:', err.message)
141+
}
142+
```
143+
144+
Stable deep import:
145+
146+
```js
147+
import generate from 'puid-js/generate'
148+
```
149+
70150
**Entropy Source**
71151

72152
`puid-js` uses `crypto.randomBytes` as the default entropy source. Options can be used to configure a specific entropy source:
@@ -86,7 +166,7 @@ randId()
86166

87167
**ID Characters**
88168

89-
By default, `puid-js` use the [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5) file system & URL safe characters. The `chars` option can by used to specify any of 16 [pre-defined character sets](#Chars) or custom characters, including Unicode:
169+
By default, `puid-js` uses the [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5) file system & URL safe characters. The `chars` option can be used to specify any of 19 [pre-defined character sets](#Chars) or custom characters, including Unicode:
90170

91171
```js
92172
const { Chars, puid } = require('puid-js')
@@ -138,15 +218,37 @@ token()
138218
yarn add puid-js
139219
```
140220

221+
Requires Node.js >= 18.
222+
141223
#### NPM
142224

143225
```bash
144226
npm install puid-js
145227
```
146228

229+
### Browser (ESM)
230+
231+
Note: Many bundlers honor the "browser" export condition. Importing `puid-js` in a browser build will resolve to the web entry automatically. You can also import explicitly from `puid-js/web`.
232+
233+
```html
234+
<script type="module">
235+
import { puid, generate, Chars } from 'puid-js/web'
236+
// Web-friendly import: defaults to Web Crypto when available and avoids bundling Node crypto
237+
const { generator: id } = puid({ chars: Chars.Safe32 })
238+
console.log(id())
239+
// Or one-off
240+
console.log(generate({ chars: Chars.Safe32 }))
241+
</script>
242+
```
243+
147244
### <a name="API"></a>API
148245

149-
`puid-js` exports a higher-order function (HOF), `puid`, used to create random ID generators. The `puid` HOF takes an optional `PuidConfig` object for configuration and returns an object of the form `{ generator: () => string, error: Error }` that either passes back the `puid` generating function or an `Error` indicating a problem with the specified configuration.
246+
`puid-js` exports:
247+
248+
- `puid(config?) => { generator, error }` — higher-order function (HOF) to create generators
249+
- `generate(config?) => string` — convenience wrapper returning a single ID (throws on invalid config)
250+
251+
The `puid` HOF takes an optional `PuidConfig` object for configuration and returns an object of the form `{ generator: () => string, error: Error }` that either passes back the `puid` generating function or an `Error` indicating a problem with the specified configuration.
150252

151253
#### PuidConfig
152254

@@ -213,7 +315,7 @@ There are 19 pre-defined character sets:
213315

214316
| Name | Characters |
215317
| :------------- | :-------------------------------------------------------------------------------------------- |
216-
| ALpha | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
318+
| Alpha | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
217319
| AlphaLower | abcdefghijklmnopqrstuvwxyz |
218320
| AlphaUpper | ABCDEFGHIJKLMNOPQRSTUVWXYZ |
219321
| AlphaNum | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 |

eslint.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module.exports = (async () => (await import('./eslint.config.mjs')).default)()
2+

0 commit comments

Comments
 (0)