You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The boundary receives a nullable `UserDto?` and either returns a fully non-null `User` or a typed failure — no `!`, no `default!`, no `dynamic` (3.1, 3.3, 3.5). The one place a `null` from the wire is handled is explicit and local (3.4). `UserId` is a branded value type minted by a validating factory (3.7), and every public field is a `record` property carrying value semantics (3.8). The interior never sees an absence value it did not ask for.
25
+
The boundary receives a nullable `UserDto?` and either returns a fully non-null `User` or a typed failure — no `!`, no `default!`, no `dynamic` (3.1, 3.3, 3.5). The one place a `null` from the wire is handled is explicit and local (3.4). `UserId` is a branded value type generated by a validating factory (3.7), and every public field is a `record` property carrying value semantics (3.8). The interior never sees an absence value it did not ask for.
Copy file name to clipboardExpand all lines: csharp/05-methods-and-functions.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -112,7 +112,7 @@ public string Describe() // good — bloc
112
112
113
113
**Reasoning, step by step:**
114
114
1. A call with three or more positional arguments is a row of unlabelled values at the call site — `Settle(amount, rate, account, true)` — where a reader cannot tell which `decimal` is which and a transposed pair compiles silently. Collecting them into an immutable `record` gives every argument a name at the call site, lets the caller use object-initializer or `with` syntax, and makes adding a field a non-breaking change instead of another positional slot (chapter [06](./06-types-and-data-modeling.md)).
115
-
2. The record also becomes the home for the validation and the defaults: `required` members force the caller to supply what matters, `init` defaults fill the rest, and the parse-don't-validate boundary (chapter [03](./03-nullability-and-the-type-system.md)) can mint a proven options value once. Two well-named parameters need no ceremony; the threshold is three, where the loss of labelling starts to cost correctness.
115
+
2. The record also becomes the home for the validation and the defaults: `required` members force the caller to supply what matters, `init` defaults fill the rest, and the parse-don't-validate boundary (chapter [03](./03-nullability-and-the-type-system.md)) can generate a proven options value once. Two well-named parameters need no ceremony; the threshold is three, where the loss of labelling starts to cost correctness.
`Money` is minted in exactly one place — `Money.parse`, the parse-constructor that validates the integer-cents value before returning an immutable value object. Nothing else creates a `Money`. The public method sits above the two helpers it calls, so the file reads top-down (5.4). Guard clauses assert the preconditions and leave the happy path flush left (5.3). The keyword argument `now:` keeps the call site legible without a positional slot that callers can transpose (5.4 rule). A postcondition pair verifies the sum from both of its parts before return (5.12). Each method holds one level of abstraction — `settle_order` orchestrates named steps and touches no arithmetic itself (5.2). The helpers reach a `Money` only through `Money.parse`, the single parse-constructor that validates (per chapter 03).
42
+
`Money` is generated in exactly one place — `Money.parse`, the parse-constructor that validates the integer-cents value before returning an immutable value object. Nothing else creates a `Money`. The public method sits above the two helpers it calls, so the file reads top-down (5.4). Guard clauses assert the preconditions and leave the happy path flush left (5.3). The keyword argument `now:` keeps the call site legible without a positional slot that callers can transpose (5.4 rule). A postcondition pair verifies the sum from both of its parts before return (5.12). Each method holds one level of abstraction — `settle_order` orchestrates named steps and touches no arithmetic itself (5.2). The helpers reach a `Money` only through `Money.parse`, the single parse-constructor that validates (per chapter 03).
Copy file name to clipboardExpand all lines: skills/typescript-bun-styleguide/reference/checklist.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,7 +32,7 @@ Bun runtime additions on top of `typescript-styleguide`. Additive only — never
32
32
- Every route declares request AND response schemas; the outbound `.parse()` is a leak tripwire.
33
33
- Handlers are thin: parse already-validated input, call one plain domain function, return; no Hono types in domain code. Test via `app.request()`, not a live socket or MSW.
34
34
- One centralized `app.onError(mapError)` maps domain errors to `problem+json`; handlers never craft a 5xx; unknown errors map to a generic 500 carrying only the correlation id.
35
-
- Every request carries a `correlationId`, minted or accepted once at the edge, propagated through `AsyncLocalStorage` via `store.run`.
35
+
- Every request carries a `correlationId`, generated or accepted once at the edge, propagated through `AsyncLocalStorage` via `store.run`.
36
36
- Set server timeouts explicitly: `idleTimeout` on the `Bun.serve` config and a per-route `timeout(...)`, kept under the upstream LB idle timeout.
37
37
- Rate-limit at the edge with bounded store state (LRU max size or Redis TTL), `429` + `Retry-After`.
38
38
- Health endpoints are honest: liveness does no dependency I/O (restart signal); readiness checks dependencies and fails first during drain (traffic signal).
Copy file name to clipboardExpand all lines: skills/typescript-styleguide/reference/checklist.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,7 +39,7 @@ One section per chapter. Read on demand for a full audit; the SKILL digest cover
39
39
- Prefer optional `?` over `| undefined` in object types (`exactOptionalPropertyTypes` enforces the difference).
40
40
- Narrow with the weakest tool that works: discriminant, then `typeof`/`instanceof`/`in`, then a custom guard.
41
41
- Unit-test every custom type guard (`x is T`) with positive and negative cases.
42
-
- Brand domain primitives in high-rigor modules; mint only through a validating constructor (the one sanctioned `as`).
42
+
- Brand domain primitives in high-rigor modules; generate only through a validating constructor (the one sanctioned `as`).
43
43
- Put `readonly`/`ReadonlyArray`/`Readonly<T>` in every public signature.
44
44
- Constrain every generic; add no gratuitous type parameters; annotate variance (`in`/`out`) on public generic interfaces.
45
45
- Write erasable syntax only: no `enum`, runtime `namespace`, parameter properties, or `import =`. Consume a codegen `enum` only at the boundary, convert to a union inside.
Copy file name to clipboardExpand all lines: typescript-bun/03-http-services.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,7 +18,7 @@ import { mapError } from './error-map.ts'; // the one domain-error
18
18
const app =newHono();
19
19
20
20
app.use('*', async (c, next) => { // 3.6 — correlate before any handler runs
21
-
const correlationId =c.req.header('x-request-id') ??randomUUID(); // accept inbound or mint
21
+
const correlationId =c.req.header('x-request-id') ??randomUUID(); // accept inbound or generate
22
22
awaitrunWithRequestContext({ correlationId }, next); // store.run wraps the rest of the request (ch. 06)
23
23
});
24
24
app.onError(mapError); // 3.5 — handlers never craft 5xx; one map owns error → problem+json
@@ -35,7 +35,7 @@ app.get(
35
35
exportdefault { fetch: app.fetch, idleTimeout: 30 }; // 3.7 — Bun.serve picks this up; idleTimeout is the slowloris guard
36
36
```
37
37
38
-
Every byte is parsed before the handler sees it and every reply field is schema-checked on the way out (3.3); the handler is glue over `getUser`, a function the unit tests call with no HTTP in sight (3.4); failures route through one `onError` (3.5); a correlation id is minted or accepted once and threaded through `AsyncLocalStorage` so every downstream log line carries it (3.6); the server idle timeout is set explicitly on the exported `Bun.serve` config (3.7). This is the Hono-on-`Bun.serve` idiom (3.1) the rest of the chapter justifies rule by rule.
38
+
Every byte is parsed before the handler sees it and every reply field is schema-checked on the way out (3.3); the handler is glue over `getUser`, a function the unit tests call with no HTTP in sight (3.4); failures route through one `onError` (3.5); a correlation id is generated or accepted once and threaded through `AsyncLocalStorage` so every downstream log line carries it (3.6); the server idle timeout is set explicitly on the exported `Bun.serve` config (3.7). This is the Hono-on-`Bun.serve` idiom (3.1) the rest of the chapter justifies rule by rule.
39
39
40
40
## Rules
41
41
@@ -128,14 +128,14 @@ app.onError((err, c) => {
128
128
### 3.6 — Every request carries a correlation id.
129
129
130
130
**Reasoning, step by step:**
131
-
1. A middleware registered with `app.use('*', ...)` either accepts an inbound `x-request-id` (so a trace spans services) or mints a fresh UUID when none arrives, via `randomUUID` from `node:crypto`. The id is decided once, at the very front of the request, before any handler or domain call.
131
+
1. A middleware registered with `app.use('*', ...)` either accepts an inbound `x-request-id` (so a trace spans services) or generates a fresh UUID when none arrives, via `randomUUID` from `node:crypto`. The id is decided once, at the very front of the request, before any handler or domain call.
132
132
2. It propagates through `AsyncLocalStorage` ([06](./06-logging.md)), which works unchanged on Bun, not by threading a parameter through every function. The middleware binds it with `store.run` wrapping the request continuation — `await store.run(ctx, next)` — the scoped form [06 §6.2](./06-logging.md) mandates over the mid-handler accessor it bans for leaking context into whatever runs next on the loop. Any code in the request's async context — domain function, repository, error handler — then reads the id from the store, so correlation survives `await` boundaries without polluting signatures.
133
133
3. Every log line during the request carries that id, which makes the one boundary log (3.5) joinable to all that led to it — parity with the kotlin-jvm correlation contract ([kotlin-jvm 06](../kotlin-jvm/06-logging.md)): one id, set at the edge, on every line, across the whole call tree.
134
134
4. The canonical log key is `correlationId`, set once here at the edge and read everywhere downstream ([06 §6.2](./06-logging.md)) — one name for the id in the child logger, the `AsyncLocalStorage` store, and every line, so a trace joins without reconciling synonyms.
135
135
136
136
```ts
137
137
app.use('*', async (c, next) => {
138
-
const correlationId =c.req.header('x-request-id') ??randomUUID(); // accept inbound or mint
138
+
const correlationId =c.req.header('x-request-id') ??randomUUID(); // accept inbound or generate
139
139
awaitrunWithRequestContext({ correlationId }, next); // store.run wraps the request continuation, the scoped form ch. 06 §6.2 mandates
- zod at the boundary, `z.infer` as the single source of type truth: [core 10.7](../typescript/10-api-design.md). Boundary route rule, `unknown` inward, `any` banned, `undefined` over `null`: [core 03's §3.2, §3.5, §3.6](../typescript/03-the-type-system.md).
166
-
- Branded `Cents`, integer minor units, the parse-mint constructor: [core 05](../typescript/05-functions.md).
166
+
- Branded `Cents`, integer minor units, the parse-generate constructor: [core 05](../typescript/05-functions.md).
167
167
- Null-versus-absent and time types, JVM parity: [kotlin-jvm serialization](../kotlin-jvm/05-serialization.md).
168
168
- Parse every boundary, crash-only boot, dependency justification: BUN-3, BUN-1, BUN-4 ([README](./README.md)). Rows parsed at the edge: [persistence](./04-persistence.md). HTTP body limits and handlers: [HTTP services](./03-http-services.md).
Copy file name to clipboardExpand all lines: typescript-bun/06-logging.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -71,7 +71,7 @@ log().info(`order ${orderId} placed with ${itemCount} items`); // bad — data m
71
71
**Reasoning, step by step:**
72
72
1. Every log line for a request needs the same cross-cutting context: a correlation id, the principal, the tenant. Threading a `logger` or `ctx` parameter through every function to achieve that pollutes signatures all the way down and breaks the moment one layer forgets to pass it.
73
73
2.`AsyncLocalStorage` from `node:async_hooks` is the answer, and the direct analog of SLF4J's MDC in [kotlin-jvm/06-logging.md](../kotlin-jvm/06-logging.md) §6.6. Bun ships it through its `node:async_hooks` compatibility, and it works for this pattern: the store follows the async call graph across every `await`, timer, and microtask — the store set before an `await` is the store seen after it. Where the JVM bridges MDC across coroutine suspensions with `MDCContext`, here no bridging is needed.
74
-
3. Bind the store once at the boundary with `runWithRequestContext(ctx, fn)` and read it through a `log()` accessor that does `root.child(store.getStore())`. Correlation id and principal then appear on every line in that request's async subtree, no parameter passed. The canonical context key is `correlationId` — one name in the store, the child logger, and every line, so a trace joins downstream without reconciling synonyms ([03 §3.6](./03-http-services.md)mints it at the edge). Use `store.run` to scope it; never `enterWith` mid-handler, which leaks the context into whatever runs next on the loop.
74
+
3. Bind the store once at the boundary with `runWithRequestContext(ctx, fn)` and read it through a `log()` accessor that does `root.child(store.getStore())`. Correlation id and principal then appear on every line in that request's async subtree, no parameter passed. The canonical context key is `correlationId` — one name in the store, the child logger, and every line, so a trace joins downstream without reconciling synonyms ([03 §3.6](./03-http-services.md)generates it at the edge). Use `store.run` to scope it; never `enterWith` mid-handler, which leaks the context into whatever runs next on the loop.
Copy file name to clipboardExpand all lines: typescript-bun/08-build-and-distribution.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -119,7 +119,7 @@ await Bun.build({
119
119
120
120
**Reasoning, step by step:**
121
121
1. Supply-chain integrity runs in both directions, and both directions need a guarantee. Outbound: a consumer installing your package wants proof the tarball was built from the source it claims and not swapped by a compromised token. Inbound: your build wants proof the dependency tree it resolves is the exact one that was reviewed.
122
-
2. Outbound is npm provenance plus 2FA. `bun publish` carries the tarball, the access flag, and 2FA (`--auth-type`, `--otp`), but as of this writing it does not emit a provenance attestation — there is no `--provenance` flag. So the publish step in CI runs `npm publish --provenance` (from a CI runner with an OIDC identity), which attaches a signed, verifiable link from the artifact back to the commit and workflow that produced it. This is a registry-tooling choice, not a workflow regression: `bun` builds, installs, and audits the package; the one publish call that mints the attestation goes through `npm` until Bun ships provenance. 2FA on the publish step means a stolen token alone cannot push a release, and a laptop cannot mint provenance — which is the point: publishing moves to CI (BUN-4). When `bun publish` gains `--provenance`, the call swaps and the rest of the pipeline is unchanged.
122
+
2. Outbound is npm provenance plus 2FA. `bun publish` carries the tarball, the access flag, and 2FA (`--auth-type`, `--otp`), but as of this writing it does not emit a provenance attestation — there is no `--provenance` flag. So the publish step in CI runs `npm publish --provenance` (from a CI runner with an OIDC identity), which attaches a signed, verifiable link from the artifact back to the commit and workflow that produced it. This is a registry-tooling choice, not a workflow regression: `bun` builds, installs, and audits the package; the one publish call that generates the attestation goes through `npm` until Bun ships provenance. 2FA on the publish step means a stolen token alone cannot push a release, and a laptop cannot generate provenance — which is the point: publishing moves to CI (BUN-4). When `bun publish` gains `--provenance`, the call swaps and the rest of the pipeline is unchanged.
123
123
3. Inbound is the committed lockfile, no exceptions. `bun.lock` pins every transitive dependency to an exact version and integrity hash, so `bun install` resolves the reviewed tree and not a freshly-floated one. An uncommitted or `.gitignore`d lockfile means every install is an unreviewed code change ([../security.md](../security.md), BUN-4).
0 commit comments