@procedures/splay 0.0.0-stage → 0.2.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +408 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/components/define.d.ts +134 -0
  6. package/dist/components/define.d.ts.map +1 -0
  7. package/dist/components/define.js +486 -0
  8. package/dist/components/define.js.map +1 -0
  9. package/dist/components/index.d.ts +39 -0
  10. package/dist/components/index.d.ts.map +1 -0
  11. package/dist/components/index.js +38 -0
  12. package/dist/components/index.js.map +1 -0
  13. package/dist/components/types.d.ts +264 -0
  14. package/dist/components/types.d.ts.map +1 -0
  15. package/dist/components/types.js +46 -0
  16. package/dist/components/types.js.map +1 -0
  17. package/dist/hydrate.d.ts +126 -0
  18. package/dist/hydrate.d.ts.map +1 -0
  19. package/dist/hydrate.js +226 -0
  20. package/dist/hydrate.js.map +1 -0
  21. package/dist/index.d.ts +40 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +53 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/module.d.ts +24 -0
  26. package/dist/module.d.ts.map +1 -0
  27. package/dist/module.js +68 -0
  28. package/dist/module.js.map +1 -0
  29. package/dist/register.d.ts +7 -0
  30. package/dist/register.d.ts.map +1 -0
  31. package/dist/register.js +9 -0
  32. package/dist/register.js.map +1 -0
  33. package/dist/registry.d.ts +60 -0
  34. package/dist/registry.d.ts.map +1 -0
  35. package/dist/registry.js +191 -0
  36. package/dist/registry.js.map +1 -0
  37. package/dist/streaming.d.ts +94 -0
  38. package/dist/streaming.d.ts.map +1 -0
  39. package/dist/streaming.js +422 -0
  40. package/dist/streaming.js.map +1 -0
  41. package/dist/types.d.ts +85 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +7 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +76 -3
  46. package/src/components/define.ts +600 -0
  47. package/src/components/index.ts +78 -0
  48. package/src/components/types.ts +357 -0
  49. package/src/hydrate.ts +355 -0
  50. package/src/index.ts +110 -0
  51. package/src/module.ts +93 -0
  52. package/src/register.ts +11 -0
  53. package/src/registry.ts +257 -0
  54. package/src/streaming.ts +554 -0
  55. package/src/types.ts +114 -0
