@procedures/splay
v0.1.0
Published
Render procedure results with splay: a bridge between the client procedures and the splay recursive renderer, with stream helpers.
Maintainers
Readme
@procedures/splay
npm install @procedures/splayBridge between splay's rendering model and client's procedure system. Enables server-side component rendering via RPC.
Table of Contents
- Overview
- Installation
- Architecture
- Quick Start
- API Reference
- ComponentOutput Format
- Integration
- Requirements
- License
Overview
client-splay bridges the splay rendering model with the RPC procedure system:
- Client Registry - Call component procedures via RPC
- Streaming Registry - Handle async component streams
- Hydration - Convert descriptors to framework elements (React, etc.)
- Utilities - Walk, transform, and validate descriptor trees
Installation
npm install github:mark1russell7/client-splay#mainArchitecture
System Overview
graph TB
subgraph "Application"
App[Your Application]
end
subgraph "client-splay"
subgraph "Registry Layer"
ClientReg[ClientRegistry<br/>Sync calls]
StreamReg[StreamingRegistry<br/>Async iterables]
DualReg[DualRegistry<br/>Both modes]
end
subgraph "Hydration Layer"
Hydrate[createHydrate]
ReactHydrate[createReactHydrate]
end
end
subgraph "RPC Layer"
Client[client.call]
Stream[client.stream]
end
subgraph "Output"
Descriptor[ComponentOutput]
Element[React Element]
end
App --> ClientReg
App --> StreamReg
ClientReg --> Client
StreamReg --> Stream
Client --> Descriptor
Stream --> Descriptor
Descriptor --> Hydrate
Descriptor --> ReactHydrate
ReactHydrate --> ElementComponent Rendering Flow
sequenceDiagram
participant App as Application
participant Reg as ClientRegistry
participant RPC as client.call
participant Server as Procedure Server
participant Hydrate as Hydrator
App->>Reg: registry.get("user-card")(ctx)
Reg->>RPC: call(["ui", "user-card"], input)
RPC->>Server: Execute procedure
Server-->>RPC: ComponentOutput
RPC-->>Reg: Descriptor
Reg-->>App: ComponentOutput
App->>Hydrate: hydrate(descriptor)
Hydrate->>Hydrate: Map to React components
Hydrate-->>App: React.ElementRegistry Types
graph TB
subgraph "ClientRegistry"
CR[Sync calls]
CROut[Single ComponentOutput]
end
subgraph "StreamingRegistry"
SR[Async streams]
SROut[AsyncIterable of ComponentOutput]
end
subgraph "DualRegistry"
DR[Both modes]
DROut[Sync + Streaming]
end
CR --> CROut
SR --> SROut
DR --> DROutQuick Start
import { createClientRegistry, createReactHydrate } from "@procedures/splay";
import { call } from "client";
// 1. Create registry backed by client procedures
const registry = createClientRegistry(call, { namespace: "ui" });
// 2. Create hydrator for React
const hydrate = createReactHydrate(React, {
components: {
"user-card": UserCard,
"badge": Badge,
},
});
// 3. Render a component
const ctx = { data: { userId: 123 }, size: { width: 400, height: 300 } };
const descriptor = await registry.get("user-card")!(ctx);
const element = hydrate(descriptor);API Reference
Registry
ClientRegistry
Sync registry for single-response component calls.
const registry = createClientRegistry(call, {
namespace?: string, // Procedure path prefix (e.g., "ui")
});
// Usage
const descriptor = await registry.get("user-card")!(ctx);StreamingRegistry
Streaming registry for multiple responses over time.
const streaming = createStreamingRegistry(stream, {
namespace?: string,
bufferSize?: number, // Default: 16
});
// Usage
for await (const descriptor of streaming.get("live-feed")!(ctx)) {
render(descriptor);
}DualRegistry
Combined registry supporting both sync and streaming.
const dual = createDualRegistry(call, stream, isStreaming, options);
// Automatically chooses based on component type
const result = await dual.get("component-name")!(ctx);Hydration
createHydrate (Generic)
Framework-agnostic hydration.
const hydrate = createHydrate<TComponent>({
components: ComponentMap, // Map of type -> component
fallback?: Component, // For unknown types
onNull?: () => TComponent, // Handle null descriptors
onFragment?: (children) => TComponent, // Handle fragments
});createReactHydrate
React-specific hydration with createElement integration.
const hydrate = createReactHydrate(React, {
components: {
"user-card": UserCard,
"badge": Badge,
"button": Button,
},
fallback?: UnknownComponent,
});
// Usage
const element = hydrate(descriptor);
// Returns: React.ElementDescriptor Utilities
// Traverse descriptor tree
walkDescriptor(descriptor, (node, path) => {
console.log(`Node at ${path.join(".")}: ${node.type}`);
});
// Transform nodes
const transformed = transformDescriptor(descriptor, node => ({
...node,
props: { ...node.props, className: "styled" }
}));
// Find matching nodes
const buttons = findInDescriptor(descriptor, n => n.type === "button");
// Get all component types used
const types: Set<string> = getUsedTypes(descriptor);
// Validate against known types
const { valid, unknownTypes } = validateDescriptor(descriptor, knownTypes);
// Serialization
const json = serializeDescriptor(descriptor);
const parsed = parseDescriptor(json);
const compact = compactDescriptor(descriptor); // Remove empty valuesStream Utilities
// Merge multiple component streams
for await (const output of mergeStreams(stream1, stream2)) {
render(output);
}
// Rate limiting
for await (const output of throttleStream(stream, 100)) {
// Max 1 update per 100ms
render(output);
}
for await (const output of debounceStream(stream, 200)) {
// Wait 200ms of quiet before emitting
render(output);
}ComponentOutput Format
The wire format for component descriptors:
interface ComponentOutput {
type: string; // Component type or "__null__" / "__fragment__"
props: Record<string, unknown>; // Component props
children?: ComponentOutput[]; // Nested children
key?: string | number; // React key
}Special Types
graph LR
subgraph "Standard"
Component["type: 'user-card'"]
end
subgraph "Null"
Null["type: '__null__'<br/>Renders nothing"]
end
subgraph "Fragment"
Fragment["type: '__fragment__'<br/>Wraps children"]
endExample Descriptor
{
"type": "user-card",
"props": { "userId": 123, "showBadge": true },
"children": [
{
"type": "badge",
"props": { "label": "Admin" }
}
]
}Integration
Built-in Procedures
| Path | Description |
|------|-------------|
| splay.bridge.info | Package information |
| splay.bridge.health | Health check |
Re-exports from client/components
import {
// Types
ComponentOutput, FragmentOutput, NullOutput, Size,
ComponentContext, ComponentFactory, ComponentDefinition,
// Helpers
nullOutput, fragment, isFragment, isNullOutput,
defineComponent, simpleComponent, streamingComponent,
} from "@procedures/splay";Package Hierarchy
graph BT
subgraph "UI Layer"
SplayReact[splay-react]
end
subgraph "Bridge"
ClientSplay[client-splay]
end
subgraph "Core"
Splay[splay]
Client[client]
end
SplayReact --> ClientSplay
ClientSplay --> Splay
ClientSplay --> ClientRequirements
- Node.js >= 25
- Dependencies:
@procedures/client@mark1russell7/splay
License
MIT
