@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/src/index.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* client-splay
|
|
3
|
+
*
|
|
4
|
+
* Bridge between splay's rendering model and client's procedure system.
|
|
5
|
+
*
|
|
6
|
+
* This package enables:
|
|
7
|
+
* - Using client procedures as splay renderers
|
|
8
|
+
* - Streaming component updates via procedures
|
|
9
|
+
* - Hydrating serializable descriptors to framework components
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { call } from "client";
|
|
14
|
+
* import {
|
|
15
|
+
* createClientRegistry,
|
|
16
|
+
* createReactHydrate,
|
|
17
|
+
* } from "@procedures/splay";
|
|
18
|
+
*
|
|
19
|
+
* // Create a registry backed by client procedures
|
|
20
|
+
* const registry = createClientRegistry(call, {
|
|
21
|
+
* namespace: "ui",
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Hydrate descriptors to React components
|
|
25
|
+
* const hydrate = createReactHydrate(React, {
|
|
26
|
+
* components: { "user-card": UserCard },
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Render a component
|
|
30
|
+
* const descriptor = await registry.get("user-card")!(ctx);
|
|
31
|
+
* const element = hydrate(descriptor);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// Importing the package registers its procedures, as in the other client packages (BUGS-2026-07 H18)
|
|
36
|
+
import "./register.js";
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Types
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
export type {
|
|
43
|
+
// Render context
|
|
44
|
+
RenderContext,
|
|
45
|
+
|
|
46
|
+
// Registry interfaces
|
|
47
|
+
ComponentRenderer,
|
|
48
|
+
StreamingComponentRenderer,
|
|
49
|
+
Registry,
|
|
50
|
+
StreamingRegistry,
|
|
51
|
+
|
|
52
|
+
// Registry options
|
|
53
|
+
ClientRegistryOptions,
|
|
54
|
+
StreamingRegistryOptions,
|
|
55
|
+
|
|
56
|
+
// Hydration
|
|
57
|
+
ComponentMap,
|
|
58
|
+
HydrateOptions,
|
|
59
|
+
} from "./types.js";
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// Registry
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
createClientRegistry,
|
|
67
|
+
createCheckedRegistry,
|
|
68
|
+
createRenderContext,
|
|
69
|
+
type ProcedureCaller,
|
|
70
|
+
} from "./registry.js";
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// Streaming
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
createStreamingRegistry,
|
|
78
|
+
createDualRegistry,
|
|
79
|
+
mergeStreams,
|
|
80
|
+
throttleStream,
|
|
81
|
+
debounceStream,
|
|
82
|
+
type StreamingProcedureCaller,
|
|
83
|
+
type DualRegistry,
|
|
84
|
+
} from "./streaming.js";
|
|
85
|
+
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// Hydration
|
|
88
|
+
// =============================================================================
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
createHydrate,
|
|
92
|
+
createReactHydrate,
|
|
93
|
+
walkDescriptor,
|
|
94
|
+
transformDescriptor,
|
|
95
|
+
findInDescriptor,
|
|
96
|
+
getUsedTypes,
|
|
97
|
+
validateDescriptor,
|
|
98
|
+
serializeDescriptor,
|
|
99
|
+
parseDescriptor,
|
|
100
|
+
compactDescriptor,
|
|
101
|
+
type CreateElement,
|
|
102
|
+
type ReactHydrateOptions,
|
|
103
|
+
} from "./hydrate.js";
|
|
104
|
+
|
|
105
|
+
// =============================================================================
|
|
106
|
+
// Components
|
|
107
|
+
// =============================================================================
|
|
108
|
+
|
|
109
|
+
// The components: serializable UI descriptors made by procedures. They moved here from
|
|
110
|
+
// "@procedures/client/components" (roadmap 3.4: this is the UI package of the ecosystem).
|
|
111
|
+
export * from "./components/index.js";
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module (no side effects): the bridge procedures as data, for `createRegistry()`.
|
|
3
|
+
*
|
|
4
|
+
* Procedure Registration
|
|
5
|
+
*
|
|
6
|
+
* Registers client-splay bridge procedures with the client system.
|
|
7
|
+
* This file is referenced by package.json's client.procedures field.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createProcedure, outputSchema, defineModule, type AnyProcedure, type ProcedureModule } from "@procedures/client";
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
interface BridgeInfo {
|
|
17
|
+
name: string;
|
|
18
|
+
version: string;
|
|
19
|
+
description: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface HealthCheck {
|
|
23
|
+
status: string;
|
|
24
|
+
timestamp: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Schemas
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
const voidSchema = outputSchema<void>();
|
|
32
|
+
const bridgeInfoSchema = outputSchema<BridgeInfo>();
|
|
33
|
+
const healthCheckSchema = outputSchema<HealthCheck>();
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Bridge Procedures
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get package info.
|
|
41
|
+
*/
|
|
42
|
+
const infoProcedure = createProcedure()
|
|
43
|
+
.path(["splay", "bridge", "info"])
|
|
44
|
+
.input(voidSchema)
|
|
45
|
+
.output(bridgeInfoSchema)
|
|
46
|
+
.meta({ description: "Get client-splay bridge information" })
|
|
47
|
+
.handler(() => ({
|
|
48
|
+
name: "@procedures/splay",
|
|
49
|
+
version: "1.0.0",
|
|
50
|
+
description: "Bridge between splay and client",
|
|
51
|
+
}))
|
|
52
|
+
.build();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Health check for the bridge.
|
|
56
|
+
*/
|
|
57
|
+
const healthProcedure = createProcedure()
|
|
58
|
+
.path(["splay", "bridge", "health"])
|
|
59
|
+
.input(voidSchema)
|
|
60
|
+
.output(healthCheckSchema)
|
|
61
|
+
.meta({ description: "Health check for client-splay bridge" })
|
|
62
|
+
.handler(() => ({
|
|
63
|
+
status: "healthy",
|
|
64
|
+
timestamp: new Date().toISOString(),
|
|
65
|
+
}))
|
|
66
|
+
.build();
|
|
67
|
+
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// Registration
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The module of this package, for `createRegistry()`: its procedures as data. Importing this
|
|
74
|
+
* file registers nothing. (`register.ts` registers the same procedures in the global registry.)
|
|
75
|
+
*/
|
|
76
|
+
export const splayModule: ProcedureModule = defineModule("@procedures/splay", [
|
|
77
|
+
infoProcedure as AnyProcedure,
|
|
78
|
+
healthProcedure as AnyProcedure,
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
export default splayModule;
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing this file registers the bridge procedures of @procedures/splay in the
|
|
3
|
+
* global registry (DECISIONS-2026-10, "Every package root registers its procedures"). The
|
|
4
|
+
* procedures as data are in `module.ts`, which registers nothing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PROCEDURE_REGISTRY } from "@procedures/client";
|
|
8
|
+
import { splayModule } from "./module.js";
|
|
9
|
+
|
|
10
|
+
export * from "./module.js";
|
|
11
|
+
|
|
12
|
+
export function registerBridge(): void {
|
|
13
|
+
// override: calling the exported function again (after the auto-register) must not throw
|
|
14
|
+
PROCEDURE_REGISTRY.registerAll(splayModule.procedures, { override: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Auto-register
|
|
18
|
+
registerBridge();
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Registry
|
|
3
|
+
*
|
|
4
|
+
* Creates a splay-compatible Registry that delegates to client procedures.
|
|
5
|
+
* This enables splay's rendering engine to use client's procedure system
|
|
6
|
+
* for component resolution and execution.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ComponentOutput, Size } from "./components/index.js";
|
|
10
|
+
import type {
|
|
11
|
+
Registry,
|
|
12
|
+
ComponentRenderer,
|
|
13
|
+
RenderContext,
|
|
14
|
+
ClientRegistryOptions,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Client Procedure Caller Type
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Type for calling client procedures.
|
|
23
|
+
* This matches the shape of client's call function.
|
|
24
|
+
*/
|
|
25
|
+
export interface ProcedureCaller {
|
|
26
|
+
<TResult>(path: string[], input: unknown): Promise<TResult>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Client Registry Implementation
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a splay-compatible registry backed by client procedures.
|
|
35
|
+
*
|
|
36
|
+
* @param call - Client's procedure call function
|
|
37
|
+
* @param options - Registry options
|
|
38
|
+
* @returns Registry instance
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* import { call } from "client";
|
|
43
|
+
* import { createClientRegistry } from "@procedures/splay";
|
|
44
|
+
*
|
|
45
|
+
* const registry = createClientRegistry(call, {
|
|
46
|
+
* namespace: "ui",
|
|
47
|
+
* defaultSize: { width: 800, height: 600 },
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* // Now pass to splay's renderer
|
|
51
|
+
* const renderer = createRenderer({ registry });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createClientRegistry(
|
|
55
|
+
call: ProcedureCaller,
|
|
56
|
+
options: ClientRegistryOptions = {}
|
|
57
|
+
): Registry {
|
|
58
|
+
const { namespace } = options;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build the procedure path for a component type.
|
|
62
|
+
*/
|
|
63
|
+
function buildPath(type: string): string[] {
|
|
64
|
+
const basePath = ["components"];
|
|
65
|
+
if (namespace) {
|
|
66
|
+
basePath.push(namespace);
|
|
67
|
+
}
|
|
68
|
+
basePath.push(type);
|
|
69
|
+
return basePath;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a renderer function for a component type.
|
|
74
|
+
*/
|
|
75
|
+
function createRenderer(type: string): ComponentRenderer {
|
|
76
|
+
const procedurePath = buildPath(type);
|
|
77
|
+
|
|
78
|
+
return async (ctx: RenderContext): Promise<ComponentOutput> => {
|
|
79
|
+
// Build the input for the component procedure
|
|
80
|
+
const input = {
|
|
81
|
+
data: ctx.data,
|
|
82
|
+
size: ctx.size,
|
|
83
|
+
path: ctx.path,
|
|
84
|
+
depth: ctx.depth,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Call the procedure
|
|
88
|
+
const result = await call<ComponentOutput>(procedurePath, input);
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Cache of created renderers
|
|
94
|
+
const rendererCache = new Map<string, ComponentRenderer>();
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
/**
|
|
98
|
+
* Get a renderer for a component type.
|
|
99
|
+
* Creates and caches the renderer on first access.
|
|
100
|
+
*/
|
|
101
|
+
get(type: string): ComponentRenderer | undefined {
|
|
102
|
+
// Check cache first
|
|
103
|
+
let renderer = rendererCache.get(type);
|
|
104
|
+
if (renderer) {
|
|
105
|
+
return renderer;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Create and cache renderer
|
|
109
|
+
// Note: We always create a renderer - the procedure may or may not exist
|
|
110
|
+
// Errors will be thrown at call time if the procedure doesn't exist
|
|
111
|
+
renderer = createRenderer(type);
|
|
112
|
+
rendererCache.set(type, renderer);
|
|
113
|
+
return renderer;
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if a renderer exists.
|
|
118
|
+
* Note: This always returns true since we lazily create renderers.
|
|
119
|
+
* Actual existence is checked at call time.
|
|
120
|
+
*/
|
|
121
|
+
has(_type: string): boolean {
|
|
122
|
+
// We optimistically return true - actual check happens at call time
|
|
123
|
+
// This matches splay's expectation that has() is a quick check
|
|
124
|
+
return true;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Register a renderer (not supported - use client procedures).
|
|
129
|
+
*/
|
|
130
|
+
register(type: string, _renderer: ComponentRenderer): void {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Cannot register renderers directly on ClientRegistry. ` +
|
|
133
|
+
`Define a component procedure at path: ${buildPath(type).join(".")}`
|
|
134
|
+
);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// Registry with Existence Check
|
|
141
|
+
// =============================================================================
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a registry that can check procedure existence.
|
|
145
|
+
* Requires an additional hasProcedure function from client.
|
|
146
|
+
*
|
|
147
|
+
* @param call - Client's procedure call function
|
|
148
|
+
* @param hasProcedure - Function to check if a procedure exists
|
|
149
|
+
* @param options - Registry options
|
|
150
|
+
* @returns Registry with accurate has() checks
|
|
151
|
+
*/
|
|
152
|
+
export function createCheckedRegistry(
|
|
153
|
+
call: ProcedureCaller,
|
|
154
|
+
hasProcedure: (path: string[]) => boolean,
|
|
155
|
+
options: ClientRegistryOptions = {}
|
|
156
|
+
): Registry {
|
|
157
|
+
const baseRegistry = createClientRegistry(call, options);
|
|
158
|
+
const { namespace } = options;
|
|
159
|
+
|
|
160
|
+
function buildPath(type: string): string[] {
|
|
161
|
+
const basePath = ["components"];
|
|
162
|
+
if (namespace) {
|
|
163
|
+
basePath.push(namespace);
|
|
164
|
+
}
|
|
165
|
+
basePath.push(type);
|
|
166
|
+
return basePath;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
...baseRegistry,
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if a procedure exists for this component type.
|
|
174
|
+
*/
|
|
175
|
+
has(type: string): boolean {
|
|
176
|
+
return hasProcedure(buildPath(type));
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// =============================================================================
|
|
182
|
+
// Render Context Factory
|
|
183
|
+
// =============================================================================
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a render context for calling component procedures.
|
|
187
|
+
*
|
|
188
|
+
* @param registry - The registry to use for child renders
|
|
189
|
+
* @param data - Data to render
|
|
190
|
+
* @param size - Available size
|
|
191
|
+
* @param path - Path in render tree
|
|
192
|
+
* @param depth - Current depth
|
|
193
|
+
* @returns RenderContext for component procedures
|
|
194
|
+
*/
|
|
195
|
+
export function createRenderContext<TData>(
|
|
196
|
+
registry: Registry,
|
|
197
|
+
data: TData,
|
|
198
|
+
size: Size,
|
|
199
|
+
path: string = "root",
|
|
200
|
+
depth: number = 0
|
|
201
|
+
): RenderContext<TData> {
|
|
202
|
+
return {
|
|
203
|
+
data,
|
|
204
|
+
size,
|
|
205
|
+
path,
|
|
206
|
+
depth,
|
|
207
|
+
render: async (childData: unknown, childSize: Size, childPath: string) => {
|
|
208
|
+
// Determine component type from data
|
|
209
|
+
const type = getComponentType(childData);
|
|
210
|
+
if (!type) {
|
|
211
|
+
throw new Error(`Cannot determine component type for data at ${childPath}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const renderer = registry.get(type);
|
|
215
|
+
if (!renderer) {
|
|
216
|
+
throw new Error(`No renderer found for component type: ${type}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Create child context
|
|
220
|
+
const childCtx = createRenderContext(
|
|
221
|
+
registry,
|
|
222
|
+
childData,
|
|
223
|
+
childSize,
|
|
224
|
+
childPath,
|
|
225
|
+
depth + 1
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return renderer(childCtx);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Extract component type from data.
|
|
235
|
+
* Looks for a `type` or `__type__` property.
|
|
236
|
+
*/
|
|
237
|
+
function getComponentType(data: unknown): string | null {
|
|
238
|
+
if (data === null || typeof data !== "object") {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const obj = data as Record<string, unknown>;
|
|
243
|
+
|
|
244
|
+
// Check for type property
|
|
245
|
+
const typeValue = obj["type"];
|
|
246
|
+
if (typeof typeValue === "string") {
|
|
247
|
+
return typeValue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check for __type__ property (alternative convention)
|
|
251
|
+
const altTypeValue = obj["__type__"];
|
|
252
|
+
if (typeof altTypeValue === "string") {
|
|
253
|
+
return altTypeValue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return null;
|
|
257
|
+
}
|