@istar-ts/actions 0.15.0-beta.0 → 0.15.0-beta.1

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
@@ -1,4 +1,4 @@
1
- import { AnyIstarModel, AnyPaletteDescription, CustomProperties, DiagnosticSeverity, ElementKind, GoalDiagnostic, IstarModel, LinkKind, Metamodel, ModelChange, ModelStore, PistarFile, PropertySchema, PropertyShape } from "@istar-ts/core";
1
+ import { AnyIstarModel, AnyPaletteDescription, CustomProperties, DiagnosticSeverity, ElementKind, FileMetamodel, GoalDiagnostic, IstarModel, LinkKind, Metamodel, ModelChange, ModelStore, PistarFile, PropertySchema, PropertyShape } from "@istar-ts/core";
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>>;
@@ -216,8 +216,35 @@ export interface UpdateLinkDisplayOperation extends Operation {
216
216
  readonly linkId: string;
217
217
  readonly patch: Readonly<Record<string, unknown>>;
218
218
  }
219
+ /**
220
+ * Changes the kind of a set of links: each listed link's id, source, target, order in the file,
221
+ * name, value, custom properties and display are kept; other links are untouched. One undo step
222
+ * for the whole set, all or nothing. A link whose kind is already `elementTypeId` is a no-op;
223
+ * an empty `linkIds` is a no-op too. Rejected (model untouched) if `canLink` refuses any listed
224
+ * link's new kind for its unchanged source and target (e.g. a mixed-refinement rule), or if the
225
+ * link keeps a `value` the new kind's `possibleLabels` disallows.
226
+ */
227
+ export interface ChangeLinkKindOperation<LK extends string = LinkKind> extends Operation {
228
+ readonly kind: "istar.changeLinkKind";
229
+ readonly linkIds: readonly string[];
230
+ /** The links' new kind, e.g. `istar.OrRefinementLink`. */
231
+ readonly elementTypeId: LK;
232
+ }
233
+ /**
234
+ * Sets (or, with `null` or an empty block, removes) the model file's own `"metamodel"` block:
235
+ * custom element kinds, groupers, link kinds with their rules and line style (`withFileMetamodel`
236
+ * in `@istar-ts/core`). One undo step; undo restores the previous block exactly. Rejected (model
237
+ * untouched) if the block is malformed, collides with the host's kinds, or drops a kind an
238
+ * element or link still uses. A host's dialect-specific checks (e.g. goal-controller's own
239
+ * validation of the block) run as an `ActionGuard` on this operation's kind, so their refusal
240
+ * also comes back as diagnostics.
241
+ */
242
+ export interface SetFileMetamodelOperation extends Operation {
243
+ readonly kind: "istar.setFileMetamodel";
244
+ readonly block: FileMetamodel | null;
245
+ }
219
246
  /** Every operation, keyed by `kind`. */
