# CHANGELOG

All notable changes to this project will be documented in this file.

See detailed changes in [docs/changes-log/](docs/changes-log/) for each work session, and
[docs/release-notes/](docs/release-notes/) for each release.

## [Unreleased]

### Changed

- Reworked the public README, installation guidance, support policy, contribution workflow, and GitHub templates for
  public repository use.
- Corrected package authentication, build-output, API example, and Cloudflare Workflows limit documentation.
- Added automated Markdown formatting and local link checks to the standard test gate.

## [1.3.0] - 2026-09-11

The eight bindings this package had no accessor for. See the [release notes](docs/release-notes/1.3.0.md) and the
[changes log](docs/changes-log/2026-09-11.md).

### Breaking Changes

- **`CloudflareEnv` gains eight members, which breaks a consumer that already declared one of them.** Found while
  adopting the release, after publication. A Worker whose `Env extends CloudflareEnv` and which declared `BROWSER`,
  `WORKFLOW`, `FLAGS`, `PIPELINE`, `AI_SEARCH`, `AI_SEARCH_NAMESPACE`, `VPC_SERVICE` or `VPC_NETWORK` with a narrower
  type now fails `TS2430`, plus cascading errors wherever that `Env` is passed as a `CloudflareEnv`. One consuming
  Worker declared `BROWSER?: BrowserWorker` and exposed the issue during adoption. The fix is to delete the local
  declaration and let the inherited one stand — no export was removed, renamed or narrowed

### Added

- **`BrowserBinding`** — the accessor for Browser Rendering, over `Binding` like the other ten. A consumer had been
  reading `env.BROWSER` directly, so a deployment missing the binding handed `undefined` to a browser automation library
  and failed inside it. It is now a `MissingBindingError` naming `BROWSER`, and the binding name moves into
  configuration
- **`BrowserSettings`**, a `browser` module in `cloudflareDefaults` and `CloudflareConfiguration`, and
  `BROWSER?: Fetcher` on `CloudflareEnv` — which previously described ten of the eighteen bindings this package can
  reach
- **`WorkflowBinding`** — the producer side of Cloudflare Workflows, carrying `create`, `createBatch` and `get` under
  the platform's own names, plus `status(env, id)` so a status endpoint reads a run in one await rather than two. The
  accessor arrives before its first known consumer, so adoption requires no migration
- **`WorkflowSettings`** with `successRetention` and `errorRetention`, a `workflow` module in `cloudflareDefaults` and
  `CloudflareConfiguration`, and `WORKFLOW?: Workflow` on `CloudflareEnv`. Retention is settings rather than a call
  argument for the reason KV's `expirationTtl` is, and the merge is all-or-nothing: a call naming `retention` owns both
  halves of it
- **`FlagshipBinding`** — feature-flag evaluation at request time, carrying `boolean`, `string`, `number` and `object`.
  **The one accessor here that does not throw on a missing binding**: a flag decides between two paths that both work,
  so a Worker that cannot reach Flagship takes the path it took before flags existed. Each method takes a fallback
  covering all three failure modes — no binding, a failed evaluation, a type mismatch — and an evaluation error is
  logged and swallowed, as `AnalyticsBinding.writeSafe` treats a failed telemetry write. `resolve(env)` still throws,
  for a caller that genuinely cannot proceed without a live evaluation
- **`FlagshipSettings`** with a `context` merged into every evaluation, a `flagship` module in `cloudflareDefaults` and
  `CloudflareConfiguration`, and `FLAGS?: Flagship` on `CloudflareEnv`. The context is the deployment-wide half of a
  targeting rule and a call's own context wins on any key both name. Only the four value methods are exposed; the
  platform's `*Details` variants and untyped `get` stay reachable through `resolve(env)`
- **`AiSearchBinding`** — managed retrieval over one indexed corpus, carrying `search` (passages only), `chat` (an
  answer plus the chunks it came from), `chatStream`, `info` and `stats`. Streaming is a separate method rather than
  `stream: true` on `chat`, because a boolean argument that changes a return type is a discriminated union pretending to
  be an option
