From 09e27896d4272c060444e25cd93ea83f696c8fe6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 8 May 2026 03:32:01 +0000 Subject: [PATCH] Remove onLog from @relayburn/sdk@2.x option types (#374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 1.x SDK accepted `onLog` to surface archive-fallback messages from the JS-side fallback path. The 2.x Node facade is SQLite-native and silently dropped the callback at the napi boundary — there is no fallback to log. Drop the option from `summary`, `sessionCost`, `overhead`, `overheadTrim`, `hotspots`, and `compare` rather than wire a hook for events that don't fire. Adds a regression test asserting the d.ts no longer declares `onLog` on any option type, plus a runtime tolerance test that the verbs still accept a stray `onLog` property (so embedders mid-migration from 1.x don't blow up at runtime when their TS shape lags). --- packages/sdk-node/CHANGELOG.md | 4 ++ packages/sdk-node/src/index.d.ts | 13 ++---- packages/sdk-node/test/no-onlog.test.js | 56 +++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 packages/sdk-node/test/no-onlog.test.js diff --git a/packages/sdk-node/CHANGELOG.md b/packages/sdk-node/CHANGELOG.md index 519a00fb..fdcef09c 100644 --- a/packages/sdk-node/CHANGELOG.md +++ b/packages/sdk-node/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Breaking Changes + +- Removed the `onLog` option from `summary`, `sessionCost`, `overhead`, `overheadTrim`, `hotspots`, and `compare` option types. The 2.x stack is SQLite-native and has no archive-fallback path to surface, so the callback was already a no-op at the napi boundary. (#374) + ## [2.1.0] - 2026-05-07 ### Added diff --git a/packages/sdk-node/src/index.d.ts b/packages/sdk-node/src/index.d.ts index ca5fa27a..9b2e8d7b 100644 --- a/packages/sdk-node/src/index.d.ts +++ b/packages/sdk-node/src/index.d.ts @@ -38,8 +38,6 @@ export interface SummaryOptions { /** ISO timestamp (e.g. `2026-04-01T00:00:00Z`) or relative range (`24h`, `7d`, `4w`, `2m`). */ since?: string; ledgerHome?: string; - /** Optional logger invoked when the SQLite archive read fails and the SDK falls back to a full ledger walk. */ - onLog?: (msg: string) => void; } export declare function summary(opts?: SummaryOptions): Promise<{ totalTokens: number | bigint; @@ -64,7 +62,6 @@ export interface SessionCostOptions { /** Session id to total. Omit for `{ note: 'no session id provided' }`. */ session?: string; ledgerHome?: string; - onLog?: (msg: string) => void; } export interface SessionCostResult { sessionId: string | null; @@ -85,7 +82,6 @@ export interface OverheadOptions { since?: string; kind?: OverheadFileKind; ledgerHome?: string; - onLog?: (msg: string) => void; } export interface OverheadSection { @@ -183,7 +179,6 @@ export interface HotspotsOptions { groupBy?: HotspotsGroupBy; patterns?: string[]; ledgerHome?: string; - onLog?: (msg: string) => void; } export interface HotspotsFileRow { @@ -333,7 +328,6 @@ export interface CompareOptions { minSample?: number; minFidelity?: FidelityClass; ledgerHome?: string; - onLog?: (msg: string) => void; } export interface CompareResult { @@ -356,9 +350,10 @@ export declare function compare(opts: CompareOptions): Promise // --------------------------------------------------------------------------- // 2.x extensions — surfaces present in `relayburn-sdk` (the Rust crate) // but not in the TS 1.x `packages/sdk/index.d.ts`. Pre-1.0 widening per -// the SDK shape rule; embedders that pinned to 1.x won't see these names -// (the missing `onLog` etc. plus the absence of these is the only TS-vs- -// napi gap the conformance gate measures). +// the SDK shape rule; embedders that pinned to 1.x won't see these names. +// The 1.x `onLog` callback is intentionally omitted: it surfaced the +// archive-fallback path that no longer exists in the SQLite-native 2.x +// stack (see issue #374), so there is nothing to log. // --------------------------------------------------------------------------- export interface SearchQueryOptions { diff --git a/packages/sdk-node/test/no-onlog.test.js b/packages/sdk-node/test/no-onlog.test.js new file mode 100644 index 00000000..3bbae985 --- /dev/null +++ b/packages/sdk-node/test/no-onlog.test.js @@ -0,0 +1,56 @@ +// Regression guard for issue #374. The 2.x Node facade is SQLite-native and +// has no archive-fallback path to surface, so `onLog` was always a no-op at +// the napi boundary. We removed it from the public option types; this test +// fails if it ever sneaks back in (verb-by-verb), so we don't accidentally +// re-document a callback that doesn't fire. +// +// Also asserts that calling each verb with a stray `onLog` property is +// tolerated at runtime — JS allows extra props on options bags, and we don't +// want to break embedders mid-migration from 1.x just because they haven't +// dropped the field yet. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const DTS_PATH = resolve(__dirname, '..', 'src', 'index.d.ts'); + +test('public option types no longer declare onLog (#374)', () => { + const dts = readFileSync(DTS_PATH, 'utf8'); + const offending = dts + .split('\n') + .map((line, i) => ({ line, n: i + 1 })) + .filter(({ line }) => /^\s*onLog\??\s*:/.test(line)); + assert.deepStrictEqual( + offending, + [], + `onLog reappeared in src/index.d.ts at:\n` + + offending.map(({ n, line }) => ` ${n}: ${line.trim()}`).join('\n'), + ); +}); + +test('verbs tolerate a stray onLog property at runtime', async (t) => { + if (process.env.RELAYBURN_SDK_NAPI_BUILT !== '1') { + t.skip('napi-rs binding not built — set RELAYBURN_SDK_NAPI_BUILT=1'); + return; + } + let sdk; + try { + sdk = await import(join(__dirname, '..', 'src', 'index.js')); + } catch (err) { + if (/native binding not found/i.test(String(err && err.message))) { + t.skip('napi-rs binding load failed — build artifact missing'); + return; + } + throw err; + } + // Cast through any-shape to bypass the now-stricter option types: the + // contract under test is the runtime forgiveness, not the TS shape. + const stray = { onLog: () => {} }; + await assert.doesNotReject(() => sdk.summary(stray)); + await assert.doesNotReject(() => sdk.sessionCost(stray)); + await assert.doesNotReject(() => sdk.hotspots(stray)); +});