Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ pnpm install # requires pnpm@11.x
pnpm build # tsdown
pnpm dev # tsdown --watch
pnpm test # pnpm build && vitest (api snapshot guards against stale dist)
pnpm typecheck # tsc --noEmit
pnpm typecheck # turbo run typecheck (per-package tsc --noEmit)
pnpm lint --fix # ESLint via @antfu/eslint-config
pnpm start # tsx src/index.ts
```

The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots compare against fresh `dist/`. `tsdown-stale-guard` enforces this in `test/api-snapshot.test.ts`.

`pnpm typecheck` fans out through Turbo: every workspace package owns a `"typecheck": "tsc --noEmit"` script and its own `tsconfig.json` (extending `tsconfig.base.json` with an explicit `include`). Cross-package imports resolve to source through the `paths` aliases in `tsconfig.base.json`, so no prior build is needed. Any package added under `packages/*` or `plugins/*` is typechecked automatically once it ships that `typecheck` script — add one to every new package so it can't silently skip type errors.

## Conventions

- RPC functions must use `defineRpcFunction`; always namespace IDs (`my-plugin:fn-name`).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"test:e2e:ui": "turbo run build && playwright test --ui",
"test:ecosystem": "tsx scripts/ecosystem-ci.ts",
"release": "bumpp -r",
"typecheck": "tsc -b",
"typecheck": "turbo run typecheck",
"postinstall": "npx simple-git-hooks && skills-npm"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/devframe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"scripts": {
"build": "tsdown",
"watch": "tsdown --watch",
"typecheck": "tsc --noEmit",
"prepack": "pnpm build && mkdir -p ./skills && cp -r ../../skills/devframe ./skills/devframe"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe('mcp adapter (in-memory)', () => {
it('surfaces shared-state keys as MCP resources', async () => {
const { ctx, client, cleanup } = await bootPair()
try {
const state = await ctx.rpc.sharedState.get('my-plugin:counter' as any, {
const state = await ctx.rpc.sharedState.get('my-plugin:counter', {
initialValue: { count: 7 },
})

Expand Down
2 changes: 1 addition & 1 deletion packages/devframe/src/adapters/mcp/build-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ function registerResourceHandlers(
}

if (parsed.kind === 'state') {
const state = await ctx.rpc.sharedState.get(parsed.key as any)
const state = await ctx.rpc.sharedState.get(parsed.key)
return {
contents: [
{
Expand Down
4 changes: 2 additions & 2 deletions packages/devframe/src/node/__tests__/host-functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ describe('rpcFunctionsHost', () => {
it('should not throw in build mode', async () => {
const host = new RpcFunctionsHost({ mode: 'build' } as DevframeNodeContext)
await expect(host.broadcast({
method: 'devframe:terminals:updated',
method: 'devframe:auth:revoked',
args: [],
})).resolves.toBeUndefined()
})

it('should not throw in dev mode when rpc group is not yet set', async () => {
const host = new RpcFunctionsHost({ mode: 'dev' } as DevframeNodeContext)
await expect(host.broadcast({
method: 'devframe:terminals:updated',
method: 'devframe:auth:revoked',
args: [],
})).resolves.toBeUndefined()
})
Expand Down
5 changes: 4 additions & 1 deletion packages/devframe/src/node/host-diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ export class DevframeDiagnosticsHost implements DevframeDiagnosticsHostType {
...opts,
reporters: [devframeReporter, ...(opts.reporters ?? [])],
} as Parameters<typeof defineDiagnostics>[0]
return defineDiagnostics(merged) as ReturnType<DevframeDiagnosticsHostType['defineDiagnostics']>
// Runtime passthrough: the per-call `Codes` generic can't be threaded
// through this assigned arrow, so the narrow return type is restored by
// the property's declared signature at every call site.
return defineDiagnostics(merged) as any
}

constructor(
Expand Down
8 changes: 4 additions & 4 deletions packages/devframe/src/node/rpc-shared-state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DevframeRpcSharedStates, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost } from 'devframe/types'
import type { RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost } from 'devframe/types'
import type { SharedState, SharedStatePatch } from 'devframe/utils/shared-state'
import { createSharedState } from 'devframe/utils/shared-state'
import { createDebug } from 'obug'
Expand Down Expand Up @@ -97,7 +97,7 @@ export function createRpcSharedStateServerHost(
handler: async (key: string) => {
if (!sharedState.has(key))
return undefined
const state = await host.get(key as keyof DevframeRpcSharedStates)
const state = await host.get(key)
return state.value()
},
// Pre-compute snapshots for the build-mode static dump so the SPA
Expand All @@ -111,7 +111,7 @@ export function createRpcSharedStateServerHost(
name: 'devframe:rpc:server-state:set',
type: 'query',
handler: async (key: string, value: any, syncId: string) => {
const state = await host.get(key as keyof DevframeRpcSharedStates, {
const state = await host.get(key, {
initialValue: value,
})
state.mutate(() => value, syncId)
Expand All @@ -124,7 +124,7 @@ export function createRpcSharedStateServerHost(
handler: async (key: string, patches: SharedStatePatch[], syncId: string) => {
if (!sharedState.has(key))
return
const state = await host.get(key as keyof DevframeRpcSharedStates)
const state = await host.get(key)
state.patch(patches, syncId)
},
})
Expand Down
4 changes: 2 additions & 2 deletions packages/devframe/src/rpc/dump/__tests__/dump.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ describe('dumps', () => {
dump: {
inputs: [[]] as [][],
},
handler: () => {
handler: (): void => {
const err = new TypeError('boom', { cause: new Error('inner') }) as Error & { tags?: unknown }
err.tags = tags
throw err
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('dumps', () => {
dump: {
inputs: [[]] as [][],
},
handler: () => {
handler: (): void => {
// eslint-disable-next-line no-throw-literal
throw 'just a string'
},
Expand Down
6 changes: 3 additions & 3 deletions packages/devframe/src/rpc/dump/__tests__/static.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ describe('collectStaticRpcDump', () => {
name: 'test:flaky',
type: 'query',
jsonSerializable: true,
handler: () => {
handler: (): void => {
throw new TypeError('boom', { cause: new Error('inner') })
},
dump: {
Expand Down Expand Up @@ -261,7 +261,7 @@ describe('collectStaticRpcDump', () => {
name: 'test:flaky-roundtrip',
type: 'query',
// default jsonSerializable: false → structured-clone shards
handler: () => {
handler: (): void => {
const err = new TypeError('boom', { cause: new Error('inner') }) as Error & { tags?: unknown }
err.tags = tags
throw err
Expand Down Expand Up @@ -295,7 +295,7 @@ describe('collectStaticRpcDump', () => {
name: 'test:flaky-non-json',
type: 'query',
jsonSerializable: true,
handler: () => {
handler: (): void => {
const err = new Error('boom') as Error & { tags?: unknown }
err.tags = new Map([['a', 1]])
throw err
Expand Down
2 changes: 1 addition & 1 deletion packages/devframe/src/rpc/dump/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function serializeWithSeen(error: unknown, seen: WeakSet<object>): RpcDumpRecord
for (const key of Object.keys(error)) {
if (key === 'name' || key === 'message' || key === 'cause')
continue
out[key] = (error as Record<string, unknown>)[key]
out[key] = (error as unknown as Record<string, unknown>)[key]
}
return out
}
Expand Down
29 changes: 29 additions & 0 deletions packages/devframe/src/types/rpc-augments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ export interface DevframeRpcClientFunctions {
* @internal
*/
'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>
/**
* Full shared-state snapshot pushed from server to subscribed clients.
* Wired by `RpcSharedStateHost`; do not register manually.
*
* @internal
*/
'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>
/**
* Incremental shared-state patch pushed from server to subscribed clients.
* Wired by `RpcSharedStateHost`; do not register manually.
*
* @internal
*/
'devframe:rpc:client-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>
/**
* Server→client notification that the client's auth token was revoked.
* Broadcast by `revokeActiveConnectionsForToken`; do not register manually.
*
* @internal
*/
'devframe:auth:revoked': () => Promise<void>
}