- **`AiSearchNamespaceBinding`** — the same product bound to a namespace, so a request names the corpus rather than the
  deployment fixing it. `instance`, `list`, `create`, `remove`, and a multi-instance `search` that reports partial
  failure rather than hiding it: chunks tagged by instance, and `errors` naming the ones that did not answer
- **`AiSearchSettings`** and **`AiSearchNamespaceSettings`**, `aiSearch` and `aiSearchNamespace` modules in
  `cloudflareDefaults` and `CloudflareConfiguration`, and `AI_SEARCH?: AiSearchInstance` plus
  `AI_SEARCH_NAMESPACE?: AiSearchNamespace` on `CloudflareEnv`. The two default names differ because a Worker may
  declare both and two bindings cannot share a name on one `env`. `AiSearchSettings.options` carries retrieval defaults,
  applied all-or-nothing like Workflows' retention
- **`PipelineBinding`** — the producer side of Cloudflare Pipelines, which lands structured records in R2 as batched
  files on rules configured on the pipeline rather than by the Worker sending to it. Two ways to send, and choosing is
  the whole decision: `send` throws like every other accessor, `sendSafe` logs and returns `false` exactly as
  `AnalyticsBinding.writeSafe` does. A pipeline's usual job is a second, independent sink beside the one doing the real
  work, and a sink like that failing the run that produced the records inverts its purpose. An audit trail that is the
  only copy should still throw
- **`PipelineSettings`**, a `pipeline` module in `cloudflareDefaults` and `CloudflareConfiguration`, and
  `PIPELINE?: Pipeline` on `CloudflareEnv`. A binding name and nothing else, because batching, destination, format and
  partitioning are configured on the pipeline and apply to every producer — moving that policy off the producer is most
  of the reason to send through a pipeline rather than writing to R2 directly
- **`VpcServiceBinding`** — one service on a private network, reached over a Cloudflare Tunnel with nothing exposed to
  the internet. `fetch` alone, over `Binding<Fetcher>`, since a VPC Service binding is `Fetcher` structurally. The
  destination is `service_id` in `wrangler.json` and fixed at deploy time, so a Worker holding this binding reaches one
  private endpoint and no request it handles can talk it into reaching another
- **`VpcNetworkBinding`** — a whole private network by Cloudflare Tunnel or Mesh, with each call naming its destination.
  `fetch` for HTTP and `connect` for raw TCP (Redis, Memcached, MQTT), plaintext only at the time of writing. **Prefer a
  VPC Service wherever one endpoint will do**: this binding reaches anything on the network, so an address derived from
  an incoming request is a server-side request forgery with a private network behind it
- **`VpcServiceSettings`**, **`VpcNetworkSettings`** and **`VpcNetwork`**, `vpcService` and `vpcNetwork` modules in
  `cloudflareDefaults` and `CloudflareConfiguration`, and `VPC_SERVICE?: Fetcher` plus `VPC_NETWORK?: VpcNetwork` on
  `CloudflareEnv`. Both settings carry a binding name and nothing else — deliberately no allowlist of reachable
  destinations, since a control enforced in library code is one a caller routes around with `resolve(env)`

### Changed

- **The package's own inventory.** `src/index.ts`, the README and the bindings guide had said "ten accessors" and
  "twelve settings shapes" since 1.0.0; they now say eighteen and twenty, and the bindings guide's table lists Browser
  Rendering, Workflows, Flagship, Pipelines and both the AI Search and Workers VPC bindings with their `wrangler.json`
  keys
- **The bindings guide's "adding a binding of your own" walkthrough** built a hypothetical `BrowserBinding` with a
  `screenshot` method, invented when no such class existed. It now builds an `EmailBinding`, which this package
  genuinely does not provide
- **No existing export is touched**

### Notes