220
- 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;
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;
221
248
  export interface RequestModelAction extends RequestAction {
222
249
  readonly kind: "requestModel";
223
250
  readonly options?: Args;
@@ -477,8 +504,20 @@ export interface ActionDispatcher<EK extends string = ElementKind, LK extends st
477
504
  * the operation; others are returned with the result.
478
505
  */
479
506
  export type ActionGuard<EK extends string = ElementKind, LK extends string = LinkKind> = (operation: IstarOperation<EK, LK>, model: IstarModel<EK, LK>) => readonly GoalDiagnostic[] | void;
507
+ /**
508
+ * A host's rule, run after each operation is applied (and after each operation of a compound
509
+ * one, plus once more on the compound's own result), inside the operation's transaction. Sees
510
+ * the model as the operation leaves it (`modelAfter`) and as it was before (`modelBefore`), so
511
+ * it can check things a pre-guard can't without re-applying the operation itself (goal-controller's
512
+ * edgeV2 engine check is the motivating case). Diagnostics of severity `error` roll the
513
+ * transaction back: the model is untouched and there is no undo entry. Others are returned with
514
+ * the result, merged with pre-guards' warnings.
515
+ */
516
+ export type ActionPostGuard<EK extends string = ElementKind, LK extends string = LinkKind> = (operation: IstarOperation<EK, LK>, modelAfter: IstarModel<EK, LK>, modelBefore: IstarModel<EK, LK>) => readonly GoalDiagnostic[] | void;
480
517
  export interface ActionDispatcherOptions<EK extends string = ElementKind, LK extends string = LinkKind> {
481
518
  readonly guards?: readonly ActionGuard<EK, LK>[];
519
+ /** Run after each operation is applied; see `ActionPostGuard`. */
520
+ readonly postGuards?: readonly ActionPostGuard<EK, LK>[];
482
521
  /** What `RequestContextActions` offers. Default: `DEFAULT_PALETTE` (iStar 2.0's). */
483
522
  readonly palette?: AnyPaletteDescription;
484
523
  /**
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnyIstarModel, AnyPaletteDescription, CustomProperties, DiagnosticSeverity, ElementKind, GoalDiagnostic, IstarModel, LinkKind, Metamodel, ModelChange, ModelStore, PistarFile, PropertySchema, PropertyShape } from "@istar-ts/core";
1
+ import { AnyIstarModel, AnyPaletteDescription, CustomProperties, DiagnosticSeverity, ElementKind, FileMetamodel, GoalDiagnostic, IstarModel, LinkKind, Metamodel, ModelChange, ModelStore, PistarFile, PropertySchema, PropertyShape } from "@istar-ts/core";
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>>;
@@ -216,8 +216,35 @@ export interface UpdateLinkDisplayOperation extends Operation {
216
216
  readonly linkId: string;
217
217
  readonly patch: Readonly<Record<string, unknown>>;
218
218
  }
219
+ /**
220
+ * Changes the kind of a set of links: each listed link's id, source, target, order in the file,
221
+ * name, value, custom properties and display are kept; other links are untouched. One undo step
222
+ * for the whole set, all or nothing. A link whose kind is already `elementTypeId` is a no-op;
223
+ * an empty `linkIds` is a no-op too. Rejected (model untouched) if `canLink` refuses any listed
224
+ * link's new kind for its unchanged source and target (e.g. a mixed-refinement rule), or if the
225
+ * link keeps a `value` the new kind's `possibleLabels` disallows.
226
+ */
227
+ export interface ChangeLinkKindOperation<LK extends string = LinkKind> extends Operation {
228
+ readonly kind: "istar.changeLinkKind";
229
+ readonly linkIds: readonly string[];
230
+ /** The links' new kind, e.g. `istar.OrRefinementLink`. */
231
+ readonly elementTypeId: LK;
232
+ }
233
+ /**
234
+ * Sets (or, with `null` or an empty block, removes) the model file's own `"metamodel"` block:
235
+ * custom element kinds, groupers, link kinds with their rules and line style (`withFileMetamodel`
236
+ * in `@istar-ts/core`). One undo step; undo restores the previous block exactly. Rejected (model
237
+ * untouched) if the block is malformed, collides with the host's kinds, or drops a kind an
238
+ * element or link still uses. A host's dialect-specific checks (e.g. goal-controller's own
239
+ * validation of the block) run as an `ActionGuard` on this operation's kind, so their refusal
240
+ * also comes back as diagnostics.
241
+ */
242
+ export interface SetFileMetamodelOperation extends Operation {
243
+ readonly kind: "istar.setFileMetamodel";
244
+ readonly block: FileMetamodel | null;
245
+ }
219
246
  /** Every operation, keyed by `kind`. */
220
- 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;
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;
221
248
  export interface RequestModelAction extends RequestAction {
222
249
  readonly kind: "requestModel";
223
250
  readonly options?: Args;
@@ -477,8 +504,20 @@ export interface ActionDispatcher<EK extends string = ElementKind, LK extends st
477
504
  * the operation; others are returned with the result.
478
505
  */
479
506
  export type ActionGuard<EK extends string = ElementKind, LK extends string = LinkKind> = (operation: IstarOperation<EK, LK>, model: IstarModel<EK, LK>) => readonly GoalDiagnostic[] | void;
507
+ /**
508
+ * A host's rule, run after each operation is applied (and after each operation of a compound
509
+ * one, plus once more on the compound's own result), inside the operation's transaction. Sees
510
+ * the model as the operation leaves it (`modelAfter`) and as it was before (`modelBefore`), so
511
+ * it can check things a pre-guard can't without re-applying the operation itself (goal-controller's
512
+ * edgeV2 engine check is the motivating case). Diagnostics of severity `error` roll the
513
+ * transaction back: the model is untouched and there is no undo entry. Others are returned with
514
+ * the result, merged with pre-guards' warnings.
515
+ */
516
+ export type ActionPostGuard<EK extends string = ElementKind, LK extends string = LinkKind> = (operation: IstarOperation<EK, LK>, modelAfter: IstarModel<EK, LK>, modelBefore: IstarModel<EK, LK>) => readonly GoalDiagnostic[] | void;
480
517
  export interface ActionDispatcherOptions<EK extends string = ElementKind, LK extends string = LinkKind> {
481
518
  readonly guards?: readonly ActionGuard<EK, LK>[];
519
+ /** Run after each operation is applied; see `ActionPostGuard`. */
520
+ readonly postGuards?: readonly ActionPostGuard<EK, LK>[];
482
521
  /** What `RequestContextActions` offers. Default: `DEFAULT_PALETTE` (iStar 2.0's). */
483
522
  readonly palette?: AnyPaletteDescription;
484
523
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_PALETTE, DIAGNOSTIC_SEVERITIES, FILE_METAMODEL_KEY, LINK_SELECTOR, ModelOperationError, canLink, connectResultToDiagnostic, createEmptyModel, dependencyEnds, effectiveLinkKind, elementBehavesLike, inheritMetamodel, inheritSourceLayout, linkBehavesLike, metamodelOf, parsePistar, toPistarObject, validateModel, validateModelProperties } from "@istar-ts/core";
1
+ import { DEFAULT_PALETTE, 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";
2
2
  //#region src/actions.ts
3
3
  const OPERATION_KIND_LIST = [
4
4
  "createNode",
@@ -16,7 +16,9 @@ const OPERATION_KIND_LIST = [
16
16
  "istar.updateProperties",
17
17
  "istar.updateDisplay",
18
18
  "istar.setLinkValue",
19
- "istar.updateLinkDisplay"
19
+ "istar.updateLinkDisplay",
20
+ "istar.changeLinkKind",
21
+ "istar.setFileMetamodel"
20
22
  ];
21
23
  const REQUEST_KIND_LIST = [
22
24
  "requestModel",
@@ -273,6 +275,7 @@ function linkIssueDiagnostics(model) {
273
275
  function createActionDispatcher(store, options = {}) {
274
276
  const s = store;
275
277
  const guards = options.guards ?? [];
278
+ const postGuards = options.postGuards ?? [];
276
279
  const palette = options.palette ?? DEFAULT_PALETTE;
277
280
  const sourceModelName = options.sourceModelName ?? "";
278
281
  const listeners = /* @__PURE__ */ new Set();
@@ -318,8 +321,18 @@ function createActionDispatcher(store, options = {}) {
318
321
  for (const guard of guards) for (const d of guard(operation, s.getModel()) ?? []) (d.severity === "error" ? errors : warnings).push(d);
319
322
  if (errors.length > 0) throw new Rejection(errors);
320
323
  };
324
+ const runPostGuards = (operation, before, warnings) => {
325
+ const errors = [];
326
+ for (const guard of postGuards) for (const d of guard(operation, s.getModel(), before) ?? []) (d.severity === "error" ? errors : warnings).push(d);
327
+ if (errors.length > 0) throw new Rejection(errors);
328
+ };
321
329
  const apply = (operation, warnings) => {
322
330
  runGuards(operation, warnings);
331
+ const before = s.getModel();
332
+ doApply(operation, warnings);
333
+ runPostGuards(operation, before, warnings);
334
+ };
335
+ const doApply = (operation, warnings) => {
323
336
  const model = s.getModel();
324
337
  const meta = metaOf(model);
325
338
  switch (operation.kind) {
@@ -486,6 +499,45 @@ function createActionDispatcher(store, options = {}) {
486
499
  s.updateLink(linkId, { label: value });
487
500
  return;
488
501
  }
502
+ case "istar.changeLinkKind": {
503
+ const { linkIds, elementTypeId: kind } = operation;
504
+ if (linkIds.length === 0) return;
505
+ const ids = new Set(linkIds);
506
+ const links = [...ids].map((id) => model.links.get(id) ?? reject(`unknown link "${id}"`, id, "unknown-element"));
507
+ const definition = meta.links.get(kind) ?? reject(`unknown link kind "${kind}" in ${meta.name}`, "", "unknown-kind");
508
+ const toChange = links.filter((link) => link.kind !== kind);
509
+ if (toChange.length === 0) return;
510
+ const remaining = new Map(model.links);
511
+ for (const id of ids) remaining.delete(id);
512
+ const without = withLinks(model, remaining);
513
+ const values = definition.info.possibleLabels;
514
+ for (const link of toChange) {
515
+ const check = canLink(without, link.source, link.target, kind);
516
+ if (!check.ok) rejectCheck(check, {
517
+ source: link.source,
518
+ target: link.target
519
+ });
520
+ if (link.label != null && values && !values.includes(link.label)) reject(`"${link.label}" is not a value of a ${definition.label} link (${values.join(", ")})`, link.id, "invalid-value");
521
+ }
522
+ const next = new Map(model.links);
523
+ for (const link of toChange) next.set(link.id, {
524
+ ...link,
525
+ kind
526
+ });
527
+ s.replace(withLinks(model, next));
528
+ return;
529
+ }
530
+ case "istar.setFileMetamodel": {
531
+ let next;
532
+ try {
533
+ next = withFileMetamodel(model, operation.block);
534
+ } catch (error) {
535
+ if (error instanceof MetamodelError) reject(error.message, "", "invalid-metamodel");
536
+ throw error;
537
+ }
538
+ s.replace(next);
539
+ return;
540
+ }
489
541
  default: reject(`unknown operation "${operation.kind}"`, "", "unknown-action");
490
542
  }
491
543
  };