/**
Expand Down Expand Up @@ -92,6 +113,14 @@ export interface DevframeRpcServerFunctions {
* @internal
*/
'devframe:streaming:upload-end': (channel: string, id: string, error?: { name: string, message: string }) => Promise<void>
/**
* Anonymous-auth handshake the browser client issues on connect. The
* standalone server registers a noop auto-trust handler when `auth: false`;
* hosted adapters register the real handler. Do not register manually.
*
* @internal
*/
'devframe:anonymous:auth': (payload: { authToken: string, ua: string, origin: string }) => Promise<{ isTrusted: boolean }>
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/devframe/src/types/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { DevframeNodeRpcSessionMeta } from 'devframe/rpc/transports/ws-serv
import type { SharedState } from 'devframe/utils/shared-state'
import type { StreamReader, StreamSink } from 'devframe/utils/streaming-channel'
import type { DevframeNodeContext } from './context'
import type { DevframeRpcClientFunctions, DevframeRpcServerFunctions, DevframeRpcSharedStates } from './rpc-augments'
import type { DevframeRpcClientFunctions, DevframeRpcServerFunctions } from './rpc-augments'

export type { DevframeNodeRpcSessionMeta }

Expand Down Expand Up @@ -72,7 +72,7 @@ export interface RpcSharedStateGetOptions<T> {
}