- **There is deliberately no `BrowserService`.** Driving a browser needs `@cloudflare/puppeteer`, a runtime dependency
  rather than a type, and taking it on would put a browser automation library in the tree of every Worker installing
  this package for a KV accessor. The driving also has one consumer, which has not earned an interface. If a second
  appears, the shape to copy is `./middlewares`: a subpath behind an optional peer. `Fetcher` is used rather than
  `BrowserWorker` for the same reason — structurally identical, and it costs no dependency
- **There is deliberately no service for Workflows, Flagship, Pipelines, either AI Search binding or either Workers VPC
  binding, for a different reason.** Browser Rendering's obstacle is a dependency; the other seven's is that the kernel
  has no capability to implement. `@bayudwiyansatria/core` names a `CacheStore`, a `DataStore`, a `MessageQueue` —
  durable execution, flag evaluation, managed retrieval, record archival and private-network access are not among them,
  and inventing an interface for a single implementation with no consumers is ceremony rather than abstraction. Flag
  evaluation is the likeliest of the eight to earn one later, since it has many providers and none of its vocabulary is
  Cloudflare's. Nothing is lost by waiting: unlike Browser Rendering, all seven bindings' own operations cost no
  dependency, so a consumer gets the real surface either way
- **The eight accessors ship under one version.** 1.3.0 had not been published when any of the later seven landed, so an
  unpublished number absorbed them rather than seven more being spent
- **Workflows, Flagship, AI Search, Pipelines and Workers VPC have no known migration requirement.** Their seven
  accessors are deliberately available before consumers need to reach for bindings directly
- **AI Search deliberately does not go through `AIBinding`.** `env.AI.autorag()` and `env.AI.aiSearch()` are the older
  path to the same product and are `@deprecated` in the pinned `@cloudflare/workers-types`, in favour of the standalone
  bindings these two classes wrap
- **`Pipeline` is the first binding type this package wraps that is not global.** It lives in
  `declare module "cloudflare:pipelines"`, so `lib/index.d.ts` now carries a module import. `rollup.config.ts` already
  listed `/^cloudflare:/` as external, so the emitted declaration keeps the import and the runtime bundles carry no
  trace of it, the import being type-only. A consumer tsconfig must resolve that module, which every Worker with
  `@cloudflare/workers-types` already does
- **Workers VPC is in beta, and `VpcNetwork` is the one binding shape this package declares itself.**
  `@cloudflare/workers-types` carries no VPC type at the pinned version, and Cloudflare states its APIs may change
  before general availability. A VPC Service needed nothing invented — it is `Fetcher` structurally — but a VPC
  Network's `connect` had no type to borrow. A hand-written type cannot be checked against the runtime, so if the
  surface changes before GA this compiles and fails in production. It is confined to one file, kept to the two
  documented methods, and should be deleted rather than maintained once the official type ships

## [1.2.2] - 2026-08-29

`cloudflareDefaults.analytics.minRequestStatus` moves from `0` to `500`. A data point is now a failure unless a Worker
says otherwise. See the [release notes](docs/release-notes/1.2.2.md).

### Breaking Changes

- **A Worker that never named `analytics.minRequestStatus` stops recording `2xx` and `4xx` request measurements** on
  upgrade, without a code change on its side. No export is removed, renamed or narrowed and `AnalyticsSettings` is the
  shape it was, which is why this lands as a patch — but a deployment querying request rate or latency percentiles from
  Analytics Engine needs `minRequestStatus: 0` set _before_ upgrading, not after noticing. `AnalyticsService.event` is
  unaffected, as ever: a domain metric does not describe a response

### Changed

- **`cloudflareDefaults.analytics.minRequestStatus` is `500`.** This reverses the 1.1.0 decision to leave the choice to
  each deployment, whose reasoning still holds but assumed the choice would be exercised: three weeks on, a quarter of
  the Workers binding an Analytics Engine dataset had set the threshold, and of those that had not, only one had
  actually decided to record everything. The rest were accepting a default they could not see
