get-or-throw 2.1.0 → 2.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.
@@ -0,0 +1,47 @@
1
+ import { defineConfig } from "vitepress";
2
+
3
+ const hostname = "https://get-or-throw.codecompose.dev";
4
+
5
+ export default defineConfig({
6
+ title: "Get-Or-Throw",
7
+ description:
8
+ "Safe indexed access for TypeScript with noUncheckedIndexedAccess",
9
+ base: "/",
10
+ cleanUrls: true,
11
+
12
+ sitemap: {
13
+ hostname,
14
+ },
15
+
16
+ transformHead({ pageData }) {
17
+ const canonicalUrl = `${hostname}/${pageData.relativePath}`
18
+ .replace(/index\.md$/, "")
19
+ .replace(/\.md$/, "");
20
+
21
+ return [["link", { rel: "canonical", href: canonicalUrl }]];
22
+ },
23
+
24
+ themeConfig: {
25
+ sidebar: [
26
+ {
27
+ text: "Guide",
28
+ items: [
29
+ { text: "Introduction", link: "/" },
30
+ { text: "Getting Started", link: "/getting-started" },
31
+ { text: "Usage", link: "/usage" },
32
+ ],
33
+ },
34
+ {
35
+ text: "Reference",
36
+ items: [
37
+ { text: "API", link: "/api" },
38
+ { text: "Why Get-Or-Throw?", link: "/reasoning" },
39
+ ],
40
+ },
41
+ ],
42
+
43
+ socialLinks: [
44
+ { icon: "github", link: "https://github.com/0x80/get-or-throw" },
45
+ ],
46
+ },
47
+ });
@@ -0,0 +1,31 @@
1
+ <script setup>
2
+ import DefaultTheme from "vitepress/theme";
3
+
4
+ const { Layout } = DefaultTheme;
5
+ </script>
6
+
7
+ <template>
8
+ <Layout>
9
+ <template #layout-bottom>
10
+ <footer class="custom-footer">
11
+ <p class="message">Released under the MIT License.</p>
12
+ <p class="copyright">Copyright &copy; Thijs Koerselman</p>
13
+ </footer>
14
+ </template>
15
+ </Layout>
16
+ </template>
17
+
18
+ <style scoped>
19
+ .custom-footer {
20
+ border-top: 1px solid var(--vp-c-divider);
21
+ padding: 26px 32px;
22
+ text-align: center;
23
+ font-size: 14px;
24
+ color: var(--vp-c-text-2);
25
+ }
26
+
27
+ .custom-footer p {
28
+ margin: 0;
29
+ line-height: 24px;
30
+ }
31
+ </style>
@@ -0,0 +1,3 @@
1
+ .VPFeature .icon {
2
+ background-color: transparent !important;
3
+ }
@@ -0,0 +1,8 @@
1
+ import DefaultTheme from "vitepress/theme";
2
+ import CustomLayout from "./CustomLayout.vue";
3
+ import "./custom.css";
4
+
5
+ export default {
6
+ extends: DefaultTheme,
7
+ Layout: CustomLayout,
8
+ };
package/docs/api.md ADDED
@@ -0,0 +1,65 @@
1
+ # API Reference
2
+
3
+ ## `getOrThrow`
4
+
5
+ Get a value from an object or array, throwing an error if the key or index does
6
+ not exist or if the resulting value is `undefined`.
7
+
8
+ ### Signatures
9
+
10
+ #### Array Access
11
+
12
+ ```ts
13
+ function getOrThrow<T>(array: T[], index: number, errorMessage?: string): T;
14
+ ```
15
+
16
+ #### Object Access
17
+
18
+ ```ts
19
+ function getOrThrow<T extends object, K extends keyof T>(
20
+ object: T,
21
+ key: K,
22
+ errorMessage?: string,
23
+ ): NonNullable<T[K]>;
24
+ ```
25
+
26
+ ### Parameters
27
+
28
+ | Parameter | Type | Description |
29
+ | ------------------ | ------------- | ------------------------------------------------------ |
30
+ | `array` / `object` | `T[] \| T` | The array or object to access. |
31
+ | `index` / `key` | `number \| K` | The index (for arrays) or key (for objects) to access. |
32
+ | `errorMessage` | `string` | Optional custom error message. |
33
+
34
+ ### Returns
35
+
36
+ The value at the given key or index, guaranteed to be defined. For objects, the
37
+ return type is `NonNullable<T[K]>`.
38
+
39
+ ### Throws
40
+
41
+ - If the index is out of bounds: `"Index {n} is out of bounds."`
42
+ - If the key does not exist: `'Key "{key}" does not exist in the object.'`
43
+ - If the value is `undefined`: `"Value at index {n} is undefined."` or
44
+ `'Value at key "{key}" is undefined.'`
45
+
46
+ When a custom `errorMessage` is provided, it is used instead of the default
47
+ messages.
48
+
49
+ ### Behavior
50
+
51
+ - **Negative indexing**: Negative indices count from the end of the array.
52
+ `got(arr, -1)` returns the last element.
53
+ - **Null values**: `null` is treated as a valid value and does not trigger an
54
+ error. Only `undefined` causes a throw.
55
+
56
+ ## `got`
57
+
58
+ An alias for `getOrThrow`. Both functions are identical — use whichever you
59
+ prefer.
60
+
61
+ ```ts
62
+ import { got } from "get-or-throw";
63
+ // is equivalent to
64
+ import { getOrThrow } from "get-or-throw";
65
+ ```
@@ -0,0 +1,53 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ pnpm add get-or-throw
7
+ ```
8
+
9
+ Or use the equivalent for your package manager:
10
+
11
+ ```bash
12
+ npm install get-or-throw
13
+ yarn add get-or-throw
14
+ ```
15
+
16
+ ## Prerequisites
17
+
18
+ Get-Or-Throw is designed to work with TypeScript's
19
+ [noUncheckedIndexedAccess](https://www.typescriptlang.org/tsconfig/#noUncheckedIndexedAccess)
20
+ compiler option. While it works without it, the library is most useful when this
21
+ option is enabled:
22
+
23
+ ```json
24
+ {
25
+ "compilerOptions": {
26
+ "noUncheckedIndexedAccess": true
27
+ }
28
+ }
29
+ ```
30
+
31
+ With this setting, TypeScript treats every indexed access as potentially
32
+ `undefined`, which is correct but can make code verbose. Get-Or-Throw provides a
33
+ clean way to handle this.
34
+
35
+ ## Basic Usage
36
+
37
+ The library exports two identical functions: `getOrThrow` and its shorter alias
38
+ `got`.
39
+
40
+ ```ts
41
+ import { got } from "get-or-throw";
42
+ // or
43
+ import { getOrThrow } from "get-or-throw";
44
+
45
+ const arr = [1, 2, 3];
46
+ const arrValue = got(arr, 1); // 2
47
+
48
+ const obj = { a: 1, b: 2, c: 3 };
49
+ const objValue = got(obj, "b"); // 2
50
+ ```
51
+
52
+ Both functions behave identically — use whichever you prefer. The `got` alias is
53
+ convenient for keeping code concise.
package/docs/index.md ADDED
@@ -0,0 +1,46 @@
1
+ ---
2
+ layout: home
3
+
4
+ hero:
5
+ name: Get-Or-Throw
6
+ tagline: Safe indexed access for TypeScript with noUncheckedIndexedAccess
7
+ actions:
8
+ - theme: brand
9
+ text: Get Started
10
+ link: /getting-started
11
+ - theme: alt
12
+ text: Why Get-Or-Throw?
13
+ link: /reasoning
14
+
15
+ features:
16
+ - title: Type-Safe Access
17
+ details: TypeScript assertions for type narrowing — the returned value is guaranteed to be defined.
18
+ - title: Arrays & Objects
19
+ details: Works with both objects and arrays, including support for negative indexing.
20
+ - title: Zero Dependencies
21
+ details: Tiny footprint with no external dependencies. Just the utility you need, nothing more.
22
+ ---
23
+
24
+ ## What is Get-Or-Throw?
25
+
26
+ Get-Or-Throw provides a convenience function for safely accessing values in
27
+ dynamic objects and arrays. It gets the value at a specified key or index, and
28
+ throws an error if the resulting value is `undefined`.
29
+
30
+ This was created to make it easy to adhere to TypeScript's
31
+ [noUncheckedIndexedAccess](https://www.typescriptlang.org/tsconfig/#noUncheckedIndexedAccess)
32
+ setting, which is recommended for strict type checking.
33
+
34
+ ```ts
35
+ import { got } from "get-or-throw";
36
+
37
+ const arr = [1, 2, 3];
38
+ const arrValue = got(arr, 1); // 2 — type is `number`, not `number | undefined`
39
+
40
+ const obj = { a: 1, b: 2, c: 3 };
41
+ const objValue = got(obj, "b"); // 2
42
+ ```
43
+
44
+ Instead of writing repetitive guard clauses or non-null assertions throughout
45
+ your code, `got()` gives you a single, expressive call that either returns a
46
+ defined value or throws a descriptive error.
@@ -0,0 +1,144 @@
1
+ # Why Get-Or-Throw?
2
+
3
+ ## The Core Question
4
+
5
+ When you access an array element or object property by a dynamic key, the value
6
+ might not be there. TypeScript's `noUncheckedIndexedAccess` makes this explicit
7
+ by adding `| undefined` to every indexed access. But how should you handle that
8
+ `undefined`?
9
+
10
+ There are two fundamental approaches:
11
+
12
+ 1. **Defensive programming** — check for `undefined` and handle the absence
13
+ gracefully
14
+ 2. **Fail-fast** — assert the value exists and throw immediately if it doesn't
15
+
16
+ Get-Or-Throw is for the second case: when absence is a bug, not a valid state.
17
+
18
+ ## When to Use `got()`
19
+
20
+ Use `got()` when a missing value means something has gone wrong — a business
21
+ invariant has been violated, data is corrupt, or an earlier operation failed
22
+ silently:
23
+
24
+ ```ts
25
+ // The config must have a "database" section
26
+ const dbConfig = got(config, "database");
27
+
28
+ // There must be at least one item after filtering
29
+ const first = got(filtered, 0, "Filter returned no results");
30
+
31
+ // Parallel arrays must have the same length
32
+ const label = got(labels, index);
33
+ ```
34
+
35
+ In these cases, continuing with `undefined` would cause confusing downstream
36
+ errors. Failing immediately with a clear message is better.
37
+
38
+ ## When NOT to Use `got()`
39
+
40
+ When absence is a legitimate possibility, use native patterns instead:
41
+
42
+ ```ts
43
+ // User might not exist — that's fine
44
+ const user = users.find((u) => u.id === id);
45
+ if (!user) {
46
+ return notFound();
47
+ }
48
+
49
+ // Optional configuration
50
+ const timeout = config.timeout ?? 5000;
51
+
52
+ // Array might be empty
53
+ const first = items[0];
54
+ if (first === undefined) {
55
+ showEmptyState();
56
+ }
57
+ ```
58
+
59
+ If the code after the access handles the `undefined` case meaningfully, you
60
+ don't need `got()`.
61
+
62
+ ## When to Use Tuple Types Instead
63
+
64
+ If the length of an array is statically known, TypeScript can track individual
65
+ element types without `| undefined`. In those cases, prefer a tuple type over
66
+ `got()`:
67
+
68
+ ```ts
69
+ // TypeScript knows this has exactly 3 elements
70
+ const rgb: [number, number, number] = [255, 128, 0];
71
+ const red = rgb[0]; // number, not number | undefined
72
+ ```
73
+
74
+ ## Problems with Defensive Patterns
75
+
76
+ ### Silent Failures
77
+
78
+ The most dangerous pattern is silently swallowing `undefined`:
79
+
80
+ ```ts
81
+ // Bug: if arr[i] is undefined, localeCompare is called on undefined
82
+ arr.sort((a, b) => {
83
+ const valA = mapping[a]; // possibly undefined
84
+ const valB = mapping[b]; // possibly undefined
85
+ return valA.localeCompare(valB); // runtime crash, no clear message
86
+ });
87
+ ```
88
+
89
+ With `got()`, the failure is immediate and descriptive:
90
+
91
+ ```ts
92
+ arr.sort((a, b) => {
93
+ const valA = got(mapping, a);
94
+ const valB = got(mapping, b);
95
+ return valA.localeCompare(valB); // safe — both are guaranteed strings
96
+ });
97
+ ```
98
+
99
+ ### Verbose Guard Clauses
100
+
101
+ Without `got()`, you end up with repetitive null checks:
102
+
103
+ ```ts
104
+ const value = obj[key];
105
+ if (value === undefined) {
106
+ throw new Error(`Expected key "${key}" to exist`);
107
+ }
108
+ // now use value
109
+ ```
110
+
111
+ This pattern repeated across a codebase adds significant noise. `got()` reduces
112
+ it to a single expression:
113
+
114
+ ```ts
115
+ const value = got(obj, key);
116
+ ```
117
+
118
+ ### Inconsistency
119
+
120
+ Different developers write different error messages, check in different ways
121
+ (`=== undefined`, `!= null`, `in` operator), or sometimes forget to check at
122
+ all. `got()` provides a single consistent pattern.
123
+
124
+ ## Addressing Objections
125
+
126
+ ### "It's a dependency for something trivial"
127
+
128
+ Get-Or-Throw has zero dependencies and is only a few lines of code. The value
129
+ isn't in the implementation complexity — it's in the consistency and
130
+ expressiveness it brings to a codebase. A shared convention is worth more than
131
+ any individual helper function.
132
+
133
+ ### "Throwing crashes the app"
134
+
135
+ That's the point. If a business invariant is violated, you _want_ to know
136
+ immediately. The alternative — continuing with `undefined` — leads to corrupted
137
+ data, confusing errors far from the source, and bugs that are hard to reproduce.
138
+
139
+ ### "I can just use the non-null assertion (`!`)"
140
+
141
+ The non-null assertion (`value!`) tells TypeScript to trust you, but provides no
142
+ runtime safety. If you're wrong, you get a confusing `undefined` error
143
+ somewhere downstream. `got()` gives you both the type narrowing _and_ a clear
144
+ runtime error at the point of access.
package/docs/usage.md ADDED
@@ -0,0 +1,85 @@
1
+ # Usage
2
+
3
+ The examples below use the `got` alias, but `getOrThrow` works identically.
4
+
5
+ ## Array Access
6
+
7
+ ```ts
8
+ const arr = [1, 2, 3];
9
+ const value = got(arr, 1); // 2
10
+ ```
11
+
12
+ ### Negative Indexing
13
+
14
+ Negative indices count from the end of the array, similar to `Array.at()`:
15
+
16
+ ```ts
17
+ const arr = [1, 2, 3];
18
+ got(arr, -1); // 3
19
+ got(arr, -2); // 2
20
+ ```
21
+
22
+ ### Out of Bounds
23
+
24
+ Accessing an index outside the array bounds throws an error:
25
+
26
+ ```ts
27
+ const arr = [1, 2, 3];
28
+ got(arr, 3); // throws: "Index 3 is out of bounds."
29
+ ```
30
+
31
+ ## Object Access
32
+
33
+ ```ts
34
+ const obj = { a: 1, b: 2, c: 3 };
35
+ const value = got(obj, "b"); // 2
36
+ ```
37
+
38
+ ### Missing Keys
39
+
40
+ Accessing a key that doesn't exist throws an error:
41
+
42
+ ```ts
43
+ const obj = { a: 1, b: 2, c: 3 };
44
+ got(obj, "d"); // throws: 'Key "d" does not exist in the object.'
45
+ ```
46
+
47
+ ## Null vs Undefined
48
+
49
+ `null` is treated as a valid value and will not cause an error. Only `undefined`
50
+ triggers a throw:
51
+
52
+ ```ts
53
+ // null is valid
54
+ const arr = [1, null, 3];
55
+ got(arr, 1); // null
56
+
57
+ const obj = { a: 1, b: null, c: 3 };
58
+ got(obj, "b"); // null
59
+
60
+ // undefined throws
61
+ const arr2 = [1, undefined, 3];
62
+ got(arr2, 1); // throws: "Value at index 1 is undefined."
63
+
64
+ const obj2 = { a: 1, b: undefined, c: 3 };
65
+ got(obj2, "b"); // throws: 'Value at key "b" is undefined.'
66
+ ```
67
+
68
+ ## Custom Error Messages
69
+
70
+ You can provide a custom error message as the third argument:
71
+
72
+ ```ts
73
+ const obj = { a: 1, b: 2, c: 3 };
74
+ const key = "d";
75
+ got(obj, key, `Failed to find ${key}`);
76
+ // throws: "Failed to find d"
77
+ ```
78
+
79
+ This is useful when you want to provide more context about why the value was
80
+ expected to exist:
81
+
82
+ ```ts
83
+ const users = [{ name: "Alice" }, { name: "Bob" }];
84
+ const user = got(users, userIndex, `User at index ${userIndex} not found`);
85
+ ```
package/package.json CHANGED
@@ -1,7 +1,23 @@
1
1
  {
2
2
  "name": "get-or-throw",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "A convenience function for safely getting values from dynamic objects and arrays",
5
+ "keywords": [
6
+ "indexed",
7
+ "noUncheckedIndexedAccess",
8
+ "typescript",
9
+ "unchecked"
10
+ ],
11
+ "homepage": "https://get-or-throw.codecompose.dev",
12
+ "bugs": {
13
+ "url": "https://github.com/0x80/get-or-throw/issues"
14
+ },
15
+ "license": "MIT",
16
+ "author": "Thijs Koerselman",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/0x80/get-or-throw.git"
20
+ },
5
21
  "type": "module",
6
22
  "main": "dist/index.cjs",
7
23
  "types": "dist/index.d.ts",
@@ -11,40 +27,37 @@
11
27
  "require": "./dist/index.cjs"
12
28
  }
13
29
  },
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://github.com/0x80/get-or-throw.git"
30
+ "publishConfig": {
31
+ "access": "public"
17
32
  },
18
- "keywords": [
19
- "typescript",
20
- "indexed",
21
- "unchecked",
22
- "noUncheckedIndexedAccess"
23
- ],
24
- "author": "Thijs Koerselman",
25
- "license": "MIT",
26
- "bugs": {
27
- "url": "https://github.com/0x80/get-or-throw/issues"
33
+ "scripts": {
34
+ "build": "tsdown",
35
+ "dev": "tsdown --watch",
36
+ "clean": "del dist tsconfig.tsbuildinfo",
37
+ "prepare": "pnpm build",
38
+ "check-lint": "oxlint -c .oxlintrc.json --type-aware",
39
+ "format": "oxfmt .",
40
+ "check-format": "oxfmt --check .",
41
+ "check-types": "tsc --noEmit",
42
+ "test": "vitest run",
43
+ "docs:dev": "vitepress dev docs",
44
+ "docs:build": "vitepress build docs",
45
+ "docs:preview": "vitepress preview docs"
28
46
  },
29
- "homepage": "https://github.com/0x80/get-or-throw#readme",
30
47
  "devDependencies": {
31
- "@codecompose/typescript-config": "^1.1.2",
32
- "@eslint/js": "^9.22.0",
48
+ "@codecompose/typescript-config": "^3.0.0",
33
49
  "del-cli": "^5.1.0",
34
- "eslint": "^9.22.0",
35
- "prettier": "^3.3.3",
36
- "prettier-plugin-jsdoc": "^1.3.0",
37
- "tsup": "^8.3.0",
38
- "typescript": "^5.6.2",
39
- "typescript-eslint": "^8.26.0",
50
+ "oxfmt": "^0.33.0",
51
+ "oxlint": "^1.48.0",
52
+ "oxlint-tsgolint": "^0.15.0",
53
+ "tsdown": "^0.12.0",
54
+ "typescript": "6.0.0-beta",
55
+ "vite": "^6.0.0",
56
+ "vitepress": "^1.6.0",
40
57
  "vitest": "^3.0.8"
41
58
  },
42
- "scripts": {
43
- "build": "tsup",
44
- "clean": "del dist tsconfig.tsbuildinfo",
45
- "lint": "eslint .",
46
- "type-check": "tsc --noEmit",
47
- "test": "vitest",
48
- "format": "prettier --write ."
49
- }
50
- }
59
+ "engines": {
60
+ "node": ">=22.12.0"
61
+ },
62
+ "packageManager": "pnpm@9.8.0+sha256.56a9e76b51796ca7f73b85e44cf83712862091f4d498c0ce4d5b7ecdc6ba18f7"
63
+ }
package/tsconfig.json CHANGED
@@ -1,6 +1,3 @@
1
1
  {
2
- "extends": "@codecompose/typescript-config/single-library.json",
3
- "compilerOptions": {
4
- "rootDir": "." // Needed for CI for some reason
5
- }
2
+ "extends": "@codecompose/typescript-config/library"
6
3
  }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: { index: "src/index.ts" },
5
+ format: ["esm", "cjs"],
6
+ target: "node20",
7
+ sourcemap: true,
8
+ dts: true,
9
+ clean: true,
10
+ });
package/.prettierignore DELETED
@@ -1 +0,0 @@
1
- *.yml
package/.prettierrc.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "proseWrap": "always",
3
- "plugins": ["./node_modules/prettier-plugin-jsdoc/dist/index.js"]
4
- }
package/dist/index.mjs DELETED
@@ -1,21 +0,0 @@
1
- // src/get-or-throw.ts
2
- function getOrThrow(objOrArr, keyOrIndex) {
3
- if (Array.isArray(objOrArr)) {
4
- if (keyOrIndex in objOrArr) {
5
- return objOrArr[keyOrIndex];
6
- } else {
7
- throw new Error(`Index ${String(keyOrIndex)} is out of bounds.`);
8
- }
9
- } else {
10
- if (keyOrIndex in objOrArr) {
11
- return objOrArr[keyOrIndex];
12
- } else {
13
- throw new Error(
14
- `Key "${String(keyOrIndex)}" does not exist in the object.`
15
- );
16
- }
17
- }
18
- }
19
- export {
20
- getOrThrow
21
- };
package/eslint.config.js DELETED
@@ -1,28 +0,0 @@
1
- import eslint from "@eslint/js";
2
- import tseslint from "typescript-eslint";
3
-
4
- export default tseslint.config(
5
- {
6
- ignores: [
7
- "dist/**",
8
- "node_modules/**",
9
- /**
10
- * Ignore all config files int the root. We should instead solve this with
11
- * projectService setting so that these files can also be linted, but
12
- * let's get this to work first
13
- */
14
- "*.{js,ts}",
15
- ],
16
- },
17
- eslint.configs.recommended,
18
- tseslint.configs.strictTypeChecked,
19
- tseslint.configs.stylisticTypeChecked,
20
- {
21
- languageOptions: {
22
- parserOptions: {
23
- projectService: true,
24
- tsconfigRootDir: import.meta.dirname,
25
- },
26
- },
27
- },
28
- );
package/tsup.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from "tsup";
2
-
3
- export default defineConfig({
4
- entry: ["src/index.ts"],
5
- format: ["esm", "cjs"],
6
- target: "node18",
7
- dts: true,
8
- });