@neo4j-ndl/react-graph 1.2.23 → 1.2.24
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/lib/cjs/styling/compile-graph-styles.js +482 -0
- package/lib/cjs/styling/compile-graph-styles.js.map +1 -0
- package/lib/cjs/styling/parse-style-rules.js +69 -0
- package/lib/cjs/styling/parse-style-rules.js.map +1 -0
- package/lib/cjs/styling/style-types.js +119 -0
- package/lib/cjs/styling/style-types.js.map +1 -0
- package/lib/esm/styling/compile-graph-styles.js +478 -0
- package/lib/esm/styling/compile-graph-styles.js.map +1 -0
- package/lib/esm/styling/parse-style-rules.js +63 -0
- package/lib/esm/styling/parse-style-rules.js.map +1 -0
- package/lib/esm/styling/style-types.js +116 -0
- package/lib/esm/styling/style-types.js.map +1 -0
- package/lib/types/styling/compile-graph-styles.d.ts +40 -0
- package/lib/types/styling/compile-graph-styles.d.ts.map +1 -0
- package/lib/types/styling/parse-style-rules.d.ts +49 -0
- package/lib/types/styling/parse-style-rules.d.ts.map +1 -0
- package/lib/types/styling/style-types.d.ts +169 -0
- package/lib/types/styling/style-types.d.ts.map +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) "Neo4j"
|
|
5
|
+
* Neo4j Sweden AB [http://neo4j.com]
|
|
6
|
+
*
|
|
7
|
+
* This file is part of Neo4j.
|
|
8
|
+
*
|
|
9
|
+
* Neo4j is free software: you can redistribute it and/or modify
|
|
10
|
+
* it under the terms of the GNU General Public License as published by
|
|
11
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
* (at your option) any later version.
|
|
13
|
+
*
|
|
14
|
+
* This program is distributed in the hope that it will be useful,
|
|
15
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
* GNU General Public License for more details.
|
|
18
|
+
*
|
|
19
|
+
* You should have received a copy of the GNU General Public License
|
|
20
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
*/
|
|
22
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
23
|
+
var t = {};
|
|
24
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
25
|
+
t[p] = s[p];
|
|
26
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
27
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
28
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
29
|
+
t[p[i]] = s[p[i]];
|
|
30
|
+
}
|
|
31
|
+
return t;
|
|
32
|
+
};
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.DEFAULT_REL_COLOR = void 0;
|
|
35
|
+
exports.compileStyleRules = compileStyleRules;
|
|
36
|
+
const word_color_1 = require("@neo4j-devtools/word-color");
|
|
37
|
+
const base_1 = require("@neo4j-ndl/base");
|
|
38
|
+
function compareByPriorityAscending(a, b) {
|
|
39
|
+
return a.priority - b.priority;
|
|
40
|
+
}
|
|
41
|
+
function collectStyleMatchers(styles) {
|
|
42
|
+
const rulesByLabel = new Map();
|
|
43
|
+
const rulesByType = new Map();
|
|
44
|
+
const globalLabelRules = [];
|
|
45
|
+
const globalReltypeRules = [];
|
|
46
|
+
// Handle null/undefined styles gracefully
|
|
47
|
+
if (!styles || styles.length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
globalLabelRules,
|
|
50
|
+
globalReltypeRules,
|
|
51
|
+
rulesByLabel,
|
|
52
|
+
rulesByType,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const totalRules = styles.length;
|
|
56
|
+
styles.forEach((style, index) => {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
if ((_a = style.disabled) !== null && _a !== void 0 ? _a : false) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (style.priority !== undefined && style.priority < 0) {
|
|
62
|
+
throw new Error(`StyleRule priority must be >= 0, got ${style.priority}. Negative values are reserved for internal use.`);
|
|
63
|
+
}
|
|
64
|
+
// Fill in priority if not set (negative values preserve original order)
|
|
65
|
+
const { priority } = style, rest = __rest(style, ["priority"]);
|
|
66
|
+
const ruleWithPriority = Object.assign(Object.assign({}, rest), { priority: priority !== null && priority !== void 0 ? priority : index - totalRules });
|
|
67
|
+
// style for nodes
|
|
68
|
+
if ('label' in style.match) {
|
|
69
|
+
// if the label is null/undefined, it's a global rule (matches any node)
|
|
70
|
+
if (style.match.label == null) {
|
|
71
|
+
globalLabelRules.push(ruleWithPriority);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Specific label rule
|
|
75
|
+
const existing = (_b = rulesByLabel.get(style.match.label)) !== null && _b !== void 0 ? _b : [];
|
|
76
|
+
rulesByLabel.set(style.match.label, [...existing, ruleWithPriority]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// style for relationships
|
|
80
|
+
if ('reltype' in style.match) {
|
|
81
|
+
if (style.match.reltype === null) {
|
|
82
|
+
// Global reltype rule (matches any relationship)
|
|
83
|
+
globalReltypeRules.push(ruleWithPriority);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Specific reltype rule
|
|
87
|
+
const existing = rulesByType.get(style.match.reltype) || [];
|
|
88
|
+
rulesByType.set(style.match.reltype, [...existing, ruleWithPriority]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
globalLabelRules,
|
|
94
|
+
globalReltypeRules,
|
|
95
|
+
rulesByLabel,
|
|
96
|
+
rulesByType,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function extractPropertyValue(prop) {
|
|
100
|
+
switch (prop.type) {
|
|
101
|
+
case 'string':
|
|
102
|
+
return JSON.parse(prop.stringified);
|
|
103
|
+
case 'number':
|
|
104
|
+
case 'integer':
|
|
105
|
+
case 'float':
|
|
106
|
+
return Number(prop.stringified);
|
|
107
|
+
case 'boolean':
|
|
108
|
+
case 'Boolean':
|
|
109
|
+
return prop.stringified === 'true';
|
|
110
|
+
case 'null':
|
|
111
|
+
return null;
|
|
112
|
+
default:
|
|
113
|
+
return prop.stringified;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Evaluates a WHERE clause using three-valued logic matching Cypher semantics.
|
|
118
|
+
*
|
|
119
|
+
* From Neo4j Cypher Manual:
|
|
120
|
+
* - Comparing any value to `null` results in `null`, not `true` or `false`
|
|
121
|
+
* - Accessing a non-existent property returns `null`
|
|
122
|
+
* - In WHERE clauses, only `true` passes; both `false` and `null` exclude the row
|
|
123
|
+
*
|
|
124
|
+
* Three-valued logic rules:
|
|
125
|
+
* - NOT null → null
|
|
126
|
+
* - true AND null → null, false AND null → false
|
|
127
|
+
* - true OR null → true, false OR null → null
|
|
128
|
+
*
|
|
129
|
+
* @see https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/
|
|
130
|
+
*/
|
|
131
|
+
function evaluateWhere(node, where) {
|
|
132
|
+
if (!where) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
if ('equal' in where) {
|
|
136
|
+
const [left, right] = where.equal;
|
|
137
|
+
const leftVal = evaluateValue(left, node);
|
|
138
|
+
const rightVal = evaluateValue(right, node);
|
|
139
|
+
// In Cypher, null = null → null (not true)
|
|
140
|
+
if (leftVal === null || rightVal === null) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return leftVal === rightVal;
|
|
144
|
+
}
|
|
145
|
+
if ('not' in where) {
|
|
146
|
+
// Cypher: NOT null → null
|
|
147
|
+
const isMatch = evaluateWhere(node, where.not);
|
|
148
|
+
return isMatch === null ? null : !isMatch;
|
|
149
|
+
}
|
|
150
|
+
if ('lessThan' in where) {
|
|
151
|
+
const [left, right] = where.lessThan;
|
|
152
|
+
return safeCompare(left, right, node, (a, b) => a < b);
|
|
153
|
+
}
|
|
154
|
+
if ('lessThanOrEqual' in where) {
|
|
155
|
+
const [left, right] = where.lessThanOrEqual;
|
|
156
|
+
return safeCompare(left, right, node, (a, b) => a <= b);
|
|
157
|
+
}
|
|
158
|
+
if ('greaterThan' in where) {
|
|
159
|
+
const [left, right] = where.greaterThan;
|
|
160
|
+
return safeCompare(left, right, node, (a, b) => a > b);
|
|
161
|
+
}
|
|
162
|
+
if ('greaterThanOrEqual' in where) {
|
|
163
|
+
const [left, right] = where.greaterThanOrEqual;
|
|
164
|
+
return safeCompare(left, right, node, (a, b) => a >= b);
|
|
165
|
+
}
|
|
166
|
+
if ('contains' in where) {
|
|
167
|
+
const [left, right] = where.contains;
|
|
168
|
+
return safeStringCompare(left, right, node, (a, b) => a.includes(b));
|
|
169
|
+
}
|
|
170
|
+
if ('startsWith' in where) {
|
|
171
|
+
const [left, right] = where.startsWith;
|
|
172
|
+
return safeStringCompare(left, right, node, (a, b) => a.startsWith(b));
|
|
173
|
+
}
|
|
174
|
+
if ('endsWith' in where) {
|
|
175
|
+
const [left, right] = where.endsWith;
|
|
176
|
+
return safeStringCompare(left, right, node, (a, b) => a.endsWith(b));
|
|
177
|
+
}
|
|
178
|
+
if ('isNull' in where) {
|
|
179
|
+
// IS NULL returns true/false (never null)
|
|
180
|
+
const value = evaluateValue(where.isNull, node);
|
|
181
|
+
return value === null;
|
|
182
|
+
}
|
|
183
|
+
if ('and' in where) {
|
|
184
|
+
// Cypher three-valued AND:
|
|
185
|
+
// - If any is false → false
|
|
186
|
+
// - Else if any is null → null
|
|
187
|
+
// - Else → true
|
|
188
|
+
let hasNull = false;
|
|
189
|
+
for (const r of where.and) {
|
|
190
|
+
const isMatch = evaluateWhere(node, r);
|
|
191
|
+
if (isMatch === false) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
if (isMatch === null) {
|
|
195
|
+
hasNull = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return hasNull ? null : true;
|
|
199
|
+
}
|
|
200
|
+
if ('or' in where) {
|
|
201
|
+
// Cypher three-valued OR:
|
|
202
|
+
// - If any is true → true
|
|
203
|
+
// - Else if any is null → null
|
|
204
|
+
// - Else → false
|
|
205
|
+
let hasNull = false;
|
|
206
|
+
for (const r of where.or) {
|
|
207
|
+
const isMatch = evaluateWhere(node, r);
|
|
208
|
+
if (isMatch === true) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
if (isMatch === null) {
|
|
212
|
+
hasNull = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return hasNull ? null : false;
|
|
216
|
+
}
|
|
217
|
+
if ('label' in where) {
|
|
218
|
+
if ('labelsSorted' in node) {
|
|
219
|
+
return where.label === null || node.labelsSorted.includes(where.label);
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if ('reltype' in where) {
|
|
224
|
+
if ('type' in node) {
|
|
225
|
+
return where.reltype === null || node.type === where.reltype;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if ('property' in where) {
|
|
230
|
+
return where.property === null || where.property in node.properties;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
function evaluateRelStyle(apply, rel) {
|
|
235
|
+
var _a;
|
|
236
|
+
return Object.assign(Object.assign({}, apply), { captions: (_a = apply.captions) === null || _a === void 0 ? void 0 : _a.map((caption) => {
|
|
237
|
+
const { value, styles } = caption;
|
|
238
|
+
if (typeof value === 'string' || value === undefined) {
|
|
239
|
+
return { styles, value };
|
|
240
|
+
}
|
|
241
|
+
if ('useType' in value) {
|
|
242
|
+
return { styles, value: rel.type };
|
|
243
|
+
}
|
|
244
|
+
if ('property' in value) {
|
|
245
|
+
const prop = rel.properties[value.property];
|
|
246
|
+
if (prop === undefined) {
|
|
247
|
+
return { styles, value: '' };
|
|
248
|
+
}
|
|
249
|
+
const resolvedValue = prop.type === 'string'
|
|
250
|
+
? prop.stringified.slice(1, -1)
|
|
251
|
+
: prop.stringified;
|
|
252
|
+
return { styles, value: resolvedValue };
|
|
253
|
+
}
|
|
254
|
+
return { styles, value: rel.id };
|
|
255
|
+
}) });
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Safely compare two values using Cypher's three-valued logic.
|
|
259
|
+
* Returns `null` if either operand is null (matching Cypher semantics).
|
|
260
|
+
*
|
|
261
|
+
* From Neo4j Cypher Manual:
|
|
262
|
+
* "Comparing any value to `null` using `=` or `<>` results in `null`"
|
|
263
|
+
*
|
|
264
|
+
* @see https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/
|
|
265
|
+
*/
|
|
266
|
+
function safeCompare(left, right, graphItem, compareFn) {
|
|
267
|
+
const leftVal = evaluateValue(left, graphItem);
|
|
268
|
+
const rightVal = evaluateValue(right, graphItem);
|
|
269
|
+
if (leftVal === null || rightVal === null) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return compareFn(leftVal, rightVal);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* String comparison using Cypher semantics.
|
|
276
|
+
* Returns `null` if either operand is null OR not a string.
|
|
277
|
+
*
|
|
278
|
+
* From Neo4j Cypher Manual:
|
|
279
|
+
* "When these operators are applied to non-string values, they return `null`
|
|
280
|
+
* instead of attempting type coercion."
|
|
281
|
+
*
|
|
282
|
+
* @see https://neo4j.com/docs/cypher-manual/current/expressions/predicates/string-operators/
|
|
283
|
+
*/
|
|
284
|
+
function safeStringCompare(left, right, graphItem, compareFn) {
|
|
285
|
+
const leftVal = evaluateValue(left, graphItem);
|
|
286
|
+
const rightVal = evaluateValue(right, graphItem);
|
|
287
|
+
// Return null if either is null or not a string (no type coercion)
|
|
288
|
+
if (leftVal === null ||
|
|
289
|
+
rightVal === null ||
|
|
290
|
+
typeof leftVal !== 'string' ||
|
|
291
|
+
typeof rightVal !== 'string') {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
return compareFn(leftVal, rightVal);
|
|
295
|
+
}
|
|
296
|
+
function evaluateValue(value, graphItem) {
|
|
297
|
+
if (typeof value === 'object' && value !== null) {
|
|
298
|
+
if ('property' in value) {
|
|
299
|
+
if (value.property === null) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const prop = graphItem.properties[value.property];
|
|
303
|
+
if (prop === undefined) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
return extractPropertyValue(prop);
|
|
307
|
+
}
|
|
308
|
+
if ('label' in value) {
|
|
309
|
+
if ('labelsSorted' in graphItem) {
|
|
310
|
+
return (value.label === null || graphItem.labelsSorted.includes(value.label));
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
if ('reltype' in value) {
|
|
315
|
+
if ('type' in graphItem) {
|
|
316
|
+
return (value.reltype === null ||
|
|
317
|
+
graphItem.type === value.reltype);
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return value;
|
|
323
|
+
}
|
|
324
|
+
// Default caption selection logic
|
|
325
|
+
const captionPriorityOrder = [
|
|
326
|
+
/^name$/i,
|
|
327
|
+
/^title$/i,
|
|
328
|
+
/^label$/i,
|
|
329
|
+
/name$/i,
|
|
330
|
+
/description$/i,
|
|
331
|
+
/^.+/,
|
|
332
|
+
];
|
|
333
|
+
function getDefaultNodeCaption(node) {
|
|
334
|
+
const propertyKeys = Object.keys(node.properties);
|
|
335
|
+
for (const regex of captionPriorityOrder) {
|
|
336
|
+
const matchingKey = propertyKeys.find((key) => regex.test(key));
|
|
337
|
+
if (matchingKey !== undefined) {
|
|
338
|
+
const prop = node.properties[matchingKey];
|
|
339
|
+
if (prop !== undefined) {
|
|
340
|
+
return { value: { property: matchingKey } };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (node.labelsSorted[0] !== undefined) {
|
|
345
|
+
return { value: { useType: true } };
|
|
346
|
+
}
|
|
347
|
+
return { value: { property: node.id } };
|
|
348
|
+
}
|
|
349
|
+
function evaluateNodeStyle(apply, node) {
|
|
350
|
+
var _a;
|
|
351
|
+
return Object.assign(Object.assign({}, apply), { captions: ((_a = apply.captions) !== null && _a !== void 0 ? _a : [getDefaultNodeCaption(node)]).map((caption) => {
|
|
352
|
+
const { value, styles } = caption;
|
|
353
|
+
if (typeof value === 'string' || value === undefined) {
|
|
354
|
+
return { styles, value };
|
|
355
|
+
}
|
|
356
|
+
if ('useType' in value) {
|
|
357
|
+
return { styles, value: node.labelsSorted[0] };
|
|
358
|
+
}
|
|
359
|
+
if ('property' in value) {
|
|
360
|
+
const prop = node.properties[value.property];
|
|
361
|
+
if (prop === undefined) {
|
|
362
|
+
return { styles, value: '' };
|
|
363
|
+
}
|
|
364
|
+
const resolvedValue = prop.type === 'string'
|
|
365
|
+
? prop.stringified.slice(1, -1)
|
|
366
|
+
: prop.stringified;
|
|
367
|
+
return { styles, value: resolvedValue };
|
|
368
|
+
}
|
|
369
|
+
return { styles, value: node.labelsSorted[0] };
|
|
370
|
+
}) });
|
|
371
|
+
}
|
|
372
|
+
function createRelStyleFunction(rules) {
|
|
373
|
+
return (rel) => {
|
|
374
|
+
const style = {};
|
|
375
|
+
// evaluateWhere uses Cypher-style three-valued logic (true/false/null).
|
|
376
|
+
// Only `true` passes; both `false` and `null` (unknown) skip the rule.
|
|
377
|
+
for (const rule of rules) {
|
|
378
|
+
// Check if the rule applies to this relationship
|
|
379
|
+
if ('reltype' in rule.match) {
|
|
380
|
+
const isReltypeMatch = rule.match.reltype === null || rel.type === rule.match.reltype;
|
|
381
|
+
if (isReltypeMatch && evaluateWhere(rel, rule.where) === true) {
|
|
382
|
+
// Merge the style properties (later rules override earlier ones)
|
|
383
|
+
Object.assign(style, rule.apply);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return evaluateRelStyle(style, rel);
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
exports.DEFAULT_REL_COLOR = base_1.tokens.palette.neutral['40'];
|
|
391
|
+
const NO_LABEL_FALLBACK_COLOR = base_1.tokens.palette.neutral['40'];
|
|
392
|
+
function createByLabelSetFunction(styleMatchers) {
|
|
393
|
+
// Memoize default color by label string (idea 2: avoid recalculating per node)
|
|
394
|
+
const defaultColorCache = new Map();
|
|
395
|
+
// Cache sorted rules by label set key (idea 1: avoid allocation + sort per node)
|
|
396
|
+
const sortedRulesCache = new Map();
|
|
397
|
+
// Cache merged Style for where-free rule sets (idea 3: skip rule iteration entirely)
|
|
398
|
+
const mergedStyleCache = new Map();
|
|
399
|
+
return (node) => {
|
|
400
|
+
var _a;
|
|
401
|
+
const labelSetKey = node.labelsSorted.join('\0');
|
|
402
|
+
// Fast path: if all rules for this label set are where-free,
|
|
403
|
+
// the merged style is identical for every node with the same labels.
|
|
404
|
+
const cachedStyle = mergedStyleCache.get(labelSetKey);
|
|
405
|
+
if (cachedStyle !== undefined) {
|
|
406
|
+
return evaluateNodeStyle(cachedStyle, node);
|
|
407
|
+
}
|
|
408
|
+
// Memoize default color by first label
|
|
409
|
+
const labelForDefaultColor = (_a = node.labelsSorted[0]) !== null && _a !== void 0 ? _a : null;
|
|
410
|
+
let defaultColor;
|
|
411
|
+
if (labelForDefaultColor === null) {
|
|
412
|
+
defaultColor = NO_LABEL_FALLBACK_COLOR;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
let cached = defaultColorCache.get(labelForDefaultColor);
|
|
416
|
+
if (cached === undefined) {
|
|
417
|
+
cached =
|
|
418
|
+
(0, word_color_1.calculateDefaultNodeColors)(labelForDefaultColor).backgroundColor;
|
|
419
|
+
defaultColorCache.set(labelForDefaultColor, cached);
|
|
420
|
+
}
|
|
421
|
+
defaultColor = cached;
|
|
422
|
+
}
|
|
423
|
+
// Cache sorted rules by label set (avoids repeated allocation + sort)
|
|
424
|
+
let sortedRules = sortedRulesCache.get(labelSetKey);
|
|
425
|
+
if (sortedRules === undefined) {
|
|
426
|
+
const matchingRules = [...styleMatchers.globalLabelRules];
|
|
427
|
+
for (const label of node.labelsSorted) {
|
|
428
|
+
const labelRules = styleMatchers.rulesByLabel.get(label);
|
|
429
|
+
if (labelRules) {
|
|
430
|
+
matchingRules.push(...labelRules);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
sortedRules = matchingRules.toSorted(compareByPriorityAscending);
|
|
434
|
+
sortedRulesCache.set(labelSetKey, sortedRules);
|
|
435
|
+
}
|
|
436
|
+
// Evaluate rules, tracking whether all are where-free
|
|
437
|
+
const collectStyles = { color: defaultColor };
|
|
438
|
+
let isAllWhereFree = true;
|
|
439
|
+
for (const rule of sortedRules) {
|
|
440
|
+
if (rule.where !== undefined) {
|
|
441
|
+
isAllWhereFree = false;
|
|
442
|
+
if (evaluateWhere(node, rule.where) === true) {
|
|
443
|
+
Object.assign(collectStyles, rule.apply);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
Object.assign(collectStyles, rule.apply);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// When all rules are where-free, the merged style depends only on labels.
|
|
451
|
+
// Cache it so subsequent nodes with the same label set skip rule iteration.
|
|
452
|
+
if (isAllWhereFree) {
|
|
453
|
+
mergedStyleCache.set(labelSetKey, collectStyles);
|
|
454
|
+
}
|
|
455
|
+
return evaluateNodeStyle(collectStyles, node);
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const DEFAULT_REL_STYLE = {
|
|
459
|
+
match: { reltype: null },
|
|
460
|
+
apply: {
|
|
461
|
+
color: exports.DEFAULT_REL_COLOR,
|
|
462
|
+
captions: [{ value: { useType: true } }],
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
function compileStyleRules({ reltypes, styleRules, }) {
|
|
466
|
+
var _a;
|
|
467
|
+
const styleMatchers = collectStyleMatchers(styleRules);
|
|
468
|
+
// as relationships can only have one type, we can use a map to store the style function for each type
|
|
469
|
+
const byType = new Map();
|
|
470
|
+
// Compile relationship type-based rules
|
|
471
|
+
for (const reltype of reltypes) {
|
|
472
|
+
const rules = (_a = styleMatchers.rulesByType.get(reltype)) !== null && _a !== void 0 ? _a : [];
|
|
473
|
+
byType.set(reltype, createRelStyleFunction([
|
|
474
|
+
DEFAULT_REL_STYLE,
|
|
475
|
+
...styleMatchers.globalReltypeRules,
|
|
476
|
+
...rules,
|
|
477
|
+
]));
|
|
478
|
+
}
|
|
479
|
+
const byLabelSet = createByLabelSetFunction(styleMatchers);
|
|
480
|
+
return { byLabelSet, byType, styleMatchers };
|
|
481
|
+
}
|
|
482
|
+
//# sourceMappingURL=compile-graph-styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-graph-styles.js","sourceRoot":"","sources":["../../../src/styling/compile-graph-styles.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport { calculateDefaultNodeColors } from '@neo4j-devtools/word-color';\nimport { tokens } from '@neo4j-ndl/base';\n\nimport {\n type NodeData,\n type PortableProperty,\n type RelData,\n} from '../graph-visualization-context';\nimport {\n type Caption,\n type CypherValue,\n type EvaluatedNvlNodeStyle,\n type EvaluatedNvlRelationshipStyle,\n type Style,\n type StyleRule,\n type Value,\n type Where,\n} from './style-types';\n\ntype StyleMatchers = {\n globalLabelRules: StyleRule[];\n globalReltypeRules: StyleRule[];\n rulesByLabel: Map<string, StyleRule[]>;\n rulesByType: Map<string, StyleRule[]>;\n};\n\nfunction compareByPriorityAscending(a: StyleRule, b: StyleRule): number {\n return a.priority! - b.priority!;\n}\n\nfunction collectStyleMatchers(\n styles: StyleRule[] | null | undefined,\n): StyleMatchers {\n const rulesByLabel = new Map<string, StyleRule[]>();\n const rulesByType = new Map<string, StyleRule[]>();\n const globalLabelRules: StyleRule[] = [];\n const globalReltypeRules: StyleRule[] = [];\n\n // Handle null/undefined styles gracefully\n if (!styles || styles.length === 0) {\n return {\n globalLabelRules,\n globalReltypeRules,\n rulesByLabel,\n rulesByType,\n };\n }\n\n const totalRules = styles.length;\n\n styles.forEach((style, index) => {\n if (style.disabled ?? false) {\n return;\n }\n\n if (style.priority !== undefined && style.priority < 0) {\n throw new Error(\n `StyleRule priority must be >= 0, got ${style.priority}. Negative values are reserved for internal use.`,\n );\n }\n\n // Fill in priority if not set (negative values preserve original order)\n const { priority, ...rest } = style;\n const ruleWithPriority: StyleRule = {\n ...rest,\n priority: priority ?? index - totalRules,\n };\n\n // style for nodes\n if ('label' in style.match) {\n // if the label is null/undefined, it's a global rule (matches any node)\n if (style.match.label == null) {\n globalLabelRules.push(ruleWithPriority);\n } else {\n // Specific label rule\n const existing = rulesByLabel.get(style.match.label) ?? [];\n rulesByLabel.set(style.match.label, [...existing, ruleWithPriority]);\n }\n }\n\n // style for relationships\n if ('reltype' in style.match) {\n if (style.match.reltype === null) {\n // Global reltype rule (matches any relationship)\n globalReltypeRules.push(ruleWithPriority);\n } else {\n // Specific reltype rule\n const existing = rulesByType.get(style.match.reltype) || [];\n rulesByType.set(style.match.reltype, [...existing, ruleWithPriority]);\n }\n }\n });\n\n return {\n globalLabelRules,\n globalReltypeRules,\n rulesByLabel,\n rulesByType,\n };\n}\n\nfunction extractPropertyValue(\n prop: PortableProperty,\n): string | number | boolean | null {\n switch (prop.type) {\n case 'string':\n return JSON.parse(prop.stringified);\n case 'number':\n case 'integer':\n case 'float':\n return Number(prop.stringified);\n case 'boolean':\n case 'Boolean':\n return prop.stringified === 'true';\n case 'null':\n return null;\n default:\n return prop.stringified;\n }\n}\n\n/**\n * Three-valued logic type matching Cypher/SQL semantics.\n * - `true`: condition is satisfied\n * - `false`: condition is not satisfied\n * - `null`: condition is unknown (e.g., comparing with null/missing property)\n *\n * @see https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/\n */\ntype Ternary = boolean | null;\n\n/**\n * Evaluates a WHERE clause using three-valued logic matching Cypher semantics.\n *\n * From Neo4j Cypher Manual:\n * - Comparing any value to `null` results in `null`, not `true` or `false`\n * - Accessing a non-existent property returns `null`\n * - In WHERE clauses, only `true` passes; both `false` and `null` exclude the row\n *\n * Three-valued logic rules:\n * - NOT null → null\n * - true AND null → null, false AND null → false\n * - true OR null → true, false OR null → null\n *\n * @see https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/\n */\nfunction evaluateWhere(node: NodeData | RelData, where?: Where): Ternary {\n if (!where) {\n return true;\n }\n\n if ('equal' in where) {\n const [left, right] = where.equal;\n const leftVal = evaluateValue(left, node);\n const rightVal = evaluateValue(right, node);\n // In Cypher, null = null → null (not true)\n if (leftVal === null || rightVal === null) {\n return null;\n }\n return leftVal === rightVal;\n }\n\n if ('not' in where) {\n // Cypher: NOT null → null\n const isMatch = evaluateWhere(node, where.not);\n return isMatch === null ? null : !isMatch;\n }\n\n if ('lessThan' in where) {\n const [left, right] = where.lessThan;\n return safeCompare(left, right, node, (a, b) => a < b);\n }\n\n if ('lessThanOrEqual' in where) {\n const [left, right] = where.lessThanOrEqual;\n return safeCompare(left, right, node, (a, b) => a <= b);\n }\n\n if ('greaterThan' in where) {\n const [left, right] = where.greaterThan;\n return safeCompare(left, right, node, (a, b) => a > b);\n }\n\n if ('greaterThanOrEqual' in where) {\n const [left, right] = where.greaterThanOrEqual;\n return safeCompare(left, right, node, (a, b) => a >= b);\n }\n\n if ('contains' in where) {\n const [left, right] = where.contains;\n return safeStringCompare(left, right, node, (a, b) => a.includes(b));\n }\n\n if ('startsWith' in where) {\n const [left, right] = where.startsWith;\n return safeStringCompare(left, right, node, (a, b) => a.startsWith(b));\n }\n\n if ('endsWith' in where) {\n const [left, right] = where.endsWith;\n return safeStringCompare(left, right, node, (a, b) => a.endsWith(b));\n }\n\n if ('isNull' in where) {\n // IS NULL returns true/false (never null)\n const value = evaluateValue(where.isNull, node);\n return value === null;\n }\n\n if ('and' in where) {\n // Cypher three-valued AND:\n // - If any is false → false\n // - Else if any is null → null\n // - Else → true\n let hasNull = false;\n for (const r of where.and) {\n const isMatch = evaluateWhere(node, r);\n if (isMatch === false) {\n return false;\n }\n if (isMatch === null) {\n hasNull = true;\n }\n }\n return hasNull ? null : true;\n }\n\n if ('or' in where) {\n // Cypher three-valued OR:\n // - If any is true → true\n // - Else if any is null → null\n // - Else → false\n let hasNull = false;\n for (const r of where.or) {\n const isMatch = evaluateWhere(node, r);\n if (isMatch === true) {\n return true;\n }\n if (isMatch === null) {\n hasNull = true;\n }\n }\n return hasNull ? null : false;\n }\n\n if ('label' in where) {\n if ('labelsSorted' in node) {\n return where.label === null || node.labelsSorted.includes(where.label);\n }\n return false;\n }\n\n if ('reltype' in where) {\n if ('type' in node) {\n return where.reltype === null || (node as RelData).type === where.reltype;\n }\n return false;\n }\n\n if ('property' in where) {\n return where.property === null || where.property in node.properties;\n }\n\n return false;\n}\n\nfunction evaluateRelStyle(\n apply: Style,\n rel: RelData,\n): EvaluatedNvlRelationshipStyle {\n return {\n ...apply,\n captions: apply.captions?.map((caption) => {\n const { value, styles } = caption;\n\n if (typeof value === 'string' || value === undefined) {\n return { styles, value };\n }\n\n if ('useType' in value) {\n return { styles, value: rel.type };\n }\n\n if ('property' in value) {\n const prop = rel.properties[value.property];\n if (prop === undefined) {\n return { styles, value: '' };\n }\n\n const resolvedValue =\n prop.type === 'string'\n ? prop.stringified.slice(1, -1)\n : prop.stringified;\n\n return { styles, value: resolvedValue };\n }\n\n return { styles, value: rel.id };\n }),\n };\n}\n\n/**\n * Safely compare two values using Cypher's three-valued logic.\n * Returns `null` if either operand is null (matching Cypher semantics).\n *\n * From Neo4j Cypher Manual:\n * \"Comparing any value to `null` using `=` or `<>` results in `null`\"\n *\n * @see https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/\n */\nfunction safeCompare(\n left: Value,\n right: Value,\n graphItem: NodeData | RelData,\n compareFn: (\n a: NonNullable<CypherValue>,\n b: NonNullable<CypherValue>,\n ) => boolean,\n): Ternary {\n const leftVal = evaluateValue(left, graphItem);\n const rightVal = evaluateValue(right, graphItem);\n if (leftVal === null || rightVal === null) {\n return null;\n }\n return compareFn(leftVal, rightVal);\n}\n\n/**\n * String comparison using Cypher semantics.\n * Returns `null` if either operand is null OR not a string.\n *\n * From Neo4j Cypher Manual:\n * \"When these operators are applied to non-string values, they return `null`\n * instead of attempting type coercion.\"\n *\n * @see https://neo4j.com/docs/cypher-manual/current/expressions/predicates/string-operators/\n */\nfunction safeStringCompare(\n left: Value,\n right: Value,\n graphItem: NodeData | RelData,\n compareFn: (a: string, b: string) => boolean,\n): Ternary {\n const leftVal = evaluateValue(left, graphItem);\n const rightVal = evaluateValue(right, graphItem);\n // Return null if either is null or not a string (no type coercion)\n if (\n leftVal === null ||\n rightVal === null ||\n typeof leftVal !== 'string' ||\n typeof rightVal !== 'string'\n ) {\n return null;\n }\n return compareFn(leftVal, rightVal);\n}\n\nfunction evaluateValue(\n value: Value,\n graphItem: NodeData | RelData,\n): CypherValue {\n if (typeof value === 'object' && value !== null) {\n if ('property' in value) {\n if (value.property === null) {\n return null;\n }\n const prop = graphItem.properties[value.property];\n if (prop === undefined) {\n return null;\n }\n return extractPropertyValue(prop);\n }\n if ('label' in value) {\n if ('labelsSorted' in graphItem) {\n return (\n value.label === null || graphItem.labelsSorted.includes(value.label)\n );\n }\n return false;\n }\n if ('reltype' in value) {\n if ('type' in graphItem) {\n return (\n value.reltype === null ||\n (graphItem as RelData).type === value.reltype\n );\n }\n return false;\n }\n }\n\n return value;\n}\n\n// Default caption selection logic\nconst captionPriorityOrder = [\n /^name$/i,\n /^title$/i,\n /^label$/i,\n /name$/i,\n /description$/i,\n /^.+/,\n];\n\nfunction getDefaultNodeCaption(node: NodeData): Caption {\n const propertyKeys = Object.keys(node.properties);\n\n for (const regex of captionPriorityOrder) {\n const matchingKey = propertyKeys.find((key) => regex.test(key));\n if (matchingKey !== undefined) {\n const prop = node.properties[matchingKey];\n if (prop !== undefined) {\n return { value: { property: matchingKey } };\n }\n }\n }\n\n if (node.labelsSorted[0] !== undefined) {\n return { value: { useType: true } };\n }\n\n return { value: { property: node.id } };\n}\n\nfunction evaluateNodeStyle(\n apply: Style,\n node: NodeData,\n): EvaluatedNvlNodeStyle {\n return {\n ...apply,\n captions: (apply.captions ?? [getDefaultNodeCaption(node)]).map(\n (caption) => {\n const { value, styles } = caption;\n\n if (typeof value === 'string' || value === undefined) {\n return { styles, value };\n }\n\n if ('useType' in value) {\n return { styles, value: node.labelsSorted[0] };\n }\n\n if ('property' in value) {\n const prop = node.properties[value.property];\n if (prop === undefined) {\n return { styles, value: '' };\n }\n\n const resolvedValue =\n prop.type === 'string'\n ? prop.stringified.slice(1, -1)\n : prop.stringified;\n\n return { styles, value: resolvedValue };\n }\n\n return { styles, value: node.labelsSorted[0] };\n },\n ),\n };\n}\n\nfunction createRelStyleFunction(\n rules: StyleRule[],\n): (rel: RelData) => EvaluatedNvlRelationshipStyle {\n return (rel: RelData) => {\n const style: Style = {};\n\n // evaluateWhere uses Cypher-style three-valued logic (true/false/null).\n // Only `true` passes; both `false` and `null` (unknown) skip the rule.\n for (const rule of rules) {\n // Check if the rule applies to this relationship\n if ('reltype' in rule.match) {\n const isReltypeMatch =\n rule.match.reltype === null || rel.type === rule.match.reltype;\n\n if (isReltypeMatch && evaluateWhere(rel, rule.where) === true) {\n // Merge the style properties (later rules override earlier ones)\n Object.assign(style, rule.apply);\n }\n }\n }\n\n return evaluateRelStyle(style, rel);\n };\n}\n\nexport type CompiledStyleRules = {\n byType: Map<string, (rel: RelData) => EvaluatedNvlRelationshipStyle>;\n styleMatchers: StyleMatchers;\n byLabelSet: (node: NodeData) => EvaluatedNvlNodeStyle;\n};\n\nexport const DEFAULT_REL_COLOR = tokens.palette.neutral['40'];\nconst NO_LABEL_FALLBACK_COLOR = tokens.palette.neutral['40'];\n\nfunction createByLabelSetFunction(\n styleMatchers: StyleMatchers,\n): (node: NodeData) => EvaluatedNvlNodeStyle {\n // Memoize default color by label string (idea 2: avoid recalculating per node)\n const defaultColorCache = new Map<string, string>();\n // Cache sorted rules by label set key (idea 1: avoid allocation + sort per node)\n const sortedRulesCache = new Map<string, StyleRule[]>();\n // Cache merged Style for where-free rule sets (idea 3: skip rule iteration entirely)\n const mergedStyleCache = new Map<string, Style>();\n\n return (node: NodeData) => {\n const labelSetKey = node.labelsSorted.join('\\0');\n\n // Fast path: if all rules for this label set are where-free,\n // the merged style is identical for every node with the same labels.\n const cachedStyle = mergedStyleCache.get(labelSetKey);\n if (cachedStyle !== undefined) {\n return evaluateNodeStyle(cachedStyle, node);\n }\n\n // Memoize default color by first label\n const labelForDefaultColor = node.labelsSorted[0] ?? null;\n let defaultColor: string;\n if (labelForDefaultColor === null) {\n defaultColor = NO_LABEL_FALLBACK_COLOR;\n } else {\n let cached = defaultColorCache.get(labelForDefaultColor);\n if (cached === undefined) {\n cached =\n calculateDefaultNodeColors(labelForDefaultColor).backgroundColor;\n defaultColorCache.set(labelForDefaultColor, cached);\n }\n defaultColor = cached;\n }\n\n // Cache sorted rules by label set (avoids repeated allocation + sort)\n let sortedRules = sortedRulesCache.get(labelSetKey);\n if (sortedRules === undefined) {\n const matchingRules: StyleRule[] = [...styleMatchers.globalLabelRules];\n for (const label of node.labelsSorted) {\n const labelRules = styleMatchers.rulesByLabel.get(label);\n if (labelRules) {\n matchingRules.push(...labelRules);\n }\n }\n sortedRules = matchingRules.toSorted(compareByPriorityAscending);\n sortedRulesCache.set(labelSetKey, sortedRules);\n }\n\n // Evaluate rules, tracking whether all are where-free\n const collectStyles: Style = { color: defaultColor };\n let isAllWhereFree = true;\n for (const rule of sortedRules) {\n if (rule.where !== undefined) {\n isAllWhereFree = false;\n if (evaluateWhere(node, rule.where) === true) {\n Object.assign(collectStyles, rule.apply);\n }\n } else {\n Object.assign(collectStyles, rule.apply);\n }\n }\n\n // When all rules are where-free, the merged style depends only on labels.\n // Cache it so subsequent nodes with the same label set skip rule iteration.\n if (isAllWhereFree) {\n mergedStyleCache.set(labelSetKey, collectStyles);\n }\n\n return evaluateNodeStyle(collectStyles, node);\n };\n}\n\nconst DEFAULT_REL_STYLE: StyleRule = {\n match: { reltype: null },\n apply: {\n color: DEFAULT_REL_COLOR,\n captions: [{ value: { useType: true } }],\n },\n};\n\nexport function compileStyleRules({\n reltypes,\n styleRules,\n}: {\n reltypes: string[];\n styleRules: StyleRule[] | null | undefined;\n}): CompiledStyleRules {\n const styleMatchers = collectStyleMatchers(styleRules);\n // as relationships can only have one type, we can use a map to store the style function for each type\n const byType = new Map<\n string,\n (rel: RelData) => EvaluatedNvlRelationshipStyle\n >();\n\n // Compile relationship type-based rules\n for (const reltype of reltypes) {\n const rules = styleMatchers.rulesByType.get(reltype) ?? [];\n byType.set(\n reltype,\n createRelStyleFunction([\n DEFAULT_REL_STYLE,\n ...styleMatchers.globalReltypeRules,\n ...rules,\n ]),\n );\n }\n\n const byLabelSet = createByLabelSetFunction(styleMatchers);\n\n return { byLabelSet, byType, styleMatchers };\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) "Neo4j"
|
|
5
|
+
* Neo4j Sweden AB [http://neo4j.com]
|
|
6
|
+
*
|
|
7
|
+
* This file is part of Neo4j.
|
|
8
|
+
*
|
|
9
|
+
* Neo4j is free software: you can redistribute it and/or modify
|
|
10
|
+
* it under the terms of the GNU General Public License as published by
|
|
11
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
* (at your option) any later version.
|
|
13
|
+
*
|
|
14
|
+
* This program is distributed in the hope that it will be useful,
|
|
15
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
* GNU General Public License for more details.
|
|
18
|
+
*
|
|
19
|
+
* You should have received a copy of the GNU General Public License
|
|
20
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
*/
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.parseStyleRules = parseStyleRules;
|
|
27
|
+
const zod_1 = __importDefault(require("zod"));
|
|
28
|
+
const style_types_1 = require("./style-types");
|
|
29
|
+
const StyleRulesArraySchema = zod_1.default.array(style_types_1.StyleRuleSchema);
|
|
30
|
+
/**
|
|
31
|
+
* Parse and validate a JSON string as an array of StyleRules.
|
|
32
|
+
*
|
|
33
|
+
* Useful for importing style rules from JSON. Uses Zod for runtime validation and returns
|
|
34
|
+
* a discriminated result:
|
|
35
|
+
*
|
|
36
|
+
* - On success: `{ success: true, data: StyleRule[] }`
|
|
37
|
+
* - On failure: `{ success: false, error: ZodError }`
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const result = parseStyleRules('[{"match": {"label": "Person"}, "apply": {"color": "red"}}]');
|
|
42
|
+
* if (result.success) {
|
|
43
|
+
* console.log(result.data); // StyleRule[]
|
|
44
|
+
* } else {
|
|
45
|
+
* console.error(result.error.issues);
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
function parseStyleRules(jsonString) {
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(jsonString);
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
return {
|
|
56
|
+
error: new zod_1.default.ZodError([
|
|
57
|
+
{
|
|
58
|
+
code: 'invalid_format',
|
|
59
|
+
message: 'Invalid JSON string',
|
|
60
|
+
path: [],
|
|
61
|
+
format: 'json',
|
|
62
|
+
},
|
|
63
|
+
]),
|
|
64
|
+
success: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return StyleRulesArraySchema.safeParse(parsed);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=parse-style-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-style-rules.js","sourceRoot":"","sources":["../../../src/styling/parse-style-rules.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;;;AA0BH,0CAuBC;AA/CD,8CAAoB;AAEpB,+CAAgE;AAChE,MAAM,qBAAqB,GAAG,aAAC,CAAC,KAAK,CAAC,6BAAe,CAAC,CAAC;AAEvD;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,eAAe,CAC7B,UAAkB;IAIlB,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAAC,WAAM,CAAC;QACP,OAAO;YACL,KAAK,EAAE,IAAI,aAAC,CAAC,QAAQ,CAAC;gBACpB;oBACE,IAAI,EAAE,gBAAgB;oBACtB,OAAO,EAAE,qBAAqB;oBAC9B,IAAI,EAAE,EAAE;oBACR,MAAM,EAAE,MAAM;iBACf;aACF,CAAC;YACF,OAAO,EAAE,KAAK;SACf,CAAC;IACJ,CAAC;IAED,OAAO,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport z from 'zod';\n\nimport { type StyleRule, StyleRuleSchema } from './style-types';\nconst StyleRulesArraySchema = z.array(StyleRuleSchema);\n\n/**\n * Parse and validate a JSON string as an array of StyleRules.\n *\n * Useful for importing style rules from JSON. Uses Zod for runtime validation and returns\n * a discriminated result:\n *\n * - On success: `{ success: true, data: StyleRule[] }`\n * - On failure: `{ success: false, error: ZodError }`\n *\n * @example\n * ```ts\n * const result = parseStyleRules('[{\"match\": {\"label\": \"Person\"}, \"apply\": {\"color\": \"red\"}}]');\n * if (result.success) {\n * console.log(result.data); // StyleRule[]\n * } else {\n * console.error(result.error.issues);\n * }\n * ```\n */\nexport function parseStyleRules(\n jsonString: string,\n):\n | { data: StyleRule[]; success: true }\n | { error: z.ZodError; success: false } {\n let parsed: unknown;\n try {\n parsed = JSON.parse(jsonString);\n } catch {\n return {\n error: new z.ZodError([\n {\n code: 'invalid_format',\n message: 'Invalid JSON string',\n path: [],\n format: 'json',\n },\n ]),\n success: false,\n };\n }\n\n return StyleRulesArraySchema.safeParse(parsed);\n}\n"]}
|