Skip to content

feat(session): pluggable persistent channel store#563

Open
deodad wants to merge 5 commits into
mainfrom
dad/pluggable-channel-store
Open

feat(session): pluggable persistent channel store#563
deodad wants to merge 5 commits into
mainfrom
dad/pluggable-channel-store

Conversation

@deodad

@deodad deodad commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Make session payment channels persist through a pluggable channelStore on the session method, keyed by payment scope (payee:token:escrow:chainId), so a consumer (e.g. a wallet) can read and persist channel state.

Motivation

An app integrating mppx needs channel state to live alongside its own state about the connected address and access keys. A wallet implementing wallet_authorizeChallenge can then read channel state to pick the right key to sign vouchers for a given payment scope. Persisting it durably is the secondary win: channels survive restarts instead of being re-opened.

Changes

  • session({ channelStore }) and sessionManager({ channelStore }) accept a ChannelStore (get/set/delete). The plugin resumes a matching channel after a 402, vouchers against it, and writes it back; closed channels are deleted.
  • Ship createChannelStore() (in-memory default) and createJsonChannelStore(kv) (durable, bigint-safe over a plain string KV). Public surface is just ChannelStore, createChannelStore, createJsonChannelStore, JsonChannelKv, entryKey — identical from mppx/client and Tempo.Session.Client.
  • The store lives in its own session/client/ChannelStore.ts (mirroring the server side); CredentialState.ts keeps only credential planning.
  • sessionManager hints Payment-Session only for a live in-memory channel; a cold start resumes through the store after the first 402. A stale resumed channel is evicted and the request retried once.
  • Hardening: channelKey takes a scope object (no positional-arg footgun); the store wrapper drops a redundant per-write read; concurrent requests on one manager now fail with a clear error instead of corrupting shared state.

Breaking: removed the sessionStore option and the SessionStore / StoredSessionChannel types. Persistent cold-start hints are gone; durable resume runs through the store after the first 402.

Testing

  • pnpm check:types — clean
  • pnpm check — clean (lint/format)
  • pnpm test — session client suites pass, including a new concurrent-request guard test
  • Also validated end-to-end against the Tempo accounts SDK (durable storage adapter + localnet session) via a local link.

@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/mppx@563

commit: 1765d4c

@deodad deodad force-pushed the dad/pluggable-channel-store branch from 55be453 to 31e7906 Compare June 17, 2026 16:18
deodad and others added 2 commits June 17, 2026 13:11
Replace the SessionManager's per-manager sessionStore with a pluggable
multi-channel ChannelStore on the session method, keyed by payment scope
(payee:token:escrow:chainId). The plugin resumes a matching channel after
a 402, vouchers against it, and writes it back; the manager only hints a
live in-memory channel on cold requests and resumes everything else via
the store's entry index after the first 402.

- session/sessionManager accept channelStore (get/set/delete)
- add createChannelStore (in-memory) and createJsonChannelStore (durable,
  bigint-safe over a plain string kv), plus entryKey/serializeEntry/
  deserializeEntry and ChannelStore/JsonChannelKv/StoredChannel types
- remove sessionStore option and SessionStore/StoredSessionChannel types;
  persistent cold-start hints are gone, durable resume runs through the
  store after the first 402

Amp-Thread-ID: https://ampcode.com/threads/T-019ed203-0f2e-751f-9a05-aa6610cab7d5
Co-authored-by: Amp <amp@ampcode.com>
Move the client channel store out of CredentialState.ts into a dedicated
ChannelStore.ts, mirroring session/server/ChannelStore.ts. CredentialState
keeps credential planning; the store owns persistence and serialization.

- new src/tempo/session/client/ChannelStore.ts holds ChannelStore,
  ChannelSink, channelKey/entryKey, createChannelStore, StoredChannel,
  serializeEntry/deserializeEntry, JsonChannelKv, createJsonChannelStore
- drop the store re-export ladder through Session.ts; client barrels import
  straight from ChannelStore.js
- SessionManager and tests import store symbols from the new module

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019ed67a-26f1-725b-8dd8-ad419a893f75
@deodad deodad force-pushed the dad/pluggable-channel-store branch from c424ad4 to 667af66 Compare June 17, 2026 18:12
deodad and others added 3 commits June 17, 2026 13:23
- unify the two client barrels on one minimal store surface
  (ChannelStore, createChannelStore, createJsonChannelStore, JsonChannelKv,
  entryKey); drop serializeEntry/deserializeEntry/StoredChannel from public
  export since createJsonChannelStore already (de)serializes
- inline ChannelNotify into ChannelSink and isReusable into getReusable

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019ed67a-26f1-725b-8dd8-ad419a893f75
- channelKey takes a scope object instead of four positional Address/number
  args, removing a silent key-transposition footgun (#9)
- drop the per-write backing read in the manager's store wrapper: the plugin
  always reads a key before writing it, so fresh-open detection keys off an
  in-memory seenExisting set instead of a second store round-trip (#4)
- reject concurrent doFetch on one manager with a clear error instead of
  letting overlapping requests corrupt the shared runtime (A2)

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019ed67a-26f1-725b-8dd8-ad419a893f75
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant