@graph-knowledge/api 0.3.2 → 0.9.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 (46) hide show
  1. package/package.json +1 -1
  2. package/src/index.d.ts +3 -2
  3. package/src/lib/clients/firebase-auth-client.d.ts +7 -0
  4. package/src/lib/clients/firebase-auth-client.js +20 -2
  5. package/src/lib/clients/firebase-firestore-client.js +83 -29
  6. package/src/lib/constants/element-defaults.js +1 -0
  7. package/src/lib/graph-knowledge-api.d.ts +21 -2
  8. package/src/lib/graph-knowledge-api.js +35 -5
  9. package/src/lib/interfaces/auth-client.interface.d.ts +10 -0
  10. package/src/lib/interfaces/custom-shape-operations.interface.d.ts +82 -0
  11. package/src/lib/interfaces/custom-shape-operations.interface.js +2 -0
  12. package/src/lib/interfaces/node-operations.interface.d.ts +29 -1
  13. package/src/lib/interfaces/template-operations.interface.d.ts +22 -1
  14. package/src/lib/operations/batch-operations.d.ts +3 -15
  15. package/src/lib/operations/batch-operations.js +8 -80
  16. package/src/lib/operations/custom-shape-operations.d.ts +27 -0
  17. package/src/lib/operations/custom-shape-operations.js +175 -0
  18. package/src/lib/operations/document-operations.js +9 -2
  19. package/src/lib/operations/element-operations.d.ts +3 -22
  20. package/src/lib/operations/element-operations.js +6 -119
  21. package/src/lib/operations/node-operations.d.ts +9 -3
  22. package/src/lib/operations/node-operations.js +18 -5
  23. package/src/lib/operations/template-operations.d.ts +3 -1
  24. package/src/lib/operations/template-operations.js +50 -7
  25. package/src/lib/testing/mock-auth-client.d.ts +2 -0
  26. package/src/lib/testing/mock-auth-client.js +7 -0
  27. package/src/lib/types/api-types.d.ts +113 -2
  28. package/src/lib/types/element-input-types.d.ts +67 -11
  29. package/src/lib/utils/element-builder.d.ts +63 -0
  30. package/src/lib/utils/element-builder.js +258 -0
  31. package/src/lib/utils/rotation.d.ts +4 -0
  32. package/src/lib/utils/rotation.js +13 -0
  33. package/src/lib/validators/custom-shape-definition-validator.d.ts +17 -0
  34. package/src/lib/validators/custom-shape-definition-validator.js +135 -0
  35. package/src/lib/validators/element-type-validators/base-element-validator.d.ts +4 -0
  36. package/src/lib/validators/element-type-validators/base-element-validator.js +30 -0
  37. package/src/lib/validators/element-type-validators/basic-shape-validators.js +2 -0
  38. package/src/lib/validators/element-type-validators/bezier-curve-validator.d.ts +12 -0
  39. package/src/lib/validators/element-type-validators/bezier-curve-validator.js +47 -0
  40. package/src/lib/validators/element-type-validators/block-arrow-validator.js +2 -0
  41. package/src/lib/validators/element-type-validators/line-validator.d.ts +1 -0
  42. package/src/lib/validators/element-type-validators/line-validator.js +12 -5
  43. package/src/lib/validators/element-type-validators/rectangle-validator.js +2 -0
  44. package/src/lib/validators/element-validator-registry.js +2 -0
  45. package/src/lib/validators/template-validator.d.ts +7 -1
  46. package/src/lib/validators/template-validator.js +21 -0
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ElementBuilder = void 0;
4
+ const uuid_1 = require("./uuid");
5
+ const api_errors_1 = require("../errors/api-errors");
6
+ const element_defaults_1 = require("../constants/element-defaults");
7
+ const text_measurement_service_1 = require("./text-measurement/text-measurement-service");
8
+ const rotation_1 = require("./rotation");
9
+ /**
10
+ * Builds GraphElement objects from AnyElementInput without persisting to Firestore.
11
+ *
12
+ * Handles: validation, default dimensions, centerX/centerY resolution,
13
+ * text auto-measurement, property building, custom shape enrichment.
14
+ *
15
+ * Shared by ElementOperations (single create) and NodeOperations (createWithElements).
16
+ */
17
+ class ElementBuilder {
18
+ validatorRegistry;
19
+ firestore;
20
+ auth;
21
+ static _textMeasurementService = null;
22
+ constructor(validatorRegistry, firestore, auth) {
23
+ this.validatorRegistry = validatorRegistry;
24
+ this.firestore = firestore;
25
+ this.auth = auth;
26
+ }
27
+ /**
28
+ * Builds multiple GraphElements from inputs.
29
+ * Pre-fetches custom shape definitions with deduplication for efficiency.
30
+ */
31
+ async buildMany(inputs) {
32
+ // Pre-fetch custom shape definitions (one fetch per unique shape)
33
+ const shapeCache = await this._prefetchCustomShapes(inputs);
34
+ const elements = [];
35
+ for (const input of inputs) {
36
+ elements.push(await this.build(input, shapeCache));
37
+ }
38
+ return elements;
39
+ }
40
+ /**
41
+ * Builds a GraphElement from input.
42
+ * Validates, resolves defaults and center positioning, builds properties,
43
+ * enriches custom shapes. Does NOT write to Firestore.
44
+ */
45
+ async build(input, shapeCache) {
46
+ // Custom shapes require premium
47
+ if (input.type.startsWith("custom:")) {
48
+ await this.auth.requirePremium();
49
+ }
50
+ // Validate centerX/centerY conflicts before anything else
51
+ this.validateCenterPosition(input);
52
+ // Resolve default dimensions
53
+ const defaults = this.getDefaultDimensions(input.type);
54
+ // Resolve text width
55
+ let resolvedWidth = input.width;
56
+ if (input.type === "text" && resolvedWidth === undefined) {
57
+ const textInput = input;
58
+ if (textInput.maxWidth !== undefined) {
59
+ resolvedWidth = textInput.maxWidth;
60
+ }
61
+ else {
62
+ resolvedWidth = this.measureTextWidth(textInput);
63
+ }
64
+ }
65
+ const width = resolvedWidth ?? defaults.width;
66
+ const height = input.height ?? defaults.height;
67
+ // Resolve centerX/centerY → x/y (after defaults, since center depends on dimensions)
68
+ const { x, y } = this.resolvePosition(input, width, height);
69
+ // Create a resolved copy of the input with x/y set for validation
70
+ const resolvedInput = { ...input, x, y };
71
+ // Remove center fields so validators don't see unknown properties
72
+ delete resolvedInput["centerX"];
73
+ delete resolvedInput["centerY"];
74
+ // Validate the resolved input (validators expect x/y to be numbers)
75
+ this.validatorRegistry.validate(resolvedInput);
76
+ let properties = this.buildProperties(input);
77
+ // For custom shapes, bake shape definition into properties
78
+ if (input.type.startsWith("custom:")) {
79
+ properties = await this._enrichCustomShapeProperties(properties, shapeCache);
80
+ }
81
+ const element = {
82
+ id: (0, uuid_1.generateUuid)(),
83
+ elementType: input.type,
84
+ x,
85
+ y,
86
+ width,
87
+ height,
88
+ rotation: (0, rotation_1.normalizeRotation)(input.rotation ?? 0),
89
+ properties
90
+ };
91
+ // Only add link properties if they have values (Firestore rejects undefined)
92
+ if (input.isLink !== undefined) {
93
+ element.isLink = input.isLink;
94
+ }
95
+ if (input.linkTarget !== undefined) {
96
+ element.linkTarget = input.linkTarget;
97
+ }
98
+ return element;
99
+ }
100
+ /**
101
+ * Validates that centerX/centerY are not used together with x/y.
102
+ */
103
+ validateCenterPosition(input) {
104
+ // ConnectorInput doesn't support center positioning
105
+ if (input.type === "connector") {
106
+ return;
107
+ }
108
+ const record = input;
109
+ const hasCenterX = record["centerX"] !== undefined;
110
+ const hasCenterY = record["centerY"] !== undefined;
111
+ if (hasCenterX && input.x !== undefined) {
112
+ throw new api_errors_1.ValidationError("Cannot specify both x and centerX", "centerX");
113
+ }
114
+ if (hasCenterY && input.y !== undefined) {
115
+ throw new api_errors_1.ValidationError("Cannot specify both y and centerY", "centerY");
116
+ }
117
+ // Validate types if provided
118
+ if (hasCenterX) {
119
+ const centerX = record["centerX"];
120
+ if (typeof centerX !== "number" || isNaN(centerX)) {
121
+ throw new api_errors_1.ValidationError("centerX must be a valid number", "centerX");
122
+ }
123
+ }
124
+ if (hasCenterY) {
125
+ const centerY = record["centerY"];
126
+ if (typeof centerY !== "number" || isNaN(centerY)) {
127
+ throw new api_errors_1.ValidationError("centerY must be a valid number", "centerY");
128
+ }
129
+ }
130
+ }
131
+ /**
132
+ * Resolves x/y position, handling centerX/centerY conversion.
133
+ */
134
+ resolvePosition(input, width, height) {
135
+ const record = input;
136
+ const centerX = record["centerX"];
137
+ const centerY = record["centerY"];
138
+ return {
139
+ x: centerX !== undefined
140
+ ? centerX - width / 2
141
+ : (input.x ?? 0),
142
+ y: centerY !== undefined
143
+ ? centerY - height / 2
144
+ : (input.y ?? 0)
145
+ };
146
+ }
147
+ /**
148
+ * Gets default dimensions for an element type.
149
+ */
150
+ getDefaultDimensions(type) {
151
+ if (type.startsWith("custom:")) {
152
+ return element_defaults_1.DEFAULT_CUSTOM_SHAPE_DIMENSIONS;
153
+ }
154
+ return (element_defaults_1.DEFAULT_ELEMENT_DIMENSIONS[type] ?? element_defaults_1.DEFAULT_CUSTOM_SHAPE_DIMENSIONS);
155
+ }
156
+ /**
157
+ * Auto-measures text width for text elements created without explicit width/maxWidth.
158
+ */
159
+ measureTextWidth(input) {
160
+ if (!ElementBuilder._textMeasurementService) {
161
+ ElementBuilder._textMeasurementService =
162
+ new text_measurement_service_1.TextMeasurementService();
163
+ }
164
+ const metrics = ElementBuilder._textMeasurementService.measureText(input.text, {
165
+ fontSize: input.fontSize,
166
+ fontFamily: input.fontFamily,
167
+ fontWeight: input.fontWeight,
168
+ textAlign: input.textAlign,
169
+ lineHeight: input.lineHeight
170
+ });
171
+ return Math.ceil(metrics.width);
172
+ }
173
+ /**
174
+ * Pre-fetches custom shape definitions from the shape store.
175
+ * Each unique shape is fetched only once for efficiency.
176
+ */
177
+ async _prefetchCustomShapes(inputs) {
178
+ const shapeIds = new Set();
179
+ for (const input of inputs) {
180
+ if (input.type.startsWith("custom:")) {
181
+ const shapeId = input["customShapeId"];
182
+ if (shapeId) {
183
+ shapeIds.add(shapeId);
184
+ }
185
+ }
186
+ }
187
+ const cache = new Map();
188
+ await Promise.all(Array.from(shapeIds).map(async (shapeId) => {
189
+ const shape = await this.firestore.getDocument(`shapeStore/${shapeId}`);
190
+ if (shape) {
191
+ cache.set(shapeId, shape);
192
+ }
193
+ }));
194
+ return cache;
195
+ }
196
+ /**
197
+ * Enriches custom shape element properties with shape definition data
198
+ * from the published shape store or a pre-fetched cache.
199
+ */
200
+ async _enrichCustomShapeProperties(properties, shapeCache) {
201
+ const shapeId = properties["customShapeId"];
202
+ if (!shapeId) {
203
+ return properties;
204
+ }
205
+ // Skip if already has svgPathData (caller provided it explicitly)
206
+ if (properties["svgPathData"]) {
207
+ return properties;
208
+ }
209
+ // Use cache if available, otherwise fetch directly
210
+ const shape = shapeCache
211
+ ? shapeCache.get(shapeId) ?? null
212
+ : await this.firestore.getDocument(`shapeStore/${shapeId}`);
213
+ if (!shape) {
214
+ return properties;
215
+ }
216
+ return {
217
+ ...properties,
218
+ svgPathData: shape.svgPathData,
219
+ viewBox: shape.viewBox,
220
+ aspectRatioLocked: shape.aspectRatioLocked ?? false
221
+ };
222
+ }
223
+ /**
224
+ * Builds properties object from element input.
225
+ * Extracts type-specific properties, excluding common/positional fields.
226
+ */
227
+ buildProperties(input) {
228
+ const props = {
229
+ __schemaVersion: 1
230
+ };
231
+ // Exclude common fields, positional aliases, and convenience aliases
232
+ const commonFields = new Set([
233
+ "type",
234
+ "x",
235
+ "y",
236
+ "centerX",
237
+ "centerY",
238
+ "width",
239
+ "height",
240
+ "rotation",
241
+ "isLink",
242
+ "linkTarget",
243
+ "maxWidth"
244
+ ]);
245
+ for (const [key, value] of Object.entries(input)) {
246
+ if (!commonFields.has(key) && value !== undefined) {
247
+ props[key] = value;
248
+ }
249
+ }
250
+ // For text elements, set __usesTopLeft: true so the app knows
251
+ // that x/y is the bounding box top-left, not the text anchor point.
252
+ if (input.type === "text") {
253
+ props["__usesTopLeft"] = true;
254
+ }
255
+ return props;
256
+ }
257
+ }
258
+ exports.ElementBuilder = ElementBuilder;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Normalizes rotation to integer in range [0, 360).
3
+ */
4
+ export declare function normalizeRotation(rotation: number): number;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeRotation = normalizeRotation;
4
+ /**
5
+ * Normalizes rotation to integer in range [0, 360).
6
+ */
7
+ function normalizeRotation(rotation) {
8
+ let normalized = rotation % 360;
9
+ if (normalized < 0) {
10
+ normalized += 360;
11
+ }
12
+ return Math.round(normalized);
13
+ }
@@ -0,0 +1,17 @@
1
+ import { CreateCustomShapeInput, UpdateCustomShapeInput } from "../types/api-types";
2
+ /**
3
+ * Validator for custom shape operations.
4
+ */
5
+ export declare class CustomShapeDefinitionValidator {
6
+ /**
7
+ * Validates input for custom shape creation.
8
+ */
9
+ validateCreate(input: CreateCustomShapeInput): void;
10
+ /**
11
+ * Validates input for custom shape update.
12
+ */
13
+ validateUpdate(input: UpdateCustomShapeInput): void;
14
+ private _validateViewBox;
15
+ private _validateDimension;
16
+ private _validateDefaultProperties;
17
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CustomShapeDefinitionValidator = void 0;
4
+ const api_errors_1 = require("../errors/api-errors");
5
+ /**
6
+ * Validator for custom shape operations.
7
+ */
8
+ class CustomShapeDefinitionValidator {
9
+ /**
10
+ * Validates input for custom shape creation.
11
+ */
12
+ validateCreate(input) {
13
+ if (!input.name || typeof input.name !== "string") {
14
+ throw new api_errors_1.ValidationError("name is required", "name");
15
+ }
16
+ if (input.name.trim().length === 0) {
17
+ throw new api_errors_1.ValidationError("name cannot be empty", "name");
18
+ }
19
+ if (input.name.length > 100) {
20
+ throw new api_errors_1.ValidationError("name must be 100 characters or less", "name");
21
+ }
22
+ if (!input.svgPathData || typeof input.svgPathData !== "string") {
23
+ throw new api_errors_1.ValidationError("svgPathData is required", "svgPathData");
24
+ }
25
+ if (input.svgPathData.trim().length === 0) {
26
+ throw new api_errors_1.ValidationError("svgPathData cannot be empty", "svgPathData");
27
+ }
28
+ if (!input.viewBox) {
29
+ throw new api_errors_1.ValidationError("viewBox is required", "viewBox");
30
+ }
31
+ this._validateViewBox(input.viewBox);
32
+ if (input.description !== undefined && typeof input.description !== "string") {
33
+ throw new api_errors_1.ValidationError("description must be a string", "description");
34
+ }
35
+ if (input.icon !== undefined && typeof input.icon !== "string") {
36
+ throw new api_errors_1.ValidationError("icon must be a string", "icon");
37
+ }
38
+ if (input.category !== undefined && typeof input.category !== "string") {
39
+ throw new api_errors_1.ValidationError("category must be a string", "category");
40
+ }
41
+ this._validateDimension(input.defaultWidth, "defaultWidth");
42
+ this._validateDimension(input.defaultHeight, "defaultHeight");
43
+ if (input.aspectRatioLocked !== undefined && typeof input.aspectRatioLocked !== "boolean") {
44
+ throw new api_errors_1.ValidationError("aspectRatioLocked must be a boolean", "aspectRatioLocked");
45
+ }
46
+ if (input.defaultProperties !== undefined) {
47
+ this._validateDefaultProperties(input.defaultProperties);
48
+ }
49
+ }
50
+ /**
51
+ * Validates input for custom shape update.
52
+ */
53
+ validateUpdate(input) {
54
+ if (input.name !== undefined) {
55
+ if (typeof input.name !== "string") {
56
+ throw new api_errors_1.ValidationError("name must be a string", "name");
57
+ }
58
+ if (input.name.trim().length === 0) {
59
+ throw new api_errors_1.ValidationError("name cannot be empty", "name");
60
+ }
61
+ if (input.name.length > 100) {
62
+ throw new api_errors_1.ValidationError("name must be 100 characters or less", "name");
63
+ }
64
+ }
65
+ if (input.svgPathData !== undefined) {
66
+ if (typeof input.svgPathData !== "string") {
67
+ throw new api_errors_1.ValidationError("svgPathData must be a string", "svgPathData");
68
+ }
69
+ if (input.svgPathData.trim().length === 0) {
70
+ throw new api_errors_1.ValidationError("svgPathData cannot be empty", "svgPathData");
71
+ }
72
+ }
73
+ if (input.viewBox !== undefined) {
74
+ this._validateViewBox(input.viewBox);
75
+ }
76
+ if (input.description !== undefined && typeof input.description !== "string") {
77
+ throw new api_errors_1.ValidationError("description must be a string", "description");
78
+ }
79
+ if (input.icon !== undefined && typeof input.icon !== "string") {
80
+ throw new api_errors_1.ValidationError("icon must be a string", "icon");
81
+ }
82
+ if (input.category !== undefined && typeof input.category !== "string") {
83
+ throw new api_errors_1.ValidationError("category must be a string", "category");
84
+ }
85
+ this._validateDimension(input.defaultWidth, "defaultWidth");
86
+ this._validateDimension(input.defaultHeight, "defaultHeight");
87
+ if (input.aspectRatioLocked !== undefined && typeof input.aspectRatioLocked !== "boolean") {
88
+ throw new api_errors_1.ValidationError("aspectRatioLocked must be a boolean", "aspectRatioLocked");
89
+ }
90
+ if (input.defaultProperties !== undefined) {
91
+ this._validateDefaultProperties(input.defaultProperties);
92
+ }
93
+ }
94
+ _validateViewBox(viewBox) {
95
+ if (typeof viewBox.width !== "number" || viewBox.width <= 0) {
96
+ throw new api_errors_1.ValidationError("viewBox.width must be a positive number", "viewBox.width");
97
+ }
98
+ if (typeof viewBox.height !== "number" || viewBox.height <= 0) {
99
+ throw new api_errors_1.ValidationError("viewBox.height must be a positive number", "viewBox.height");
100
+ }
101
+ if (typeof viewBox.minX !== "number" || isNaN(viewBox.minX)) {
102
+ throw new api_errors_1.ValidationError("viewBox.minX must be a valid number", "viewBox.minX");
103
+ }
104
+ if (typeof viewBox.minY !== "number" || isNaN(viewBox.minY)) {
105
+ throw new api_errors_1.ValidationError("viewBox.minY must be a valid number", "viewBox.minY");
106
+ }
107
+ }
108
+ _validateDimension(value, field) {
109
+ if (value !== undefined) {
110
+ if (typeof value !== "number" || isNaN(value)) {
111
+ throw new api_errors_1.ValidationError(`${field} must be a valid number`, field);
112
+ }
113
+ if (value < 10 || value > 2000) {
114
+ throw new api_errors_1.ValidationError(`${field} must be between 10 and 2000`, field);
115
+ }
116
+ }
117
+ }
118
+ _validateDefaultProperties(props) {
119
+ if (props.fillColor !== undefined && typeof props.fillColor !== "string") {
120
+ throw new api_errors_1.ValidationError("defaultProperties.fillColor must be a string", "defaultProperties.fillColor");
121
+ }
122
+ if (props.strokeColor !== undefined && typeof props.strokeColor !== "string") {
123
+ throw new api_errors_1.ValidationError("defaultProperties.strokeColor must be a string", "defaultProperties.strokeColor");
124
+ }
125
+ if (props.strokeWidth !== undefined) {
126
+ if (typeof props.strokeWidth !== "number" || isNaN(props.strokeWidth)) {
127
+ throw new api_errors_1.ValidationError("defaultProperties.strokeWidth must be a valid number", "defaultProperties.strokeWidth");
128
+ }
129
+ if (props.strokeWidth < 0 || props.strokeWidth > 50) {
130
+ throw new api_errors_1.ValidationError("defaultProperties.strokeWidth must be between 0 and 50", "defaultProperties.strokeWidth");
131
+ }
132
+ }
133
+ }
134
+ }
135
+ exports.CustomShapeDefinitionValidator = CustomShapeDefinitionValidator;
@@ -32,4 +32,8 @@ export declare abstract class BaseElementValidator {
32
32
  * Validates stroke width.
33
33
  */
34
34
  protected validateStrokeWidth(value: unknown, field: string): void;
35
+ /**
36
+ * Validates optional text label properties shared by basic shapes.
37
+ */
38
+ protected validateTextLabelProperties(input: Record<string, unknown>): void;
35
39
  }
@@ -96,5 +96,35 @@ class BaseElementValidator {
96
96
  throw new api_errors_1.ValidationError(`${field} must be non-negative`, field);
97
97
  }
98
98
  }
