oui-spec 0.2.0 → 0.3.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 (63) hide show
  1. package/README.md +94 -122
  2. package/dist/{chunk-CVUUSPF4.js → chunk-BMCL2K74.js} +2 -2
  3. package/dist/chunk-BMCL2K74.js.map +1 -0
  4. package/dist/{chunk-MTMEW6MM.cjs → chunk-DCVVTP3V.cjs} +72 -23
  5. package/dist/chunk-DCVVTP3V.cjs.map +1 -0
  6. package/dist/chunk-F3SKRAGU.cjs +98 -0
  7. package/dist/chunk-F3SKRAGU.cjs.map +1 -0
  8. package/dist/{chunk-YDMGV72W.js → chunk-F3WKIRAU.js} +72 -23
  9. package/dist/chunk-F3WKIRAU.js.map +1 -0
  10. package/dist/chunk-IIZ6HL2L.js +89 -0
  11. package/dist/chunk-IIZ6HL2L.js.map +1 -0
  12. package/dist/chunk-KNWGQRXL.cjs +567 -0
  13. package/dist/chunk-KNWGQRXL.cjs.map +1 -0
  14. package/dist/chunk-NYCJZQSP.js +92 -0
  15. package/dist/chunk-NYCJZQSP.js.map +1 -0
  16. package/dist/chunk-ROGKEMLL.cjs +91 -0
  17. package/dist/chunk-ROGKEMLL.cjs.map +1 -0
  18. package/dist/chunk-TRNVXAB5.js +563 -0
  19. package/dist/chunk-TRNVXAB5.js.map +1 -0
  20. package/dist/{chunk-3DCIL4FV.cjs → chunk-ZKM4XWAB.cjs} +2 -2
  21. package/dist/chunk-ZKM4XWAB.cjs.map +1 -0
  22. package/dist/core/index.cjs +10 -6
  23. package/dist/core/index.d.cts +15 -8
  24. package/dist/core/index.d.ts +15 -8
  25. package/dist/core/index.js +3 -3
  26. package/dist/index.cjs +28 -12
  27. package/dist/index.d.cts +5 -4
  28. package/dist/index.d.ts +5 -4
  29. package/dist/index.js +5 -5
  30. package/dist/react/index.cjs +15 -3
  31. package/dist/react/index.d.cts +33 -50
  32. package/dist/react/index.d.ts +33 -50
  33. package/dist/react/index.js +1 -1
  34. package/dist/spec/index.cjs +2 -2
  35. package/dist/spec/index.d.cts +35 -7
  36. package/dist/spec/index.d.ts +35 -7
  37. package/dist/spec/index.js +1 -1
  38. package/dist/{define-surface-D6XPtoqu.d.cts → surface-runtime--2lZGB7Z.d.cts} +92 -2
  39. package/dist/{define-surface-DXBop-uB.d.ts → surface-runtime-Dj_nhd7r.d.ts} +92 -2
  40. package/dist/transport/index.cjs +4 -4
  41. package/dist/transport/index.d.cts +2 -2
  42. package/dist/transport/index.d.ts +2 -2
  43. package/dist/transport/index.js +2 -2
  44. package/dist/{websocket-CrI0yRNo.d.cts → websocket-YfWILdZ4.d.cts} +36 -18
  45. package/dist/{websocket-CiWvkaz_.d.ts → websocket-c5tIUWY5.d.ts} +36 -18
  46. package/package.json +5 -1
  47. package/spec/OUI-SPEC-v0.1.md +229 -155
  48. package/dist/chunk-2ON5IEXB.cjs +0 -167
  49. package/dist/chunk-2ON5IEXB.cjs.map +0 -1
  50. package/dist/chunk-3DCIL4FV.cjs.map +0 -1
  51. package/dist/chunk-5TQ6AVQH.cjs +0 -108
  52. package/dist/chunk-5TQ6AVQH.cjs.map +0 -1
  53. package/dist/chunk-CVUUSPF4.js.map +0 -1
  54. package/dist/chunk-HK7OXB2W.cjs +0 -157
  55. package/dist/chunk-HK7OXB2W.cjs.map +0 -1
  56. package/dist/chunk-ILUTAHUJ.js +0 -154
  57. package/dist/chunk-ILUTAHUJ.js.map +0 -1
  58. package/dist/chunk-MTMEW6MM.cjs.map +0 -1
  59. package/dist/chunk-O6EI77LD.js +0 -106
  60. package/dist/chunk-O6EI77LD.js.map +0 -1
  61. package/dist/chunk-UIGRLZOX.js +0 -164
  62. package/dist/chunk-UIGRLZOX.js.map +0 -1
  63. package/dist/chunk-YDMGV72W.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/use-surface.ts"],"names":["createContext","createElement","useContext","useRef","useEffect","useCallback","useMemo"],"mappings":"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","file":"chunk-F3SKRAGU.cjs","sourcesContent":["import {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type ReactNode,\n} from \"react\";\nimport type {\n DefinedSurface,\n SurfaceRuntime,\n MountedSurface,\n} from \"../core/index.js\";\nimport type { OUIActionResult } from \"../spec/index.js\";\n\n// ─── Runtime context ─────────────────────────────────────────────────────────\n\nconst SurfaceRuntimeContext = createContext<SurfaceRuntime | null>(null);\n\nexport interface SurfaceRuntimeProviderProps {\n runtime: SurfaceRuntime;\n children?: ReactNode;\n}\n\n/** Makes one surface runtime available to every `useSurface` below it. */\nexport function SurfaceRuntimeProvider({\n runtime,\n children,\n}: SurfaceRuntimeProviderProps) {\n return createElement(\n SurfaceRuntimeContext.Provider,\n { value: runtime },\n children,\n );\n}\n\n/** The surface runtime from the nearest `SurfaceRuntimeProvider`. */\nexport function useSurfaceRuntime(): SurfaceRuntime {\n const runtime = useContext(SurfaceRuntimeContext);\n if (!runtime) {\n throw new Error(\n \"[OUI] useSurface needs a SurfaceRuntimeProvider above it. Create one runtime per client with \" +\n \"createSurfaceRuntime() and provide it at the root.\",\n );\n }\n return runtime;\n}\n\n// ─── useSurface ──────────────────────────────────────────────────────────────\n\nexport interface UseSurfaceOptions<TContext> {\n /** The defined surface (from defineSurface()) */\n surface: DefinedSurface<TContext>;\n\n /** Passed to action handlers. Read when an action runs, so it is always current. */\n context: TContext;\n\n /** Mount the surface only while true. Default true. */\n active?: boolean;\n}\n\nexport interface UseSurfaceResult {\n surfaceId: string;\n /** Record an observation value on this surface. Unchanged values are ignored. */\n pushObservation(observationId: string, value: unknown): void;\n /** Run one of this surface's actions locally, exactly as a dispatched request would. */\n execute(\n actionId: string,\n params?: Record<string, unknown>,\n ): Promise<OUIActionResult>;\n}\n\nlet localRequestSeq = 0;\n\n/**\n * Mount a surface into the client's surface runtime for as long as the\n * component is mounted (and `active`). The runtime answers dispatched requests\n * for it, so the component needs no listener of its own.\n */\nexport function useSurface<TContext>(\n options: UseSurfaceOptions<TContext>,\n): UseSurfaceResult {\n const { surface, context, active = true } = options;\n const runtime = useSurfaceRuntime();\n\n const contextRef = useRef(context);\n contextRef.current = context;\n\n const mountedRef = useRef<MountedSurface | null>(null);\n // Observations pushed before the mount effect ran (effects declared earlier\n // in the same component run first) are kept and delivered on mount rather\n // than lost.\n const pendingRef = useRef(new Map<string, unknown>());\n\n useEffect(() => {\n if (!active) return;\n const mounted = runtime.mount(surface, () => contextRef.current);\n mountedRef.current = mounted;\n for (const [observationId, value] of pendingRef.current) {\n mounted.pushObservation(observationId, value);\n }\n pendingRef.current.clear();\n return () => {\n mounted.unmount();\n if (mountedRef.current === mounted) mountedRef.current = null;\n };\n }, [runtime, surface, active]);\n\n const pushObservation = useCallback(\n (observationId: string, value: unknown) => {\n const mounted = mountedRef.current;\n if (mounted) mounted.pushObservation(observationId, value);\n else pendingRef.current.set(observationId, value);\n },\n [],\n );\n\n const execute = useCallback(\n (actionId: string, params: Record<string, unknown> = {}) =>\n runtime.execute({\n requestId: `local_${Date.now()}_${++localRequestSeq}`,\n surfaceId: surface.id,\n actionId,\n params,\n timestamp: Date.now(),\n }),\n [runtime, surface.id],\n );\n\n return useMemo(\n () => ({ surfaceId: surface.id, pushObservation, execute }),\n [surface.id, pushObservation, execute],\n );\n}\n\n// ─── useObservation ──────────────────────────────────────────────────────────\n\n/** Keep an observation on a mounted surface equal to `value`. */\nexport function useObservation(\n surface: Pick<UseSurfaceResult, \"pushObservation\">,\n observationId: string,\n value: unknown,\n): void {\n const { pushObservation } = surface;\n let serialized: string;\n try {\n serialized = JSON.stringify(value) ?? \"undefined\";\n } catch {\n serialized = String(value);\n }\n const valueRef = useRef(value);\n valueRef.current = value;\n\n useEffect(() => {\n pushObservation(observationId, valueRef.current);\n }, [pushObservation, observationId, serialized]);\n}\n\n// ─── useSurfaceHold ──────────────────────────────────────────────────────────\n\n/**\n * Keep the UI unsettled while `busy` is true: an action's result waits for it.\n * For work mounting cannot show, such as a route's code or a page's data still\n * loading.\n */\nexport function useSurfaceHold(busy: boolean): void {\n const runtime = useSurfaceRuntime();\n useEffect(() => {\n if (!busy) return;\n return runtime.hold();\n }, [runtime, busy]);\n}\n"]}