export interface RpcSharedStateHost {
get: <T extends keyof DevframeRpcSharedStates>(key: T, options?: RpcSharedStateGetOptions<DevframeRpcSharedStates[T]>) => Promise<SharedState<DevframeRpcSharedStates[T]>>
get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>
keys: () => string[]
/**
* Subscribe to new shared-state keys becoming available. Fires when
Expand Down
5 changes: 3 additions & 2 deletions packages/devframe/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"lib": ["esnext", "dom"]
}
},
"include": ["src", "test", "scripts", "tsdown.config.ts"],
"exclude": ["dist", "node_modules"]
}
1 change: 1 addition & 0 deletions packages/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"scripts": {
"build": "tsdown",
"watch": "tsdown --watch",
"typecheck": "tsc --noEmit",
"prepack": "pnpm run build"
},
"peerDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/hub/src/node/__tests__/context.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DevframeDockEntry } from '../../types/docks'
import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
Expand All @@ -22,7 +23,7 @@ describe('createHubContext shared state', () => {
host: createHost(),
})

const docks = await context.rpc.sharedState.get('devframe:docks')
const docks = await context.rpc.sharedState.get<DevframeDockEntry[]>('devframe:docks')
expect(docks.value().map(dock => dock.id)).toEqual([
'~terminals',
'~messages',
Expand Down
3 changes: 3 additions & 0 deletions packages/hub/src/node/__tests__/host-docks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('devframeDockHost remote URL enrichment', () => {
type: 'iframe',
id: 'remote',
title: 'Remote',
icon: 'ph:cube-duotone',
url: 'https://remote.test/app#/inspect?tab=state',
remote: true,
})
Expand All @@ -47,6 +48,7 @@ describe('devframeDockHost remote URL enrichment', () => {
type: 'iframe',
id: 'remote',
title: 'Remote',
icon: 'ph:cube-duotone',
url: firstUrl,
remote: true,
})
Expand All @@ -66,6 +68,7 @@ describe('devframeDockHost remote URL enrichment', () => {
type: 'iframe',
id: 'remote',
title: 'Remote',
icon: 'ph:cube-duotone',
url: 'https://remote.test/app#section',
remote: true,
})
Expand Down
21 changes: 21 additions & 0 deletions packages/hub/src/node/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ import { DevframeMessagesHost as MessagesHostImpl } from './host-messages'
import { DevframeTerminalsHost as TerminalsHostImpl } from './host-terminals'
import { builtinHubRpcDeclarations } from './rpc-builtins'

declare module 'devframe/types' {
interface DevframeRpcClientFunctions {
/**
* Server→client notification that terminal sessions changed. Broadcast
* by the hub context; a hub-aware client re-reads terminal state in
* response. Do not register manually.
*
* @internal
*/
'devframe:terminals:updated': () => Promise<void>
/**
* Server→client notification that the message list changed. Broadcast
* by the hub context; a hub-aware client re-reads message state in
* response. Do not register manually.
*
* @internal
*/
'devframe:messages:updated': () => Promise<void>
}
}

/**
* Hub-augmented node context — extends devframe's framework-neutral
* `DevframeNodeContext` with the hub-level subsystems (`docks`,
Expand Down
5 changes: 3 additions & 2 deletions packages/hub/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"lib": ["esnext", "dom"]
}
},
"include": ["src", "tsdown.config.ts"],
"exclude": ["dist", "node_modules"]
}
1 change: 1 addition & 0 deletions packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"scripts": {
"build": "tsdown",
"watch": "tsdown --watch",
"typecheck": "tsc --noEmit",
"prepack": "pnpm run build"
},
"peerDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/nuxt/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"lib": ["esnext", "dom"],
"types": ["node"]
},
"include": ["nuxt.d.ts", "src/**/*.ts", "../devframe/src/**/*.ts"]
"include": ["nuxt.d.ts", "src", "tsdown.config.ts"],
"exclude": ["dist", "node_modules"]
}
1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ trustPolicyExclude:
- tinyexec@1.2.2
packages:
- packages/*
- plugins/*
- examples/*
- docs
overrides:
Expand Down
3 changes: 1 addition & 2 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
{
"compilerOptions": {
"composite": true,
"target": "esnext",
"lib": [
"esnext"
],
"rootDir": ".",
"module": "esnext",
"moduleResolution": "Bundler",
"paths": {
Expand Down Expand Up @@ -131,6 +129,7 @@
]
},
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"strict": true,
"noEmit": true,
"isolatedDeclarations": false,
Expand Down
5 changes: 4 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["pnpm-lock.yaml"],
"globalDependencies": ["pnpm-lock.yaml", "tsconfig.base.json"],
"tasks": {
"typecheck": {
"dependsOn": ["^typecheck"]
},
"devframe#build": {
"outputLogs": "new-only",
"outputs": ["dist/**"]
Expand Down
Loading