use-prms 0.2.0 → 0.4.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
@@ -1,35 +1,57 @@
1
1
  # use-prms
2
2
 
3
- Type-safe URL query parameter management with minimal, human-readable encoding.
4
-
5
- ## Features
3
+ [![npm version](https://img.shields.io/npm/v/use-prms)](https://www.npmjs.com/package/use-prms)
4
+ [![license](https://img.shields.io/npm/l/use-prms)](https://github.com/runsascoded/use-prms/blob/main/LICENSE)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/use-prms)](https://bundlephobia.com/package/use-prms)
6
+
7
+ Type-safe URL-parameter (query and hash) management with minimal, human-readable encoding and decoding.
8
+
9
+ <!-- `toc` -->
10
+ - [Features](#features)
11
+ - [Installation](#install)
12
+ - [Quick Start](#quick-start)
13
+ - [Built-in Param Types](#param-types)
14
+ - [Custom Params](#custom)
15
+ - [Batch Updates](#batch)
16
+ - [URL Encoding](#encoding)
17
+ - [Binary Encoding](#binary)
18
+ - [Framework-Agnostic Core](#core)
19
+ - [Hash Params](#hash)
20
+ - [API Reference](#api)
21
+ - [Examples](#examples)
22
+ - [Reverse Inspo](#reverse-inspo)
23
+ - [License](#license)
24
+
25
+ ## Features <a id="features"></a>
6
26
 
7
27
  - 🎯 **Type-safe**: Full TypeScript support with generic `Param<T>` interface
8
28
  - 📦 **Tiny URLs**: Smart encoding - omit defaults, use short keys, `+` for spaces
9
- - ⚛️ **React hooks**: `useUrlParam()` and `useUrlParams()` for seamless integration
29
+ - ⚛️ **React hooks**: `useUrlState()` and `useUrlStates()` for seamless integration
10
30
  - 🔧 **Framework-agnostic**: Core utilities work anywhere, React hooks are optional
11
31
  - 🌳 **Tree-shakeable**: ESM + CJS builds with TypeScript declarations
12
32
  - 0️⃣ **Zero dependencies**: Except React (peer dependency, optional)
13
33
  - 🔁 **Multi-value params**: Support for repeated keys like `?tag=a&tag=b`
14
34
  - #️⃣ **Hash params**: Use hash fragment (`#key=value`) instead of query string
15
35
 
16
- ## Installation
36
+ ## Installation <a id="install"></a>
17
37
 
18
38
  ```bash
19
39
  npm install use-prms
20
- # or
40
+ ```
41
+ Or:
42
+ ```bash
21
43
  pnpm add use-prms
22
44
  ```
23
45
 
24
- ## Quick Start
46
+ ## Quick Start <a id="quick-start"></a>
25
47
 
26
48
  ```typescript
27
- import { useUrlParam, boolParam, stringParam, intParam } from 'use-prms'
49
+ import { useUrlState, boolParam, stringParam, intParam } from 'use-prms'
28
50
 
29
51
  function MyComponent() {
30
- const [zoom, setZoom] = useUrlParam('z', boolParam)
31
- const [device, setDevice] = useUrlParam('d', stringParam())
32
- const [count, setCount] = useUrlParam('n', intParam(10))
52
+ const [zoom, setZoom] = useUrlState('z', boolParam)
53
+ const [device, setDevice] = useUrlState('d', stringParam())
54
+ const [count, setCount] = useUrlState('n', intParam(10))
33
55
 
34
56
  // URL: ?z&d=gym&n=5
35
57
  // zoom = true, device = "gym", count = 5
@@ -44,35 +66,35 @@ function MyComponent() {
44
66
  }
45
67
  ```
46
68
 
47
- ## Built-in Param Types
69
+ ## Built-in Param Types <a id="param-types"></a>
48
70
 
49
71
  ### Boolean
50
72
  ```typescript
51
- const [enabled, setEnabled] = useUrlParam('e', boolParam)
73
+ const [enabled, setEnabled] = useUrlState('e', boolParam)
52
74
  // ?e → true
53
75
  // (absent) → false
54
76
  ```
55
77
 
56
78
  ### Strings
57
79
  ```typescript
58
- const [name, setName] = useUrlParam('n', stringParam()) // optional
59
- const [mode, setMode] = useUrlParam('m', defStringParam('auto')) // with default
80
+ const [name, setName] = useUrlState('n', stringParam()) // optional
81
+ const [mode, setMode] = useUrlState('m', defStringParam('auto')) // with default
60
82
  // ?n=foo → "foo"
61
83
  // (absent) → undefined / "auto"
62
84
  ```
63
85
 
64
86
  ### Numbers
65
87
  ```typescript
66
- const [count, setCount] = useUrlParam('c', intParam(0))
67
- const [ratio, setRatio] = useUrlParam('r', floatParam(1.0))
68
- const [id, setId] = useUrlParam('id', optIntParam) // number | null
88
+ const [count, setCount] = useUrlState('c', intParam(0))
89
+ const [ratio, setRatio] = useUrlState('r', floatParam(1.0))
90
+ const [id, setId] = useUrlState('id', optIntParam) // number | null
69
91
  // ?c=5&r=1.5&id=123 → 5, 1.5, 123
70
92
  // (absent) → 0, 1.0, null
71
93
  ```
72
94
 
73
95
  ### Enums
74
96
  ```typescript
75
- const [theme, setTheme] = useUrlParam(
97
+ const [theme, setTheme] = useUrlState(
76
98
  't',
77
99
  enumParam('light', ['light', 'dark', 'auto'] as const)
78
100
  )
@@ -82,20 +104,20 @@ const [theme, setTheme] = useUrlParam(
82
104
 
83
105
  ### Arrays (delimiter-separated)
84
106
  ```typescript
85
- const [tags, setTags] = useUrlParam('tags', stringsParam([], ','))
86
- const [ids, setIds] = useUrlParam('ids', numberArrayParam([]))
107
+ const [tags, setTags] = useUrlState('tags', stringsParam([], ','))
108
+ const [ids, setIds] = useUrlState('ids', numberArrayParam([]))
87
109
  // ?tags=foo,bar,baz → ["foo", "bar", "baz"]
88
110
  // ?ids=1,2,3 → [1, 2, 3]
89
111
  ```
90
112
 
91
113
  ### Multi-value Arrays (repeated keys)
92
114
  ```typescript
93
- import { useMultiUrlParam, multiStringParam, multiIntParam } from 'use-prms'
115
+ import { useMultiUrlState, multiStringParam, multiIntParam } from 'use-prms'
94
116
 
95
- const [tags, setTags] = useMultiUrlParam('tag', multiStringParam())
117
+ const [tags, setTags] = useMultiUrlState('tag', multiStringParam())
96
118
  // ?tag=foo&tag=bar&tag=baz → ["foo", "bar", "baz"]
97
119
 
98
- const [ids, setIds] = useMultiUrlParam('id', multiIntParam())
120
+ const [ids, setIds] = useMultiUrlState('id', multiIntParam())
99
121
  // ?id=1&id=2&id=3 → [1, 2, 3]
100
122
 
101
123
  // Also available: multiFloatParam()
@@ -104,14 +126,14 @@ const [ids, setIds] = useMultiUrlParam('id', multiIntParam())
104
126
  ### Compact Code Mapping
105
127
  ```typescript
106
128
  // Single value with short codes
107
- const [metric, setMetric] = useUrlParam('y', codeParam('Rides', {
129
+ const [metric, setMetric] = useUrlState('y', codeParam('Rides', {
108
130
  Rides: 'r',
109
131
  Minutes: 'm',
110
132
  }))
111
133
  // ?y=m → "Minutes", omitted for default "Rides"
112
134
 
113
135
  // Multi-value with short codes (omits when all selected)
114
- const [regions, setRegions] = useUrlParam('r', codesParam(
136
+ const [regions, setRegions] = useUrlState('r', codesParam(
115
137
  ['NYC', 'JC', 'HOB'],
116
138
  { NYC: 'n', JC: 'j', HOB: 'h' }
117
139
  ))
@@ -120,7 +142,7 @@ const [regions, setRegions] = useUrlParam('r', codesParam(
120
142
 
121
143
  ### Pagination
122
144
  ```typescript
123
- const [page, setPage] = useUrlParam('p', paginationParam(20))
145
+ const [page, setPage] = useUrlState('p', paginationParam(20))
124
146
  // Encodes offset + pageSize compactly using + as delimiter:
125
147
  // { offset: 0, pageSize: 20 } → (omitted)
126
148
  // { offset: 0, pageSize: 50 } → ?p=+50
@@ -128,7 +150,7 @@ const [page, setPage] = useUrlParam('p', paginationParam(20))
128
150
  // { offset: 100, pageSize: 50 } → ?p=100+50
129
151
  ```
130
152
 
131
- ## Custom Params
153
+ ## Custom Params <a id="custom"></a>
132
154
 
133
155
  Create your own param encoders/decoders:
134
156
 
@@ -152,18 +174,18 @@ const dateParam: Param<Date> = {
152
174
  }
153
175
  }
154
176
 
155
- const [date, setDate] = useUrlParam('d', dateParam)
177
+ const [date, setDate] = useUrlState('d', dateParam)
156
178
  // ?d=251123 → Date(2025, 10, 23)
157
179
  ```
158
180
 
159
- ## Batch Updates
181
+ ## Batch Updates <a id="batch"></a>
160
182
 
161
- Use `useUrlParams()` to update multiple parameters atomically:
183
+ Use `useUrlStates()` to update multiple parameters atomically:
162
184
 
163
185
  ```typescript
164
- import { useUrlParams, intParam, boolParam } from 'use-prms'
186
+ import { useUrlStates, intParam, boolParam } from 'use-prms'
165
187
 
166
- const { values, setValues } = useUrlParams({
188
+ const { values, setValues } = useUrlStates({
167
189
  page: intParam(1),
168
190
  size: intParam(20),
169
191
  grid: boolParam
@@ -173,7 +195,7 @@ const { values, setValues } = useUrlParams({
173
195
  setValues({ page: 2, size: 50 })
174
196
  ```
175
197
 
176
- ## URL Encoding
198
+ ## URL Encoding <a id="encoding"></a>
177
199
 
178
200
  - **Spaces**: Encoded as `+` (standard form-urlencoded)
179
201
  - **Defaults**: Omitted from URL (keeps URLs minimal)
@@ -182,44 +204,105 @@ setValues({ page: 2, size: 50 })
182
204
 
183
205
  Example:
184
206
  ```typescript
185
- const [devices, setDevices] = useUrlParam('d', stringsParam([], ' '))
207
+ const [devices, setDevices] = useUrlState('d', stringsParam([], ' '))
186
208
  setDevices(['gym', 'bedroom'])
187
209
  // URL: ?d=gym+bedroom
188
210
  ```
189
211
 
190
- ## Framework-Agnostic Core
212
+ ## Binary Encoding <a id="binary"></a>
213
+
214
+ For complex data that doesn't fit well into string encoding, `use-prms` provides binary encoding utilities with URL-safe base64.
215
+
216
+ ### BitBuffer
217
+
218
+ Low-level bit packing for custom binary formats:
219
+
220
+ ```typescript
221
+ import { BitBuffer } from 'use-prms'
222
+
223
+ // Encoding
224
+ const buf = new BitBuffer()
225
+ buf.encodeInt(myEnum, 3) // 3 bits for enum (0-7)
226
+ buf.encodeInt(myCount, 8) // 8 bits for count (0-255)
227
+ buf.encodeBigInt(myId, 48) // 48 bits for ID
228
+ const urlParam = buf.toBase64()
229
+
230
+ // Decoding
231
+ const buf = BitBuffer.fromBase64(urlParam)
232
+ const myEnum = buf.decodeInt(3)
233
+ const myCount = buf.decodeInt(8)
234
+ const myId = buf.decodeBigInt(48)
235
+ ```
236
+
237
+ ### Float Params
238
+
239
+ Encode floats compactly as base64:
240
+
241
+ ```typescript
242
+ import { floatParam } from 'use-prms'
243
+
244
+ // Lossless (11 chars, exact IEEE 754)
245
+ const [zoom, setZoom] = useUrlState('z', floatParam(1.0))
246
+
247
+ // Lossy (fewer chars, configurable precision)
248
+ const [lat, setLat] = useUrlState('lat', floatParam({
249
+ default: 0,
250
+ exp: 5, // exponent bits
251
+ mant: 22, // mantissa bits (~7 decimal digits)
252
+ }))
253
+ ```
254
+
255
+ ### Custom Alphabets
256
+
257
+ Choose between standard base64url or ASCII-sorted alphabet:
258
+
259
+ ```typescript
260
+ import { ALPHABETS, binaryParam, floatParam } from 'use-prms'
261
+
262
+ // Standard RFC 4648 (default)
263
+ // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_
264
+
265
+ // ASCII-sorted (lexicographic sort = numeric sort)
266
+ // -0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
267
+
268
+ const param = floatParam({ default: 0, alphabet: 'sortable' })
269
+ ```
270
+
271
+ The `sortable` alphabet is useful when encoded strings need to sort in the same order as their numeric values (e.g., for database indexing).
272
+
273
+ ## Framework-Agnostic Core <a id="core"></a>
191
274
 
192
275
  Use the core utilities without React:
193
276
 
194
277
  ```typescript
195
- import { boolParam, serializeParams, parseParams } from 'use-prms'
278
+ import { boolParam, serializeMultiParams, parseMultiParams } from 'use-prms'
196
279
 
197
280
  // Encode
198
- const params = { z: boolParam.encode(true), d: 'gym' }
199
- const search = serializeParams(params) // "z&d=gym"
281
+ const params = { z: [boolParam.encode(true) ?? ''], d: ['gym'] }
282
+ const search = serializeMultiParams(params) // "z&d=gym"
200
283
 
201
284
  // Decode
202
- const parsed = parseParams(window.location.search)
203
- const zoom = boolParam.decode(parsed.z) // true
285
+ const parsed = parseMultiParams(window.location.search)
286
+ const zoom = boolParam.decode(parsed.z?.[0]) // true
204
287
  ```
205
288
 
206
- ## Hash Params
289
+ ## Hash Params <a id="hash"></a>
207
290
 
208
291
  Use hash fragment (`#key=value`) instead of query string (`?key=value`):
209
292
 
210
293
  ```typescript
211
294
  // Just change the import path
212
- import { useUrlParam, boolParam } from 'use-prms/hash'
295
+ import { useUrlState, boolParam } from 'use-prms/hash'
213
296
 
214
- const [zoom, setZoom] = useUrlParam('z', boolParam)
297
+ const [zoom, setZoom] = useUrlState('z', boolParam)
215
298
  // URL: https://example.com/#z (instead of ?z)
216
299
  ```
217
300
 
218
301
  Same API, different URL location. Useful when query strings conflict with server routing or you want params to survive page reloads without server involvement.
219
302
 
220
- ## API Reference
303
+ ## API Reference <a id="api"></a>
221
304
 
222
- ### `useUrlParam<T>(key: string, param: Param<T>, push?: boolean)`
305
+ ### `useUrlState<T>(key: string, param: Param<T>, push?: boolean)`
223
306
 
224
307
  React hook for managing a single URL parameter.
225
308
 
@@ -228,7 +311,7 @@ React hook for managing a single URL parameter.
228
311
  - `push`: Use pushState (true) or replaceState (false, default)
229
312
  - Returns: `[value: T, setValue: (value: T) => void]`
230
313
 
231
- ### `useUrlParams<P>(params: P, push?: boolean)`
314
+ ### `useUrlStates<P>(params: P, push?: boolean)`
232
315
 
233
316
  React hook for managing multiple URL parameters together.
234
317
 
@@ -236,7 +319,7 @@ React hook for managing multiple URL parameters together.
236
319
  - `push`: Use pushState (true) or replaceState (false, default)
237
320
  - Returns: `{ values, setValues }`
238
321
 
239
- ### `useMultiUrlParam<T>(key: string, param: MultiParam<T>, push?: boolean)`
322
+ ### `useMultiUrlState<T>(key: string, param: MultiParam<T>, push?: boolean)`
240
323
 
241
324
  React hook for managing a multi-value URL parameter (repeated keys).
242
325
 
@@ -292,6 +375,17 @@ type MultiParam<T> = {
292
375
  | `multiIntParam(init?)` | `MultiParam<number[]>` | Repeated integer params |
293
376
  | `multiFloatParam(init?)` | `MultiParam<number[]>` | Repeated float params |
294
377
 
378
+ ### Binary Encoding
379
+
380
+ | Export | Description |
381
+ |--------|-------------|
382
+ | `BitBuffer` | Bit-level buffer for packing/unpacking arbitrary bit widths |
383
+ | `binaryParam(opts)` | Create param from `toBytes`/`fromBytes` converters |
384
+ | `base64Param(toBytes, fromBytes)` | Shorthand for `binaryParam` |
385
+ | `base64Encode(bytes, opts?)` | Encode `Uint8Array` to base64 string |
386
+ | `base64Decode(str, opts?)` | Decode base64 string to `Uint8Array` |
387
+ | `ALPHABETS` | Preset alphabets: `rfc4648` (default), `sortable` (ASCII-ordered) |
388
+
295
389
  ### Core Utilities
296
390
 
297
391
  - `serializeParams(params)`: Convert params object to URL query string *(deprecated, use `serializeMultiParams`)*
@@ -301,20 +395,135 @@ type MultiParam<T> = {
301
395
  - `getCurrentParams()`: Get current URL params (browser only)
302
396
  - `updateUrl(params, push?)`: Update URL without reloading (browser only)
303
397
 
304
- ## Examples
398
+ ## Examples <a id="examples"></a>
305
399
 
306
400
  Projects using `use-prms`:
307
401
 
308
- - [runsascoded/awair] – Air quality dashboard with URL-persisted chart settings
402
+ - **[awair.runsascoded.com]** – Air quality dashboard ([GitHub][awair-gh], [usage][awair-search])
309
403
 
310
- Example: [`awair.runsascoded.com/?d=+br&y=thZ&t=-3d`][awair-example]
311
- - `d=+br`: show default device + "bedroom" (`+` encodes space, leading space means "include default")
312
- - `y=thZ`: left axis = temp (`t`), right axis = humidity (`h`), Y-axes don't start from zero (`Z`)
313
- - `t=-3d`: time range = last 3 days
404
+ Example: [`?d=+br&y=thZ&t=-3d`][awair-example]
405
+ - `d=+br`: devices (leading space = "include default")
406
+ - `y=thZ`: Y-axes config
407
+ - `t=-3d`: time range
314
408
 
315
- [runsascoded/awair]: https://github.com/runsascoded/awair
409
+ - **[ctbk.dev]** – Citi Bike trip data explorer ([GitHub][ctbk-gh], [usage][ctbk-search])
410
+
411
+ - **[kbd.rbw.sh]** – Keyboard shortcut manager demo site ([GitHub][use-kbd-gh], [usage][use-kbd-search])
412
+
413
+ [awair.runsascoded.com]: https://awair.runsascoded.com
414
+ [awair-gh]: https://github.com/runsascoded/awair
415
+ [awair-search]: https://github.com/search?q=repo%3Arunsascoded%2Fawair+use-prms&type=code
316
416
  [awair-example]: https://awair.runsascoded.com/?d=+br&y=thZ&t=-3d
317
417
 
318
- ## License
418
+ [ctbk.dev]: https://ctbk.dev
419
+ [ctbk-gh]: https://github.com/hudcostreets/ctbk.dev
420
+ [ctbk-search]: https://github.com/search?q=repo%3Ahudcostreets%2Fctbk.dev+use-prms&type=code
421
+
422
+ [kbd.rbw.sh]: https://kbd.rbw.sh
423
+ [use-kbd-gh]: https://github.com/runsascoded/use-kbd
424
+ [use-kbd-search]: https://github.com/search?q=repo%3Arunsascoded%2Fuse-kbd+use-prms&type=code
425
+
426
+ ## Reverse Inspo <a id="reverse-inspo"></a>
427
+
428
+ It's nice when URLs are concise but also reasonably human-readable. Some examples I've seen in the wild that exhibit room for improvement:
429
+
430
+ ### UUID Soup (OpenAI Careers)
431
+ ```
432
+ https://openai.com/careers/search/
433
+ ?l=e8062547-b090-4206-8f1e-7329e0014e98%2C07ed9191-5bc6-421b-9883-f1ac2e276ad7
434
+ &c=e1e973fe-6f0a-475f-9361-a9b6c095d869%2Cf002fe09-4cec-46b0-8add-8bf9ff438a62
435
+ %2Cab2b9da4-24a4-47df-8bed-1ed5a39c7036%2C687d87ec-1505-40e7-a2b5-cc7f31c0ea48
436
+ %2Cd36236ec-fb74-49bd-bd3f-9d8365e2e2cb%2C27c9a852-c401-450e-9480-d3b507b8f64a
437
+ %2C6dd4a467-446d-4093-8d57-d4633a571123%2C7cba3ac0-2b6e-4d52-ad38-e39a5f61c73f
438
+ %2C0f06f916-a404-414f-813f-6ac7ff781c61%2Cfb2b77c5-5f20-4a93-a1c4-c3d640d88e04
439
+ ```
440
+ 12 UUIDs for location and category filters. Each UUID is 36 characters. With short codes, this could be `?l=sf,ny&c=eng,res,des,acct,data,hr,infra,accel,acq,bus`.
441
+
442
+ ### Encrypted Blobs (Supercast, Priceline)
443
+ ```
444
+ https://feeds.supercast.com/episodes/8a1aa9e2dde4319825e6a8171b4d51fa1835ef4a
445
+ 6730170db60a92c8f0670bb08c3cef884f0e4288c970c980083820e89cd692f582c44cde
446
+ 544c7aae86fc721f69ed9f695a43e5e21f4d344b32e70bae48a8fe0ae8b472d99502041a
447
+ bad3dc650a6973653c094eae0631f637d96bb42ab5d26b8ea6b1638b7ffa23f66e46282b
448
+ 52970b59b2c13f9e6214251ad793be244bb9dc7e5bd7cefe77b6ec71b06c85e3bc9c194a
449
+ d4ca10b27cfd7b8b1c181b3d9aea144bb978d1d790f08d89049d5a29a477651f1b799eec
450
+ 827ed95209dc741207e2b331170cb01c625d51982913eb8757ef2b2037235624a7bbfab9
451
+ 8a641e98a507ee096d0678c8ab458fd87731a9a7a0bdc87a99fbbfe684be10f5d4259265
452
+ 68b041a308017ce2901b3c6bf4b3bc89a2b13f3c54047d2fc5f69e9a5053b5e5bb2e0f70
453
+ a2a77d9a25c97b890faec970e29f1c6961b1e00ccd1d8ba9c4006ba8b657193fe5a5b8e4
454
+ 6aa6a86492c381c79afe09d347d25c550c195d080695e3b97c012be3ebf1e2e64bd9f6c2
455
+ 9977e4b34184858bcf99164010dc3746f49d90df559f7dfa6f029f50f35f7777c44d1247
456
+ ecdfc7861969f172d63eb3acc620ac25919cdc5caf4397793b7d564ccc4b0519118027.mp3
457
+ ?key=8kSKDMBUEi2TCGyzhdzZBVSN&v=0
458
+ ```
459
+
460
+ ```
461
+ https://www.priceline.com/relax/at/2003205/from/20240628/to/20240629/rooms/1
462
+ ?meta-id=AyOy_-ov9Edvq6cYGWUbaO9KdvlksSZCnHtEiIUqbvfIqUNLp0ZV0WiDB-MXSyZhxM
463
+ mSw6xJm0HTePNNo_NwvV_Mzo1DeUvJhE53dMnjIqnwb7rfeGGSHOuOML_0zcWCYppfcv6Cf8T
464
+ Na_TIadYlC8PJkvC_qY7bm0lXIqygsn03MyXPyXyUCXNRcKiIm2QS5bWoOeiO48zWgHRtLUDm
465
+ cNx8o6rdlIukl18vqu8RQYajSd3Yt9bbWwDTBjeEduJ2sfoh4Mi3XtGzbqy8YpUrRgIUCGCYf
466
+ DHBdaS47dUkqKfqtQvY7yCPh9Y4YNUZtt9w-TRqndd6AdvbOMprSAbawg8IU5wIj-yEbZr82e
467
+ CcQg2dylETYccSaRK07WHSEJx7
468
+ &pclnId=0571D9ABC99167E702D55CD454625E1BD51BC6742D4EB3A6869799404CB9B21E0E31
469
+ CA463BDC3DE5A56EDB9C6B55C3F06EB5CBBC77502608C5279D0943A5F2545B3F0E4366F3FB
470
+ CCDE32424FB9D2CC10B7E2B68DD59C89151023C9B800744FDDF1C7D85AEB2CF27E
471
+ &gid=5369&cityId=3000035889&cur=USD&backlink-id=gotjhpxt5bp
472
+ ```
473
+ 900 hex characters, 400-char tracking IDs, session blobs.
474
+
475
+ ### Tracking Parameter Avalanche (Apple TV)
476
+ ```
477
+ https://tv.apple.com/us/show/severance/umc.cmc.1srk2goyh2q2zdxcx605w8vtx
478
+ ?ign-itscg=MC_20000&ign-itsct=atvp_brand_omd
479
+ &mttn3pid=Google%20AdWords&mttnagencyid=a5e&mttncc=US
480
+ &mttnsiteid=143238&mttnsubad=OUS2019927_1-592764821446-m
481
+ &mttnsubkw=133111427260__zxnj5jSX_&mttnsubplmnt=
482
+ ```
483
+ Seven `mttn*` tracking parameters that are excessively verbose (and come from a single ad click).
484
+
485
+ ### Base64-Encoded Redirect URLs (Wired)
486
+ ```
487
+ https://link.wired.com/external/39532383.1121/aHR0cHM6Ly9jb25kZW5hc3Quem9vbS
488
+ 51cy93ZWJpbmFyL3JlZ2lzdGVyL1dOX29kcldRdE5uUkdhSUN3MHZob0N3ckE_dXRtX3Nvd
489
+ XJjZT1ubCZ1dG1fYnJhbmQ9d2lyZWQmdXRtX21haWxpbmc9V0lSX1BheXdhbGxTdWJzXzA0
490
+ MjMyNV9TcGVjaWFsX0FJVW5sb2NrZWRfTkxTVUJTSW52aXRlJnV0bV9jYW1wYWlnbj1hdWQ
491
+ tZGV2JnV0bV9tZWRpdW09ZW1haWwmdXRtX2NvbnRlbnQ9V0lSX1BheXdhbGxTdWJzXzA0Mj
492
+ MyNV9TcGVjaWFsX0FJVW5sb2NrZWRfTkxTVUJTSW52aXRlJmJ4aWQ9NWNjOWUwZjdmYzk0M
493
+ mQxM2ViMWY0YjhjJmNuZGlkPTUwNTQyMzY4Jmhhc2hhPTQwODY5ZjRmY2ExOWRkZjU2NTUz
494
+ M2Q2NzMxYmVkMTExJmhhc2hiPWFjNzQxNjk4NjkyMTE1YWExOGRkNzg5N2JjMTIxNmIwNWM
495
+ 0YmI2ODgmaGFzaGM9ZTA5YTA4NzM0MTM3NDA4ODE3NzZlNjExNzQ3NzQ3NDM5ZDYzMGM2YT
496
+ k0NGVmYTIwOGFhMzhhYTMwZjljYTE0NyZlc3JjPU9JRENfU0VMRUNUX0FDQ09VTlRfUEFHR
497
+ Q/5cc9e0f7fc942d13eb1f4b8cB8513f7ce
498
+ ```
499
+ A URL containing another (base64-encoded) URL containing UTM params, hashes, and tracking IDs.
500
+
501
+ ### Kitchen Sink (Grubhub)
502
+ ```
503
+ https://www.grubhub.com/restaurant/bobs-noodle-house-123-main-st-newark/4857291
504
+ /grouporder/Xk7rPwchQfDsT3J9yCtghR
505
+ ?pageNum=1&pageSize=20
506
+ &facet=scheduled%3Afalse&facet=orderType%3AALL
507
+ &includePartnerOrders=true&sorts=default&blockModal=true
508
+ &utm_source=grubhub_web&utm_medium=content_owned
509
+ &utm_campaign=product_sharedcart_join&utm_content=share-link
510
+ ```
511
+ Session IDs, pagination defaults that could be omitted, boolean flags, four UTM parameters, and all more verbose than necessary, resulting in an unwieldy URL.
512
+
513
+ ### The `use-prms` way
514
+
515
+ This may not be best in all cases, but `use-prms` encourages encoding the same information more compactly:
516
+
517
+ | Verbose | Compact | Meaning |
518
+ |----------------------------------------|----------------------------------|--------------------------------------|
519
+ | `?show_grid=true` | `?g` | Boolean flag |
520
+ | `?page_number=5&page_size=50` | `?p=5x50` | Compact, combined state |
521
+ | `?page_number=5&page_size=20` | `?p=5` | Default values omitted |
522
+ | `?category=e1e973fe-6f0a-...` | `?c=eng` | Short, human-readable codes for enums |
523
+ | `?latitude=40.7128&longitude=-74.0060` | `?ll=40.7128-74.0060` | Compact, combined state |
524
+
525
+ URLs are part of your UI. Treat them with the same care as your design.
526
+
527
+ ## License <a id="license"></a>
319
528
 
320
529
  MIT