@dxos/react-ui-canvas-compute 0.13.0 → 0.14.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 (244) hide show
  1. package/dist/lib/chunk-types.mjs +142 -0
  2. package/dist/lib/chunk-types.mjs.map +1 -0
  3. package/dist/lib/index.mjs +526 -430
  4. package/dist/lib/index.mjs.map +1 -1
  5. package/dist/lib/types.mjs +2 -0
  6. package/dist/types/src/graph/controller.d.ts +1 -1
  7. package/dist/types/src/graph/controller.d.ts.map +1 -1
  8. package/dist/types/src/hooks/compute-context.d.ts +5 -4
  9. package/dist/types/src/hooks/compute-context.d.ts.map +1 -1
  10. package/dist/types/src/hooks/index.d.ts +0 -1
  11. package/dist/types/src/hooks/index.d.ts.map +1 -1
  12. package/dist/types/src/hooks/useGraphMonitor.d.ts +18 -1
  13. package/dist/types/src/hooks/useGraphMonitor.d.ts.map +1 -1
  14. package/dist/types/src/index.d.ts +1 -1
  15. package/dist/types/src/index.d.ts.map +1 -1
  16. package/dist/types/src/registry.d.ts +5 -8
  17. package/dist/types/src/registry.d.ts.map +1 -1
  18. package/dist/types/src/scene/compute.stories.d.ts.map +1 -1
  19. package/dist/types/src/scene/echo-store.d.ts +1 -1
  20. package/dist/types/src/scene/echo-store.d.ts.map +1 -1
  21. package/dist/types/src/scene/index.d.ts +0 -3
  22. package/dist/types/src/scene/index.d.ts.map +1 -1
  23. package/dist/types/src/scene/projection.d.ts.map +1 -1
  24. package/dist/types/src/scene/scene.d.ts +2 -2
  25. package/dist/types/src/scene/scene.d.ts.map +1 -1
  26. package/dist/types/src/shapes/Append.d.ts +2 -2
  27. package/dist/types/src/shapes/Append.d.ts.map +1 -1
  28. package/dist/types/src/shapes/Array.d.ts +3 -3
  29. package/dist/types/src/shapes/Array.d.ts.map +1 -1
  30. package/dist/types/src/shapes/Audio.d.ts +2 -2
  31. package/dist/types/src/shapes/Audio.d.ts.map +1 -1
  32. package/dist/types/src/shapes/Beacon.d.ts +2 -2
  33. package/dist/types/src/shapes/Beacon.d.ts.map +1 -1
  34. package/dist/types/src/shapes/Boolean.d.ts +4 -52
  35. package/dist/types/src/shapes/Boolean.d.ts.map +1 -1
  36. package/dist/types/src/shapes/Chat.d.ts +3 -3
  37. package/dist/types/src/shapes/Chat.d.ts.map +1 -1
  38. package/dist/types/src/shapes/Constant.d.ts +3 -4
  39. package/dist/types/src/shapes/Constant.d.ts.map +1 -1
  40. package/dist/types/src/shapes/Database.d.ts +2 -2
  41. package/dist/types/src/shapes/Database.d.ts.map +1 -1
  42. package/dist/types/src/shapes/Feed.d.ts +2 -2
  43. package/dist/types/src/shapes/Feed.d.ts.map +1 -1
  44. package/dist/types/src/shapes/Function.d.ts +3 -5
  45. package/dist/types/src/shapes/Function.d.ts.map +1 -1
  46. package/dist/types/src/shapes/Gpt.d.ts +2 -2
  47. package/dist/types/src/shapes/Gpt.d.ts.map +1 -1
  48. package/dist/types/src/shapes/GptRealtime.d.ts +2 -2
  49. package/dist/types/src/shapes/GptRealtime.d.ts.map +1 -1
  50. package/dist/types/src/shapes/Json.d.ts +5 -5
  51. package/dist/types/src/shapes/Json.d.ts.map +1 -1
  52. package/dist/types/src/shapes/Logic.d.ts +5 -5
  53. package/dist/types/src/shapes/Logic.d.ts.map +1 -1
  54. package/dist/types/src/shapes/RNG.d.ts +2 -2
  55. package/dist/types/src/shapes/RNG.d.ts.map +1 -1
  56. package/dist/types/src/shapes/Scope.d.ts +2 -2
  57. package/dist/types/src/shapes/Scope.d.ts.map +1 -1
  58. package/dist/types/src/shapes/Surface.d.ts +2 -2
  59. package/dist/types/src/shapes/Surface.d.ts.map +1 -1
  60. package/dist/types/src/shapes/Switch.d.ts +2 -2
  61. package/dist/types/src/shapes/Switch.d.ts.map +1 -1
  62. package/dist/types/src/shapes/Table.d.ts +2 -2
  63. package/dist/types/src/shapes/Table.d.ts.map +1 -1
  64. package/dist/types/src/shapes/Template.d.ts +2 -2
  65. package/dist/types/src/shapes/Template.d.ts.map +1 -1
  66. package/dist/types/src/shapes/Text.d.ts +2 -2
  67. package/dist/types/src/shapes/Text.d.ts.map +1 -1
  68. package/dist/types/src/shapes/TextToImage.d.ts +2 -2
  69. package/dist/types/src/shapes/TextToImage.d.ts.map +1 -1
  70. package/dist/types/src/shapes/Thread.d.ts +2 -2
  71. package/dist/types/src/shapes/Thread.d.ts.map +1 -1
  72. package/dist/types/src/shapes/Trigger.d.ts +3 -3
  73. package/dist/types/src/shapes/Trigger.d.ts.map +1 -1
  74. package/dist/types/src/shapes/append-def.d.ts +1 -2
  75. package/dist/types/src/shapes/append-def.d.ts.map +1 -1
  76. package/dist/types/src/shapes/array-def.d.ts +2 -3
  77. package/dist/types/src/shapes/array-def.d.ts.map +1 -1
  78. package/dist/types/src/shapes/audio-def.d.ts +1 -2
  79. package/dist/types/src/shapes/audio-def.d.ts.map +1 -1
  80. package/dist/types/src/shapes/beacon-def.d.ts +1 -2
  81. package/dist/types/src/shapes/beacon-def.d.ts.map +1 -1
  82. package/dist/types/src/shapes/chat-def.d.ts +1 -2
  83. package/dist/types/src/shapes/chat-def.d.ts.map +1 -1
  84. package/dist/types/src/shapes/common/Box.d.ts.map +1 -1
  85. package/dist/types/src/shapes/common/FunctionBody.d.ts.map +1 -1
  86. package/dist/types/src/shapes/common/TextBox.d.ts +26 -0
  87. package/dist/types/src/shapes/common/TextBox.d.ts.map +1 -0
  88. package/dist/types/src/shapes/common/index.d.ts +3 -1
  89. package/dist/types/src/shapes/common/index.d.ts.map +1 -1
  90. package/dist/types/src/shapes/common/node-def.d.ts +24 -0
  91. package/dist/types/src/shapes/common/node-def.d.ts.map +1 -0
  92. package/dist/types/src/shapes/common/ports.d.ts +33 -0
  93. package/dist/types/src/shapes/common/ports.d.ts.map +1 -0
  94. package/dist/types/src/shapes/constant-def.d.ts +1 -2
  95. package/dist/types/src/shapes/constant-def.d.ts.map +1 -1
  96. package/dist/types/src/shapes/database-def.d.ts +1 -2
  97. package/dist/types/src/shapes/database-def.d.ts.map +1 -1
  98. package/dist/types/src/shapes/defs.d.ts +7 -2
  99. package/dist/types/src/shapes/defs.d.ts.map +1 -1
  100. package/dist/types/src/shapes/feed-def.d.ts +1 -2
  101. package/dist/types/src/shapes/feed-def.d.ts.map +1 -1
  102. package/dist/types/src/shapes/function-def.d.ts +1 -2
  103. package/dist/types/src/shapes/function-def.d.ts.map +1 -1
  104. package/dist/types/src/shapes/gpt-def.d.ts +1 -2
  105. package/dist/types/src/shapes/gpt-def.d.ts.map +1 -1
  106. package/dist/types/src/shapes/gpt-realtime-def.d.ts +1 -2
  107. package/dist/types/src/shapes/gpt-realtime-def.d.ts.map +1 -1
  108. package/dist/types/src/shapes/json-def.d.ts +2 -3
  109. package/dist/types/src/shapes/json-def.d.ts.map +1 -1
  110. package/dist/types/src/shapes/logic-def.d.ts +2 -3
  111. package/dist/types/src/shapes/logic-def.d.ts.map +1 -1
  112. package/dist/types/src/shapes/ports.test.d.ts +2 -0
  113. package/dist/types/src/shapes/ports.test.d.ts.map +1 -0
  114. package/dist/types/src/shapes/rng-def.d.ts +1 -2
  115. package/dist/types/src/shapes/rng-def.d.ts.map +1 -1
  116. package/dist/types/src/shapes/scope-def.d.ts +1 -2
  117. package/dist/types/src/shapes/scope-def.d.ts.map +1 -1
  118. package/dist/types/src/shapes/surface-def.d.ts +1 -2
  119. package/dist/types/src/shapes/surface-def.d.ts.map +1 -1
  120. package/dist/types/src/shapes/switch-def.d.ts +1 -2
  121. package/dist/types/src/shapes/switch-def.d.ts.map +1 -1
  122. package/dist/types/src/shapes/table-def.d.ts +1 -2
  123. package/dist/types/src/shapes/table-def.d.ts.map +1 -1
  124. package/dist/types/src/shapes/template-def.d.ts +1 -2
  125. package/dist/types/src/shapes/template-def.d.ts.map +1 -1
  126. package/dist/types/src/shapes/text-def.d.ts +1 -2
  127. package/dist/types/src/shapes/text-def.d.ts.map +1 -1
  128. package/dist/types/src/shapes/text-to-image-def.d.ts +1 -2
  129. package/dist/types/src/shapes/text-to-image-def.d.ts.map +1 -1
  130. package/dist/types/src/shapes/thread-def.d.ts +1 -2
  131. package/dist/types/src/shapes/thread-def.d.ts.map +1 -1
  132. package/dist/types/src/shapes/trigger-def.d.ts +1 -2
  133. package/dist/types/src/shapes/trigger-def.d.ts.map +1 -1
  134. package/dist/types/src/testing/circuits.d.ts +1 -1
  135. package/dist/types/src/testing/circuits.d.ts.map +1 -1
  136. package/dist/types/src/types/canvas-board.test.d.ts +2 -0
  137. package/dist/types/src/types/canvas-board.test.d.ts.map +1 -0
  138. package/dist/types/src/types/index.d.ts +4 -0
  139. package/dist/types/src/types/index.d.ts.map +1 -0
  140. package/dist/types/src/types/model.d.ts +10 -0
  141. package/dist/types/src/types/model.d.ts.map +1 -0
  142. package/dist/types/src/types/schema.d.ts +66 -0
  143. package/dist/types/src/types/schema.d.ts.map +1 -0
  144. package/dist/types/src/types/shapes.d.ts +114 -0
  145. package/dist/types/src/types/shapes.d.ts.map +1 -0
  146. package/dist/types/tsconfig.tsbuildinfo +1 -1
  147. package/package.json +43 -35
  148. package/src/graph/controller.ts +1 -1
  149. package/src/hooks/compute-context.ts +7 -4
  150. package/src/hooks/index.ts +0 -1
  151. package/src/hooks/useGraphMonitor.ts +10 -1
  152. package/src/index.ts +1 -1
  153. package/src/playwright/scene.spec.ts +3 -3
  154. package/src/registry.ts +63 -91
  155. package/src/scene/compute.stories.tsx +3 -6
  156. package/src/scene/echo-store.test.ts +18 -7
  157. package/src/scene/echo-store.ts +7 -10
  158. package/src/scene/index.ts +0 -3
  159. package/src/scene/projection.ts +2 -3
  160. package/src/scene/scene.test.ts +4 -4
  161. package/src/scene/scene.ts +6 -4
  162. package/src/schema.test.ts +7 -36
  163. package/src/shapes/Append.tsx +2 -3
  164. package/src/shapes/Array.tsx +3 -4
  165. package/src/shapes/Audio.tsx +2 -2
  166. package/src/shapes/Beacon.tsx +2 -2
  167. package/src/shapes/Boolean.tsx +32 -29
  168. package/src/shapes/Chat.tsx +5 -10
  169. package/src/shapes/Constant.tsx +19 -12
  170. package/src/shapes/Database.tsx +2 -4
  171. package/src/shapes/Feed.tsx +2 -3
  172. package/src/shapes/Function.tsx +4 -10
  173. package/src/shapes/Gpt.tsx +2 -3
  174. package/src/shapes/GptRealtime.tsx +2 -2
  175. package/src/shapes/Json.tsx +5 -6
  176. package/src/shapes/Logic.tsx +5 -6
  177. package/src/shapes/RNG.tsx +2 -2
  178. package/src/shapes/Scope.tsx +2 -2
  179. package/src/shapes/Surface.tsx +9 -6
  180. package/src/shapes/Switch.tsx +10 -12
  181. package/src/shapes/Table.tsx +2 -4
  182. package/src/shapes/Template.tsx +10 -9
  183. package/src/shapes/Text.tsx +2 -3
  184. package/src/shapes/TextToImage.tsx +2 -4
  185. package/src/shapes/Thread.tsx +2 -3
  186. package/src/shapes/Trigger.tsx +3 -4
  187. package/src/shapes/append-def.ts +8 -7
  188. package/src/shapes/array-def.ts +8 -8
  189. package/src/shapes/audio-def.ts +8 -7
  190. package/src/shapes/beacon-def.ts +9 -8
  191. package/src/shapes/chat-def.tsx +8 -8
  192. package/src/shapes/common/Box.tsx +7 -4
  193. package/src/shapes/common/FunctionBody.tsx +13 -26
  194. package/src/shapes/common/TextBox.tsx +137 -0
  195. package/src/shapes/common/index.ts +3 -1
  196. package/src/shapes/common/node-def.tsx +117 -0
  197. package/src/shapes/common/ports.ts +107 -0
  198. package/src/shapes/constant-def.tsx +8 -8
  199. package/src/shapes/database-def.ts +8 -8
  200. package/src/shapes/defs.ts +21 -6
  201. package/src/shapes/feed-def.ts +8 -7
  202. package/src/shapes/function-def.ts +8 -7
  203. package/src/shapes/gpt-def.ts +8 -7
  204. package/src/shapes/gpt-realtime-def.ts +10 -10
  205. package/src/shapes/json-def.ts +15 -13
  206. package/src/shapes/logic-def.ts +14 -11
  207. package/src/shapes/ports.test.ts +23 -0
  208. package/src/shapes/rng-def.ts +8 -7
  209. package/src/shapes/scope-def.ts +8 -7
  210. package/src/shapes/surface-def.ts +8 -8
  211. package/src/shapes/switch-def.ts +8 -7
  212. package/src/shapes/table-def.ts +7 -7
  213. package/src/shapes/template-def.ts +8 -7
  214. package/src/shapes/text-def.ts +8 -8
  215. package/src/shapes/text-to-image-def.ts +8 -8
  216. package/src/shapes/thread-def.ts +8 -7
  217. package/src/shapes/trigger-def.ts +11 -8
  218. package/src/testing/circuits.ts +21 -7
  219. package/src/types/canvas-board.test.ts +84 -0
  220. package/src/types/index.ts +7 -0
  221. package/src/types/model.ts +63 -0
  222. package/src/types/schema.ts +73 -0
  223. package/src/types/shapes.ts +73 -0
  224. package/dist/types/src/compute-layout.d.ts +0 -9
  225. package/dist/types/src/compute-layout.d.ts.map +0 -1
  226. package/dist/types/src/compute.stories.d.ts +0 -58
  227. package/dist/types/src/compute.stories.d.ts.map +0 -1
  228. package/dist/types/src/hooks/useComputeGraphController.d.ts +0 -10
  229. package/dist/types/src/hooks/useComputeGraphController.d.ts.map +0 -1
  230. package/dist/types/src/scene/NodeView.d.ts +0 -11
  231. package/dist/types/src/scene/NodeView.d.ts.map +0 -1
  232. package/dist/types/src/scene/defs.d.ts +0 -5
  233. package/dist/types/src/scene/defs.d.ts.map +0 -1
  234. package/dist/types/src/scene/ports.d.ts +0 -5
  235. package/dist/types/src/scene/ports.d.ts.map +0 -1
  236. package/dist/types/src/shapes/common/function-anchors.d.ts +0 -12
  237. package/dist/types/src/shapes/common/function-anchors.d.ts.map +0 -1
  238. package/src/compute-layout.ts +0 -37
  239. package/src/compute.stories.tsx +0 -302
  240. package/src/hooks/useComputeGraphController.ts +0 -65
  241. package/src/scene/NodeView.tsx +0 -42
  242. package/src/scene/defs.ts +0 -158
  243. package/src/scene/ports.ts +0 -30
  244. package/src/shapes/common/function-anchors.ts +0 -44
