@stocksharp/diagram 0.2.2 → 0.3.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 (68) hide show
  1. package/README.md +12 -0
  2. package/dist/esm/canvas-renderer.js +28 -22
  3. package/dist/esm/canvas-renderer.js.map +1 -1
  4. package/dist/esm/core/action-registry.js +24 -0
  5. package/dist/esm/core/action-registry.js.map +1 -1
  6. package/dist/esm/core/document.js +85 -28
  7. package/dist/esm/core/document.js.map +1 -1
  8. package/dist/esm/core/json.js +17 -0
  9. package/dist/esm/core/json.js.map +1 -0
  10. package/dist/esm/core/model.js +4 -0
  11. package/dist/esm/core/model.js.map +1 -1
  12. package/dist/esm/core/state.js +16 -2
  13. package/dist/esm/core/state.js.map +1 -1
  14. package/dist/esm/diagram/catalog.js +17 -5
  15. package/dist/esm/diagram/catalog.js.map +1 -1
  16. package/dist/esm/diagram/event-emitter.js +10 -0
  17. package/dist/esm/diagram/event-emitter.js.map +1 -1
  18. package/dist/esm/diagram/stocksharp-diagram.js +69 -57
  19. package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
  20. package/dist/esm/diagram/types.js +2 -1
  21. package/dist/esm/diagram/types.js.map +1 -1
  22. package/dist/esm/embed.js +129 -39
  23. package/dist/esm/embed.js.map +1 -1
  24. package/dist/esm/index.js +1 -1
  25. package/dist/esm/index.js.map +1 -1
  26. package/dist/ssdiagram.js +314 -147
  27. package/dist/ssdiagram.js.map +4 -4
  28. package/dist/types/canvas-renderer.d.ts +3 -5
  29. package/dist/types/canvas-renderer.d.ts.map +1 -1
  30. package/dist/types/core/action-registry.d.ts +7 -0
  31. package/dist/types/core/action-registry.d.ts.map +1 -1
  32. package/dist/types/core/document.d.ts.map +1 -1
  33. package/dist/types/core/json.d.ts +11 -0
  34. package/dist/types/core/json.d.ts.map +1 -0
  35. package/dist/types/core/model.d.ts +10 -1
  36. package/dist/types/core/model.d.ts.map +1 -1
  37. package/dist/types/core/state.d.ts +24 -1
  38. package/dist/types/core/state.d.ts.map +1 -1
  39. package/dist/types/diagram/api.d.ts +13 -6
  40. package/dist/types/diagram/api.d.ts.map +1 -1
  41. package/dist/types/diagram/catalog.d.ts +2 -1
  42. package/dist/types/diagram/catalog.d.ts.map +1 -1
  43. package/dist/types/diagram/event-emitter.d.ts +6 -1
  44. package/dist/types/diagram/event-emitter.d.ts.map +1 -1
  45. package/dist/types/diagram/palette.d.ts +1 -1
  46. package/dist/types/diagram/palette.d.ts.map +1 -1
  47. package/dist/types/diagram/stocksharp-diagram.d.ts +32 -21
  48. package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
  49. package/dist/types/diagram/types.d.ts +4 -4
  50. package/dist/types/diagram/types.d.ts.map +1 -1
  51. package/dist/types/embed.d.ts.map +1 -1
  52. package/dist/types/index.d.ts +3 -3
  53. package/dist/types/index.d.ts.map +1 -1
  54. package/package.json +2 -2
  55. package/src/canvas-renderer.ts +26 -24
  56. package/src/core/action-registry.ts +25 -0
  57. package/src/core/document.ts +91 -31
  58. package/src/core/json.ts +16 -0
  59. package/src/core/model.ts +14 -1
  60. package/src/core/state.ts +41 -3
  61. package/src/diagram/api.ts +14 -5
  62. package/src/diagram/catalog.ts +18 -6
  63. package/src/diagram/event-emitter.ts +12 -1
  64. package/src/diagram/palette.ts +1 -1
  65. package/src/diagram/stocksharp-diagram.ts +91 -75
  66. package/src/diagram/types.ts +6 -5
  67. package/src/embed.ts +149 -40
  68. package/src/index.ts +4 -0
@@ -1 +1 @@
1
- {"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../../src/embed.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGpE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AA0BnE,MAAM,WAAW,sBAAsB;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;CAAE;AAC3G,MAAM,WAAW,sBAAsB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CAAE;AACvG,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,KAAK,EAAE,sBAAsB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IACnC,qBAAqB,CAAC,EAAE,CACvB,OAAO,EAAE,0BAA0B,EACnC,MAAM,EAAE,kBAAkB,KACtB,IAAI,CAAC;IACV,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACnD;AAmKD,wBAAsB,YAAY,CACjC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAEpC;AAgJD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAGhE;AAyCD,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA4BpC;AAKD,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAyBpC;AAKD,wBAAgB,SAAS,CAAC,IAAI,GAAE,UAAqB,EAAE,OAAO,GAAE,mBAAwB,GAAG,IAAI,CAkB9F"}
1
+ {"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../../src/embed.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGpE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AA4BnE,MAAM,WAAW,sBAAsB;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;CAAE;AAC3G,MAAM,WAAW,sBAAsB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CAAE;AACvG,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,KAAK,EAAE,sBAAsB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IACnC,qBAAqB,CAAC,EAAE,CACvB,OAAO,EAAE,0BAA0B,EACnC,MAAM,EAAE,kBAAkB,KACtB,IAAI,CAAC;IACV,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACnD;AAgPD,wBAAsB,YAAY,CACjC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAEpC;AA6JD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAGhE;AA4DD,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2BpC;AAKD,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAwBpC;AAKD,wBAAgB,SAAS,CAAC,IAAI,GAAE,UAAqB,EAAE,OAAO,GAAE,mBAAwB,GAAG,IAAI,CAkB9F"}
@@ -4,11 +4,11 @@ export type { DiagramAction, DiagramActionState, } from './core/action-registry.
4
4
  export { DiagramCommandHistory } from './core/history.js';
