@istar-ts/core 0.13.7 → 0.14.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.
package/dist/index.d.cts CHANGED
@@ -208,6 +208,12 @@ export interface HubLinkParts {
208
208
  readonly leg: string;
209
209
  readonly end: string;
210
210
  }
211
+ /**
212
+ * The kinds an n-ary link kind `K` generates under the default names: its hub element kind
213
+ * (`<K>Hub`), its leg (`<K>Start`) and its end (`<K>End`). A schema for one of them is declared
214
+ * like any other: `defineProperties('kaos.RefinementLinkStart', { … })`.
215
+ */
216
+ export type HubPartKind<K extends string> = `${K}Hub` | `${K}Start` | `${K}End`;
211
217
  /**
212
218
  * Declarative link rules, as piStar-ext's "Create a new Construct" dialog collects them:
213
219
  * which kinds may be the source and target. Entries are kind names (`istar.Goal`,
@@ -1087,14 +1093,25 @@ export interface PropertyOptions<T> {
1087
1093
  description?: string;
1088
1094
  }
1089
1095
  export interface PropertyType<T> {
1090
- readonly type: "string" | "number" | "boolean" | "enum";
1096
+ readonly type: "string" | "number" | "boolean" | "enum" | "reference";
1091
1097
  readonly options: PropertyOptions<T>;
1092
1098
  readonly values?: readonly string[];
1093
1099
  readonly integer?: boolean;
1094
1100
  readonly min?: number;
1095
1101
  readonly max?: number;
1102
+ /**
1103
+ * Numbers with `max: 1` (a probability, a rate): the value may also be written as a
1104
+ * percentage (`20.00%`). CAIRIS writes `0.2`, KAOSTools `.2` and `20.00%`.
1105
+ */
1106
+ readonly percent?: boolean;
1107
+ /** References: the element or link kinds the id may name; absent, any kind. */
1108
+ readonly kinds?: readonly string[];
1096
1109
  parse(raw: string): PropertyResult<T>;
1097
- serialize(value: T): string;
1110
+ /**
1111
+ * The value as a string. `previous`, the raw string being replaced, lets a type keep the form
1112
+ * it was read in (a number read as `20.00%` is written as a percentage).
1113
+ */
1114
+ serialize(value: T, previous?: string): string;
1098
1115
  }
1099
1116
  export declare function string(options?: PropertyOptions<string>): PropertyType<string>;