@@ -0,0 +1,117 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ //
6
+ // The compute shapes as scene-engine node types: the schema is the shape's with the engine's `z`, the view
7
+ // renders the shape's component under the engine's view props, and the ports come from the shape.
8
+ //
9
+
10
+ import * as Schema from 'effect/Schema';
11
+ import React, { type ComponentType, type FC } from 'react';
12
+
13
+ import {
14
+ type CreateProps,
15
+ DEFAULT_CELL,
16
+ type Node,
17
+ type NodeDef,
18
+ type NodeViewProps,
19
+ type Port,
20
+ type Size,
21
+ UnknownNodeView,
22
+ nodeDef,
23
+ } from '@dxos/react-ui-canvas/scene';
24
+ import * as Icon from '@dxos/react-ui/Icon';
25
+
26
+ import { ComputeNodeDefContext, useComputeContext } from '../../hooks/compute-context.ts';
27
+ import { useControllerUpdates } from '../../hooks/useControllerUpdates.ts';
28
+ import { type ComputeShape } from '../defs.ts';
29
+
30
+ /** The engine's view props with the node narrowed to the shape. */
31
+ export type ComputeNodeViewProps<S extends ComputeShape> = Omit<NodeViewProps, 'node'> & { node: S };
32
+
33
+ export type ComputeNodeSpec<S extends ComputeShape> = Pick<
34
+ NodeDef,
35
+ 'name' | 'icon' | 'group' | 'resizable' | 'openable'
36
+ > & {
37
+ type: S['type'];
38
+ /** The shape's schema with `z` (`withZ`), so the node is checked as the shape before its view or ports read it. */
39
+ schema: Schema.Codec<S & { z: string }, unknown, never, never>;
40
+ component: FC<ComputeNodeViewProps<S>>;
41
+ /** The shape's factory; the engine's id, centre, size and z are applied over what it makes. */
42
+ create: (props: Pick<ComputeShape, 'id' | 'center'>) => S;
43
+ /** In scene px, as the shapes were designed; defaults to the size the factory gives a new shape. */
44
+ defaultSize?: Size;
45
+ ports: (shape: S) => readonly Port[];
46
+ };
47
+
48
+ /**
49
+ * A size in scene px as nominal units (major cells) to the nearest half cell, so a new node lands close to that px
50
+ * size and its edges on the grid.
51
+ */
52
+ const toNominal = ({ width, height }: Size): Size => ({
53
+ width: Math.round((width / DEFAULT_CELL) * 2) / 2,
54
+ height: Math.round((height / DEFAULT_CELL) * 2) / 2,
55
+ });
56
+
57
+ /** A compute shape's node definition. */
58
+ export const defineComputeNode = <S extends ComputeShape>({
59
+ schema,
60
+ component,
61
+ create,
62
+ defaultSize,
63
+ ports,
64
+ ...rest
65
+ }: ComputeNodeSpec<S>): NodeDef => {
66
+ const isShape = Schema.is(schema);
67
+ return {
68
+ ...rest,
69
+ schema,
70
+ component: computeNodeView(isShape, component),
71
+ create: ({ id, z, center, size }: CreateProps): Node => ({ ...create({ id, center }), z, size }),
72
+ // The engine sizes a new node in nominal units (major grid cells), so the px size is stated in cells.
73
+ defaultSize: toNominal(defaultSize ?? create({ id: 'default', center: { x: 0, y: 0 } }).size),
74
+ ports: (node) => (isShape(node) ? ports(node) : []),
75
+ };
76
+ };
77
+
78
+ /**
79
+ * The shape's component under the engine's view props, re-rendered on every controller update since the
80
+ * runtime state it reads is not reactive; a node that is not the shape renders as an unknown node.
81
+ */
82
+ const computeNodeView = <S extends ComputeShape>(
83
+ isShape: (node: Node) => node is Node & S & { z: string },
84
+ Component: FC<ComputeNodeViewProps<S>>,
85
+ ): ComponentType<NodeViewProps> => {
86
+ const View = (props: NodeViewProps) => {
87
+ const { node, registry } = props;
88
+ const { controller } = useComputeContext();
89
+ useControllerUpdates(controller);
90
+ if (!isShape(node)) {
91
+ return <UnknownNodeView {...props} />;
92
+ }
93
+ // A shape with no compute node behind it yet (the ghost of a create or palette drag) has no runtime state to
94
+ // read, so it shows what it will be rather than mounting the live component.
95
+ if (!node.node) {
96
+ const def = nodeDef(registry, node);
97
+ return (
98
+ <div className='dx-cover flex flex-col items-center justify-center gap-1 text-fg-muted'>
99
+ {def?.icon && <Icon.Icon icon={def.icon} size='lg' />}
100
+ <span className='text-sm truncate'>{def?.name ?? node.type}</span>
101
+ </div>
102
+ );
103
+ }
104
+
105
+ // The components lay out as a full-size flex container (centring with `grow` / `w-full`); the engine's
106
+ // node frame is not one, so the wrapper provides it.
107
+ return (
108
+ <ComputeNodeDefContext.Provider value={nodeDef(registry, node)}>
109
+ <div className='dx-cover flex'>
110
+ <Component {...props} node={node} />
111
+ </div>
112
+ </ComputeNodeDefContext.Provider>
113
+ );
114
+ };
115
+ View.displayName = `ComputeNodeView(${Component.displayName ?? Component.name})`;
116
+ return View;
117
+ };
@@ -0,0 +1,107 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import type * as Schema from 'effect/Schema';
6
+
7
+ import { VoidInput, VoidOutput } from '@dxos/conductor';
8
+ import * as SchemaAST from '@dxos/effect/SchemaAST';
9
+ import { DEFAULT_GRID, type Point, type Port, type Size } from '@dxos/react-ui-canvas/scene';
10
+
11
+ import { createAnchorId, parseAnchorId } from '../defs.ts';
12
+ import { footerHeight, headerHeight } from './box-defs.ts';
13
+
14
+ // Kept out of `FunctionBody.tsx`: react-refresh only fast-refreshes a module whose
15
+ // exports are all components, so values exported beside them force a full page reload on every edit.
16
+
17
+ /**
18
+ * Height of one property row, two minor grid cells: with the rows centred in a body that is a whole number of rows,
19
+ * every row's centre, and so every port, lands on a minor grid line.
20
+ */
21
+ export const rowHeight = 2 * DEFAULT_GRID;
22
+
23
+ /** A function shape's height: the chrome plus one row per input (at least one), each a whole number of rows. */
24
+ export const getHeight = (input: Schema.Schema<any>) => {
25
+ const properties = SchemaAST.getPropertySignatures(input.ast);
26
+ return headerHeight + footerHeight + Math.max(properties.length, 1) * rowHeight;
27
+ };
28
+
29
+ /** `n` points one row apart, centred on `center`. */
30
+ export const getRowPoints = (center: Point, n: number): Point[] => {
31
+ const span = (n - 1) * rowHeight;
32
+ return [...Array(n)].map((_, index) => ({ x: center.x, y: center.y - span / 2 + index * rowHeight }));
33
+ };
34
+
35
+ /** A port at a point relative to the node's centre, in px. */
36
+ export type PortPoint = { id: string; pos: Point };
37
+
38
+ /**
39
+ * The ports at the given points: each sits on whichever edge it is nearer to, at its offset along it; the
40
+ * id's `input.` / `output.` prefix says which way a link may run.
41
+ */
42
+ export const pointsToPorts = (points: readonly PortPoint[], size: Size): Port[] =>
43
+ points.map(({ id, pos }) => {
44
+ const [kind] = parseAnchorId(id);
45
+ const half = { x: size.width / 2, y: size.height / 2 };
46
+ // Ties go to the vertical edges (the input / output sides).
47
+ const vertical = Math.abs(Math.abs(pos.x) - half.x) <= Math.abs(Math.abs(pos.y) - half.y);
48
+ const side = vertical ? (pos.x < 0 ? 'w' : 'e') : pos.y < 0 ? 'n' : 's';
49
+ const along = vertical ? (pos.y + half.y) / size.height : (pos.x + half.x) / size.width;
50
+ return {
51
+ id,
52
+ side,
53
+ offset: Math.min(Math.max(along, 0), 1),
54
+ ...(kind === 'input' ? { accepts: 'in' as const } : kind === 'output' ? { accepts: 'out' as const } : {}),
55
+ };
56
+ });
57
+
58
+ /** Ports at points given in half-sizes from the centre (`{ x: 1, y: 0 }` is the middle of the east side). */
59
+ export const createPorts = (size: Size, points: Record<string, Point>): Port[] =>
60
+ pointsToPorts(
61
+ Object.entries(points).map(([id, { x, y }]) => ({
62
+ id,
63
+ pos: { x: (x * size.width) / 2, y: (y * size.height) / 2 },
64
+ })),
65
+ size,
66
+ );
67
+
68
+ /** Inputs stacked one row apart down the west side and outputs down the east, centred on `center`. */
69
+ export const createRowPorts = ({
70
+ size,
71
+ inputs,
72
+ outputs,
73
+ center = { x: 0, y: 0 },
74
+ }: {
75
+ size: Size;
76
+ inputs: readonly string[];
77
+ outputs: readonly string[];
78
+ center?: Point;
79
+ }): Port[] =>
80
+ pointsToPorts(
81
+ [
82
+ ...getRowPoints({ x: center.x - size.width / 2, y: center.y }, inputs.length).map((pos, index) => ({
83
+ id: inputs[index],
84
+ pos,
85
+ })),
86
+ ...getRowPoints({ x: center.x + size.width / 2, y: center.y }, outputs.length).map((pos, index) => ({
87
+ id: outputs[index],
88
+ pos,
89
+ })),
90
+ ],
91
+ size,
92
+ );
93
+
94
+ /** A function shape's ports: one per property of its input and output schemas, level with the body's rows. */
95
+ export const createFunctionPorts = (
96
+ size: Size,
97
+ input: Schema.Schema<any> = VoidInput,
98
+ output: Schema.Schema<any> = VoidOutput,
99
+ ): Port[] => {
100
+ // TODO(burdon): Set type.
101
+ const inputs = SchemaAST.getPropertySignatures(input.ast).map(({ name }) => createAnchorId('input', name.toString()));
102
+ const outputs = SchemaAST.getPropertySignatures(output.ast).map(({ name }) =>
103
+ createAnchorId('output', name.toString()),
104
+ );
105
+ // The rows are centred in the body, between the header and the footer.
106
+ return createRowPorts({ size, inputs, outputs, center: { x: 0, y: (headerHeight - footerHeight) / 2 } });
107
+ };
@@ -6,11 +6,9 @@ import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
  import React from 'react';