99
+ /**
100
+ * Validates optional text label properties shared by basic shapes.
101
+ */
102
+ validateTextLabelProperties(input) {
103
+ if (input["text"] !== undefined && typeof input["text"] !== "string") {
104
+ throw new api_errors_1.ValidationError("text must be a string", "text");
105
+ }
106
+ if (input["fontSize"] !== undefined) {
107
+ const fs = input["fontSize"];
108
+ if (typeof fs !== "number" || isNaN(fs)) {
109
+ throw new api_errors_1.ValidationError("fontSize must be a valid number", "fontSize");
110
+ }
111
+ if (fs < 1 || fs > 200) {
112
+ throw new api_errors_1.ValidationError("fontSize must be between 1 and 200", "fontSize");
113
+ }
114
+ }
115
+ this.validateColor(input["textColor"], "textColor");
116
+ if (input["textAlign"] !== undefined) {
117
+ const valid = ["left", "center", "right"];
118
+ if (!valid.includes(input["textAlign"])) {
119
+ throw new api_errors_1.ValidationError(`textAlign must be one of: ${valid.join(", ")}`, "textAlign");
120
+ }
121
+ }
122
+ if (input["verticalAlign"] !== undefined) {
123
+ const valid = ["top", "middle", "bottom"];
124
+ if (!valid.includes(input["verticalAlign"])) {
125
+ throw new api_errors_1.ValidationError(`verticalAlign must be one of: ${valid.join(", ")}`, "verticalAlign");
126
+ }
127
+ }
128
+ }
99
129
  }
