@synnaxlabs/client 0.55.0 → 0.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -13
- package/dist/client.cjs +60 -36
- package/dist/client.js +6435 -4786
- package/dist/src/access/policy/client.d.ts +70 -80
- package/dist/src/access/policy/client.d.ts.map +1 -1
- package/dist/src/access/policy/types.gen.d.ts +18 -20
- package/dist/src/access/policy/types.gen.d.ts.map +1 -1
- package/dist/src/access/role/client.d.ts.map +1 -1
- package/dist/src/access/role/types.gen.d.ts +2 -2
- package/dist/src/actions/actions.d.ts +68 -0
- package/dist/src/actions/actions.d.ts.map +1 -0
- package/dist/src/actions/actions.spec.d.ts +2 -0
- package/dist/src/actions/actions.spec.d.ts.map +1 -0
- package/dist/src/actions/external.d.ts +2 -0
- package/dist/src/actions/external.d.ts.map +1 -0
- package/dist/src/actions/index.d.ts +2 -0
- package/dist/src/actions/index.d.ts.map +1 -0
- package/dist/src/arc/client.d.ts.map +1 -1
- package/dist/src/arc/compiler/types.gen.d.ts +1 -1
- package/dist/src/arc/compiler/types.gen.d.ts.map +1 -1
- package/dist/src/arc/graph/types.gen.d.ts +29 -29
- package/dist/src/arc/graph/types.gen.d.ts.map +1 -1
- package/dist/src/arc/ir/types.gen.d.ts +123 -123
- package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
- package/dist/src/arc/module/types.gen.d.ts +45 -45
- package/dist/src/arc/program/types.gen.d.ts +45 -45
- package/dist/src/arc/types/types.gen.d.ts +11 -11
- package/dist/src/arc/types/types.gen.d.ts.map +1 -1
- package/dist/src/arc/types.gen.d.ts +99 -99
- package/dist/src/auth/auth.d.ts +3 -3
- package/dist/src/auth/auth.d.ts.map +1 -1
- package/dist/src/channel/client.d.ts +2 -2
- package/dist/src/channel/client.d.ts.map +1 -1
- package/dist/src/channel/retriever.d.ts +5 -8
- package/dist/src/channel/retriever.d.ts.map +1 -1
- package/dist/src/channel/types.gen.d.ts +3 -3
- package/dist/src/channel/writer.d.ts.map +1 -1
- package/dist/src/connection/checker.d.ts +1 -1
- package/dist/src/connection/checker.d.ts.map +1 -1
- package/dist/src/device/client.d.ts.map +1 -1
- package/dist/src/device/types.gen.d.ts +6 -8
- package/dist/src/device/types.gen.d.ts.map +1 -1
- package/dist/src/errors.d.ts +2 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/framer/adapter.d.ts.map +1 -1
- package/dist/src/framer/client.d.ts +2 -2
- package/dist/src/framer/codec.d.ts +9 -1
- package/dist/src/framer/codec.d.ts.map +1 -1
- package/dist/src/framer/deleter.d.ts.map +1 -1
- package/dist/src/framer/frame.d.ts +1 -1
- package/dist/src/framer/iterator.d.ts +84 -3
- package/dist/src/framer/iterator.d.ts.map +1 -1
- package/dist/src/framer/streamProxy.d.ts.map +1 -1
- package/dist/src/framer/streamer.d.ts +1 -3
- package/dist/src/framer/streamer.d.ts.map +1 -1
- package/dist/src/framer/types.gen.d.ts +18 -0
- package/dist/src/framer/types.gen.d.ts.map +1 -1
- package/dist/src/framer/writer.d.ts +8 -8
- package/dist/src/framer/writer.d.ts.map +1 -1
- package/dist/src/group/client.d.ts +1 -2
- package/dist/src/group/client.d.ts.map +1 -1
- package/dist/src/group/types.gen.d.ts +2 -2
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/label/client.d.ts +5 -8
- package/dist/src/label/client.d.ts.map +1 -1
- package/dist/src/lineplot/client.d.ts.map +1 -1
- package/dist/src/lineplot/types.gen.d.ts +2 -2
- package/dist/src/log/client.d.ts.map +1 -1
- package/dist/src/log/types.gen.d.ts +2 -2
- package/dist/src/ontology/client.d.ts +1 -3
- package/dist/src/ontology/client.d.ts.map +1 -1
- package/dist/src/ontology/payload.d.ts +12 -16
- package/dist/src/ontology/payload.d.ts.map +1 -1
- package/dist/src/ontology/types.gen.d.ts +1 -2
- package/dist/src/ontology/types.gen.d.ts.map +1 -1
- package/dist/src/ontology/writer.d.ts +5 -10
- package/dist/src/ontology/writer.d.ts.map +1 -1
- package/dist/src/rack/client.d.ts.map +1 -1
- package/dist/src/rack/types.gen.d.ts +3 -3
- package/dist/src/ranger/alias/client.d.ts.map +1 -1
- package/dist/src/ranger/client.d.ts.map +1 -1
- package/dist/src/ranger/kv/client.d.ts.map +1 -1
- package/dist/src/ranger/types.gen.d.ts +6 -6
- package/dist/src/ranger/types.gen.d.ts.map +1 -1
- package/dist/src/ranger/writer.d.ts +2 -3
- package/dist/src/ranger/writer.d.ts.map +1 -1
- package/dist/src/schematic/actions.d.ts +147 -0
- package/dist/src/schematic/actions.d.ts.map +1 -0
- package/dist/src/schematic/actions.gen.d.ts +484 -0
- package/dist/src/schematic/actions.gen.d.ts.map +1 -0
- package/dist/src/schematic/actions.spec.d.ts +2 -0
- package/dist/src/schematic/actions.spec.d.ts.map +1 -0
- package/dist/src/schematic/client.d.ts +53 -2
- package/dist/src/schematic/client.d.ts.map +1 -1
- package/dist/src/schematic/external.d.ts +2 -0
- package/dist/src/schematic/external.d.ts.map +1 -1
- package/dist/src/schematic/symbol/client.d.ts.map +1 -1
- package/dist/src/schematic/symbol/types.gen.d.ts +48 -58
- package/dist/src/schematic/symbol/types.gen.d.ts.map +1 -1
- package/dist/src/schematic/types.gen.d.ts +131 -5
- package/dist/src/schematic/types.gen.d.ts.map +1 -1
- package/dist/src/status/client.d.ts.map +1 -1
- package/dist/src/status/payload.d.ts +3 -3
- package/dist/src/table/actions.d.ts +156 -0
- package/dist/src/table/actions.d.ts.map +1 -0
- package/dist/src/table/actions.gen.d.ts +587 -0
- package/dist/src/table/actions.gen.d.ts.map +1 -0
- package/dist/src/table/client.d.ts +28 -2
- package/dist/src/table/client.d.ts.map +1 -1
- package/dist/src/table/external.d.ts +2 -0
- package/dist/src/table/external.d.ts.map +1 -1
- package/dist/src/table/types.gen.d.ts +71 -4
- package/dist/src/table/types.gen.d.ts.map +1 -1
- package/dist/src/task/client.d.ts.map +1 -1
- package/dist/src/task/types.gen.d.ts +7 -7
- package/dist/src/task/types.gen.d.ts.map +1 -1
- package/dist/src/user/client.d.ts +2 -2
- package/dist/src/user/client.d.ts.map +1 -1
- package/dist/src/user/types.gen.d.ts +2 -2
- package/dist/src/view/client.d.ts.map +1 -1
- package/dist/src/view/types.gen.d.ts +2 -2
- package/dist/src/workspace/client.d.ts.map +1 -1
- package/dist/src/workspace/types.gen.d.ts +3 -3
- package/dist/src/workspace/types.gen.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/access/policy/client.ts +4 -7
- package/src/access/role/client.ts +6 -26
- package/src/actions/actions.spec.ts +229 -0
- package/src/actions/actions.ts +104 -0
- package/src/actions/external.ts +10 -0
- package/src/actions/index.ts +10 -0
- package/src/arc/client.ts +3 -7
- package/src/arc/compiler/types.gen.ts +2 -1
- package/src/arc/ir/types.gen.ts +2 -2
- package/src/arc/lsp.spec.ts +3 -7
- package/src/arc/types/types.gen.ts +3 -3
- package/src/auth/auth.spec.ts +12 -13
- package/src/auth/auth.ts +36 -34
- package/src/channel/batchRetriever.spec.ts +13 -4
- package/src/channel/client.ts +8 -6
- package/src/channel/retriever.ts +7 -16
- package/src/channel/writer.ts +4 -20
- package/src/connection/checker.ts +6 -6
- package/src/connection/connection.spec.ts +5 -8
- package/src/device/client.ts +5 -8
- package/src/device/types.gen.ts +4 -4
- package/src/errors.ts +9 -9
- package/src/framer/adapter.ts +2 -4
- package/src/framer/client.ts +1 -1
- package/src/framer/codec.spec.ts +53 -3
- package/src/framer/codec.ts +58 -25
- package/src/framer/deleter.ts +2 -8
- package/src/framer/iterator.ts +43 -40
- package/src/framer/streamProxy.ts +13 -13
- package/src/framer/streamer.spec.ts +12 -3
- package/src/framer/streamer.ts +7 -12
- package/src/framer/types.gen.ts +20 -0
- package/src/framer/writer.spec.ts +77 -0
- package/src/framer/writer.ts +51 -28
- package/src/group/client.ts +4 -7
- package/src/index.ts +3 -2
- package/src/label/client.ts +6 -16
- package/src/lineplot/client.ts +6 -21
- package/src/log/client.ts +6 -21
- package/src/ontology/client.ts +3 -4
- package/src/ontology/types.gen.ts +0 -1
- package/src/ontology/writer.ts +4 -7
- package/src/rack/client.ts +4 -7
- package/src/ranger/alias/client.ts +6 -11
- package/src/ranger/client.ts +3 -4
- package/src/ranger/kv/client.ts +5 -8
- package/src/ranger/writer.ts +4 -17
- package/src/schematic/access.spec.ts +6 -6
- package/src/schematic/actions.gen.ts +200 -0
- package/src/schematic/actions.spec.ts +699 -0
- package/src/schematic/actions.ts +168 -0
- package/src/schematic/client.ts +34 -30
- package/src/schematic/external.ts +2 -0
- package/src/schematic/schematic.spec.ts +233 -69
- package/src/schematic/symbol/client.ts +6 -11
- package/src/schematic/symbol/types.gen.ts +1 -10
- package/src/schematic/types.gen.ts +55 -6
- package/src/status/client.ts +4 -10
- package/src/table/access.spec.ts +0 -6
- package/src/table/actions.gen.ts +243 -0
- package/src/table/actions.ts +255 -0
- package/src/table/client.ts +21 -25
- package/src/table/external.ts +2 -0
- package/src/table/table.spec.ts +588 -43
- package/src/table/types.gen.ts +58 -5
- package/src/task/client.ts +7 -11
- package/src/task/types.gen.ts +8 -6
- package/src/user/client.ts +6 -11
- package/src/view/client.ts +4 -7
- package/src/workspace/client.ts +6 -16
- package/src/workspace/types.gen.ts +2 -1
|
@@ -0,0 +1,168 @@
|
|
|
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 { color, type record } from "@synnaxlabs/x";
|
|
11
|
+
import { current, type Draft, isDraft } from "immer";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
type Action,
|
|
15
|
+
addEdge,
|
|
16
|
+
createReduceAll,
|
|
17
|
+
type HandlerResult,
|
|
18
|
+
type Handlers,
|
|
19
|
+
removeEdge,
|
|
20
|
+
removeNode,
|
|
21
|
+
rename,
|
|
22
|
+
setConfig,
|
|
23
|
+
setNode,
|
|
24
|
+
setNodePosition,
|
|
25
|
+
} from "@/schematic/actions.gen";
|
|
26
|
+
|
|
27
|
+
const NO_OP: HandlerResult = { inverse: [], targets: [] };
|
|
28
|
+
|
|
29
|
+
// Snapshots a value out of an Immer draft so the result is safe to embed in an
|
|
30
|
+
// action stored on the undo stack. When reduceAll applies multiple actions in
|
|
31
|
+
// one produce(), an earlier action's wholesale assignment (e.g. state.configs[k]
|
|
32
|
+
// = {...existing, ...payload.config}) leaves the slot as a plain object — the
|
|
33
|
+
// next action would crash if it called current() unconditionally.
|
|
34
|
+
const snapshot = <T>(v: T): T => (isDraft(v) ? current(v as Draft<T>) : v);
|
|
35
|
+
|
|
36
|
+
const handlers: Handlers = {
|
|
37
|
+
rename: (state, payload) => {
|
|
38
|
+
const oldName = state.name;
|
|
39
|
+
state.name = payload.name;
|
|
40
|
+
return {
|
|
41
|
+
inverse: [rename({ name: oldName })],
|
|
42
|
+
targets: [state.key],
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
setNodePosition: (state, payload) => {
|
|
46
|
+
const node = state.nodes.find((n) => n.key === payload.key);
|
|
47
|
+
if (node == null) return NO_OP;
|
|
48
|
+
const oldPosition = { x: node.position.x, y: node.position.y };
|
|
49
|
+
node.position = payload.position;
|
|
50
|
+
return {
|
|
51
|
+
inverse: [setNodePosition({ key: payload.key, position: oldPosition })],
|
|
52
|
+
targets: [payload.key],
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// set_node_measured carries layout-derived dimensions, not user intent.
|
|
57
|
+
// Both the inverse and the target list are empty so it neither contributes
|
|
58
|
+
// to the undo stack nor invalidates undoables targeting the same node when
|
|
59
|
+
// a remote session emits it.
|
|
60
|
+
setNodeMeasured: (state, payload) => {
|
|
61
|
+
const node = state.nodes.find((n) => n.key === payload.key);
|
|
62
|
+
if (node == null) return NO_OP;
|
|
63
|
+
node.measured = payload.measured;
|
|
64
|
+
return { inverse: [], targets: [] };
|
|
65
|
+
},
|
|
66
|
+
setNode: (state, payload) => {
|
|
67
|
+
const idx = state.nodes.findIndex((n) => n.key === payload.node.key);
|
|
68
|
+
if (idx === -1) {
|
|
69
|
+
state.nodes.push(payload.node);
|
|
70
|
+
if (payload.config != null) state.configs[payload.node.key] = payload.config;
|
|
71
|
+
return {
|
|
72
|
+
inverse: [removeNode({ key: payload.node.key })],
|
|
73
|
+
targets: [payload.node.key],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const oldNode = snapshot(state.nodes[idx]);
|
|
77
|
+
const oldConfigRaw = state.configs[payload.node.key];
|
|
78
|
+
const oldConfig = oldConfigRaw != null ? snapshot(oldConfigRaw) : undefined;
|
|
79
|
+
state.nodes[idx] = payload.node;
|
|
80
|
+
if (payload.config != null) state.configs[payload.node.key] = payload.config;
|
|
81
|
+
return {
|
|
82
|
+
inverse: [
|
|
83
|
+
setNode(
|
|
84
|
+
oldConfig != null
|
|
85
|
+
? { node: oldNode, config: oldConfig }
|
|
86
|
+
: { node: oldNode, config: undefined },
|
|
87
|
+
),
|
|
88
|
+
],
|
|
89
|
+
targets: [payload.node.key],
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
removeNode: (state, payload) => {
|
|
93
|
+
const idx = state.nodes.findIndex((n) => n.key === payload.key);
|
|
94
|
+
if (idx === -1) return NO_OP;
|
|
95
|
+
const oldNode = snapshot(state.nodes[idx]);
|
|
96
|
+
const oldConfigRaw = state.configs[payload.key];
|
|
97
|
+
const oldConfig = oldConfigRaw != null ? snapshot(oldConfigRaw) : undefined;
|
|
98
|
+
state.nodes.splice(idx, 1);
|
|
99
|
+
delete state.configs[payload.key];
|
|
100
|
+
return {
|
|
101
|
+
inverse: [
|
|
102
|
+
setNode(
|
|
103
|
+
oldConfig != null
|
|
104
|
+
? { node: oldNode, config: oldConfig }
|
|
105
|
+
: { node: oldNode, config: undefined },
|
|
106
|
+
),
|
|
107
|
+
],
|
|
108
|
+
targets: [payload.key],
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
addEdge: (state, payload) => {
|
|
112
|
+
if (state.edges.some((e) => e.key === payload.edge.key)) return NO_OP;
|
|
113
|
+
state.edges.push(payload.edge);
|
|
114
|
+
return {
|
|
115
|
+
inverse: [removeEdge({ key: payload.edge.key })],
|
|
116
|
+
targets: [payload.edge.key],
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
removeEdge: (state, payload) => {
|
|
121
|
+
const idx = state.edges.findIndex((e) => e.key === payload.key);
|
|
122
|
+
if (idx === -1) return NO_OP;
|
|
123
|
+
const oldEdge = snapshot(state.edges[idx]);
|
|
124
|
+
state.edges.splice(idx, 1);
|
|
125
|
+
return {
|
|
126
|
+
inverse: [addEdge({ edge: oldEdge })],
|
|
127
|
+
targets: [payload.key],
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
// The inverse of SetConfig is imperfect for keys the action newly
|
|
131
|
+
// introduces: SetConfig only merges, so it cannot remove keys that did not
|
|
132
|
+
// previously exist. The inverse here restores values for keys that DID
|
|
133
|
+
// exist before the merge; keys added by the action remain on undo as
|
|
134
|
+
// phantom fields. A future ReplaceConfig action can close the gap by
|
|
135
|
+
// enabling wholesale replacement.
|
|
136
|
+
setConfig: (state, payload) => {
|
|
137
|
+
const existingRaw = state.configs[payload.key];
|
|
138
|
+
if (existingRaw != null) {
|
|
139
|
+
const existing = snapshot(existingRaw);
|
|
140
|
+
const restoreFields: record.Unknown = {};
|
|
141
|
+
for (const k of Object.keys(payload.config))
|
|
142
|
+
if (existing[k] !== undefined) restoreFields[k] = existing[k];
|
|
143
|
+
state.configs[payload.key] = { ...existing, ...payload.config };
|
|
144
|
+
if (Object.keys(restoreFields).length === 0)
|
|
145
|
+
return { inverse: [], targets: [payload.key] };
|
|
146
|
+
return {
|
|
147
|
+
inverse: [setConfig({ key: payload.key, config: restoreFields })],
|
|
148
|
+
targets: [payload.key],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
let cfg = payload.config;
|
|
152
|
+
const edge = state.edges.find((e) => e.key === payload.key);
|
|
153
|
+
if (edge != null) {
|
|
154
|
+
const srcCfg = state.configs[edge.source.node] as
|
|
155
|
+
| { color?: color.Crude }
|
|
156
|
+
| undefined;
|
|
157
|
+
if (srcCfg?.color != null && !color.isZero(srcCfg.color))
|
|
158
|
+
cfg = { ...cfg, color: srcCfg.color };
|
|
159
|
+
}
|
|
160
|
+
state.configs[payload.key] = cfg;
|
|
161
|
+
return { inverse: [], targets: [payload.key] };
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const reduceAll = createReduceAll(handlers);
|
|
166
|
+
|
|
167
|
+
export const isUndoable = (action: Action): boolean =>
|
|
168
|
+
action.type !== "set_node_measured";
|
package/src/schematic/client.ts
CHANGED
|
@@ -7,10 +7,15 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import { array
|
|
10
|
+
import { type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
|
+
import { array } from "@synnaxlabs/x";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
14
|
+
import {
|
|
15
|
+
type Action,
|
|
16
|
+
dispatchReqZ,
|
|
17
|
+
rename as renameAction,
|
|
18
|
+
} from "@/schematic/actions.gen";
|
|
14
19
|
import { symbol } from "@/schematic/symbol";
|
|
15
20
|
import {
|
|
16
21
|
type Key,
|
|
@@ -23,12 +28,11 @@ import {
|
|
|
23
28
|
import { checkForMultipleOrNoResults } from "@/util/retrieve";
|
|
24
29
|
import { workspace } from "@/workspace";
|
|
25
30
|
|
|
26
|
-
const
|
|
31
|
+
export const SET_CHANNEL_NAME = "sy_schematic_set";
|
|
27
32
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
33
|
+
const setDataBodyZ = schematicZ.omit({ key: true, name: true, snapshot: true });
|
|
34
|
+
export type SetDataBody = z.input<typeof setDataBodyZ>;
|
|
35
|
+
const setDataReqZ = z.object({ key: keyZ, data: setDataBodyZ });
|
|
32
36
|
const deleteReqZ = z.object({ keys: keyZ.array() });
|
|
33
37
|
|
|
34
38
|
const copyReqZ = z.object({
|
|
@@ -75,8 +79,7 @@ export class Client {
|
|
|
75
79
|
schematics: New | New[],
|
|
76
80
|
): Promise<Schematic | Schematic[]> {
|
|
77
81
|
const isMany = Array.isArray(schematics);
|
|
78
|
-
const res = await
|
|
79
|
-
this.client,
|
|
82
|
+
const res = await this.client.send(
|
|
80
83
|
"/schematic/create",
|
|
81
84
|
{ workspace, schematics: array.toArray(schematics) },
|
|
82
85
|
createReqZ,
|
|
@@ -86,18 +89,11 @@ export class Client {
|
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
async rename(key: Key, name: string): Promise<void> {
|
|
89
|
-
await
|
|
90
|
-
this.client,
|
|
91
|
-
"/schematic/rename",
|
|
92
|
-
{ key, name },
|
|
93
|
-
renameReqZ,
|
|
94
|
-
emptyResZ,
|
|
95
|
-
);
|
|
92
|
+
await this.dispatch(key, "", [renameAction({ name })]);
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
async setData(key: Key, data:
|
|
99
|
-
await
|
|
100
|
-
this.client,
|
|
95
|
+
async setData(key: Key, data: SetDataBody): Promise<void> {
|
|
96
|
+
await this.client.send(
|
|
101
97
|
"/schematic/set-data",
|
|
102
98
|
{ key, data },
|
|
103
99
|
setDataReqZ,
|
|
@@ -105,14 +101,22 @@ export class Client {
|
|
|
105
101
|
);
|
|
106
102
|
}
|
|
107
103
|
|
|
104
|
+
async dispatch(key: Key, dispatchKey: string, actions: Action[]): Promise<void> {
|
|
105
|
+
await this.client.send(
|
|
106
|
+
"/schematic/dispatch",
|
|
107
|
+
{ key, dispatchKey, actions },
|
|
108
|
+
dispatchReqZ,
|
|
109
|
+
emptyResZ,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
108
113
|
async retrieve(args: RetrieveSingleParams): Promise<Schematic>;
|
|
109
114
|
async retrieve(args: RetrieveMultipleParams): Promise<Schematic[]>;
|
|
110
115
|
async retrieve(
|
|
111
116
|
args: RetrieveSingleParams | RetrieveMultipleParams,
|
|
112
117
|
): Promise<Schematic | Schematic[]> {
|
|
113
118
|
const isSingle = singleRetrieveArgsZ.safeParse(args).success;
|
|
114
|
-
const res = await
|
|
115
|
-
this.client,
|
|
119
|
+
const res = await this.client.send(
|
|
116
120
|
"/schematic/retrieve",
|
|
117
121
|
args,
|
|
118
122
|
retrieveArgsZ,
|
|
@@ -123,8 +127,7 @@ export class Client {
|
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
async delete(keys: Key | Key[]): Promise<void> {
|
|
126
|
-
await
|
|
127
|
-
this.client,
|
|
130
|
+
await this.client.send(
|
|
128
131
|
"/schematic/delete",
|
|
129
132
|
{ keys: array.toArray(keys) },
|
|
130
133
|
deleteReqZ,
|
|
@@ -133,13 +136,14 @@ export class Client {
|
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
async copy(args: CopyArgs): Promise<Schematic> {
|
|
136
|
-
const res = await
|
|
137
|
-
this.client,
|
|
138
|
-
"/schematic/copy",
|
|
139
|
-
args,
|
|
140
|
-
copyReqZ,
|
|
141
|
-
copyResZ,
|
|
142
|
-
);
|
|
139
|
+
const res = await this.client.send("/schematic/copy", args, copyReqZ, copyResZ);
|
|
143
140
|
return res.schematic;
|
|
144
141
|
}
|
|
145
142
|
}
|
|
143
|
+
|
|
144
|
+
export const ZERO_NEW: New = {
|
|
145
|
+
name: "",
|
|
146
|
+
nodes: [],
|
|
147
|
+
edges: [],
|
|
148
|
+
configs: {},
|
|
149
|
+
};
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
+
export * from "@/schematic/actions";
|
|
11
|
+
export * from "@/schematic/actions.gen";
|
|
10
12
|
export * from "@/schematic/client";
|
|
11
13
|
export * from "@/schematic/symbol";
|
|
12
14
|
export * from "@/schematic/types.gen";
|
|
@@ -11,8 +11,18 @@ import { uuid } from "@synnaxlabs/x";
|
|
|
11
11
|
import { describe, expect, it, test } from "vitest";
|
|
12
12
|
|
|
13
13
|
import { NotFoundError, ValidationError } from "@/errors";
|
|
14
|
+
import { schematic } from "@/schematic";
|
|
14
15
|
import { createTestClient } from "@/testutil/client";
|
|
15
16
|
|
|
17
|
+
const newWorkspaceSchematic = async (client: ReturnType<typeof createTestClient>) => {
|
|
18
|
+
const ws = await client.workspaces.create({ name: "dispatch", layout: {} });
|
|
19
|
+
const schem = await client.schematics.create(ws.key, {
|
|
20
|
+
...schematic.ZERO_NEW,
|
|
21
|
+
name: "dispatch",
|
|
22
|
+
});
|
|
23
|
+
return { ws, schem };
|
|
24
|
+
};
|
|
25
|
+
|
|
16
26
|
const client = createTestClient();
|
|
17
27
|
|
|
18
28
|
describe("Schematic", () => {
|
|
@@ -22,17 +32,14 @@ describe("Schematic", () => {
|
|
|
22
32
|
name: "Schematic",
|
|
23
33
|
layout: { one: 1 },
|
|
24
34
|
});
|
|
25
|
-
const
|
|
35
|
+
const schem = await client.schematics.create(ws.key, {
|
|
36
|
+
...schematic.ZERO_NEW,
|
|
26
37
|
name: "Schematic",
|
|
27
|
-
data: { One: 1 },
|
|
28
|
-
});
|
|
29
|
-
expect(schematic.name).toEqual("Schematic");
|
|
30
|
-
expect(schematic.key).not.toEqual(uuid.ZERO);
|
|
31
|
-
expect(schematic.data.One).toEqual(1);
|
|
32
|
-
const retrieved = await client.schematics.retrieve({
|
|
33
|
-
key: schematic.key,
|
|
34
38
|
});
|
|
35
|
-
expect(
|
|
39
|
+
expect(schem.name).toEqual("Schematic");
|
|
40
|
+
expect(schem.key).not.toEqual(uuid.ZERO);
|
|
41
|
+
const retrieved = await client.schematics.retrieve({ key: schem.key });
|
|
42
|
+
expect(retrieved.key).toEqual(schem.key);
|
|
36
43
|
});
|
|
37
44
|
});
|
|
38
45
|
|
|
@@ -42,33 +49,36 @@ describe("Schematic", () => {
|
|
|
42
49
|
name: "Schematic",
|
|
43
50
|
layout: { one: 1 },
|
|
44
51
|
});
|
|
45
|
-
const
|
|
52
|
+
const schem = await client.schematics.create(ws.key, {
|
|
53
|
+
...schematic.ZERO_NEW,
|
|
46
54
|
name: "Schematic",
|
|
47
|
-
data: { one: 1 },
|
|
48
|
-
});
|
|
49
|
-
await client.schematics.rename(schematic.key, "Schematic2");
|
|
50
|
-
const res = await client.schematics.retrieve({
|
|
51
|
-
key: schematic.key,
|
|
52
55
|
});
|
|
56
|
+
await client.schematics.rename(schem.key, "Schematic2");
|
|
57
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
53
58
|
expect(res.name).toEqual("Schematic2");
|
|
54
59
|
});
|
|
55
60
|
});
|
|
56
61
|
|
|
57
62
|
describe("setData", () => {
|
|
58
|
-
test("set data", async () => {
|
|
63
|
+
test("set data replaces body fields while preserving key and name", async () => {
|
|
59
64
|
const ws = await client.workspaces.create({
|
|
60
65
|
name: "Schematic",
|
|
61
66
|
layout: { one: 1 },
|
|
62
67
|
});
|
|
63
|
-
const
|
|
68
|
+
const schem = await client.schematics.create(ws.key, {
|
|
69
|
+
...schematic.ZERO_NEW,
|
|
64
70
|
name: "Schematic",
|
|
65
|
-
data: { one: 1 },
|
|
66
71
|
});
|
|
67
|
-
await client.schematics.setData(
|
|
68
|
-
|
|
69
|
-
key:
|
|
72
|
+
await client.schematics.setData(schem.key, {
|
|
73
|
+
...schematic.ZERO_NEW,
|
|
74
|
+
nodes: [{ key: "n1", position: { x: 10, y: 20 }, zIndex: 0 }],
|
|
75
|
+
configs: { n1: { variant: "valve" } },
|
|
70
76
|
});
|
|
71
|
-
|
|
77
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
78
|
+
expect(res.name).toEqual("Schematic");
|
|
79
|
+
expect(res.nodes).toHaveLength(1);
|
|
80
|
+
expect(res.nodes[0].key).toEqual("n1");
|
|
81
|
+
expect((res.configs.n1 as Record<string, unknown>).variant).toEqual("valve");
|
|
72
82
|
});
|
|
73
83
|
});
|
|
74
84
|
|
|
@@ -78,57 +88,48 @@ describe("Schematic", () => {
|
|
|
78
88
|
name: "Schematic",
|
|
79
89
|
layout: { one: 1 },
|
|
80
90
|
});
|
|
81
|
-
const
|
|
91
|
+
const schem = await client.schematics.create(ws.key, {
|
|
92
|
+
...schematic.ZERO_NEW,
|
|
82
93
|
name: "Schematic",
|
|
83
|
-
data: { one: 1 },
|
|
84
94
|
});
|
|
85
|
-
await client.schematics.delete(
|
|
86
|
-
await expect(client.schematics.retrieve({ key:
|
|
95
|
+
await client.schematics.delete(schem.key);
|
|
96
|
+
await expect(client.schematics.retrieve({ key: schem.key })).rejects.toThrow(
|
|
87
97
|
NotFoundError,
|
|
88
98
|
);
|
|
89
99
|
});
|
|
90
100
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
|
|
102
|
+
describe("config case preservation", () => {
|
|
103
|
+
test("preserves arbitrary key casing within config values", async () => {
|
|
104
|
+
const ws = await client.workspaces.create({ name: "CaseTest", layout: {} });
|
|
105
|
+
const schem = await client.schematics.create(ws.key, {
|
|
106
|
+
...schematic.ZERO_NEW,
|
|
107
|
+
configs: {
|
|
108
|
+
n1: {
|
|
109
|
+
camelCaseKey: "value1",
|
|
110
|
+
PascalCaseKey: "value2",
|
|
111
|
+
snake_case_key: "value3",
|
|
112
|
+
nested: {
|
|
113
|
+
innerCamelCase: 123,
|
|
114
|
+
InnerPascalCase: { deepKey: true },
|
|
115
|
+
},
|
|
106
116
|
},
|
|
107
117
|
},
|
|
108
118
|
});
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
expect(data.camelCaseKey).toEqual("value1");
|
|
116
|
-
expect(data.PascalCaseKey).toEqual("value2");
|
|
117
|
-
expect(data.snake_case_key).toEqual("value3");
|
|
118
|
-
expect((data.nested as Record<string, unknown>).innerCamelCase).toEqual(123);
|
|
119
|
+
const retrieved = await client.schematics.retrieve({ key: schem.key });
|
|
120
|
+
const config = retrieved.configs.n1 as Record<string, unknown>;
|
|
121
|
+
expect(config.camelCaseKey).toEqual("value1");
|
|
122
|
+
expect(config.PascalCaseKey).toEqual("value2");
|
|
123
|
+
expect(config.snake_case_key).toEqual("value3");
|
|
124
|
+
expect((config.nested as Record<string, unknown>).innerCamelCase).toEqual(123);
|
|
119
125
|
expect(
|
|
120
126
|
(
|
|
121
|
-
(
|
|
127
|
+
(config.nested as Record<string, unknown>).InnerPascalCase as Record<
|
|
122
128
|
string,
|
|
123
129
|
unknown
|
|
124
130
|
>
|
|
125
131
|
).deepKey,
|
|
126
132
|
).toEqual(true);
|
|
127
|
-
expect(Object.keys(data)).toContain("camelCaseKey");
|
|
128
|
-
expect(Object.keys(data)).toContain("PascalCaseKey");
|
|
129
|
-
expect(Object.keys(data)).toContain("snake_case_key");
|
|
130
|
-
expect(Object.keys(data)).not.toContain("camel_case_key");
|
|
131
|
-
expect(Object.keys(data)).not.toContain("pascal_case_key");
|
|
132
133
|
});
|
|
133
134
|
});
|
|
134
135
|
|
|
@@ -138,18 +139,17 @@ describe("Schematic", () => {
|
|
|
138
139
|
name: "Schematic",
|
|
139
140
|
layout: { one: 1 },
|
|
140
141
|
});
|
|
141
|
-
const
|
|
142
|
+
const schem = await client.schematics.create(ws.key, {
|
|
143
|
+
...schematic.ZERO_NEW,
|
|
142
144
|
name: "Schematic",
|
|
143
|
-
data: { one: 1 },
|
|
144
145
|
});
|
|
145
|
-
const
|
|
146
|
-
key:
|
|
146
|
+
const schem2 = await client.schematics.copy({
|
|
147
|
+
key: schem.key,
|
|
147
148
|
name: "Schematic2",
|
|
148
149
|
snapshot: false,
|
|
149
150
|
});
|
|
150
|
-
expect(
|
|
151
|
-
expect(
|
|
152
|
-
expect(schematic2.data.one).toEqual(1);
|
|
151
|
+
expect(schem2.name).toEqual("Schematic2");
|
|
152
|
+
expect(schem2.key).not.toEqual(uuid.ZERO);
|
|
153
153
|
});
|
|
154
154
|
|
|
155
155
|
describe("snapshot", () => {
|
|
@@ -158,19 +158,183 @@ describe("Schematic", () => {
|
|
|
158
158
|
name: "Schematic",
|
|
159
159
|
layout: { one: 1 },
|
|
160
160
|
});
|
|
161
|
-
const
|
|
161
|
+
const schem = await client.schematics.create(ws.key, {
|
|
162
|
+
...schematic.ZERO_NEW,
|
|
162
163
|
name: "Schematic",
|
|
163
|
-
data: { one: 1 },
|
|
164
164
|
});
|
|
165
|
-
const
|
|
166
|
-
key:
|
|
165
|
+
const schem2 = await client.schematics.copy({
|
|
166
|
+
key: schem.key,
|
|
167
167
|
name: "Schematic2",
|
|
168
168
|
snapshot: true,
|
|
169
169
|
});
|
|
170
170
|
await expect(
|
|
171
|
-
client.schematics.setData(
|
|
171
|
+
client.schematics.setData(schem2.key, { ...schematic.ZERO_NEW }),
|
|
172
172
|
).rejects.toThrow(ValidationError);
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
175
|
});
|
|
176
|
+
|
|
177
|
+
describe("dispatch", () => {
|
|
178
|
+
test("setNodePosition moves the matching node", async () => {
|
|
179
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
180
|
+
await client.schematics.setData(schem.key, {
|
|
181
|
+
...schematic.ZERO_NEW,
|
|
182
|
+
nodes: [{ key: "n1", position: { x: 0, y: 0 } }],
|
|
183
|
+
});
|
|
184
|
+
await client.schematics.dispatch(schem.key, "sess-1", [
|
|
185
|
+
schematic.setNodePosition({ key: "n1", position: { x: 100, y: 200 } }),
|
|
186
|
+
]);
|
|
187
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
188
|
+
expect(res.nodes).toHaveLength(1);
|
|
189
|
+
expect(res.nodes[0].position).toEqual({ x: 100, y: 200 });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("setNode inserts a node and writes its config", async () => {
|
|
193
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
194
|
+
await client.schematics.dispatch(schem.key, "sess-1", [
|
|
195
|
+
schematic.setNode({
|
|
196
|
+
node: { key: "n1", position: { x: 1, y: 2 } },
|
|
197
|
+
config: { label: "Pump" },
|
|
198
|
+
}),
|
|
199
|
+
]);
|
|
200
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
201
|
+
expect(res.nodes).toHaveLength(1);
|
|
202
|
+
expect(res.nodes[0]).toMatchObject({ key: "n1", position: { x: 1, y: 2 } });
|
|
203
|
+
expect(res.configs.n1.label).toBe("Pump");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("removeNode removes the node and drops its config", async () => {
|
|
207
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
208
|
+
await client.schematics.setData(schem.key, {
|
|
209
|
+
...schematic.ZERO_NEW,
|
|
210
|
+
nodes: [
|
|
211
|
+
{ key: "n1", position: { x: 0, y: 0 } },
|
|
212
|
+
{ key: "n2", position: { x: 1, y: 1 } },
|
|
213
|
+
],
|
|
214
|
+
configs: { n1: { label: "Pump" }, n2: { label: "Tank" } },
|
|
215
|
+
});
|
|
216
|
+
await client.schematics.dispatch(schem.key, "sess-1", [
|
|
217
|
+
schematic.removeNode({ key: "n1" }),
|
|
218
|
+
]);
|
|
219
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
220
|
+
expect(res.nodes).toHaveLength(1);
|
|
221
|
+
expect(res.nodes[0]).toMatchObject({ key: "n2", position: { x: 1, y: 1 } });
|
|
222
|
+
expect(res.configs).toEqual({ n2: { label: "Tank" } });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("addEdge appends new edges and is a no-op on duplicate keys", async () => {
|
|
226
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
227
|
+
const e = (
|
|
228
|
+
key: string,
|
|
229
|
+
srcNode: string,
|
|
230
|
+
srcParam: string,
|
|
231
|
+
tgtNode: string,
|
|
232
|
+
tgtParam: string,
|
|
233
|
+
) => ({
|
|
234
|
+
key,
|
|
235
|
+
source: { node: srcNode, param: srcParam },
|
|
236
|
+
target: { node: tgtNode, param: tgtParam },
|
|
237
|
+
});
|
|
238
|
+
await client.schematics.setData(schem.key, {
|
|
239
|
+
...schematic.ZERO_NEW,
|
|
240
|
+
edges: [e("e1", "a", "o", "b", "i"), e("e2", "b", "o", "c", "i")],
|
|
241
|
+
});
|
|
242
|
+
await client.schematics.dispatch(schem.key, "sess-1", [
|
|
243
|
+
schematic.addEdge({ edge: e("e2", "x", "y", "z", "w") }),
|
|
244
|
+
schematic.addEdge({ edge: e("e3", "c", "o", "d", "i") }),
|
|
245
|
+
]);
|
|
246
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
247
|
+
expect(res.edges).toEqual([
|
|
248
|
+
e("e1", "a", "o", "b", "i"),
|
|
249
|
+
e("e2", "b", "o", "c", "i"),
|
|
250
|
+
e("e3", "c", "o", "d", "i"),
|
|
251
|
+
]);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("removeEdge removes the matching edge", async () => {
|
|
255
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
256
|
+
await client.schematics.setData(schem.key, {
|
|
257
|
+
...schematic.ZERO_NEW,
|
|
258
|
+
edges: [
|
|
259
|
+
{
|
|
260
|
+
key: "e1",
|
|
261
|
+
source: { node: "a", param: "o" },
|
|
262
|
+
target: { node: "b", param: "i" },
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
});
|
|
266
|
+
await client.schematics.dispatch(schem.key, "sess-1", [
|
|
267
|
+
schematic.removeEdge({ key: "e1" }),
|
|
268
|
+
]);
|
|
269
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
270
|
+
expect(res.edges).toEqual([]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("setConfig upserts config under the given key", async () => {
|
|
274
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
275
|
+
await client.schematics.dispatch(schem.key, "sess-1", [
|
|
276
|
+
schematic.setConfig({ key: "n1", config: { label: "Original" } }),
|
|
277
|
+
schematic.setConfig({ key: "n1", config: { label: "Replaced" } }),
|
|
278
|
+
]);
|
|
279
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
280
|
+
expect(res.configs.n1.label).toBe("Replaced");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("applies a multi-action sequence atomically", async () => {
|
|
284
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
285
|
+
await client.schematics.dispatch(schem.key, "sess-1", [
|
|
286
|
+
schematic.setNode({ node: { key: "pump", position: { x: 0, y: 0 } } }),
|
|
287
|
+
schematic.setNode({ node: { key: "valve", position: { x: 100, y: 0 } } }),
|
|
288
|
+
schematic.addEdge({
|
|
289
|
+
edge: {
|
|
290
|
+
key: "e1",
|
|
291
|
+
source: { node: "pump", param: "out" },
|
|
292
|
+
target: { node: "valve", param: "in" },
|
|
293
|
+
},
|
|
294
|
+
}),
|
|
295
|
+
schematic.setConfig({ key: "pump", config: { label: "Main Pump" } }),
|
|
296
|
+
]);
|
|
297
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
298
|
+
expect(res.nodes).toHaveLength(2);
|
|
299
|
+
expect(res.edges).toHaveLength(1);
|
|
300
|
+
expect(res.configs.pump.label).toBe("Main Pump");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("converges to the final position after a 30-action drag storm", async () => {
|
|
304
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
305
|
+
await client.schematics.setData(schem.key, {
|
|
306
|
+
...schematic.ZERO_NEW,
|
|
307
|
+
nodes: [{ key: "pump", position: { x: 0, y: 0 } }],
|
|
308
|
+
});
|
|
309
|
+
const actions = Array.from({ length: 30 }, (_, i) =>
|
|
310
|
+
schematic.setNodePosition({ key: "pump", position: { x: i, y: i * 2 } }),
|
|
311
|
+
);
|
|
312
|
+
await client.schematics.dispatch(schem.key, "sess-1", actions);
|
|
313
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
314
|
+
expect(res.nodes[0].position).toEqual({ x: 29, y: 58 });
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("preserves arbitrary key casing within config values through dispatch", async () => {
|
|
318
|
+
const { schem } = await newWorkspaceSchematic(client);
|
|
319
|
+
await client.schematics.dispatch(schem.key, "sess-1", [
|
|
320
|
+
schematic.setConfig({
|
|
321
|
+
key: "n1",
|
|
322
|
+
config: {
|
|
323
|
+
camelCaseKey: "v1",
|
|
324
|
+
PascalCaseKey: "v2",
|
|
325
|
+
snake_case_key: "v3",
|
|
326
|
+
nested: { innerCamelCase: 1, InnerPascalCase: { deepKey: true } },
|
|
327
|
+
},
|
|
328
|
+
}),
|
|
329
|
+
]);
|
|
330
|
+
const res = await client.schematics.retrieve({ key: schem.key });
|
|
331
|
+
const config = res.configs.n1;
|
|
332
|
+
expect(config.camelCaseKey).toBe("v1");
|
|
333
|
+
expect(config.PascalCaseKey).toBe("v2");
|
|
334
|
+
expect(config.snake_case_key).toBe("v3");
|
|
335
|
+
const nested = config.nested as Record<string, unknown>;
|
|
336
|
+
expect(nested.innerCamelCase).toBe(1);
|
|
337
|
+
expect((nested.InnerPascalCase as Record<string, unknown>).deepKey).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
176
340
|
});
|