5
5
  export type { DiagramCommand, DiagramHistoryListener, DiagramHistoryState, } from './core/history.js';
6
6
  export { DIAGRAM_DOCUMENT_VERSION } from './core/model.js';
7
- export type { DiagramDocument, DiagramDocumentEndpoint, DiagramDocumentInput, DiagramDocumentLink, DiagramDocumentLinkInput, DiagramDocumentNode, DiagramDocumentNodeInput, DiagramDocumentPort, DiagramDocumentPortInput, DiagramDocumentVersion, DiagramParameterSchema, JsonObject, JsonPrimitive, JsonValue, } from './core/model.js';
8
- export { createEditableDiagramPermissions, createDiagramNodeRuntimeState, createDiagramPortRuntimeState, createDiagramRuntimeState, cloneDiagramRuntimeState, createDiagramSelection, createDiagramViewState, createReadOnlyDiagramPermissions, } from './core/state.js';
7
+ export type { DiagramDocument, DiagramDocumentEndpoint, DiagramDocumentInput, DiagramDocumentLink, DiagramDocumentLinkInput, DiagramDocumentNode, DiagramDocumentNodeInput, DiagramDocumentPort, DiagramDocumentPortInput, DiagramDocumentVersion, DiagramParameterSchema, JsonObject, JsonPrimitive, JsonValue, PortDynamicMode, } from './core/model.js';
8
+ export { createEditableDiagramPermissions, createDiagramNodeRuntimeState, createDiagramPortRuntimeState, createDiagramRuntimeState, cloneDiagramNodeErrors, cloneDiagramRuntimeState, createDiagramSelection, createDiagramViewState, createReadOnlyDiagramPermissions, } from './core/state.js';
9
9
  export { DIAGRAM_VIEW_STATE_VERSION, DiagramViewStateError, createDiagramViewStateDocument, parseDiagramViewState, serializeDiagramViewState, } from './core/view-state.js';
10
10
  export type { DiagramViewStateDocument } from './core/view-state.js';
11
- export type { DiagramErrorState, DiagramGlobalErrorKind, DiagramInteractionPermissions, DiagramNodeErrorKind, DiagramNodePortRuntimeState, DiagramNodeRuntimeState, DiagramPortDirection, DiagramPortRuntimeState, DiagramRuntimeState, DiagramSelectedPort, DiagramSelection, DiagramViewState, } from './core/state.js';
11
+ export type { DiagramErrorState, DiagramGlobalErrorKind, DiagramInteractionPermissions, DiagramNodeErrorKind, DiagramNodeErrors, DiagramNodePortRuntimeState, DiagramNodeRuntimeState, DiagramPortDirection, DiagramPortRuntimeState, DiagramRuntimeState, DiagramSelectedPort, DiagramSelection, DiagramSnapshot, DiagramViewState, } from './core/state.js';
12
12
  export { StockSharpDiagram, } from './diagram/stocksharp-diagram.js';
13
13
  export type { ContextCommand, ContextCommandPayload, ContextCommandState, ContextMenuRequestedPayload, DiagramEvents, DiagramClipboard, DiagramGridSettings, DiagramLoadOptions, DiagramNodeBounds, DiagramOptions, DiagramPoint, DiagramScreenshotOptions, DiagramScreenshotScope, DiagramThemeOptions, DocumentLoadFailedPayload, LinkChangePayload, LinkHoverPayload, LinkRelinkedPayload, LinkSelectedPayload, LinkValidationPayload, LinkValidationReason, LinkValidationResult, LinkValidator, LinkValidatorArgs, LoadFinishedPayload, NodeChangePayload, NodeErrorKind, NodeErrorOptions, NodeHoverPayload, NodeMovedPayload, NodeSelectedPayload, PortHoverPayload, PortClickAction, PortClickedPayload, PortSelectedPayload, } from './diagram/api.js';
14
14
  export { StockSharpCatalog } from './diagram/catalog.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stocksharp/diagram",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Browser strategy-diagram editor for StockSharp with typed ports, palette, canvas rendering, persistence, and a TypeScript API.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