- **`AnalyticsSettings.minRequestStatus` is documented around the new default**, naming `400` and `0` as the two reasons
  to move off it rather than presenting `0` as the baseline. `AnalyticsService.request` applies the same
  `status >= minRequestStatus` check it always has
- **`wrangler.json` declares its `$schema`**, moves to a `2026-08-28` compatibility date, and orders the binding arrays
  as the reference documentation lists them. Repository configuration only, but it is the file a consumer copies

### Fixed

- **`drops the point when the dataset is unbound` passed a `200`**, which the new default filters before the
  unbound-binding path it exists to cover is reached. It now passes a `500` — a test passing for the wrong reason,
  exposed by moving the default

## [1.2.1] - 2026-08-24

What 330 production log records showed: half of every line was already in `$metadata`, the Message column was blank, 46%
of rows carried none of our fields, and a third of the client addresses we recorded were synthetic. See the
[release notes](docs/release-notes/1.2.1.md).

### Breaking Changes

- **`service`, `colo`, `country` and `durationMs` are off the line by default.** `cloudflareDefaults.logging.fields`
  narrows a line to `requestId`, `method`, `path`, `ip` and `status` — what Cloudflare does not already record on its
  own `$metadata` envelope. Each is one word in `LOG_FIELDS` away, and a deployment shipping through Logpush or OTel
  should add `service` back
- **The payload is flattened onto the line** by the kernel, so anything reading `data.status` reads `status`
- **`logToAnalytics` emits a `message`** — `Request completed: POST /api/v1/search (400)` — alongside the unchanged
  `event`. It is what fills the dashboard's Message column, which `$metadata` promotes from the top-level `message` key
  and nothing else

### Added

- **`CloudflareEnv.LOG_FIELDS`** — which ambient fields a line carries, per deployment, without a rebuild. `*` keeps
  every field; `""` keeps none
- **`CloudflareConfiguration.logging`** — the seam that lets this package narrow a kernel default. The only entry in the
  surface that is not a binding, and the only place this package overrides the kernel rather than adding to it: knowing
  that Cloudflare records the script name is exactly the knowledge an adapter exists to hold

### Fixed

- **`CloudflareRequestMetadata` reads `cf-connecting-ipv6` ahead of `cf-connecting-ip`.** A zone with Pseudo-IPv4 set to
  overwrite headers replaces the latter with a synthetic `240.0.0.0/4` address for every IPv6 client — 15 of 28 distinct
  addresses in the export, and byte-identical to `cf-pseudo-ipv4` on 50 of 150 invocation rows. It routes nowhere,
  geolocates to nothing, and need not be stable between requests, while reading as a real address
- **`authentication.failed` logs at `warn`**, not `info`. Four production failures never reached a severity filter

## [1.2.0] - 2026-08-23

Observability, rebuilt so that a log line means something on its own. See the
[release notes](docs/release-notes/1.2.0.md).

### Breaking Changes

- **Log lines carry `event` where they carried `message`**, and the values are dotted event names rather than prose.
  Anything querying on `message` — a saved Workers Logs view, a Logpush consumer, an alert — needs updating
- **Every Worker needs `SERVICE_NAME` in its `wrangler.json` `vars`**, beside its own `name`. Every line still carries a
  `service` — the change is where the value comes from. It used to live in `config/LoggingConfig.ts`, a file a rename
  does not touch, which once allowed a stale service name to reach production. A deployment that omits the var logs
  `service: "unknown"` rather than nothing
- **`LoggingSettings.service` is optional**, so code reading it reads `string | undefined`

### Added

- **`events`** — the cross-cutting event vocabulary as a frozen constant, so the spelling of `request.failed` is not
  available to typos. A Worker's own domain events stay in that Worker
- **`RequestCorrelation`** — the `X-Request-Id` header that carries one request id across a Service Binding hop, which
  the edge never sees and so never assigns a `CF-Ray` to. Correlation only: the value is attacker-controlled and gates
  nothing