8
8
 
9
- import { type ShapeDef } from '@dxos/react-ui-canvas-editor';
10
- import { createAnchorMap } from '@dxos/react-ui-canvas-editor';
11
-
9
+ import { createPorts, defineComputeNode } from './common/index.ts';
12
10
  import { ConstantComponent } from './Constant.tsx';
13
- import { ComputeShape, type CreateShapeProps, createAnchorId, createShape } from './defs.ts';
11
+ import { ComputeShape, type CreateShapeProps, createAnchorId, createShape, withZ } from './defs.ts';
14
12
 
15
13
  // Kept out of `Constant.tsx`: react-refresh only fast-refreshes a module whose
16
14
  // exports are all components, so values exported beside them force a full page reload on every edit.
@@ -37,12 +35,14 @@ export type CreateConstantProps = CreateShapeProps<ConstantShape>;
37
35
  export const createConstant = (props: CreateConstantProps) =>
38
36
  createShape<ConstantShape>({ type: 'constant', size: { width: 192, height: 128 }, ...props });
39
37
 
40
- export const constantShape: ShapeDef<ConstantShape> = {
38
+ export const constantNodeDef = defineComputeNode<ConstantShape>({
41
39
  type: 'constant',
42
40
  name: 'Value',
43
41
  icon: 'ph--dots-three-circle--regular',
42
+ group: 'Inputs',
43
+ schema: withZ(ConstantShape),
44
44
  component: (props) => <ConstantComponent {...props} placeholder={'Constant'} />,
45
- createShape: createConstant,
46
- getAnchors: (shape) => createAnchorMap(shape, { [createAnchorId('output')]: { x: 1, y: 0 } }),
45
+ create: createConstant,
46
+ ports: (shape) => createPorts(shape.size, { [createAnchorId('output')]: { x: 1, y: 0 } }),
47
47
  resizable: true,
48
- };
48
+ });
@@ -5,11 +5,9 @@
5
5
  import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
 
