@statelyai/graph 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/schemas.mjs CHANGED
@@ -9,7 +9,7 @@ const PortDirectionSchema = z.enum([
9
9
  ]);
10
10
  const PortSchema = z.object({
11
11
  name: z.string(),
12
- direction: PortDirectionSchema.optional(),
12
+ direction: PortDirectionSchema,
13
13
  label: z.string().optional(),
14
14
  data: z.any(),
15
15
  x: z.number().optional(),
@@ -66,6 +66,162 @@ const GraphSchema = z.object({
66
66
  ]).optional(),
67
67
  style: StyleSchema.optional()
68
68
  });
69
+ function getValidationIssues(schema, value) {
70
+ const result = schema.safeParse(value);
71
+ if (result.success) return [];
72
+ return result.error.issues.map((issue) => ({
73
+ code: issue.code,
74
+ message: issue.message,
75
+ path: issue.path.map((segment) => typeof segment === "symbol" ? String(segment) : segment)
76
+ }));
77
+ }
78
+ function isGraphPort(value) {
79
+ return PortSchema.safeParse(value).success;
80
+ }
81
+ function isGraphNode(value) {
82
+ return NodeSchema.safeParse(value).success;
83
+ }
84
+ function isGraphEdge(value) {
85
+ return EdgeSchema.safeParse(value).success;
86
+ }
87
+ function isGraph(value) {
88
+ return validateGraph(value).length === 0;
89
+ }
90
+ function getGraphPortIssues(value) {
91
+ return getValidationIssues(PortSchema, value);
92
+ }
93
+ function getGraphNodeIssues(value) {
94
+ return getValidationIssues(NodeSchema, value);
95
+ }
96
+ function getGraphEdgeIssues(value) {
97
+ return getValidationIssues(EdgeSchema, value);
98
+ }
99
+ function getGraphIssues(value) {
100
+ return validateGraph(value);
101
+ }
102
+ function createIssue(code, message, path) {
103
+ return {
104
+ code,
105
+ message,
106
+ path
107
+ };
108
+ }
109
+ function getDuplicateIndexes(items, getKey) {
110
+ const indexesByKey = /* @__PURE__ */ new Map();
111
+ items.forEach((item, index) => {
112
+ const key = getKey(item);
113
+ if (key == null) return;
114
+ const indexes = indexesByKey.get(key) ?? [];
115
+ indexes.push(index);
116
+ indexesByKey.set(key, indexes);
117
+ });
118
+ for (const [key, indexes] of indexesByKey) if (indexes.length < 2) indexesByKey.delete(key);
119
+ return indexesByKey;
120
+ }
121
+ function getGraphInvariantIssues(graph) {
122
+ const issues = [];
123
+ const nodeIndexes = /* @__PURE__ */ new Map();
124
+ const nodesById = /* @__PURE__ */ new Map();
125
+ for (const [id, indexes] of getDuplicateIndexes(graph.nodes, (node) => node.id)) for (const index of indexes) issues.push(createIssue("duplicate_node_id", `Duplicate node id "${id}"`, [
126
+ "nodes",
127
+ index,
128
+ "id"
129
+ ]));
130
+ for (const [id, indexes] of getDuplicateIndexes(graph.edges, (edge) => edge.id)) for (const index of indexes) issues.push(createIssue("duplicate_edge_id", `Duplicate edge id "${id}"`, [
131
+ "edges",
132
+ index,
133
+ "id"
134
+ ]));
135
+ graph.nodes.forEach((node, index) => {
136
+ nodeIndexes.set(node.id, index);
137
+ nodesById.set(node.id, node);
138
+ });
139
+ if (graph.initialNodeId && !nodeIndexes.has(graph.initialNodeId)) issues.push(createIssue("missing_initial_node", `Initial node "${graph.initialNodeId}" does not exist`, ["initialNodeId"]));
140
+ graph.nodes.forEach((node, index) => {
141
+ if (node.id === "") issues.push(createIssue("empty_node_id", "Node id must be a non-empty string", [
142
+ "nodes",
143
+ index,
144
+ "id"
145
+ ]));
146
+ if (node.parentId === "") issues.push(createIssue("empty_parent_id", "Node parentId must be a non-empty string", [
147
+ "nodes",
148
+ index,
149
+ "parentId"
150
+ ]));
151
+ else if (node.parentId != null && !nodeIndexes.has(node.parentId)) issues.push(createIssue("missing_parent", `Parent node "${node.parentId}" does not exist`, [
152
+ "nodes",
153
+ index,
154
+ "parentId"
155
+ ]));
156
+ if (node.initialNodeId && !nodeIndexes.has(node.initialNodeId)) issues.push(createIssue("missing_node_initial", `Initial node "${node.initialNodeId}" does not exist`, [
157
+ "nodes",
158
+ index,
159
+ "initialNodeId"
160
+ ]));
161
+ for (const [name, indexes] of getDuplicateIndexes(node.ports ?? [], (port) => port.name)) for (const portIndex of indexes) issues.push(createIssue("duplicate_port_name", `Duplicate port name "${name}" on node "${node.id}"`, [
162
+ "nodes",
163
+ index,
164
+ "ports",
165
+ portIndex,
166
+ "name"
167
+ ]));
168
+ });
169
+ for (const node of graph.nodes) {
170
+ const seen = /* @__PURE__ */ new Set();
171
+ let current = node.parentId;
172
+ while (current != null) {
173
+ if (current === node.id || seen.has(current)) {
174
+ issues.push(createIssue("parent_cycle", `Node "${node.id}" is part of a parent cycle`, [
175
+ "nodes",
176
+ nodeIndexes.get(node.id) ?? 0,
177
+ "parentId"
178
+ ]));
179
+ break;
180
+ }
181
+ seen.add(current);
182
+ current = nodesById.get(current)?.parentId;
183
+ }
184
+ }
185
+ graph.edges.forEach((edge, index) => {
186
+ if (edge.id === "") issues.push(createIssue("empty_edge_id", "Edge id must be a non-empty string", [
187
+ "edges",
188
+ index,
189
+ "id"
190
+ ]));
191
+ const source = nodesById.get(edge.sourceId);
192
+ const target = nodesById.get(edge.targetId);
193
+ if (!source) issues.push(createIssue("missing_source_node", `Source node "${edge.sourceId}" does not exist`, [
194
+ "edges",
195
+ index,
196
+ "sourceId"
197
+ ]));
198
+ if (!target) issues.push(createIssue("missing_target_node", `Target node "${edge.targetId}" does not exist`, [
199
+ "edges",
200
+ index,
201
+ "targetId"
202
+ ]));
203
+ if (source && edge.sourcePort !== void 0 && !source.ports?.some((port) => port.name === edge.sourcePort)) issues.push(createIssue("missing_source_port", `Port "${edge.sourcePort}" does not exist on source node "${edge.sourceId}"`, [
204
+ "edges",
205
+ index,
206
+ "sourcePort"
207
+ ]));
208
+ if (target && edge.targetPort !== void 0 && !target.ports?.some((port) => port.name === edge.targetPort)) issues.push(createIssue("missing_target_port", `Port "${edge.targetPort}" does not exist on target node "${edge.targetId}"`, [
209
+ "edges",
210
+ index,
211
+ "targetPort"
212
+ ]));
213
+ });
214
+ return issues;
215
+ }
216
+ function validateGraph(value) {
217
+ const shapeResult = GraphSchema.safeParse(value);
218
+ if (!shapeResult.success) return shapeResult.error.issues.map((issue) => ({
219
+ code: issue.code,
220
+ message: issue.message,
221
+ path: issue.path.map((segment) => typeof segment === "symbol" ? String(segment) : segment)
222
+ }));
223
+ return getGraphInvariantIssues(shapeResult.data);
224
+ }
69
225
 
70
226
  //#endregion
71
- export { EdgeSchema, GraphSchema, NodeSchema, PortSchema };
227
+ export { EdgeSchema, GraphSchema, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort, validateGraph };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@statelyai/graph",
3
3
  "type": "module",
4
- "version": "0.11.1",
4
+ "version": "0.13.0",
5
5
  "description": "A TypeScript-first graph library with plain JSON-serializable objects",
6
6
  "author": "David Khourshid <david@stately.ai>",
7
7
  "license": "MIT",
@@ -150,6 +150,7 @@
150
150
  },
151
151
  "required": [
152
152
  "name",
153
+ "direction",
153
154
  "data"
154
155
  ],
155
156
  "additionalProperties": false
@@ -125,6 +125,7 @@
125
125
  },
126
126
  "required": [
127
127
  "name",
128
+ "direction",
128
129
  "data"
129
130
  ],
130
131
  "additionalProperties": false