- **`CloudflareEnv.LOG_LEVEL` and `CloudflareEnv.SERVICE_NAME`** — so this package's own logging call sites are typeable
  from a generic `E extends CloudflareEnv`
- **`docs/reference/observability.md`** — event naming, correlation, the sensitive-data rules, sampling, source maps,
  and how to walk a production failure back to its cause

### Changed

- **`logToAnalytics` emits `request.completed` and `request.failed`** rather than one `request` event. `service`,
  `colo`, and `country` stay on the line: each overlaps a Workers Logs column on purpose, because that column does not
  travel with an exported line
- **`CloudflareRequestMetadata` prefers an inbound `X-Request-Id`** over `CF-Ray`, so an id propagated by an upstream
  Worker spans the whole fan-out
- **`SecurityMiddleware` records why it refused** — `authentication.failed` with `api_key_mismatch` or `api_key_absent`,
  `authorization.denied` for a rejected origin, `request.failed` for a Worker deployed without its key. No key value
  appears in any of them. It also stopped logging the client IP at `debug`, which the request line already carries
- **`AnalyticsBinding.writeSafe` distinguishes an unbound dataset from a rejected write**, at `debug` and `warn`
  respectively, through `Logger` rather than a bare `console.error`. The old line fired once per request on every Worker
  that binds no dataset

### Fixed

- **Credential-named fields are scrubbed from every log payload** before serialisation, through `redact` in
  `@bayudwiyansatria/core@1.2.0`

### Dependencies

- `@bayudwiyansatria/core` → `^1.2.0`

## [1.1.0] - 2026-08-08

One addition. A Worker can now keep its successful requests out of Analytics Engine without keeping its own copy of the
logging middleware, through the same `configure`/`resolve` path every other setting travels.

### Breaking Changes

- **`AnalyticsSettings` gains a required member**, `minRequestStatus`. Anything constructing that shape by hand rather
  than spreading `cloudflareDefaults` will stop type-checking until it supplies one. Overrides stay partial, so a
  deployment naming only `binding` or only `enabled` is unaffected — in practice the compile error is confined to test
  fixtures that assemble a literal, which is why this lands as a minor

### Added

- **`AnalyticsSettings.minRequestStatus`** and **`cloudflareDefaults.analytics.minRequestStatus`** — a threshold below
  which a request measurement is not recorded. Default `0`, which records everything, because a dataset holding only
  failures cannot answer request rate or latency percentiles: the denominator is no longer in it
- No new exported name. `minRequestStatus` is a member on an interface the entry point already published, so both
  `exports` subpaths and the count of names behind them are unchanged

### Changed

- **The check lives in `AnalyticsService.request`, not in `logToAnalytics`.** It is a telemetry policy rather than a
  delivery one, so a caller that never registers the Hono middleware gets the same answer. `event()` is deliberately
  unaffected — a threshold over response status has nothing to say about a domain metric
- **`@bayudwiyansatria/core` moves to `^1.1.0`**, which brings `DeliverySettings`. Nothing in this package reads it; the
  bump keeps the pair in step so a Worker spreading both defaults gets `delivery` in its configuration surface

### Notes

- Runtime behaviour is unchanged for anyone who does not set the new field
- 57 specs across five suites, up from 47 across four; `test/core/services/AnalyticsService.spec.ts` is new

See [docs/release-notes/1.1.0.md](docs/release-notes/1.1.0.md) for full release details.

## [1.0.0] - 2026-08-06

First release of `@bayudwiyansatria/cloudflare`, extracting previously vendored Cloudflare adapters into a reusable
library. The existing library scaffolding was kept and its placeholder domain was replaced.

### Added

- **Bindings** — `Binding` plus ten accessors: AI, Analytics Engine, D1, Durable Objects, Hyperdrive, KV, Queues, R2,
  Rate Limiting, Vectorize