8
- import { type ShapeDef } from '@dxos/react-ui-canvas-editor';
9
- import { createAnchorMap } from '@dxos/react-ui-canvas-editor';
10
-
8
+ import { createPorts, defineComputeNode } from './common/index.ts';
11
9
  import { DatabaseComponent } from './Database.tsx';
12
- import { ComputeShape, type CreateShapeProps, createAnchorId, createShape } from './defs.ts';
10
+ import { ComputeShape, type CreateShapeProps, createAnchorId, createShape, withZ } from './defs.ts';
13
11
 
14
12
  // Kept out of `Database.tsx`: react-refresh only fast-refreshes a module whose
15
13
  // exports are all components, so values exported beside them force a full page reload on every edit.
@@ -27,11 +25,13 @@ export type CreateDatabaseProps = CreateShapeProps<DatabaseShape>;
27
25
  export const createDatabase = (props: CreateDatabaseProps) =>
28
26
  createShape<DatabaseShape>({ type: 'database', size: { width: 128, height: 64 }, ...props });
29
27
 
30
- export const databaseShape: ShapeDef<DatabaseShape> = {
28
+ export const databaseNodeDef = defineComputeNode<DatabaseShape>({
31
29
  type: 'database',
32
30
  name: 'ECHO',
33
31
  icon: 'ph--database--regular',
32
+ group: 'Transform',
33
+ schema: withZ(DatabaseShape),
34
34
  component: DatabaseComponent,
35
- createShape: createDatabase,
36
- getAnchors: (shape) => createAnchorMap(shape, { [createAnchorId('output')]: { x: 1, y: 0 } }),
37
- };
35
+ create: createDatabase,
36
+ ports: (shape) => createPorts(shape.size, { [createAnchorId('output')]: { x: 1, y: 0 } }),
37
+ });
@@ -8,9 +8,11 @@ import * as Struct from 'effect/Struct';
8
8
  import { DEFAULT_INPUT, DEFAULT_OUTPUT } from '@dxos/conductor';
