@synnaxlabs/client 0.54.2 → 0.56.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 (222) hide show
  1. package/.turbo/turbo-build.log +10 -13
  2. package/dist/client.cjs +60 -42
  3. package/dist/client.js +8037 -6265
  4. package/dist/src/access/policy/client.d.ts +70 -80
  5. package/dist/src/access/policy/client.d.ts.map +1 -1
  6. package/dist/src/access/policy/types.gen.d.ts +18 -20
  7. package/dist/src/access/policy/types.gen.d.ts.map +1 -1
  8. package/dist/src/access/role/client.d.ts.map +1 -1
  9. package/dist/src/access/role/types.gen.d.ts +2 -2
  10. package/dist/src/actions/actions.d.ts +68 -0
  11. package/dist/src/actions/actions.d.ts.map +1 -0
  12. package/dist/src/actions/actions.spec.d.ts +2 -0
  13. package/dist/src/actions/actions.spec.d.ts.map +1 -0
  14. package/dist/src/actions/external.d.ts +2 -0
  15. package/dist/src/actions/external.d.ts.map +1 -0
  16. package/dist/src/actions/index.d.ts +2 -0
  17. package/dist/src/actions/index.d.ts.map +1 -0
  18. package/dist/src/arc/arc.spec.d.ts +2 -0
  19. package/dist/src/arc/arc.spec.d.ts.map +1 -0
  20. package/dist/src/arc/client.d.ts.map +1 -1
  21. package/dist/src/arc/compiler/types.gen.d.ts +1 -1
  22. package/dist/src/arc/compiler/types.gen.d.ts.map +1 -1
  23. package/dist/src/arc/graph/types.gen.d.ts +40 -40
  24. package/dist/src/arc/graph/types.gen.d.ts.map +1 -1
  25. package/dist/src/arc/ir/types.gen.d.ts +202 -233
  26. package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
  27. package/dist/src/arc/module/types.gen.d.ts +63 -82
  28. package/dist/src/arc/module/types.gen.d.ts.map +1 -1
  29. package/dist/src/arc/program/types.gen.d.ts +63 -82
  30. package/dist/src/arc/program/types.gen.d.ts.map +1 -1
  31. package/dist/src/arc/types/types.gen.d.ts +11 -11
  32. package/dist/src/arc/types/types.gen.d.ts.map +1 -1
  33. package/dist/src/arc/types.gen.d.ts +139 -158
  34. package/dist/src/arc/types.gen.d.ts.map +1 -1
  35. package/dist/src/auth/auth.d.ts +3 -3
  36. package/dist/src/auth/auth.d.ts.map +1 -1
  37. package/dist/src/channel/client.d.ts +2 -2
  38. package/dist/src/channel/client.d.ts.map +1 -1
  39. package/dist/src/channel/retriever.d.ts +5 -8
  40. package/dist/src/channel/retriever.d.ts.map +1 -1
  41. package/dist/src/channel/types.gen.d.ts +3 -3
  42. package/dist/src/channel/types.gen.d.ts.map +1 -1
  43. package/dist/src/channel/writer.d.ts.map +1 -1
  44. package/dist/src/client.d.ts +5 -0
  45. package/dist/src/client.d.ts.map +1 -1
  46. package/dist/src/connection/checker.d.ts +17 -2
  47. package/dist/src/connection/checker.d.ts.map +1 -1
  48. package/dist/src/control/state.d.ts.map +1 -1
  49. package/dist/src/device/client.d.ts.map +1 -1
  50. package/dist/src/device/types.gen.d.ts +6 -8
  51. package/dist/src/device/types.gen.d.ts.map +1 -1
  52. package/dist/src/errors.d.ts +2 -0
  53. package/dist/src/errors.d.ts.map +1 -1
  54. package/dist/src/framer/adapter.d.ts.map +1 -1
  55. package/dist/src/framer/client.d.ts +2 -2
  56. package/dist/src/framer/client.d.ts.map +1 -1
  57. package/dist/src/framer/codec.d.ts +9 -1
  58. package/dist/src/framer/codec.d.ts.map +1 -1
  59. package/dist/src/framer/deleter.d.ts.map +1 -1
  60. package/dist/src/framer/frame.d.ts +1 -1
  61. package/dist/src/framer/iterator.d.ts +84 -3
  62. package/dist/src/framer/iterator.d.ts.map +1 -1
  63. package/dist/src/framer/streamProxy.d.ts.map +1 -1
  64. package/dist/src/framer/streamer.d.ts +1 -3
  65. package/dist/src/framer/streamer.d.ts.map +1 -1
  66. package/dist/src/framer/types.gen.d.ts +18 -0
  67. package/dist/src/framer/types.gen.d.ts.map +1 -1
  68. package/dist/src/framer/writer.d.ts +8 -8
  69. package/dist/src/framer/writer.d.ts.map +1 -1
  70. package/dist/src/group/client.d.ts +1 -2
  71. package/dist/src/group/client.d.ts.map +1 -1
  72. package/dist/src/group/types.gen.d.ts +2 -2
  73. package/dist/src/index.d.ts +2 -1
  74. package/dist/src/index.d.ts.map +1 -1
  75. package/dist/src/label/client.d.ts +5 -8
  76. package/dist/src/label/client.d.ts.map +1 -1
  77. package/dist/src/lineplot/client.d.ts.map +1 -1
  78. package/dist/src/lineplot/types.gen.d.ts +2 -2
  79. package/dist/src/log/client.d.ts.map +1 -1
  80. package/dist/src/log/types.gen.d.ts +2 -2
  81. package/dist/src/ontology/client.d.ts +1 -3
  82. package/dist/src/ontology/client.d.ts.map +1 -1
  83. package/dist/src/ontology/payload.d.ts +12 -16
  84. package/dist/src/ontology/payload.d.ts.map +1 -1
  85. package/dist/src/ontology/types.gen.d.ts +1 -2
  86. package/dist/src/ontology/types.gen.d.ts.map +1 -1
  87. package/dist/src/ontology/writer.d.ts +5 -10
  88. package/dist/src/ontology/writer.d.ts.map +1 -1
  89. package/dist/src/rack/client.d.ts.map +1 -1
  90. package/dist/src/rack/types.gen.d.ts +3 -3
  91. package/dist/src/ranger/alias/client.d.ts.map +1 -1
  92. package/dist/src/ranger/client.d.ts.map +1 -1
  93. package/dist/src/ranger/kv/client.d.ts.map +1 -1
  94. package/dist/src/ranger/types.gen.d.ts +6 -6
  95. package/dist/src/ranger/types.gen.d.ts.map +1 -1
  96. package/dist/src/ranger/writer.d.ts +2 -3
  97. package/dist/src/ranger/writer.d.ts.map +1 -1
  98. package/dist/src/schematic/actions.d.ts +147 -0
  99. package/dist/src/schematic/actions.d.ts.map +1 -0
  100. package/dist/src/schematic/actions.gen.d.ts +484 -0
  101. package/dist/src/schematic/actions.gen.d.ts.map +1 -0
  102. package/dist/src/schematic/actions.spec.d.ts +2 -0
  103. package/dist/src/schematic/actions.spec.d.ts.map +1 -0
  104. package/dist/src/schematic/client.d.ts +53 -2
  105. package/dist/src/schematic/client.d.ts.map +1 -1
  106. package/dist/src/schematic/external.d.ts +2 -0
  107. package/dist/src/schematic/external.d.ts.map +1 -1
  108. package/dist/src/schematic/symbol/client.d.ts.map +1 -1
  109. package/dist/src/schematic/symbol/types.gen.d.ts +48 -58
  110. package/dist/src/schematic/symbol/types.gen.d.ts.map +1 -1
  111. package/dist/src/schematic/types.gen.d.ts +131 -5
  112. package/dist/src/schematic/types.gen.d.ts.map +1 -1
  113. package/dist/src/status/client.d.ts.map +1 -1
  114. package/dist/src/status/payload.d.ts +3 -3
  115. package/dist/src/table/actions.d.ts +156 -0
  116. package/dist/src/table/actions.d.ts.map +1 -0
  117. package/dist/src/table/actions.gen.d.ts +587 -0
  118. package/dist/src/table/actions.gen.d.ts.map +1 -0
  119. package/dist/src/table/client.d.ts +28 -2
  120. package/dist/src/table/client.d.ts.map +1 -1
  121. package/dist/src/table/external.d.ts +2 -0
  122. package/dist/src/table/external.d.ts.map +1 -1
  123. package/dist/src/table/types.gen.d.ts +71 -4
  124. package/dist/src/table/types.gen.d.ts.map +1 -1
  125. package/dist/src/task/client.d.ts.map +1 -1
  126. package/dist/src/task/types.gen.d.ts +7 -7
  127. package/dist/src/task/types.gen.d.ts.map +1 -1
  128. package/dist/src/user/client.d.ts +2 -2
  129. package/dist/src/user/client.d.ts.map +1 -1
  130. package/dist/src/user/types.gen.d.ts +2 -2
  131. package/dist/src/view/client.d.ts.map +1 -1
  132. package/dist/src/view/types.gen.d.ts +2 -2
  133. package/dist/src/workspace/client.d.ts.map +1 -1
  134. package/dist/src/workspace/types.gen.d.ts +3 -3
  135. package/dist/src/workspace/types.gen.d.ts.map +1 -1
  136. package/package.json +12 -11
  137. package/src/access/policy/client.ts +4 -7
  138. package/src/access/role/client.ts +6 -26
  139. package/src/actions/actions.spec.ts +229 -0
  140. package/src/actions/actions.ts +104 -0
  141. package/src/actions/external.ts +10 -0
  142. package/src/actions/index.ts +10 -0
  143. package/src/arc/arc.spec.ts +44 -0
  144. package/src/arc/client.ts +3 -7
  145. package/src/arc/compiler/types.gen.ts +2 -1
  146. package/src/arc/ir/types.gen.ts +102 -48
  147. package/src/arc/lsp.spec.ts +3 -7
  148. package/src/arc/types/types.gen.ts +3 -3
  149. package/src/auth/auth.spec.ts +12 -13
  150. package/src/auth/auth.ts +48 -34
  151. package/src/channel/batchRetriever.spec.ts +13 -4
  152. package/src/channel/channel.spec.ts +13 -0
  153. package/src/channel/client.ts +8 -6
  154. package/src/channel/retriever.ts +7 -16
  155. package/src/channel/types.gen.ts +1 -2
  156. package/src/channel/writer.ts +4 -20
  157. package/src/client.ts +3 -0
  158. package/src/connection/checker.ts +48 -10
  159. package/src/connection/connection.spec.ts +64 -2
  160. package/src/control/state.ts +5 -4
  161. package/src/device/client.ts +5 -8
  162. package/src/device/device.spec.ts +7 -5
  163. package/src/device/types.gen.ts +4 -4
  164. package/src/errors.ts +8 -9
  165. package/src/framer/adapter.ts +2 -4
  166. package/src/framer/client.ts +12 -0
  167. package/src/framer/codec.spec.ts +53 -3
  168. package/src/framer/codec.ts +58 -25
  169. package/src/framer/deleter.ts +2 -8
  170. package/src/framer/iterator.ts +42 -39
  171. package/src/framer/streamProxy.ts +11 -12
  172. package/src/framer/streamer.spec.ts +1 -1
  173. package/src/framer/streamer.ts +2 -7
  174. package/src/framer/types.gen.ts +20 -0
  175. package/src/framer/writer.spec.ts +221 -1
  176. package/src/framer/writer.ts +53 -28
  177. package/src/group/client.ts +4 -7
  178. package/src/index.ts +3 -2
  179. package/src/label/client.ts +6 -16
  180. package/src/label/label.spec.ts +12 -0
  181. package/src/lineplot/client.ts +6 -21
  182. package/src/log/client.ts +6 -21
  183. package/src/ontology/client.ts +2 -3
  184. package/src/ontology/ontology.spec.ts +10 -0
  185. package/src/ontology/types.gen.ts +0 -1
  186. package/src/ontology/writer.ts +4 -7
  187. package/src/rack/client.ts +4 -7
  188. package/src/rack/rack.spec.ts +12 -1
  189. package/src/ranger/alias/client.ts +6 -11
  190. package/src/ranger/client.ts +2 -3
  191. package/src/ranger/kv/client.ts +4 -7
  192. package/src/ranger/ranger.spec.ts +12 -0
  193. package/src/ranger/writer.ts +4 -17
  194. package/src/schematic/access.spec.ts +6 -6
  195. package/src/schematic/actions.gen.ts +200 -0
  196. package/src/schematic/actions.spec.ts +699 -0
  197. package/src/schematic/actions.ts +168 -0
  198. package/src/schematic/client.ts +34 -30
  199. package/src/schematic/external.ts +2 -0
  200. package/src/schematic/schematic.spec.ts +233 -69
  201. package/src/schematic/symbol/client.spec.ts +33 -9
  202. package/src/schematic/symbol/client.ts +6 -11
  203. package/src/schematic/symbol/types.gen.ts +1 -10
  204. package/src/schematic/types.gen.ts +55 -6
  205. package/src/status/client.ts +4 -10
  206. package/src/status/status.spec.ts +7 -6
  207. package/src/table/access.spec.ts +0 -6
  208. package/src/table/actions.gen.ts +243 -0
  209. package/src/table/actions.ts +255 -0
  210. package/src/table/client.ts +21 -25
  211. package/src/table/external.ts +2 -0
  212. package/src/table/table.spec.ts +588 -43
  213. package/src/table/types.gen.ts +58 -5
  214. package/src/task/client.ts +14 -20
  215. package/src/task/task.spec.ts +15 -1
  216. package/src/task/types.gen.ts +8 -6
  217. package/src/user/client.ts +6 -11
  218. package/src/view/client.ts +4 -7
  219. package/src/view/view.spec.ts +9 -5
  220. package/src/workspace/client.ts +6 -16
  221. package/src/workspace/types.gen.ts +2 -1
  222. package/src/workspace/workspace.spec.ts +14 -1