- **Services** — ten capability services, each implementing an interface from `@bayudwiyansatria/core`
- **Configuration** — `cloudflareDefaults` and the twelve settings shapes
- **A second published entry**, `./middlewares`, carrying `SecurityMiddleware` and `logToAnalytics` so a Worker using
  another framework — or none — depends on this package without installing Hono. An ESLint rule keeps Hono confined to
  that directory, because the moment a binding imports it the peer stops being optional
- **`lazySettings()`** — memoises a getter and looks the settings module up on first use. Accessors are created at
  module scope, which runs while a consumer's imports are still being hoisted, before `configure()` can have run;
  resolving in a constructor made the _import statement_ throw, with no ordering a consumer could write to avoid it
- **`assertExternals`**, a Rollup plugin proving on every build that the kernel is not inlined. `MissingBindingError`
  extends `MissingCapabilityError` across the package boundary and the configuration registry is module state, so a
  second copy would break both silently — a check the unit tests cannot make, since they import from `src/`

### Changed

- **Configuration is registered, not imported.** `resolve()` used to import the application's `src/config/` directly. A
  package cannot reach into its consumer, so the application now hands the surface to the kernel through `configure()`
- **`Env` became `CloudflareEnv`.** The ambient global is now an exported interface a Worker extends: a published
  package has no reliable way to merge into a consumer's global scope, and two attempting it would collide. Every member
  is optional, widened from the boilerplate's required `TELEMETRY`, `AI`, and `KV`
- **Bindings and services are generic** over `<E extends CloudflareEnv>` with a default, so `new KVService()` still
  works and `KVService<Env>` gets the Worker's own type at call sites
- **`src/` moved onto the library template's layout** — `bindings/` and `services/` under `core/`, with `middlewares/`
  left beside it so the directory boundary and the package boundary are the same line

### Removed

- The template's placeholder domain: `src/core/Health.ts`, `src/core/Metadata.ts`, `src/types/HealthStatus.ts`,
  `src/types/LibraryMetadata.ts`, `src/utils/Text.ts`, `src/exceptions/LibraryException.ts`, and the three specs over
  them

### Notes

- Two entries (`.` and `./middlewares`), each emitting CommonJS, ESM, and declarations. `typesVersions` maps the subpath
  for consumers on classic (node10) resolution
- No UMD output: it needs every dependency reachable as a browser global, and this package has real externals
- No `.d.ts.map`: `rollup-plugin-dts` emits one with `sources: []`, which is inert and crashes TypeDoc downstream

See [docs/release-notes/1.0.0.md](docs/release-notes/1.0.0.md) for full release details.

## Project scaffold history

These entries describe the library scaffold inherited by this repository. Its build, `@/*` mapping, TypeDoc gate, and
documentation portal remain relevant, but the versions predate this package.

### Scaffold revision — 2026-08-05

Gives the template a working structure and a documentation system, and repairs the tooling that was supposed to keep
both honest.

#### Breaking Changes

- **The CommonJS and UMD bundles changed extension**, `lib/index.min.js` → `lib/index.min.cjs` and
  `lib/index.min.umd.js` → `lib/index.min.umd.cjs`. `package.json` declares `"type": "module"`, which makes Node read
  every `.js` file in the package as ESM — so the CommonJS bundle parsed as ESM and `require()` of it returned nothing
  usable. The build succeeded and the tests passed throughout, because neither exercises the packaged form. Consumers
  using `require` were already broken and are now fixed; anyone referencing the bundle paths directly must update them
- **`baseUrl: "src"` is replaced by `paths: { "@/*": ["./src/*"] }`.** Imports move from `'types'` to `'@/types'`. The
  old form produced bare specifiers, and `types`, `constants`, and `utils` are all real packages on npm — the meaning of
  such an import depended on what happened to be installed
- **`peerDependencies: { rollup }` removed.** A library built with Rollup does not require its consumers to install
  Rollup; this forced an unrelated peer on every downstream project
- **`.eslintrc.json` and `.eslintignore` replaced by `eslint.config.ts`.** ESLint 10 does not read the former

#### Added

