@playfast/reform-remote 1.0.2 → 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/README.md +16 -16
- 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 +34 -22
- package/src/client-keyed-slot.test.ts +19 -6
- package/src/client-remount.test.ts +14 -5
- package/src/client.ts +74 -41
- package/src/counterFixtures.ts +142 -0
- package/src/fixtures.ts +10 -247
- package/src/listFixtures.ts +174 -0
- package/src/optionalFixtures.ts +64 -0
- package/src/react.ts +1 -6
- package/src/remote-view.typecheck.ts +3 -2
- package/src/server.test.ts +26 -12
- package/src/server.ts +150 -211
- package/src/slottedFixtures.ts +120 -0
- package/src/transport.test.ts +2 -1
- package/src/transport.ts +88 -24
- package/src/wireNode.ts +153 -0
- package/src/wireRender.ts +235 -0
- package/src/connect.ts +0 -44
- package/src/contract.ts +0 -3
- package/src/remote-server.ts +0 -109
- package/src/transport.types.ts +0 -29
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ data — UI-tree patches one way, trigger invocations the other — so there is
|
|
|
19
19
|
This builds on reform's existing seams: the **`ui` contract** already separates logic from
|
|
20
20
|
presentation, the **`CaptureSink`** already serializes the rendered surface headlessly (the same
|
|
21
21
|
mechanism proofs use), and the **schema-first `ui`** form (`ui(name, { props, events })`) carries
|
|
22
|
-
the wire schemas that make props and trigger payloads typed
|
|
22
|
+
the wire schemas that make props and trigger payloads typed _and_ runtime-validated at the seam.
|
|
23
23
|
See [`REMOTE_UI.md`](../../REMOTE_UI.md) for the design.
|
|
24
24
|
|
|
25
25
|
## Install
|
|
@@ -35,15 +35,15 @@ sockets pair with [`@playfast/reform-remote-node`](https://www.npmjs.com/package
|
|
|
35
35
|
|
|
36
36
|
## Key concepts
|
|
37
37
|
|
|
38
|
-
| Concept
|
|
39
|
-
|
|
|
40
|
-
| `makeRemoteServer(scene)`
|
|
41
|
-
| `renderWireTree(tree, { views, invoke })`
|
|
42
|
-
| `remoteContract({...})` / `remoteViews<C>({...})`
|
|
43
|
-
| `serve({ scene, transport })` / `connect({ transport, views })` | Bind both ends to any `RemoteTransport`.
|
|
44
|
-
| `inMemoryTransportPair()`
|
|
45
|
-
| `<RemoteUI transport views />` / `useRemoteUI`
|
|
46
|
-
| `useConnectionStatus(reporter)`
|
|
38
|
+
| Concept | What it does |
|
|
39
|
+
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
40
|
+
| `makeRemoteServer(scene)` | Renders a `Scene` to a `WireTree`; `render()`/`renderDiff()` emit full tree/patches; `invoke(handle, payload)` fires a trigger. |
|
|
41
|
+
| `renderWireTree(tree, { views, invoke })` | Folds a `WireTree` back into React using local presentations. |
|
|
42
|
+
| `remoteContract({...})` / `remoteViews<C>({...})` | The trpc-style typesafe seam — server declares the contract, client implements exactly it. |
|
|
43
|
+
| `serve({ scene, transport })` / `connect({ transport, views })` | Bind both ends to any `RemoteTransport`. |
|
|
44
|
+
| `inMemoryTransportPair()` | In-process duplex `RemoteTransport` (the simplest concrete adapter). |
|
|
45
|
+
| `<RemoteUI transport views />` / `useRemoteUI` | React binding that owns connect, subscription, re-render, teardown. |
|
|
46
|
+
| `useConnectionStatus(reporter)` | Reads a transport's live `StatusReporter` status. |
|
|
47
47
|
|
|
48
48
|
## How it fits together
|
|
49
49
|
|
|
@@ -106,12 +106,12 @@ sockets pair with [`@playfast/reform-remote-node`](https://www.npmjs.com/package
|
|
|
106
106
|
|
|
107
107
|
## Transport adapters
|
|
108
108
|
|
|
109
|
-
| Package
|
|
110
|
-
|
|
|
111
|
-
| `inMemoryTransportPair` (here)
|
|
112
|
-
| [`@playfast/reform-remote-node`](https://www.npmjs.com/package/@playfast/reform-remote-node) | WebSocket server
|
|
113
|
-
| [`@playfast/reform-remote-bun`](https://www.npmjs.com/package/@playfast/reform-remote-bun)
|
|
114
|
-
| [`@playfast/reform-remote-web`](https://www.npmjs.com/package/@playfast/reform-remote-web)
|
|
109
|
+
| Package | Role | Built on |
|
|
110
|
+
| -------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------ |
|
|
111
|
+
| `inMemoryTransportPair` (here) | in-process duplex | — |
|
|
112
|
+
| [`@playfast/reform-remote-node`](https://www.npmjs.com/package/@playfast/reform-remote-node) | WebSocket server | `ws` |
|
|
113
|
+
| [`@playfast/reform-remote-bun`](https://www.npmjs.com/package/@playfast/reform-remote-bun) | WebSocket server | `Bun.serve` |
|
|
114
|
+
| [`@playfast/reform-remote-web`](https://www.npmjs.com/package/@playfast/reform-remote-web) | WebSocket client (factory, auto-reconnect) | global `WebSocket` |
|
|
115
115
|
|
|
116
116
|
## License
|
|
117
117
|
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type MadeView, type UiContract } from '@playfast/reform';
|
|
3
|
+
import { type AnyUi, type UiReflection, type WiredUiManifest, type WireTree } from '@playfast/reform/internal';
|
|
4
|
+
export interface SlotPropsExternalApi {
|
|
5
|
+
readonly slotKey?: string;
|
|
6
|
+
}
|
|
7
|
+
export type RemoteView = (props: Record<string, unknown>, slots: Record<string, (slotProps?: SlotPropsExternalApi) => ReactNode>, events: Record<string, (payload: unknown) => void>) => ReactNode;
|
|
8
|
+
export interface RegisteredRemoteView {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly view: RemoteView;
|
|
11
|
+
}
|
|
12
|
+
type ViewRegistry = Readonly<Record<string, RegisteredRemoteView>>;
|
|
13
|
+
declare const ViewSetContract: unique symbol;
|
|
14
|
+
export type RemoteViewSet<C extends RemoteContract> = ViewRegistry & {
|
|
15
|
+
readonly [ViewSetContract]: (contract: C) => C;
|
|
16
|
+
};
|
|
17
|
+
type RemoteUi = UiReflection<UiContract, string> & AnyUi & {
|
|
18
|
+
readonly manifest: WiredUiManifest;
|
|
19
|
+
};
|
|
20
|
+
export type RemoteContract = Readonly<Record<string, RemoteUi>>;
|
|
21
|
+
export declare const remoteContract: <const C extends RemoteContract>(contract: C) => C;
|
|
22
|
+
type RemoteViewFor<U extends RemoteUi> = U extends UiReflection<infer Contract, infer Name> ? MadeView<Contract, Name, U> : never;
|
|
23
|
+
export type RemoteViews<C extends RemoteContract> = {
|
|
24
|
+
readonly [K in keyof C]: RemoteViewFor<C[K]>;
|
|
25
|
+
};
|
|
26
|
+
export declare const remoteViews: <C extends RemoteContract>(views: RemoteViews<C>) => RemoteViewSet<C>;
|
|
27
|
+
export interface ClientConfig<C extends RemoteContract> {
|
|
28
|
+
readonly views: RemoteViewSet<C>;
|
|
29
|
+
readonly invoke: (handle: string, payload: unknown) => void;
|
|
30
|
+
}
|
|
31
|
+
export declare const renderWireTree: <C extends RemoteContract>(tree: WireTree, config: ClientConfig<C>) => ReactNode;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,SAAS,EAAU,MAAM,OAAO,CAAA;AAEvE,OAAO,EAAE,KAAK,QAAQ,EAAM,KAAK,UAAU,EAAiC,MAAM,kBAAkB,CAAA;AACpG,OAAO,EAEL,KAAK,KAAK,EACV,KAAK,YAAY,EACjB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EACd,MAAM,2BAA2B,CAAA;AAElC,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,UAAU,GAAG,CACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,oBAAoB,KAAK,SAAS,CAAC,EACtE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,KAC/C,SAAS,CAAA;AAEd,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAC1B;AAED,KAAK,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAA;AAElE,QAAA,MAAM,eAAe,EAAE,OAAO,MAAsD,CAAA;AAEpF,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,cAAc,IAAI,YAAY,GAAG;IACnE,QAAQ,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAA;CAC/C,CAAA;AAED,KAAK,QAAQ,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,KAAK,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,CAAA;AACjG,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;AAE/D,eAAO,MAAM,cAAc,GAAI,KAAK,CAAC,CAAC,SAAS,cAAc,EAAE,UAAU,CAAC,KAAG,CAAa,CAAA;AAE1F,KAAK,aAAa,CAAC,CAAC,SAAS,QAAQ,IACnC,CAAC,SAAS,YAAY,CAAC,MAAM,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,KAAK,CAAA;AAC1F,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,cAAc,IAAI;IAClD,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7C,CAAA;AAwCD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,cAAc,EAAE,OAAO,WAAW,CAAC,CAAC,CAAC,KAAG,aAAa,CAAC,CAAC,CAS5F,CAAA;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,cAAc;IACpD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5D;AAiFD,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,cAAc,EACrD,MAAM,QAAQ,EACd,QAAQ,YAAY,CAAC,CAAC,CAAC,KACtB,SAOA,CAAA"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createElement, Fragment, useRef } from 'react';
|
|
2
|
+
import { Effect, Record as Rec, Schema } from 'effect';
|
|
3
|
+
import { Ui, UiViewContract } from '@playfast/reform';
|
|
4
|
+
import { Wire, } from '@playfast/reform/internal';
|
|
5
|
+
const ViewSetContract = Symbol.for('reform-remote/view-set-contract');
|
|
6
|
+
export const remoteContract = (contract) => contract;
|
|
7
|
+
function bindSlots(slots) {
|
|
8
|
+
return slots;
|
|
9
|
+
}
|
|
10
|
+
function bindEvents(events) {
|
|
11
|
+
return events;
|
|
12
|
+
}
|
|
13
|
+
const registerView = (view) => view.capture((exact) => {
|
|
14
|
+
const contract = exact[UiViewContract];
|
|
15
|
+
const propsReflection = contract.manifest.props;
|
|
16
|
+
if (propsReflection === undefined) {
|
|
17
|
+
return Effect.runSync(Effect.dieMessage(`reform-remote: view ${contract.manifest.name} needs a wire props schema`));
|
|
18
|
+
}
|
|
19
|
+
const propsSchema = Schema.make(propsReflection.ast);
|
|
20
|
+
return {
|
|
21
|
+
name: contract.manifest.name,
|
|
22
|
+
view: (encoded, slots, events) => exact(Schema.decodeUnknownSync(propsSchema)(encoded), bindSlots(slots), bindEvents(events)),
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
export const remoteViews = (views) => {
|
|
26
|
+
const madeViews = Object.values(views);
|
|
27
|
+
const byName = Rec.fromEntries(madeViews.map((view) => {
|
|
28
|
+
const entry = registerView(view);
|
|
29
|
+
return [entry.name, entry];
|
|
30
|
+
}));
|
|
31
|
+
return { ...byName, [ViewSetContract]: (contract) => contract };
|
|
32
|
+
};
|
|
33
|
+
const WireNodeView = ({ node, tree, config }) => {
|
|
34
|
+
// Stable slot closures read current inputs through this ref.
|
|
35
|
+
const latest = useRef({ node, tree, config });
|
|
36
|
+
latest.current = { node, tree, config };
|
|
37
|
+
// Fresh slot component types remount descendants and reset their hooks.
|
|
38
|
+
const slotCache = useRef({});
|
|
39
|
+
const registered = config.views[node.name];
|
|
40
|
+
if (registered === undefined) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const encoded = Rec.fromEntries(node.props.flatMap((prop) => prop._tag === 'Data' ? [[prop.name, prop.value]] : []));
|
|
44
|
+
const eventEntries = node.props.flatMap((prop) => {
|
|
45
|
+
if (prop._tag !== 'Event') {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return [[prop.name, (payload) => config.invoke(prop.handle, payload)]];
|
|
49
|
+
});
|
|
50
|
+
const events = Rec.fromEntries(eventEntries);
|
|
51
|
+
// slotKey selects one wire child; omitting it renders every child in the slot.
|
|
52
|
+
const slotFor = (slotName) => {
|
|
53
|
+
const cached = slotCache.current[slotName];
|
|
54
|
+
if (cached !== undefined) {
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
const stable = (slotProps) => {
|
|
58
|
+
const { node: currentNode, tree: currentTree, config: currentConfig } = latest.current;
|
|
59
|
+
const requestedKey = slotProps?.slotKey;
|
|
60
|
+
return createElement(Fragment, null, ...Wire.childrenOf(currentTree, currentNode.id)
|
|
61
|
+
.filter((child) => child.slot === slotName)
|
|
62
|
+
.filter((child) => requestedKey === undefined || child.key === requestedKey)
|
|
63
|
+
.map((child) => createElement(WireNodeView, {
|
|
64
|
+
key: child.id,
|
|
65
|
+
node: child,
|
|
66
|
+
tree: currentTree,
|
|
67
|
+
config: currentConfig,
|
|
68
|
+
})));
|
|
69
|
+
};
|
|
70
|
+
slotCache.current[slotName] = stable;
|
|
71
|
+
return stable;
|
|
72
|
+
};
|
|
73
|
+
// Empty slots must still produce a callable component, not an invalid undefined element type.
|
|
74
|
+
const slots = new Proxy(Object.create(null), {
|
|
75
|
+
get: (_target, key) => (typeof key === 'string' ? slotFor(key) : undefined),
|
|
76
|
+
});
|
|
77
|
+
return registered.view(encoded, slots, events);
|
|
78
|
+
};
|
|
79
|
+
export const renderWireTree = (tree, config) => createElement(Fragment, null, ...Wire.roots(tree).map((node) => createElement(WireNodeView, { key: node.id, node, tree, config })));
|
|
80
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAkB,MAAM,EAAE,MAAM,OAAO,CAAA;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACtD,OAAO,EAAiB,EAAE,EAAmB,cAAc,EAAiB,MAAM,kBAAkB,CAAA;AACpG,OAAO,EAKL,IAAI,GAGL,MAAM,2BAA2B,CAAA;AAmBlC,MAAM,eAAe,GAAkB,MAAM,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAA;AASpF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAiC,QAAW,EAAK,EAAE,CAAC,QAAQ,CAAA;AAY1F,SAAS,SAAS,CAAC,KAAkB;IACnC,OAAO,KAAK,CAAA;AACd,CAAC;AAGD,SAAS,UAAU,CAAC,MAAoB;IACtC,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,YAAY,GAAG,CAAC,IAAiB,EAAwB,EAAE,CAC/D,IAAI,CAAC,OAAO,CACV,CAAgE,KAAwB,EAAE,EAAE;IAC1F,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,CAAC,CAAA;IACtC,MAAM,eAAe,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAA;IAC/C,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,OAAO,MAAM,CAAC,OAAO,CACnB,MAAM,CAAC,UAAU,CACf,uBAAuB,QAAQ,CAAC,QAAQ,CAAC,IAAI,4BAA4B,CAC1E,CACF,CAAA;IACH,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAsB,eAAe,CAAC,GAAG,CAAC,CAAA;IACzE,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI;QAC5B,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAC/B,KAAK,CACH,MAAM,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,EAC9C,SAAS,CAAI,KAAK,CAAC,EACnB,UAAU,CAAI,MAAM,CAAC,CACtB;KACJ,CAAA;AACH,CAAC,CACF,CAAA;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAA2B,KAAqB,EAAoB,EAAE;IAC/F,MAAM,SAAS,GAA+B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAClE,MAAM,MAAM,GAAiB,GAAG,CAAC,WAAW,CAC1C,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAA2C,EAAE;QAC9D,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;QAChC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC5B,CAAC,CAAC,CACH,CAAA;IACD,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,QAAW,EAAK,EAAE,CAAC,QAAQ,EAAE,CAAA;AACvE,CAAC,CAAA;AAkBD,MAAM,YAAY,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAqB,EAAa,EAAE;IAC5E,6DAA6D;IAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IAC7C,MAAM,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACvC,wEAAwE;IACxE,MAAM,SAAS,GAAG,MAAM,CAAkC,EAAE,CAAC,CAAA;IAE7D,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC1C,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,OAAO,GAA4B,GAAG,CAAC,WAAW,CACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAChB,CAAC,IAAI,EAA6C,EAAE,CAClD,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CACxD,CACF,CAAA;IACD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CACrC,CAAC,IAAI,EAAgE,EAAE;QACrE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,OAAO,EAAE,CAAA;QACX,CAAC;QACD,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,OAAgB,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACjF,CAAC,CACF,CAAA;IACD,MAAM,MAAM,GAA+C,GAAG,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;IAExF,+EAA+E;IAC/E,MAAM,OAAO,GAAG,CAAC,QAAgB,EAAqD,EAAE;QACtF,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAC1C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,MAAM,CAAA;QACf,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,SAAgC,EAAa,EAAE;YAC7D,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,OAAO,CAAA;YACtF,MAAM,YAAY,GAAG,SAAS,EAAE,OAAO,CAAA;YACvC,OAAO,aAAa,CAClB,QAAQ,EACR,IAAI,EACJ,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;iBAC5C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;iBAC1C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,YAAY,CAAC;iBAC3E,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACb,aAAa,CAAC,YAAY,EAAE;gBAC1B,GAAG,EAAE,KAAK,CAAC,EAAE;gBACb,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,WAAW;gBACjB,MAAM,EAAE,aAAa;aACtB,CAAC,CACH,CACJ,CAAA;QACH,CAAC,CAAA;QACD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAA;QACpC,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;IAED,8FAA8F;IAC9F,MAAM,KAAK,GAAoE,IAAI,KAAK,CACtF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EACnB;QACE,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;KAC5E,CACF,CAAA;IAED,OAAO,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;AAChD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAc,EACd,MAAuB,EACZ,EAAE,CACb,aAAa,CACX,QAAQ,EACR,IAAI,EACJ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC/B,aAAa,CAAC,YAAY,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAClE,CACF,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * as Server from './server.js';
|
|
2
|
+
export * as Client from './client.js';
|
|
3
|
+
export * as Transport from './transport.js';
|
|
4
|
+
export * as Memory from './memory.js';
|
|
5
|
+
export * as React from './react.js';
|
|
6
|
+
export { makeRemoteServer, type RemoteServer, renderSceneToWire } from './server.js';
|
|
7
|
+
export { type ClientConfig, type RegisteredRemoteView, type RemoteContract, remoteContract, type RemoteView, type RemoteViews, type RemoteViewSet, remoteViews, renderWireTree, } from './client.js';
|
|
8
|
+
export { type ClientBinding, connect, type ConnectOptions, type InvokeMessage, type PatchesMessage, type RemoteTransport, serve, type ServeOptions, serveShared, type ServeSharedOptions, type ServerBinding, type ServerMessage, type SharedClientHandle, type SharedServerBinding, type SnapshotMessage, } from './transport.js';
|
|
9
|
+
export { inMemoryTransportPair, type TransportPair } from './memory.js';
|
|
10
|
+
export { RemoteUI, type RemoteUIProps, type StatusReporter, useConnectionStatus, useRemoteUI, } from './react.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAA;AAClC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAA;AAClC,OAAO,KAAK,SAAS,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAA;AAClC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAA;AAEhC,OAAO,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AACjF,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,cAAc,EACd,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,WAAW,EACX,cAAc,GACf,MAAM,UAAU,CAAA;AACjB,OAAO,EACL,KAAK,aAAa,EAClB,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,EACL,KAAK,YAAY,EACjB,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,qBAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAA;AACpE,OAAO,EACL,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,mBAAmB,EACnB,WAAW,GACZ,MAAM,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * as Server from './server.js';
|
|
2
|
+
export * as Client from './client.js';
|
|
3
|
+
export * as Transport from './transport.js';
|
|
4
|
+
export * as Memory from './memory.js';
|
|
5
|
+
export * as React from './react.js';
|
|
6
|
+
export { makeRemoteServer, renderSceneToWire } from './server.js';
|
|
7
|
+
export { remoteContract, remoteViews, renderWireTree, } from './client.js';
|
|
8
|
+
export { connect, serve, serveShared, } from './transport.js';
|
|
9
|
+
export { inMemoryTransportPair } from './memory.js';
|
|
10
|
+
export { RemoteUI, useConnectionStatus, useRemoteUI, } from './react.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAA;AAClC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAA;AAClC,OAAO,KAAK,SAAS,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAA;AAClC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAA;AAEhC,OAAO,EAAE,gBAAgB,EAAqB,iBAAiB,EAAE,MAAM,UAAU,CAAA;AACjF,OAAO,EAIL,cAAc,EAId,WAAW,EACX,cAAc,GACf,MAAM,UAAU,CAAA;AACjB,OAAO,EAEL,OAAO,EAKP,KAAK,EAEL,WAAW,GAOZ,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,qBAAqB,EAAsB,MAAM,UAAU,CAAA;AACpE,OAAO,EACL,QAAQ,EAGR,mBAAmB,EACnB,WAAW,GACZ,MAAM,SAAS,CAAA"}
|
package/dist/memory.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RemoteTransport } from './transport.js';
|
|
2
|
+
export interface TransportPair<Server, Client> {
|
|
3
|
+
readonly server: RemoteTransport<Server, Client>;
|
|
4
|
+
readonly client: RemoteTransport<Client, Server>;
|
|
5
|
+
}
|
|
6
|
+
export declare const inMemoryTransportPair: <Server, Client>() => TransportPair<Server, Client>;
|
|
7
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,WAAW,aAAa,CAAC,MAAM,EAAE,MAAM;IAC3C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChD,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjD;AAED,eAAO,MAAM,qBAAqB,GAAI,MAAM,EAAE,MAAM,OAAK,aAAa,CAAC,MAAM,EAAE,MAAM,CAmBpF,CAAA"}
|
package/dist/memory.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const inMemoryTransportPair = () => {
|
|
2
|
+
const serverHandlers = new Set();
|
|
3
|
+
const clientHandlers = new Set();
|
|
4
|
+
return {
|
|
5
|
+
server: {
|
|
6
|
+
send: (message) => clientHandlers.forEach((handler) => handler(message)),
|
|
7
|
+
onMessage: (handler) => {
|
|
8
|
+
serverHandlers.add(handler);
|
|
9
|
+
return () => void serverHandlers.delete(handler);
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
client: {
|
|
13
|
+
send: (message) => serverHandlers.forEach((handler) => handler(message)),
|
|
14
|
+
onMessage: (handler) => {
|
|
15
|
+
clientHandlers.add(handler);
|
|
16
|
+
return () => void clientHandlers.delete(handler);
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAkD,EAAE;IACvF,MAAM,cAAc,GAAG,IAAI,GAAG,EAA6B,CAAA;IAC3D,MAAM,cAAc,GAAG,IAAI,GAAG,EAA6B,CAAA;IAC3D,OAAO;QACL,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACxE,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;gBACrB,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;gBAC3B,OAAO,GAAG,EAAE,CAAC,KAAK,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAClD,CAAC;SACF;QACD,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACxE,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;gBACrB,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;gBAC3B,OAAO,GAAG,EAAE,CAAC,KAAK,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAClD,CAAC;SACF;KACF,CAAA;AACH,CAAC,CAAA"}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type InvokeMessage, type RemoteTransport, type ServerMessage } from './transport.js';
|
|
3
|
+
import type { RemoteContract, RemoteViewSet } from './client.js';
|
|
4
|
+
export declare const useRemoteUI: <C extends RemoteContract>(transport: RemoteTransport<InvokeMessage, ServerMessage>, views: RemoteViewSet<C>) => ReactNode;
|
|
5
|
+
export interface RemoteUIProps<C extends RemoteContract> {
|
|
6
|
+
readonly transport: RemoteTransport<InvokeMessage, ServerMessage>;
|
|
7
|
+
readonly views: RemoteViewSet<C>;
|
|
8
|
+
}
|
|
9
|
+
export declare const RemoteUI: <C extends RemoteContract>({ transport, views, }: RemoteUIProps<C>) => ReactNode;
|
|
10
|
+
export interface StatusReporter<S> {
|
|
11
|
+
readonly status: () => S;
|
|
12
|
+
readonly onStatusChange: (listener: () => void) => () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare const useConnectionStatus: <S>(reporter: StatusReporter<S>) => S;
|
|
15
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA6C,MAAM,OAAO,CAAA;AACjF,OAAO,EAAW,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AACnG,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE7D,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,cAAc,EAClD,WAAW,eAAe,CAAC,aAAa,EAAE,aAAa,CAAC,EACxD,OAAO,aAAa,CAAC,CAAC,CAAC,KACtB,SAKF,CAAA;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,cAAc;IACrD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC,aAAa,EAAE,aAAa,CAAC,CAAA;IACjE,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;CACjC;AAED,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,cAAc,EAAE,uBAGhD,aAAa,CAAC,CAAC,CAAC,KAAG,SAA0C,CAAA;AAEhE,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxB,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAC9D;AAED,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,UAAU,cAAc,CAAC,CAAC,CAAC,KAAG,CACY,CAAA"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
2
|
+
import { connect } from './transport.js';
|
|
3
|
+
export const useRemoteUI = (transport, views) => {
|
|
4
|
+
const [binding] = useState(() => connect({ transport, views }));
|
|
5
|
+
useEffect(() => () => binding.dispose(), [binding]);
|
|
6
|
+
useSyncExternalStore(binding.subscribe, binding.snapshot, binding.snapshot);
|
|
7
|
+
return binding.node();
|
|
8
|
+
};
|
|
9
|
+
export const RemoteUI = ({ transport, views, }) => useRemoteUI(transport, views);
|
|
10
|
+
export const useConnectionStatus = (reporter) => useSyncExternalStore(reporter.onStatusChange, reporter.status, reporter.status);
|
|
11
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,SAAS,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AACjF,OAAO,EAAE,OAAO,EAAgE,MAAM,aAAa,CAAA;AAGnG,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,SAAwD,EACxD,KAAuB,EACZ,EAAE;IACb,MAAM,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAC/D,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IACnD,oBAAoB,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC3E,OAAO,OAAO,CAAC,IAAI,EAAE,CAAA;AACvB,CAAC,CAAA;AAOD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAA2B,EACjD,SAAS,EACT,KAAK,GACY,EAAa,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;AAOhE,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAI,QAA2B,EAAK,EAAE,CACvE,oBAAoB,CAAC,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Effect, type ParseResult } from 'effect';
|
|
2
|
+
import { type CapturedScene, type UiContract } from '@playfast/reform';
|
|
3
|
+
import { type AnyScene, type WirePatch, type WireTree } from '@playfast/reform/internal';
|
|
4
|
+
import { type RenderSceneToWireOptions } from './wireRender.js';
|
|
5
|
+
export type { RenderSceneToWireOptions } from './wireRender.js';
|
|
6
|
+
export declare const renderSceneToWire: <C extends UiContract, S extends ReadonlyArray<unknown>, Services, P, N extends string, Identity>(scene: CapturedScene<C, S, Services, P, N, Identity>, options?: RenderSceneToWireOptions) => Effect.Effect<WireTree, ParseResult.ParseError, Services>;
|
|
7
|
+
export interface RemoteServer {
|
|
8
|
+
readonly render: () => Promise<WireTree>;
|
|
9
|
+
readonly renderDiff: () => Promise<ReadonlyArray<WirePatch>>;
|
|
10
|
+
readonly invoke: (handle: string, encodedPayload: unknown) => Promise<void>;
|
|
11
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
12
|
+
readonly currentTree: () => WireTree;
|
|
13
|
+
readonly dispose: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare const makeRemoteServer: (scene: AnyScene) => RemoteServer;
|
|
16
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EAIN,KAAK,WAAW,EAGjB,MAAM,QAAQ,CAAA;AACf,OAAO,EAAE,KAAK,aAAa,EAAsB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC1F,OAAO,EACL,KAAK,QAAQ,EAKb,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAGL,KAAK,wBAAwB,EAG9B,MAAM,cAAc,CAAA;AAGrB,YAAY,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAG5D,eAAO,MAAM,iBAAiB,GAC5B,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,aAAa,CAAC,OAAO,CAAC,EAChC,QAAQ,EACR,CAAC,EACD,CAAC,SAAS,MAAM,EAChB,QAAQ,EAER,OAAO,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,EACpD,UAAU,wBAAwB,KACjC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,UAAU,EAAE,QAAQ,CAUxD,CAAA;AAEH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAA;IAC5D,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3E,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,QAAQ,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AA2GD,eAAO,MAAM,gBAAgB,GAAI,OAAO,QAAQ,KAAG,YAYhD,CAAA"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Effect, Layer, ManagedRuntime, Option, PubSub, Queue, } from 'effect';
|
|
2
|
+
import { forceSync, publish } from '@playfast/reform';
|
|
3
|
+
import { Bus, Triggers, Wire, } from '@playfast/reform/internal';
|
|
4
|
+
import { capturedSlotProvider, renderCapturedComposition, renderSceneWith, serviceFromContext, } from './wireRender.js';
|
|
5
|
+
import { handlesOf, settleDrain } from './wireNode.js';
|
|
6
|
+
// oxlint-disable-next-line reform-rules/prefer-effect-fn -- exported binding: Effect.fn's inferred type isn't portable under isolatedDeclarations
|
|
7
|
+
export const renderSceneToWire = (scene, options) => Effect.context().pipe(Effect.flatMap((context) => renderSceneWith(scene, options, (slotDefinition) => capturedSlotProvider(context, slotDefinition), (composition, props) => renderCapturedComposition(context, composition, props))));
|
|
8
|
+
const makeCapturedRemoteServer = (scene) => {
|
|
9
|
+
const layer = scene.provide.reduce((acc, provided) => Layer.merge(acc, provided));
|
|
10
|
+
const runtime = ManagedRuntime.make(layer);
|
|
11
|
+
const registry = forceSync(() => runtime.runSync(Triggers.make));
|
|
12
|
+
const context = forceSync(() => runtime.runSync(Effect.context()));
|
|
13
|
+
const busFromContext = serviceFromContext(context, Bus, 'reform-remote: scene does not provide Bus');
|
|
14
|
+
const renderEffect = settleDrain.pipe(Effect.zipRight(renderSceneWith(scene, { register: registry.register }, (slotDefinition) => capturedSlotProvider(context, slotDefinition), (composition, props) => renderCapturedComposition(context, composition, props))));
|
|
15
|
+
const changeListeners = new Set();
|
|
16
|
+
const notifyChange = () => {
|
|
17
|
+
changeListeners.forEach((listener) => listener());
|
|
18
|
+
};
|
|
19
|
+
runtime.runFork(Effect.scoped(Effect.gen(function* () {
|
|
20
|
+
const bus = yield* busFromContext;
|
|
21
|
+
const subscription = yield* PubSub.subscribe(bus);
|
|
22
|
+
// The transport debounces; settling here would perturb the drain's scheduling.
|
|
23
|
+
yield* Queue.take(subscription).pipe(Effect.zipRight(Effect.sync(notifyChange)), Effect.forever);
|
|
24
|
+
})));
|
|
25
|
+
runtime.runSync(Effect.forEach(Option.getOrElse(Option.fromNullable(scene.boot), () => []), (event) => busFromContext.pipe(Effect.flatMap((bus) => publish('High', event).pipe(Effect.provideService(Bus, bus))))));
|
|
26
|
+
const frame = { tree: [] };
|
|
27
|
+
const render = () => runtime.runPromise(renderEffect.pipe(Effect.tap((tree) => Effect.sync(() => {
|
|
28
|
+
frame.tree = tree;
|
|
29
|
+
}))));
|
|
30
|
+
const renderDiff = () => runtime.runPromise(Effect.gen(function* () {
|
|
31
|
+
const next = yield* renderEffect;
|
|
32
|
+
const previous = frame.tree;
|
|
33
|
+
const patches = Wire.diff(previous, next);
|
|
34
|
+
frame.tree = next;
|
|
35
|
+
const nextIds = new Set(next.map((node) => node.id));
|
|
36
|
+
const staleHandles = previous.filter((node) => !nextIds.has(node.id)).flatMap(handlesOf);
|
|
37
|
+
yield* Effect.forEach(staleHandles, (handle) => registry.revoke(handle));
|
|
38
|
+
return patches;
|
|
39
|
+
}));
|
|
40
|
+
return {
|
|
41
|
+
render,
|
|
42
|
+
renderDiff,
|
|
43
|
+
invoke: (handle, encodedPayload) => runtime.runPromise(Effect.gen(function* () {
|
|
44
|
+
yield* registry.invoke(handle, encodedPayload);
|
|
45
|
+
yield* settleDrain;
|
|
46
|
+
})),
|
|
47
|
+
subscribe: (listener) => {
|
|
48
|
+
changeListeners.add(listener);
|
|
49
|
+
return () => void changeListeners.delete(listener);
|
|
50
|
+
},
|
|
51
|
+
currentTree: () => frame.tree,
|
|
52
|
+
dispose: () => runtime.dispose(),
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export const makeRemoteServer = (scene) => scene.captureAny((exactScene) => makeCapturedRemoteServer(exactScene));
|
|
56
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type AnyScene, type WirePatch, type WireTree } from '@playfast/reform/internal';
|
|
3
|
+
import { type RemoteContract, type RemoteViewSet } from './client.js';
|
|
4
|
+
export interface SnapshotMessage {
|
|
5
|
+
readonly _tag: 'Snapshot';
|
|
6
|
+
readonly tree: WireTree;
|
|
7
|
+
}
|
|
8
|
+
export interface PatchesMessage {
|
|
9
|
+
readonly _tag: 'Patches';
|
|
10
|
+
readonly patches: ReadonlyArray<WirePatch>;
|
|
11
|
+
}
|
|
12
|
+
export type ServerMessage = SnapshotMessage | PatchesMessage;
|
|
13
|
+
export interface InvokeMessage {
|
|
14
|
+
readonly _tag: 'Invoke';
|
|
15
|
+
readonly handle: string;
|
|
16
|
+
readonly payload: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface RemoteTransport<Out, In> {
|
|
19
|
+
readonly send: (message: Out) => void;
|
|
20
|
+
readonly onMessage: (handler: (message: In) => void) => () => void;
|
|
21
|
+
}
|
|
22
|
+
export interface ServerBinding {
|
|
23
|
+
readonly start: () => Promise<void>;
|
|
24
|
+
readonly dispose: () => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface ServeOptions {
|
|
27
|
+
readonly scene: AnyScene;
|
|
28
|
+
readonly transport: RemoteTransport<ServerMessage, InvokeMessage>;
|
|
29
|
+
}
|
|
30
|
+
export declare const serve: (options: ServeOptions) => ServerBinding;
|
|
31
|
+
export interface SharedClientHandle {
|
|
32
|
+
readonly remove: () => void;
|
|
33
|
+
}
|
|
34
|
+
export interface SharedServerBinding {
|
|
35
|
+
readonly addClient: (transport: RemoteTransport<ServerMessage, InvokeMessage>) => SharedClientHandle;
|
|
36
|
+
readonly dispose: () => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export interface ServeSharedOptions {
|
|
39
|
+
readonly scene: AnyScene;
|
|
40
|
+
}
|
|
41
|
+
export declare const serveShared: (options: ServeSharedOptions) => SharedServerBinding;
|
|
42
|
+
export interface ClientBinding {
|
|
43
|
+
readonly node: () => ReactNode;
|
|
44
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
45
|
+
readonly snapshot: () => WireTree;
|
|
46
|
+
readonly dispose: () => void;
|
|
47
|
+
}
|
|
48
|
+
export interface ConnectOptions<C extends RemoteContract> {
|
|
49
|
+
readonly transport: RemoteTransport<InvokeMessage, ServerMessage>;
|
|
50
|
+
readonly views: RemoteViewSet<C>;
|
|
51
|
+
}
|
|
52
|
+
export declare const connect: <C extends RemoteContract>(options: ConnectOptions<C>) => ClientBinding;
|
|
53
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAE,KAAK,QAAQ,EAAQ,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAE9F,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,UAAU,CAAA;AAEjB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAA;CAC3C;AAED,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,cAAc,CAAA;AAE5D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe,CAAC,GAAG,EAAE,EAAE;IACtC,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;CACnE;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAID,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC,aAAa,EAAE,aAAa,CAAC,CAAA;CAClE;AAED,eAAO,MAAM,KAAK,GAAI,SAAS,YAAY,KAAG,aA+E7C,CAAA;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,CAClB,SAAS,EAAE,eAAe,CAAC,aAAa,EAAE,aAAa,CAAC,KACrD,kBAAkB,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;CACzB;AAED,eAAO,MAAM,WAAW,GAAI,SAAS,kBAAkB,KAAG,mBA4GzD,CAAA;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,QAAQ,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,cAAc;IACtD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC,aAAa,EAAE,aAAa,CAAC,CAAA;IACjE,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;CACjC;AAED,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,cAAc,EAAE,SAAS,cAAc,CAAC,CAAC,CAAC,KAAG,aAyB9E,CAAA"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Effect, Fiber, Match, Option } from 'effect';
|
|
2
|
+
import { Wire } from '@playfast/reform/internal';
|
|
3
|
+
import { makeRemoteServer } from './server.js';
|
|
4
|
+
import { renderWireTree, } from './client.js';
|
|
5
|
+
const BACKGROUND_FLUSH_MS = 16;
|
|
6
|
+
export const serve = (options) => {
|
|
7
|
+
const { scene, transport } = options;
|
|
8
|
+
const server = makeRemoteServer(scene);
|
|
9
|
+
// Snapshot gates diffs; single-flight rendering protects the shared frame baseline.
|
|
10
|
+
const started = { value: false };
|
|
11
|
+
const flight = { promise: Option.none(), dirty: false };
|
|
12
|
+
const push = () => {
|
|
13
|
+
if (!started.value) {
|
|
14
|
+
return Effect.runPromise(Effect.void);
|
|
15
|
+
}
|
|
16
|
+
if (Option.isSome(flight.promise)) {
|
|
17
|
+
flight.dirty = true;
|
|
18
|
+
return flight.promise.value;
|
|
19
|
+
}
|
|
20
|
+
// ensuring clears the single-flight slot even when rendering fails.
|
|
21
|
+
const run = Effect.runPromise(Effect.gen(function* () {
|
|
22
|
+
const patches = yield* Effect.promise(() => server.renderDiff());
|
|
23
|
+
if (patches.length > 0) {
|
|
24
|
+
transport.send({ _tag: 'Patches', patches });
|
|
25
|
+
}
|
|
26
|
+
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
27
|
+
flight.promise = Option.none();
|
|
28
|
+
if (flight.dirty) {
|
|
29
|
+
flight.dirty = false;
|
|
30
|
+
void push();
|
|
31
|
+
}
|
|
32
|
+
}))));
|
|
33
|
+
flight.promise = Option.some(run);
|
|
34
|
+
return run;
|
|
35
|
+
};
|
|
36
|
+
// Background debounce lets the drain settle and coalesces bursts without delaying invokes.
|
|
37
|
+
const debounce = { fiber: Option.none() };
|
|
38
|
+
const scheduleFlush = () => {
|
|
39
|
+
if (!started.value || Option.isSome(debounce.fiber)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const fiber = Effect.runFork(Effect.sleep(BACKGROUND_FLUSH_MS).pipe(Effect.zipRight(Effect.sync(() => {
|
|
43
|
+
debounce.fiber = Option.none();
|
|
44
|
+
void push();
|
|
45
|
+
}))));
|
|
46
|
+
debounce.fiber = Option.some(fiber);
|
|
47
|
+
};
|
|
48
|
+
const start = async () => {
|
|
49
|
+
const tree = await server.render();
|
|
50
|
+
transport.send({ _tag: 'Snapshot', tree });
|
|
51
|
+
started.value = true;
|
|
52
|
+
scheduleFlush();
|
|
53
|
+
};
|
|
54
|
+
const off = transport.onMessage((message) => {
|
|
55
|
+
void Effect.runPromise(Effect.promise(() => server.invoke(message.handle, message.payload)).pipe(Effect.zipRight(Effect.promise(push))));
|
|
56
|
+
});
|
|
57
|
+
const offChange = server.subscribe(scheduleFlush);
|
|
58
|
+
return {
|
|
59
|
+
start,
|
|
60
|
+
dispose: async () => {
|
|
61
|
+
off();
|
|
62
|
+
offChange();
|
|
63
|
+
if (Option.isSome(debounce.fiber)) {
|
|
64
|
+
Effect.runFork(Fiber.interrupt(debounce.fiber.value));
|
|
65
|
+
}
|
|
66
|
+
await server.dispose();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export const serveShared = (options) => {
|
|
71
|
+
const { scene } = options;
|
|
72
|
+
const server = makeRemoteServer(scene);
|
|
73
|
+
const clients = new Set();
|
|
74
|
+
const broadcast = (message) => {
|
|
75
|
+
clients.forEach((client) => client.send(message));
|
|
76
|
+
};
|
|
77
|
+
// One shared render baseline requires the same single-flight discipline as serve.
|
|
78
|
+
const started = { value: false };
|
|
79
|
+
const flight = { promise: Option.none(), dirty: false };
|
|
80
|
+
const push = () => {
|
|
81
|
+
if (!started.value) {
|
|
82
|
+
return Effect.runPromise(Effect.void);
|
|
83
|
+
}
|
|
84
|
+
if (Option.isSome(flight.promise)) {
|
|
85
|
+
flight.dirty = true;
|
|
86
|
+
return flight.promise.value;
|
|
87
|
+
}
|
|
88
|
+
const run = Effect.runPromise(Effect.gen(function* () {
|
|
89
|
+
const patches = yield* Effect.promise(() => server.renderDiff());
|
|
90
|
+
if (patches.length > 0) {
|
|
91
|
+
broadcast({ _tag: 'Patches', patches });
|
|
92
|
+
}
|
|
93
|
+
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
94
|
+
flight.promise = Option.none();
|
|
95
|
+
if (flight.dirty) {
|
|
96
|
+
flight.dirty = false;
|
|
97
|
+
void push();
|
|
98
|
+
}
|
|
99
|
+
}))));
|
|
100
|
+
flight.promise = Option.some(run);
|
|
101
|
+
return run;
|
|
102
|
+
};
|
|
103
|
+
const debounce = { fiber: Option.none() };
|
|
104
|
+
const scheduleFlush = () => {
|
|
105
|
+
if (!started.value || Option.isSome(debounce.fiber)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const fiber = Effect.runFork(Effect.sleep(BACKGROUND_FLUSH_MS).pipe(Effect.zipRight(Effect.sync(() => {
|
|
109
|
+
debounce.fiber = Option.none();
|
|
110
|
+
void push();
|
|
111
|
+
}))));
|
|
112
|
+
debounce.fiber = Option.some(fiber);
|
|
113
|
+
};
|
|
114
|
+
// Clients await the initial baseline so none snapshot an empty tree.
|
|
115
|
+
const ready = (async () => {
|
|
116
|
+
await server.render();
|
|
117
|
+
started.value = true;
|
|
118
|
+
scheduleFlush();
|
|
119
|
+
})();
|
|
120
|
+
const offChange = server.subscribe(scheduleFlush);
|
|
121
|
+
const addClient = (transport) => {
|
|
122
|
+
const off = transport.onMessage((message) => {
|
|
123
|
+
void Effect.runPromise(Effect.promise(() => server.invoke(message.handle, message.payload)).pipe(Effect.zipRight(Effect.promise(push))));
|
|
124
|
+
});
|
|
125
|
+
// Snapshot and broadcast membership must change in one tick so no diff interleaves.
|
|
126
|
+
void Effect.runPromise(Effect.promise(() => ready).pipe(Effect.zipRight(Effect.sync(() => {
|
|
127
|
+
transport.send({ _tag: 'Snapshot', tree: server.currentTree() });
|
|
128
|
+
clients.add(transport);
|
|
129
|
+
}))));
|
|
130
|
+
return {
|
|
131
|
+
remove: () => {
|
|
132
|
+
off();
|
|
133
|
+
clients.delete(transport);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
addClient,
|
|
139
|
+
dispose: async () => {
|
|
140
|
+
offChange();
|
|
141
|
+
if (Option.isSome(debounce.fiber)) {
|
|
142
|
+
Effect.runFork(Fiber.interrupt(debounce.fiber.value));
|
|
143
|
+
}
|
|
144
|
+
clients.clear();
|
|
145
|
+
await server.dispose();
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
export const connect = (options) => {
|
|
150
|
+
const { transport, views } = options;
|
|
151
|
+
const state = { tree: [] };
|
|
152
|
+
const listeners = new Set();
|
|
153
|
+
const config = {
|
|
154
|
+
views,
|
|
155
|
+
invoke: (handle, payload) => transport.send({ _tag: 'Invoke', handle, payload }),
|
|
156
|
+
};
|
|
157
|
+
const off = transport.onMessage((message) => {
|
|
158
|
+
state.tree = Match.value(message).pipe(Match.tag('Snapshot', ({ tree }) => tree), Match.tag('Patches', ({ patches }) => Wire.apply(state.tree, patches)), Match.exhaustive);
|
|
159
|
+
listeners.forEach((listener) => listener());
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
node: () => renderWireTree(state.tree, config),
|
|
163
|
+
subscribe: (listener) => {
|
|
164
|
+
listeners.add(listener);
|
|
165
|
+
return () => void listeners.delete(listener);
|
|
166
|
+
},
|
|
167
|
+
snapshot: () => state.tree,
|
|
168
|
+
dispose: off,
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=transport.js.map
|