@procedures/splay 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +408 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/components/define.d.ts +134 -0
- package/dist/components/define.d.ts.map +1 -0
- package/dist/components/define.js +486 -0
- package/dist/components/define.js.map +1 -0
- package/dist/components/index.d.ts +39 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +38 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/types.d.ts +264 -0
- package/dist/components/types.d.ts.map +1 -0
- package/dist/components/types.js +46 -0
- package/dist/components/types.js.map +1 -0
- package/dist/hydrate.d.ts +126 -0
- package/dist/hydrate.d.ts.map +1 -0
- package/dist/hydrate.js +226 -0
- package/dist/hydrate.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +16 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +58 -0
- package/dist/module.js.map +1 -0
- package/dist/register.d.ts +8 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +15 -0
- package/dist/register.js.map +1 -0
- package/dist/registry.d.ts +60 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +191 -0
- package/dist/registry.js.map +1 -0
- package/dist/streaming.d.ts +94 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +422 -0
- package/dist/streaming.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +73 -3
- package/src/components/define.ts +600 -0
- package/src/components/index.ts +78 -0
- package/src/components/types.ts +357 -0
- package/src/hydrate.ts +355 -0
- package/src/index.ts +111 -0
- package/src/module.ts +81 -0
- package/src/register.ts +18 -0
- package/src/registry.ts +257 -0
- package/src/streaming.ts +554 -0
- package/src/types.ts +114 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @procedures/splay
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
The first release on npm. The packages of the client monorepo have one version together.
|
|
6
|
+
|
|
7
|
+
- The procedure components moved here from `@procedures/client` (roadmap 3.4): `defineComponent`, `componentToProcedure`, `simpleComponent`, `namespacedComponent`, `streamingComponent`, the bundles and the output helpers. Import them from the root of this package.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Mark Russell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,409 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @procedures/splay
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@procedures/splay)
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @procedures/splay
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
[](https://nodejs.org/)
|
|
12
|
+
|
|
13
|
+
> Bridge between splay's rendering model and client's procedure system. Enables server-side component rendering via RPC.
|
|
14
|
+
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
- [Overview](#overview)
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Architecture](#architecture)
|
|
20
|
+
- [Quick Start](#quick-start)
|
|
21
|
+
- [API Reference](#api-reference)
|
|
22
|
+
- [Registry](#registry)
|
|
23
|
+
- [Hydration](#hydration)
|
|
24
|
+
- [Descriptor Utilities](#descriptor-utilities)
|
|
25
|
+
- [Stream Utilities](#stream-utilities)
|
|
26
|
+
- [ComponentOutput Format](#componentoutput-format)
|
|
27
|
+
- [Integration](#integration)
|
|
28
|
+
- [Requirements](#requirements)
|
|
29
|
+
- [License](#license)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Overview
|
|
34
|
+
|
|
35
|
+
**client-splay** bridges the splay rendering model with the RPC procedure system:
|
|
36
|
+
|
|
37
|
+
- **Client Registry** - Call component procedures via RPC
|
|
38
|
+
- **Streaming Registry** - Handle async component streams
|
|
39
|
+
- **Hydration** - Convert descriptors to framework elements (React, etc.)
|
|
40
|
+
- **Utilities** - Walk, transform, and validate descriptor trees
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install github:mark1russell7/client-splay#main
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Architecture
|
|
53
|
+
|
|
54
|
+
### System Overview
|
|
55
|
+
|
|
56
|
+
```mermaid
|
|
57
|
+
graph TB
|
|
58
|
+
subgraph "Application"
|
|
59
|
+
App[Your Application]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
subgraph "client-splay"
|
|
63
|
+
subgraph "Registry Layer"
|
|
64
|
+
ClientReg[ClientRegistry<br/>Sync calls]
|
|
65
|
+
StreamReg[StreamingRegistry<br/>Async iterables]
|
|
66
|
+
DualReg[DualRegistry<br/>Both modes]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
subgraph "Hydration Layer"
|
|
70
|
+
Hydrate[createHydrate]
|
|
71
|
+
ReactHydrate[createReactHydrate]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
subgraph "RPC Layer"
|
|
76
|
+
Client[client.call]
|
|
77
|
+
Stream[client.stream]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
subgraph "Output"
|
|
81
|
+
Descriptor[ComponentOutput]
|
|
82
|
+
Element[React Element]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
App --> ClientReg
|
|
86
|
+
App --> StreamReg
|
|
87
|
+
ClientReg --> Client
|
|
88
|
+
StreamReg --> Stream
|
|
89
|
+
Client --> Descriptor
|
|
90
|
+
Stream --> Descriptor
|
|
91
|
+
Descriptor --> Hydrate
|
|
92
|
+
Descriptor --> ReactHydrate
|
|
93
|
+
ReactHydrate --> Element
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Component Rendering Flow
|
|
97
|
+
|
|
98
|
+
```mermaid
|
|
99
|
+
sequenceDiagram
|
|
100
|
+
participant App as Application
|
|
101
|
+
participant Reg as ClientRegistry
|
|
102
|
+
participant RPC as client.call
|
|
103
|
+
participant Server as Procedure Server
|
|
104
|
+
participant Hydrate as Hydrator
|
|
105
|
+
|
|
106
|
+
App->>Reg: registry.get("user-card")(ctx)
|
|
107
|
+
Reg->>RPC: call(["ui", "user-card"], input)
|
|
108
|
+
RPC->>Server: Execute procedure
|
|
109
|
+
Server-->>RPC: ComponentOutput
|
|
110
|
+
RPC-->>Reg: Descriptor
|
|
111
|
+
Reg-->>App: ComponentOutput
|
|
112
|
+
|
|
113
|
+
App->>Hydrate: hydrate(descriptor)
|
|
114
|
+
Hydrate->>Hydrate: Map to React components
|
|
115
|
+
Hydrate-->>App: React.Element
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Registry Types
|
|
119
|
+
|
|
120
|
+
```mermaid
|
|
121
|
+
graph TB
|
|
122
|
+
subgraph "ClientRegistry"
|
|
123
|
+
CR[Sync calls]
|
|
124
|
+
CROut[Single ComponentOutput]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
subgraph "StreamingRegistry"
|
|
128
|
+
SR[Async streams]
|
|
129
|
+
SROut[AsyncIterable of ComponentOutput]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
subgraph "DualRegistry"
|
|
133
|
+
DR[Both modes]
|
|
134
|
+
DROut[Sync + Streaming]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
CR --> CROut
|
|
138
|
+
SR --> SROut
|
|
139
|
+
DR --> DROut
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Quick Start
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { createClientRegistry, createReactHydrate } from "@procedures/splay";
|
|
148
|
+
import { call } from "client";
|
|
149
|
+
|
|
150
|
+
// 1. Create registry backed by client procedures
|
|
151
|
+
const registry = createClientRegistry(call, { namespace: "ui" });
|
|
152
|
+
|
|
153
|
+
// 2. Create hydrator for React
|
|
154
|
+
const hydrate = createReactHydrate(React, {
|
|
155
|
+
components: {
|
|
156
|
+
"user-card": UserCard,
|
|
157
|
+
"badge": Badge,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// 3. Render a component
|
|
162
|
+
const ctx = { data: { userId: 123 }, size: { width: 400, height: 300 } };
|
|
163
|
+
const descriptor = await registry.get("user-card")!(ctx);
|
|
164
|
+
const element = hydrate(descriptor);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## API Reference
|
|
170
|
+
|
|
171
|
+
### Registry
|
|
172
|
+
|
|
173
|
+
#### ClientRegistry
|
|
174
|
+
|
|
175
|
+
Sync registry for single-response component calls.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const registry = createClientRegistry(call, {
|
|
179
|
+
namespace?: string, // Procedure path prefix (e.g., "ui")
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Usage
|
|
183
|
+
const descriptor = await registry.get("user-card")!(ctx);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### StreamingRegistry
|
|
187
|
+
|
|
188
|
+
Streaming registry for multiple responses over time.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const streaming = createStreamingRegistry(stream, {
|
|
192
|
+
namespace?: string,
|
|
193
|
+
bufferSize?: number, // Default: 16
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Usage
|
|
197
|
+
for await (const descriptor of streaming.get("live-feed")!(ctx)) {
|
|
198
|
+
render(descriptor);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### DualRegistry
|
|
203
|
+
|
|
204
|
+
Combined registry supporting both sync and streaming.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const dual = createDualRegistry(call, stream, isStreaming, options);
|
|
208
|
+
|
|
209
|
+
// Automatically chooses based on component type
|
|
210
|
+
const result = await dual.get("component-name")!(ctx);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### Hydration
|
|
216
|
+
|
|
217
|
+
#### createHydrate (Generic)
|
|
218
|
+
|
|
219
|
+
Framework-agnostic hydration.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
const hydrate = createHydrate<TComponent>({
|
|
223
|
+
components: ComponentMap, // Map of type -> component
|
|
224
|
+
fallback?: Component, // For unknown types
|
|
225
|
+
onNull?: () => TComponent, // Handle null descriptors
|
|
226
|
+
onFragment?: (children) => TComponent, // Handle fragments
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### createReactHydrate
|
|
231
|
+
|
|
232
|
+
React-specific hydration with createElement integration.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const hydrate = createReactHydrate(React, {
|
|
236
|
+
components: {
|
|
237
|
+
"user-card": UserCard,
|
|
238
|
+
"badge": Badge,
|
|
239
|
+
"button": Button,
|
|
240
|
+
},
|
|
241
|
+
fallback?: UnknownComponent,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Usage
|
|
245
|
+
const element = hydrate(descriptor);
|
|
246
|
+
// Returns: React.Element
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### Descriptor Utilities
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// Traverse descriptor tree
|
|
255
|
+
walkDescriptor(descriptor, (node, path) => {
|
|
256
|
+
console.log(`Node at ${path.join(".")}: ${node.type}`);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Transform nodes
|
|
260
|
+
const transformed = transformDescriptor(descriptor, node => ({
|
|
261
|
+
...node,
|
|
262
|
+
props: { ...node.props, className: "styled" }
|
|
263
|
+
}));
|
|
264
|
+
|
|
265
|
+
// Find matching nodes
|
|
266
|
+
const buttons = findInDescriptor(descriptor, n => n.type === "button");
|
|
267
|
+
|
|
268
|
+
// Get all component types used
|
|
269
|
+
const types: Set<string> = getUsedTypes(descriptor);
|
|
270
|
+
|
|
271
|
+
// Validate against known types
|
|
272
|
+
const { valid, unknownTypes } = validateDescriptor(descriptor, knownTypes);
|
|
273
|
+
|
|
274
|
+
// Serialization
|
|
275
|
+
const json = serializeDescriptor(descriptor);
|
|
276
|
+
const parsed = parseDescriptor(json);
|
|
277
|
+
const compact = compactDescriptor(descriptor); // Remove empty values
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### Stream Utilities
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// Merge multiple component streams
|
|
286
|
+
for await (const output of mergeStreams(stream1, stream2)) {
|
|
287
|
+
render(output);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Rate limiting
|
|
291
|
+
for await (const output of throttleStream(stream, 100)) {
|
|
292
|
+
// Max 1 update per 100ms
|
|
293
|
+
render(output);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
for await (const output of debounceStream(stream, 200)) {
|
|
297
|
+
// Wait 200ms of quiet before emitting
|
|
298
|
+
render(output);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## ComponentOutput Format
|
|
305
|
+
|
|
306
|
+
The wire format for component descriptors:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
interface ComponentOutput {
|
|
310
|
+
type: string; // Component type or "__null__" / "__fragment__"
|
|
311
|
+
props: Record<string, unknown>; // Component props
|
|
312
|
+
children?: ComponentOutput[]; // Nested children
|
|
313
|
+
key?: string | number; // React key
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Special Types
|
|
318
|
+
|
|
319
|
+
```mermaid
|
|
320
|
+
graph LR
|
|
321
|
+
subgraph "Standard"
|
|
322
|
+
Component["type: 'user-card'"]
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
subgraph "Null"
|
|
326
|
+
Null["type: '__null__'<br/>Renders nothing"]
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
subgraph "Fragment"
|
|
330
|
+
Fragment["type: '__fragment__'<br/>Wraps children"]
|
|
331
|
+
end
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Example Descriptor
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
{
|
|
338
|
+
"type": "user-card",
|
|
339
|
+
"props": { "userId": 123, "showBadge": true },
|
|
340
|
+
"children": [
|
|
341
|
+
{
|
|
342
|
+
"type": "badge",
|
|
343
|
+
"props": { "label": "Admin" }
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Integration
|
|
352
|
+
|
|
353
|
+
### Built-in Procedures
|
|
354
|
+
|
|
355
|
+
| Path | Description |
|
|
356
|
+
|------|-------------|
|
|
357
|
+
| `splay.bridge.info` | Package information |
|
|
358
|
+
| `splay.bridge.health` | Health check |
|
|
359
|
+
|
|
360
|
+
### Re-exports from client/components
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import {
|
|
364
|
+
// Types
|
|
365
|
+
ComponentOutput, FragmentOutput, NullOutput, Size,
|
|
366
|
+
ComponentContext, ComponentFactory, ComponentDefinition,
|
|
367
|
+
|
|
368
|
+
// Helpers
|
|
369
|
+
nullOutput, fragment, isFragment, isNullOutput,
|
|
370
|
+
defineComponent, simpleComponent, streamingComponent,
|
|
371
|
+
} from "@procedures/splay";
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Package Hierarchy
|
|
375
|
+
|
|
376
|
+
```mermaid
|
|
377
|
+
graph BT
|
|
378
|
+
subgraph "UI Layer"
|
|
379
|
+
SplayReact[splay-react]
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
subgraph "Bridge"
|
|
383
|
+
ClientSplay[client-splay]
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
subgraph "Core"
|
|
387
|
+
Splay[splay]
|
|
388
|
+
Client[client]
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
SplayReact --> ClientSplay
|
|
392
|
+
ClientSplay --> Splay
|
|
393
|
+
ClientSplay --> Client
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Requirements
|
|
399
|
+
|
|
400
|
+
- **Node.js** >= 25
|
|
401
|
+
- **Dependencies:**
|
|
402
|
+
- `@procedures/client`
|
|
403
|
+
- `@mark1russell7/splay`
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## License
|
|
408
|
+
|
|
409
|
+
MIT
|