---
summary: "Canvas: inferred schemas always nullable, query() streaming cap, missing-table NotFound, system-catalog deny layer, approxSizeBytes in describe(); packaging: exports default condition, HEALTHCHECK, template toolchain sync, wrangler date, importHelpers removed; test-infra: test:node, test:leaks, fast-check devDep, coverage bumps"
breaking: false
security: false
agent-notes: |
  Consumers using the bridge-pattern workarounds for inferred NOT NULL or system-catalog denial can remove them: inferSchemaFromRows always emits nullable: true now, and assertNoSystemCatalogs is available at /canvas. The new default condition in exports is additive — no import changes needed.
---

# 0.10.4 — 2026-06-10

## Added

- **`assertNoSystemCatalogs(sql)`** — new export from `/canvas`; opt-in SQL gate layer that rejects `information_schema`, `pg_catalog`, `sqlite_master`, and `duckdb_<name>()` references. Enable via `QueryOptions.denySystemCatalogs` or `RegisterViewOptions.denySystemCatalogs`. Throws `ValidationError` with `data.reason: 'system_catalog_access'`. ([#224](https://github.com/cyanheads/mcp-ts-core/issues/224))
- **`QueryResult.truncated`** — `true` when the non-`registerAs` query path capped results at `rowLimit`; `rowCount` reflects materialized rows when truncated. ([#222](https://github.com/cyanheads/mcp-ts-core/issues/222))
- **`QueryOptions.denySystemCatalogs`** / **`RegisterViewOptions.denySystemCatalogs`** — opt-in catalog deny flag for both query and view-registration paths. ([#224](https://github.com/cyanheads/mcp-ts-core/issues/224))
- **`TableInfo.approxSizeBytes`** — now populated for base tables via `duckdb_tables().estimated_size`; `undefined` for views. ([#226](https://github.com/cyanheads/mcp-ts-core/issues/226))
- **`"default"` condition** on all 18 JS export-map entries — satisfies default-walking resolvers; same target file as `"import"`. ([#217](https://github.com/cyanheads/mcp-ts-core/issues/217))
- **`HEALTHCHECK`** in `Dockerfile` and `templates/Dockerfile` — `bun -e fetch(...)` every 30s against `/healthz`; no `curl`/`wget` dependency. ([#217](https://github.com/cyanheads/mcp-ts-core/issues/217))
- **`test:node`** script — runs the full suite under a real Node binary (bypasses the `bun-node` PATH shim).
- **`test:leaks`** script — runs Vitest with `--detect-async-leaks`.
- **`fast-check` `^4.8.0`** added to devDependencies (already used in the fuzz suite); remains a peer dependency.
- **`skills/api-testing`** `v1.4` — new section: output schema assertions via `expect.schemaMatching`.
- **`skills/api-canvas`** `v1.6` — documents `truncated`, `denySystemCatalogs`, `approxSizeBytes`, inferred-nullable behavior, and missing-table `NotFound`.

## Changed

- **`query()` non-`registerAs` path** — replaced `runAndReadAll` + JS-side `.slice()` with `streamAndReadUntil(sql, rowLimit + 1)` to prevent full JS-heap materialization on large or cross-product queries. ([#222](https://github.com/cyanheads/mcp-ts-core/issues/222))
- **Missing-table error from `assertReadOnlySql`** — DuckDB binder/catalog errors at `prepare(0)` now surface as `NotFound` with `data.reason: 'missing_table'` and a recovery hint; previously classified as `ValidationError` with reason `non_select_statement`. ([#223](https://github.com/cyanheads/mcp-ts-core/issues/223))
- **`inferSchemaFromRows`** — inferred columns are always `nullable: true`; the previous behavior emitted `nullable: false` when all sampled rows were non-null, which caused appender failures when nulls appeared past the sniff window. Pass explicit `RegisterTableOptions.schema` to enforce `NOT NULL`. ([#221](https://github.com/cyanheads/mcp-ts-core/issues/221))
- **`test:all`** — expanded to include `test:coverage`, `test:node`, `test:worker`, `test:integration` (previously omitted `test:coverage` and `test:node`).
- **`packageManager`** — bumped `bun@1.3.2` → `bun@1.3.11`.
- **`peerDependencies`** — `diff: "latest"` → `^9.0.0`; `fast-check: "latest"` → `^4.0.0` (pins to compatible majors). ([#217](https://github.com/cyanheads/mcp-ts-core/issues/217))
- **`templates/package.json`** devDependencies synced to framework toolchain: `typescript ^5.9.3` → `^6.0.3`, `@types/node ^25.6.0` → `25.9.3`, `@biomejs/biome ^2.4.7` → `2.4.16`, `vitest ^4.1.0` → `^4.1.8`, `tsc-alias ^1.8.16` → `^1.8.17`. ([#217](https://github.com/cyanheads/mcp-ts-core/issues/217))
- **`wrangler.toml` `compatibility_date`** — `"2026-02-13"` → `"2026-06-10"`. ([#217](https://github.com/cyanheads/mcp-ts-core/issues/217))
- **`tsconfig.base.json` / `tsconfig.json`** — `importHelpers: true` removed; flag is a no-op at ESNext target and `tslib` was never declared. ([#217](https://github.com/cyanheads/mcp-ts-core/issues/217))
- **`src/mcp-server/server.ts`** — `DRAFT-2025-11-25` references updated to `spec revision 2025-11-25 (GA)`. ([#217](https://github.com/cyanheads/mcp-ts-core/issues/217))
- **Coverage thresholds** raised: statements 88 → 91, branches 79 → 83, functions 90 → 92, lines 89 → 92.

## Removed

- **`tsconfig.test.json`** — deleted; tests now share `tsconfig.json`. Stale `.gitattributes` `export-ignore` entry removed. ([#217](https://github.com/cyanheads/mcp-ts-core/issues/217))
