diff --git a/packages/devframe/src/client/rpc-shared-state.test.ts b/packages/devframe/src/client/rpc-shared-state.test.ts new file mode 100644 index 0000000..099ccfb --- /dev/null +++ b/packages/devframe/src/client/rpc-shared-state.test.ts @@ -0,0 +1,36 @@ +import { createEventEmitter } from 'devframe/utils/events' +import { describe, expect, it } from 'vitest' +import { createRpcSharedStateClientHost } from './rpc-shared-state' + +function makeFakeRpc() { + const events = createEventEmitter() + const setCalls: any[][] = [] + const rpc = { + connectionMeta: { backend: 'websocket' }, + isTrusted: false, + events, + client: { register: () => {} }, + callEvent: (name: string, ...args: any[]) => { + if (name === 'devframe:rpc:server-state:set') + setCalls.push(args) + }, + call: async () => undefined, + } as any + return { rpc, events, setCalls } +} + +describe('client shared state', () => { + it('registers the server-sync bridge once across repeated trust flips', async () => { + const { rpc, events, setCalls } = makeFakeRpc() + const host = createRpcSharedStateClientHost(rpc) + const state = await host.get('k', { initialValue: { a: 1 } }) + + events.emit('rpc:is-trusted:updated', true) + events.emit('rpc:is-trusted:updated', true) // second flip must not re-register + + state.mutate((d: any) => { + d.a = 2 + }) + expect(setCalls).toHaveLength(1) // exactly one server-state:set, not two + }) +}) diff --git a/packages/devframe/src/client/rpc-shared-state.ts b/packages/devframe/src/client/rpc-shared-state.ts index 09bb4af..dcbd687 100644 --- a/packages/devframe/src/client/rpc-shared-state.ts +++ b/packages/devframe/src/client/rpc-shared-state.ts @@ -114,8 +114,10 @@ export function createRpcSharedStateClientHost(rpc: DevframeRpcClient): RpcShare return new Promise>((resolve) => { if (!rpc.isTrusted) { resolve(state) + let initialized = false rpc.events.on('rpc:is-trusted:updated', (isTrusted) => { - if (isTrusted) { + if (isTrusted && !initialized) { + initialized = true initSharedState() } }) diff --git a/packages/devframe/src/client/rpc.ts b/packages/devframe/src/client/rpc.ts index db34c5b..11b1911 100644 --- a/packages/devframe/src/client/rpc.ts +++ b/packages/devframe/src/client/rpc.ts @@ -316,14 +316,11 @@ export async function getDevframeRpcClient( async onRequest(req, next, resolve) { await rpcOptions.onRequest?.call(this, req, next, resolve) if (cacheOptions && cacheManager?.validate(req.m)) { - const cached = cacheManager.cached(req.m, req.a) - if (cached) { - return resolve(cached) - } - else { - const res = await next(req) - cacheManager?.apply(req, res) + if (cacheManager.has(req.m, req.a)) { + return resolve(cacheManager.cached(req.m, req.a)) } + const res = await next(req) + cacheManager.apply(req, res) } else { await next(req) diff --git a/packages/devframe/src/node/__tests__/storage.test.ts b/packages/devframe/src/node/__tests__/storage.test.ts index 49f511b..7dc252e 100644 --- a/packages/devframe/src/node/__tests__/storage.test.ts +++ b/packages/devframe/src/node/__tests__/storage.test.ts @@ -40,4 +40,67 @@ describe('createStorage', () => { fs.rmSync(dir, { recursive: true, force: true }) } }) + + it('writes atomically via temp file + rename', async () => { + const dir = fs.mkdtempSync(join(os.tmpdir(), 'devframe-storage-')) + const filepath = join(dir, 'state.json') + + try { + const state = createStorage({ + filepath, + initialValue: { count: 1 }, + debounce: 0, + }) + + state.mutate((draft) => { + draft.count = 2 + }) + + await wait(20) + + // no leftover temp file + const entries = fs.readdirSync(dir) + expect(entries).toEqual(['state.json']) + + const saved = JSON.parse(fs.readFileSync(filepath, 'utf-8')) + expect(saved).toEqual({ count: 2 }) + } + finally { + fs.rmSync(dir, { recursive: true, force: true }) + } + }) + + it('reports a write failure instead of throwing', async () => { + const dir = fs.mkdtempSync(join(os.tmpdir(), 'devframe-storage-')) + const filepath = join(dir, 'state.json') + + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + const renameSpy = vi.spyOn(fs, 'renameSync').mockImplementation(() => { + throw new Error('EACCES: permission denied') + }) + + try { + const state = createStorage({ + filepath, + initialValue: { count: 1 }, + debounce: 0, + }) + + expect(() => { + state.mutate((draft) => { + draft.count = 2 + }) + }).not.toThrow() + + await wait(20) + + expect(errorSpy).toHaveBeenCalled() + expect(fs.existsSync(filepath)).toBe(false) + } + finally { + renameSpy.mockRestore() + errorSpy.mockRestore() + fs.rmSync(dir, { recursive: true, force: true }) + } + }) }) diff --git a/packages/devframe/src/node/diagnostics.ts b/packages/devframe/src/node/diagnostics.ts index e71beb8..458300a 100644 --- a/packages/devframe/src/node/diagnostics.ts +++ b/packages/devframe/src/node/diagnostics.ts @@ -64,5 +64,9 @@ export const diagnostics = defineDiagnostics({ `Scoped RPC registration for namespace "${p.namespace}" received an already-namespaced function name "${p.name}".`, fix: 'A scoped context auto-namespaces ids. Pass a bare name without a ":" separator (e.g. `register({ name: "get-cwd" })`), or use the unscoped `ctx.base.rpc.register` for a fully-qualified name.', }, + DF0035: { + why: (p: { filepath: string }) => `Failed to persist storage file: ${p.filepath}`, + fix: 'Check that the storage directory is writable and has free space.', + }, }, }) diff --git a/packages/devframe/src/node/storage.ts b/packages/devframe/src/node/storage.ts index 507b035..40a4ab6 100644 --- a/packages/devframe/src/node/storage.ts +++ b/packages/devframe/src/node/storage.ts @@ -1,4 +1,5 @@ import fs from 'node:fs' +import process from 'node:process' import { destr } from 'destr' import { createSharedState } from 'devframe/utils/shared-state' import { dirname } from 'pathe' @@ -39,8 +40,16 @@ export function createStorage(options: CreateStorageOptions state.on( 'updated', debounce((newState) => { - fs.mkdirSync(dirname(options.filepath), { recursive: true }) - fs.writeFileSync(options.filepath, `${JSON.stringify(newState, null, 2)}\n`) + try { + const dir = dirname(options.filepath) + fs.mkdirSync(dir, { recursive: true }) + const tmp = `${options.filepath}.${process.pid}.tmp` + fs.writeFileSync(tmp, `${JSON.stringify(newState, null, 2)}\n`) + fs.renameSync(tmp, options.filepath) // atomic replace on same filesystem + } + catch (error) { + diagnostics.DF0035({ filepath: options.filepath, cause: error }, { method: 'error' }) + } }, debounceTime), ) diff --git a/packages/devframe/src/rpc/cache.test.ts b/packages/devframe/src/rpc/cache.test.ts index bdc1dcb..3116f42 100644 --- a/packages/devframe/src/rpc/cache.test.ts +++ b/packages/devframe/src/rpc/cache.test.ts @@ -19,3 +19,46 @@ it('cache', async () => { cache.clear() expect(cache.cached('fn2', [3, 4])).toBeUndefined() }) + +it('serves falsy cached values via `has` (presence, not truthiness)', () => { + const cache = new RpcCacheManager({ functions: ['fn'] }) + + // absent key: `has` reports false, distinct from a stored falsy value + expect(cache.has('fn', ['zero'])).toBe(false) + + cache.apply({ m: 'fn', a: ['zero'] }, 0) + cache.apply({ m: 'fn', a: ['false'] }, false) + cache.apply({ m: 'fn', a: ['empty'] }, '') + cache.apply({ m: 'fn', a: ['null'] }, null) + + expect(cache.has('fn', ['zero'])).toBe(true) + expect(cache.has('fn', ['false'])).toBe(true) + expect(cache.has('fn', ['empty'])).toBe(true) + expect(cache.has('fn', ['null'])).toBe(true) + + expect(cache.cached('fn', ['zero'])).toBe(0) + expect(cache.cached('fn', ['false'])).toBe(false) + expect(cache.cached('fn', ['empty'])).toBe('') + expect(cache.cached('fn', ['null'])).toBe(null) +}) + +it('the client cache path (has-then-cached) serves a falsy value without a second fetch', async () => { + const cache = new RpcCacheManager({ functions: ['fn'] }) + let nextCalls = 0 + const next = async (req: { m: string, a: unknown[] }) => { + nextCalls++ + cache.apply(req, 0) + return 0 + } + + // mirrors the onRequest cache path in client/rpc.ts + async function callThroughCache(req: { m: string, a: unknown[] }) { + if (cache.has(req.m, req.a)) + return cache.cached(req.m, req.a) + return next(req) + } + + expect(await callThroughCache({ m: 'fn', a: [] })).toBe(0) + expect(await callThroughCache({ m: 'fn', a: [] })).toBe(0) + expect(nextCalls).toBe(1) // second call served from cache, not re-fetched +}) diff --git a/packages/devframe/src/rpc/cache.ts b/packages/devframe/src/rpc/cache.ts index f690cc8..98adcf5 100644 --- a/packages/devframe/src/rpc/cache.ts +++ b/packages/devframe/src/rpc/cache.ts @@ -33,6 +33,10 @@ export class RpcCacheManager { return undefined } + has(m: string, a: unknown[]): boolean { + return this.cacheMap.get(m)?.has(this.keySerializer(a)) ?? false + } + apply(req: { m: string, a: unknown[] }, res: unknown): void { const methodCache = this.cacheMap.get(req.m) || new Map() methodCache.set(this.keySerializer(req.a), res) diff --git a/plans/010-client-sharedstate-listener-leak.md b/plans/010-client-sharedstate-listener-leak.md deleted file mode 100644 index fbad923..0000000 --- a/plans/010-client-sharedstate-listener-leak.md +++ /dev/null @@ -1,203 +0,0 @@ -# Plan 010: Stop client shared-state from re-initializing (and double-writing) on every trust flip - -> **Executor instructions**: Follow this plan step by step. Run every -> verification command and confirm the expected result before moving on. If a -> STOP condition occurs, stop and report. When done, update this plan's row in -> `plans/README.md` unless a reviewer told you they maintain the index. -> -> **Drift check (run first)**: `git diff --stat 610a7b0..HEAD -- packages/devframe/src/client/rpc-shared-state.ts` -> On any change since this plan was written, compare against the "Current state" -> excerpt before proceeding; on a mismatch, STOP. - -## Status - -- **Priority**: P2 -- **Effort**: S -- **Risk**: MED (must not break first-init or genuine resubscribe) -- **Depends on**: none -- **Category**: bug -- **Planned at**: commit `610a7b0`, 2026-07-06 - -## Why this matters - -When a client is untrusted, `get(key)` resolves immediately and registers a -`rpc.events.on('rpc:is-trusted:updated', …)` listener that calls -`initSharedState()` on **every** `true`. Trust flips to `true` more than once in -practice (initial `requestTrust`, cross-tab auth channel, code exchange), and -`initSharedState()` re-runs `registerSharedState(key, state)` each time — which -adds another `state.on('updated', …)` bridge. So after N trust re-emits, one -local mutation fires N duplicate `server-state:set`/`patch` calls to the server, -and the per-`get` trust listeners never unsubscribe. - -## Current state - -`packages/devframe/src/client/rpc-shared-state.ts:43-61` — `registerSharedState` -adds an `updated` bridge that emits `server-state:set`/`patch`: - -```ts -function registerSharedState(key: string, state: SharedState) { - const offs: (() => void)[] = [] - offs.push(state.on('updated', (fullState, patches, syncId) => { - if (isStaticBackend) return - if (patches) rpc.callEvent('devframe:rpc:server-state:patch', key, patches, syncId) - else rpc.callEvent('devframe:rpc:server-state:set', key, fullState, syncId) - })) - return () => { for (const off of offs) off() } -} -``` - -`get` (`:71-127`), untrusted branch: - -```ts -return new Promise>((resolve) => { - if (!rpc.isTrusted) { - resolve(state) - rpc.events.on('rpc:is-trusted:updated', (isTrusted) => { - if (isTrusted) { - initSharedState() // ← re-runs on every trust flip → duplicate bridges - } - }) - } - else { - initSharedState().then(resolve) - } -}) -``` - -`get` early-returns a cached state for a known key (`:75-77`), so the only -re-entry into `initSharedState` for a key is via this trust listener. - -## Commands you will need - -| Purpose | Command | Expected | -|---|---|---| -| Install | `pnpm install` | exit 0 | -| Test | `pnpm exec vitest run packages/devframe/src/client/rpc-shared-state.test.ts` | all pass | -| Typecheck | `pnpm --filter devframe typecheck` | exit 0 | -| Lint | `pnpm lint` | exit 0 | - -## Scope - -**In scope**: -- `packages/devframe/src/client/rpc-shared-state.ts` -- `packages/devframe/src/client/rpc-shared-state.test.ts` (create) - -**Out of scope**: the server host (`node/rpc-shared-state.ts`) and the trust/auth -flow. Keep the fix inside the client host. - -## Git workflow - -- Branch: `advisor/010-client-sharedstate-listener-leak`. -- Commit style: `fix(client): initialize shared state once per key across trust flips`. -- Do NOT push or open a PR unless instructed. - -## Steps - -### Step 1: Guard init to run once per key - -Make the trust-triggered init idempotent so the `updated` bridge is registered -exactly once per key: - -```ts -return new Promise>((resolve) => { - if (!rpc.isTrusted) { - resolve(state) - let initialized = false - rpc.events.on('rpc:is-trusted:updated', (isTrusted) => { - if (isTrusted && !initialized) { - initialized = true - initSharedState() - } - }) - } - else { - initSharedState().then(resolve) - } -}) -``` - -(Optional defense-in-depth: also track a `registeredBridges = new Set()` -in the host closure and skip `registerSharedState(key, state)` if the key is -already present — so even a future second call can't double-register.) - -### Step 2: Test - -Create `packages/devframe/src/client/rpc-shared-state.test.ts` with a fake `rpc`: - -```ts -import { createEventEmitter } from 'devframe/utils/events' -import { describe, expect, it } from 'vitest' -import { createRpcSharedStateClientHost } from './rpc-shared-state' - -function makeFakeRpc() { - const events = createEventEmitter() - const setCalls: any[][] = [] - const rpc = { - connectionMeta: { backend: 'websocket' }, - isTrusted: false, - events, - client: { register: () => {} }, - callEvent: (name: string, ...args: any[]) => { - if (name === 'devframe:rpc:server-state:set') setCalls.push(args) - }, - call: async () => undefined, - } as any - return { rpc, events, setCalls } -} - -describe('client shared state', () => { - it('registers the server-sync bridge once across repeated trust flips', async () => { - const { rpc, events, setCalls } = makeFakeRpc() - const host = createRpcSharedStateClientHost(rpc) - const state = await host.get('k', { initialValue: { a: 1 } }) - - events.emit('rpc:is-trusted:updated', true) - events.emit('rpc:is-trusted:updated', true) // second flip must not re-register - - state.mutate((d: any) => { d.a = 2 }) - expect(setCalls).toHaveLength(1) // exactly one server-state:set, not two - }) -}) -``` - -Adjust member names to whatever `createRpcSharedStateClientHost` actually reads -(the essentials are `connectionMeta.backend`, `isTrusted`, `events`, `client.register`, -`callEvent`, `call`). If the real type needs more members to satisfy the compiler, -add `as any` stubs — the assertion (one emit, not two) is the point. - -**Verify**: `pnpm exec vitest run packages/devframe/src/client/rpc-shared-state.test.ts` -→ passes; before the Step 1 fix it would record 2 calls. - -## Test plan - -- New test: two trust flips + one mutate → exactly one `server-state:set`. -- `pnpm --filter devframe typecheck` + `pnpm lint` clean. -- Run any adjacent client tests to be safe: `pnpm exec vitest run packages/devframe/src/client`. - -## Done criteria - -- [ ] `initSharedState` runs at most once per key regardless of trust re-emits. -- [ ] The new test asserts exactly one `server-state:set` after two trust flips + one mutate, and passes. -- [ ] `pnpm exec vitest run packages/devframe/src/client` passes. -- [ ] `pnpm --filter devframe typecheck` + `pnpm lint` exit 0. -- [ ] Only the 2 in-scope files changed. -- [ ] `plans/README.md` status row updated. - -## STOP conditions - -Stop and report if: - -- A reconnect mechanism is discovered that *relies* on `initSharedState` re-running - on trust flips to resubscribe (this WS transport has no reconnect today — see - the finding for plan 011 — but if one exists, the fix must resubscribe without - re-registering the bridge; report before changing). -- `createRpcSharedStateClientHost` can't be constructed with a minimal fake rpc - (its dependencies are heavier than listed) — report what else it needs. - -## Maintenance notes - -- If reconnect is added later, separate "initialize once" (bridge + map) from - "(re)subscribe" (send `server-state:subscribe` + refetch) so reconnect - resubscribes without duplicating the bridge. -- Reviewer: confirm first init still happens exactly once and the `updated` - bridge isn't registered twice. diff --git a/plans/012-atomic-storage-write.md b/plans/012-atomic-storage-write.md deleted file mode 100644 index 925317c..0000000 --- a/plans/012-atomic-storage-write.md +++ /dev/null @@ -1,171 +0,0 @@ -# Plan 012: Make `createStorage` writes atomic and non-throwing - -> **Executor instructions**: Follow this plan step by step. Run every -> verification command and confirm the expected result before moving on. If a -> STOP condition occurs, stop and report. When done, update this plan's row in -> `plans/README.md` unless a reviewer told you they maintain the index. -> -> **Drift check (run first)**: `git diff --stat 610a7b0..HEAD -- packages/devframe/src/node/storage.ts packages/devframe/src/node/diagnostics.ts` -> On any change since this plan was written, compare against the "Current state" -> excerpt before proceeding; on a mismatch, STOP. - -## Status - -- **Priority**: P2 -- **Effort**: M -- **Risk**: LOW-MED -- **Depends on**: none -- **Category**: bug (data integrity) -- **Planned at**: commit `610a7b0`, 2026-07-06 - -## Why this matters - -`createStorage` persists state with a debounced `fs.writeFileSync` that has no -`try/catch` and is not atomic. A crash mid-`writeFileSync` truncates/corrupts the -JSON file, and a write error (EACCES/ENOSPC) throws **uncaught inside the -debounce timer**. The read path already tolerates a bad file (it warns and falls -back to defaults, `storage.ts:22-31`), so a corrupt write degrades to "settings -reset" — but a torn file and an uncaught async throw are avoidable with a -temp-file+rename write and error handling. - -## Current state - -`packages/devframe/src/node/storage.ts:38-45`: - -```ts -// throttle the write to the file -state.on( - 'updated', - debounce((newState) => { - fs.mkdirSync(dirname(options.filepath), { recursive: true }) - fs.writeFileSync(options.filepath, `${JSON.stringify(newState, null, 2)}\n`) - }, debounceTime), -) -``` - -Imports at top: `fs` (`node:fs`), `destr`, `createSharedState`, `dirname` -(`pathe`), `debounce` (`perfect-debounce`), `diagnostics` (`./diagnostics`). - -Diagnostics live in `packages/devframe/src/node/diagnostics.ts` (codes -DF0006–DF0034 with gaps). Reported (non-thrown) diagnostics are called like -`diagnostics.DF0012({ filepath, cause: error }, { method: 'warn' })` (see the -read path, `storage.ts:28`). - -## Commands you will need - -| Purpose | Command | Expected | -|---|---|---| -| Install | `pnpm install` | exit 0 | -| Test | `pnpm exec vitest run packages/devframe/src/node/__tests__/storage.test.ts` | all pass incl. new cases | -| Typecheck | `pnpm --filter devframe typecheck` | exit 0 | -| Lint | `pnpm lint` | exit 0 | - -## Scope - -**In scope**: -- `packages/devframe/src/node/storage.ts` -- `packages/devframe/src/node/diagnostics.ts` (add one code) -- `packages/devframe/src/node/__tests__/storage.test.ts` (new cases) - -**Out of scope**: changing the `createStorage` return type / adding a public -`flush()` API (that's a larger change — see maintenance notes). Do not register -global `process` exit handlers here (conflicts with the headless-by-default -principle unless the maintainer opts in). - -## Git workflow - -- Branch: `advisor/012-atomic-storage-write`. -- Commit style: `fix(storage): write atomically via temp+rename and report write failures`. -- Do NOT push or open a PR unless instructed. - -## Steps - -### Step 1: Add a diagnostic code - -In `node/diagnostics.ts`, add the next available `DF00xx` code (check the file; -if `DF0035` is unused, use it) for a storage write failure: - -```ts -DF0035: { - why: (p: { filepath: string }) => `Failed to persist storage file: ${p.filepath}`, - fix: 'Check that the storage directory is writable and has free space.', -}, -``` - -### Step 2: Atomic, guarded write - -Rewrite the debounced writer in `storage.ts`. Import `process` (`node:process`) -for a pid-scoped temp name: - -```ts -state.on( - 'updated', - debounce((newState) => { - try { - const dir = dirname(options.filepath) - fs.mkdirSync(dir, { recursive: true }) - const tmp = `${options.filepath}.${process.pid}.tmp` - fs.writeFileSync(tmp, `${JSON.stringify(newState, null, 2)}\n`) - fs.renameSync(tmp, options.filepath) // atomic replace on same filesystem - } - catch (error) { - diagnostics.DF0035({ filepath: options.filepath, cause: error }, { method: 'error' }) - } - }, debounceTime), -) -``` - -`rename` on the same directory is atomic on POSIX and Windows, so a reader never -sees a half-written file. - -### Step 3: Tests - -Add to `storage.test.ts` (model after the existing cases there): - -1. **Round-trips atomically**: create storage in a temp dir with a small - `debounce` (e.g. `debounce: 0` or `1`), mutate, wait for the write, then read - the file back and assert it parses and matches the mutated state. -2. **A write failure does not throw**: point `filepath` at an un-writable - location (e.g. a path whose parent is a file, or use `vi.spyOn(fs, 'renameSync')` - / `'writeFileSync'` to throw once), mutate, and assert no unhandled rejection - /throw escapes (the diagnostic is reported instead). Restore the spy after. - -**Verify**: `pnpm exec vitest run packages/devframe/src/node/__tests__/storage.test.ts` -→ all pass. - -## Test plan - -- New: atomic round-trip produces valid JSON matching state; an injected write - error is swallowed into a diagnostic (no throw). -- Existing storage tests keep passing (read-tolerance behavior unchanged). -- `pnpm --filter devframe typecheck` + `pnpm lint` clean. - -## Done criteria - -- [ ] A `DF00xx` storage-write-failure diagnostic exists in `node/diagnostics.ts`. -- [ ] The writer uses temp-file + `renameSync` and wraps the write in `try/catch` reporting that diagnostic. -- [ ] New tests pass; existing storage tests still pass. -- [ ] `pnpm --filter devframe typecheck` + `pnpm lint` exit 0. -- [ ] Only the 3 in-scope files changed. -- [ ] `plans/README.md` status row updated. - -## STOP conditions - -Stop and report if: - -- The chosen `DF00xx` code is already defined (pick the next free one and note it). -- `renameSync` across the temp and target reveals a cross-device issue in the test - environment (keep the temp file in the *same directory* as the target to avoid - EXDEV — the snippet does this). - -## Maintenance notes - -- **Deferred**: flush-on-exit (a mutation within the trailing debounce window is - still lost if the process exits before the timer fires). Fixing that cleanly - needs a `flush()` on the storage handle + a caller-owned shutdown hook — a - larger, opt-in change kept out of scope to respect headless-by-default. Note it - for a follow-up. -- Reviewer: confirm the temp file shares the target's directory (atomic rename) - and that the error path reports rather than throws. -- When `docs/` gains an error page for the new code, add `docs/errors/DF00xx.md` - per the template (see plan 020's convention). diff --git a/plans/025-rpc-cache-falsy.md b/plans/025-rpc-cache-falsy.md deleted file mode 100644 index 5509e85..0000000 --- a/plans/025-rpc-cache-falsy.md +++ /dev/null @@ -1,141 +0,0 @@ -# Plan 025: Make the client RPC response cache serve falsy values - -> **Executor instructions**: Follow this plan step by step. If a STOP condition -> occurs, stop and report. When done, update this plan's row in `plans/README.md` -> unless a reviewer told you they maintain the index. -> -> **Drift check (run first)**: `git diff --stat 610a7b0..HEAD -- packages/devframe/src/rpc/cache.ts packages/devframe/src/rpc/cache.test.ts packages/devframe/src/client/rpc.ts` -> On a mismatch with "Current state", STOP. - -## Status - -- **Priority**: P3 -- **Effort**: S -- **Risk**: LOW -- **Depends on**: none -- **Category**: bug (experimental API) -- **Planned at**: commit `610a7b0`, 2026-07-06 - -## Why this matters - -The client RPC response cache checks `if (cached)` — a truthiness test — so a -legitimately cached `0` / `''` / `false` / `null` reads as a miss and is -re-fetched every call. The cache is `@experimental`, so severity is low, but the -fix is a one-liner presence check that makes the cache correct for all value -types. - -## Current state - -`packages/devframe/src/rpc/cache.ts:28-40`: - -```ts -cached(m: string, a: unknown[]): T | undefined { - const methodCache = this.cacheMap.get(m) - if (methodCache) return methodCache.get(this.keySerializer(a)) as T - return undefined -} -apply(req, res): void { /* sets methodCache.set(key, res) */ } -``` - -`packages/devframe/src/client/rpc.ts:316-331` (the cache path in `onRequest`): - -```ts -async onRequest(req, next, resolve) { - await rpcOptions.onRequest?.call(this, req, next, resolve) - if (cacheOptions && cacheManager?.validate(req.m)) { - const cached = cacheManager.cached(req.m, req.a) - if (cached) { // ← falsy cached values re-fetch - return resolve(cached) - } - else { - const res = await next(req) - cacheManager?.apply(req, res) - } - } - else { - await next(req) - } -} -``` - -Test file: `packages/devframe/src/rpc/cache.test.ts`. - -## Commands you will need - -| Purpose | Command | Expected | -|---|---|---| -| Test | `pnpm exec vitest run packages/devframe/src/rpc/cache.test.ts` | all pass incl. new case | -| Typecheck | `pnpm --filter devframe typecheck` | exit 0 | -| Lint | `pnpm lint` | exit 0 | - -## Scope - -**In scope**: -- `packages/devframe/src/rpc/cache.ts` (add a `has` presence check) -- `packages/devframe/src/client/rpc.ts` (use presence instead of truthiness) -- `packages/devframe/src/rpc/cache.test.ts` (falsy-value case) - -**Out of scope**: redesigning the cache eviction/TTL; the broader `onRequest` -composition (see STOP for the double-`next` note). - -## Git workflow - -- Branch: `advisor/025-rpc-cache-falsy`. -- Commit style: `fix(rpc): cache falsy RPC results (presence check, not truthiness)`. - -## Steps - -### Step 1: Add `has` to the cache manager - -```ts -has(m: string, a: unknown[]): boolean { - return this.cacheMap.get(m)?.has(this.keySerializer(a)) ?? false -} -``` - -### Step 2: Use presence in the client cache path - -```ts -if (cacheOptions && cacheManager?.validate(req.m)) { - if (cacheManager.has(req.m, req.a)) { - return resolve(cacheManager.cached(req.m, req.a)) - } - const res = await next(req) - cacheManager.apply(req, res) -} -else { - await next(req) -} -``` - -### Step 3: Test falsy caching - -Add to `cache.test.ts`: `apply` a `0` (and a `false`/`''`) result, then assert -`has` is `true` and the client path serves it without calling `next` a second -time (use a spy on `next` / a counting fake). - -**Verify**: `pnpm exec vitest run packages/devframe/src/rpc/cache.test.ts` → pass. - -## Done criteria - -- [ ] `RpcCacheManager.has` exists and the client uses it instead of `if (cached)`. -- [ ] A cached `0`/`false`/`''` is served (not re-fetched); new test proves it. -- [ ] `pnpm exec vitest run packages/devframe/src/rpc/cache.test.ts` passes. -- [ ] `pnpm --filter devframe typecheck` + `pnpm lint` exit 0. -- [ ] Only the 3 in-scope files changed. -- [ ] `plans/README.md` status row updated. - -## STOP conditions - -Stop and report if: -- You confirm the secondary bug: `rpcOptions.onRequest?.call(...)` at `rpc.ts:317` - may already call `next`/`resolve` (birpc contract), and then this code calls - `next` again — a double-dispatch when a user hook is combined with - `cacheOptions`. If you can reproduce it, report it as a separate finding rather - than expanding this plan's scope. - -## Maintenance notes - -- Cache is `@experimental` (`cache.ts:9-10`); keep changes minimal. -- Reviewer: confirm the presence check distinguishes "stored undefined/null" from - "absent". diff --git a/plans/README.md b/plans/README.md index 4b35fb7..25d960e 100644 --- a/plans/README.md +++ b/plans/README.md @@ -22,9 +22,9 @@ changes are allowed as long as they're marked). | 007 | Behavioral tests for the auth/OTP trust boundary | tests | P1 | S | — | DONE | | 008 | Cap hub terminal buffer + reject restart-after-terminate | bug | P2 | S | — | TODO | | 009 | Fix two server-side streaming lifecycle leaks | bug | P2 | S-M | — | TODO | -| 010 | Initialize client shared state once across trust flips | bug | P2 | S | — | TODO | +| 010 | Initialize client shared state once across trust flips | bug | P2 | S | — | DONE | | 011 | Don't leak/drop when WS client posts on a closing socket | bug | P2 | S | — | DONE | -| 012 | Atomic, non-throwing `createStorage` writes | bug | P2 | M | — | TODO | +| 012 | Atomic, non-throwing `createStorage` writes | bug | P2 | M | — | DONE | | 013 | De-duplicate git parse helpers into `node/git.ts` | tech-debt | P2 | S | 004 | TODO | | 014 | Parallelize the `git:show` static dump | perf | P2 | S-M | 004, 013 | TODO | | 015 | Expire + bound the persisted trusted-token store | security | P2 | S | 007 (shared test) | TODO | @@ -37,7 +37,7 @@ changes are allowed as long as they're marked). | 022 | Memoize git commit-log rows | perf | P3 | M | — | TODO | | 023 | Single-pass MessagesView filtering | perf | P3 | M | — | TODO | | 024 | Batch stream-replay into one frame | perf | P3 | M | 009 | TODO | -| 025 | Cache falsy RPC results (presence check) | bug | P3 | S | — | TODO | +| 025 | Cache falsy RPC results (presence check) | bug | P3 | S | — | DONE | | 026 | Clear dev/docs-only dependency advisories | deps | P3 | S | 002 | TODO | | 027 | Spike: `@devframes/next` host-integration package | direction | P3 | M | — | TODO | | 028 | Spike: MCP-inspector + OG-viewer plugins | direction | P3 | L | — | TODO | diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts index f0a6751..6230f5d 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts @@ -12,7 +12,7 @@ export declare function defineJsonRenderSpec(_: JsonRenderSpec): JsonRenderSpec; // #endregion // #region Variables -export declare const defineHubRpcFunction: (definition: _$devframe_rpc0.RpcFunctionDefinition) => _$devframe_rpc0.RpcFunctionDefinition; +export declare const defineHubRpcFunction: (definition: import("devframe/rpc").RpcFunctionDefinition) => import("devframe/rpc").RpcFunctionDefinition; // #endregion // #region Other diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/node.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/hub/node.snapshot.d.ts index 2e15eaa..9af324f 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/node.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/hub/node.snapshot.d.ts @@ -103,13 +103,13 @@ export declare const hubCommandsExecute: { args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: DevframeHubContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, ...args: any[]], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: DevframeHubContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: ((id: string, ...args: any[]) => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[id: string, ...args: any[]], Promise, DevframeHubContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[id: string, ...args: any[]], Promise, DevframeHubContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, ...args: any[]], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }; export declare const hubMessagesAdd: { name: "hub:messages:add"; @@ -118,13 +118,13 @@ export declare const hubMessagesAdd: { args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: DevframeHubContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[input: DevframeMessageEntryInput], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: DevframeHubContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: ((input: DevframeMessageEntryInput) => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[input: DevframeMessageEntryInput], Promise, DevframeHubContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[input: DevframeMessageEntryInput], Promise, DevframeHubContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[input: DevframeMessageEntryInput], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }; export declare const hubMessagesClear: { name: "hub:messages:clear"; @@ -133,13 +133,13 @@ export declare const hubMessagesClear: { args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: DevframeHubContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: DevframeHubContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: (() => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[], Promise, DevframeHubContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[], Promise, DevframeHubContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }; export declare const hubMessagesRemove: { name: "hub:messages:remove"; @@ -148,13 +148,13 @@ export declare const hubMessagesRemove: { args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: DevframeHubContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: DevframeHubContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: ((id: string) => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[id: string], Promise, DevframeHubContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[id: string], Promise, DevframeHubContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }; export declare const hubMessagesUpdate: { name: "hub:messages:update"; @@ -163,13 +163,13 @@ export declare const hubMessagesUpdate: { args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: DevframeHubContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, patch: Partial], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: DevframeHubContext) => import("devframe/rpc").Thenable], Promise>>) | undefined; handler?: ((id: string, patch: Partial) => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[id: string, patch: Partial], Promise, DevframeHubContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[id: string, patch: Partial], Promise, DevframeHubContext> | undefined; snapshot?: boolean; - __cache?: WeakMap], Promise>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, patch: Partial], Promise>> | undefined; + __cache?: WeakMap], Promise>>> | undefined; + __promise?: import("devframe/rpc").Thenable], Promise>> | undefined; }; export declare const hubTerminalsResize: { name: "hub:terminals:resize"; @@ -178,13 +178,13 @@ export declare const hubTerminalsResize: { args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: DevframeHubContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, cols: number, rows: number], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: DevframeHubContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: ((id: string, cols: number, rows: number) => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[id: string, cols: number, rows: number], Promise, DevframeHubContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[id: string, cols: number, rows: number], Promise, DevframeHubContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, cols: number, rows: number], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }; export declare const hubTerminalsWrite: { name: "hub:terminals:write"; @@ -193,13 +193,13 @@ export declare const hubTerminalsWrite: { args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: DevframeHubContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, data: string], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: DevframeHubContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: ((id: string, data: string) => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[id: string, data: string], Promise, DevframeHubContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[id: string, data: string], Promise, DevframeHubContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, data: string], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }; // #endregion diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-code-server/node.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-code-server/node.snapshot.d.ts index e7ad65a..167dd7b 100644 --- a/tests/__snapshots__/tsnapi/@devframes/plugin-code-server/node.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-code-server/node.snapshot.d.ts @@ -50,7 +50,7 @@ export declare function setupCodeServer(_: DevframeNodeContext, _?: CodeServerOp // #endregion // #region Variables -export declare const diagnostics: _$nostics.Diagnostics<{ +export declare const diagnostics: import("nostics").Diagnostics<{ readonly DP_CODE_SERVER_0001: { readonly why: (p: { bin: string; diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-code-server/rpc.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-code-server/rpc.snapshot.d.ts index eb17eee..bd2917b 100644 --- a/tests/__snapshots__/tsnapi/@devframes/plugin-code-server/rpc.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-code-server/rpc.snapshot.d.ts @@ -9,13 +9,13 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: (() => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[], Promise, _$devframe.DevframeNodeContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[], Promise, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }, { name: "devframes-plugin-code-server:status"; type?: "query" | undefined; @@ -23,13 +23,13 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], CodeServerStatusResult>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>) | undefined; handler?: (() => CodeServerStatusResult) | undefined; - dump?: _$devframe_rpc0.RpcDump<[], CodeServerStatusResult, _$devframe.DevframeNodeContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[], CodeServerStatusResult, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], CodeServerStatusResult>> | undefined; + __cache?: WeakMap>> | undefined; + __promise?: import("devframe/rpc").Thenable> | undefined; }, { name: "devframes-plugin-code-server:start"; type?: "action" | undefined; @@ -37,13 +37,13 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[req?: CodeServerStartRequest | undefined], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: ((req?: CodeServerStartRequest | undefined) => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[req?: CodeServerStartRequest | undefined], Promise, _$devframe.DevframeNodeContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[req?: CodeServerStartRequest | undefined], Promise, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[req?: CodeServerStartRequest | undefined], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }, { name: "devframes-plugin-code-server:stop"; type?: "action" | undefined; @@ -51,12 +51,12 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], CodeServerStatusResult>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>) | undefined; handler?: (() => CodeServerStatusResult) | undefined; - dump?: _$devframe_rpc0.RpcDump<[], CodeServerStatusResult, _$devframe.DevframeNodeContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[], CodeServerStatusResult, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], CodeServerStatusResult>> | undefined; + __cache?: WeakMap>> | undefined; + __promise?: import("devframe/rpc").Thenable> | undefined; }]; // #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-messages/rpc.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-messages/rpc.snapshot.d.ts index 3a67558..eb5ffe4 100644 --- a/tests/__snapshots__/tsnapi/@devframes/plugin-messages/rpc.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-messages/rpc.snapshot.d.ts @@ -9,13 +9,13 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[since?: number | null | undefined], Promise<_$_devframes_hub_types0.DevframeMessagesListDelta>>>) | undefined; - handler?: ((since?: number | null | undefined) => Promise<_$_devframes_hub_types0.DevframeMessagesListDelta>) | undefined; - dump?: _$devframe_rpc0.RpcDump<[since?: number | null | undefined], Promise<_$_devframes_hub_types0.DevframeMessagesListDelta>, _$devframe.DevframeNodeContext> | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>>) | undefined; + handler?: ((since?: number | null | undefined) => Promise) | undefined; + dump?: import("devframe/rpc").RpcDump<[since?: number | null | undefined], Promise, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[since?: number | null | undefined], Promise<_$_devframes_hub_types0.DevframeMessagesListDelta>>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }, { name: "devframes-plugin-messages:add"; type?: "action" | undefined; @@ -23,13 +23,13 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[input: _$_devframes_hub_types0.DevframeMessageEntryInput], Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>>>) | undefined; - handler?: ((input: _$_devframes_hub_types0.DevframeMessageEntryInput) => Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>) | undefined; - dump?: _$devframe_rpc0.RpcDump<[input: _$_devframes_hub_types0.DevframeMessageEntryInput], Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>, _$devframe.DevframeNodeContext> | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>>) | undefined; + handler?: ((input: import("@devframes/hub/types").DevframeMessageEntryInput) => Promise) | undefined; + dump?: import("devframe/rpc").RpcDump<[input: import("@devframes/hub/types").DevframeMessageEntryInput], Promise, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[input: _$_devframes_hub_types0.DevframeMessageEntryInput], Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }, { name: "devframes-plugin-messages:update"; type?: "action" | undefined; @@ -37,13 +37,13 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, patch: Partial<_$_devframes_hub_types0.DevframeMessageEntryInput>], Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>>>) | undefined; - handler?: ((id: string, patch: Partial<_$_devframes_hub_types0.DevframeMessageEntryInput>) => Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>) | undefined; - dump?: _$devframe_rpc0.RpcDump<[id: string, patch: Partial<_$_devframes_hub_types0.DevframeMessageEntryInput>], Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>, _$devframe.DevframeNodeContext> | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable], Promise>>) | undefined; + handler?: ((id: string, patch: Partial) => Promise) | undefined; + dump?: import("devframe/rpc").RpcDump<[id: string, patch: Partial], Promise, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap], Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string, patch: Partial<_$_devframes_hub_types0.DevframeMessageEntryInput>], Promise<_$_devframes_hub_types0.DevframeMessageEntry | null>>> | undefined; + __cache?: WeakMap], Promise>>> | undefined; + __promise?: import("devframe/rpc").Thenable], Promise>> | undefined; }, { name: "devframes-plugin-messages:remove"; type?: "action" | undefined; @@ -51,13 +51,13 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: ((id: string) => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[id: string], Promise, _$devframe.DevframeNodeContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[id: string], Promise, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[id: string], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }, { name: "devframes-plugin-messages:clear"; type?: "action" | undefined; @@ -65,12 +65,12 @@ export declare const serverFunctions: readonly [{ args?: undefined; returns?: undefined; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], Promise>>) | undefined; + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>>) | undefined; handler?: (() => Promise) | undefined; - dump?: _$devframe_rpc0.RpcDump<[], Promise, _$devframe.DevframeNodeContext> | undefined; + dump?: import("devframe/rpc").RpcDump<[], Promise, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], Promise>> | undefined; + __cache?: WeakMap>>> | undefined; + __promise?: import("devframe/rpc").Thenable>> | undefined; }]; // #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-terminals/node.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-terminals/node.snapshot.d.ts index 7d5b158..c9e4c96 100644 --- a/tests/__snapshots__/tsnapi/@devframes/plugin-terminals/node.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-terminals/node.snapshot.d.ts @@ -48,7 +48,7 @@ export declare function setupTerminals(_: DevframeNodeContext, _?: TerminalsOpti // #endregion // #region Variables -export declare const diagnostics: _$nostics.Diagnostics<{ +export declare const diagnostics: import("nostics").Diagnostics<{ readonly DP_TERMINALS_0001: { readonly why: (p: { id: string; diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-terminals/rpc.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-terminals/rpc.snapshot.d.ts index 63ed480..4de51be 100644 --- a/tests/__snapshots__/tsnapi/@devframes/plugin-terminals/rpc.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-terminals/rpc.snapshot.d.ts @@ -7,29 +7,29 @@ export declare const serverFunctions: readonly [{ type?: "query" | undefined; cacheable?: boolean; args: readonly []; - returns: _$valibot.ArraySchema<_$valibot.ObjectSchema<{ - readonly id: _$valibot.StringSchema; - readonly title: _$valibot.StringSchema; - readonly processName: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly customTitle: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly mode: _$valibot.PicklistSchema<["interactive", "readonly"], undefined>; - readonly status: _$valibot.PicklistSchema<["running", "exited", "error"], undefined>; - readonly backend: _$valibot.PicklistSchema<["pty", "pipe"], undefined>; - readonly command: _$valibot.StringSchema; - readonly args: _$valibot.ArraySchema<_$valibot.StringSchema, undefined>; - readonly cwd: _$valibot.StringSchema; - readonly cols: _$valibot.NumberSchema; - readonly rows: _$valibot.NumberSchema; - readonly pid: _$valibot.OptionalSchema<_$valibot.NumberSchema, undefined>; - readonly exitCode: _$valibot.OptionalSchema<_$valibot.NumberSchema, undefined>; - readonly icon: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly channel: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly presetId: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly createdAt: _$valibot.NumberSchema; + returns: import("valibot").ArraySchema; + readonly title: import("valibot").StringSchema; + readonly processName: import("valibot").OptionalSchema, undefined>; + readonly customTitle: import("valibot").OptionalSchema, undefined>; + readonly mode: import("valibot").PicklistSchema<["interactive", "readonly"], undefined>; + readonly status: import("valibot").PicklistSchema<["running", "exited", "error"], undefined>; + readonly backend: import("valibot").PicklistSchema<["pty", "pipe"], undefined>; + readonly command: import("valibot").StringSchema; + readonly args: import("valibot").ArraySchema, undefined>; + readonly cwd: import("valibot").StringSchema; + readonly cols: import("valibot").NumberSchema; + readonly rows: import("valibot").NumberSchema; + readonly pid: import("valibot").OptionalSchema, undefined>; + readonly exitCode: import("valibot").OptionalSchema, undefined>; + readonly icon: import("valibot").OptionalSchema, undefined>; + readonly channel: import("valibot").OptionalSchema, undefined>; + readonly presetId: import("valibot").OptionalSchema, undefined>; + readonly createdAt: import("valibot").NumberSchema; }, undefined>, undefined>; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], { + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable | undefined; + }[], import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], { + __promise?: import("devframe/rpc").Thenable; - readonly title: _$valibot.StringSchema; - readonly command: _$valibot.StringSchema; - readonly args: _$valibot.ArraySchema<_$valibot.StringSchema, undefined>; - readonly mode: _$valibot.PicklistSchema<["interactive", "readonly"], undefined>; - readonly icon: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; + returns: import("valibot").ArraySchema; + readonly title: import("valibot").StringSchema; + readonly command: import("valibot").StringSchema; + readonly args: import("valibot").ArraySchema, undefined>; + readonly mode: import("valibot").PicklistSchema<["interactive", "readonly"], undefined>; + readonly icon: import("valibot").OptionalSchema, undefined>; }, undefined>, undefined>; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], { + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable | undefined; + }[], import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[], { + __promise?: import("devframe/rpc").Thenable, undefined>; - readonly command: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly args: _$valibot.OptionalSchema<_$valibot.ArraySchema<_$valibot.StringSchema, undefined>, undefined>; - readonly cwd: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly mode: _$valibot.OptionalSchema<_$valibot.PicklistSchema<["interactive", "readonly"], undefined>, undefined>; - readonly title: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly cols: _$valibot.OptionalSchema<_$valibot.NumberSchema, undefined>; - readonly rows: _$valibot.OptionalSchema<_$valibot.NumberSchema, undefined>; - readonly env: _$valibot.OptionalSchema<_$valibot.RecordSchema<_$valibot.StringSchema, _$valibot.StringSchema, undefined>, undefined>; + args: readonly [import("valibot").ObjectSchema<{ + readonly presetId: import("valibot").OptionalSchema, undefined>; + readonly command: import("valibot").OptionalSchema, undefined>; + readonly args: import("valibot").OptionalSchema, undefined>, undefined>; + readonly cwd: import("valibot").OptionalSchema, undefined>; + readonly mode: import("valibot").OptionalSchema, undefined>; + readonly title: import("valibot").OptionalSchema, undefined>; + readonly cols: import("valibot").OptionalSchema, undefined>; + readonly rows: import("valibot").OptionalSchema, undefined>; + readonly env: import("valibot").OptionalSchema, import("valibot").StringSchema, undefined>, undefined>; }, undefined>]; - returns: _$valibot.ObjectSchema<{ - readonly id: _$valibot.StringSchema; - readonly title: _$valibot.StringSchema; - readonly processName: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly customTitle: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly mode: _$valibot.PicklistSchema<["interactive", "readonly"], undefined>; - readonly status: _$valibot.PicklistSchema<["running", "exited", "error"], undefined>; - readonly backend: _$valibot.PicklistSchema<["pty", "pipe"], undefined>; - readonly command: _$valibot.StringSchema; - readonly args: _$valibot.ArraySchema<_$valibot.StringSchema, undefined>; - readonly cwd: _$valibot.StringSchema; - readonly cols: _$valibot.NumberSchema; - readonly rows: _$valibot.NumberSchema; - readonly pid: _$valibot.OptionalSchema<_$valibot.NumberSchema, undefined>; - readonly exitCode: _$valibot.OptionalSchema<_$valibot.NumberSchema, undefined>; - readonly icon: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly channel: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly presetId: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly createdAt: _$valibot.NumberSchema; + returns: import("valibot").ObjectSchema<{ + readonly id: import("valibot").StringSchema; + readonly title: import("valibot").StringSchema; + readonly processName: import("valibot").OptionalSchema, undefined>; + readonly customTitle: import("valibot").OptionalSchema, undefined>; + readonly mode: import("valibot").PicklistSchema<["interactive", "readonly"], undefined>; + readonly status: import("valibot").PicklistSchema<["running", "exited", "error"], undefined>; + readonly backend: import("valibot").PicklistSchema<["pty", "pipe"], undefined>; + readonly command: import("valibot").StringSchema; + readonly args: import("valibot").ArraySchema, undefined>; + readonly cwd: import("valibot").StringSchema; + readonly cols: import("valibot").NumberSchema; + readonly rows: import("valibot").NumberSchema; + readonly pid: import("valibot").OptionalSchema, undefined>; + readonly exitCode: import("valibot").OptionalSchema, undefined>; + readonly icon: import("valibot").OptionalSchema, undefined>; + readonly channel: import("valibot").OptionalSchema, undefined>; + readonly presetId: import("valibot").OptionalSchema, undefined>; + readonly createdAt: import("valibot").NumberSchema; }, undefined>; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable | undefined; + }, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + __promise?: import("devframe/rpc").Thenable; - readonly data: _$valibot.StringSchema; + args: readonly [import("valibot").ObjectSchema<{ + readonly id: import("valibot").StringSchema; + readonly data: import("valibot").StringSchema; }, undefined>]; - returns: _$valibot.VoidSchema; + returns: import("valibot").VoidSchema; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>) | undefined; @@ -403,16 +403,16 @@ export declare const serverFunctions: readonly [{ id: string; data: string; }) => void) | undefined; - dump?: _$devframe_rpc0.RpcDump<[{ + dump?: import("devframe/rpc").RpcDump<[{ id: string; data: string; - }], void, _$devframe.DevframeNodeContext> | undefined; + }], void, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + __promise?: import("devframe/rpc").Thenable> | undefined; @@ -420,15 +420,15 @@ export declare const serverFunctions: readonly [{ name: "devframes-plugin-terminals:resize"; type?: "action" | undefined; cacheable?: boolean; - args: readonly [_$valibot.ObjectSchema<{ - readonly id: _$valibot.StringSchema; - readonly cols: _$valibot.SchemaWithPipe, _$valibot.IntegerAction, _$valibot.MinValueAction]>; - readonly rows: _$valibot.SchemaWithPipe, _$valibot.IntegerAction, _$valibot.MinValueAction]>; + args: readonly [import("valibot").ObjectSchema<{ + readonly id: import("valibot").StringSchema; + readonly cols: import("valibot").SchemaWithPipe, import("valibot").IntegerAction, import("valibot").MinValueAction]>; + readonly rows: import("valibot").SchemaWithPipe, import("valibot").IntegerAction, import("valibot").MinValueAction]>; }, undefined>]; - returns: _$valibot.VoidSchema; + returns: import("valibot").VoidSchema; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable void) | undefined; - dump?: _$devframe_rpc0.RpcDump<[{ + dump?: import("devframe/rpc").RpcDump<[{ id: string; cols: number; rows: number; - }], void, _$devframe.DevframeNodeContext> | undefined; + }], void, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + __promise?: import("devframe/rpc").Thenable; + args: readonly [import("valibot").ObjectSchema<{ + readonly id: import("valibot").StringSchema; }, undefined>]; - returns: _$valibot.VoidSchema; + returns: import("valibot").VoidSchema; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>) | undefined; handler?: ((args_0: { id: string; }) => void) | undefined; - dump?: _$devframe_rpc0.RpcDump<[{ + dump?: import("devframe/rpc").RpcDump<[{ id: string; - }], void, _$devframe.DevframeNodeContext> | undefined; + }], void, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + __promise?: import("devframe/rpc").Thenable> | undefined; }, { name: "devframes-plugin-terminals:restart"; type?: "action" | undefined; cacheable?: boolean; - args: readonly [_$valibot.ObjectSchema<{ - readonly id: _$valibot.StringSchema; + args: readonly [import("valibot").ObjectSchema<{ + readonly id: import("valibot").StringSchema; }, undefined>]; - returns: _$valibot.ObjectSchema<{ - readonly id: _$valibot.StringSchema; - readonly title: _$valibot.StringSchema; - readonly processName: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly customTitle: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly mode: _$valibot.PicklistSchema<["interactive", "readonly"], undefined>; - readonly status: _$valibot.PicklistSchema<["running", "exited", "error"], undefined>; - readonly backend: _$valibot.PicklistSchema<["pty", "pipe"], undefined>; - readonly command: _$valibot.StringSchema; - readonly args: _$valibot.ArraySchema<_$valibot.StringSchema, undefined>; - readonly cwd: _$valibot.StringSchema; - readonly cols: _$valibot.NumberSchema; - readonly rows: _$valibot.NumberSchema; - readonly pid: _$valibot.OptionalSchema<_$valibot.NumberSchema, undefined>; - readonly exitCode: _$valibot.OptionalSchema<_$valibot.NumberSchema, undefined>; - readonly icon: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly channel: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly presetId: _$valibot.OptionalSchema<_$valibot.StringSchema, undefined>; - readonly createdAt: _$valibot.NumberSchema; + returns: import("valibot").ObjectSchema<{ + readonly id: import("valibot").StringSchema; + readonly title: import("valibot").StringSchema; + readonly processName: import("valibot").OptionalSchema, undefined>; + readonly customTitle: import("valibot").OptionalSchema, undefined>; + readonly mode: import("valibot").PicklistSchema<["interactive", "readonly"], undefined>; + readonly status: import("valibot").PicklistSchema<["running", "exited", "error"], undefined>; + readonly backend: import("valibot").PicklistSchema<["pty", "pipe"], undefined>; + readonly command: import("valibot").StringSchema; + readonly args: import("valibot").ArraySchema, undefined>; + readonly cwd: import("valibot").StringSchema; + readonly cols: import("valibot").NumberSchema; + readonly rows: import("valibot").NumberSchema; + readonly pid: import("valibot").OptionalSchema, undefined>; + readonly exitCode: import("valibot").OptionalSchema, undefined>; + readonly icon: import("valibot").OptionalSchema, undefined>; + readonly channel: import("valibot").OptionalSchema, undefined>; + readonly presetId: import("valibot").OptionalSchema, undefined>; + readonly createdAt: import("valibot").NumberSchema; }, undefined>; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable | undefined; + }, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + __promise?: import("devframe/rpc").Thenable; - readonly title: _$valibot.StringSchema; + args: readonly [import("valibot").ObjectSchema<{ + readonly id: import("valibot").StringSchema; + readonly title: import("valibot").StringSchema; }, undefined>]; - returns: _$valibot.VoidSchema; + returns: import("valibot").VoidSchema; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>) | undefined; @@ -639,16 +639,16 @@ export declare const serverFunctions: readonly [{ id: string; title: string; }) => void) | undefined; - dump?: _$devframe_rpc0.RpcDump<[{ + dump?: import("devframe/rpc").RpcDump<[{ id: string; title: string; - }], void, _$devframe.DevframeNodeContext> | undefined; + }], void, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + __promise?: import("devframe/rpc").Thenable> | undefined; @@ -656,26 +656,26 @@ export declare const serverFunctions: readonly [{ name: "devframes-plugin-terminals:remove"; type?: "action" | undefined; cacheable?: boolean; - args: readonly [_$valibot.ObjectSchema<{ - readonly id: _$valibot.StringSchema; + args: readonly [import("valibot").ObjectSchema<{ + readonly id: import("valibot").StringSchema; }, undefined>]; - returns: _$valibot.VoidSchema; + returns: import("valibot").VoidSchema; jsonSerializable?: boolean; - agent?: _$devframe.RpcFunctionAgentOptions; - setup?: ((context: _$devframe.DevframeNodeContext) => _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + agent?: import("devframe").RpcFunctionAgentOptions; + setup?: ((context: import("devframe").DevframeNodeContext) => import("devframe/rpc").Thenable>) | undefined; handler?: ((args_0: { id: string; }) => void) | undefined; - dump?: _$devframe_rpc0.RpcDump<[{ + dump?: import("devframe/rpc").RpcDump<[{ id: string; - }], void, _$devframe.DevframeNodeContext> | undefined; + }], void, import("devframe").DevframeNodeContext> | undefined; snapshot?: boolean; - __cache?: WeakMap>> | undefined; - __promise?: _$devframe_rpc0.Thenable<_$devframe_rpc0.RpcFunctionSetupResult<[{ + __promise?: import("devframe/rpc").Thenable> | undefined; }]; diff --git a/tests/__snapshots__/tsnapi/devframe/rpc.snapshot.js b/tests/__snapshots__/tsnapi/devframe/rpc.snapshot.js index ddf5283..f7ed893 100644 --- a/tests/__snapshots__/tsnapi/devframe/rpc.snapshot.js +++ b/tests/__snapshots__/tsnapi/devframe/rpc.snapshot.js @@ -9,6 +9,7 @@ export class RpcCacheManager { constructor(_) {} updateOptions(_) {} cached(_, _) {} + has(_, _) {} apply(_, _) {} validate(_) {} clear(_) {} @@ -31,11 +32,17 @@ export class RpcFunctionsCollectorBase { // #endregion // #region Variables +/** @deprecated */ export var collectStaticRpcDump /* const */ +/** @deprecated */ export var createClientFromDump /* const */ +/** @deprecated */ export var dumpFunctions /* const */ +/** @deprecated */ export var getDefinitionsWithDumps /* const */ +/** @deprecated */ export var reviveDumpError /* const */ +/** @deprecated */ export var serializeDumpError /* const */ // #endregion