@@ -0,0 +1,229 @@
1
+ // Copyright 2026 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { type Draft, produce } from "immer";
11
+ import { describe, expect, it } from "vitest";
12
+ import { z } from "zod";
13
+
14
+ import { actions } from "@/actions";
15
+
16
+ interface DemoState {
17
+ name: string;
18
+ items: { key: string; value: number }[];
19
+ }
20
+
21
+ type DemoAction =
22
+ | { type: "rename"; payload: { name: string } }
23
+ | { type: "set_item"; payload: { key: string; value: number } }
24
+ | { type: "remove_item"; payload: { key: string } };
25
+
26
+ const renameAction = (name: string): DemoAction => ({
27
+ type: "rename",
28
+ payload: { name },
29
+ });
30
+ const setItemAction = (key: string, value: number): DemoAction => ({
31
+ type: "set_item",
32
+ payload: { key, value },
33
+ });
34
+ const removeItemAction = (key: string): DemoAction => ({
35
+ type: "remove_item",
36
+ payload: { key },
37
+ });
38
+
39
+ const reduceOne = (
40
+ draft: Draft<DemoState>,
41
+ action: DemoAction,
42
+ ): actions.HandlerResult<DemoAction> => {
43
+ switch (action.type) {
44
+ case "rename": {
45
+ const prev = draft.name;
46
+ draft.name = action.payload.name;
47
+ return { inverse: [renameAction(prev)], targets: ["name"] };
48
+ }
49
+ case "set_item": {
50
+ const idx = draft.items.findIndex((i) => i.key === action.payload.key);
51
+ if (idx === -1) {
52
+ draft.items.push(action.payload);
53
+ return {
54
+ inverse: [removeItemAction(action.payload.key)],
55
+ targets: [action.payload.key],
56
+ };
57
+ }
58
+ const prevValue = draft.items[idx].value;
59
+ draft.items[idx].value = action.payload.value;
60
+ return {
61
+ inverse: [setItemAction(action.payload.key, prevValue)],
62
+ targets: [action.payload.key],
63
+ };
64
+ }
65
+ case "remove_item": {
66
+ const idx = draft.items.findIndex((i) => i.key === action.payload.key);
67
+ if (idx === -1) return actions.NO_OP_RESULT;
68
+ const removed = draft.items[idx];
69
+ draft.items.splice(idx, 1);
70
+ return {
71
+ inverse: [setItemAction(removed.key, removed.value)],
72
+ targets: [removed.key],
73
+ };
74
+ }
75
+ }
76
+ };
77
+
78
+ const reduceAll = actions.createReduceAll(reduceOne);
79
+
80
+ describe("snapshotDraft", () => {
81
+ it("Should return plain values unchanged", () => {
82
+ const v = { a: 1, b: [2, 3] };
83
+ expect(actions.snapshotDraft(v)).toBe(v);
84
+ });
85
+
86
+ it("Should freeze a draft into a plain object that survives the produce closure", () => {
87
+ const original = { items: [{ key: "a", value: 1 }] };
88
+ let captured: typeof original | undefined;
89
+ const next = produce(original, (draft) => {
90
+ captured = actions.snapshotDraft(draft.items[0]) as unknown as typeof original;
91
+ draft.items[0].value = 99;
92
+ });
93
+ expect(next.items[0].value).toBe(99);
94
+ expect(captured).toEqual({ key: "a", value: 1 });
95
+ });
96
+
97
+ it("Should preserve primitive values without wrapping", () => {
98
+ expect(actions.snapshotDraft(42)).toBe(42);
99
+ expect(actions.snapshotDraft("hello")).toBe("hello");
100
+ expect(actions.snapshotDraft(null)).toBe(null);
101
+ });
102
+ });
103
+
104
+ describe("NO_OP_RESULT", () => {
105
+ it("Should expose empty inverse and targets", () => {
106
+ expect(actions.NO_OP_RESULT.inverse).toHaveLength(0);
107
+ expect(actions.NO_OP_RESULT.targets).toHaveLength(0);
108
+ });
109
+ });
110
+
111
+ describe("createReduceAll", () => {
112
+ const initial: DemoState = { name: "before", items: [{ key: "a", value: 1 }] };
113
+
114
+ it("Should apply a single action and return the new state", () => {
115
+ const { next } = reduceAll(initial, [renameAction("after")]);
116
+ expect(next.name).toBe("after");
117
+ expect(initial.name).toBe("before");
118
+ });
119
+
120
+ it("Should accumulate inverses in reverse application order", () => {
121
+ const { inverse } = reduceAll(initial, [
122
+ renameAction("step-1"),
123
+ setItemAction("a", 2),
124
+ setItemAction("b", 7),
125
+ ]);
126
+ expect(inverse).toEqual([
127
+ removeItemAction("b"),
128
+ setItemAction("a", 1),
129
+ renameAction("before"),
130
+ ]);
131
+ });
132
+
133
+ it("Should round-trip state through forward then inverse application", () => {
134
+ const forward = [
135
+ renameAction("after"),
136
+ setItemAction("a", 2),
137
+ setItemAction("b", 7),
138
+ ];
139
+ const { next, inverse } = reduceAll(initial, forward);
140
+ const { next: roundtripped } = reduceAll(next, inverse);
141
+ expect(roundtripped).toEqual(initial);
142
+ });
143
+
144
+ it("Should de-duplicate targets across actions", () => {
145
+ const { targets } = reduceAll(initial, [
146
+ setItemAction("a", 5),
147
+ setItemAction("a", 6),
148
+ setItemAction("b", 7),
149
+ ]);
150
+ expect(targets).toHaveLength(2);
151
+ expect(targets).toEqual(expect.arrayContaining(["a", "b"]));
152
+ });
153
+
154
+ it("Should drop NO_OP_RESULT entries from the inverse list", () => {
155
+ const { inverse } = reduceAll(initial, [removeItemAction("missing")]);
156
+ expect(inverse).toHaveLength(0);
157
+ });
158
+
159
+ it("Should leave the source state untouched after produce", () => {
160
+ const before = JSON.parse(JSON.stringify(initial));
161
+ reduceAll(initial, [
162
+ renameAction("x"),
163
+ setItemAction("a", 99),
164
+ setItemAction("c", 3),
165
+ ]);
166
+ expect(initial).toEqual(before);
167
+ });
168
+ });
169
+
170
+ describe("scopedZ", () => {
171
+ const schema = actions.scopedZ(z.string(), z.object({ kind: z.string() }));
172
+
173
+ it("Should parse a fully-populated envelope", () => {
174
+ const parsed = schema.parse({
175
+ key: "abc",
176
+ dispatchKey: "dk",
177
+ seq: 42,
178
+ actions: [{ kind: "rename" }],
179
+ });
180
+ expect(parsed.key).toBe("abc");
181
+ expect(parsed.seq).toBe(42);
182
+ expect(parsed.actions).toEqual([{ kind: "rename" }]);
183
+ });
184
+
185
+ it("Should default seq to 0 when missing", () => {
186
+ const parsed = schema.parse({ key: "abc", dispatchKey: "dk", actions: [] });
187
+ expect(parsed.seq).toBe(0);
188
+ });
189
+
190
+ it("Should reject negative seq", () => {
191
+ expect(() =>
192
+ schema.parse({ key: "abc", dispatchKey: "dk", seq: -1, actions: [] }),
193
+ ).toThrow();
194
+ });
195
+
196
+ it("Should reject non-integer seq", () => {
197
+ expect(() =>
198
+ schema.parse({ key: "abc", dispatchKey: "dk", seq: 1.5, actions: [] }),
199
+ ).toThrow();
200
+ });
201
+
202
+ it("Should reject envelopes whose actions fail the inner schema", () => {
203
+ expect(() =>
204
+ schema.parse({
205
+ key: "abc",
206
+ dispatchKey: "dk",
207
+ actions: [{ kind: 7 }],
208
+ }),
209
+ ).toThrow();
210
+ });
211
+ });
212
+
213
+ describe("dispatchReqZ", () => {
214
+ const schema = actions.dispatchReqZ(z.string(), z.object({ kind: z.string() }));
215
+
216
+ it("Should parse a camelCase dispatchKey body", () => {
217
+ const parsed = schema.parse({
218
+ key: "abc",
219
+ dispatchKey: "dk",
220
+ actions: [{ kind: "x" }],
221
+ });
222
+ expect(parsed.dispatchKey).toBe("dk");
223
+ expect(parsed.actions).toEqual([{ kind: "x" }]);
224
+ });
225
+
226
+ it("Should reject a body missing dispatchKey", () => {
227
+ expect(() => schema.parse({ key: "abc", actions: [] })).toThrow();
228
+ });
229
+ });
@@ -0,0 +1,104 @@
1
+ // Copyright 2026 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { current, type Draft, isDraft, produce } from "immer";
11
+ import { z } from "zod";
12
+
13
+ /**
14
+ * Result returned by a single per-variant action handler. `inverse` is the
15
+ * action sequence the undo stack should replay to revert the handler's effect.
16
+ * `targets` is the set of document keys the handler touched, used by consumers
17
+ * to invalidate undoables targeting overlapping resources when a remote
18
+ * session emits a competing action.
19
+ */
20
+ export interface HandlerResult<A> {
21
+ inverse: A[];
22
+ targets: readonly string[];
23
+ }
24
+
25
+ /**
26
+ * NO_OP_RESULT is the zero-value HandlerResult. Return it from a handler that
27
+ * declined to mutate state (e.g. lookup miss) so it neither contributes to the
28
+ * undo stack nor invalidates undoables targeting the same resource.
29
+ */
30
+ export const NO_OP_RESULT: HandlerResult<never> = { inverse: [], targets: [] };
31
+
32
+ /**
33
+ * Result returned by a reduceAll application: the new state, the inverse
34
+ * sequence that reverts the whole batch, and the union of all targets touched.
35
+ */
36
+ export interface ReduceAllResult<S, A> {
37
+ next: S;
38
+ inverse: A[];
39
+ targets: readonly string[];
40
+ }
41
+
42
+ /**
43
+ * Returns a plain-object snapshot of an Immer draft so the result is safe to
44
+ * embed in an action stored on the undo stack. Returns non-draft inputs
45
+ * unchanged. The non-draft passthrough exists because when a reducer applies
46
+ * multiple actions in one produce(), an earlier action's wholesale
47
+ * assignment leaves the slot as a plain object, so the next action would
48
+ * crash if it called `current` unconditionally.
49
+ */
50
+ export const snapshotDraft = <T>(v: T): T => (isDraft(v) ? current(v as Draft<T>) : v);
51
+
52
+ /**
53
+ * createReduceAll lifts a per-variant `reduceOne` switch into a batched
54
+ * reducer. The returned function applies each action against an Immer draft of
55
+ * `state` in order, accumulates the inverses in reverse application order so a
56
+ * single undo replays them correctly, and unions the touched targets.
57
+ */
58
+ export const createReduceAll =
59
+ <S, A>(reduceOne: (draft: Draft<S>, action: A) => HandlerResult<A>) =>
60
+ (state: S, actions: A[]): ReduceAllResult<S, A> => {
61
+ const inverse: A[] = [];
62
+ const targetsSet = new Set<string>();
63
+ const next = produce(state, (draft) => {
64
+ for (const action of actions) {
65
+ const result = reduceOne(draft, action);
66
+ if (result.inverse.length > 0) inverse.unshift(...result.inverse);
67
+ for (const t of result.targets) targetsSet.add(t);
68
+ }
69
+ });
70
+ return { next, inverse, targets: Array.from(targetsSet) };
71
+ };
72
+
73
+ /**
74
+ * scopedZ builds the schema for envelopes broadcast on the sy_<service>_set
75
+ * cluster signals channel. The framer's JSON codec runs snakeToCamel before
76
+ * handing the value to the schema, so `dispatchKey` and `seq` are camelCase
77
+ * here even though the server emits them as snake_case. `seq` defaults to 0 so
78
+ * frames from servers that predate the field stay parseable.
79
+ */
80
+ export const scopedZ = <K extends z.ZodType, A extends z.ZodType>(
81
+ keyZ: K,
82
+ actionZ: A,
83
+ ) =>
84
+ z.object({
85
+ key: keyZ,
86
+ dispatchKey: z.string(),
87
+ seq: z.number().int().nonnegative().default(0),
88
+ actions: actionZ.array(),
89
+ });
90
+
91
+ /**
92
+ * dispatchReqZ builds the request body schema for the per-service dispatch
93
+ * endpoint. Stays camelCase; the JSON codec runs camelToSnake on encode so the
94
+ * wire ends up snake_case without per-field conversion here.
95
+ */
96
+ export const dispatchReqZ = <K extends z.ZodType, A extends z.ZodType>(
97
+ keyZ: K,
98
+ actionZ: A,
99
+ ) =>
100
+ z.object({
101
+ key: keyZ,
102
+ dispatchKey: z.string(),
103
+ actions: actionZ.array(),
104
+ });
@@ -0,0 +1,10 @@
1
+ // Copyright 2026 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * from "@/actions/actions";
@@ -0,0 +1,10 @@
1
+ // Copyright 2026 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * as actions from "@/actions/external";
@@ -0,0 +1,44 @@
1
+ // Copyright 2026 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { id } from "@synnaxlabs/x";
11
+ import { describe, expect, it } from "vitest";
12
+
13
+ import { type arc } from "@/arc";
14
+ import { createTestClient } from "@/testutil/client";
15
+
16
+ const client = createTestClient();
17
+
18
+ const newTextArc = (name: string): arc.New => ({
19
+ name,
20
+ mode: "text",
21
+ graph: {
22
+ nodes: [],
23
+ edges: [],
24
+ viewport: { position: { x: 0, y: 0 }, zoom: 1 },
25
+ functions: [],
26
+ },
27
+ text: { raw: "" },
28
+ });
29
+
30
+ describe("arc", () => {
31
+ describe("retrieve", () => {
32
+ it("should retrieve arcs by search term", async () => {
33
+ const prefix = `searchable-arc-${id.create()}`;
34
+ const names = [`${prefix}-1`, `${prefix}-2`];
35
+ await client.arcs.create(names.map((name) => newTextArc(name)));
36
+ await expect
37
+ .poll(async () => {
38
+ const results = await client.arcs.retrieve({ searchTerm: prefix });
39
+ return results.map((a) => a.name).sort();
40
+ })
41
+ .toEqual(names);
42
+ });
43
+ });
44
+ });
package/src/arc/client.ts CHANGED
@@ -8,7 +8,6 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import {
11
- sendRequired,
12
11
  type Stream,
13
12
  type StreamClient,
14
13
  type UnaryClient,
@@ -77,8 +76,7 @@ export class Client {
77
76
  async create(arcs: New[]): Promise<Arc[]>;
78
77
  async create(arcs: New | New[]): Promise<Arc | Arc[]> {
79
78
  const isMany = Array.isArray(arcs);
80
- const res = await sendRequired(
81
- this.client,
79
+ const res = await this.client.send(
82
80
  "/arc/create",
83
81
  { arcs: array.toArray(arcs) },
84
82
  createReqZ,
@@ -91,8 +89,7 @@ export class Client {
91
89
  async retrieve(args: RetrieveArgs): Promise<Arc[]>;
92
90
  async retrieve(args: RetrieveArgs): Promise<Arc | Arc[]> {
93
91
  const isSingle = "key" in args || "name" in args;
94
- const res = await sendRequired(
95
- this.client,
92
+ const res = await this.client.send(
96
93
  "/arc/retrieve",
97
94
  args,
98
95
  retrieveArgsZ,
@@ -103,8 +100,7 @@ export class Client {
103
100
  }
104
101
 
105
102
  async delete(keys: Key | Key[]): Promise<void> {
106
- await sendRequired(
107
- this.client,
103
+ await this.client.send(
108
104
  "/arc/delete",
109
105
  { keys: array.toArray(keys) },
110
106
  deleteReqZ,
@@ -9,6 +9,7 @@
9
9
 
10
10
  // Code generated by Oracle. DO NOT EDIT.
11
11
 
12
+ import { record } from "@synnaxlabs/x";
12
13
  import { z } from "zod";
13
14
 
14
15
  /**
@@ -22,6 +23,6 @@ export const outputZ = z.object({
22
23
  * outputMemoryBases contains memory base addresses for multi-output functions, mapping
23
24
  * function keys to their base addresses.
24
25
  */
25
- outputMemoryBases: z.record(z.string(), z.uint32()),
26
+ outputMemoryBases: record.nullishToEmpty(z.string(), z.uint32()),
26
27
  });
27
28
  export interface Output extends z.infer<typeof outputZ> {}
@@ -9,7 +9,7 @@
9
9
 
10
10
  // Code generated by Oracle. DO NOT EDIT.
11
11
 
12
- import { array, zod } from "@synnaxlabs/x";
12
+ import { array, record, zod } from "@synnaxlabs/x";
13
13
  import { z } from "zod";
14
14
 
15
15
  import { types } from "@/arc/types";
@@ -21,6 +21,20 @@ export enum EdgeKind {
21
21
  }
22
22
  export const edgeKindZ = z.enum(EdgeKind);
23
23
 
24
+ export enum ScopeMode {
25
+ unspecified = 0,
26
+ parallel = 1,
27
+ sequential = 2,
28
+ }
29
+ export const scopeModeZ = z.enum(ScopeMode);
30
+
31
+ export enum Liveness {
32
+ unspecified = 0,
33
+ always = 1,
34
+ gated = 2,
35
+ }
36
+ export const livenessZ = z.enum(Liveness);
37
+
24
38
  /** Handle is a reference to a specific parameter on a specific node in the dataflow graph. */
25
39
  export const handleZ = z.object({
26
40
  /** node is the node identifier. */
@@ -47,13 +61,13 @@ export const nodeZ = z.object({
47
61
  /** type is the function type being instantiated. */
48
62
  type: z.string(),
49
63
  /** config contains configuration parameter values. */
50
- config: types.paramsZ.optional(),
64
+ config: types.paramsZ,
51
65
  /** inputs contains input parameter type signatures. */
52
- inputs: types.paramsZ.optional(),
66
+ inputs: types.paramsZ,
53
67
  /** outputs contains output parameter type signatures. */
54
- outputs: types.paramsZ.optional(),
68
+ outputs: types.paramsZ,
55
69
  /** channels contains channel read/write mappings. */
56
- channels: types.channelsZ.optional(),
70
+ channels: types.channelsZ,
57
71
  });
58
72
  export interface Node extends z.infer<typeof nodeZ> {}
59
73
 
@@ -62,13 +76,10 @@ export const authoritiesZ = z.object({
62
76
  /** default is the default authority for all write channels not explicitly listed. */
63
77
  default: zod.uint8.optional(),
64
78
  /** channels maps channel keys to their specific authority values. */
65
- channels: z.record(z.uint32(), zod.uint8).optional(),
79
+ channels: record.nullishToEmpty(z.uint32(), zod.uint8),
66
80
  });
67
81
  export interface Authorities extends z.infer<typeof authoritiesZ> {}
68
82
 
69
- export const stratumZ = array.nullishToEmpty(z.string());
70
- export type Stratum = z.infer<typeof stratumZ>;
71
-
72
83
  /** Edge is a dataflow connection between node parameters in the Arc graph. */
73
84
  export const edgeZ = z.object({
74
85
  /** source is the source node parameter producing data. */
@@ -76,10 +87,22 @@ export const edgeZ = z.object({
76
87
  /** target is the target node parameter consuming data. */
77
88
  target: handleZ,
78
89
  /** kind defines execution semantics for this connection. */
79
- kind: edgeKindZ.optional(),
90
+ kind: edgeKindZ,
80
91
  });
81
92
  export interface Edge extends z.infer<typeof edgeZ> {}
82
93
 
94
+ /** Transition is a declarative state-transition rule on a sequential Scope. */
95
+ export const transitionZ = z.object({
96
+ /** on is the dataflow handle whose truthy value fires this transition. */
97
+ on: handleZ,
98
+ /**
99
+ * targetKey is the sibling step key to activate. Null when the transition
100
+ * exits the scope, yielding to the parent.
101
+ */
102
+ targetKey: z.string().optional(),
103
+ });
104
+ export interface Transition extends z.infer<typeof transitionZ> {}
105
+
83
106
  /**
84
107
  * Function is a function template definition with typed parameters, serving as a
85
108
  * blueprint for node instantiation.
@@ -90,22 +113,19 @@ export const functionZ = z.object({
90
113
  /** body is raw source code for user-defined functions. */
91
114
  body: bodyZ.optional(),
92
115
  /** config contains configuration parameter definitions. */
93
- config: types.paramsZ.optional(),
116
+ config: types.paramsZ,
94
117
  /** inputs contains input parameter definitions. */
95
- inputs: types.paramsZ.optional(),
118
+ inputs: types.paramsZ,
96
119
  /** outputs contains output parameter definitions. */
97
- outputs: types.paramsZ.optional(),
120
+ outputs: types.paramsZ,
98
121
  /** channels contains channel read/write declarations. */
99
- channels: types.channelsZ.optional(),
122
+ channels: types.channelsZ,
100
123
  });
101
124
  export interface Function extends z.infer<typeof functionZ> {}
102
125
 
103
126
  export const nodesZ = array.nullishToEmpty(nodeZ);
104
127
  export type Nodes = z.infer<typeof nodesZ>;
105
128
 
106
- export const strataZ = array.nullishToEmpty(stratumZ);
107
- export type Strata = z.infer<typeof strataZ>;
108
-
109
129
  export const edgesZ = array.nullishToEmpty(edgeZ);
110
130
  export type Edges = z.infer<typeof edgesZ>;
111
131
 
@@ -113,36 +133,65 @@ export const functionsZ = array.nullishToEmpty(functionZ);
113
133
  export type Functions = z.infer<typeof functionsZ>;
114
134
 
115
135
  /**
116
- * Stage is a stage in a sequence state machine, containing active nodes and their
117
- * execution stratification.
136
+ * Member is a tagged union representing a single child of a Scope. Exactly one
137
+ * of nodeKey or scope is set. The member's lookup key (used as the
138
+ * target of `=> name` transitions) is derived from the set variant via
139
+ * Member.key().
118
140
  */
119
- export const stageZ = z.object({
120
- /** key is the stage identifier. */
121
- key: z.string(),
122
- /** nodes contains node keys active in this stage. */
123
- nodes: array.nullishToEmpty(z.string()),
124
- /** strata contains execution stratification for nodes in this stage. */
125
- strata: strataZ,
141
+ export interface Member {
142
+ nodeKey?: string;
143
+ scope?: Scope;
144
+ }
145
+ export const memberZ: z.ZodType<Member> = z.object({
146
+ /**
147
+ * nodeKey is the key of the referenced node in IR.nodes. Null when this
148
+ * member is a nested scope.
149
+ */
150
+ nodeKey: z.string().optional(),
151
+ /** scope is set when this member is a nested scope. */
152
+ get scope() {
153
+ return scopeZ.optional();
154
+ },
126
155
  });
127
- export interface Stage extends z.infer<typeof stageZ> {}
128
156
 
129
157
  /**
130
- * Sequence is a state machine defining ordered stages of execution, where entry point
131
- * is always the first stage.
158
+ * Scope is the unified Layer 2 execution primitive. Parameterized by mode
159
+ * (parallel or sequential) and liveness (always-live or gated). Parallel
160
+ * scopes organize members into strata; sequential scopes run one step
161
+ * at a time and advance via transitions.
132
162
  */
133
- export const sequenceZ = z.object({
134
- /** key is the sequence identifier. */
163
+ export interface Scope {
164
+ key: string;
165
+ mode: ScopeMode;
166
+ liveness: Liveness;
167
+ activation?: Handle;
168
+ strata: Members[];
169
+ steps: Members;
170
+ transitions: Transition[];
171
+ }
172
+ export const scopeZ: z.ZodType<Scope> = z.object({
173
+ /** key is the scope identifier. */
135
174
  key: z.string(),
136
- /** stages contains ordered stages in this sequence. */
137
- stages: array.nullishToEmpty(stageZ),
175
+ /** mode defines whether this scope runs steps in parallel or sequentially. */
176
+ mode: scopeModeZ,
177
+ /** liveness defines whether this scope is continuously active or must be activated. */
178
+ liveness: livenessZ,
179
+ /** activation is the handle whose truthy value activates a gated scope. Unset for always-live scopes. */
180
+ activation: handleZ.optional(),
181
+ /**
182
+ * strata contains stratified execution layers for parallel scopes. Empty
183
+ * for sequential scopes. Stratum N depends only on strata 0 to N-1.
184
+ */
185
+ get strata() {
186
+ return array.nullishToEmpty(membersZ);
187
+ },
188
+ /** steps contains ordered steps for sequential scopes. Empty for parallel scopes. */
189
+ get steps() {
190
+ return membersZ;
191
+ },
192
+ /** transitions contains state-transition rules for sequential scopes. Empty for parallel scopes. */
193
+ transitions: array.nullishToEmpty(transitionZ),
138
194
  });
139
- export interface Sequence extends z.infer<typeof sequenceZ> {}
140
-
141
- export const stagesZ = array.nullishToEmpty(stageZ);
142
- export type Stages = z.infer<typeof stagesZ>;
143
-
144
- export const sequencesZ = array.nullishToEmpty(sequenceZ);
145
- export type Sequences = z.infer<typeof sequencesZ>;
146
195
 
147
196
  /**
148
197
  * IR is the intermediate representation of an Arc program as a dataflow graph
@@ -151,16 +200,21 @@ export type Sequences = z.infer<typeof sequencesZ>;
151
200
  */
152
201
  export const irZ = z.object({
153
202
  /** functions contains function template definitions. */
154
- functions: functionsZ.optional(),
203
+ functions: functionsZ,
155
204
  /** nodes contains node instantiations. */
156
- nodes: nodesZ.optional(),
205
+ nodes: nodesZ,
157
206
  /** edges contains dataflow connections. */
158
- edges: edgesZ.optional(),
159
- /** strata contains execution stratification layers. */
160
- strata: strataZ.optional(),
161
- /** sequences contains state machine definitions. */
162
- sequences: sequencesZ.optional(),
207
+ edges: edgesZ,
163
208
  /** authorities contains the static authority declarations for this program. */
164
- authorities: authoritiesZ.optional(),
209
+ authorities: authoritiesZ,
210
+ /**
211
+ * root is the top-level execution context. The root is always a
212
+ * parallel, always-live Scope whose strata mix module-scope
213
+ * reactive flow with top-level gated scopes.
214
+ */
215
+ root: scopeZ,
165
216
  });
166
217
  export interface IR extends z.infer<typeof irZ> {}
218
+
219
+ export const membersZ = array.nullishToEmpty(memberZ);
220
+ export type Members = z.infer<typeof membersZ>;