- **A layered `src/`** — `core/` (domain), `types/`, `constants/`, `exceptions/`, `utils/`, each with an
  `@module`-documented barrel stating what belongs in the layer and what may import it
- **`LibraryException`** — a base exception carrying a stable `code` and an optional `cause`, so consumers can catch and
  branch without matching on message text. `cause` is declared on the class rather than taken from `ErrorOptions`, which
  is ES2022, keeping the ES2020 baseline intact
- **A lint-enforced layer boundary.** `types/`, `constants/`, `exceptions/`, and `utils/` may not import from `@/core`
- **`exports` map** in `package.json`, so modern Node and bundlers resolve the package instead of falling back to legacy
  `main`/`module`
- **A documentation site** — `docker/docs.Dockerfile` and nginx serving a landing page, the TypeDoc reference, the
  coverage report, and the HonKit book, published on port 80 at `http://localhost/`
- **Documentation gates.** `typedoc.json` gains `treatWarningsAsErrors` with `validation.notDocumented`, `invalidLink`,
  `notExported`, and `rewrittenLink`; `skipErrorChecking` is `false`. Turning it on immediately caught a stale README
  link left by a file rename
- **A restructured book** — `getting-started/`, `guides/`, and `reference/`, with an index that states which docs are
  living and which are point-in-time records
- **Tests** for the new modules; `test/index.spec.ts` is unchanged and still passes, which is what proves the public
  surface survived the restructure
- `.dockerignore`, and the `build:static` / `build:all` / `docker:*` scripts

#### Changed

- **HonKit replaces GitBook.** `gitbook-cli` is abandoned and no longer installs; the old image was pinned to
  `node:10-buster`
- **`docs/book.json` `root`** corrected from `./docs` to `.` — the file already lives inside `docs/`
- **Toolchain modernized** — ESLint 8.23 → 10, `@typescript-eslint` 5 → 8, TypeScript 4.7.4 → 5.0.4, Jest 29 → 30,
  TypeDoc 0.23 → 0.28, plus an `overrides` block pinning vulnerable transitives
- **Coverage reporters** are now `lcovonly` + `html`, so Codecov still gets `lcov.info` while the site serves a
  browsable report
- **`tsconfig.test.json`** — its `types` array sat outside `compilerOptions`, where TypeScript ignored it
- The stale UMD global inherited from an unrelated project was replaced with a neutral library name
- Rollup emits `exports: 'named'` for CommonJS and UMD, and drops warnings originating in `node_modules`
- `docs/changes-log/2026-apr-19.md` renamed to `2026-04-19.md` for ISO consistency
- `docs/release-notes/1.0.0.md` release date corrected to 2026-04-19, matching `CHANGELOG.md`

#### Fixed

- **`lint:run` linted nothing.** `eslint -c .eslintrc.json --ext=.ts,tsx` was missing a path argument
- **`npm test` hid lint failures.** `npm run lint & npm run test:run` backgrounds the linter on POSIX shells; it is now
  `&&`
- **`dev` referenced a runtime CLI** that was never a dependency and has no meaning in a library. It is now Rollup watch
  mode

#### Removed

- The root `Dockerfile` and `docker-compose.yaml`, whose final image ran `tail -f /dev/null` and served nothing
- `docker/book.Dockerfile` and `docker/docker-compose.book.yaml`, the dead GitBook build
- Dead devDependencies `@eslint/config-array`, `eslint-plugin-import`, and `eslint-config-typescript`, none of which any
  config referenced
- The Deployment section of `AGENTS.md`, which documented an Azure Static Web Apps workflow, a
  `staticwebapp.config.json`, and a `DEPLOYMENT.md` — none of which exist in this repository

### Initial scaffold — 2026-04-19

#### Added

- Initial repository scaffold for a Node.js library template
- TypeScript, Rollup, Jest, ESLint, Prettier, and TypeDoc configuration
- Community documentation and contribution standards
- Changelog and release notes structure under `docs/`
