@playfast/reform-remote 1.1.0 → 1.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.
- package/dist/client.d.ts +33 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +80 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.d.ts +7 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +21 -0
- package/dist/memory.js.map +1 -0
- package/dist/react.d.ts +15 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +11 -0
- package/dist/react.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +56 -0
- package/dist/server.js.map +1 -0
- package/dist/transport.d.ts +53 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +171 -0
- package/dist/transport.js.map +1 -0
- package/dist/wireNode.d.ts +24 -0
- package/dist/wireNode.d.ts.map +1 -0
- package/dist/wireNode.js +80 -0
- package/dist/wireNode.js.map +1 -0
- package/dist/wireRender.d.ts +20 -0
- package/dist/wireRender.d.ts.map +1 -0
- package/dist/wireRender.js +97 -0
- package/dist/wireRender.js.map +1 -0
- package/package.json +17 -5
- package/src/counterFixtures.ts +142 -0
- package/src/fixtures.ts +10 -453
- package/src/listFixtures.ts +174 -0
- package/src/optionalFixtures.ts +64 -0
- package/src/server.ts +11 -349
- package/src/slottedFixtures.ts +120 -0
- package/src/wireNode.ts +153 -0
- package/src/wireRender.ts +235 -0
package/dist/wireNode.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Array as Arr, Effect, Option, Record as Rec, Schema } from 'effect';
|
|
2
|
+
import { isFeatureBinding, isSlot, } from '@playfast/reform';
|
|
3
|
+
import {} from '@playfast/reform/internal';
|
|
4
|
+
export const SETTLE_DRAIN = 30;
|
|
5
|
+
export const settleDrain = Effect.yieldNow().pipe(Effect.repeatN(SETTLE_DRAIN));
|
|
6
|
+
// The erased event map's never values remain assignable to Trigger<unknown> without a cast.
|
|
7
|
+
export const eventsOf = (structure) => Option.match(Option.fromNullable(structure.events), {
|
|
8
|
+
onNone: () => ({}),
|
|
9
|
+
onSome: (events) => events,
|
|
10
|
+
});
|
|
11
|
+
export const childComposition = (child) => {
|
|
12
|
+
if (!isFeatureBinding(child)) {
|
|
13
|
+
return child;
|
|
14
|
+
}
|
|
15
|
+
return child.capture((binding) => binding.composition);
|
|
16
|
+
};
|
|
17
|
+
export const isRecord = (candidate) => typeof candidate === 'object' && candidate !== null && !Array.isArray(candidate);
|
|
18
|
+
export const handlesOf = (node) => node.props.flatMap((prop) => (prop._tag === 'Event' ? [prop.handle] : []));
|
|
19
|
+
// One-shot renders still emit deterministic handles but have no client registry.
|
|
20
|
+
export const noRegister = () => Effect.void;
|
|
21
|
+
export const toWireNodeFromStructure = Effect.fn('toWireNodeFromStructure')(function* (mounted, structure, register) {
|
|
22
|
+
const manifest = mounted.comp.manifest.ui.manifest;
|
|
23
|
+
const name = manifest.name;
|
|
24
|
+
// The manifest stores schema AST only; reconstruct an unknown-safe schema at this wire seam.
|
|
25
|
+
const encodedProps = yield* Option.match(Option.fromNullable(manifest.props), {
|
|
26
|
+
onNone: () => Effect.succeed({}),
|
|
27
|
+
onSome: (reflection) => Schema.encodeUnknown(Schema.make(reflection.ast))(structure.props).pipe(Effect.flatMap((encoded) => {
|
|
28
|
+
if (isRecord(encoded)) {
|
|
29
|
+
return Effect.succeed(encoded);
|
|
30
|
+
}
|
|
31
|
+
return Effect.dieMessage(`reform-remote: ${manifest.name} props did not encode to a record`);
|
|
32
|
+
})),
|
|
33
|
+
});
|
|
34
|
+
const dataProps = Rec.toEntries(encodedProps).map(([propName, propValue]) => ({
|
|
35
|
+
_tag: 'Data',
|
|
36
|
+
name: propName,
|
|
37
|
+
value: propValue,
|
|
38
|
+
}));
|
|
39
|
+
const eventSchemas = Option.fromNullable(manifest.events);
|
|
40
|
+
const events = eventsOf(structure);
|
|
41
|
+
const eventProps = yield* Effect.forEach(Rec.toEntries(events), ([eventName, trigger]) => Effect.gen(function* () {
|
|
42
|
+
const schema = Option.flatMap(eventSchemas, (schemas) => Rec.get(schemas, eventName));
|
|
43
|
+
if (Option.isNone(schema)) {
|
|
44
|
+
return Option.none();
|
|
45
|
+
}
|
|
46
|
+
const handle = `${mounted.id}:${eventName}`;
|
|
47
|
+
yield* register(handle, trigger, Schema.make(schema.value.ast));
|
|
48
|
+
return Option.some({
|
|
49
|
+
_tag: 'Event',
|
|
50
|
+
name: eventName,
|
|
51
|
+
handle,
|
|
52
|
+
});
|
|
53
|
+
})).pipe(Effect.map(Arr.getSomes));
|
|
54
|
+
return {
|
|
55
|
+
id: mounted.id,
|
|
56
|
+
name,
|
|
57
|
+
parentId: Option.getOrNull(mounted.parentId),
|
|
58
|
+
childIndex: mounted.childIndex,
|
|
59
|
+
slot: Option.getOrNull(mounted.slot),
|
|
60
|
+
key: Option.getOrNull(mounted.key),
|
|
61
|
+
props: [...dataProps, ...eventProps],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
// Structure erases per-slot fill types; recover SlotFill structurally without `as`.
|
|
65
|
+
export const isSlotFill = (candidate) => typeof candidate === 'object' &&
|
|
66
|
+
candidate !== null &&
|
|
67
|
+
'_tag' in candidate &&
|
|
68
|
+
(candidate._tag === 'Each' || candidate._tag === 'One' || candidate._tag === 'Absent');
|
|
69
|
+
export const structureFills = (structure) => Rec.fromEntries(Rec.toEntries(structure.slots).flatMap(([slotName, fillValue]) => isSlotFill(fillValue) ? [[slotName, fillValue]] : []));
|
|
70
|
+
export const slotsOf = (comp) => comp.capture((exact) => {
|
|
71
|
+
if (!('slots' in exact.manifest)) {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
const candidate = exact.manifest.slots;
|
|
75
|
+
if (!isRecord(candidate)) {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
return Rec.fromEntries(Rec.toEntries(candidate).flatMap(([name, slotDefinition]) => isSlot(slotDefinition) ? [[name, slotDefinition]] : []));
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=wireNode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wireNode.js","sourceRoot":"","sources":["../src/wireNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAoB,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC9F,OAAO,EACL,gBAAgB,EAChB,MAAM,GAKP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAQN,MAAM,2BAA2B,CAAA;AAElC,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAA;AAC9B,MAAM,CAAC,MAAM,WAAW,GAAwB,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAA;AAEpG,4FAA4F;AAC5F,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,SAAgC,EAAoC,EAAE,CAC7F,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE;IAClD,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;IAClB,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;CAC3B,CAAC,CAAA;AAYJ,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAmB,EAAkB,EAAE;IACtE,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAiB,CAAC,OAAO,EAAkB,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;AACxF,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,SAAkB,EAAwC,EAAE,CACnF,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AAElF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAc,EAAyB,EAAE,CACjE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAE5E,iFAAiF;AACjF,MAAM,CAAC,MAAM,UAAU,GAAmC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAA;AAE3E,MAAM,CAAC,MAAM,uBAAuB,GAImB,MAAM,CAAC,EAAE,CAAC,yBAAyB,CAAC,CACzF,QAAQ,CAAC,EACT,OAAgB,EAChB,SAAgC,EAChC,QAAwC;IAExC,MAAM,QAAQ,GAAe,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAA;IAC9D,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAA;IAE1B,6FAA6F;IAC7F,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAC5E,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,EAAE,CAAC,UAAU,EAAE,EAAE,CACrB,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAmB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,CACvF,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YACzB,IAAI,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACtB,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;YAChC,CAAC;YACD,OAAO,MAAM,CAAC,UAAU,CACtB,kBAAkB,QAAQ,CAAC,IAAI,mCAAmC,CACnE,CAAA;QACH,CAAC,CAAC,CACH;KACJ,CAAC,CAAA;IACF,MAAM,SAAS,GAA4B,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,GAAG,CACxE,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAY,EAAE,CAAC,CAAC;QACpC,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,SAAS;KACjB,CAAC,CACH,CAAA;IAED,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACzD,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAA;IAClC,MAAM,UAAU,GAA4B,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAC/D,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,EACrB,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,CACvB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAA;QACrF,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC,IAAI,EAAY,CAAA;QAChC,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,IAAI,SAAS,EAAE,CAAA;QAC3C,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAmB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;QACjF,OAAO,MAAM,CAAC,IAAI,CAAW;YAC3B,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,SAAS;YACf,MAAM;SACP,CAAC,CAAA;IACJ,CAAC,CAAC,CACL,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEhC,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI;QACJ,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC5C,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;QACpC,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC;QAClC,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,GAAG,UAAU,CAAC;KACrC,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,SAAkB,EAAkC,EAAE,CAC/E,OAAO,SAAS,KAAK,QAAQ;IAC7B,SAAS,KAAK,IAAI;IAClB,MAAM,IAAI,SAAS;IACnB,CAAC,SAAS,CAAC,IAAI,KAAK,MAAM,IAAI,SAAS,CAAC,IAAI,KAAK,KAAK,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;AAExF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAAgC,EAAqC,EAAE,CACpG,GAAG,CAAC,WAAW,CACb,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CACpC,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAuD,EAAE,CAC7E,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CACvD,CACF,CAAA;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAoB,EAA2B,EAAE,CACvE,IAAI,CAAC,OAAO,CAA0B,CAAC,KAAK,EAAE,EAAE;IAC9C,IAAI,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,SAAS,GAAY,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAA;IAC/C,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,OAAO,GAAG,CAAC,WAAW,CACpB,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,OAAO,CAC9B,CAAC,CAAC,IAAI,EAAE,cAAc,CAAC,EAA6C,EAAE,CACpE,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CACzD,CACF,CAAA;AACH,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Context, Effect, type ParseResult } from 'effect';
|
|
2
|
+
import { type SlotFill, type Structure, type UiContract } from '@playfast/reform';
|
|
3
|
+
import { type AnyComposition, type AnyScene, type AnySlot, type AnySlotChild, type TriggerRegistryApi, type WireNode, type WireTree } from '@playfast/reform/internal';
|
|
4
|
+
import { type Mounted } from './wireNode.js';
|
|
5
|
+
export interface RenderSceneToWireOptions {
|
|
6
|
+
readonly register: TriggerRegistryApi['register'];
|
|
7
|
+
}
|
|
8
|
+
export declare const serviceFromContext: <Services, Identifier, Service>(context: Context.Context<Services>, tag: Context.Tag<Identifier, Service>, missing: string) => Effect.Effect<Service, never, never>;
|
|
9
|
+
export declare const capturedSlotProvider: <Services>(context: Context.Context<Services>, slotDefinition: AnySlot) => Effect.Effect<AnySlotChild, never, never>;
|
|
10
|
+
export declare const renderCapturedComposition: <Services>(context: Context.Context<Services>, composition: AnyComposition, props: unknown) => Effect.Effect<Structure<UiContract>, never, never>;
|
|
11
|
+
export declare const enqueueStructureSlot: (parent: Mounted, slotName: string, child: AnyComposition, fill: SlotFill<unknown>) => ReadonlyArray<Mounted>;
|
|
12
|
+
export type SlotProvider<Services> = (slotDefinition: AnySlot) => Effect.Effect<AnySlotChild, never, Services>;
|
|
13
|
+
export declare const collect: <Services>(root: AnyComposition, provideSlot: SlotProvider<Services>) => Effect.Effect<Map<AnySlot, AnyComposition>, never, Services>;
|
|
14
|
+
export type CompositionRenderer<Services> = (composition: AnyComposition, props: unknown) => Effect.Effect<Structure<UiContract>, never, Services>;
|
|
15
|
+
export interface RenderLevel {
|
|
16
|
+
<Services>(frontier: ReadonlyArray<Mounted>, bindings: ReadonlyMap<AnySlot, AnyComposition>, register: TriggerRegistryApi['register'], render: CompositionRenderer<Services>): Effect.Effect<ReadonlyArray<WireNode>, ParseResult.ParseError, Services>;
|
|
17
|
+
}
|
|
18
|
+
export declare const renderLevel: RenderLevel;
|
|
19
|
+
export declare const renderSceneWith: <SlotServices, CompositionServices>(scene: AnyScene, options: RenderSceneToWireOptions | undefined, provideSlot: SlotProvider<SlotServices>, render: CompositionRenderer<CompositionServices>) => Effect.Effect<WireTree, ParseResult.ParseError, SlotServices | CompositionServices>;
|
|
20
|
+
//# sourceMappingURL=wireRender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wireRender.d.ts","sourceRoot":"","sources":["../src/wireRender.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Context, Effect, Match, Option, Record as Rec } from 'effect';
|
|
2
|
+
import { Composition, isStructure, } from '@playfast/reform';
|
|
3
|
+
import {} from '@playfast/reform/internal';
|
|
4
|
+
import { childComposition, noRegister, slotsOf, structureFills, toWireNodeFromStructure, } from './wireNode.js';
|
|
5
|
+
export const serviceFromContext = (context, tag, missing) => Option.match(Context.getOption(context, tag), {
|
|
6
|
+
onNone: () => Effect.dieMessage(missing),
|
|
7
|
+
onSome: Effect.succeed,
|
|
8
|
+
});
|
|
9
|
+
export const capturedSlotProvider = (context, slotDefinition) => slotDefinition.capture((exact) => serviceFromContext(context, exact.provider, 'reform-remote: scene does not provide a declared slot'));
|
|
10
|
+
export const renderCapturedComposition = (context, composition, props) => composition.capture((exact) => Option.match(exact.parseProps(props), {
|
|
11
|
+
onNone: () => Effect.dieMessage(`reform-remote: invalid props for composition ${composition.manifest.name}`),
|
|
12
|
+
onSome: (parsed) => serviceFromContext(context, exact.tag, `reform-remote: scene does not provide composition ${composition.manifest.name}`).pipe(Effect.flatMap((service) => Composition.render(service, {
|
|
13
|
+
props: parsed,
|
|
14
|
+
tracker: { add: () => { } },
|
|
15
|
+
})), Effect.flatMap((frame) => {
|
|
16
|
+
if (isStructure(frame)) {
|
|
17
|
+
return Effect.succeed(frame);
|
|
18
|
+
}
|
|
19
|
+
return Effect.dieMessage(`reform-remote: composition ${composition.manifest.name} did not return a Structure`);
|
|
20
|
+
})),
|
|
21
|
+
}));
|
|
22
|
+
export const enqueueStructureSlot = (parent, slotName, child, fill) => Match.value(fill).pipe(Match.tag('Each', (each) => each.items.map((entry, index) => ({
|
|
23
|
+
comp: child,
|
|
24
|
+
props: entry.props,
|
|
25
|
+
id: `${parent.id}.${slotName}.${index}`,
|
|
26
|
+
parentId: Option.some(parent.id),
|
|
27
|
+
slot: Option.some(slotName),
|
|
28
|
+
childIndex: index,
|
|
29
|
+
key: Option.fromNullable(entry.key),
|
|
30
|
+
}))), Match.tag('One', (single) => [
|
|
31
|
+
{
|
|
32
|
+
comp: child,
|
|
33
|
+
props: single.props,
|
|
34
|
+
id: `${parent.id}.${slotName}.0`,
|
|
35
|
+
parentId: Option.some(parent.id),
|
|
36
|
+
slot: Option.some(slotName),
|
|
37
|
+
childIndex: 0,
|
|
38
|
+
key: Option.none(),
|
|
39
|
+
},
|
|
40
|
+
]), Match.tag('Absent', () => []), Match.exhaustive);
|
|
41
|
+
export const collect = Effect.fn('collect')(function* (root, provideSlot) {
|
|
42
|
+
const bindings = new Map();
|
|
43
|
+
// Explicit type keeps the recursive reference cast-free.
|
|
44
|
+
const walk = Effect.fn('walk')(function* (comp) {
|
|
45
|
+
yield* Effect.forEach(Rec.values(slotsOf(comp)), (slotClass) => Effect.gen(function* () {
|
|
46
|
+
if (bindings.has(slotClass)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const child = childComposition(yield* provideSlot(slotClass));
|
|
50
|
+
bindings.set(slotClass, child);
|
|
51
|
+
yield* walk(child);
|
|
52
|
+
}));
|
|
53
|
+
});
|
|
54
|
+
yield* walk(root);
|
|
55
|
+
return bindings;
|
|
56
|
+
});
|
|
57
|
+
export const renderLevel = Effect.fn('renderLevel')(function* (frontier, bindings, register, render) {
|
|
58
|
+
if (frontier.length === 0) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const rendered = yield* Effect.forEach(frontier, (mounted) => Effect.gen(function* () {
|
|
62
|
+
const frame = yield* render(mounted.comp, mounted.props);
|
|
63
|
+
const node = yield* toWireNodeFromStructure(mounted, frame, register);
|
|
64
|
+
const fills = structureFills(frame);
|
|
65
|
+
const children = Rec.toEntries(slotsOf(mounted.comp)).flatMap(([slotName, slotClass]) => {
|
|
66
|
+
const child = bindings.get(slotClass);
|
|
67
|
+
if (child === undefined) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const fill = fills[slotName];
|
|
71
|
+
if (fill === undefined) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
return enqueueStructureSlot(mounted, slotName, child, fill);
|
|
75
|
+
});
|
|
76
|
+
return { node, children };
|
|
77
|
+
}));
|
|
78
|
+
const nodes = rendered.map((entry) => entry.node);
|
|
79
|
+
const next = rendered.flatMap((entry) => entry.children);
|
|
80
|
+
const rest = yield* renderLevel(next, bindings, register, render);
|
|
81
|
+
return [...nodes, ...rest];
|
|
82
|
+
});
|
|
83
|
+
export const renderSceneWith = Effect.fn('renderSceneWith')(function* (scene, options, provideSlot, render) {
|
|
84
|
+
const register = options?.register ?? noRegister;
|
|
85
|
+
const bindings = yield* collect(scene.composition, provideSlot);
|
|
86
|
+
const root = {
|
|
87
|
+
comp: scene.composition,
|
|
88
|
+
props: {},
|
|
89
|
+
id: '0',
|
|
90
|
+
parentId: Option.none(),
|
|
91
|
+
slot: Option.none(),
|
|
92
|
+
childIndex: 0,
|
|
93
|
+
key: Option.none(),
|
|
94
|
+
};
|
|
95
|
+
return yield* renderLevel([root], bindings, register, render);
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=wireRender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wireRender.js","sourceRoot":"","sources":["../src/wireRender.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playfast/reform-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Run a reform scene's logic on the server and stream its rendered UI to a thin client over any duplex transport.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect",
|
|
@@ -20,15 +20,26 @@
|
|
|
20
20
|
"directory": "packages/reform-remote"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
+
"dist",
|
|
23
24
|
"src",
|
|
24
25
|
"README.md"
|
|
25
26
|
],
|
|
26
27
|
"type": "module",
|
|
27
28
|
"sideEffects": false,
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
28
31
|
"exports": {
|
|
29
32
|
"./package.json": "./package.json",
|
|
30
|
-
".":
|
|
31
|
-
|
|
33
|
+
".": {
|
|
34
|
+
"playfast-src": "./src/index.ts",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./*": {
|
|
39
|
+
"playfast-src": "./src/*.ts",
|
|
40
|
+
"types": "./dist/*.d.ts",
|
|
41
|
+
"default": "./dist/*.js"
|
|
42
|
+
}
|
|
32
43
|
},
|
|
33
44
|
"publishConfig": {
|
|
34
45
|
"access": "public"
|
|
@@ -36,12 +47,13 @@
|
|
|
36
47
|
"scripts": {
|
|
37
48
|
"clean": "rm -rf dist .tsbuildinfo",
|
|
38
49
|
"check": "tsc --noEmit",
|
|
39
|
-
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"build": "rm -rf dist .tsbuildinfo && tsc -p tsconfig.build.json && bun ../../scripts/fix-esm-extensions.ts dist",
|
|
40
51
|
"test": "vitest run",
|
|
41
52
|
"test:watch": "vitest",
|
|
42
53
|
"coverage": "vitest run --coverage",
|
|
43
54
|
"lint": "oxlint src",
|
|
44
|
-
"lint:fix": "oxlint --fix src"
|
|
55
|
+
"lint:fix": "oxlint --fix src",
|
|
56
|
+
"prepack": "bun run build"
|
|
45
57
|
},
|
|
46
58
|
"peerDependencies": {
|
|
47
59
|
"@playfast/reform": "*",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Duration, Effect, Layer, Schema as S } from 'effect'
|
|
2
|
+
import {
|
|
3
|
+
Channel,
|
|
4
|
+
Composition,
|
|
5
|
+
Engine,
|
|
6
|
+
Event,
|
|
7
|
+
Procedure,
|
|
8
|
+
Reducer,
|
|
9
|
+
State,
|
|
10
|
+
StateGroup,
|
|
11
|
+
Ui,
|
|
12
|
+
mount,
|
|
13
|
+
provide,
|
|
14
|
+
scene,
|
|
15
|
+
type CapturedScene,
|
|
16
|
+
type DerivedContract,
|
|
17
|
+
type EventOf,
|
|
18
|
+
ui,
|
|
19
|
+
} from '@playfast/reform'
|
|
20
|
+
import { type EngineServices, type WiredUi } from '@playfast/reform/internal'
|
|
21
|
+
|
|
22
|
+
const CountBase: State.StateDefinition<'count', number, typeof S.Number> = State.make(
|
|
23
|
+
'count',
|
|
24
|
+
S.Number,
|
|
25
|
+
)
|
|
26
|
+
class Count extends CountBase {}
|
|
27
|
+
class Counters extends StateGroup.make(Count) {}
|
|
28
|
+
class Bumped extends Event.make('Bumped', S.Struct({ by: S.Number })) {}
|
|
29
|
+
class Bump extends Reducer.make('Bump', {
|
|
30
|
+
states: [Count],
|
|
31
|
+
events: [Bumped],
|
|
32
|
+
}) {}
|
|
33
|
+
const CounterUiBase: WiredUi<
|
|
34
|
+
DerivedContract<{
|
|
35
|
+
props: S.Struct<{ count: typeof S.Number }>
|
|
36
|
+
events: { bump: S.Struct<{ by: typeof S.Number }> }
|
|
37
|
+
}>,
|
|
38
|
+
'Counter'
|
|
39
|
+
> = ui('Counter', {
|
|
40
|
+
props: S.Struct({ count: S.Number }),
|
|
41
|
+
events: { bump: S.Struct({ by: S.Number }) },
|
|
42
|
+
})
|
|
43
|
+
export class CounterUi extends CounterUiBase {}
|
|
44
|
+
|
|
45
|
+
class Counter extends Composition.make('Counter', {
|
|
46
|
+
title: 'Counter',
|
|
47
|
+
ui: CounterUi,
|
|
48
|
+
states: [Count],
|
|
49
|
+
})<Counter>() {}
|
|
50
|
+
|
|
51
|
+
type CounterScene = CapturedScene<
|
|
52
|
+
typeof CounterUi.Contract,
|
|
53
|
+
readonly [typeof Count],
|
|
54
|
+
| EngineServices
|
|
55
|
+
| State.StateStore<'count', number>
|
|
56
|
+
| Ui.UiService<'Counter'>
|
|
57
|
+
| Composition.CompositionId<'Counter', Counter, never>,
|
|
58
|
+
unknown,
|
|
59
|
+
'Counter',
|
|
60
|
+
Counter
|
|
61
|
+
>
|
|
62
|
+
|
|
63
|
+
export const bumpedBy = (amount: number): EventOf<'Bumped', { by: number }> =>
|
|
64
|
+
Event.construct(Bumped, { by: amount })
|
|
65
|
+
|
|
66
|
+
const makeCounterScene = (
|
|
67
|
+
boot?: ReadonlyArray<EventOf<'Bumped', { by: number }>>,
|
|
68
|
+
): CounterScene => {
|
|
69
|
+
const presentation = Layer.mergeAll(
|
|
70
|
+
provide(
|
|
71
|
+
CounterUi,
|
|
72
|
+
Ui.make(CounterUi, ({ count }) => `count:${count}`),
|
|
73
|
+
),
|
|
74
|
+
StateGroup.live(Counters, { count: 0 }),
|
|
75
|
+
)
|
|
76
|
+
const app = Layer.mergeAll(
|
|
77
|
+
Composition.live(Counter, function* () {
|
|
78
|
+
const count = yield* StateGroup.select(Counters, 'count')
|
|
79
|
+
const bump = yield* Event.trigger(Bumped)
|
|
80
|
+
return mount({ props: { count }, slots: {}, events: { bump } })
|
|
81
|
+
}),
|
|
82
|
+
Reducer.live(Bump, (count, event) => count + event.by),
|
|
83
|
+
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
84
|
+
return scene(Counter, boot === undefined ? { provide: [app] } : { provide: [app], boot })
|
|
85
|
+
}
|
|
86
|
+
export const counterScene: typeof makeCounterScene = makeCounterScene
|
|
87
|
+
|
|
88
|
+
const makeStructureCounterScene = (): CounterScene => {
|
|
89
|
+
const presentation = Layer.mergeAll(
|
|
90
|
+
provide(
|
|
91
|
+
CounterUi,
|
|
92
|
+
Ui.make(CounterUi, ({ count }) => `count:${count}`),
|
|
93
|
+
),
|
|
94
|
+
StateGroup.live(Counters, { count: 0 }),
|
|
95
|
+
)
|
|
96
|
+
const app = Layer.mergeAll(
|
|
97
|
+
Composition.live(Counter, function* () {
|
|
98
|
+
const count = yield* StateGroup.select(Counters, 'count')
|
|
99
|
+
const bump = yield* Event.trigger(Bumped)
|
|
100
|
+
return mount({ props: { count }, slots: {}, events: { bump } })
|
|
101
|
+
}),
|
|
102
|
+
Reducer.live(Bump, (count, event) => count + event.by),
|
|
103
|
+
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
104
|
+
return scene(Counter, { provide: [app] })
|
|
105
|
+
}
|
|
106
|
+
export const structureCounterScene: typeof makeStructureCounterScene = makeStructureCounterScene
|
|
107
|
+
|
|
108
|
+
class Kick extends Event.make('Kick', S.Struct({ to: S.Number })) {}
|
|
109
|
+
class Loader extends Channel.make('Loader', { policy: { _tag: 'latest' } }) {}
|
|
110
|
+
class DelayedBump extends Procedure.make('DelayedBump', {
|
|
111
|
+
channel: Loader,
|
|
112
|
+
events: [Kick],
|
|
113
|
+
}) {}
|
|
114
|
+
|
|
115
|
+
const makeAsyncCounterScene = (delay: Duration.DurationInput = '40 millis'): CounterScene => {
|
|
116
|
+
const presentation = Layer.mergeAll(
|
|
117
|
+
provide(
|
|
118
|
+
CounterUi,
|
|
119
|
+
Ui.make(CounterUi, ({ count }) => `count:${count}`),
|
|
120
|
+
),
|
|
121
|
+
StateGroup.live(Counters, { count: 0 }),
|
|
122
|
+
)
|
|
123
|
+
const app = Layer.mergeAll(
|
|
124
|
+
Composition.live(Counter, function* () {
|
|
125
|
+
const count = yield* StateGroup.select(Counters, 'count')
|
|
126
|
+
const bump = yield* Event.trigger(Bumped)
|
|
127
|
+
return mount({ props: { count }, slots: {}, events: { bump } })
|
|
128
|
+
}),
|
|
129
|
+
Reducer.live(Bump, (count, event) => count + event.by),
|
|
130
|
+
Procedure.live(DelayedBump, function* (event) {
|
|
131
|
+
yield* Effect.sleep(delay)
|
|
132
|
+
yield* Event.dispatch(Bumped, { by: event.to })
|
|
133
|
+
}),
|
|
134
|
+
// Without Channel.live the boot Kick is dropped.
|
|
135
|
+
Channel.live(Loader),
|
|
136
|
+
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
137
|
+
return scene(Counter, {
|
|
138
|
+
provide: [app],
|
|
139
|
+
boot: [Event.construct(Kick, { to: 7 })],
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
export const asyncCounterScene: typeof makeAsyncCounterScene = makeAsyncCounterScene
|