Skip to content

feat(data-rpc): schema-driven bidirectional service projection across iframe/MessagePort - #197

Merged
krisnye merged 4 commits into
mainfrom
krisnye/iframe
Sep 4, 2026
Merged

feat(data-rpc): schema-driven bidirectional service projection across iframe/MessagePort#197
krisnye merged 4 commits into
mainfrom
krisnye/iframe

Conversation

@krisnye

@krisnye krisnye commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Description

New @adobe/data-rpc package: schema-driven, bidirectional projection of
@adobe/data async data services across a boundary (iframe / MessagePort /
Worker). One createRpcEndpoint(transport) per side; each can expose its own
services and consume the peer's over a single channel.

Core invariant: only Data ever crosses the wire. We never serialize an
Observe/Promise/AsyncGenerator/function — the consuming side synthesizes an
equivalent-shaped service locally whose members translate to/from Data-only
messages. Gating expose on AsyncDataService.IsValidWithCompleteSchema
guarantees there is nothing but Data to marshal.

Highlights:

  • All five member kinds: Observe, fn:observe, fn:promise, fn:generator
    (strictly pull-based), fn:void.
  • Nested sub-services shimmed recursively to any depth; members addressed by
    path from the service root.
  • Constructor-typed arguments (reverse channel): an Observe, Promise, or
    AsyncGenerator passed as/inside a call argument is serviced from the caller —
    the caller sends a ref and the value is streamed/settled/pulled back over a
    reverse channel; only Data crosses.
  • Transports: createMessagePortTransport (primary), createWindowTransport
    (port handoff + origin allowlist), createRpcLoopbackTransport (tests).
  • Errors marshaled as {name,message,stack}; separate per-originator id tables;
    graceful close teardown (pending calls reject, subscriptions/generators end).
  • Trust v1: transport-level (origin allowlist + only exposed services) + a
    canInvoke seam + incoming arg validation against signature.parameters.

Also:

  • Extracts the shared memberKind classifier out of create-lazy.ts into
    async-data-service/member-kind.ts (re-exported via AsyncDataService);
    createLazy behavior unchanged.
  • Adds data-rpc-iframe — a Lit sample (main frame + real iframe over a
    MessagePort) demonstrating all four kinds in both directions plus a nested
    sub-service.

Test evidence

  • 48 unit tests in @adobe/data-rpc (every member kind both directions; hard
    paths: close-mid-call, unsubscribe-in-flight, generator early return/dispose,
    bidirectional id independence, consume-before-expose, arg-validation rejection,
    teardown-on-close; nested services and Observe/Promise/AsyncGenerator arguments
    over loopback and a real MessageChannel).
  • @adobe/data service suite (122 tests) still green after the memberKind extract.
  • Sample integration test + live browser verification: all four kinds and the
    nested sub.calc.* sub-service confirmed working across the iframe, console clean.
  • Full monorepo pnpm run lint + pnpm run typecheck pass (caches cleared).

Related PRs

Builds on #196 (pure-JSON Schema + observable Service.schema slot).

Jira ticket

N/A — no ticket (public repo, library feature).

krisnye and others added 4 commits September 4, 2026 01:19
… iframe/MessagePort

New @adobe/data-rpc package projects AsyncDataServices across a boundary in both
directions over one channel; only Data crosses the wire, far side synthesizes
equivalent-shaped members (incl. nested sub-services). Extracts shared memberKind
from create-lazy. Adds data-rpc-iframe Lit sample. Bumps to 0.10.10.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… a reverse channel

An Observe passed as/inside a call argument streams caller→callee: the caller
sends a ref and streams values back on demand (arg-subscribe/arg-next/
arg-unsubscribe); the callee reconstructs a local Observe. Only Data crosses.
Providers released on op-teardown and endpoint close. Relaxes the expose gate to
schema-describes-service (like createLazy), since IsValid rejects Observe args.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…enerator

Extends the argument reverse channel beyond Observe: a Promise or AsyncGenerator
passed as/inside a call argument is serviced from the caller. Unified arg-marshal
walker dispatches per constructor kind; promise=arg-resolve/arg-reject,
generator=arg-pull/arg-yield/arg-done/arg-throw/arg-return/arg-raise (pull-based).
Only Data crosses; providers released on op-teardown and endpoint close.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pe-path guard, safer transports

- B1: suppress unhandled rejection when a member ignores a rejecting promise arg
- S1: cancel a promise/generator arg on release so the callee can't hang after the call completes
- S2: own-property guards in host path resolution (reject constructor/__proto__ paths)
- S4: void members reject constructor-typed arguments (no completion signal to release them)
- N1: createWindowTransport requires an explicit targetOrigin when origins are ambiguous (never "*")
- N2: arg refs travel as a tagged envelope so a literal value can't be mistaken for a ref
- N3: remove dead `deadline` field
Adds red-then-green pin tests for each (48 tests total).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@krisnye
krisnye merged commit 46a3dc3 into main Sep 4, 2026
3 checks passed
@krisnye
krisnye deleted the krisnye/iframe branch September 4, 2026 20:53
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