9
9
  import { Obj } from '@dxos/echo';
10
10
  import * as SchemaAST from '@dxos/effect/SchemaAST';
11
- import { Polygon } from '@dxos/react-ui-canvas-editor';
11
+ import { Point, Size } from '@dxos/react-ui-canvas/scene';
12
12
  import { type MakeOptional } from '@dxos/util';
13
13
 
14
+ import { CanvasBoard } from '../types/index.ts';
15
+
14
16
  //
15
17
  // Properties
16
18
  //
@@ -32,17 +34,30 @@ export const parseAnchorId = (id: string): [PropertyKind | undefined, string] =>
32
34
  // Shapes
33
35
  //
34
36
 
35
- export type CreateShapeProps<S extends Polygon> = Omit<MakeOptional<S, 'id' | 'size'>, 'type' | 'node'>;
37
+ export type CreateShapeProps<S extends ComputeShape> = Omit<MakeOptional<S, 'id' | 'size'>, 'type' | 'node'>;
36
38
 
37
- export const ComputeShape = Polygon.mapFields(
39
+ // The board's stored shape (a closed shape with a centre and size, every key mutable), so boards the editor wrote
40
+ // still decode.
41
+ export const ComputeShape = CanvasBoard.Shape.mapFields(
38
42
  Struct.assign({
39
- // TODO(burdon): Rename computeNode?
40
- node: Schema.mutableKey(Schema.optional(Obj.ID.annotate({ description: 'Compute node id' }))),
43
+ center: Point,
44
+ size: Size.mapFields(Struct.map(Schema.mutableKey)),
41
45
  }),
42
- );
46
+ )
47
+ .mapFields(Struct.map(Schema.mutableKey))
48
+ .mapFields(
49
+ Struct.assign({
50
+ // TODO(burdon): Rename computeNode?
51
+ node: Schema.mutableKey(Schema.optional(Obj.ID.annotate({ description: 'Compute node id' }))),
52
+ }),
53
+ );
43
54
 