100
130
  exports.BaseElementValidator = BaseElementValidator;
@@ -19,6 +19,8 @@ class BasicShapeValidator extends base_element_validator_1.BaseElementValidator
19
19
  this.validateColor(shapeInput.fillColor, "fillColor");
20
20
  this.validateColor(shapeInput.strokeColor, "strokeColor");
21
21
  this.validateStrokeWidth(shapeInput.strokeWidth, "strokeWidth");
22
+ // Validate text label properties
23
+ this.validateTextLabelProperties(shapeInput);
22
24
  }
23
25
  }
24
26
  /**
@@ -0,0 +1,12 @@
1
+ import { IElementTypeValidator } from "../../interfaces/element-validator.interface";
2
+ import { AnyElementInput } from "../../types/element-input-types";
3
+ import { BaseElementValidator } from "./base-element-validator";
4
+ /**
5
+ * Validator for bezier curve elements.
6
+ */
7
+ export declare class BezierCurveValidator extends BaseElementValidator implements IElementTypeValidator {
8
+ readonly elementType = "bezier-curve";
9
+ validate(input: AnyElementInput): void;
10
+ private validateLineStyle;
11
+ private validateNumber;
12
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BezierCurveValidator = void 0;
4
+ const api_errors_1 = require("../../errors/api-errors");
5
+ const base_element_validator_1 = require("./base-element-validator");
6
+ /**
7
+ * Validator for bezier curve elements.
8
+ */
9
+ class BezierCurveValidator extends base_element_validator_1.BaseElementValidator {
10
+ elementType = "bezier-curve";
11
+ validate(input) {
12
+ if (input.type !== "bezier-curve") {
13
+ throw new api_errors_1.ValidationError(`Expected type "bezier-curve", got "${input.type}"`, "type");
14
+ }
15
+ const curveInput = input;
16
+ // Validate common properties
17
+ this.validateCommon(curveInput);
18
+ // Validate stroke properties
19
+ this.validateColor(curveInput.strokeColor, "strokeColor");
20
+ this.validateStrokeWidth(curveInput.strokeWidth, "strokeWidth");
21
+ this.validateLineStyle(curveInput.lineStyle);
22
+ // Validate control point offsets (must be numbers if provided)
23
+ this.validateNumber(curveInput.controlPoint1X, "controlPoint1X");
24
+ this.validateNumber(curveInput.controlPoint1Y, "controlPoint1Y");
25
+ this.validateNumber(curveInput.controlPoint2X, "controlPoint2X");
26
+ this.validateNumber(curveInput.controlPoint2Y, "controlPoint2Y");
27
+ }
28
+ validateLineStyle(value) {
29
+ if (value === undefined)
30
+ return;
31
+ if (typeof value !== "string") {
32
+ throw new api_errors_1.ValidationError("lineStyle must be a string", "lineStyle");
33
+ }
34
+ const validStyles = ["solid", "dashed", "dotted"];
35
+ if (!validStyles.includes(value)) {
36
+ throw new api_errors_1.ValidationError(`lineStyle must be one of: ${validStyles.join(", ")}`, "lineStyle");
37
+ }
38
+ }
39
+ validateNumber(value, field) {
40
+ if (value === undefined)
41
+ return;
42
+ if (typeof value !== "number" || isNaN(value)) {
43
+ throw new api_errors_1.ValidationError(`${field} must be a valid number`, field);
44
+ }
45
+ }
46
+ }
47
+ exports.BezierCurveValidator = BezierCurveValidator;
@@ -20,6 +20,8 @@ class BlockArrowValidator extends base_element_validator_1.BaseElementValidator
20
20
  this.validateColor(blockArrowInput.strokeColor, "strokeColor");
21
21
  this.validateStrokeWidth(blockArrowInput.strokeWidth, "strokeWidth");
22
22
  this.validateDirection(blockArrowInput.direction);
23
+ // Validate text label properties
24
+ this.validateTextLabelProperties(blockArrowInput);
23
25
  }