@@ -0,0 +1,600 @@
1
+ /**
2
+ * Component Definition Helpers
3
+ *
4
+ * Utilities for defining components as procedures.
5
+ * Components are procedures with special handling for UI rendering.
6
+ */
7
+
8
+ import type { ZodLike } from "@procedures/client/client";
9
+ import type { Procedure, ProcedurePath, ProcedureContext } from "@procedures/client";
10
+ import { PROCEDURE_REGISTRY, type ProcedureRegistry } from "@procedures/client";
11
+ import { invokeProcedure, isAsyncIterable } from "@procedures/client";
12
+ import type {
13
+ ComponentDefinition,
14
+ ComponentContext,
15
+ ComponentOutput,
16
+ ComponentBundle,
17
+ AnyComponentDefinition,
18
+ AnyComponentFactory,
19
+ Size,
20
+ ComponentMetadata,
21
+ } from "./types.js";
22
+ import { isStreamingFactory } from "./types.js";
23
+
24
+ // =============================================================================
25
+ // Component Input Schema
26
+ // =============================================================================
27
+
28
+ /**
29
+ * Standard component input structure.
30
+ * Components receive data, size, and path for rendering.
31
+ */
32
+ export interface ComponentInput<TData = unknown> {
33
+ /** Component data */
34
+ data: TData;
35
+ /** Available size for rendering */
36
+ size: Size;
37
+ /** Path in render tree */
38
+ path: string;
39
+ /** Depth in render tree */
40
+ depth?: number;
41
+ }
42
+
43
+ /**
44
+ * Default component input schema.
45
+ * Validates the standard { data, size, path } structure.
46
+ */
47
+ export const componentInputSchema: ZodLike<ComponentInput> = {
48
+ parse(value: unknown): ComponentInput {
49
+ const result = this.safeParse(value);
50
+ if (!result.success) {
51
+ throw new Error(result.error.message);
52
+ }
53
+ return result.data;
54
+ },
55
+ safeParse(value: unknown) {
56
+ if (!value || typeof value !== "object") {
57
+ return {
58
+ success: false as const,
59
+ error: { message: "Expected object", errors: [{ path: [], message: "Expected object" }] },
60
+ };
61
+ }
62
+
63
+ const input = value as Record<string, unknown>;
64
+
65
+ if (!("data" in input)) {
66
+ return {
67
+ success: false as const,
68
+ error: { message: "Missing required field: data", errors: [{ path: ["data"], message: "Missing required field: data" }] },
69
+ };
70
+ }
71
+
72
+ if (!("size" in input) || typeof input["size"] !== "object") {
73
+ return {
74
+ success: false as const,
75
+ error: { message: "Missing or invalid field: size", errors: [{ path: ["size"], message: "Missing or invalid field: size" }] },
76
+ };
77
+ }
78
+
79
+ const size = input["size"] as Record<string, unknown>;
80
+ if (typeof size["width"] !== "number" || typeof size["height"] !== "number") {
81
+ return {
82
+ success: false as const,
83
+ error: { message: "Size must have numeric width and height", errors: [{ path: ["size"], message: "Size must have numeric width and height" }] },
84
+ };
85
+ }
86
+
87
+ if (!("path" in input) || typeof input["path"] !== "string") {
88
+ return {
89
+ success: false as const,
90
+ error: { message: "Missing or invalid field: path", errors: [{ path: ["path"], message: "Missing or invalid field: path" }] },
91
+ };
92
+ }
93
+
94
+ return {
95
+ success: true as const,
96
+ data: input as unknown as ComponentInput,
97
+ };
98
+ },
99
+ };
100
+
101
+ /**
102
+ * Component output schema.
103
+ * Validates that output is a serializable ComponentOutput.
104
+ */
105
+ export const componentOutputSchema: ZodLike<ComponentOutput> = {
106
+ parse(value: unknown): ComponentOutput {
107
+ const result = this.safeParse(value);
108
+ if (!result.success) {
109
+ throw new Error(result.error.message);
110
+ }
111
+ return result.data;
112
+ },
113
+ safeParse(value: unknown) {
114
+ if (!value || typeof value !== "object") {
115
+ return {
116
+ success: false as const,
117
+ error: { message: "Expected object", errors: [{ path: [], message: "Expected object" }] },
118
+ };
119
+ }
120
+
121
+ const output = value as Record<string, unknown>;
122
+
123
+ if (typeof output["type"] !== "string") {
124
+ return {
125
+ success: false as const,
126
+ error: { message: "Missing or invalid field: type", errors: [{ path: ["type"], message: "Missing or invalid field: type" }] },
127
+ };
128
+ }
129
+
130
+ if (!("props" in output) || typeof output["props"] !== "object") {
131
+ return {
132
+ success: false as const,
133
+ error: { message: "Missing or invalid field: props", errors: [{ path: ["props"], message: "Missing or invalid field: props" }] },
134
+ };
135
+ }
136
+
137
+ return {
138
+ success: true as const,
139
+ data: output as unknown as ComponentOutput,
140
+ };
141
+ },
142
+ };
143
+
144
+ // =============================================================================
145
+ // defineComponent
146
+ // =============================================================================
147
+
148
+ /**
149
+ * Define a component and convert it to a procedure.
150
+ *
151
+ * Components are registered under ["components", namespace?, type].
152
+ *
153
+ * @param definition - Component definition
154
+ * @returns The component definition (for chaining)
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * // Simple component
159
+ * const userCard = defineComponent({
160
+ * type: "user-card",
161
+ * namespace: "ui",
162
+ * input: userSchema,
163
+ * factory: (ctx) => ({
164
+ * type: "user-card",
165
+ * props: { user: ctx.data },
166
+ * }),
167
+ * });
168
+ *
169
+ * // Streaming component
170
+ * const liveDashboard = defineComponent({
171
+ * type: "live-dashboard",
172
+ * streaming: true,
173
+ * factory: async function* (ctx) {
174
+ * yield { type: "dashboard", props: { loading: true } };
175
+ * for await (const data of ctx.bus.stream("updates")) {
176
+ * yield { type: "dashboard", props: { data } };
177
+ * }
178
+ * },
179
+ * });
180
+ * ```
181
+ */
182
+ export function defineComponent<TData = unknown>(
183
+ definition: ComponentDefinition<TData>
184
+ ): ComponentDefinition<TData> {
185
+ // Build the procedure path
186
+ const path: ProcedurePath = definition.namespace
187
+ ? ["components", definition.namespace, definition.type]
188
+ : ["components", definition.type];
189
+
190
+ // Create the procedure from the component
191
+ const procedure = componentToProcedure(definition, path);
192
+
193
+ // Register the procedure
194
+ PROCEDURE_REGISTRY.register(procedure);
195
+
196
+ return definition;
197
+ }
198
+
199
+ /**
200
+ * Convert a component definition to a procedure.
201
+ *
202
+ * @param definition - Component definition
203
+ * @param path - Procedure path
204
+ * @returns Procedure that wraps the component
205
+ */
206
+ export function componentToProcedure<TData>(
207
+ definition: ComponentDefinition<TData>,
208
+ path: ProcedurePath
209
+ ): Procedure<ComponentInput<TData>, ComponentOutput> {
210
+ const { factory, streaming, metadata } = definition;
211
+
212
+ // Build procedure metadata
213
+ const procedureMetadata: ComponentMetadata = {
214
+ ...metadata,
215
+ streaming: streaming ?? isStreamingFactory(factory),
216
+ tags: [...(metadata?.tags ?? []), "component"],
217
+ };
218
+
219
+ // Create handler based on factory type. With no `streaming` flag and a plain function, the
220
+ // handler gives the factory's result: one output, or the items of a generator that it returns.
221
+ const handler = streaming === true || (streaming === undefined && isStreamingFactory(factory))
222
+ ? createStreamingHandler(factory as AnyComponentFactory<TData>)
223
+ : streaming === false
224
+ ? createStandardHandler(factory as AnyComponentFactory<TData>)
225
+ : createAdaptiveHandler(factory as AnyComponentFactory<TData>);
226
+
227
+ return {
228
+ path,
229
+ input: definition.input
230
+ ? createWrappedInputSchema(definition.input)
231
+ : componentInputSchema as ZodLike<ComponentInput<TData>>,
232
+ output: componentOutputSchema,
233
+ metadata: procedureMetadata,
234
+ streaming: procedureMetadata.streaming ?? false,
235
+ handler,
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Create a wrapped input schema that validates both structure and data.
241
+ */
242
+ function createWrappedInputSchema<TData>(
243
+ dataSchema: ZodLike<TData>
244
+ ): ZodLike<ComponentInput<TData>> {
245
+ return {
246
+ parse(value: unknown): ComponentInput<TData> {
247
+ const result = this.safeParse(value);
248
+ if (!result.success) {
249
+ throw new Error(result.error.message);
250
+ }
251
+ return result.data;
252
+ },
253
+ safeParse(value: unknown) {
254
+ // First validate the structure
255
+ const structureResult = componentInputSchema.safeParse(value);
256
+ if (!structureResult.success) {
257
+ return structureResult;
258
+ }
259
+
260
+ // Then validate the data field
261
+ const input = structureResult.data as ComponentInput<unknown>;
262
+ const dataResult = dataSchema.safeParse(input.data);
263
+ if (!dataResult.success) {
264
+ return {
265
+ success: false as const,
266
+ error: {
267
+ message: `Invalid data: ${dataResult.error.message}`,
268
+ errors: (dataResult.error.issues ?? dataResult.error.errors ?? []).map((e) => ({
269
+ path: ["data", ...(e.path ?? [])],
270
+ message: e.message,
271
+ })),
272
+ },
273
+ };
274
+ }
275
+
276
+ return {
277
+ success: true as const,
278
+ data: { ...input, data: dataResult.data } as ComponentInput<TData>,
279
+ };
280
+ },
281
+ };
282
+ }
283
+
284
+ /**
285
+ * Create a standard (non-streaming) handler from a component factory.
286
+ */
287
+ function createStandardHandler<TData>(
288
+ factory: AnyComponentFactory<TData>
289
+ ): (input: ComponentInput<TData>, ctx: ProcedureContext) => Promise<ComponentOutput> {
290
+ return async (input, ctx) => {
291
+ const componentCtx = procedureToComponentContext(input, ctx);
292
+ const result = factory(componentCtx);
293
+
294
+ // Handle both sync and async returns
295
+ if (result instanceof Promise) {
296
+ return await result;
297
+ }
298
+
299
+ // Handle generator (shouldn't happen for standard, but just in case)
300
+ if (isAsyncGenerator(result)) {
301
+ // Take only the first yield, then end the generator so its finally blocks run
302
+ // (BUGS-2026-07 L3: it was left suspended)
303
+ try {
304
+ const { value, done } = await result.next();
305
+ if (done || value === undefined) {
306
+ throw new Error("Component factory yielded no output");
307
+ }
308
+ return value;
309
+ } finally {
310
+ await result.return?.(undefined);
311
+ }
312
+ }
313
+
314
+ return result as ComponentOutput;
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Create a handler that gives what the factory gives: one output, or a stream when the factory
320
+ * returns an async generator. `invokeProcedure` reads a returned generator as a stream.
321
+ */
322
+ function createAdaptiveHandler<TData>(
323
+ factory: AnyComponentFactory<TData>
324
+ ): (input: ComponentInput<TData>, ctx: ProcedureContext) => Promise<ComponentOutput> {
325
+ return async (input, ctx) => {
326
+ const result = factory(procedureToComponentContext(input, ctx));
327
+ if (isAsyncIterable(result)) {
328
+ // The handler type has no "maybe a stream" form: invokeProcedure tests the result
329
+ return result as unknown as ComponentOutput;
330
+ }
331
+ return await (result as ComponentOutput | Promise<ComponentOutput>);
332
+ };
333
+ }
334
+
335
+ /**
336
+ * Create a streaming handler from a component factory.
337
+ */
338
+ function createStreamingHandler<TData>(
339
+ factory: AnyComponentFactory<TData>
340
+ ): (input: ComponentInput<TData>, ctx: ProcedureContext) => AsyncGenerator<ComponentOutput, void, unknown> {
341
+ return async function* (input, ctx) {
342
+ const componentCtx = procedureToComponentContext(input, ctx);
343
+ const result = factory(componentCtx);
344
+
345
+ if (isAsyncGenerator(result)) {
346
+ yield* result;
347
+ } else {
348
+ // Convert single result to single yield
349
+ const output = result instanceof Promise ? await result : result;
350
+ yield output as ComponentOutput;
351
+ }
352
+ };
353
+ }
354
+
355
+ /**
356
+ * Convert procedure context to component context.
357
+ */
358
+ function procedureToComponentContext<TData>(
359
+ input: ComponentInput<TData>,
360
+ ctx: ProcedureContext
361
+ ): ComponentContext<TData> {
362
+ const result: ComponentContext<TData> = {
363
+ data: input.data,
364
+ size: input.size,
365
+ path: input.path,
366
+ depth: input.depth ?? 0,
367
+ metadata: ctx.metadata,
368
+ render: createRenderFunction(ctx),
369
+ };
370
+ if (ctx.bus) {
371
+ result.bus = ctx.bus;
372
+ }
373
+ return result;
374
+ }
375
+
376
+ /**
377
+ * Create a render function for child components.
378
+ * This delegates to the procedure registry.
379
+ */
380
+ function createRenderFunction(
381
+ ctx: ProcedureContext
382
+ ): ComponentContext["render"] {
383
+ return async (data, size, path) => {
384
+ // Determine component type from data
385
+ // This is a simple heuristic - in practice, the renderer would decide
386
+ const type = inferComponentType(data);
387
+
388
+ if (!type) {
389
+ // Return a passthrough if we can't determine the type
390
+ return {
391
+ type: "raw",
392
+ props: { data },
393
+ };
394
+ }
395
+
396
+ // Find the component procedure: in the namespace of the component that renders, then
397
+ // the global one (BUGS-2026-07 L3: namespaced components were never found). A context
398
+ // that names its registry is searched first.
399
+ const registry = (ctx as ProcedureContext & { registry?: ProcedureRegistry }).registry ?? PROCEDURE_REGISTRY;
400
+ const namespace = ctx.path?.length === 3 ? ctx.path[1] : undefined;
401
+ const procedure =
402
+ (namespace !== undefined ? registry.get(["components", namespace, type]) : undefined) ??
403
+ registry.get(["components", type]);
404
+ if (!procedure || !procedure.handler) {
405
+ return {
406
+ type: "unknown",
407
+ props: { type, data },
408
+ };
409
+ }
410
+
411
+ // Call the procedure
412
+ const input: ComponentInput = {
413
+ data,
414
+ size,
415
+ path,
416
+ depth: (path.match(/\./g) || []).length,
417
+ };
418
+
419
+ // The child runs through the invocation path: its input and output are validated, and its
420
+ // context has its own path. (Before, the parent called the handler directly, with no
421
+ // validation and with the parent's context.)
422
+ const options: Parameters<typeof invokeProcedure>[2] = { metadata: ctx.metadata, client: ctx.client };
423
+ if (ctx.signal) options.signal = ctx.signal;
424
+ if (ctx.bus) options.bus = ctx.bus;
425
+ if (ctx.repository) options.repository = ctx.repository;
426
+ const output = await invokeProcedure<ComponentOutput>(procedure, input, options);
427
+ if (output.kind === "value") {
428
+ return output.value;
429
+ }
430
+
431
+ // A streaming child: take the first output, then end the stream so its finally blocks run
432
+ try {
433
+ const { value, done } = await output.items.next();
434
+ if (done || value === undefined) {
435
+ throw new Error("Child component yielded no output");
436
+ }
437
+ return value;
438
+ } finally {
439
+ await output.items.return(undefined);
440
+ }
441
+ };
442
+ }
443
+
444
+ /**
445
+ * Infer component type from data.
446
+ * This is a placeholder - real apps would use a type field or registry lookup.
447
+ */
448
+ function inferComponentType(data: unknown): string | null {
449
+ if (!data || typeof data !== "object") {
450
+ return null;
451
+ }
452
+
453
+ const obj = data as Record<string, unknown>;
454
+
455
+ // Check for explicit __component field
456
+ if (typeof obj["__component"] === "string") {
457
+ return obj["__component"];
458
+ }
459
+
460
+ // Check for type field
461
+ if (typeof obj["type"] === "string") {
462
+ return obj["type"];
463
+ }
464
+
465
+ return null;
466
+ }
467
+
468
+ /**
469
+ * Check if a value is an async generator.
470
+ */
471
+ function isAsyncGenerator(value: unknown): value is AsyncGenerator {
472
+ return (
473
+ value !== null &&
474
+ typeof value === "object" &&
475
+ Symbol.asyncIterator in value &&
476
+ typeof (value as any).next === "function"
477
+ );
478
+ }
479
+
480
+ // =============================================================================
481
+ // Bundle Registration
482
+ // =============================================================================
483
+
484
+ /**
485
+ * Register a bundle of components.
486
+ *
487
+ * @param bundle - Component bundle
488
+ * @returns The registered components
489
+ *
490
+ * @example
491
+ * ```typescript
492
+ * const registered = registerBundle({
493
+ * namespace: "ui",
494
+ * components: [buttonComponent, cardComponent],
495
+ * });
496
+ * ```
497
+ */
498
+ export function registerBundle(bundle: ComponentBundle): AnyComponentDefinition[] {
499
+ const registered: AnyComponentDefinition[] = [];
500
+
501
+ for (const component of bundle.components) {
502
+ // Apply bundle namespace if component doesn't have one
503
+ const withNamespace: AnyComponentDefinition = {
504
+ ...component,
505
+ namespace: component.namespace ?? bundle.namespace,
506
+ };
507
+
508
+ defineComponent(withNamespace);
509
+ registered.push(withNamespace);
510
+ }
511
+
512
+ return registered;
513
+ }
514
+
515
+ /**
516
+ * Create a component bundle.
517
+ *
518
+ * @param namespace - Shared namespace
519
+ * @param components - Component definitions
520
+ * @param metadata - Optional bundle metadata
521
+ * @returns ComponentBundle
522
+ */
523
+ export function createBundle(
524
+ namespace: string,
525
+ components: AnyComponentDefinition[],
526
+ metadata?: ComponentBundle["metadata"]
527
+ ): ComponentBundle {
528
+ const result: ComponentBundle = { namespace, components };
529
+ if (metadata) {
530
+ result.metadata = metadata;
531
+ }
532
+ return result;
533
+ }
534
+
535
+ // =============================================================================
536
+ // Quick Define Helpers
537
+ // =============================================================================
538
+
539
+ /**
540
+ * Define a simple component with minimal boilerplate.
541
+ *
542
+ * @param type - Component type
543
+ * @param factory - Factory function
544
+ * @returns ComponentDefinition
545
+ *
546
+ * @example
547
+ * ```typescript
548
+ * const badge = simpleComponent("badge", (ctx) => ({
549
+ * type: "badge",
550
+ * props: { label: ctx.data.label },
551
+ * }));
552
+ * ```
553
+ */
554
+ export function simpleComponent<TData = unknown>(
555
+ type: string,
556
+ factory: (ctx: ComponentContext<TData>) => ComponentOutput
557
+ ): ComponentDefinition<TData> {
558
+ return defineComponent({
559
+ type,
560
+ factory,
561
+ });
562
+ }
563
+
564
+ /**
565
+ * Define a namespaced component with minimal boilerplate.
566
+ *
567
+ * @param namespace - Component namespace
568
+ * @param type - Component type
569
+ * @param factory - Factory function
570
+ * @returns ComponentDefinition
571
+ */
572
+ export function namespacedComponent<TData = unknown>(
573
+ namespace: string,
574
+ type: string,
575
+ factory: (ctx: ComponentContext<TData>) => ComponentOutput
576
+ ): ComponentDefinition<TData> {
577
+ return defineComponent({
578
+ type,
579
+ namespace,
580
+ factory,
581
+ });
582
+ }
583
+
584
+ /**
585
+ * Define a streaming component.
586
+ *
587
+ * @param type - Component type
588
+ * @param factory - Generator factory function
589
+ * @returns ComponentDefinition
590
+ */
591
+ export function streamingComponent<TData = unknown>(
592
+ type: string,
593
+ factory: (ctx: ComponentContext<TData>) => AsyncGenerator<ComponentOutput, void, unknown>
594
+ ): ComponentDefinition<TData> {
595
+ return defineComponent({
596
+ type,
597
+ streaming: true,
598
+ factory,
599
+ });
600
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Components Module
3
+ *
4
+ * Components are procedures that return serializable UI descriptors.
5
+ * This enables SSR, caching, and transport over wire.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { defineComponent, simpleComponent, registerBundle } from "@procedures/splay";
10
+ *
11
+ * // Define a component
12
+ * const userCard = defineComponent({
13
+ * type: "user-card",
14
+ * namespace: "ui",
15
+ * input: userSchema,
16
+ * factory: (ctx) => ({
17
+ * type: "user-card",
18
+ * props: { user: ctx.data },
19
+ * }),
20
+ * });
21
+ *
22
+ * // Simple shorthand
23
+ * const badge = simpleComponent("badge", (ctx) => ({
24
+ * type: "badge",
25
+ * props: { label: ctx.data.label },
26
+ * }));
27
+ *
28
+ * // Register a bundle
29
+ * registerBundle({
30
+ * namespace: "ui",
31
+ * components: [buttonComponent, cardComponent],
32
+ * });
33
+ * ```
34
+ */
35
+
36
+ // Types
37
+ export type {
38
+ ComponentOutput,
39
+ FragmentOutput,
40
+ NullOutput,
41
+ AnyComponentOutput,
42
+ Size,
43
+ ComponentContext,
44
+ ComponentFactory,
45
+ StreamingComponentFactory,
46
+ AnyComponentFactory,
47
+ ComponentMetadata,
48
+ ComponentDefinition,
49
+ AnyComponentDefinition,
50
+ RegisteredComponent,
51
+ ComponentBundle,
52
+ InferComponentInput,
53
+ IsStreamingComponent,
54
+ } from "./types.js";
55
+
56
+ // Type utilities
57
+ export {
58
+ nullOutput,
59
+ fragment,
60
+ isFragment,
61
+ isNullOutput,
62
+ isStreamingFactory,
63
+ } from "./types.js";
64
+
65
+ // Definition helpers
66
+ export type { ComponentInput } from "./define.js";
67
+
68
+ export {
69
+ defineComponent,
70
+ componentToProcedure,
71
+ registerBundle,
72
+ createBundle,
73
+ simpleComponent,
74
+ namespacedComponent,
75
+ streamingComponent,
76
+ componentInputSchema,
77
+ componentOutputSchema,
78
+ } from "./define.js";