@@ -6,17 +6,28 @@ function createWebSocketTransport(socket, config) {
6
6
  const shouldBuffer = config?.bufferWhileDisconnected ?? true;
7
7
  let connected = socket.connected ?? false;
8
8
  const connectionHandlers = [];
9
- socket.on("connect", () => {
9
+ const attached = [];
10
+ function listen(event, handler) {
11
+ socket.on(event, handler);
12
+ const entry = { event, handler };
13
+ attached.push(entry);
14
+ return () => {
15
+ socket.off(event, handler);
16
+ const i = attached.indexOf(entry);
17
+ if (i >= 0) attached.splice(i, 1);
18
+ };
19
+ }
20
+ listen("connect", () => {
10
21
  connected = true;
11
- connectionHandlers.forEach((h) => h(true));
12
22
  if (shouldBuffer) {
13
23
  while (buffer.length > 0) {
14
24
  const msg = buffer.shift();
15
25
  socket.emit(msg.event, msg.data);
16
26
  }
17
27
  }
28
+ connectionHandlers.forEach((h) => h(true));
18
29
  });
19
- socket.on("disconnect", () => {
30
+ listen("disconnect", () => {
20
31
  connected = false;
21
32
  connectionHandlers.forEach((h) => h(false));
22
33
  });
@@ -28,25 +39,50 @@ function createWebSocketTransport(socket, config) {
28
39
  }
29
40
  }
30
41
  return {
31
- // ─── Dispatch Channel ───────────────────────────────────────
32
- dispatch(surfaceId, actionId, params) {
33
- emit(`${ns}:dispatch`, { surfaceId, actionId, params, timestamp: Date.now() });
42
+ // ─── Dispatch ───────────────────────────────────────────────
43
+ dispatch(request) {
44
+ emit(`${ns}:dispatch`, request);
34
45
  },
35
46
  onAction(handler) {
36
- const listener = (data) => {
37
- handler(data.surfaceId, data.actionId, data.params);
38
- };
39
- socket.on(`${ns}:dispatch`, listener);
40
- return () => socket.off(`${ns}:dispatch`, listener);
47
+ return listen(
48
+ `${ns}:dispatch`,
49
+ (data) => {
50
+ if (!data || typeof data.requestId !== "string" || !data.requestId || typeof data.surfaceId !== "string" || typeof data.actionId !== "string") {
51
+ console.warn(
52
+ "[OUI] Dropped a dispatch without requestId/surfaceId/actionId",
53
+ data
54
+ );
55
+ return;
56
+ }
57
+ handler({
58
+ requestId: data.requestId,
59
+ surfaceId: data.surfaceId,
60
+ actionId: data.actionId,
61
+ params: data.params ?? {},
62
+ timestamp: data.timestamp ?? Date.now()
63
+ });
64
+ }
65
+ );
66
+ },
67
+ // ─── Result ─────────────────────────────────────────────────
68
+ sendResult(result) {
69
+ emit(`${ns}:action:result`, result);
70
+ },
71
+ onResult(handler) {
72
+ return listen(
73
+ `${ns}:action:result`,
74
+ (data) => handler(data)
75
+ );
41
76
  },
42
77
  // ─── Observation Channel ────────────────────────────────────
43
78
  pushObservation(update) {
44
79
  emit(`${ns}:observation`, update);
45
80
  },
46
81
  onObservation(handler) {
47
- const listener = (data) => handler(data);
48
- socket.on(`${ns}:observation`, listener);
49
- return () => socket.off(`${ns}:observation`, listener);
82
+ return listen(
83
+ `${ns}:observation`,
84
+ (data) => handler(data)
85
+ );
50
86
  },
51
87
  // ─── Surface Lifecycle ──────────────────────────────────────
52
88
  registerSurface(surface) {
@@ -56,14 +92,16 @@ function createWebSocketTransport(socket, config) {
56
92
  emit(`${ns}:surface:deregister`, { surfaceId, timestamp: Date.now() });
57
93
  },
58
94
  onSurfaceRegister(handler) {
59
- const listener = (data) => handler(data.surface);
60
- socket.on(`${ns}:surface:register`, listener);
61
- return () => socket.off(`${ns}:surface:register`, listener);
95
+ return listen(
96
+ `${ns}:surface:register`,
97
+ (data) => handler(data.surface)
98
+ );
62
99
  },
63
100
  onSurfaceDeregister(handler) {
64
- const listener = (data) => handler(data.surfaceId);
65
- socket.on(`${ns}:surface:deregister`, listener);
66
- return () => socket.off(`${ns}:surface:deregister`, listener);
101
+ return listen(
102
+ `${ns}:surface:deregister`,
103
+ (data) => handler(data.surfaceId)
104
+ );
67
105
  },
68
106
  // ─── Connection ─────────────────────────────────────────────
69
107
  get connected() {
@@ -73,7 +111,11 @@ function createWebSocketTransport(socket, config) {
73
111
  if (connected) return;
74
112
  return new Promise((resolve, reject) => {
75
113
  const timeout = setTimeout(() => {
76
- reject(new Error(`OUI transport connect timeout (${config?.connectTimeoutMs ?? 1e4}ms)`));
114
+ reject(
115
+ new Error(
116
+ `OUI transport connect timeout (${config?.connectTimeoutMs ?? 1e4}ms)`
117
+ )
118
+ );
77
119
  }, config?.connectTimeoutMs ?? 1e4);
78
120
  socket.once("connect", () => {
79
121
  clearTimeout(timeout);
@@ -95,10 +137,17 @@ function createWebSocketTransport(socket, config) {
95
137
  const idx = connectionHandlers.indexOf(handler);
96
138
  if (idx >= 0) connectionHandlers.splice(idx, 1);
97
139
  };
140
+ },
141
+ dispose() {
142
+ for (const { event, handler } of attached.splice(0)) {
143
+ socket.off(event, handler);
144
+ }
145
+ connectionHandlers.length = 0;
146
+ buffer.length = 0;
98
147
  }
99
148
  };
100
149
  }
101
150
 
102
151
  export { createWebSocketTransport };
103
- //# sourceMappingURL=chunk-YDMGV72W.js.map
104
- //# sourceMappingURL=chunk-YDMGV72W.js.map
152
+ //# sourceMappingURL=chunk-F3WKIRAU.js.map
153
+ //# sourceMappingURL=chunk-F3WKIRAU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transport/websocket.ts"],"names":[],"mappings":"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","file":"chunk-F3WKIRAU.js","sourcesContent":["import type {\n OUITransport,\n OUITransportConfig,\n OUIActionHandler,\n OUIObservationHandler,\n OUIResultHandler,\n} from \"./types.js\";\nimport type {\n OUISurface,\n OUIObservationUpdate,\n OUIActionRequest,\n OUIActionResult,\n} from \"../spec/index.js\";\n\n/**\n * WebSocket transport using Socket.IO.\n * Maps OUI protocol events to socket events with a configurable namespace prefix.\n *\n * Events:\n * {ns}:dispatch — action request (runtime → client)\n * {ns}:action:result — action result (client → runtime)\n * {ns}:observation — observation (client → runtime)\n * {ns}:surface:register — registration (client → runtime)\n * {ns}:surface:deregister — deregistration (client → runtime)\n */\nexport function createWebSocketTransport(\n socket: SocketLike,\n config?: OUITransportConfig,\n): OUITransport {\n const ns = config?.namespace ?? \"oui\";\n const buffer: Array<{ event: string; data: unknown }> = [];\n const maxBuffer = config?.maxBufferSize ?? 100;\n const shouldBuffer = config?.bufferWhileDisconnected ?? true;\n\n let connected = socket.connected ?? false;\n const connectionHandlers: Array<(c: boolean) => void> = [];\n\n // Every listener this transport attaches, so dispose() can remove exactly\n // these and nothing the integrator attached to the same socket.\n const attached: Array<{ event: string; handler: (...args: any[]) => void }> =\n [];\n function listen(\n event: string,\n handler: (...args: any[]) => void,\n ): () => void {\n socket.on(event, handler);\n const entry = { event, handler };\n attached.push(entry);\n return () => {\n socket.off(event, handler);\n const i = attached.indexOf(entry);\n if (i >= 0) attached.splice(i, 1);\n };\n }\n\n listen(\"connect\", () => {\n connected = true;\n // Flush before announcing: anything queued while offline was sent earlier\n // than whatever a connection handler is about to send.\n if (shouldBuffer) {\n while (buffer.length > 0) {\n const msg = buffer.shift()!;\n socket.emit(msg.event, msg.data);\n }\n }\n connectionHandlers.forEach((h) => h(true));\n });\n\n listen(\"disconnect\", () => {\n connected = false;\n connectionHandlers.forEach((h) => h(false));\n });\n\n function emit(event: string, data: unknown) {\n if (connected) {\n socket.emit(event, data);\n } else if (shouldBuffer && buffer.length < maxBuffer) {\n buffer.push({ event, data });\n }\n }\n\n return {\n // ─── Dispatch ───────────────────────────────────────────────\n dispatch(request: OUIActionRequest) {\n emit(`${ns}:dispatch`, request);\n },\n\n onAction(handler: OUIActionHandler) {\n return listen(\n `${ns}:dispatch`,\n (data: Partial<OUIActionRequest> | undefined) => {\n // A request without a requestId cannot be answered, and answering is\n // the contract. Refuse it loudly rather than run an action whose\n // result has nowhere to go.\n if (\n !data ||\n typeof data.requestId !== \"string\" ||\n !data.requestId ||\n typeof data.surfaceId !== \"string\" ||\n typeof data.actionId !== \"string\"\n ) {\n console.warn(\n \"[OUI] Dropped a dispatch without requestId/surfaceId/actionId\",\n data,\n );\n return;\n }\n handler({\n requestId: data.requestId,\n surfaceId: data.surfaceId,\n actionId: data.actionId,\n params: data.params ?? {},\n timestamp: data.timestamp ?? Date.now(),\n });\n },\n );\n },\n\n // ─── Result ─────────────────────────────────────────────────\n sendResult(result: OUIActionResult) {\n emit(`${ns}:action:result`, result);\n },\n\n onResult(handler: OUIResultHandler) {\n return listen(`${ns}:action:result`, (data: OUIActionResult) =>\n handler(data),\n );\n },\n\n // ─── Observation Channel ────────────────────────────────────\n pushObservation(update: OUIObservationUpdate) {\n emit(`${ns}:observation`, update);\n },\n\n onObservation(handler: OUIObservationHandler) {\n return listen(`${ns}:observation`, (data: OUIObservationUpdate) =>\n handler(data),\n );\n },\n\n // ─── Surface Lifecycle ──────────────────────────────────────\n registerSurface(surface: OUISurface) {\n emit(`${ns}:surface:register`, { surface, timestamp: Date.now() });\n },\n\n deregisterSurface(surfaceId: string) {\n emit(`${ns}:surface:deregister`, { surfaceId, timestamp: Date.now() });\n },\n\n onSurfaceRegister(handler: (surface: OUISurface) => void) {\n return listen(`${ns}:surface:register`, (data: { surface: OUISurface }) =>\n handler(data.surface),\n );\n },\n\n onSurfaceDeregister(handler: (surfaceId: string) => void) {\n return listen(`${ns}:surface:deregister`, (data: { surfaceId: string }) =>\n handler(data.surfaceId),\n );\n },\n\n // ─── Connection ─────────────────────────────────────────────\n get connected() {\n return connected;\n },\n\n async connect() {\n if (connected) return;\n return new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(\n new Error(\n `OUI transport connect timeout (${config?.connectTimeoutMs ?? 10000}ms)`,\n ),\n );\n }, config?.connectTimeoutMs ?? 10000);\n\n socket.once(\"connect\", () => {\n clearTimeout(timeout);\n resolve();\n });\n\n if (typeof socket.connect === \"function\") {\n socket.connect();\n }\n });\n },\n\n disconnect() {\n if (typeof socket.disconnect === \"function\") {\n socket.disconnect();\n }\n },\n\n onConnectionChange(handler: (c: boolean) => void) {\n connectionHandlers.push(handler);\n return () => {\n const idx = connectionHandlers.indexOf(handler);\n if (idx >= 0) connectionHandlers.splice(idx, 1);\n };\n },\n\n dispose() {\n for (const { event, handler } of attached.splice(0)) {\n socket.off(event, handler);\n }\n connectionHandlers.length = 0;\n buffer.length = 0;\n },\n };\n}\n\n/**\n * Minimal socket interface — compatible with Socket.IO client or server socket.\n * Only the methods OUI actually uses.\n */\nexport interface SocketLike {\n readonly connected?: boolean;\n emit(event: string, data: unknown): void;\n on(event: string, handler: (...args: any[]) => void): void;\n off(event: string, handler: (...args: any[]) => void): void;\n once(event: string, handler: (...args: any[]) => void): void;\n connect?(): void;\n disconnect?(): void;\n}\n"]}
@@ -0,0 +1,89 @@
1
+ // src/transport/direct.ts
2
+ function createDirectTransportPair() {
3
+ const actionHandlers = [];
4
+ const resultHandlers = [];
5
+ const observationHandlers = [];
6
+ const surfaceRegisterHandlers = [];
7
+ const surfaceDeregisterHandlers = [];
8
+ function subscribe(list, handler) {
9
+ list.push(handler);
10
+ return () => {
11
+ const i = list.indexOf(handler);
12
+ if (i >= 0) list.splice(i, 1);
13
+ };
14
+ }
15
+ const noop = () => {
16
+ };
17
+ const unsubscribed = () => noop;
18
+ const server = {
19
+ dispatch(request) {
20
+ actionHandlers.forEach((h) => h(request));
21
+ },
22
+ onAction: unsubscribed,
23
+ // Server doesn't receive dispatches
24
+ sendResult: noop,
25
+ // Server doesn't answer requests
26
+ onResult(handler) {
27
+ return subscribe(resultHandlers, handler);
28
+ },
29
+ pushObservation: noop,
30
+ // Server doesn't push observations
31
+ onObservation(handler) {
32
+ return subscribe(observationHandlers, handler);
33
+ },
34
+ registerSurface: noop,
35
+ deregisterSurface: noop,
36
+ onSurfaceRegister(handler) {
37
+ return subscribe(surfaceRegisterHandlers, handler);
38
+ },
39
+ onSurfaceDeregister(handler) {
40
+ return subscribe(surfaceDeregisterHandlers, handler);
41
+ },
42
+ get connected() {
43
+ return true;
44
+ },
45
+ async connect() {
46
+ },
47
+ disconnect: noop,
48
+ onConnectionChange: unsubscribed,
49
+ dispose: noop
50
+ };
51
+ const client = {
52
+ dispatch: noop,
53
+ // Client doesn't dispatch
54
+ onAction(handler) {
55
+ return subscribe(actionHandlers, handler);
56
+ },
57
+ sendResult(result) {
58
+ resultHandlers.forEach((h) => h(result));
59
+ },
60
+ onResult: unsubscribed,
61
+ // Client doesn't receive results
62
+ pushObservation(update) {
63
+ observationHandlers.forEach((h) => h(update));
64
+ },
65
+ onObservation: unsubscribed,
66
+ // Client doesn't receive observations
67
+ registerSurface(surface) {
68
+ surfaceRegisterHandlers.forEach((h) => h(surface));
69
+ },
70
+ deregisterSurface(surfaceId) {
71
+ surfaceDeregisterHandlers.forEach((h) => h(surfaceId));
72
+ },
73
+ onSurfaceRegister: unsubscribed,
74
+ onSurfaceDeregister: unsubscribed,
75
+ get connected() {
76
+ return true;
77
+ },
78
+ async connect() {
79
+ },
80
+ disconnect: noop,
81
+ onConnectionChange: unsubscribed,
82
+ dispose: noop
83
+ };
84
+ return { server, client };
85
+ }
86
+
87
+ export { createDirectTransportPair };
88
+ //# sourceMappingURL=chunk-IIZ6HL2L.js.map
89
+ //# sourceMappingURL=chunk-IIZ6HL2L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transport/direct.ts"],"names":[],"mappings":"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","file":"chunk-IIZ6HL2L.js","sourcesContent":["import type {\n OUITransport,\n OUIActionHandler,\n OUIObservationHandler,\n OUIResultHandler,\n} from \"./types.js\";\nimport type { OUISurface, OUIObservationUpdate } from \"../spec/index.js\";\n\n/**\n * Direct transport — for same-process communication.\n * Used for API surfaces (where the agent runtime and surface are in the same Lambda)\n * and for testing (where you want synchronous, in-memory communication).\n *\n * Creates a paired (server, client) transport where messages sent on one\n * side are immediately received on the other.\n */\nexport function createDirectTransportPair(): {\n server: OUITransport;\n client: OUITransport;\n} {\n const actionHandlers: OUIActionHandler[] = [];\n const resultHandlers: OUIResultHandler[] = [];\n const observationHandlers: OUIObservationHandler[] = [];\n const surfaceRegisterHandlers: Array<(s: OUISurface) => void> = [];\n const surfaceDeregisterHandlers: Array<(id: string) => void> = [];\n\n function subscribe<T>(list: T[], handler: T): () => void {\n list.push(handler);\n return () => {\n const i = list.indexOf(handler);\n if (i >= 0) list.splice(i, 1);\n };\n }\n\n const noop = () => {};\n const unsubscribed = () => noop;\n\n const server: OUITransport = {\n dispatch(request) {\n // Server dispatches → client receives\n actionHandlers.forEach((h) => h(request));\n },\n onAction: unsubscribed, // Server doesn't receive dispatches\n sendResult: noop, // Server doesn't answer requests\n onResult(handler) {\n return subscribe(resultHandlers, handler);\n },\n pushObservation: noop, // Server doesn't push observations\n onObservation(handler) {\n return subscribe(observationHandlers, handler);\n },\n registerSurface: noop,\n deregisterSurface: noop,\n onSurfaceRegister(handler) {\n return subscribe(surfaceRegisterHandlers, handler);\n },\n onSurfaceDeregister(handler) {\n return subscribe(surfaceDeregisterHandlers, handler);\n },\n get connected() {\n return true;\n },\n async connect() {},\n disconnect: noop,\n onConnectionChange: unsubscribed,\n dispose: noop,\n };\n\n const client: OUITransport = {\n dispatch: noop, // Client doesn't dispatch\n onAction(handler) {\n return subscribe(actionHandlers, handler);\n },\n sendResult(result) {\n // Client answers → server receives\n resultHandlers.forEach((h) => h(result));\n },\n onResult: unsubscribed, // Client doesn't receive results\n pushObservation(update: OUIObservationUpdate) {\n // Client pushes → server receives\n observationHandlers.forEach((h) => h(update));\n },\n onObservation: unsubscribed, // Client doesn't receive observations\n registerSurface(surface) {\n surfaceRegisterHandlers.forEach((h) => h(surface));\n },\n deregisterSurface(surfaceId) {\n surfaceDeregisterHandlers.forEach((h) => h(surfaceId));\n },\n onSurfaceRegister: unsubscribed,\n onSurfaceDeregister: unsubscribed,\n get connected() {\n return true;\n },\n async connect() {},\n disconnect: noop,\n onConnectionChange: unsubscribed,\n dispose: noop,\n };\n\n return { server, client };\n}\n"]}