24
26
  validateDirection(value) {
25
27
  if (value === undefined)
@@ -8,4 +8,5 @@ export declare class LineValidator extends BaseElementValidator implements IElem
8
8
  readonly elementType = "line";
9
9
  validate(input: AnyElementInput): void;
10
10
  private validateLineStyle;
11
+ private validateMarker;
11
12
  }
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LineValidator = void 0;
4
4
  const api_errors_1 = require("../../errors/api-errors");
5
5
  const base_element_validator_1 = require("./base-element-validator");
6
+ const VALID_LINE_STYLES = ["solid", "dashed", "dotted"];
7
+ const VALID_MARKERS = ["none", "arrow", "diamond", "circle"];
6
8
  /**
7
9
  * Validator for line elements.
8
10
  */
@@ -19,16 +21,21 @@ class LineValidator extends base_element_validator_1.BaseElementValidator {
19
21
  this.validateColor(lineInput.strokeColor, "strokeColor");
20
22
  this.validateStrokeWidth(lineInput.strokeWidth, "strokeWidth");
21
23
  this.validateLineStyle(lineInput.lineStyle);
24
+ this.validateMarker(lineInput.startMarker, "startMarker");
25
+ this.validateMarker(lineInput.endMarker, "endMarker");
22
26
  }
23
27
  validateLineStyle(value) {
24
28
  if (value === undefined)
25
29
  return;
26
- if (typeof value !== "string") {
27
- throw new api_errors_1.ValidationError("lineStyle must be a string", "lineStyle");
30
+ if (!VALID_LINE_STYLES.includes(value)) {
31
+ throw new api_errors_1.ValidationError(`lineStyle must be one of: ${VALID_LINE_STYLES.join(", ")}`, "lineStyle");
28
32
  }
29
- const validStyles = ["solid", "dashed", "dotted"];
30
- if (!validStyles.includes(value)) {
31
- throw new api_errors_1.ValidationError(`lineStyle must be one of: ${validStyles.join(", ")}`, "lineStyle");
33
+ }
34
+ validateMarker(value, field) {
35
+ if (value === undefined)
36
+ return;
37
+ if (!VALID_MARKERS.includes(value)) {
38
+ throw new api_errors_1.ValidationError(`${field} must be one of: ${VALID_MARKERS.join(", ")}`, field);
32
39
  }
33
40
  }
34
41
  }
@@ -20,6 +20,8 @@ class RectangleValidator extends base_element_validator_1.BaseElementValidator {
20
20
  this.validateColor(rectangleInput.strokeColor, "strokeColor");
21
21
  this.validateStrokeWidth(rectangleInput.strokeWidth, "strokeWidth");
22
22
  this.validateCornerRadius(rectangleInput.cornerRadius);
23
+ // Validate text label properties
24
+ this.validateTextLabelProperties(rectangleInput);
23
25
  }
24
26
  validateCornerRadius(value) {
25
27
  if (value === undefined)
@@ -8,6 +8,7 @@ const connector_validator_1 = require("./element-type-validators/connector-valid
8
8
  const uml_validators_1 = require("./element-type-validators/uml-validators");
9
9
  const basic_shape_validators_1 = require("./element-type-validators/basic-shape-validators");
10
10
  const line_validator_1 = require("./element-type-validators/line-validator");
11
+ const bezier_curve_validator_1 = require("./element-type-validators/bezier-curve-validator");
11
12
  const block_arrow_validator_1 = require("./element-type-validators/block-arrow-validator");
12
13
  const custom_shape_validator_1 = require("./element-type-validators/custom-shape-validator");
13
14
  /**
@@ -39,6 +40,7 @@ class ElementValidatorRegistry {
39
40
  this.register(new basic_shape_validators_1.HexagonValidator());
40
41
  this.register(new basic_shape_validators_1.EllipseValidator());
41
42
  this.register(new line_validator_1.LineValidator());
43
+ this.register(new bezier_curve_validator_1.BezierCurveValidator());
42
44
  this.register(new block_arrow_validator_1.BlockArrowValidator());
43
45
  }
44
46
  register(validator) {
@@ -1,4 +1,4 @@
1
- import { CreateTemplateInput } from "../types/api-types";
1
+ import { CreateTemplateInput, UpdateTemplateInput } from "../types/api-types";
2
2
  /**
3
3
  * Validator for template operations.
4
4
  */
@@ -9,6 +9,12 @@ export declare class TemplateValidator {
9
9
  * @throws ValidationError if validation fails
10
10
  */
11
11
  validateCreate(input: CreateTemplateInput): void;
12
+ /**
13
+ * Validates input for template update.
14
+ * @param input The update input to validate
15
+ * @throws ValidationError if validation fails
16
+ */
17
+ validateUpdate(input: UpdateTemplateInput): void;
12
18
  /**
13
19
  * Validates canvas dimensions.
14
20
  */