@@ -107,7 +107,7 @@
107
107
  "scripts": {
108
108
  "build": "node build.mjs",
109
109
  "prepack": "npm run build",
110
- "typecheck": "tsc --noEmit -p tsconfig.json",
110
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.demo.json && tsc --noEmit -p tsconfig.test.json",
111
111
  "api:check": "node tools/check-public-api.mjs",
112
112
  "api:update": "node tools/check-public-api.mjs --update",
113
113
  "serve": "node serve.mjs",
@@ -6,11 +6,14 @@
6
6
 
7
7
  import { createDiagramDocument, parseDiagramDocument } from './core/document.js';
8
8
  import { DiagramCommandHistory } from './core/history.js';
9
+ import { setJsonKey } from './core/json.js';
10
+ import { toPortDynamicMode } from './core/model.js';
9
11
  import type {
10
12
  DiagramDocument,
11
13
  DiagramParameterSchema,
12
14
  JsonObject,
13
15
  JsonValue,
16
+ PortDynamicMode,
14
17
  } from './core/model.js';
15
18
  import type {
16
19
  DiagramGlobalErrorKind,
@@ -40,7 +43,7 @@ export interface PortInit {
40
43
  maxLinks?: number;
41
44
  availableTypes?: string[];
42
45
  isDynamic?: boolean;
43
- dynamicMode?: string;
46
+ dynamicMode?: PortDynamicMode;
44
47
  isSibling?: boolean;
45
48
  metadata?: JsonObject;
46
49
  }
@@ -258,7 +261,7 @@ function copyJsonValue(value: JsonValue): JsonValue {
258
261
  function copyJsonObject(value: JsonObject | undefined): JsonObject {
259
262
  if (value === undefined) return {};
260
263
  const result: JsonObject = {};
261
- for (const [key, item] of Object.entries(value)) result[key] = copyJsonValue(item);
264
+ for (const [key, item] of Object.entries(value)) setJsonKey(result, key, copyJsonValue(item));
262
265
  return result;
263
266
  }
264
267
 
@@ -298,7 +301,7 @@ export class PortModel {
298
301
  maxLinks: number;
299
302
  availableTypes: string[];
300
303
  isDynamic: boolean;
301
- dynamicMode: string;
304
+ dynamicMode: PortDynamicMode;
302
305
  isSibling: boolean;
303
306
  metadata: JsonObject;
304
307
  // layout cache (world coords), filled at draw time
@@ -313,7 +316,7 @@ export class PortModel {
313
316
  this.maxLinks = typeof init.maxLinks === 'number' ? init.maxLinks : 0;
314
317
  this.availableTypes = [...(init.availableTypes ?? [])];
315
318
  this.isDynamic = init.isDynamic ?? false;
316
- this.dynamicMode = init.dynamicMode ?? '';
319
+ this.dynamicMode = toPortDynamicMode(init.dynamicMode);
317
320
  this.isSibling = init.isSibling ?? false;
318
321
  this.metadata = copyJsonObject(init.metadata);
319
322
  }
@@ -439,10 +442,8 @@ export class LinkModel {
439
442
  }
440
443
  }
441
444
 
442
- const HEADER_H = 22;
443
445
  const PORT_R = 6;
444
446
  const PORT_ROW_H = 20;
445
- const NODE_PAD = 10;
446
447
  const RELINK_HANDLE_PX = 8; // WPF relink adornment is an 8x8 diamond
447
448
  const RELINK_DRAG_THRESHOLD_PX = 4;
448
449
  const PORT_SQ = 9; // socket square size — sits outside the node
@@ -530,8 +531,6 @@ export class Diagram {
530
531
  private dragNode: NodeModel | null = null;
531
532
  private dragStart: Array<{ n: NodeModel; x: number; y: number }> = []; // group-drag origin
532
533
  private dragAnchor = { wx: 0, wy: 0 };
533
- private dragDX = 0;
534
- private dragDY = 0;
535
534
  private panning = false;
536
535
  private panX = 0;
537
536
  private panY = 0;
@@ -1121,7 +1120,7 @@ export class Diagram {
1121
1120
  setNodeParamValue(nodeId: string, name: string, value: string | undefined): boolean {
1122
1121
  return this.updateNodeState(nodeId, 'set node parameter', (node) => {
1123
1122
  if (value === undefined) delete node.paramValues[name];
1124
- else node.paramValues[name] = value;
1123
+ else setJsonKey(node.paramValues, name, value);
1125
1124
  });
1126
1125
  }
1127
1126
 
@@ -1207,7 +1206,7 @@ export class Diagram {
1207
1206
  for (const node of this.nodes) {
1208
1207
  if (node.loadError.length === 0) continue;
1209
1208
  const state = createDiagramNodeRuntimeState();
1210
- state.error = { kind: 'load', message: node.loadError, pulse: ++this.runtimePulse };
1209
+ state.errors.load = { kind: 'load', message: node.loadError, pulse: ++this.runtimePulse };
1211
1210
  this.runtimeState.nodes[node.id] = state;
1212
1211
  }
1213
1212
  if (Object.keys(this.runtimeState.nodes).length > 0) this.emitRuntimeStateChanged();
@@ -1418,15 +1417,17 @@ export class Diagram {
1418
1417
  this.runtimePulse = Math.max(
1419
1418
  this.runtimePulse,
1420
1419
  this.runtimeState.globalError?.pulse ?? 0,
1421
- ...Object.values(this.runtimeState.nodes).map((node) => node.error?.pulse ?? 0),
1420
+ ...Object.values(this.runtimeState.nodes)
1421
+ .flatMap((node) => [node.errors.runtime?.pulse ?? 0, node.errors.load?.pulse ?? 0]),
1422
1422
  );
1423
1423
  for (const node of this.nodes) {
1424
- const error = this.runtimeState.nodes[node.id]?.error ?? null;
1425
- const previousError = previous.nodes[node.id]?.error ?? null;
1426
- node.runtimeError = error?.kind === 'runtime' ? error.message : '';
1427
- node.loadError = error?.kind === 'load' ? error.message : '';
1428
- if (error?.kind === 'runtime') {
1429
- if (previousError?.kind !== 'runtime' || previousError.pulse !== error.pulse) {
1424
+ const errors = this.runtimeState.nodes[node.id]?.errors;
1425
+ const previousRuntime = previous.nodes[node.id]?.errors.runtime;
1426
+ const runtimeError = errors?.runtime;
1427
+ node.runtimeError = runtimeError?.message ?? '';
1428
+ node.loadError = errors?.load?.message ?? '';
1429
+ if (runtimeError !== undefined) {
1430
+ if (previousRuntime === undefined || previousRuntime.pulse !== runtimeError.pulse) {
1430
1431
  node.errorFlashStart = performance.now();
1431
1432
  }
1432
1433
  } else {
@@ -1486,7 +1487,11 @@ export class Diagram {
1486
1487
  const state = this.getRuntimeState();
1487
1488
  const nodeState = state.nodes[id] ?? createDiagramNodeRuntimeState();
1488
1489
  state.nodes[id] = nodeState;
1489
- nodeState.error = message.length === 0 ? null : { kind, message, pulse: ++this.runtimePulse };
1490
+ // Only this kind's slot is written, so an error of the other kind that is
1491
+ // already on the node survives.
1492
+ if (message.length === 0) delete nodeState.errors[kind];
1493
+ else if (kind === 'load') nodeState.errors.load = { kind, message, pulse: ++this.runtimePulse };
1494
+ else nodeState.errors.runtime = { kind, message, pulse: ++this.runtimePulse };
1490
1495
  this.runtimeState = state;
1491
1496
  this.emitRuntimeStateChanged();
1492
1497
  return true;
@@ -1501,12 +1506,9 @@ export class Diagram {
1501
1506
  if (kind === undefined || kind === 'load') node.loadError = '';
1502
1507
  const state = this.getRuntimeState();
1503
1508
  const nodeState = state.nodes[id];
1504
- if (nodeState !== undefined && (kind === undefined || nodeState.error?.kind === kind)) {
1505
- nodeState.error = node.runtimeError.length > 0
1506
- ? { kind: 'runtime', message: node.runtimeError, pulse: ++this.runtimePulse }
1507
- : node.loadError.length > 0
1508
- ? { kind: 'load', message: node.loadError, pulse: ++this.runtimePulse }
1509
- : null;
1509
+ if (nodeState !== undefined) {
1510
+ if (kind === undefined || kind === 'runtime') delete nodeState.errors.runtime;
1511
+ if (kind === undefined || kind === 'load') delete nodeState.errors.load;
1510
1512
  }
1511
1513
  this.runtimeState = state;
1512
1514
  this.emitRuntimeStateChanged();
@@ -22,6 +22,31 @@ export class DiagramActionRegistry<TId extends string, TContext> {
22
22
  };
23
23
  }
24
24
 
25
+ /**
26
+ * Registers one action per id from a table keyed by TId, in key order.
27
+ * Because the table is a total Record, adding a member to TId without adding
28
+ * its action stops compiling instead of producing a command that silently
29
+ * does nothing.
30
+ */
31
+ registerAll(actions: Record<TId, Omit<DiagramAction<TId, TContext>, 'id'>>): () => void {
32
+ // Object.keys is typed string[] because a value can carry keys beyond the
33
+ // ones its type names. This parameter is a total Record over TId, so its
34
+ // keys are exactly TId.
35
+ const ids = Object.keys(actions) as TId[];
36
+ // Delegating rather than spreading: a spread would copy own fields and
37
+ // drop the prototype, so a class-based action would register cleanly and
38
+ // then throw on its first call.
39
+ const disposers = ids.map((id) => {
40
+ const action = actions[id];
41
+ return this.register({
42
+ id,
43
+ canExecute: (context) => action.canExecute(context),
44
+ execute: (context) => action.execute(context),
45
+ });
46
+ });
47
+ return () => disposers.forEach((dispose) => dispose());
48
+ }
49
+
25
50
  get(id: TId): DiagramAction<TId, TContext> | null {
26
51
  return this.actions.get(id) ?? null;
27
52
  }
@@ -1,15 +1,15 @@
1
+ import { setJsonKey } from './json.js';
2
+ import { toPortDynamicMode } from './model.js';
1
3
  import {
2
4
  DIAGRAM_DOCUMENT_VERSION,
3
5
  type DiagramDocument,
4
6
  type DiagramDocumentEndpoint,
5
7
  type DiagramDocumentInput,
6
8
  type DiagramDocumentLink,
7
- type DiagramDocumentLinkInput,
8
9
  type DiagramDocumentNode,
9
- type DiagramDocumentNodeInput,
10
10
  type DiagramDocumentPort,
11
- type DiagramDocumentPortInput,
12
11
  type DiagramParameterSchema,
12
+ type PortDynamicMode,
13
13
  type JsonObject,
14
14
  type JsonValue,
15
15
  } from './model.js';
@@ -40,13 +40,18 @@ export class DiagramDocumentError extends Error {
40
40
  }
41
41
 
42
42
  export function createDiagramDocument(input: DiagramDocumentInput = {}): DiagramDocument {
43
- const nodes = (input.nodes ?? []).map((node, index) => normalizeNode(node, `$.nodes[${index}]`));
43
+ // DiagramDocumentInput is a structural interface, so its shape is a claim by
44
+ // the caller, not a guarantee. Elements are checked the same way parsed ones
45
+ // are, otherwise a malformed one surfaces as a TypeError naming no path.
46
+ const rawNodes = mapElements(input.nodes, '$.nodes');
47
+ const rawLinks = mapElements(input.links, '$.links');
48
+ const nodes = rawNodes.map((node, index) => normalizeNode(node, `$.nodes[${index}]`));
44
49
  const usedLinkIds = new Set<string>();
45
50
 
46
- for (let index = 0; index < (input.links ?? []).length; index++) {
47
- const id = input.links?.[index].id;
48
- if (id === undefined) continue;
49
- requireIdentifier(id, `$.links[${index}].id`);
51
+ for (let index = 0; index < rawLinks.length; index++) {
52
+ const raw = rawLinks[index].id;
53
+ if (raw === undefined) continue;
54
+ const id = requireIdentifier(raw, `$.links[${index}].id`);
50
55
  if (usedLinkIds.has(id)) {
51
56
  throw new DiagramDocumentError(`duplicate link id "${id}"`, `$.links[${index}].id`);
52
57
  }
@@ -54,12 +59,14 @@ export function createDiagramDocument(input: DiagramDocumentInput = {}): Diagram
54
59
  }
55
60
 
56
61
  let sequence = 1;
57
- const links = (input.links ?? []).map((link, index) => {
62
+ const links = rawLinks.map((link, index) => {
58
63
  let id = link.id;
59
64
  if (id === undefined) {
60
- do id = `link_${sequence++}`;
61
- while (usedLinkIds.has(id));
62
- usedLinkIds.add(id);
65
+ let generated: string;
66
+ do generated = `link_${sequence++}`;
67
+ while (usedLinkIds.has(generated));
68
+ usedLinkIds.add(generated);
69
+ id = generated;
63
70
  }
64
71
  return normalizeLink({ ...link, id }, `$.links[${index}]`);
65
72
  });
@@ -92,7 +99,7 @@ export function parseDiagramDocument(source: string | unknown): DiagramDocument
92
99
  }
93
100
  }
94
101
 
95
- const root = requireObject(value, '$');
102
+ const root = requireStructure(value, '$');
96
103
  const version = requireNumber(root.version, '$.version');
97
104
  if (version !== DIAGRAM_DOCUMENT_VERSION) {
98
105
  throw new DiagramDocumentError(`unsupported document version ${version}`, '$.version');
@@ -107,7 +114,8 @@ export function parseDiagramDocument(source: string | unknown): DiagramDocument
107
114
  return document;
108
115
  }
109
116
 
110
- function normalizeNode(input: DiagramDocumentNodeInput, path: string): DiagramDocumentNode {
117
+ function normalizeNode(value: unknown, path: string): DiagramDocumentNode {
118
+ const input = requireStructure(value, path);
111
119
  const id = requireIdentifier(input.id, `${path}.id`);
112
120
  return {
113
121
  id,
@@ -122,30 +130,36 @@ function normalizeNode(input: DiagramDocumentNodeInput, path: string): DiagramDo
122
130
  icon: requireString(input.icon ?? '', `${path}.icon`),
123
131
  message: requireString(input.message ?? '', `${path}.message`),
124
132
  openAction: requireString(input.openAction ?? '', `${path}.openAction`),
125
- inPorts: (input.inPorts ?? []).map((port, index) => normalizePort(port, `${path}.inPorts[${index}]`)),
126
- outPorts: (input.outPorts ?? []).map((port, index) => normalizePort(port, `${path}.outPorts[${index}]`)),
127
- parameters: (input.parameters ?? []).map((parameter, index) => normalizeParameter(parameter, `${path}.parameters[${index}]`)),
133
+ inPorts: mapElements(input.inPorts, `${path}.inPorts`)
134
+ .map((port, index) => normalizePort(port, `${path}.inPorts[${index}]`)),
135
+ outPorts: mapElements(input.outPorts, `${path}.outPorts`)
136
+ .map((port, index) => normalizePort(port, `${path}.outPorts[${index}]`)),
137
+ parameters: mapElements(input.parameters, `${path}.parameters`)
138
+ .map((parameter, index) => normalizeParameter(parameter, `${path}.parameters[${index}]`)),
128
139
  paramValues: cloneStringRecord(input.paramValues ?? {}, `${path}.paramValues`),
129
140
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`),
130
141
  };
131
142
  }
132
143
 
133
- function normalizePort(input: DiagramDocumentPortInput, path: string): DiagramDocumentPort {
144
+ function normalizePort(value: unknown, path: string): DiagramDocumentPort {
145
+ const input = requireStructure(value, path);
134
146
  return {
135
147
  id: requireIdentifier(input.id, `${path}.id`),
136
148
  name: requireString(input.name, `${path}.name`),
137
149
  description: requireString(input.description ?? '', `${path}.description`),
138
150
  type: requireString(input.type ?? '', `${path}.type`),
139
151
  maxLinks: requireNonNegativeInteger(input.maxLinks ?? 0, `${path}.maxLinks`),
140
- availableTypes: (input.availableTypes ?? []).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
152
+ availableTypes: requireArray(input.availableTypes ?? [], `${path}.availableTypes`)
153
+ .map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
141
154
  isDynamic: requireBoolean(input.isDynamic ?? false, `${path}.isDynamic`),
142
- dynamicMode: requireString(input.dynamicMode ?? '', `${path}.dynamicMode`),
155
+ dynamicMode: requirePortDynamicMode(input.dynamicMode ?? '', `${path}.dynamicMode`),
143
156
  isSibling: requireBoolean(input.isSibling ?? false, `${path}.isSibling`),
144
157
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`),
145
158
  };
146
159
  }
147
160
 
148
- function normalizeParameter(input: DiagramParameterSchema, path: string): DiagramParameterSchema {
161
+ function normalizeParameter(value: unknown, path: string): DiagramParameterSchema {
162
+ const input = requireStructure(value, path);
149
163
  return {
150
164
  name: requireIdentifier(input.name, `${path}.name`),
151
165
  displayName: requireString(input.displayName, `${path}.displayName`),
@@ -162,7 +176,8 @@ function normalizeParameter(input: DiagramParameterSchema, path: string): Diagra
162
176
  };
163
177
  }
164
178
 
165
- function normalizeLink(input: DiagramDocumentLinkInput & { id: string }, path: string): DiagramDocumentLink {
179
+ function normalizeLink(value: unknown, path: string): DiagramDocumentLink {
180
+ const input = requireStructure(value, path);
166
181
  return {
167
182
  id: requireIdentifier(input.id, `${path}.id`),
168
183
  from: normalizeEndpoint(input.from, `${path}.from`),
@@ -171,7 +186,8 @@ function normalizeLink(input: DiagramDocumentLinkInput & { id: string }, path: s
171
186
  };
172
187
  }
173
188
 
174
- function normalizeEndpoint(input: DiagramDocumentEndpoint, path: string): DiagramDocumentEndpoint {
189
+ function normalizeEndpoint(value: unknown, path: string): DiagramDocumentEndpoint {
190
+ const input = requireStructure(value, path);
175
191
  return {
176
192
  nodeId: requireIdentifier(input.nodeId, `${path}.nodeId`),
177
193
  portId: requireIdentifier(input.portId, `${path}.portId`),
@@ -179,7 +195,7 @@ function normalizeEndpoint(input: DiagramDocumentEndpoint, path: string): Diagra
179
195
  }
180
196
 
181
197
  function parseNode(value: unknown, path: string): DiagramDocumentNode {
182
- const node = requireObject(value, path);
198
+ const node = requireStructure(value, path);
183
199
  return normalizeNode({
184
200
  id: requireString(node.id, `${path}.id`),
185
201
  typeId: requireString(node.typeId, `${path}.typeId`),
@@ -202,7 +218,7 @@ function parseNode(value: unknown, path: string): DiagramDocumentNode {
202
218
  }
203
219
 
204
220
  function parsePort(value: unknown, path: string): DiagramDocumentPort {
205
- const port = requireObject(value, path);
221
+ const port = requireStructure(value, path);
206
222
  return normalizePort({
207
223
  id: requireString(port.id, `${path}.id`),
208
224
  name: requireString(port.name, `${path}.name`),
@@ -211,14 +227,14 @@ function parsePort(value: unknown, path: string): DiagramDocumentPort {
211
227
  maxLinks: requireNumber(port.maxLinks, `${path}.maxLinks`),
212
228
  availableTypes: requireArray(port.availableTypes, `${path}.availableTypes`).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
213
229
  isDynamic: requireBoolean(port.isDynamic, `${path}.isDynamic`),
214
- dynamicMode: requireString(port.dynamicMode, `${path}.dynamicMode`),
230
+ dynamicMode: requirePortDynamicMode(port.dynamicMode, `${path}.dynamicMode`),
215
231
  isSibling: requireBoolean(port.isSibling, `${path}.isSibling`),
216
232
  metadata: cloneJsonObject(port.metadata, `${path}.metadata`),
217
233
  }, path);
218
234
  }
219
235
 
220
236
  function parseParameter(value: unknown, path: string): DiagramParameterSchema {
221
- const parameter = requireObject(value, path);
237
+ const parameter = requireStructure(value, path);
222
238
  return normalizeParameter({
223
239
  name: requireString(parameter.name, `${path}.name`),
224
240
  displayName: requireString(parameter.displayName, `${path}.displayName`),
@@ -236,7 +252,7 @@ function parseParameter(value: unknown, path: string): DiagramParameterSchema {
236
252
  }
237
253
 
238
254
  function parseLink(value: unknown, path: string): DiagramDocumentLink {
239
- const link = requireObject(value, path);
255
+ const link = requireStructure(value, path);
240
256
  return normalizeLink({
241
257
  id: requireString(link.id, `${path}.id`),
242
258
  from: parseEndpoint(link.from, `${path}.from`),
@@ -246,7 +262,7 @@ function parseLink(value: unknown, path: string): DiagramDocumentLink {
246
262
  }
247
263
 
248
264
  function parseEndpoint(value: unknown, path: string): DiagramDocumentEndpoint {
249
- const endpoint = requireObject(value, path);
265
+ const endpoint = requireStructure(value, path);
250
266
  return {
251
267
  nodeId: requireIdentifier(endpoint.nodeId, `${path}.nodeId`),
252
268
  portId: requireIdentifier(endpoint.portId, `${path}.portId`),
@@ -298,7 +314,7 @@ function validateUniquePorts(ports: readonly DiagramDocumentPort[], path: string
298
314
  function cloneStringRecord(value: unknown, path: string): Record<string, string> {
299
315
  const object = requireObject(value, path);
300
316
  const result: Record<string, string> = {};
301
- for (const [key, item] of Object.entries(object)) result[key] = requireString(item, `${path}.${key}`);
317
+ for (const [key, item] of Object.entries(object)) setJsonKey(result, key, requireString(item, `${path}.${key}`));
302
318
  return result;
303
319
  }
304
320
 
@@ -309,7 +325,7 @@ function cloneJsonObject(value: unknown, path: string, ancestors = new WeakSet<o
309
325
  const result: JsonObject = {};
310
326
  try {
311
327
  for (const [key, item] of Object.entries(object)) {
312
- result[key] = cloneJsonValue(item, `${path}.${key}`, ancestors);
328
+ setJsonKey(result, key, cloneJsonValue(item, `${path}.${key}`, ancestors));
313
329
  }
314
330
  } finally {
315
331
  ancestors.delete(object);
@@ -333,6 +349,23 @@ function cloneJsonValue(value: unknown, path: string, ancestors: WeakSet<object>
333
349
  throw new DiagramDocumentError('expected a JSON value', path);
334
350
  }
335
351
 
352
+ /**
353
+ * A container whose fields are read individually. Any object will do, including
354
+ * a class instance: hosts build schemes out of DiagramNode and Port, not object
355
+ * literals, and every field is validated on its own below.
356
+ */
357
+ function requireStructure(value: unknown, path: string): Record<string, unknown> {
358
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
359
+ throw new DiagramDocumentError('expected an object', path);
360
+ }
361
+ return value as Record<string, unknown>;
362
+ }
363
+
364
+ /**
365
+ * A value that has to survive JSON round-tripping unchanged. Stricter than
366
+ * requireStructure on purpose: a Date or any other class instance would
367
+ * serialize to something the parser could not read back.
368
+ */
336
369
  function requireObject(value: unknown, path: string): Record<string, unknown> {
337
370
  if (!isObject(value)) throw new DiagramDocumentError('expected an object', path);
338
371
  return value;
@@ -344,6 +377,21 @@ function isObject(value: unknown): value is Record<string, unknown> {
344
377
  return prototype === Object.prototype || prototype === null;
345
378
  }
346
379
 
380
+ /**
381
+ * Reads an optional array of objects, rejecting a non-array, a hole or a
382
+ * non-object element. null counts as absent, like the `?? []` every other
383
+ * optional field is read through.
384
+ */
385
+ function mapElements(value: unknown, path: string): Record<string, unknown>[] {
386
+ if (value === undefined || value === null) return [];
387
+ // Array.from rather than map: map skips holes, carrying an undefined element
388
+ // into the result instead of reporting it.
389
+ return Array.from(
390
+ requireArray(value, path),
391
+ (element, index) => requireStructure(element, `${path}[${index}]`),
392
+ );
393
+ }
394
+
347
395
  function requireArray(value: unknown, path: string): unknown[] {
348
396
  if (!Array.isArray(value)) throw new DiagramDocumentError('expected an array', path);
349
397
  return value;
@@ -354,6 +402,18 @@ function requireString(value: unknown, path: string): string {
354
402
  return value;
355
403
  }
356
404
 
405
+ /**
406
+ * Coerces rather than rejects. The set is closed in the types, which is where a
407
+ * typo gets caught, but a persisted scheme can carry anything a JavaScript host
408
+ * wrote before that -- and refusing to load it would brick the whole document
409
+ * over a field whose every value except 'onConnect' behaves identically. The
410
+ * type must still be a string, so a number or an object is a real error.
411
+ */
412
+ function requirePortDynamicMode(value: unknown, path: string): PortDynamicMode {
413
+ const mode = requireString(value, path);
414
+ return toPortDynamicMode(mode);
415
+ }
416
+
357
417
  function requireIdentifier(value: unknown, path: string): string {
358
418
  const id = requireString(value, path);
359
419
  if (id.trim().length === 0) throw new DiagramDocumentError('identifier cannot be empty', path);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Stores a key that JSON can legitimately carry but plain assignment cannot hold.
3
+ *
4
+ * `JSON.parse` produces `__proto__` as an ordinary own key, yet writing it back
5
+ * with `target[key] = value` hits the inherited `__proto__` setter: the value is
6
+ * dropped and the object's prototype is replaced instead. A document parsed that
7
+ * way loses data and stops satisfying the parser's own "plain object" guard, so
8
+ * anything the parser accepted could no longer be serialized or cloned.
9
+ */
10
+ export function setJsonKey<T>(target: Record<string, T>, key: string, value: T): void {
11
+ if (key === '__proto__') {
12
+ Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
13
+ return;
14
+ }
15
+ target[key] = value;
16
+ }
package/src/core/model.ts CHANGED
@@ -20,6 +20,19 @@ export interface DiagramParameterSchema {
20
20
  editorType: string;
21
21
  }
22
22
 
23
+ /**
24
+ * How a dynamic port grows. Only 'onConnect' makes the editor add a typed
25
+ * sibling when a link lands on the port; '' and 'manual' leave it to the host.
26
+ * The renderer compares this with ===, so a value outside the set silently
27
+ * selects the do-nothing branch -- hence a union rather than a string.
28
+ */
29
+ export type PortDynamicMode = '' | 'manual' | 'onConnect';
30
+
31
+ /** Narrows an untyped value to the closed set, defaulting to the inert mode. */
32
+ export function toPortDynamicMode(value: unknown): PortDynamicMode {
33
+ return value === 'manual' || value === 'onConnect' ? value : '';
34
+ }
35
+
23
36
  export interface DiagramDocumentPort {
24
37
  id: string;
25
38
  name: string;
@@ -28,7 +41,7 @@ export interface DiagramDocumentPort {
28
41
  maxLinks: number;
29
42
  availableTypes: string[];
30
43
  isDynamic: boolean;
31
- dynamicMode: string;
44
+ dynamicMode: PortDynamicMode;
32
45
  isSibling: boolean;
33
46
  metadata: JsonObject;
34
47
  }
package/src/core/state.ts CHANGED
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Recursively read-only view of a state object. The snapshot accessors hand back
3
+ * detached copies, so writing to one is silently discarded -- this makes that a
4
+ * compile error instead. The setters accept it, so a snapshot still round-trips.
5
+ */
6
+ export type DiagramSnapshot<T> = {
7
+ readonly [K in keyof T]: DiagramSnapshotValue<T[K]>;
8
+ };
9
+
10
+ // Distributes over unions, so an optional or nullable object property keeps
11
+ // being descended into instead of falling through as its writable self.
12
+ type DiagramSnapshotValue<T> = T extends readonly (infer TItem)[]
13
+ ? readonly DiagramSnapshotValue<TItem>[]
14
+ : T extends object
15
+ ? DiagramSnapshot<T>
16
+ : T;
17
+
1
18
  export type DiagramPortDirection = 'in' | 'out';
2
19
  export type DiagramNodeErrorKind = 'runtime' | 'load';
3
20
  export type DiagramGlobalErrorKind = 'invalid' | 'load' | 'locked' | 'encrypted';
@@ -23,9 +40,18 @@ export interface DiagramNodePortRuntimeState {
23
40
  out: Record<string, DiagramPortRuntimeState>;
24
41
  }
25
42
 
43
+ /**
44
+ * At most one error per kind. A node can carry a load error and a runtime error
45
+ * at the same time -- the tooltip shows both -- so a single slot could not hold
46
+ * what the editor already models, and every state write dropped one of them.
47
+ */
48
+ export type DiagramNodeErrors = {
49
+ [TKind in DiagramNodeErrorKind]?: DiagramErrorState<TKind>;
50
+ };
51
+
26
52
  export interface DiagramNodeRuntimeState {
27
53
  active: boolean;
28
- error: DiagramErrorState<DiagramNodeErrorKind> | null;
54
+ errors: DiagramNodeErrors;
29
55
  ports: DiagramNodePortRuntimeState;
30
56
  }
31
57
 
@@ -77,7 +103,7 @@ export function cloneDiagramRuntimeState(state: DiagramRuntimeState): DiagramRun
77
103
  globalError: state.globalError === null ? null : { ...state.globalError },
78
104
  nodes: Object.fromEntries(Object.entries(state.nodes).map(([nodeId, node]) => [nodeId, {
79
105
  active: node.active,
80
- error: node.error === null ? null : { ...node.error },
106
+ errors: cloneDiagramNodeErrors(node.errors),
81
107
  ports: {
82
108
  in: Object.fromEntries(Object.entries(node.ports.in)
83
109
  .map(([portId, port]) => [portId, { ...port }])),
@@ -142,7 +168,19 @@ export function createReadOnlyDiagramPermissions(): DiagramInteractionPermission
142
168
  export function createDiagramNodeRuntimeState(): DiagramNodeRuntimeState {
143
169
  return {
144
170
  active: false,
145
- error: null,
171
+ errors: {},
146
172
  ports: { in: {}, out: {} },
147
173
  };
148
174
  }
175
+
176
+ /**
177
+ * Tolerates a missing map: hosts persist runtime snapshots and build them by
178
+ * hand, so a state written before this field existed still has to be readable.
179
+ */
180
+ export function cloneDiagramNodeErrors(errors: DiagramNodeErrors | undefined | null): DiagramNodeErrors {
181
+ const result: DiagramNodeErrors = {};
182
+ if (errors === undefined || errors === null) return result;
183
+ if (errors.runtime !== undefined) result.runtime = { ...errors.runtime };
184
+ if (errors.load !== undefined) result.load = { ...errors.load };
185
+ return result;
186
+ }