@istar-ts/actions 0.15.0-beta.3 → 0.15.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2,6 +2,10 @@ import { AnyIstarModel, AnyMetamodel, AnyPaletteDescription, CustomProperties, D
2
2
  //#region src/actions.d.ts
3
3
  /** GLSP's custom arguments: a flat map of primitives. */
4
4
  export type Args = Readonly<Record<string, string | number | boolean>>;
5
+ /** A value that survives a JSON round trip, for `istar.updateModelExtra`'s `set`. */
6
+ export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
7
+ readonly [key: string]: JsonValue;
8
+ };
5
9
  export interface Point {
6
10
  readonly x: number;
7
11
  readonly y: number;
@@ -243,8 +247,35 @@ export interface SetFileMetamodelOperation extends Operation {
243
247
  readonly kind: "istar.setFileMetamodel";
244
248
  readonly block: FileMetamodel | null;
245
249
  }
250
+ /**
251
+ * Sets, removes or reads back the model's own top-level keys that core keeps in `model.extra`
252
+ * (e.g. a dialect's `metamodelVersion`): `set`'s keys are written (other extra keys and their
253
+ * order are kept; a new key is appended where `toPistar` would place it), `remove`'s keys are
254
+ * deleted. `tool` sets (or, with `null`, removes) the model's own `tool` field in the same undo
255
+ * step — `set`/`remove` reject it, and every key core models itself (`actors`, `orphans`,
256
+ * `dependencies`, `links`, `display`, `istar`, `saveDate`, `diagram`), and `"metamodel"` (use
257
+ * `istar.setFileMetamodel` for that one). One undo step.
258
+ */
259
+ export interface UpdateModelExtraOperation extends Operation {
260
+ readonly kind: "istar.updateModelExtra";
261
+ readonly set?: Readonly<Record<string, JsonValue>>;
262
+ readonly remove?: readonly string[];
263
+ readonly tool?: string | null;
264
+ }
265
+ /**
266
+ * Replaces the whole model from a piStar save file, as one undo step: ids, order and extra keys
267
+ * come from `model`, so a parse → `toPistar` round trip is byte-identical to it. Parsed with
268
+ * `parsePistar` under the dispatcher's own metamodel (`fileMetamodel` as in `parsePistar`); a
269
+ * parse error rejects with a typed diagnostic, the model untouched. Pre-guards and postGuards
270
+ * run against this operation, the model before and the model after, like any other operation.
271
+ */
272
+ export interface ReplaceModelOperation extends Operation {
273
+ readonly kind: "istar.replaceModel";
274
+ readonly model: PistarFile;
275
+ readonly fileMetamodel?: boolean;
276
+ }
246
277
  /** Every operation, keyed by `kind`. */
247
- export type IstarOperation<EK extends string = ElementKind, LK extends string = LinkKind> = CreateNodeOperation<EK> | CreateEdgeOperation<EK, LK> | DeleteElementOperation | ChangeBoundsOperation | ChangeContainerOperation | ReconnectEdgeOperation | ApplyLabelEditOperation | CompoundOperation<EK, LK> | AddLegOperation | RemoveLegOperation | SetCollapsedOperation | UpdateDiagramOperation | UpdatePropertiesOperation | UpdateDisplayOperation | SetLinkValueOperation | UpdateLinkDisplayOperation | ChangeLinkKindOperation<LK> | SetFileMetamodelOperation;
278
+ export type IstarOperation<EK extends string = ElementKind, LK extends string = LinkKind> = CreateNodeOperation<EK> | CreateEdgeOperation<EK, LK> | DeleteElementOperation | ChangeBoundsOperation | ChangeContainerOperation | ReconnectEdgeOperation | ApplyLabelEditOperation | CompoundOperation<EK, LK> | AddLegOperation | RemoveLegOperation | SetCollapsedOperation | UpdateDiagramOperation | UpdatePropertiesOperation | UpdateDisplayOperation | SetLinkValueOperation | UpdateLinkDisplayOperation | ChangeLinkKindOperation<LK> | SetFileMetamodelOperation | UpdateModelExtraOperation | ReplaceModelOperation;
248
279
  export interface RequestModelAction extends RequestAction {
249
280
  readonly kind: "requestModel";
250
281
  readonly options?: Args;
@@ -470,6 +501,11 @@ export declare function isOperation<EK extends string, LK extends string>(action
470
501
  /** True for the requests (actions answered with a response action). */
471
502
  export declare function isRequestAction(action: Action): action is IstarRequestAction;
472
503
  //#endregion
504
+ //#region src/created-ids.d.ts
505
+ type Operation$1 = IstarOperation<string, string>;
506
+ /** `operation`, with `createdIds` (an accepted result's) injected as explicit ids. */
507
+ export declare function injectCreatedIds(operation: Operation$1, createdIds: readonly string[], model: AnyIstarModel): Operation$1;
508
+ //#endregion
473
509
  //#region src/dispatcher.d.ts
474
510
  /** The response a request is answered with; `never` for other actions. */
475
511
  export type ResponseFor<A> = A extends RequestModelAction ? SetModelAction : A extends RequestTypeHintsAction ? SetTypeHintsAction : A extends RequestCheckEdgeAction ? CheckEdgeResultAction : A extends RequestContextActions ? SetContextActions : A extends RequestMarkersAction ? SetMarkersAction : never;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,10 @@ import { AnyIstarModel, AnyMetamodel, AnyPaletteDescription, CustomProperties, D
2
2
  //#region src/actions.d.ts
3
3
  /** GLSP's custom arguments: a flat map of primitives. */
4
4
  export type Args = Readonly<Record<string, string | number | boolean>>;
5
+ /** A value that survives a JSON round trip, for `istar.updateModelExtra`'s `set`. */
6
+ export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
7
+ readonly [key: string]: JsonValue;
8
+ };
5
9
  export interface Point {
6
10
  readonly x: number;
7
11
  readonly y: number;
@@ -243,8 +247,35 @@ export interface SetFileMetamodelOperation extends Operation {
243
247
  readonly kind: "istar.setFileMetamodel";
244
248
  readonly block: FileMetamodel | null;
245
249
  }
250
+ /**
251
+ * Sets, removes or reads back the model's own top-level keys that core keeps in `model.extra`
252
+ * (e.g. a dialect's `metamodelVersion`): `set`'s keys are written (other extra keys and their
253
+ * order are kept; a new key is appended where `toPistar` would place it), `remove`'s keys are
254
+ * deleted. `tool` sets (or, with `null`, removes) the model's own `tool` field in the same undo
255
+ * step — `set`/`remove` reject it, and every key core models itself (`actors`, `orphans`,
256
+ * `dependencies`, `links`, `display`, `istar`, `saveDate`, `diagram`), and `"metamodel"` (use
257
+ * `istar.setFileMetamodel` for that one). One undo step.
258
+ */
259
+ export interface UpdateModelExtraOperation extends Operation {
260
+ readonly kind: "istar.updateModelExtra";
261
+ readonly set?: Readonly<Record<string, JsonValue>>;
262
+ readonly remove?: readonly string[];
263
+ readonly tool?: string | null;
264
+ }
265
+ /**
266
+ * Replaces the whole model from a piStar save file, as one undo step: ids, order and extra keys
267
+ * come from `model`, so a parse → `toPistar` round trip is byte-identical to it. Parsed with
268
+ * `parsePistar` under the dispatcher's own metamodel (`fileMetamodel` as in `parsePistar`); a
269
+ * parse error rejects with a typed diagnostic, the model untouched. Pre-guards and postGuards
270
+ * run against this operation, the model before and the model after, like any other operation.
271
+ */
272
+ export interface ReplaceModelOperation extends Operation {
273
+ readonly kind: "istar.replaceModel";
274
+ readonly model: PistarFile;
275
+ readonly fileMetamodel?: boolean;
276
+ }
246
277
  /** Every operation, keyed by `kind`. */
247
- export type IstarOperation<EK extends string = ElementKind, LK extends string = LinkKind> = CreateNodeOperation<EK> | CreateEdgeOperation<EK, LK> | DeleteElementOperation | ChangeBoundsOperation | ChangeContainerOperation | ReconnectEdgeOperation | ApplyLabelEditOperation | CompoundOperation<EK, LK> | AddLegOperation | RemoveLegOperation | SetCollapsedOperation | UpdateDiagramOperation | UpdatePropertiesOperation | UpdateDisplayOperation | SetLinkValueOperation | UpdateLinkDisplayOperation | ChangeLinkKindOperation<LK> | SetFileMetamodelOperation;
278
+ export type IstarOperation<EK extends string = ElementKind, LK extends string = LinkKind> = CreateNodeOperation<EK> | CreateEdgeOperation<EK, LK> | DeleteElementOperation | ChangeBoundsOperation | ChangeContainerOperation | ReconnectEdgeOperation | ApplyLabelEditOperation | CompoundOperation<EK, LK> | AddLegOperation | RemoveLegOperation | SetCollapsedOperation | UpdateDiagramOperation | UpdatePropertiesOperation | UpdateDisplayOperation | SetLinkValueOperation | UpdateLinkDisplayOperation | ChangeLinkKindOperation<LK> | SetFileMetamodelOperation | UpdateModelExtraOperation | ReplaceModelOperation;
248
279
  export interface RequestModelAction extends RequestAction {
249
280
  readonly kind: "requestModel";
250
281
  readonly options?: Args;
@@ -470,6 +501,11 @@ export declare function isOperation<EK extends string, LK extends string>(action
470
501
  /** True for the requests (actions answered with a response action). */
471
502
  export declare function isRequestAction(action: Action): action is IstarRequestAction;
472
503
  //#endregion
504
+ //#region src/created-ids.d.ts
505
+ type Operation$1 = IstarOperation<string, string>;
506
+ /** `operation`, with `createdIds` (an accepted result's) injected as explicit ids. */
507
+ export declare function injectCreatedIds(operation: Operation$1, createdIds: readonly string[], model: AnyIstarModel): Operation$1;
508
+ //#endregion
473
509
  //#region src/dispatcher.d.ts
474
510
  /** The response a request is answered with; `never` for other actions. */
475
511
  export type ResponseFor<A> = A extends RequestModelAction ? SetModelAction : A extends RequestTypeHintsAction ? SetTypeHintsAction : A extends RequestCheckEdgeAction ? CheckEdgeResultAction : A extends RequestContextActions ? SetContextActions : A extends RequestMarkersAction ? SetMarkersAction : never;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DIAGNOSTIC_SEVERITIES, FILE_METAMODEL_KEY, LINK_SELECTOR, MetamodelError, ModelOperationError, canLink, connectResultToDiagnostic, createEmptyModel, dependencyEnds, effectiveLinkKind, elementBehavesLike, inheritMetamodel, inheritSourceLayout, linkBehavesLike, metamodelOf, parsePistar, toPistarObject, validateModel, validateModelProperties, withFileMetamodel } from "@istar-ts/core";
1
+ import { DIAGNOSTIC_SEVERITIES, FILE_METAMODEL_KEY, LINK_SELECTOR, MetamodelError, ModelOperationError, PistarParseError, TOP_LEVEL_KEYS, canLink, connectResultToDiagnostic, createEmptyModel, dependencyEnds, effectiveLinkKind, elementBehavesLike, hostMetamodelOf, inheritMetamodel, inheritSourceLayout, linkBehavesLike, metamodelOf, parsePistar, toPistarObject, validateModel, validateModelProperties, withFileMetamodel } from "@istar-ts/core";
2
2
  //#region src/actions.ts
3
3
  const OPERATION_KIND_LIST = [
4
4
  "createNode",
@@ -18,7 +18,9 @@ const OPERATION_KIND_LIST = [
18
18
  "istar.setLinkValue",
19
19
  "istar.updateLinkDisplay",
20
20
  "istar.changeLinkKind",
21
- "istar.setFileMetamodel"
21
+ "istar.setFileMetamodel",
22
+ "istar.updateModelExtra",
23
+ "istar.replaceModel"
22
24
  ];
23
25
  const REQUEST_KIND_LIST = [
24
26
  "requestModel",
@@ -63,6 +65,141 @@ function isRequestAction(action) {
63
65
  return REQUEST_KINDS.has(action.kind);
64
66
  }
65
67
  //#endregion
68
+ //#region src/created-ids.ts
69
+ /** How many new element ids, then new link ids, `operation` creates, mirroring the dispatcher's
70
+ * `createdIds` diff (`dispatcher.ts`) and `connect`/`addDependency`/`addLeg`
71
+ * (`packages/core/src/operations.ts`). */
72
+ function countCreatedIds(operation, model) {
73
+ switch (operation.kind) {
74
+ case "createNode": return {
75
+ elements: 1,
76
+ links: 0
77
+ };
78
+ case "createEdge": {
79
+ const metamodel = metamodelOf(model);
80
+ const definition = metamodel.links.get(operation.elementTypeId);
81
+ if (definition?.category === "dependency") return {
82
+ elements: 1,
83
+ links: 2
84
+ };
85
+ if (definition?.parts && !targetIsHub(operation, model, metamodel)) return {
86
+ elements: 1,
87
+ links: 2
88
+ };
89
+ return {
90
+ elements: 0,
91
+ links: 1
92
+ };
93
+ }
94
+ case "istar.addLeg": return {
95
+ elements: 0,
96
+ links: 1
97
+ };
98
+ case "compound": {
99
+ let elements = 0;
100
+ let links = 0;
101
+ for (const child of operation.operationList) {
102
+ const counts = countCreatedIds(child, model);
103
+ elements += counts.elements;
104
+ links += counts.links;
105
+ }
106
+ return {
107
+ elements,
108
+ links
109
+ };
110
+ }
111
+ default: return {
112
+ elements: 0,
113
+ links: 0
114
+ };
115
+ }
116
+ }
117
+ function targetIsHub(operation, model, metamodel) {
118
+ const targetKind = model.elements.get(operation.targetElementId)?.kind;
119
+ return targetKind !== void 0 && metamodel.elements.get(targetKind)?.role === "hub";
120
+ }
121
+ /** `operation` with ids from `elementIds`/`linkIds` (consumed in model order) injected as the
122
+ * explicit ids its creates take, so a dispatcher that applies it against the same model state
123
+ * creates the same ids. */
124
+ function withCreatedIds(operation, elementIds, linkIds, model) {
125
+ switch (operation.kind) {
126
+ case "createNode": {
127
+ const id = elementIds.shift();
128
+ return id === void 0 ? operation : {
129
+ ...operation,
130
+ args: {
131
+ ...operation.args,
132
+ id
133
+ }
134
+ };
135
+ }
136
+ case "createEdge": {
137
+ const metamodel = metamodelOf(model);
138
+ const definition = metamodel.links.get(operation.elementTypeId);
139
+ if (definition?.category === "dependency") {
140
+ const dependumId = elementIds.shift();
141
+ const linkIds2 = [linkIds.shift(), linkIds.shift()];
142
+ if (dependumId === void 0 || linkIds2[0] === void 0 || linkIds2[1] === void 0) return operation;
143
+ return {
144
+ ...operation,
145
+ args: {
146
+ ...operation.args,
147
+ dependum: {
148
+ ...operation.args?.dependum,
149
+ id: dependumId
150
+ },
151
+ linkIds: linkIds2
152
+ }
153
+ };
154
+ }
155
+ if (definition?.parts && !targetIsHub(operation, model, metamodel)) {
156
+ const hubId = elementIds.shift();
157
+ const legId = linkIds.shift();
158
+ const endId = linkIds.shift();
159
+ return hubId === void 0 || legId === void 0 || endId === void 0 ? operation : {
160
+ ...operation,
161
+ args: {
162
+ ...operation.args,
163
+ id: hubId,
164
+ legId,
165
+ endId
166
+ }
167
+ };
168
+ }
169
+ const id = linkIds.shift();
170
+ return id === void 0 ? operation : {
171
+ ...operation,
172
+ args: {
173
+ ...operation.args,
174
+ id
175
+ }
176
+ };
177
+ }
178
+ case "istar.addLeg": {
179
+ const id = linkIds.shift();
180
+ return id === void 0 ? operation : {
181
+ ...operation,
182
+ args: {
183
+ ...operation.args,
184
+ id
185
+ }
186
+ };
187
+ }
188
+ case "compound": return {
189
+ ...operation,
190
+ operationList: operation.operationList.map((child) => withCreatedIds(child, elementIds, linkIds, model))
191
+ };
192
+ default: return operation;
193
+ }
194
+ }
195
+ /** `operation`, with `createdIds` (an accepted result's) injected as explicit ids. */
196
+ function injectCreatedIds(operation, createdIds, model) {
197
+ const counts = countCreatedIds(operation, model);
198
+ const elementIds = createdIds.slice(0, counts.elements);
199
+ const linkIds = createdIds.slice(counts.elements, counts.elements + counts.links);
200
+ return withCreatedIds(operation, [...elementIds], [...linkIds], model);
201
+ }
202
+ //#endregion
66
203
  //#region src/hints.ts
67
204
  /**
68
205
  * What a client can know before it asks: type hints from the metamodel, and the palette as
@@ -275,6 +412,11 @@ function rejectCheck(check, endpoints) {
275
412
  function metaOf(model) {
276
413
  return metamodelOf(model);
277
414
  }
415
+ /**
416
+ * Keys `istar.updateModelExtra`'s `set`/`remove` must not touch: every key core models itself
417
+ * (`TOP_LEVEL_KEYS`, which includes `tool`), and `"metamodel"` (`istar.setFileMetamodel`'s own).
418
+ */
419
+ const RESERVED_EXTRA_KEYS = /* @__PURE__ */ new Set([...TOP_LEVEL_KEYS, FILE_METAMODEL_KEY]);
278
420
  /** A model with `links` changed, as the core operations derive one. */
279
421
  function withLinks(model, links) {
280
422
  return inheritMetamodel(model, inheritSourceLayout(model, {
@@ -581,6 +723,48 @@ function createActionDispatcher(store, options = {}) {
581
723
  s.replace(next);
582
724
  return;
583
725
  }
726
+ case "istar.updateModelExtra": {
727
+ const { set, remove, tool } = operation;
728
+ for (const key of [...Object.keys(set ?? {}), ...remove ?? []]) {
729
+ if (!RESERVED_EXTRA_KEYS.has(key)) continue;
730
+ if (key === "tool") reject(`"tool" is a modelled field: set it with this operation's own "tool"`, "", "reserved-key");
731
+ if (key === FILE_METAMODEL_KEY) reject(`"${key}" is set with istar.setFileMetamodel, not updateModelExtra`, "", "reserved-key");
732
+ reject(`"${key}" is modelled by the file itself and cannot be an extra key`, "", "reserved-key");
733
+ }
734
+ let withTool = model;
735
+ if (tool === null) {
736
+ const { tool: _tool, ...rest } = withTool;
737
+ withTool = rest;
738
+ } else if (tool !== void 0) withTool = {
739
+ ...withTool,
740
+ tool
741
+ };
742
+ const extra = { ...withTool.extra };
743
+ if (set) for (const [key, value] of Object.entries(set)) extra[key] = value;
744
+ if (remove) for (const key of remove) delete extra[key];
745
+ const { extra: _extra, ...rest } = withTool;
746
+ const next = {
747
+ ...rest,
748
+ ...Object.keys(extra).length > 0 ? { extra } : {}
749
+ };
750
+ s.replace(inheritMetamodel(model, inheritSourceLayout(model, next)));
751
+ return;
752
+ }
753
+ case "istar.replaceModel": {
754
+ const host = hostMetamodelOf(model) ?? meta;
755
+ let next;
756
+ try {
757
+ next = parsePistar(operation.model, {
758
+ metamodel: host,
759
+ fileMetamodel: operation.fileMetamodel ?? false
760
+ });
761
+ } catch (error) {
762
+ if (error instanceof PistarParseError) reject(error.message, "", "parse-error");
763
+ throw error;
764
+ }
765
+ s.replace(next);
766
+ return;
767
+ }
584
768
  default: reject(`unknown operation "${operation.kind}"`, "", "unknown-action");
585
769
  }
586
770
  };
@@ -593,7 +777,8 @@ function createActionDispatcher(store, options = {}) {
593
777
  for (const [key, value] of Object.entries(properties)) if (value === null) delete next[key];
594
778
  else if (typeof value === "string") next[key] = value;
595
779
  else reject(`the value of "${key}" must be a string or null`, id, "invalid-value");
596
- const customProperties = Object.keys(next).length > 0 ? next : null;
780
+ if (Object.keys(next).length === 0 && owner.customProperties === void 0) return;
781
+ const customProperties = next;
597
782
  if (model.elements.has(id)) s.updateElement(id, { customProperties });
598
783
  else s.updateLink(id, { customProperties });
599
784
  };
@@ -1206,6 +1391,6 @@ function createRemoteDispatcher(port) {
1206
1391
  /** Version of the action protocol this package speaks. */
1207
1392
  const ACTIONS_PROTOCOL_VERSION = "0.0.0";
1208
1393
  //#endregion
1209
- export { ACTIONS_PROTOCOL_VERSION, HANDLED_ACTION_KINDS, connectDispatcher, contextActionsFor, contextText, createActionDispatcher, createMirrorStore, createRemoteDispatcher, diagnosticToMarker, hostToolContext, isOperation, isRequestAction, markerToDiagnostic, paletteFromMetamodel, readOnlyDispatcher, typeHintsFor };
1394
+ export { ACTIONS_PROTOCOL_VERSION, HANDLED_ACTION_KINDS, connectDispatcher, contextActionsFor, contextText, createActionDispatcher, createMirrorStore, createRemoteDispatcher, diagnosticToMarker, hostToolContext, injectCreatedIds, isOperation, isRequestAction, markerToDiagnostic, paletteFromMetamodel, readOnlyDispatcher, typeHintsFor };
1210
1395
 
1211
1396
  //# sourceMappingURL=index.js.map