44
55
  export type ComputeShape = Schema.Schema.Type<typeof ComputeShape>;
45
56
 
57
+ /** The shape's schema with the engine's z-order key, so it is a node schema. */
58
+ export const withZ = <Fields extends Schema.Struct.Fields>(shape: Schema.Struct<Fields>) =>
59
+ shape.mapFields(Struct.assign({ z: Schema.String }));
60
+
46
61
  export const createShape = <S extends ComputeShape>({ id, ...rest }: CreateShapeProps<S> & { type: string }): S => {
47
62
  return {
48
63
  id: id ?? Obj.ID.random(),
@@ -6,10 +6,9 @@ import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
 
8
8
  import { QueueInput, QueueOutput } from '@dxos/conductor';
9
- import { type ShapeDef } from '@dxos/react-ui-canvas-editor';
10
9
 
11
- import { createFunctionAnchors } from './common/index.ts';
12
- import { ComputeShape, type CreateShapeProps, createShape } from './defs.ts';
10
+ import { createFunctionPorts, defineComputeNode } from './common/index.ts';
11
+ import { ComputeShape, type CreateShapeProps, createShape, withZ } from './defs.ts';
13
12
  import { FeedComponent } from './Feed.tsx';
14
13
 
15
14
  // Kept out of `Feed.tsx`: react-refresh only fast-refreshes a module whose
@@ -32,13 +31,15 @@ export const createFeed = (props: CreateFeedProps) =>
32
31
  ...props,
33
32
  });
34
33
 
35
- export const feedShape: ShapeDef<FeedShape> = {
34
+ export const feedNodeDef = defineComputeNode<FeedShape>({
36
35
  // Must match the shape's `type` literal ('queue') so the registry resolves it; the registry is keyed by this.
37
36
  type: 'queue',
38
37
  name: 'Feed',
39
38
  icon: 'ph--queue--regular',
39
+ group: 'Outputs',
40
+ schema: withZ(FeedShape),
40
41
  component: FeedComponent,
41
- createShape: createFeed,
42
- getAnchors: (shape) => createFunctionAnchors(shape, QueueInput, QueueOutput),
42
+ create: createFeed,
43
+ ports: (shape) => createFunctionPorts(shape.size, QueueInput, QueueOutput),
43
44
  resizable: true,
44
- };
45
+ });
@@ -6,10 +6,9 @@ import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
 
8
8
  import { AnyOutput, FunctionInput } from '@dxos/conductor';
9
- import { type ShapeDef } from '@dxos/react-ui-canvas-editor';
10
9
 
11
- import { createFunctionAnchors } from './common/index.ts';
12
- import { ComputeShape, type CreateShapeProps, createShape } from './defs.ts';
10
+ import { createFunctionPorts, defineComputeNode } from './common/index.ts';
11
+ import { ComputeShape, type CreateShapeProps, createShape, withZ } from './defs.ts';
13
12
  import { FunctionShapeComponent } from './Function.tsx';
14
13
 
15
14
  // Kept out of `Function.tsx`: react-refresh only fast-refreshes a module whose
@@ -36,11 +35,13 @@ export const createFunction = (props: CreateFunctionProps) =>
36
35
  // Defs
37
36
  //
38
37
 
39
- export const functionShape: ShapeDef<FunctionShape> = {
38
+ export const functionNodeDef = defineComputeNode<FunctionShape>({
40
39
  type: 'function',
41
40
  name: 'Function',
42
41
  icon: 'ph--function--regular',
42
+ group: 'Transform',
43
+ schema: withZ(FunctionShape),
43
44
  component: FunctionShapeComponent,
44
- createShape: createFunction,
45
- getAnchors: (shape) => createFunctionAnchors(shape, FunctionInput, AnyOutput),
46
- };
45
+ create: createFunction,
46
+ ports: (shape) => createFunctionPorts(shape.size, FunctionInput, AnyOutput),
47
+ });
@@ -6,10 +6,9 @@ import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
 
