diff --git a/package-lock.json b/package-lock.json index 83520a20..ea305e95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,31 @@ "vitest": "4.1.5" } }, + "../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi": { + "name": "@parity/truapi", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "neverthrow": "^8.2.0", + "scale-ts": "^1.6.1" + }, + "devDependencies": { + "typescript": "^5.7" + } + }, + "../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi-host": { + "name": "@parity/truapi-host", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@parity/truapi": "file:../truapi", + "neverthrow": "^8.2.0", + "scale-ts": "^1.6.1" + }, + "devDependencies": { + "typescript": "^5.7" + } + }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", @@ -4507,6 +4532,14 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@parity/truapi": { + "resolved": "../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi", + "link": true + }, + "node_modules/@parity/truapi-host": { + "resolved": "../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi-host", + "link": true + }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -16925,6 +16958,7 @@ "license": "Apache-2.0", "dependencies": { "@novasamatech/scale": "0.7.8", + "@parity/truapi": "file:../../../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi", "nanoevents": "9.1.0", "nanoid": "5.1.9", "neverthrow": "^8.2.0", @@ -16998,6 +17032,8 @@ "dependencies": { "@noble/hashes": "2.2.0", "@novasamatech/host-api": "0.7.8", + "@parity/truapi": "file:../../../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi", + "@parity/truapi-host": "file:../../../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi-host", "@polkadot-api/substrate-client": "^0.7.0", "nanoid": "5.1.9", "neverthrow": "^8.2.0", diff --git a/packages/host-api/MIGRATION.md b/packages/host-api/MIGRATION.md new file mode 100644 index 00000000..c7ed0a08 --- /dev/null +++ b/packages/host-api/MIGRATION.md @@ -0,0 +1,56 @@ +# host-api migration to `@parity/truapi` + +This package is being reworked into a thin compatibility facade over +[`@parity/truapi`][1]. The Rust trait surface in `paritytech/truapi` +(`rust/crates/truapi/src/api/*.rs`) is the single source of truth for wire +ids, payload shapes, and dispatch. + +The work tracking issue is [`paritytech/truapi#54`][issue]. + +[1]: https://github.com/paritytech/truapi/tree/main/js/packages/truapi +[issue]: https://github.com/paritytech/truapi/issues/54 + +## Status + +### Done + +- `@parity/truapi` added to `package.json` dependencies (currently a `file:` + link to the local truapi worktree; switch to the published version once + `@parity/truapi@0.2.0` ships). +- `src/truapi-transport.ts`: directional facade that wraps a legacy + `Provider` and produces a `@parity/truapi` `TrUApiTransport` instance. + +### Not done + +- `src/transport.ts` still owns its own request correlation, subscription + router, frame envelope, and subscription multiplexing. Replace its + internals with calls into the `@parity/truapi` transport returned from + `createTruapiTransportFacade()`. Keep the public `Transport` shape from + `./types.ts` byte-for-byte stable so downstream consumers (`host-papp`, + `product-react-renderer`, `product-bulletin`, `host-worker-sandbox`, + `host-api-wrapper`) do not break. +- Active handshake retry / readiness state currently lives in `isReady()` + inside `transport.ts`. Re-implement on top of the new transport's public + handshake helpers (the auto-response path inside `@parity/truapi` covers + passive handshake; active retry stays here as consumer policy). +- Drop subscription multiplexing (the `activeSubscriptions` map and + `getSubscriptionKey`). `@parity/truapi` exposes 1:1 wire subscriptions, + any consumer that relies on dedup adds its own multiplexer above this + package. +- Drop inbound `handleRequest` / `handleSubscription` from the public + surface, they exist only to support `host-container`, which migrates onto + `@parity/truapi-host` separately. Update `Transport` in `./types.ts`. +- Repoint the per-codec exports in `src/index.ts` (lines 36 onward) to + re-export from `@parity/truapi`'s generated `types.ts`. The legacy class + names (`SigningErr`, `CreateTransactionErr`, `AccountConnectionStatus`, + …) need either a thin alias layer or a one-shot rename in downstream + consumers, the generated equivalents use tagged-union shape + (`{ tag, value }`) rather than `new Err.Variant({...})` calls. +- Delete `src/protocol/v1/*.ts` once every type alias above is repointed. + +### Tests + +- `src/transport.spec.ts` covers subscription multiplexing today, that test + will need to move or get retired alongside the multiplexer. +- Add a 56-method byte-equivalence fixture test against a captured + `@novasamatech/host-api@0.7.7` baseline. diff --git a/packages/host-api/package.json b/packages/host-api/package.json index 53418e2b..919cbacb 100644 --- a/packages/host-api/package.json +++ b/packages/host-api/package.json @@ -23,6 +23,7 @@ ], "dependencies": { "@novasamatech/scale": "0.7.8", + "@parity/truapi": "file:../../../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi", "nanoevents": "9.1.0", "nanoid": "5.1.9", "neverthrow": "^8.2.0", diff --git a/packages/host-api/src/truapi-transport.ts b/packages/host-api/src/truapi-transport.ts new file mode 100644 index 00000000..89232ed4 --- /dev/null +++ b/packages/host-api/src/truapi-transport.ts @@ -0,0 +1,110 @@ +/** + * Facade transport built on `@parity/truapi`. + * + * Replaces the bespoke request correlation, subscription router, and wire + * frame envelope that `transport.ts` currently owns with the equivalent + * primitives from `@parity/truapi`. The public surface intentionally mirrors + * `Transport` from `./types.ts` so consumers of `createHostApi` and the + * factories in `host-container` can switch over without churn. + * + * What this file does NOT do, by design: + * - Subscription multiplexing or dedup. `@parity/truapi` exposes 1:1 wire + * subscriptions, products that need fan-out layer their own. + * - Inbound handler registration (`handleRequest`, `handleSubscription`). + * Hosts should use `@parity/truapi-host`'s typed dispatcher instead, and + * `host-container`'s `createContainer` is being migrated onto it in + * `truapi-container.ts`. + * + * Status: directional. Active handshake retry, connection-status events, and + * the legacy `postMessage`/`listenMessages` low-level helpers still need to + * be re-implemented on top of this. See `MIGRATION.md` for the punch list. + */ + +import type { Provider as TruapiProvider, TrUApiTransport } from '@parity/truapi'; +import { createTransport as createTruapiTransport } from '@parity/truapi'; +import { createNanoEvents } from 'nanoevents'; + +import type { Provider } from './provider.js'; +import type { ConnectionStatus, Logger } from './types.js'; + +/** + * Adapt a legacy `Provider` (postMessage/listenMessages over wire-frame + * codec objects) to the raw byte-level `Provider` shape `@parity/truapi` + * expects. + * + * The legacy provider already speaks SCALE-encoded wire frames, so this is + * a pass-through, message-codec wrapping and unwrapping is now owned by + * `@parity/truapi`'s `encodeWireMessage`/`decodeWireMessage`. + */ +function adaptProvider(provider: Provider): TruapiProvider { + return { + postMessage(bytes) { + // The legacy `Provider` postMessage accepts already-encoded bytes; the + // wrapping `MessageProvider.postMessage(message)` indirection that + // `transport.ts` adds to encode the wire-frame envelope is now + // performed by `@parity/truapi.createTransport`. + provider.postMessage(bytes); + }, + subscribe(callback) { + const unsub = provider.subscribe(callback); + return () => unsub(); + }, + dispose() { + provider.dispose(); + }, + }; +} + +export interface TruapiTransportOptions { + logger?: Logger; +} + +export interface TruapiTransport { + /** + * Underlying `@parity/truapi` transport. Generated client modules accept + * this directly; legacy `host-api` consumers wrap it via the facade + * methods below. + **/ + readonly inner: TrUApiTransport; + + /** + * Subscribe to connection-status changes. Mirrors the legacy + * `onConnectionStatusChange` surface. + **/ + onConnectionStatusChange(callback: (status: ConnectionStatus) => void): VoidFunction; + + /** + * Tear down the transport and detach provider listeners. + **/ + destroy(): void; +} + +/** + * Build a `@parity/truapi` transport bound to the supplied legacy + * `Provider`. Generated client modules (from `@parity/truapi`) accept the + * returned `inner` transport directly. + */ +export function createTruapiTransportFacade( + provider: Provider, + _options: TruapiTransportOptions = {}, +): TruapiTransport { + const status = createNanoEvents<{ + change: (status: ConnectionStatus) => void; + }>(); + let currentStatus: ConnectionStatus = 'connecting'; + + const inner = createTruapiTransport(adaptProvider(provider)); + + return { + inner, + onConnectionStatusChange(callback) { + callback(currentStatus); + return status.on('change', callback); + }, + destroy() { + inner.dispose(); + currentStatus = 'disconnected'; + status.emit('change', currentStatus); + }, + }; +} diff --git a/packages/host-container/MIGRATION.md b/packages/host-container/MIGRATION.md new file mode 100644 index 00000000..2b09f45c --- /dev/null +++ b/packages/host-container/MIGRATION.md @@ -0,0 +1,56 @@ +# host-container migration to `@parity/truapi-host` + +This package is being reworked into a thin compatibility facade over +[`@parity/truapi-host`][1]. The Rust trait surface in `paritytech/truapi` +drives both the wire ids and the typed handler interfaces emitted by the +generator. + +The work tracking issue is [`paritytech/truapi#54`][issue]. + +[1]: https://github.com/paritytech/truapi/tree/main/js/packages/truapi-host +[issue]: https://github.com/paritytech/truapi/issues/54 + +## Status + +### Done + +- `@parity/truapi` and `@parity/truapi-host` added to `package.json` + dependencies (currently `file:` links to the local truapi worktree). +- `src/truapi-container.ts`: directional facade that forwards a fully + populated `TrUApiHostHandlers` to `createTrUApiServer`. The TypeScript + compiler enforces completeness; there are no throwing "Not + Implemented" stubs, the dispatcher contract requires handlers to + express every outcome (including missing implementations) as a typed + return rather than an exception. + +### Not done + +- `src/createContainer.ts` still owns: + - 40+ `handleXxx(handler)` slots (the public surface). + - `handleV1Request` / `handleV1Subscription` wrappers around versioned + payload envelopes. + - `makeNotImplementedSlot` / `makePermissionGatedRequestSlot` / + `makeDevicePermissionGatedRequestSlot`, all of which produce + method-specific `Unknown { reason: NOT_IMPLEMENTED }` defaults. + - `handleChainConnection()` (lines ~759–1099): polkadot-api bridge. + This stays inside this package, only its registration call should + change to forward to `@parity/truapi-host`'s typed handlers. +- Replace each `handleXxx` slot's update mechanism with a call into + `@parity/truapi-host`'s typed registration. The slot can remain as the + public API; under the hood it should mutate the handler reference that + `createTrUApiServer(...)` is dispatching against. +- Replace `handleV1Request` / `handleV1Subscription` with the typed + handler signatures from `@parity/truapi-host`'s generated `server.ts`. + The versioned envelope wrap/unwrap is now performed by the generator, + not by hand. +- Drop the dependency on `@novasamatech/host-api` once `host-api` itself + drops its inbound `handleRequest` / `handleSubscription` surface (see + `packages/host-api/MIGRATION.md`). + +### Tests + +- `src/chainConnectionManager.spec.ts` is unchanged scope, must continue + to pass. +- Add an integration test that wires a `@parity/truapi` client and a + `@parity/truapi-host` server through a `MessageChannel` and exercises + every wire method via the container's compat shims. diff --git a/packages/host-container/package.json b/packages/host-container/package.json index 3ce29ffb..fc11ccb5 100644 --- a/packages/host-container/package.json +++ b/packages/host-container/package.json @@ -29,6 +29,8 @@ "polkadot-api": ">=2", "@polkadot-api/substrate-client": "^0.7.0", "@novasamatech/host-api": "0.7.8", + "@parity/truapi": "file:../../../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi", + "@parity/truapi-host": "file:../../../truapi/.claude/worktrees/issue-54-truapi-host/js/packages/truapi-host", "nanoid": "5.1.9", "neverthrow": "^8.2.0" }, diff --git a/packages/host-container/src/truapi-container.ts b/packages/host-container/src/truapi-container.ts new file mode 100644 index 00000000..2aed1a86 --- /dev/null +++ b/packages/host-container/src/truapi-container.ts @@ -0,0 +1,36 @@ +/** + * Facade host container built on `@parity/truapi-host`. + * + * Replaces the bespoke per-method dispatch, "Not Implemented" fallbacks, and + * `handleV1Request` / `handleV1Subscription` wrappers in `createContainer.ts` + * with the typed handler registration produced by `@parity/truapi-host`'s + * generator. + * + * The new public surface is structured-by-service, mirroring the Rust trait + * surface in `truapi`. Hosts that consume `createContainer()` today (with + * `handleXxx` slots) keep working through compat shims in + * `createContainer.ts`, which forward to `createTruapiContainer()` under the + * hood. + * + * Status: directional. The generated `TrUApiHostHandlers` interface forces + * the caller to supply every service handler (handlers must not throw, per + * the `@parity/truapi-host` contract; every outcome including unsupported + * versions and missing implementations is expressed as a typed return). + * Lifting each existing `handleXxx` slot's behaviour into the matching typed + * handler is the bulk of the remaining migration. + */ + +import type { Provider as TruapiProvider } from '@parity/truapi'; +import type { TrUApiHostHandlers, TrUApiHostServer } from '@parity/truapi-host'; +import { createTrUApiServer } from '@parity/truapi-host'; + +/** + * Attach a host server built on `@parity/truapi-host` to a `Provider`. + * + * The caller supplies the full `TrUApiHostHandlers` shape. The TypeScript + * compiler enforces completeness, anything missing is flagged at the call + * site rather than papered over with a stub that would throw at runtime. + */ +export function createTruapiContainer(provider: TruapiProvider, handlers: TrUApiHostHandlers): TrUApiHostServer { + return createTrUApiServer(provider, handlers); +}