@marianmeres/uid 1.0.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/AGENTS.md ADDED
@@ -0,0 +1,111 @@
1
+ # @marianmeres/uid — Agent Guide
2
+
3
+ ## Quick Reference
4
+
5
+ - **Stack**: Deno, TypeScript. Core has zero runtime deps; `rhr` is an optional
6
+ peer dep (`@marianmeres/random-human-readable`).
7
+ - **Test**: `deno task test` | **Watch**: `deno task test:watch`
8
+ - **Lint**: `deno lint` | **Format**: `deno fmt`
9
+ - **JSR check**: `deno publish --dry-run --allow-dirty` (must report no slow types)
10
+ - **Build npm**: `deno task npm:build` | **Publish**: `deno task publish`
11
+ - **Targets**: browsers, Deno, Node 19+, Bun (Web Crypto global required).
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ /src
17
+ mod.ts — core entry point (re-exports everything except rhr)
18
+ uid.ts — uid() dispatcher, strategy registry, per-strategy option types
19
+ random.ts — crypto core: randomBytes, randomString (rejection sampling),
20
+ ALPHABETS, DEFAULT_LENGTH, assert helpers
21
+ uuid.ts — uuid (v4), uuidv7, ulid
22
+ base56.ts — base56 random + reversible uuid<->base56 (fixed 23 chars)
23
+ alphabet.ts — nanoid, hex, base32, base36, base58, base62, numeric
24
+ counter.ts — counter (shared, per-prefix), createCounter, resetCounters
25
+ reversible.ts — encodeInt / decodeInt (salt-shuffled, reversible)
26
+ rhr.ts — OPT-IN subpath "@marianmeres/uid/rhr": rhr() + self-registers
27
+ /tests — one *.test.ts per src module
28
+ /scripts
29
+ build-npm.ts — npm distribution builder (dnt via @marianmeres/npmbuild)
30
+ mcp.ts — MCP tool definitions (generate-uid + base56 / integer codecs)
31
+ mcp-include.txt — MCP discovery description (opts the package into the MCP server)
32
+ ```
33
+
34
+ ## MCP Tools (mcp.ts)
35
+
36
+ Exposed to AI agents via `@marianmeres/mcp-server` (namespaced `uid:<tool>`):
37
+ `generate-uid` (any strategy + options; imports `./src/rhr.ts` so `rhr` works),
38
+ `uuid-to-base56`, `base56-to-uuid`, `encode-int`, `decode-int`. Handlers return
39
+ plain strings; every param uses `.describe()`. Keep tools in sync with the public
40
+ API when strategies change.
41
+
42
+ ## Public API
43
+
44
+ Entry `@marianmeres/uid`: `uid(strategy?, options?)` dispatcher plus standalone
45
+ named exports for every strategy.
46
+
47
+ | Export | Signature |
48
+ | ------------------------------------------------------------------------ | ------------------------------------------------------------------- |
49
+ | `uid` | `(strategy?: string, options?) => string` (overloaded per strategy) |
50
+ | `uuid` / `uuidv7` / `ulid` | `() => string` / `(timestamp?) => string` |
51
+ | `base56` / `base56Uuid` | `(length?) => string` / `() => string` |
52
+ | `uuidToBase56` / `base56ToUuid` | `(string) => string` (reversible) |
53
+ | `nanoid` / `hex` / `base32` / `base36` / `base58` / `base62` / `numeric` | `(length?, …) => string` |
54
+ | `randomString` / `randomBytes` | `(length, alphabet?) => string` / `(size) => Uint8Array` |
55
+ | `counter` / `createCounter` / `resetCounters` | counter helpers |
56
+ | `encodeInt` / `decodeInt` | reversible integer codec |
57
+ | `registerStrategy` / `listStrategies` | registry |
58
+ | `ALPHABETS` / `DEFAULT_LENGTH` | constants |
59
+
60
+ Subpath `@marianmeres/uid/rhr`: `rhr(options?) => string` (also registers the
61
+ `rhr` strategy on import).
62
+
63
+ ## Critical Conventions
64
+
65
+ 1. **Core is zero-dependency.** Never import `@marianmeres/random-human-readable`
66
+ from any module except `src/rhr.ts`. It is an _optional peer dep_ surfaced
67
+ only via the `./rhr` subpath so its word lists stay out of the core bundle.
68
+ 2. **All randomness flows through `random.ts`** (`randomBytes` / `randomString`,
69
+ Web Crypto + rejection sampling). Never use `Math.random`.
70
+ 3. **Adding a strategy** = (a) implement it as a standalone named export in the
71
+ relevant file, (b) `registerStrategy("name", …)` in `src/uid.ts`, (c) add a
72
+ typed overload signature to `uid` and an options interface. Keep both the
73
+ named export and the dispatcher path working.
74
+ 4. **Explicit return types on every exported symbol** — JSR rejects slow types.
75
+ Verify with `deno publish --dry-run` before publishing.
76
+ 5. **base56-from-uuid is fixed-width 23 chars** (128 bits need 23 base56 digits;
77
+ `56^22 < 2^128`). Do not "fix" it back to 22.
78
+ 6. **Multi-file layout is intentional** (tree-shaking). Keep pure modules pure;
79
+ `counter.ts` holds the only module-level mutable state (the shared counters).
80
+ 7. **rhr strategy is opt-in**: `uid("rhr")` throws a helpful error until
81
+ `import "@marianmeres/uid/rhr"` registers it. Tests must isolate this (see
82
+ `tests/rhr.test.ts` — dynamic import, the only file touching the subpath).
83
+
84
+ ## Validation Rules (enforced at runtime)
85
+
86
+ | Input | Constraint | Throws |
87
+ | ----------------------------------------- | ----------------------------------------- | -------------------------- |
88
+ | `randomString` length, `randomBytes` size | positive integer | `TypeError` |
89
+ | `randomString` alphabet | string, 1–256 chars | `TypeError` / `RangeError` |
90
+ | `uuidv7` / `ulid` timestamp | integer in `[0, 2^48)` | `RangeError` |
91
+ | `uuidToBase56` input | valid 32-hex-digit UUID | `TypeError` |
92
+ | `counter` prefix/start/step/pad | string / int / positive int / non-neg int | `TypeError` |
93
+ | `encodeInt` value | safe non-negative integer | `RangeError` |
94
+ | reversible alphabet | ≥ 2 unique characters | `RangeError` |
95
+ | `uid(strategy)` unknown name | — | `Error` (lists available) |
96
+ | `uid("custom")` / `uid("reversible")` | required `alphabet` / `value` | `TypeError` |
97
+
98
+ ## Documentation Index
99
+
100
+ - [README.md](./README.md) — user-facing overview and usage
101
+ - [API.md](./API.md) — complete API reference
102
+
103
+ ## Before Making Changes
104
+
105
+ - [ ] Check existing patterns in the relevant `src/*.ts` and its test
106
+ - [ ] `deno task test` (add/extend tests for new behavior)
107
+ - [ ] `deno fmt` and `deno lint`
108
+ - [ ] `deno publish --dry-run --allow-dirty` (no slow types)
109
+ - [ ] `deno task npm:build` if deps, entry points, or build config changed
110
+ - [ ] If the public API changed: update [README.md](./README.md),
111
+ [API.md](./API.md), and this file
package/API.md ADDED
@@ -0,0 +1,425 @@
1
+ # API
2
+
3
+ Complete reference for `@marianmeres/uid`.
4
+
5
+ - Core entry point: `@marianmeres/uid` — everything below **except** `rhr`.
6
+ - Opt-in subpath: `@marianmeres/uid/rhr` — the `rhr` function and strategy.
7
+
8
+ All generators are crypto-backed (`crypto.getRandomValues` / `crypto.randomUUID`)
9
+ and work in browsers, Deno, Node 19+, and Bun. Randomness is good quality but
10
+ **not** cryptographically secret — do not use for tokens, keys, or password
11
+ resets.
12
+
13
+ ---
14
+
15
+ ## The dispatcher
16
+
17
+ ### `uid(strategy?, options?)`
18
+
19
+ Generates a unique id using the named `strategy`. Defaults to `uuid` (v4). The
20
+ `options` shape is validated per strategy (TypeScript overloads narrow it from
21
+ the strategy name). The `rhr` strategy must be enabled first via
22
+ `import "@marianmeres/uid/rhr"`.
23
+
24
+ **Parameters:**
25
+
26
+ - `strategy` (string, optional) — one of: `uuid`, `uuidv4`, `uuidv7`, `ulid`,
27
+ `base56`, `nanoid`, `hex`, `base32`, `base36`, `base58`, `base62`, `numeric`,
28
+ `custom`, `counter`, `reversible`, `rhr`, or any name registered via
29
+ `registerStrategy`. Default: `"uuid"`.
30
+ - `options` (object, optional) — strategy-specific; see each strategy below.
31
+
32
+ **Returns:** `string`
33
+
34
+ **Throws:** `Error` if the strategy is unknown (message lists available
35
+ strategies), or a strategy-specific `TypeError`/`RangeError` for invalid options.
36
+
37
+ **Example:**
38
+
39
+ ```typescript
40
+ import { uid } from "@marianmeres/uid";
41
+
42
+ uid(); // uuid v4
43
+ uid("uuidv7"); // sortable uuid
44
+ uid("nanoid", { length: 12 }); // "V1StGXR8_Z5j"
45
+ uid("custom", { alphabet: "AB01", length: 6 });
46
+ ```
47
+
48
+ | Strategy | Options type | Backing function |
49
+ | ----------------- | ------------------- | ---------------------------------- |
50
+ | `uuid`/`uuidv4` | `UuidOptions` | `uuid()` |
51
+ | `uuidv7` | `Uuidv7Options` | `uuidv7(timestamp)` |
52
+ | `ulid` | `UlidOptions` | `ulid(timestamp)` |
53
+ | `base56` | `Base56Options` | `base56()` / `uuidToBase56()` |
54
+ | `nanoid` | `NanoidOptions` | `nanoid(length, alphabet)` |
55
+ | `hex` … `numeric` | `AlphabetOptions` | `hex(length)`, `base58(length)`, … |
56
+ | `custom` | `CustomOptions` | `randomString(length, alphabet)` |
57
+ | `counter` | `CounterOptions` | `counter(options)` |
58
+ | `reversible` | `ReversibleOptions` | `encodeInt(value, options)` |
59
+ | `rhr` | `RhrOptions` | `rhr(options)` _(opt-in)_ |
60
+
61
+ ---
62
+
63
+ ## UUID & sortable strategies
64
+
65
+ ### `uuid()`
66
+
67
+ Random RFC 9562 UUID v4 via native `crypto.randomUUID()`.
68
+
69
+ **Returns:** `string` — e.g. `"1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"`
70
+
71
+ ### `uuidv7(timestamp?)`
72
+
73
+ RFC 9562 UUID v7 — a 48-bit Unix-millisecond timestamp prefix plus random bits.
74
+ Valid UUID **and** lexicographically sortable by creation time. Ideal for
75
+ database primary keys.
76
+
77
+ **Parameters:**
78
+
79
+ - `timestamp` (number, optional) — Unix epoch milliseconds to embed. Must be in
80
+ `[0, 2^48)`. Default: `Date.now()`.
81
+
82
+ **Returns:** `string`
83
+ **Throws:** `RangeError` if `timestamp` is outside `[0, 2^48)`.
84
+
85
+ **Example:**
86
+
87
+ ```typescript
88
+ uuidv7(); // "0192f6c4-1d2e-7a3b-8c4d-5e6f7a8b9c0d"
89
+ uuidv7(0); // deterministic time prefix (tests / back-dating)
90
+ ```
91
+
92
+ ### `ulid(timestamp?)`
93
+
94
+ ULID — 26-character Crockford-base32 string: 48-bit ms timestamp (10 chars) +
95
+ 80 bits of randomness (16 chars). Sorts by time like UUID v7, but shorter,
96
+ dash-free, and case-insensitive (not a valid UUID string).
97
+
98
+ **Parameters:**
99
+
100
+ - `timestamp` (number, optional) — Unix epoch milliseconds. Must be in
101
+ `[0, 2^48)`. Default: `Date.now()`.
102
+
103
+ **Returns:** `string` — e.g. `"01J9Z7Q8K3M4N5P6R7S8T9V0W1"`
104
+ **Throws:** `RangeError` if `timestamp` is outside `[0, 2^48)`.
105
+
106
+ ---
107
+
108
+ ## Base56 strategy
109
+
110
+ Human-friendly alphabet with no ambiguous characters (`0 O o 1 l I`).
111
+
112
+ ### `base56(length?)`
113
+
114
+ Random base56 string.
115
+
116
+ **Parameters:** `length` (number, optional) — default `DEFAULT_LENGTH` (21).
117
+ **Returns:** `string`
118
+
119
+ ### `uuidToBase56(uuid)`
120
+
121
+ Encodes a UUID into a fixed-width **23-character** base56 string. Reversible via
122
+ `base56ToUuid`. (128 bits need 23 base56 digits — `56^22 ≈ 2^127.76 < 2^128`.)
123
+
124
+ **Parameters:** `uuid` (string) — a UUID, with or without dashes.
125
+ **Returns:** `string` (23 chars)
126
+ **Throws:** `TypeError` if `uuid` is not a valid 32-hex-digit UUID.
127
+
128
+ ### `base56ToUuid(encoded)`
129
+
130
+ Decodes a base56 string from `uuidToBase56` back to a canonical UUID.
131
+
132
+ **Parameters:** `encoded` (string)
133
+ **Returns:** `string` — canonical UUID
134
+ **Throws:** `Error` if `encoded` contains characters outside the base56 alphabet.
135
+
136
+ ### `base56Uuid()`
137
+
138
+ Generates a fresh UUID and returns its 23-character base56 encoding. Equivalent
139
+ to `uuidToBase56(uuid())`.
140
+
141
+ **Returns:** `string` (23 chars)
142
+
143
+ **Example:**
144
+
145
+ ```typescript
146
+ const id = base56Uuid(); // "2WjkHTAH3tTuAHceeZxnuDL"
147
+ base56ToUuid(id); // the original uuid
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Alphabet strategies
153
+
154
+ Fixed-length random strings over a named alphabet. Each takes an optional
155
+ `length` (default `DEFAULT_LENGTH` = 21) and returns a `string`.
156
+
157
+ | Function | Alphabet |
158
+ | ---------------------------- | ------------------------------------------------------------- |
159
+ | `nanoid(length?, alphabet?)` | URL-safe 64-char (`A–Z a–z 0–9 _ -`); custom alphabet allowed |
160
+ | `hex(length?)` | `0-9 a-f` |
161
+ | `base32(length?)` | Crockford (no I L O U) |
162
+ | `base36(length?)` | `0-9 a-z` |
163
+ | `base58(length?)` | Bitcoin (no 0 O I l) |
164
+ | `base62(length?)` | `0-9 A-Z a-z` |
165
+ | `numeric(length?)` | `0-9` (OTP / numeric coupons) |
166
+
167
+ **Example:**
168
+
169
+ ```typescript
170
+ nanoid(); // 21-char URL-safe id
171
+ hex(32); // 32-char hex
172
+ numeric(6); // "048217"
173
+ nanoid(10, "ABCDEF01"); // custom alphabet
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Counter strategy
179
+
180
+ In-memory, monotonically increasing ids. State lives for the lifetime of the
181
+ process/page and resets on reload — **not** for cross-process uniqueness.
182
+
183
+ ### `counter(options?)`
184
+
185
+ Returns the next value of a shared, per-prefix counter. Each distinct `prefix`
186
+ has its own sequence. `start`/`step`/`pad` are honored only on the first call
187
+ that creates a prefix's sequence.
188
+
189
+ **Parameters:** `options` (`CounterOptions`, optional)
190
+ **Returns:** `string`
191
+ **Throws:** `TypeError` for invalid `prefix`/`start`/`step`/`pad`.
192
+
193
+ ### `createCounter(options?)`
194
+
195
+ Creates an isolated counter function with its own private state — no sharing with
196
+ `counter()` or other instances. Prefer this for independent sequences or
197
+ deterministic tests.
198
+
199
+ **Parameters:** `options` (`CounterOptions`, optional)
200
+ **Returns:** `() => string`
201
+
202
+ ### `resetCounters(prefix?)`
203
+
204
+ Clears the shared state used by `counter()`. Mainly for tests. Does not affect
205
+ `createCounter` instances.
206
+
207
+ **Parameters:** `prefix` (string, optional) — reset only this prefix; omit to
208
+ reset all.
209
+ **Returns:** `void`
210
+
211
+ **Example:**
212
+
213
+ ```typescript
214
+ const next = createCounter({ prefix: "row-", start: 1, pad: 4 });
215
+ next(); // "row-0001"
216
+ next(); // "row-0002"
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Reversible integer codec
222
+
223
+ A small "sqids/hashids-lite": encode a non-negative integer to a short string
224
+ and back. A `salt` deterministically shuffles the alphabet so sequential ids
225
+ don't look sequential. **Obfuscation, not encryption** — reversible by anyone
226
+ who knows the salt and alphabet.
227
+
228
+ ### `encodeInt(value, options?)`
229
+
230
+ **Parameters:**
231
+
232
+ - `value` (number) — non-negative safe integer (`0 … Number.MAX_SAFE_INTEGER`).
233
+ - `options` (`EncodeIntOptions`, optional)
234
+
235
+ **Returns:** `string`
236
+ **Throws:** `RangeError` if `value` is out of range, or `alphabet` has fewer than
237
+ 2 characters or contains duplicates.
238
+
239
+ ### `decodeInt(str, options?)`
240
+
241
+ **Parameters:**
242
+
243
+ - `str` (string) — a string produced by `encodeInt`.
244
+ - `options` (`DecodeIntOptions`, optional) — `alphabet`/`salt` must match those
245
+ used to encode.
246
+
247
+ **Returns:** `number`
248
+ **Throws:** `Error` for characters outside the alphabet; `RangeError` for a bad
249
+ alphabet or if the decoded value exceeds the safe-integer range.
250
+
251
+ **Example:**
252
+
253
+ ```typescript
254
+ const code = encodeInt(12345, { salt: "my-secret" }); // "Yq9"
255
+ decodeInt(code, { salt: "my-secret" }); // 12345
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Core primitives
261
+
262
+ ### `randomString(length, alphabet?)`
263
+
264
+ Random string of `length` characters from `alphabet`, using rejection sampling
265
+ (the nanoid algorithm) so output is unbiased even for non-power-of-two alphabets.
266
+
267
+ **Parameters:**
268
+
269
+ - `length` (number) — positive integer.
270
+ - `alphabet` (string, optional) — 1–256 characters. Default: the URL-safe nanoid
271
+ alphabet.
272
+
273
+ **Returns:** `string`
274
+ **Throws:** `TypeError` for non-positive-integer `length` or non-string
275
+ `alphabet`; `RangeError` if `alphabet` length is outside 1–256.
276
+
277
+ ### `randomBytes(size)`
278
+
279
+ `size` cryptographically-strong random bytes. Transparently chunks calls larger
280
+ than the 65536-byte `getRandomValues` limit.
281
+
282
+ **Parameters:** `size` (number) — positive integer.
283
+ **Returns:** `Uint8Array`
284
+
285
+ ### `assertPositiveInt(n, label)` / `assertNonNegativeInt(n, label)`
286
+
287
+ Throw `TypeError` unless `n` is an integer `>= 1` / `>= 0`. The `label` is used
288
+ in the error message. Exposed for building custom strategies with consistent
289
+ validation.
290
+
291
+ **Returns:** `void` (assertion functions)
292
+
293
+ ---
294
+
295
+ ## Registry
296
+
297
+ ### `registerStrategy(name, fn)`
298
+
299
+ Registers (or overrides) a strategy under `name`, making it callable via
300
+ `uid(name, options)`.
301
+
302
+ **Parameters:**
303
+
304
+ - `name` (string) — non-empty strategy name.
305
+ - `fn` (`StrategyFn`) — `(options?) => string`.
306
+
307
+ **Returns:** `void`
308
+ **Throws:** `TypeError` for an empty `name` or non-function `fn`.
309
+
310
+ ### `listStrategies()`
311
+
312
+ **Returns:** `string[]` — names of all currently registered strategies.
313
+
314
+ **Example:**
315
+
316
+ ```typescript
317
+ import { registerStrategy, uid } from "@marianmeres/uid";
318
+
319
+ registerStrategy("order", () => "ORD-" + uid("numeric", { length: 8 }));
320
+ uid("order"); // "ORD-40582193"
321
+ ```
322
+
323
+ ---
324
+
325
+ ## Opt-in: `@marianmeres/uid/rhr`
326
+
327
+ ### `rhr(options?)`
328
+
329
+ Human-readable id built on `@marianmeres/random-human-readable`. Joined with `-`
330
+ by default. Importing this module also registers the `rhr` strategy so
331
+ `uid("rhr", …)` works.
332
+
333
+ **Parameters:** `options` (`RhrOptions`, optional) — passed through to
334
+ `getRandomHumanReadable` (e.g. `adjCount`, `colorsCount`, `nounsCount`,
335
+ `randomizeCase`, `joinWith`).
336
+
337
+ **Returns:** `string` — e.g. `"happy-blue-otter-canyon"`
338
+
339
+ **Example:**
340
+
341
+ ```typescript
342
+ import "@marianmeres/uid/rhr"; // enables uid("rhr")
343
+ import { rhr } from "@marianmeres/uid/rhr";
344
+
345
+ rhr(); // "happy-blue-otter-canyon"
346
+ rhr({ nounsCount: 1 }); // "otter"
347
+ ```
348
+
349
+ On npm, install the peer dependency:
350
+ `npm install @marianmeres/random-human-readable`.
351
+
352
+ ---
353
+
354
+ ## Types
355
+
356
+ ```typescript
357
+ interface Uuidv7Options {
358
+ timestamp?: number;
359
+ }
360
+ interface UlidOptions {
361
+ timestamp?: number;
362
+ }
363
+ interface Base56Options {
364
+ length?: number;
365
+ uuid?: string;
366
+ }
367
+ interface NanoidOptions {
368
+ length?: number;
369
+ alphabet?: string;
370
+ }
371
+ interface AlphabetOptions {
372
+ length?: number;
373
+ }
374
+ interface CustomOptions {
375
+ alphabet: string;
376
+ length?: number;
377
+ }
378
+ interface CounterOptions {
379
+ prefix?: string;
380
+ start?: number;
381
+ step?: number;
382
+ pad?: number;
383
+ }
384
+ interface EncodeIntOptions {
385
+ alphabet?: string;
386
+ salt?: string;
387
+ minLength?: number;
388
+ }
389
+ interface DecodeIntOptions {
390
+ alphabet?: string;
391
+ salt?: string;
392
+ }
393
+ interface ReversibleOptions {
394
+ value: number;
395
+ alphabet?: string;
396
+ salt?: string;
397
+ minLength?: number;
398
+ }
399
+ interface RhrOptions {/* getRandomHumanReadable options that return a string */}
400
+ type StrategyFn = (options?: Record<string, unknown>) => string;
401
+ ```
402
+
403
+ ---
404
+
405
+ ## Constants
406
+
407
+ ### `DEFAULT_LENGTH`
408
+
409
+ `21` — default length for random, fixed-length strategies.
410
+
411
+ ### `ALPHABETS`
412
+
413
+ Named alphabets used by the built-in strategies:
414
+
415
+ ```typescript
416
+ ALPHABETS.hex; // "0123456789abcdef"
417
+ ALPHABETS.base32; // Crockford (no I L O U)
418
+ ALPHABETS.base36; // "0-9a-z"
419
+ ALPHABETS.base56; // no 0 O o 1 l I
420
+ ALPHABETS.base58; // Bitcoin (no 0 O I l)
421
+ ALPHABETS.base62; // "0-9A-Za-z"
422
+ ALPHABETS.numeric; // "0123456789"
423
+ ALPHABETS.alphanumeric; // alias of base62
424
+ ALPHABETS.nanoid; // URL-safe 64-char set
425
+ ```
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marian Meres
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.