8
8
  import { GptInput, GptOutput } from '@dxos/conductor';
9
- import { type ShapeDef } from '@dxos/react-ui-canvas-editor';
10
9
 
11
- import { createFunctionAnchors, getHeight } from './common/index.ts';
12
- import { ComputeShape, type CreateShapeProps, createShape } from './defs.ts';
10
+ import { createFunctionPorts, defineComputeNode, getHeight } from './common/index.ts';
11
+ import { ComputeShape, type CreateShapeProps, createShape, withZ } from './defs.ts';
13
12
  import { GptComponent } from './Gpt.tsx';
14
13
 
15
14
  // Kept out of `Gpt.tsx`: react-refresh only fast-refreshes a module whose
@@ -32,12 +31,14 @@ export const createGpt = (props: CreateGptProps) =>
32
31
  ...props,
33
32
  });
34
33
 
35
- export const gptShape: ShapeDef<GptShape> = {
34
+ export const gptNodeDef = defineComputeNode<GptShape>({
36
35
  type: 'gpt',
37
36
  name: 'GPT',
38
37
  icon: 'ph--brain--regular',
38
+ group: 'Transform',
39
+ schema: withZ(GptShape),
39
40
  component: GptComponent,
40
- createShape: createGpt,
41
- getAnchors: (shape) => createFunctionAnchors(shape, GptInput, GptOutput),
41
+ create: createGpt,
42
+ ports: (shape) => createFunctionPorts(shape.size, GptInput, GptOutput),
42
43
  openable: true,
43
- };
44
+ });
@@ -5,10 +5,8 @@
5
5
  import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
 
8
- import { type ShapeDef } from '@dxos/react-ui-canvas-editor';
9
-
10
- import { createFunctionAnchors } from './common/index.ts';
11
- import { ComputeShape, type CreateShapeProps, createShape } from './defs.ts';
8
+ import { createFunctionPorts, defineComputeNode } from './common/index.ts';
9
+ import { ComputeShape, type CreateShapeProps, createShape, withZ } from './defs.ts';
12
10
  import { GptRealtimeComponent } from './GptRealtime.tsx';
13
11
 
14
12
  // Kept out of `GptRealtime.tsx`: react-refresh only fast-refreshes a module whose
@@ -27,20 +25,22 @@ export type CreateGptRealtimeProps = CreateShapeProps<GptRealtimeShape>;
27
25
  export const createGptRealtime = (props: CreateGptRealtimeProps) =>
28
26
  createShape<GptRealtimeShape>({ type: 'gpt-realtime', size: { width: 256, height: 256 }, ...props });
29
27
 