1100
1117
  export declare function number(options?: PropertyOptions<number> & {
@@ -1104,11 +1121,20 @@ export declare function number(options?: PropertyOptions<number> & {
1104
1121
  }): PropertyType<number>;
1105
1122
  export declare function boolean(options?: PropertyOptions<boolean>): PropertyType<boolean>;
1106
1123
  export declare function enumOf<const V extends readonly string[]>(values: V, options?: PropertyOptions<V[number]>): PropertyType<V[number]>;
1124
+ /**
1125
+ * A value that is another element's or link's id, e.g. the goal a KAOS resolution restores. The
1126
+ * schema only checks it is non-empty; `validateModelProperties` reports an id that isn't in the
1127
+ * model or names a kind outside `kinds`.
1128
+ */
1129
+ export declare function reference(options?: PropertyOptions<string> & {
1130
+ kinds?: readonly string[];
1131
+ }): PropertyType<string>;
1107
1132
  export declare const prop: {
1108
1133
  string: typeof string;
1109
1134
  number: typeof number;
1110
1135
  boolean: typeof boolean;
1111
1136
  enum: typeof enumOf;
1137
+ reference: typeof reference;
1112
1138
  };
1113
1139
  export type PropertyShape = Record<string, PropertyType<unknown>>;
1114
1140
  export type InferProperties<S extends PropertyShape> = { [K in keyof S]: S[K] extends PropertyType<infer T> ? T : never; };
package/dist/index.d.ts CHANGED
@@ -208,6 +208,12 @@ export interface HubLinkParts {
208
208
  readonly leg: string;
209
209
  readonly end: string;
210
210
  }
211
+ /**
212
+ * The kinds an n-ary link kind `K` generates under the default names: its hub element kind
213
+ * (`<K>Hub`), its leg (`<K>Start`) and its end (`<K>End`). A schema for one of them is declared
214
+ * like any other: `defineProperties('kaos.RefinementLinkStart', { … })`.
215
+ */
216
+ export type HubPartKind<K extends string> = `${K}Hub` | `${K}Start` | `${K}End`;
211
217
  /**
212
218
  * Declarative link rules, as piStar-ext's "Create a new Construct" dialog collects them:
213
219
  * which kinds may be the source and target. Entries are kind names (`istar.Goal`,
@@ -1087,14 +1093,25 @@ export interface PropertyOptions<T> {
1087
1093
  description?: string;
1088
1094
  }
1089
1095
  export interface PropertyType<T> {
1090
- readonly type: "string" | "number" | "boolean" | "enum";
1096
+ readonly type: "string" | "number" | "boolean" | "enum" | "reference";
1091
1097
  readonly options: PropertyOptions<T>;
1092
1098
  readonly values?: readonly string[];
1093
1099
  readonly integer?: boolean;
1094
1100
  readonly min?: number;
1095
1101
  readonly max?: number;
1102
+ /**
1103
+ * Numbers with `max: 1` (a probability, a rate): the value may also be written as a
1104
+ * percentage (`20.00%`). CAIRIS writes `0.2`, KAOSTools `.2` and `20.00%`.
1105
+ */
1106
+ readonly percent?: boolean;
1107
+ /** References: the element or link kinds the id may name; absent, any kind. */
1108
+ readonly kinds?: readonly string[];
1096
1109
  parse(raw: string): PropertyResult<T>;
1097
- serialize(value: T): string;
1110
+ /**
1111
+ * The value as a string. `previous`, the raw string being replaced, lets a type keep the form
1112
+ * it was read in (a number read as `20.00%` is written as a percentage).
1113
+ */
1114
+ serialize(value: T, previous?: string): string;
1098
1115
  }
1099
1116
  export declare function string(options?: PropertyOptions<string>): PropertyType<string>;
1100
1117
  export declare function number(options?: PropertyOptions<number> & {
@@ -1104,11 +1121,20 @@ export declare function number(options?: PropertyOptions<number> & {
1104
1121
  }): PropertyType<number>;
1105
1122
  export declare function boolean(options?: PropertyOptions<boolean>): PropertyType<boolean>;
1106
1123
  export declare function enumOf<const V extends readonly string[]>(values: V, options?: PropertyOptions<V[number]>): PropertyType<V[number]>;
1124
+ /**
1125
+ * A value that is another element's or link's id, e.g. the goal a KAOS resolution restores. The
1126
+ * schema only checks it is non-empty; `validateModelProperties` reports an id that isn't in the
1127
+ * model or names a kind outside `kinds`.
1128
+ */
1129
+ export declare function reference(options?: PropertyOptions<string> & {
1130
+ kinds?: readonly string[];
1131
+ }): PropertyType<string>;
1107
1132
  export declare const prop: {
1108
1133
  string: typeof string;
1109
1134
  number: typeof number;
1110
1135
  boolean: typeof boolean;
1111
1136
  enum: typeof enumOf;
1137
+ reference: typeof reference;
1112
1138
  };
1113
1139
  export type PropertyShape = Record<string, PropertyType<unknown>>;
1114
1140
  export type InferProperties<S extends PropertyShape> = { [K in keyof S]: S[K] extends PropertyType<infer T> ? T : never; };
package/dist/index.js CHANGED
@@ -2499,19 +2499,50 @@ function string(options) {
2499
2499
  }
2500
2500
  };
2501
2501
  }
2502
+ /** `20.00%` → 20.00 as text, or undefined when `raw` isn't a percentage. */
2503
+ function percentBody(raw) {
2504
+ const text = raw.trim();
2505
+ if (!text.endsWith("%")) return void 0;
2506
+ const body = text.slice(0, -1).trim();
2507
+ return body === "" ? void 0 : body;
2508
+ }
2509
+ /** Digits after the decimal point of a number written as text (`20.00` → 2). */
2510
+ function decimals(text) {
2511
+ const dot = text.indexOf(".");
2512
+ return dot === -1 ? 0 : text.length - dot - 1;
2513
+ }
2514
+ /** `value` without floating-point noise from a ×100 (0.35 * 100 = 35.00000000000001). */
2515
+ function plain(value) {
2516
+ return String(Number(value.toPrecision(12)));
2517
+ }
2518
+ /** Writes `value` in the form of `previous`: a percentage, a leading dot, or plain. */
2519
+ function formatNumber(value, previous, percent) {
2520
+ if (previous === void 0) return String(value);
2521
+ const body = percent ? percentBody(previous) : void 0;
2522
+ if (body !== void 0) {
2523
+ const text = plain(value * 100);
2524
+ const places = decimals(body);
2525
+ return `${places > decimals(text) ? (value * 100).toFixed(places) : text}%`;
2526
+ }
2527
+ if (/^\s*[+-]?\.\d/.test(previous) && Math.abs(value) < 1) return String(value).replace(/^(-?)0\./, "$1.");
2528
+ return String(value);
2529
+ }
2502
2530
  function number(options) {
2503
2531
  const opts = options ?? {};
2504
2532
  const integer = options?.integer;
2505
2533
  const min = options?.min;
2506
2534
  const max = options?.max;
2535
+ const percent = max === 1;
2507
2536
  return {
2508
2537
  type: "number",
2509
2538
  options: opts,
2510
2539
  integer,
2511
2540
  min,
2512
2541
  max,
2542
+ ...percent ? { percent } : {},
2513
2543
  parse(raw) {
2514
- const value = Number(raw);
2544
+ const body = percent ? percentBody(raw) : void 0;
2545
+ const value = body !== void 0 ? Number(body) / 100 : Number(raw);
2515
2546
  if (!Number.isFinite(value)) return {
2516
2547
  ok: false,
2517
2548
  error: "must be a finite number"
@@ -2533,8 +2564,8 @@ function number(options) {
2533
2564
  value
2534
2565
  };
2535
2566
  },
2536
- serialize(value) {
2537
- return String(value);
2567
+ serialize(value, previous) {
2568
+ return formatNumber(value, previous, percent);
2538
2569
  }
2539
2570
  };
2540
2571
  }
@@ -2581,11 +2612,34 @@ function enumOf(values, options) {
2581
2612
  }
2582
2613
  };
2583
2614
  }
2615
+ /**
2616
+ * A value that is another element's or link's id, e.g. the goal a KAOS resolution restores. The
2617
+ * schema only checks it is non-empty; `validateModelProperties` reports an id that isn't in the
2618
+ * model or names a kind outside `kinds`.
2619
+ */
2620
+ function reference(options) {
2621
+ const { kinds, ...opts } = options ?? {};
2622
+ return {
2623
+ type: "reference",
2624
+ options: opts,
2625
+ ...kinds ? { kinds } : {},
2626
+ parse(raw) {
2627
+ return {
2628
+ ok: true,
2629
+ value: raw
2630
+ };
2631
+ },
2632
+ serialize(value) {
2633
+ return value;
2634
+ }
2635
+ };
2636
+ }
2584
2637
  const prop = {
2585
2638
  string,
2586
2639
  number,
2587
2640
  boolean,
2588
- enum: enumOf
2641
+ enum: enumOf,
2642
+ reference
2589
2643
  };
2590
2644
  function isCustomPropertiesRecord(source) {
2591
2645
  if ("customProperties" in source) return false;
@@ -2627,7 +2681,7 @@ function readShape(shape, source) {
2627
2681
  }
2628
2682
  issues.push({
2629
2683
  key,
2630
- message: field.type === "boolean" ? "must be true or false" : field.type === "enum" ? `must be one of: ${field.values?.join(", ") ?? ""}` : "must be a finite number"
2684
+ message: field.type === "boolean" ? "must be true or false" : field.type === "enum" ? `must be one of: ${field.values?.join(", ") ?? ""}` : field.type === "reference" ? "must be an element or link id" : "must be a finite number"
2631
2685
  });
2632
2686
  continue;
2633
2687
  }
@@ -2646,6 +2700,15 @@ function readShape(shape, source) {
2646
2700
  issues
2647
2701
  };
2648
2702
  }
