vanara 0.1.0 → 0.3.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.
Files changed (195) hide show
  1. package/README.md +14 -0
  2. package/bin/vanara.js +66 -2
  3. package/catalog/agents/api-designer/AGENT.md +156 -0
  4. package/catalog/agents/api-designer/examples/openapi-snippet.yaml +130 -0
  5. package/catalog/agents/api-designer/examples/review-notes.md +61 -0
  6. package/catalog/agents/api-designer/references/contract-and-openapi.md +82 -0
  7. package/catalog/agents/api-designer/references/design-checklist.md +68 -0
  8. package/catalog/agents/api-designer/references/versioning-and-evolution.md +63 -0
  9. package/catalog/agents/api-designer/scripts/lint-openapi.mjs +180 -0
  10. package/catalog/agents/code-reviewer/AGENT.md +184 -0
  11. package/catalog/agents/code-reviewer/examples/pr-comment-template.md +48 -0
  12. package/catalog/agents/code-reviewer/examples/sample-review-output.md +38 -0
  13. package/catalog/agents/code-reviewer/references/review-checklist.md +46 -0
  14. package/catalog/agents/code-reviewer/references/security-review.md +62 -0
  15. package/catalog/agents/code-reviewer/references/severity-rubric.md +63 -0
  16. package/catalog/agents/code-reviewer/scripts/review-guard.mjs +102 -0
  17. package/catalog/agents/debugger/AGENT.md +128 -0
  18. package/catalog/agents/debugger/examples/postmortem-template.md +59 -0
  19. package/catalog/agents/debugger/examples/repro-template.md +50 -0
  20. package/catalog/agents/debugger/references/bisection.md +63 -0
  21. package/catalog/agents/debugger/references/debugging-method.md +57 -0
  22. package/catalog/agents/debugger/references/observability.md +61 -0
  23. package/catalog/agents/debugger/scripts/parse-stacktrace.mjs +109 -0
  24. package/catalog/agents/pr-summarizer/AGENT.md +172 -0
  25. package/catalog/agents/pr-summarizer/examples/sample-pr-summary.md +46 -0
  26. package/catalog/agents/pr-summarizer/examples/weak-vs-strong-summary.md +68 -0
  27. package/catalog/agents/pr-summarizer/references/identifying-risk.md +68 -0
  28. package/catalog/agents/pr-summarizer/references/reading-the-diff.md +68 -0
  29. package/catalog/agents/pr-summarizer/references/test-plan-and-missing-tests.md +64 -0
  30. package/catalog/agents/pr-summarizer/references/writing-the-summary.md +63 -0
  31. package/catalog/agents/pr-summarizer/scripts/diff-risk.mjs +111 -0
  32. package/catalog/agents/refactoring-specialist/AGENT.md +201 -0
  33. package/catalog/agents/refactoring-specialist/examples/before-after.md +91 -0
  34. package/catalog/agents/refactoring-specialist/examples/refactor-plan.md +97 -0
  35. package/catalog/agents/refactoring-specialist/references/large-scale-refactor.md +50 -0
  36. package/catalog/agents/refactoring-specialist/references/safe-workflow.md +75 -0
  37. package/catalog/agents/refactoring-specialist/references/smell-catalog.md +76 -0
  38. package/catalog/agents/refactoring-specialist/scripts/detect-smells.mjs +150 -0
  39. package/catalog/agents/security-auditor/AGENT.md +184 -0
  40. package/catalog/agents/security-auditor/examples/audit-report.md +68 -0
  41. package/catalog/agents/security-auditor/examples/finding-template.md +31 -0
  42. package/catalog/agents/security-auditor/references/audit-workflow.md +116 -0
  43. package/catalog/agents/security-auditor/references/severity-and-reporting.md +56 -0
  44. package/catalog/agents/security-auditor/references/vuln-classes.md +129 -0
  45. package/catalog/agents/security-auditor/scripts/scan-secrets.mjs +86 -0
  46. package/catalog/agents/technical-writer/AGENT.md +179 -0
  47. package/catalog/agents/technical-writer/examples/how-to-example.md +76 -0
  48. package/catalog/agents/technical-writer/examples/reference-example.md +75 -0
  49. package/catalog/agents/technical-writer/references/clarity-and-style.md +52 -0
  50. package/catalog/agents/technical-writer/references/diataxis-and-doc-types.md +61 -0
  51. package/catalog/agents/technical-writer/references/structure-and-maintenance.md +59 -0
  52. package/catalog/agents/technical-writer/scripts/readability.mjs +103 -0
  53. package/catalog/agents/test-author/AGENT.md +193 -0
  54. package/catalog/agents/test-author/examples/test-plan.md +54 -0
  55. package/catalog/agents/test-author/examples/unit-test-example.ts +55 -0
  56. package/catalog/agents/test-author/references/aaa-and-naming.md +75 -0
  57. package/catalog/agents/test-author/references/mocking-and-fixtures.md +94 -0
  58. package/catalog/agents/test-author/references/test-types-and-pyramid.md +60 -0
  59. package/catalog/agents/test-author/scripts/check-coverage.mjs +145 -0
  60. package/catalog/agents/threat-modeler/AGENT.md +179 -0
  61. package/catalog/agents/threat-modeler/examples/dfd-example.md +55 -0
  62. package/catalog/agents/threat-modeler/examples/threat-model.md +81 -0
  63. package/catalog/agents/threat-modeler/references/dfd-and-trust-boundaries.md +76 -0
  64. package/catalog/agents/threat-modeler/references/mitigation-catalog.md +77 -0
  65. package/catalog/agents/threat-modeler/references/stride-method.md +68 -0
  66. package/catalog/agents/threat-modeler/scripts/stride-checklist.mjs +183 -0
  67. package/catalog/agents/vuln-scanner/AGENT.md +162 -0
  68. package/catalog/agents/vuln-scanner/examples/finding-template.md +57 -0
  69. package/catalog/agents/vuln-scanner/examples/scan-report.md +49 -0
  70. package/catalog/agents/vuln-scanner/references/remediation-and-severity.md +68 -0
  71. package/catalog/agents/vuln-scanner/references/scan-types-and-tools.md +83 -0
  72. package/catalog/agents/vuln-scanner/references/triage-and-false-positives.md +73 -0
  73. package/catalog/agents/vuln-scanner/scripts/parse-scan-results.mjs +133 -0
  74. package/catalog/packs/code-review-pack/PACK.md +100 -0
  75. package/catalog/packs/code-review-pack/examples/pr-flow-example.md +50 -0
  76. package/catalog/packs/code-review-pack/references/rollout-guide.md +40 -0
  77. package/catalog/packs/security-pack/PACK.md +128 -0
  78. package/catalog/packs/security-pack/examples/owasp-vuln-triage-and-fix.md +66 -0
  79. package/catalog/packs/security-pack/examples/threat-model-then-audit.md +77 -0
  80. package/catalog/packs/security-pack/references/choosing-the-right-tool.md +37 -0
  81. package/catalog/packs/security-pack/references/threat-model-to-remediation.md +69 -0
  82. package/catalog/packs/security-pack/references/workflow.md +56 -0
  83. package/catalog/skills/api-pagination/SKILL.md +83 -0
  84. package/catalog/skills/api-pagination/examples/keyset-queries.sql +38 -0
  85. package/catalog/skills/api-pagination/examples/response-shapes.md +51 -0
  86. package/catalog/skills/api-pagination/references/consuming-pages.md +39 -0
  87. package/catalog/skills/api-pagination/references/counting-and-totals.md +41 -0
  88. package/catalog/skills/api-pagination/references/cursor-encoding.md +34 -0
  89. package/catalog/skills/api-pagination/scripts/check-cursor.mjs +51 -0
  90. package/catalog/skills/caching-strategies/SKILL.md +180 -0
  91. package/catalog/skills/caching-strategies/examples/http-cache-headers.md +82 -0
  92. package/catalog/skills/caching-strategies/examples/redis-cache-aside.js +110 -0
  93. package/catalog/skills/caching-strategies/references/cache-patterns.md +93 -0
  94. package/catalog/skills/caching-strategies/references/eviction-and-ttl.md +67 -0
  95. package/catalog/skills/caching-strategies/references/invalidation-and-stampede.md +99 -0
  96. package/catalog/skills/caching-strategies/scripts/ttl-jitter.mjs +99 -0
  97. package/catalog/skills/conventional-commits/SKILL.md +135 -0
  98. package/catalog/skills/conventional-commits/examples/commit-examples.md +60 -0
  99. package/catalog/skills/conventional-commits/examples/commitlint.config.js +31 -0
  100. package/catalog/skills/conventional-commits/references/breaking-changes-semver.md +50 -0
  101. package/catalog/skills/conventional-commits/references/scoping-commits.md +42 -0
  102. package/catalog/skills/conventional-commits/references/spec.md +78 -0
  103. package/catalog/skills/conventional-commits/scripts/lint-commit.mjs +97 -0
  104. package/catalog/skills/database-migrations/SKILL.md +137 -0
  105. package/catalog/skills/database-migrations/examples/add-column-safe.sql +34 -0
  106. package/catalog/skills/database-migrations/examples/migration-config.example.json +27 -0
  107. package/catalog/skills/database-migrations/references/expand-contract.md +74 -0
  108. package/catalog/skills/database-migrations/references/rollback-and-safety.md +71 -0
  109. package/catalog/skills/database-migrations/references/zero-downtime-changes.md +84 -0
  110. package/catalog/skills/database-migrations/scripts/check-migration-reversible.mjs +139 -0
  111. package/catalog/skills/error-handling-patterns/SKILL.md +136 -0
  112. package/catalog/skills/error-handling-patterns/examples/express-error-middleware.js +93 -0
  113. package/catalog/skills/error-handling-patterns/examples/result-pattern.ts +81 -0
  114. package/catalog/skills/error-handling-patterns/references/error-taxonomy.md +91 -0
  115. package/catalog/skills/error-handling-patterns/references/language-patterns.md +102 -0
  116. package/catalog/skills/error-handling-patterns/references/retry-and-backoff.md +76 -0
  117. package/catalog/skills/error-handling-patterns/scripts/lint-empty-catch.mjs +113 -0
  118. package/catalog/skills/git-collaboration-workflows/SKILL.md +104 -0
  119. package/catalog/skills/git-collaboration-workflows/examples/branch-protection.md +48 -0
  120. package/catalog/skills/git-collaboration-workflows/examples/pr-review-playbook.md +48 -0
  121. package/catalog/skills/git-collaboration-workflows/references/branching-models.md +52 -0
  122. package/catalog/skills/git-collaboration-workflows/references/history-hygiene.md +44 -0
  123. package/catalog/skills/git-collaboration-workflows/scripts/check-branch-hygiene.mjs +62 -0
  124. package/catalog/skills/owasp-top10/SKILL.md +112 -0
  125. package/catalog/skills/owasp-top10/examples/sql-injection-fix.md +33 -0
  126. package/catalog/skills/owasp-top10/examples/xss-fix.md +27 -0
  127. package/catalog/skills/owasp-top10/references/access-control.md +36 -0
  128. package/catalog/skills/owasp-top10/references/crypto-failures.md +29 -0
  129. package/catalog/skills/owasp-top10/references/injection.md +39 -0
  130. package/catalog/skills/owasp-top10/references/ssrf-and-design.md +35 -0
  131. package/catalog/skills/owasp-top10/scripts/scan-injection.mjs +73 -0
  132. package/catalog/skills/prompt-engineering/SKILL.md +100 -0
  133. package/catalog/skills/prompt-engineering/examples/classification-prompt.md +29 -0
  134. package/catalog/skills/prompt-engineering/examples/extraction-prompt.md +25 -0
  135. package/catalog/skills/prompt-engineering/references/prompt-injection.md +34 -0
  136. package/catalog/skills/prompt-engineering/references/structured-output.md +34 -0
  137. package/catalog/skills/prompt-engineering/references/techniques.md +33 -0
  138. package/catalog/skills/prompt-engineering/scripts/validate-output.mjs +71 -0
  139. package/catalog/skills/readme-writing/SKILL.md +176 -0
  140. package/catalog/skills/readme-writing/examples/README.template.md +73 -0
  141. package/catalog/skills/readme-writing/examples/good-readme-example.md +95 -0
  142. package/catalog/skills/readme-writing/references/anatomy.md +107 -0
  143. package/catalog/skills/readme-writing/references/maintenance.md +68 -0
  144. package/catalog/skills/readme-writing/references/writing-style.md +79 -0
  145. package/catalog/skills/readme-writing/scripts/lint-readme.mjs +117 -0
  146. package/catalog/skills/refactoring-patterns/SKILL.md +178 -0
  147. package/catalog/skills/refactoring-patterns/examples/extract-function.before-after.js +57 -0
  148. package/catalog/skills/refactoring-patterns/examples/replace-conditional-with-polymorphism.before-after.ts +63 -0
  149. package/catalog/skills/refactoring-patterns/references/code-smells.md +92 -0
  150. package/catalog/skills/refactoring-patterns/references/refactoring-catalog.md +78 -0
  151. package/catalog/skills/refactoring-patterns/references/safe-workflow.md +74 -0
  152. package/catalog/skills/refactoring-patterns/scripts/detect-smells.mjs +137 -0
  153. package/catalog/skills/rest-api-design/SKILL.md +147 -0
  154. package/catalog/skills/rest-api-design/examples/error-envelope.json +26 -0
  155. package/catalog/skills/rest-api-design/examples/orders-api.openapi.yaml +106 -0
  156. package/catalog/skills/rest-api-design/references/error-handling.md +43 -0
  157. package/catalog/skills/rest-api-design/references/pagination.md +41 -0
  158. package/catalog/skills/rest-api-design/references/status-codes.md +32 -0
  159. package/catalog/skills/rest-api-design/references/versioning.md +31 -0
  160. package/catalog/skills/rest-api-design/scripts/check-envelope.mjs +62 -0
  161. package/catalog/skills/secrets-management/SKILL.md +134 -0
  162. package/catalog/skills/secrets-management/examples/env-example.md +41 -0
  163. package/catalog/skills/secrets-management/examples/rotation-runbook.md +50 -0
  164. package/catalog/skills/secrets-management/references/leak-response.md +47 -0
  165. package/catalog/skills/secrets-management/references/rotation.md +46 -0
  166. package/catalog/skills/secrets-management/references/secret-managers.md +52 -0
  167. package/catalog/skills/secrets-management/scripts/detect-hardcoded.mjs +96 -0
  168. package/catalog/skills/secure-auth/SKILL.md +135 -0
  169. package/catalog/skills/secure-auth/examples/auth-flow.md +60 -0
  170. package/catalog/skills/secure-auth/examples/jwt-verify.ts +89 -0
  171. package/catalog/skills/secure-auth/references/auth-attacks.md +76 -0
  172. package/catalog/skills/secure-auth/references/oauth2-oidc.md +65 -0
  173. package/catalog/skills/secure-auth/references/password-hashing.md +70 -0
  174. package/catalog/skills/secure-auth/references/sessions-vs-jwt.md +69 -0
  175. package/catalog/skills/secure-auth/scripts/check-password-policy.mjs +114 -0
  176. package/catalog/skills/sql-index-tuning/SKILL.md +171 -0
  177. package/catalog/skills/sql-index-tuning/examples/explain-walkthrough.sql +91 -0
  178. package/catalog/skills/sql-index-tuning/examples/index-ddl.sql +67 -0
  179. package/catalog/skills/sql-index-tuning/references/btree-internals.md +58 -0
  180. package/catalog/skills/sql-index-tuning/references/composite-and-covering.md +60 -0
  181. package/catalog/skills/sql-index-tuning/references/reading-explain.md +54 -0
  182. package/catalog/skills/sql-index-tuning/references/selectivity-and-cardinality.md +62 -0
  183. package/catalog/skills/sql-index-tuning/scripts/suggest-index.mjs +213 -0
  184. package/catalog/skills/test-plan-design/SKILL.md +154 -0
  185. package/catalog/skills/test-plan-design/examples/test-case-template.md +45 -0
  186. package/catalog/skills/test-plan-design/examples/test-plan.md +57 -0
  187. package/catalog/skills/test-plan-design/references/case-design.md +79 -0
  188. package/catalog/skills/test-plan-design/references/risk-prioritization.md +58 -0
  189. package/catalog/skills/test-plan-design/references/test-pyramid.md +58 -0
  190. package/catalog/skills/test-plan-design/scripts/coverage-gaps.mjs +109 -0
  191. package/free-tier.json +15 -0
  192. package/package.json +15 -4
  193. package/premium-index.json +1 -0
  194. package/src/config.js +31 -1
  195. package/src/license.js +143 -0
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: error-handling-patterns
3
+ description: How to handle errors explicitly and consistently across an app — validate at boundaries, classify operational vs programmer errors, add context while propagating, retry transient failures with backoff, and never swallow. Covers JS/Python/Go/Rust patterns with runnable checks.
4
+ type: skill
5
+ version: 2.0.0
6
+ updated: 2026-06-29
7
+ ---
8
+ # Error Handling Patterns
9
+
10
+ Errors are part of the contract, not an afterthought. A robust system **handles errors where it can act,
11
+ propagates them where it can't, and never lets a failure vanish silently**. This skill is the deep
12
+ reference: the decisions, the trade-offs, and the anti-patterns. Heavy detail lives in `references/`,
13
+ copy-paste material in `examples/`, and a runnable linter for swallowed errors in `scripts/`.
14
+
15
+ ## Mental model
16
+
17
+ Every error sits on two axes that decide what you do with it:
18
+
19
+ | Axis | Question | Consequence |
20
+ |---|---|---|
21
+ | **Operational vs programmer** | Is this an expected runtime condition (network down, bad input) or a bug (null deref, broken invariant)? | Operational → recover/retry/surface. Programmer → fail fast, let it crash, fix the code. |
22
+ | **Recoverable vs fatal** | Can the caller do something useful about it here? | Yes → handle locally. No → add context and propagate. |
23
+
24
+ Classifying wrong is the root cause of most bad error handling: retrying a programmer bug forever, or
25
+ crashing the process over a single bad HTTP request. The full taxonomy is in
26
+ [`references/error-taxonomy.md`](references/error-taxonomy.md).
27
+
28
+ ## The five rules
29
+
30
+ 1. **Validate at the boundary.** Reject bad input early with a clear, specific message (fail fast). Never
31
+ trust data crossing a trust boundary — HTTP bodies, env vars, file contents, API responses.
32
+ 2. **Classify the error.** Operational errors are values you handle; programmer errors are bugs you
33
+ surface loudly. Don't `catch` a `TypeError` to "be safe".
34
+ 3. **Add context as you propagate.** Wrap with *what you were doing* and preserve the original cause and
35
+ stack (`new Error(msg, { cause })` in JS, `fmt.Errorf("...: %w", err)` in Go).
36
+ 4. **One owner for user-facing messaging.** Translate to a friendly message at the edge (the HTTP layer,
37
+ the UI). Keep full detail in server-side logs only — never leak stack traces to users.
38
+ 5. **Never swallow.** An empty `catch {}`, a bare `except: pass`, or a `.catch(() => {})` is a defect.
39
+ Run `scripts/lint-empty-catch.mjs` in CI to catch these automatically.
40
+
41
+ ## Adding context without losing the cause
42
+
43
+ The single most valuable habit: wrap errors with context while chaining the original, so the final log
44
+ shows the full causal trail from the low-level failure up to the request that triggered it.
45
+
46
+ ```js
47
+ // Node 16.9+ supports the standard `cause` option on Error.
48
+ async function placeOrder(userId, cart) {
49
+ try {
50
+ const order = await db.orders.insert({ userId, items: cart.items });
51
+ return order;
52
+ } catch (err) {
53
+ // Wrap: keep the DB error as `cause`, add the business context.
54
+ throw new Error(`failed to place order for user ${userId}`, { cause: err });
55
+ }
56
+ }
57
+ ```
58
+
59
+ ```python
60
+ # Python's `raise ... from` preserves the chain (shown as "The above exception
61
+ # was the direct cause of the following exception" in the traceback).
62
+ def place_order(user_id, cart):
63
+ try:
64
+ return db.orders.insert(user_id=user_id, items=cart.items)
65
+ except DBError as err:
66
+ raise OrderError(f"failed to place order for user {user_id}") from err
67
+ ```
68
+
69
+ Language-by-language patterns (try/catch vs `Result`/`Either` vs panics) are in
70
+ [`references/language-patterns.md`](references/language-patterns.md).
71
+
72
+ ## Retrying transient failures
73
+
74
+ Only **operational, transient, idempotent** failures are safe to retry — a 503 or a connection reset,
75
+ not a 400 or a `NullPointerException`. Retry with **exponential backoff plus full jitter** to avoid
76
+ synchronized retry storms (the "thundering herd"), and always cap attempts and total time.
77
+
78
+ ```js
79
+ async function withRetry(fn, { retries = 4, baseMs = 100, isRetryable } = {}) {
80
+ let attempt = 0;
81
+ for (;;) {
82
+ try {
83
+ return await fn();
84
+ } catch (err) {
85
+ attempt++;
86
+ if (attempt > retries || (isRetryable && !isRetryable(err))) throw err;
87
+ const backoff = baseMs * 2 ** (attempt - 1);
88
+ const jittered = Math.random() * backoff; // full jitter
89
+ await new Promise((r) => setTimeout(r, jittered));
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ When NOT to retry, circuit breakers, and budgets are covered in
96
+ [`references/retry-and-backoff.md`](references/retry-and-backoff.md).
97
+
98
+ ## Common pitfalls (anti-patterns)
99
+
100
+ - **Swallowing errors** — empty `catch {}`, `except: pass`, `.catch(() => {})`, or `if err != nil {}` in
101
+ Go. The failure disappears and you debug blind. This is the #1 defect; lint for it.
102
+ - **Catch-log-rethrow-wrapped duplication** — logging *and* rethrowing at every layer produces the same
103
+ error logged ten times. Log once, at the boundary that owns the response.
104
+ - **Catching too broadly** — `except Exception` or `catch (e)` that also eats `KeyboardInterrupt`,
105
+ programmer bugs, or `OutOfMemory`. Catch the narrowest type you can actually handle.
106
+ - **Returning a sentinel instead of erroring** — returning `null`/`-1`/`{}` on failure forces every
107
+ caller to remember the magic check; most won't. Prefer throwing or a `Result` type.
108
+ - **Leaking internals to users** — returning raw stack traces or SQL errors is both bad UX and a security
109
+ leak (reveals schema, file paths, library versions).
110
+ - **Retrying non-idempotent or non-transient operations** — retrying a `POST /charge` on timeout can
111
+ double-charge; retrying a `400` just wastes time. See the retry reference.
112
+ - **Losing the original cause** — `throw new Error("save failed")` without `{ cause }` discards the stack
113
+ that tells you *why*.
114
+
115
+ ## When NOT to use / trade-offs
116
+
117
+ - **Don't catch programmer errors to keep running.** A bug that corrupted state should crash the process
118
+ (let a supervisor like systemd/k8s/PM2 restart it clean) rather than limp along with bad data.
119
+ - **Don't add a `Result`/`Either` type to a small script** — the ceremony outweighs the benefit. Plain
120
+ exceptions are fine when there's one boundary and one owner.
121
+ - **Don't retry by default.** Retries amplify load during incidents and can mask a real outage; only
122
+ retry classified-transient, idempotent calls, and always with a budget.
123
+ - **Defensive `try/catch` everywhere is a smell**, not safety. It scatters handling, hides bugs, and
124
+ makes control flow unreadable. Handle at meaningful boundaries.
125
+
126
+ ## Files in this package
127
+
128
+ - [`references/error-taxonomy.md`](references/error-taxonomy.md) — operational vs programmer errors, and how the distinction drives recovery
129
+ - [`references/retry-and-backoff.md`](references/retry-and-backoff.md) — when to retry, exponential backoff, jitter, circuit breakers, budgets
130
+ - [`references/language-patterns.md`](references/language-patterns.md) — try/catch vs `Result`/`Either` vs panics across JS, Python, Go, Rust
131
+ - [`examples/express-error-middleware.js`](examples/express-error-middleware.js) — a complete centralized error handler with an `AppError` class
132
+ - [`examples/result-pattern.ts`](examples/result-pattern.ts) — a typed `Result<T, E>` with helpers, no exceptions for expected failures
133
+ - [`scripts/lint-empty-catch.mjs`](scripts/lint-empty-catch.mjs) — runnable Node linter that flags swallowed errors; supports `--selftest`
134
+
135
+ Pairs with the `code-reviewer` agent, the `silent-failure-hunter` agent, the `rest-api-design` skill
136
+ (for the HTTP error envelope), and the `owasp-top10` skill (for not leaking sensitive data in errors).
@@ -0,0 +1,93 @@
1
+ // Centralized error handling for an Express app.
2
+ //
3
+ // Pattern: routes never format error responses themselves. They throw (or pass
4
+ // to next()) typed AppErrors, and ONE error-handling middleware translates them
5
+ // to a consistent HTTP envelope, logs full detail server-side, and hides
6
+ // internals from the client.
7
+ //
8
+ // Run a quick demo: node express-error-middleware.js (no deps; uses a fake req/res)
9
+
10
+ // --- 1. A typed, operational error -----------------------------------------
11
+
12
+ class AppError extends Error {
13
+ constructor(message, { code, status = 500, retryable = false, cause } = {}) {
14
+ super(message, { cause });
15
+ this.name = 'AppError';
16
+ this.code = code; // stable, machine-readable: 'order_not_found'
17
+ this.status = status; // maps to HTTP status
18
+ this.retryable = retryable;
19
+ this.isOperational = true; // distinguishes from programmer bugs
20
+ }
21
+ }
22
+
23
+ // Convenience constructors for common cases.
24
+ const NotFound = (msg, code = 'not_found') => new AppError(msg, { code, status: 404 });
25
+ const BadRequest = (msg, code = 'bad_request') => new AppError(msg, { code, status: 400 });
26
+ const Conflict = (msg, code = 'conflict') => new AppError(msg, { code, status: 409 });
27
+
28
+ // --- 2. Wrap async route handlers so rejections reach the error middleware --
29
+ // Without this, a thrown error in an async handler becomes an unhandledRejection
30
+ // instead of hitting Express's error pipeline.
31
+ const asyncHandler = (fn) => (req, res, next) =>
32
+ Promise.resolve(fn(req, res, next)).catch(next);
33
+
34
+ // --- 3. The ONE error-handling middleware (must have 4 args) ----------------
35
+
36
+ function errorMiddleware(logger) {
37
+ return (err, req, res, _next) => {
38
+ const operational = err instanceof AppError && err.isOperational;
39
+ const status = operational ? err.status : 500;
40
+
41
+ // Log full detail server-side ALWAYS — including the cause chain.
42
+ logger.error('request failed', {
43
+ method: req.method,
44
+ path: req.path,
45
+ status,
46
+ code: err.code,
47
+ message: err.message,
48
+ cause: err.cause && String(err.cause),
49
+ stack: err.stack,
50
+ });
51
+
52
+ // Never leak internals to the client on a non-operational (bug) error.
53
+ const body = operational
54
+ ? { data: null, error: { code: err.code, message: err.message } }
55
+ : { data: null, error: { code: 'internal_error', message: 'Something went wrong.' } };
56
+
57
+ res.status(status).json(body);
58
+ };
59
+ }
60
+
61
+ // --- 4. Example routes ------------------------------------------------------
62
+ // app.get('/orders/:id', asyncHandler(async (req, res) => {
63
+ // const order = await db.orders.find(req.params.id);
64
+ // if (!order) throw NotFound(`order ${req.params.id} not found`, 'order_not_found');
65
+ // res.json({ data: order, error: null });
66
+ // }));
67
+ // app.use(errorMiddleware(logger)); // registered LAST, after all routes.
68
+
69
+ // --- Tiny self-contained demo (no Express needed) --------------------------
70
+
71
+ function demo() {
72
+ const logger = { error: (m, ctx) => console.log(`[log] ${m}`, ctx.code, '|', ctx.message) };
73
+ const handle = errorMiddleware(logger);
74
+ const fakeReq = { method: 'GET', path: '/orders/42' };
75
+ const fakeRes = {
76
+ status(s) { this._s = s; return this; },
77
+ json(b) { console.log(`-> HTTP ${this._s}`, JSON.stringify(b)); },
78
+ };
79
+
80
+ console.log('Operational error (safe to show user):');
81
+ handle(NotFound('order 42 not found', 'order_not_found'), fakeReq, fakeRes);
82
+
83
+ console.log('\nProgrammer bug (internals hidden from user):');
84
+ handle(new TypeError("Cannot read properties of undefined (reading 'id')"), fakeReq, fakeRes);
85
+ }
86
+
87
+ export { AppError, NotFound, BadRequest, Conflict, asyncHandler, errorMiddleware };
88
+
89
+ // Run the demo when executed directly (robust on Windows paths with spaces).
90
+ import { fileURLToPath } from 'node:url';
91
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
92
+ demo();
93
+ }
@@ -0,0 +1,81 @@
1
+ // A typed Result<T, E> for TypeScript: model *expected* failures as values so the
2
+ // compiler forces callers to handle them, instead of throwing for control flow.
3
+ //
4
+ // Reserve `throw` for programmer errors / truly exceptional paths. Use Result for
5
+ // validation, "not found", parse failures, and other ordinary outcomes.
6
+
7
+ // --- The type --------------------------------------------------------------
8
+
9
+ export type Ok<T> = { readonly ok: true; readonly value: T };
10
+ export type Err<E> = { readonly ok: false; readonly error: E };
11
+ export type Result<T, E> = Ok<T> | Err<E>;
12
+
13
+ export const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
14
+ export const err = <E>(error: E): Err<E> => ({ ok: false, error });
15
+
16
+ // --- Helpers ---------------------------------------------------------------
17
+
18
+ /** Transform the success value, leaving errors untouched. */
19
+ export function map<T, U, E>(r: Result<T, E>, fn: (t: T) => U): Result<U, E> {
20
+ return r.ok ? ok(fn(r.value)) : r;
21
+ }
22
+
23
+ /** Chain operations that can themselves fail (railway-oriented). */
24
+ export function flatMap<T, U, E>(
25
+ r: Result<T, E>,
26
+ fn: (t: T) => Result<U, E>,
27
+ ): Result<U, E> {
28
+ return r.ok ? fn(r.value) : r;
29
+ }
30
+
31
+ /** Provide a fallback for the error case. */
32
+ export function unwrapOr<T, E>(r: Result<T, E>, fallback: T): T {
33
+ return r.ok ? r.value : fallback;
34
+ }
35
+
36
+ /** Wrap a throwing function so its exception becomes an Err value. */
37
+ export function tryCatch<T>(fn: () => T): Result<T, Error> {
38
+ try {
39
+ return ok(fn());
40
+ } catch (e) {
41
+ return err(e instanceof Error ? e : new Error(String(e)));
42
+ }
43
+ }
44
+
45
+ // --- Domain error type -----------------------------------------------------
46
+
47
+ export type ValidationError = { code: 'validation'; field: string; message: string };
48
+
49
+ // --- Example: validation that returns Result instead of throwing -----------
50
+
51
+ interface SignupInput {
52
+ email: string;
53
+ age: number;
54
+ }
55
+
56
+ export function validateSignup(input: SignupInput): Result<SignupInput, ValidationError> {
57
+ if (!input.email.includes('@')) {
58
+ return err({ code: 'validation', field: 'email', message: 'must be a valid email' });
59
+ }
60
+ if (input.age < 18) {
61
+ return err({ code: 'validation', field: 'age', message: 'must be 18 or older' });
62
+ }
63
+ return ok(input);
64
+ }
65
+
66
+ // --- Usage: the caller CANNOT forget the error case (compiler enforces it) --
67
+
68
+ export function handleSignup(input: SignupInput): string {
69
+ const result = validateSignup(input);
70
+ if (!result.ok) {
71
+ // result is narrowed to Err<ValidationError> here.
72
+ return `rejected: ${result.error.field} ${result.error.message}`;
73
+ }
74
+ // result is narrowed to Ok<SignupInput> here.
75
+ return `accepted: ${result.value.email}`;
76
+ }
77
+
78
+ // Example with chaining:
79
+ // const parsed = tryCatch(() => JSON.parse(raw));
80
+ // const validated = flatMap(parsed, (obj) => validateSignup(obj as SignupInput));
81
+ // const msg = validated.ok ? 'ok' : `bad: ${validated.error.message}`;
@@ -0,0 +1,91 @@
1
+ # Error Taxonomy — Operational vs Programmer Errors
2
+
3
+ The most important classification in error handling. Get it wrong and you either crash on recoverable
4
+ conditions or limp along with corrupted state.
5
+
6
+ ## The two categories
7
+
8
+ ### Operational errors
9
+ Expected runtime conditions a correct program will still encounter. They are **values to be handled**,
10
+ not bugs.
11
+
12
+ - Failed to connect to a server / connection reset
13
+ - Request timed out
14
+ - Server returned `503` / `429`
15
+ - Invalid user input (failed validation)
16
+ - File not found, disk full, permission denied
17
+ - Out-of-money / out-of-stock business conditions
18
+
19
+ **Response:** recover, retry (if transient + idempotent), or surface a clear message to the caller/user.
20
+ These should *never* crash the process.
21
+
22
+ ### Programmer errors
23
+ Bugs. The code is wrong. The program is now in a state its author did not anticipate.
24
+
25
+ - `undefined is not a function` / `TypeError` / `AttributeError`
26
+ - Reading a property of `null`
27
+ - Passing the wrong type to a function
28
+ - Forgetting to `await` a promise
29
+ - A violated invariant / failed assertion
30
+
31
+ **Response:** **fail fast and loud.** Do not `catch` them to "be safe". Let the process crash and let a
32
+ supervisor (systemd, Kubernetes, PM2, a worker pool) restart it in a known-good state. Catching a bug to
33
+ continue running risks acting on corrupted data.
34
+
35
+ ## Why the distinction drives everything
36
+
37
+ | Decision | Operational | Programmer |
38
+ |---|---|---|
39
+ | Catch it? | Yes, where you can act | No — let it propagate to a top-level crash |
40
+ | Retry it? | If transient + idempotent | Never (the bug is deterministic) |
41
+ | Show the user? | A friendly, specific message | A generic "something went wrong" + 500, log internally |
42
+ | Alert on it? | Rate/aggregate (it's expected sometimes) | Page someone — it's a bug |
43
+
44
+ ## Modeling operational errors as types
45
+
46
+ Give operational errors a stable, machine-readable identity so callers and the HTTP layer can branch on
47
+ them without string-matching messages.
48
+
49
+ ```js
50
+ class AppError extends Error {
51
+ constructor(message, { code, status = 500, retryable = false, cause } = {}) {
52
+ super(message, { cause });
53
+ this.name = 'AppError';
54
+ this.code = code; // e.g. 'order_not_found' — stable, machine-readable
55
+ this.status = status; // maps to an HTTP status at the boundary
56
+ this.retryable = retryable;
57
+ this.isOperational = true; // distinguishes from programmer bugs
58
+ }
59
+ }
60
+
61
+ // At the top-level handler:
62
+ function isOperational(err) {
63
+ return err instanceof AppError && err.isOperational === true;
64
+ }
65
+ ```
66
+
67
+ At your crash handler, **only** swallow-and-respond for `isOperational` errors; rethrow everything else
68
+ so the process restarts.
69
+
70
+ ## The uncaught handler
71
+
72
+ ```js
73
+ process.on('uncaughtException', (err) => {
74
+ logger.fatal('uncaught exception — crashing', { err });
75
+ // Do NOT resume. Flush logs, then exit; let the supervisor restart.
76
+ process.exit(1);
77
+ });
78
+ process.on('unhandledRejection', (reason) => {
79
+ // Promote to an exception so the above handler runs.
80
+ throw reason;
81
+ });
82
+ ```
83
+
84
+ ## Pitfalls
85
+ - **Treating all errors the same.** A blanket `try/catch` around `main()` that logs and continues turns
86
+ bugs into silent data corruption.
87
+ - **Retrying programmer errors.** They're deterministic — the retry fails identically while burning time
88
+ and load.
89
+ - **A "500 for everything" API.** Validation failures are operational and should be `400/422`, not `500`.
90
+ - **Catching to add a log then re-throwing at every layer** — you get the same bug logged N times with no
91
+ added signal. Decide one owner.
@@ -0,0 +1,102 @@
1
+ # Language Patterns — Exceptions vs Result vs Panics
2
+
3
+ Different languages encode failure differently. Using the idiom the language was designed for keeps code
4
+ readable and tooling effective.
5
+
6
+ ## JavaScript / TypeScript — exceptions + `cause`
7
+
8
+ Throw/`try`/`catch` is idiomatic. Since Node 16.9 / ES2022, always chain the cause:
9
+
10
+ ```js
11
+ try {
12
+ await writeFile(path, data);
13
+ } catch (err) {
14
+ throw new Error(`failed to persist ${path}`, { cause: err });
15
+ }
16
+ ```
17
+
18
+ For *expected* failures (validation, "not found") in TypeScript, a `Result` type makes the failure path
19
+ explicit in the signature and forces the caller to handle it — see [`result-pattern.ts`](../examples/result-pattern.ts).
20
+ Reserve `throw` for exceptional/programmer paths.
21
+
22
+ Async caveat: a rejected promise that isn't `await`ed or `.catch`ed becomes an `unhandledRejection`.
23
+ Never write `.catch(() => {})` to silence it — that's swallowing.
24
+
25
+ ## Python — exceptions + `raise from`
26
+
27
+ Exceptions are the idiom. Preserve the chain and catch narrowly:
28
+
29
+ ```python
30
+ try:
31
+ config = json.loads(raw)
32
+ except json.JSONDecodeError as err:
33
+ raise ConfigError("config file is not valid JSON") from err
34
+ ```
35
+
36
+ - Catch the **narrowest** exception (`except KeyError`, not bare `except:`).
37
+ - A bare `except:` even catches `KeyboardInterrupt`/`SystemExit` — almost always a bug.
38
+ - Use `else` for the success path and `finally` for cleanup; or a context manager (`with`) for
39
+ acquire/release so cleanup can't be forgotten.
40
+
41
+ ```python
42
+ # Anti-pattern this skill's linter flags:
43
+ try:
44
+ do_thing()
45
+ except Exception:
46
+ pass # swallowed — the failure is now invisible
47
+ ```
48
+
49
+ ## Go — errors as values
50
+
51
+ Go has no exceptions for ordinary failures; errors are returned values you must check. Wrap with `%w` to
52
+ preserve the chain, then unwrap with `errors.Is`/`errors.As`.
53
+
54
+ ```go
55
+ func loadUser(id string) (*User, error) {
56
+ u, err := db.Get(id)
57
+ if err != nil {
58
+ return nil, fmt.Errorf("loadUser %s: %w", id, err)
59
+ }
60
+ return u, nil
61
+ }
62
+
63
+ // caller:
64
+ if err != nil {
65
+ if errors.Is(err, sql.ErrNoRows) { /* operational: 404 */ }
66
+ return err // propagate with context already attached
67
+ }
68
+ ```
69
+
70
+ - `panic` is for **programmer errors / unrecoverable** states only — not control flow.
71
+ - `if err != nil {}` with an empty body is the Go form of swallowing.
72
+ - `recover()` belongs at goroutine/server boundaries to stop one bad request from crashing the process,
73
+ not as a general try/catch.
74
+
75
+ ## Rust — `Result<T, E>` and `?`
76
+
77
+ Failure is in the type system. `Result<T, E>` must be handled (the compiler warns on an unused one), and
78
+ `?` propagates concisely while converting error types via `From`.
79
+
80
+ ```rust
81
+ fn read_config(path: &str) -> Result<Config, ConfigError> {
82
+ let raw = std::fs::read_to_string(path)?; // io::Error -> ConfigError via From
83
+ let cfg: Config = toml::from_str(&raw)?; // toml error -> ConfigError via From
84
+ Ok(cfg)
85
+ }
86
+ ```
87
+
88
+ - `Option<T>` models absence; `Result<T, E>` models failure with a reason.
89
+ - `panic!`, `.unwrap()`, `.expect()` are for unrecoverable/programmer errors — avoid in library code.
90
+ - Crates like `thiserror` (typed library errors) and `anyhow` (application-level context) are the
91
+ ecosystem standard.
92
+
93
+ ## Cross-cutting principles
94
+
95
+ | Principle | JS/TS | Python | Go | Rust |
96
+ |---|---|---|---|---|
97
+ | Preserve cause | `{ cause }` | `raise ... from` | `%w` + `errors.Is/As` | `source()` / `?` + `From` |
98
+ | Expected-failure idiom | `Result` type | exceptions | error return | `Result<T,E>` |
99
+ | Programmer-error idiom | `throw` (crash) | exception (crash) | `panic` | `panic!`/`unwrap` |
100
+ | Swallow anti-pattern | `.catch(()=>{})` | `except: pass` | `if err != nil {}` | `let _ = ...;` ignoring `Result` |
101
+
102
+ The throughline: **make failure explicit, preserve the cause, handle at a boundary, and never silence.**
@@ -0,0 +1,76 @@
1
+ # Retry & Backoff
2
+
3
+ Retries turn transient failures into success — or turn a small blip into a self-inflicted outage. The
4
+ difference is discipline: retry only the right errors, back off exponentially, add jitter, and cap the
5
+ budget.
6
+
7
+ ## What is safe to retry
8
+
9
+ A call is retry-safe only if **all three** hold:
10
+
11
+ 1. **Operational + transient** — `503`, `429`, connection reset, timeout. Not `400`, `401`, `404`, `422`,
12
+ and never a programmer bug.
13
+ 2. **Idempotent** — the same call twice has the same effect. `GET`, `PUT`, `DELETE` usually qualify.
14
+ `POST` does *not* unless protected by an idempotency key.
15
+ 3. **Within budget** — you have attempts and time left.
16
+
17
+ Retrying a non-idempotent `POST /charge` after a timeout can double-charge the customer (the first
18
+ request may have succeeded; only the *response* was lost). Use an `Idempotency-Key` so the server
19
+ deduplicates — see the `rest-api-design` skill.
20
+
21
+ ## Exponential backoff with jitter
22
+
23
+ Fixed-interval retries from many clients synchronize into waves that hammer a recovering service (the
24
+ **thundering herd**). Exponential backoff spreads attempts out; **jitter** desynchronizes clients.
25
+
26
+ ```js
27
+ // Full jitter (AWS-recommended): sleep = random(0, base * 2**attempt), capped.
28
+ function backoffDelay(attempt, { baseMs = 100, capMs = 10_000 } = {}) {
29
+ const exp = Math.min(capMs, baseMs * 2 ** attempt);
30
+ return Math.random() * exp;
31
+ }
32
+ ```
33
+
34
+ | Strategy | Behavior | Verdict |
35
+ |---|---|---|
36
+ | Fixed delay | every client retries at the same cadence | thundering herd |
37
+ | Exponential, no jitter | spreads over time but clients still align | better, still synchronized |
38
+ | Exponential + **full jitter** | random spread over the window | **recommended default** |
39
+
40
+ ## Caps and budgets
41
+
42
+ - **Max attempts:** typically 3–5. Beyond that you're usually masking a real outage.
43
+ - **Total deadline:** cap wall-clock time (e.g. 30s) so a request doesn't hang the caller.
44
+ - **Respect `Retry-After`:** if the server sends it (on `429`/`503`), honor it over your own backoff.
45
+ - **Retry budget:** limit retries to a small fraction (e.g. 10%) of total requests so retries can't more
46
+ than ~double load during an incident.
47
+
48
+ ## Circuit breakers
49
+
50
+ When a dependency is clearly down, stop sending it traffic — retries just pile on. A circuit breaker
51
+ tracks the recent failure rate and **opens** (fails fast) past a threshold, periodically letting a
52
+ **half-open** trial request through to test recovery.
53
+
54
+ ```
55
+ CLOSED --(failures > threshold)--> OPEN --(cooldown elapsed)--> HALF-OPEN
56
+ ^ |
57
+ +--------------------(trial request succeeds)------------------------+
58
+ (trial fails -> back to OPEN)
59
+ ```
60
+
61
+ This protects the failing service (lets it recover) and the caller (fails fast instead of waiting on
62
+ timeouts).
63
+
64
+ ## Timeouts come first
65
+
66
+ Retries are pointless without timeouts — a hung connection with no timeout never fails, so it never
67
+ retries. Always set a per-attempt timeout *and* an overall deadline. `AbortController` (JS),
68
+ `context.WithTimeout` (Go), and `asyncio.wait_for` (Python) are the standard tools.
69
+
70
+ ## Pitfalls
71
+ - **Retrying everything** — retrying `400`/`401`/`422` is pure waste; they'll fail identically.
72
+ - **No jitter** — synchronized retries DDoS your own recovering service.
73
+ - **Unbounded retries** — a stuck dependency turns into an infinite loop and resource exhaustion.
74
+ - **Retrying non-idempotent writes** without an idempotency key — duplicate charges, duplicate orders.
75
+ - **Nested retries** — retry at the HTTP client *and* the service *and* the caller multiplies attempts
76
+ exponentially (3×3×3 = 27). Retry at exactly one layer.