30
- export const gptRealtimeShape: ShapeDef<GptRealtimeShape> = {
28
+ export const gptRealtimeNodeDef = defineComputeNode<GptRealtimeShape>({
31
29
  type: 'gpt-realtime',
32
30
  name: 'GPT Realtime',
33
31
  icon: 'ph--pulse--regular',
32
+ group: 'Transform',
33
+ schema: withZ(GptRealtimeShape),
34
34
  component: GptRealtimeComponent,
35
- createShape: createGptRealtime,
35
+ create: createGptRealtime,
36
36
  // TODO(dmaretskyi): Can we fetch the schema dynamically?
37
- getAnchors: (shape) =>
38
- createFunctionAnchors(
39
- shape,
37
+ ports: (shape) =>
38
+ createFunctionPorts(
39
+ shape.size,
40
40
  Schema.Struct({
41
41
  audio: Schema.Any,
42
42
  }),
43
43
  Schema.Struct({}),
44
44
  ),
45
45
  resizable: true,
46
- };
46
+ });
@@ -6,11 +6,9 @@ import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
 
8
8
  import { DefaultOutput, JsonTransformInput } from '@dxos/conductor';
9
- import { type ShapeDef } from '@dxos/react-ui-canvas-editor';
10
- import { createAnchorMap } from '@dxos/react-ui-canvas-editor';
11
9
 
12
- import { createFunctionAnchors, getHeight } from './common/index.ts';
13
- import { ComputeShape, type CreateShapeProps, createAnchorId, createShape } from './defs.ts';
10
+ import { createFunctionPorts, createPorts, defineComputeNode, getHeight } from './common/index.ts';
11
+ import { ComputeShape, type CreateShapeProps, createAnchorId, createShape, withZ } from './defs.ts';
14
12
  import { JsonComponent, JsonTransformComponent } from './Json.tsx';
15
13
 
16
14
  // Kept out of `Json.tsx`: react-refresh only fast-refreshes a module whose
@@ -45,19 +43,21 @@ export type CreateJsonProps = CreateShapeProps<JsonShape>;
45
43
  export const createJson = (props: CreateJsonProps) =>
46
44
  createShape<JsonShape>({ type: 'json', size: { width: 256, height: 256 }, ...props });
47
45
 
48
- export const jsonShape: ShapeDef<JsonShape> = {
46
+ export const jsonNodeDef = defineComputeNode<JsonShape>({
49
47
  type: 'json',
50
48
  name: 'JSON',
51
49
  icon: 'ph--code--regular',
50
+ group: 'Outputs',
51
+ schema: withZ(JsonShape),
52
52
  component: JsonComponent,
53
- createShape: createJson,
54
- getAnchors: (shape) =>
55
- createAnchorMap(shape, {
53
+ create: createJson,
54
+ ports: (shape) =>
55
+ createPorts(shape.size, {
56
56
  [createAnchorId('input')]: { x: -1, y: 0 },
57
57
  [createAnchorId('output')]: { x: 1, y: 0 },
58
58
  }),
59
59
  resizable: true,
60
- };
60
+ });
61
61
 
62
62
  export type CreateJsonTransformProps = CreateShapeProps<JsonTransformShape> & { expression?: string };
63
63
 
@@ -68,12 +68,14 @@ export const createJsonTransform = (props: CreateJsonTransformProps) =>
68
68
  ...props,
69
69
  });
70
70
 
71
- export const jsonTransformShape: ShapeDef<JsonTransformShape> = {
71
+ export const jsonTransformNodeDef = defineComputeNode<JsonTransformShape>({
72
72
  type: 'json-transform',
73
73
  name: 'Transform',
74
74
  icon: 'ph--shuffle-simple--regular',
75
+ group: 'Operations',
76
+ schema: withZ(JsonTransformShape),
75
77
  component: JsonTransformComponent,
76
- createShape: createJsonTransform,
77
- getAnchors: (shape) => createFunctionAnchors(shape, JsonTransformInput, DefaultOutput),
78
+ create: createJsonTransform,
79
+ ports: (shape) => createFunctionPorts(shape.size, JsonTransformInput, DefaultOutput),
78
80
  resizable: true,
79
- };
81
+ });
@@ -6,10 +6,9 @@ import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
 
8
8
  import { IfElseInput, IfElseOutput, IfInput, IfOutput } from '@dxos/conductor';
9
- import { type ShapeDef } from '@dxos/react-ui-canvas-editor';
10
9
 
11
- import { createFunctionAnchors, getHeight } from './common/index.ts';
12
- import { ComputeShape, type CreateShapeProps, createShape } from './defs.ts';
10
+ import { createFunctionPorts, defineComputeNode, getHeight } from './common/index.ts';
11
+ import { ComputeShape, type CreateShapeProps, createShape, withZ } from './defs.ts';
13
12
  import { IfComponent, IfElseComponent } from './Logic.tsx';
14
13
 
15
14
  // Kept out of `Logic.tsx`: react-refresh only fast-refreshes a module whose
@@ -44,25 +43,29 @@ export type CreateIfProps = CreateShapeProps<IfShape> & { if?: string };
44
43
  export const createIf = (props: CreateIfProps) =>
45
44
  createShape<IfShape>({ type: 'if', size: { width: 192, height: getHeight(IfInput) }, ...props });
46
45
 
47
- export const ifShape: ShapeDef<IfShape> = {
46
+ export const ifNodeDef = defineComputeNode<IfShape>({
48
47
  type: 'if',
49
48
  name: 'IF',
50
49
  icon: 'ph--arrows-split--regular',
50
+ group: 'Operations',
51
+ schema: withZ(IfShape),
51
52
  component: IfComponent,
52
- createShape: createIf,
53
- getAnchors: (shape) => createFunctionAnchors(shape, IfInput, IfOutput),
54
- };
53
+ create: createIf,
54
+ ports: (shape) => createFunctionPorts(shape.size, IfInput, IfOutput),
55
+ });
55
56
 
56
57
  export type CreateIfElseProps = CreateShapeProps<IfShape> & { if?: string };
57
58
 
58
59
  export const createIfElse = (props: CreateIfElseProps) =>
59
60
  createShape<IfElseShape>({ type: 'if-else', size: { width: 192, height: getHeight(IfElseInput) }, ...props });
60
61
 
61
- export const ifElseShape: ShapeDef<IfElseShape> = {
62
+ export const ifElseNodeDef = defineComputeNode<IfElseShape>({
62
63
  type: 'if-else',
63
64
  name: 'IF/ELSE',
64
65
  icon: 'ph--arrows-merge--regular',
66
+ group: 'Operations',
67
+ schema: withZ(IfElseShape),
65
68
  component: IfElseComponent,
66
- createShape: createIfElse,
67
- getAnchors: (shape) => createFunctionAnchors(shape, IfElseInput, IfElseOutput),
68
- };
69
+ create: createIfElse,
70
+ ports: (shape) => createFunctionPorts(shape.size, IfElseInput, IfElseOutput),
71
+ });
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { DEFAULT_GRID, nominalSize } from '@dxos/react-ui-canvas/scene';
8
+
9
+ import { computeNodeDefs } from '../registry.ts';
10
+
11
+ describe('compute ports', () => {
12
+ test('every port of a new shape sits on a minor grid line', ({ expect }) => {
13
+ for (const def of computeNodeDefs) {
14
+ const size = nominalSize(def.defaultSize);
15
+ const node = def.create({ id: 'n', z: 'a0', center: { x: size.width / 2, y: size.height / 2 }, size });
16
+ for (const port of def.ports?.(node) ?? []) {
17
+ const along = port.side === 'w' || port.side === 'e' ? size.height : size.width;
18
+ // Distance from the node's top (or left) edge, which snaps to the grid.
19
+ expect(`${def.type} ${port.id} ${(port.offset * along) % DEFAULT_GRID}`).toBe(`${def.type} ${port.id} 0`);
20
+ }
21
+ }
22
+ });
23
+ });
@@ -5,9 +5,8 @@
5
5
  import * as Schema from 'effect/Schema';
6
6
  import * as Struct from 'effect/Struct';
7
7
 
8
- import { type ShapeDef, createAnchorMap } from '@dxos/react-ui-canvas-editor';
9
-
10
- import { ComputeShape, type CreateShapeProps, createAnchorId, createShape } from './defs.ts';
8
+ import { createPorts, defineComputeNode } from './common/index.ts';
9
+ import { ComputeShape, type CreateShapeProps, createAnchorId, createShape, withZ } from './defs.ts';
11
10
  import { RandomComponent } from './RNG.tsx';
12
11
 
13
12
  // Kept out of `RNG.tsx`: react-refresh only fast-refreshes a module whose
@@ -32,11 +31,13 @@ export const createRandom = (props: CreateRandomProps) =>
32
31
  ...props,
33
32
  });
34
33
 
35
- export const randomShape: ShapeDef<RandomShape> = {
34
+ export const randomNodeDef = defineComputeNode<RandomShape>({
36
35
  type: 'rng',
37
36
  name: 'Random',
38
37
  icon: 'ph--dice-six--regular',
38
+ group: 'Inputs',
39
+ schema: withZ(RandomShape),
39
40
  component: RandomComponent,
40
- createShape: createRandom,
41
- getAnchors: (shape) => createAnchorMap(shape, { [createAnchorId('output')]: { x: 1, y: 0 } }),
42
- };
41
+ create: createRandom,
42
+ ports: (shape) => createPorts(shape.size, { [createAnchorId('output')]: { x: 1, y: 0 } }),
43
+ });