From 76f69175fecdcd4b8dd27464805f99a0ce7c3db2 Mon Sep 17 00:00:00 2001 From: Suleiman Shahbari Date: Thu, 16 Jul 2026 23:07:13 +0300 Subject: [PATCH] fix(framework): savePreferences returns the typed error on a failed write --- .changeset/save-preferences-result.md | 5 +++++ .../src/dashboard-rpc/preferences.telefunc.test.ts | 14 ++++++++++++++ .../src/dashboard-rpc/preferences.telefunc.ts | 10 ++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 .changeset/save-preferences-result.md diff --git a/.changeset/save-preferences-result.md b/.changeset/save-preferences-result.md new file mode 100644 index 00000000..43400ee6 --- /dev/null +++ b/.changeset/save-preferences-result.md @@ -0,0 +1,5 @@ +--- +'@gemstack/framework': patch +--- + +Fix `savePreferences` rejecting the RPC when the underlying write fails. The telefunction advertises a `{ ok: false, error }` result and already returns it for the not-enabled case, but a failed disk write threw straight through, so the client saw a rejected call instead of the typed error. The write is now wrapped, so both failure modes return `{ ok: false }` and the client handles them the same way. diff --git a/packages/framework/src/dashboard-rpc/preferences.telefunc.test.ts b/packages/framework/src/dashboard-rpc/preferences.telefunc.test.ts index f580400e..d9c7eaf3 100644 --- a/packages/framework/src/dashboard-rpc/preferences.telefunc.test.ts +++ b/packages/framework/src/dashboard-rpc/preferences.telefunc.test.ts @@ -1,6 +1,8 @@ import { strict as assert } from 'node:assert' import { test } from 'node:test' +import { provideTelefuncContext } from 'telefunc' import { onPreferences, savePreferences } from './preferences.telefunc.js' +import type { PreferencesStore } from '../registry.js' // Outside a Telefunc `serve({ context })` there is no preferences store on the context — the // same situation as the public relay, which never wires one. The RPCs must degrade safely: a @@ -15,3 +17,15 @@ test('savePreferences with no store is a not-enabled no-op', async () => { const result = await savePreferences({ autopilot: false }) assert.deepEqual(result, { ok: false, error: 'preferences are not enabled on this server' }) }) + +test('savePreferences returns the typed error when the store write fails, not a rejection', async () => { + const store: PreferencesStore = { + read: async () => ({}), + save: async () => { + throw new Error('disk full') + }, + } + provideTelefuncContext({ preferences: store }) + const result = await savePreferences({ autopilot: true }) + assert.deepEqual(result, { ok: false, error: 'failed to save preferences' }) +}) diff --git a/packages/framework/src/dashboard-rpc/preferences.telefunc.ts b/packages/framework/src/dashboard-rpc/preferences.telefunc.ts index a3f05acb..dca97dc9 100644 --- a/packages/framework/src/dashboard-rpc/preferences.telefunc.ts +++ b/packages/framework/src/dashboard-rpc/preferences.telefunc.ts @@ -22,6 +22,12 @@ export async function onPreferences(): Promise { export async function savePreferences(preferences: Preferences): Promise { const store = contextPreferences() if (!store) return { ok: false, error: 'preferences are not enabled on this server' } - await store.save(preferences) - return { ok: true } + // A failed write returns the advertised typed error rather than rejecting the RPC, + // so the client handles it the same as the not-enabled case (both `{ ok: false }`). + try { + await store.save(preferences) + return { ok: true } + } catch { + return { ok: false, error: 'failed to save preferences' } + } }