2703
+ /**
2704
+ * A patched value as a string, written back as read: the current string is kept when it already
2705
+ * holds the value (`.2` stays `.2`), and otherwise the type writes the value in its form.
2706
+ */
2707
+ function writeField(field, value, current) {
2708
+ const parsed = field.parse(current);
2709
+ if (parsed.ok && Object.is(parsed.value, value)) return current;
2710
+ return field.serialize(value, current);
2711
+ }
2649
2712
  function defineProperties(kind, shape, options) {
2650
2713
  const crossValidate = options?.validate;
2651
2714
  return {
@@ -2667,7 +2730,7 @@ function defineProperties(kind, shape, options) {
2667
2730
  const patched = patch[key];
2668
2731
  if (patched === void 0) continue;
2669
2732
  const field = shape[key];
2670
- if (field) result[key] = field.serialize(patched);
2733
+ if (field) result[key] = writeField(field, patched, value);
2671
2734
  } else result[key] = value;
2672
2735
  seen.add(key);
2673
2736
  }
@@ -2701,20 +2764,39 @@ function validateModelProperties(model, schemas) {
2701
2764
  byKind.set(schema.kind, list);
2702
2765
  }
2703
2766
  const issues = [];
2704
- for (const element of model.elements.values()) {
2705
- const kindSchemas = byKind.get(element.kind);
2706
- if (!kindSchemas) continue;
2707
- for (const schema of kindSchemas) for (const issue of schema.validate(element)) issues.push({
2708
- ...issue,
2709
- id: element.id
2767
+ const check = (owner) => {
2768
+ const kindSchemas = byKind.get(owner.kind);
2769
+ if (!kindSchemas) return;
2770
+ for (const schema of kindSchemas) {
2771
+ for (const issue of schema.validate(owner)) issues.push({
2772
+ ...issue,
2773
+ id: owner.id
2774
+ });
2775
+ for (const issue of referenceIssues(model, schema.shape, owner.customProperties)) issues.push({
2776
+ ...issue,
2777
+ id: owner.id
2778
+ });
2779
+ }
2780
+ };
2781
+ for (const element of model.elements.values()) check(element);
2782
+ for (const link of model.links.values()) check(link);
2783
+ return issues;
2784
+ }
2785
+ /** References (`prop.reference`) to ids that aren't in the model, or to the wrong kind. */
2786
+ function referenceIssues(model, shape, custom) {
2787
+ const issues = [];
2788
+ for (const [key, field] of Object.entries(shape)) {
2789
+ if (field.type !== "reference") continue;
2790
+ const id = custom?.[key];
2791
+ if (id === void 0 || id === "") continue;
2792
+ const target = model.elements.get(id) ?? model.links.get(id);
2793
+ if (!target) issues.push({
2794
+ key,
2795
+ message: `refers to "${id}", which is not in the model`
2710
2796
  });
2711
- }
2712
- for (const link of model.links.values()) {
2713
- const kindSchemas = byKind.get(link.kind);
2714
- if (!kindSchemas) continue;
2715
- for (const schema of kindSchemas) for (const issue of schema.validate(link)) issues.push({
2716
- ...issue,
2717
- id: link.id
2797
+ else if (field.kinds && !field.kinds.includes(target.kind)) issues.push({
2798
+ key,
2799
+ message: `must refer to a ${field.kinds.join(" or ")}`
2718
2800
  });
2719
2801
  }
2720
2802
  return issues;
@@ -2890,6 +2972,6 @@ function fromNodeIdDiagnostics(diagnostics) {
2890
2972
  return result;
2891
2973
  }
2892
2974
  //#endregion
2893
- export { ACTOR_KINDS, ACTOR_LINK_KINDS, CONTRIBUTION_LABELS, DEFAULT_DIAGRAM, DEFAULT_ELEMENT_SIZE, DEPENDENCY_LINK_KINDS, DIAGNOSTIC_SEVERITIES, DIAGRAM_LAYOUT_KEY, DSM3_FILE_KEYS, DSM3_TOOL, ELEMENT_KINDS, FILE_METAMODEL_KEY, HUB_ENTRY_KEY, ISTAR_2_0, ISTAR_2_0_DECLARATION, ISTAR_PREFIX, ISTAR_VERSION, LINK_KINDS, LINK_KIND_INFO, LINK_SELECTOR, METAMODEL_VERSION, MetamodelError, ModelOperationError, NODE_KINDS, NODE_KIND_INFO, NODE_LINK_KINDS, PISTAR_TOOL, PistarParseError, PistarWriteError, addDependency, addElement, addLeg, boolean, canLink, childrenOf, compareSeverity, connect, createDiagnosticsStore, createEmptyModel, createModelStore, defaultHubPosition, defaultIdGenerator, defineMetamodel, defineMetamodelExtension, defineProperties, dependencyEnds, dependencyLinksOf, detectFramework, detectPrefix, disconnect, effectiveElementKind, effectiveLinkKind, elementBehavesLike, elementCenter, elementKindDefinition, enumOf, extendMetamodel, fileMetamodelExtension, fileMetamodelOf, fromLspDiagnostic, fromLspDiagnostics, fromNodeIdDiagnostic, fromNodeIdDiagnostics, getSourceLayout, groupDiagnostics, hostMetamodelOf, hubEntryLayoutKey, hubLinkOf, hubOfLink, inheritMetamodel, inheritSourceLayout, isActor, isActorIn, isActorKind, isActorKindIn, isActorLinkKind, isContributionLabel, isDependencyLinkKind, isElementKind, isHubElement, isLinkKind, isLinkRef, isNode, isNodeIn, isNodeKind, isNodeKindIn, isNodeLinkKind, linkBehavesLike, linkKindDefinition, linkTargetOf, linksOf, mergeDiagnostics, metamodelOf, moveElement, nestElement, number, parsePistar, prop, removeElement, removeElements, removeLeg, setHostMetamodel, shortKindName, string, targetsLinks, toPistar, toPistarObject, updateDiagram, updateElement, updateLink, validateFileMetamodel, validateModel, validateModelProperties, withFileMetamodel, withMetamodel, worstDiagnosticSeverity };
2975
+ export { ACTOR_KINDS, ACTOR_LINK_KINDS, CONTRIBUTION_LABELS, DEFAULT_DIAGRAM, DEFAULT_ELEMENT_SIZE, DEPENDENCY_LINK_KINDS, DIAGNOSTIC_SEVERITIES, DIAGRAM_LAYOUT_KEY, DSM3_FILE_KEYS, DSM3_TOOL, ELEMENT_KINDS, FILE_METAMODEL_KEY, HUB_ENTRY_KEY, ISTAR_2_0, ISTAR_2_0_DECLARATION, ISTAR_PREFIX, ISTAR_VERSION, LINK_KINDS, LINK_KIND_INFO, LINK_SELECTOR, METAMODEL_VERSION, MetamodelError, ModelOperationError, NODE_KINDS, NODE_KIND_INFO, NODE_LINK_KINDS, PISTAR_TOOL, PistarParseError, PistarWriteError, addDependency, addElement, addLeg, boolean, canLink, childrenOf, compareSeverity, connect, createDiagnosticsStore, createEmptyModel, createModelStore, defaultHubPosition, defaultIdGenerator, defineMetamodel, defineMetamodelExtension, defineProperties, dependencyEnds, dependencyLinksOf, detectFramework, detectPrefix, disconnect, effectiveElementKind, effectiveLinkKind, elementBehavesLike, elementCenter, elementKindDefinition, enumOf, extendMetamodel, fileMetamodelExtension, fileMetamodelOf, fromLspDiagnostic, fromLspDiagnostics, fromNodeIdDiagnostic, fromNodeIdDiagnostics, getSourceLayout, groupDiagnostics, hostMetamodelOf, hubEntryLayoutKey, hubLinkOf, hubOfLink, inheritMetamodel, inheritSourceLayout, isActor, isActorIn, isActorKind, isActorKindIn, isActorLinkKind, isContributionLabel, isDependencyLinkKind, isElementKind, isHubElement, isLinkKind, isLinkRef, isNode, isNodeIn, isNodeKind, isNodeKindIn, isNodeLinkKind, linkBehavesLike, linkKindDefinition, linkTargetOf, linksOf, mergeDiagnostics, metamodelOf, moveElement, nestElement, number, parsePistar, prop, reference, removeElement, removeElements, removeLeg, setHostMetamodel, shortKindName, string, targetsLinks, toPistar, toPistarObject, updateDiagram, updateElement, updateLink, validateFileMetamodel, validateModel, validateModelProperties, withFileMetamodel, withMetamodel, worstDiagnosticSeverity };
2894
2976
 
2895
2977
  //# sourceMappingURL=index.js.map