@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/README.md +19 -1
- package/dist/index.cjs +102 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.js +102 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -176,7 +176,25 @@ const modelIssues = validateModelProperties(model, [resourceSchema]);
|
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
`read` / `validate` accept `customProperties`, a full element/link, or `undefined`. `write` merges
|
|
179
|
-
patches, preserves unknown keys, and serializes typed values back to strings.
|
|
179
|
+
patches, preserves unknown keys, and serializes typed values back to strings. A value that
|
|
180
|
+
didn't change keeps its string as read (`.2` stays `.2`).
|
|
181
|
+
|
|
182
|
+
Two optional hints:
|
|
183
|
+
|
|
184
|
+
- `prop.number({ min: 0, max: 1 })`: with `max: 1` (a probability) the reader also accepts a
|
|
185
|
+
percentage, so `0.2`, `.2` and `20.00%` read as 0.2. A changed value is written in the form
|
|
186
|
+
the old one was read in (`20.00%` → `35.00%`, `.2` → `.35`).
|
|
187
|
+
- `prop.reference({ kinds })`: the value is another element's (or link's) id. The schema only
|
|
188
|
+
checks it isn't empty; `validateModelProperties` reports an id that isn't in the model, or
|
|
189
|
+
whose kind isn't in `kinds`.
|
|
190
|
+
|
|
191
|
+
Formulas (a KAOS `FormalSpec`, a `BoundaryCondition`) stay `prop.string`: core has no formula
|
|
192
|
+
language.
|
|
193
|
+
|
|
194
|
+
A schema can be declared for the kinds an n-ary link generates (its hub, legs and end) like for
|
|
195
|
+
any kind; `HubPartKind<'kaos.RefinementLink'>` is `'kaos.RefinementLinkHub' |
|
|
196
|
+
'kaos.RefinementLinkStart' | 'kaos.RefinementLinkEnd'`. Legs keep their `customProperties` on
|
|
197
|
+
disk.
|
|
180
198
|
|
|
181
199
|
## Extending the metamodel
|
|
182
200
|
|
package/dist/index.cjs
CHANGED
|
@@ -2500,19 +2500,50 @@ function string(options) {
|
|
|
2500
2500
|
}
|
|
2501
2501
|
};
|
|
2502
2502
|
}
|
|
2503
|
+
/** `20.00%` → 20.00 as text, or undefined when `raw` isn't a percentage. */
|
|
2504
|
+
function percentBody(raw) {
|
|
2505
|
+
const text = raw.trim();
|
|
2506
|
+
if (!text.endsWith("%")) return void 0;
|
|
2507
|
+
const body = text.slice(0, -1).trim();
|
|
2508
|
+
return body === "" ? void 0 : body;
|
|
2509
|
+
}
|
|
2510
|
+
/** Digits after the decimal point of a number written as text (`20.00` → 2). */
|
|
2511
|
+
function decimals(text) {
|
|
2512
|
+
const dot = text.indexOf(".");
|
|
2513
|
+
return dot === -1 ? 0 : text.length - dot - 1;
|
|
2514
|
+
}
|
|
2515
|
+
/** `value` without floating-point noise from a ×100 (0.35 * 100 = 35.00000000000001). */
|
|
2516
|
+
function plain(value) {
|
|
2517
|
+
return String(Number(value.toPrecision(12)));
|
|
2518
|
+
}
|
|
2519
|
+
/** Writes `value` in the form of `previous`: a percentage, a leading dot, or plain. */
|
|
2520
|
+
function formatNumber(value, previous, percent) {
|
|
2521
|
+
if (previous === void 0) return String(value);
|
|
2522
|
+
const body = percent ? percentBody(previous) : void 0;
|
|
2523
|
+
if (body !== void 0) {
|
|
2524
|
+
const text = plain(value * 100);
|
|
2525
|
+
const places = decimals(body);
|
|
2526
|
+
return `${places > decimals(text) ? (value * 100).toFixed(places) : text}%`;
|
|
2527
|
+
}
|
|
2528
|
+
if (/^\s*[+-]?\.\d/.test(previous) && Math.abs(value) < 1) return String(value).replace(/^(-?)0\./, "$1.");
|
|
2529
|
+
return String(value);
|
|
2530
|
+
}
|
|
2503
2531
|
function number(options) {
|
|
2504
2532
|
const opts = options ?? {};
|
|
2505
2533
|
const integer = options?.integer;
|
|
2506
2534
|
const min = options?.min;
|
|
2507
2535
|
const max = options?.max;
|
|
2536
|
+
const percent = max === 1;
|
|
2508
2537
|
return {
|
|
2509
2538
|
type: "number",
|
|
2510
2539
|
options: opts,
|
|
2511
2540
|
integer,
|
|
2512
2541
|
min,
|
|
2513
2542
|
max,
|
|
2543
|
+
...percent ? { percent } : {},
|
|
2514
2544
|
parse(raw) {
|
|
2515
|
-
const
|
|
2545
|
+
const body = percent ? percentBody(raw) : void 0;
|
|
2546
|
+
const value = body !== void 0 ? Number(body) / 100 : Number(raw);
|
|
2516
2547
|
if (!Number.isFinite(value)) return {
|
|
2517
2548
|
ok: false,
|
|
2518
2549
|
error: "must be a finite number"
|
|
@@ -2534,8 +2565,8 @@ function number(options) {
|
|
|
2534
2565
|
value
|
|
2535
2566
|
};
|
|
2536
2567
|
},
|
|
2537
|
-
serialize(value) {
|
|
2538
|
-
return
|
|
2568
|
+
serialize(value, previous) {
|
|
2569
|
+
return formatNumber(value, previous, percent);
|
|
2539
2570
|
}
|
|
2540
2571
|
};
|
|
2541
2572
|
}
|
|
@@ -2582,11 +2613,34 @@ function enumOf(values, options) {
|
|
|
2582
2613
|
}
|
|
2583
2614
|
};
|
|
2584
2615
|
}
|
|
2616
|
+
/**
|
|
2617
|
+
* A value that is another element's or link's id, e.g. the goal a KAOS resolution restores. The
|
|
2618
|
+
* schema only checks it is non-empty; `validateModelProperties` reports an id that isn't in the
|
|
2619
|
+
* model or names a kind outside `kinds`.
|
|
2620
|
+
*/
|
|
2621
|
+
function reference(options) {
|
|
2622
|
+
const { kinds, ...opts } = options ?? {};
|
|
2623
|
+
return {
|
|
2624
|
+
type: "reference",
|
|
2625
|
+
options: opts,
|
|
2626
|
+
...kinds ? { kinds } : {},
|
|
2627
|
+
parse(raw) {
|
|
2628
|
+
return {
|
|
2629
|
+
ok: true,
|
|
2630
|
+
value: raw
|
|
2631
|
+
};
|
|
2632
|
+
},
|
|
2633
|
+
serialize(value) {
|
|
2634
|
+
return value;
|
|
2635
|
+
}
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2585
2638
|
const prop = {
|
|
2586
2639
|
string,
|
|
2587
2640
|
number,
|
|
2588
2641
|
boolean,
|
|
2589
|
-
enum: enumOf
|
|
2642
|
+
enum: enumOf,
|
|
2643
|
+
reference
|
|
2590
2644
|
};
|
|
2591
2645
|
function isCustomPropertiesRecord(source) {
|
|
2592
2646
|
if ("customProperties" in source) return false;
|
|
@@ -2628,7 +2682,7 @@ function readShape(shape, source) {
|
|
|
2628
2682
|
}
|
|
2629
2683
|
issues.push({
|
|
2630
2684
|
key,
|
|
2631
|
-
message: field.type === "boolean" ? "must be true or false" : field.type === "enum" ? `must be one of: ${field.values?.join(", ") ?? ""}` : "must be a finite number"
|
|
2685
|
+
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"
|
|
2632
2686
|
});
|
|
2633
2687
|
continue;
|
|
2634
2688
|
}
|
|
@@ -2647,6 +2701,15 @@ function readShape(shape, source) {
|
|
|
2647
2701
|
issues
|
|
2648
2702
|
};
|
|
2649
2703
|
}
|
|
2704
|
+
/**
|
|
2705
|
+
* A patched value as a string, written back as read: the current string is kept when it already
|
|
2706
|
+
* holds the value (`.2` stays `.2`), and otherwise the type writes the value in its form.
|
|
2707
|
+
*/
|
|
2708
|
+
function writeField(field, value, current) {
|
|
2709
|
+
const parsed = field.parse(current);
|
|
2710
|
+
if (parsed.ok && Object.is(parsed.value, value)) return current;
|
|
2711
|
+
return field.serialize(value, current);
|
|
2712
|
+
}
|
|
2650
2713
|
function defineProperties(kind, shape, options) {
|
|
2651
2714
|
const crossValidate = options?.validate;
|
|
2652
2715
|
return {
|
|
@@ -2668,7 +2731,7 @@ function defineProperties(kind, shape, options) {
|
|
|
2668
2731
|
const patched = patch[key];
|
|
2669
2732
|
if (patched === void 0) continue;
|
|
2670
2733
|
const field = shape[key];
|
|
2671
|
-
if (field) result[key] = field
|
|
2734
|
+
if (field) result[key] = writeField(field, patched, value);
|
|
2672
2735
|
} else result[key] = value;
|
|
2673
2736
|
seen.add(key);
|
|
2674
2737
|
}
|
|
@@ -2702,20 +2765,39 @@ function validateModelProperties(model, schemas) {
|
|
|
2702
2765
|
byKind.set(schema.kind, list);
|
|
2703
2766
|
}
|
|
2704
2767
|
const issues = [];
|
|
2705
|
-
|
|
2706
|
-
const kindSchemas = byKind.get(
|
|
2707
|
-
if (!kindSchemas)
|
|
2708
|
-
for (const schema of kindSchemas)
|
|
2709
|
-
|
|
2710
|
-
|
|
2768
|
+
const check = (owner) => {
|
|
2769
|
+
const kindSchemas = byKind.get(owner.kind);
|
|
2770
|
+
if (!kindSchemas) return;
|
|
2771
|
+
for (const schema of kindSchemas) {
|
|
2772
|
+
for (const issue of schema.validate(owner)) issues.push({
|
|
2773
|
+
...issue,
|
|
2774
|
+
id: owner.id
|
|
2775
|
+
});
|
|
2776
|
+
for (const issue of referenceIssues(model, schema.shape, owner.customProperties)) issues.push({
|
|
2777
|
+
...issue,
|
|
2778
|
+
id: owner.id
|
|
2779
|
+
});
|
|
2780
|
+
}
|
|
2781
|
+
};
|
|
2782
|
+
for (const element of model.elements.values()) check(element);
|
|
2783
|
+
for (const link of model.links.values()) check(link);
|
|
2784
|
+
return issues;
|
|
2785
|
+
}
|
|
2786
|
+
/** References (`prop.reference`) to ids that aren't in the model, or to the wrong kind. */
|
|
2787
|
+
function referenceIssues(model, shape, custom) {
|
|
2788
|
+
const issues = [];
|
|
2789
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
2790
|
+
if (field.type !== "reference") continue;
|
|
2791
|
+
const id = custom?.[key];
|
|
2792
|
+
if (id === void 0 || id === "") continue;
|
|
2793
|
+
const target = model.elements.get(id) ?? model.links.get(id);
|
|
2794
|
+
if (!target) issues.push({
|
|
2795
|
+
key,
|
|
2796
|
+
message: `refers to "${id}", which is not in the model`
|
|
2711
2797
|
});
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
if (!kindSchemas) continue;
|
|
2716
|
-
for (const schema of kindSchemas) for (const issue of schema.validate(link)) issues.push({
|
|
2717
|
-
...issue,
|
|
2718
|
-
id: link.id
|
|
2798
|
+
else if (field.kinds && !field.kinds.includes(target.kind)) issues.push({
|
|
2799
|
+
key,
|
|
2800
|
+
message: `must refer to a ${field.kinds.join(" or ")}`
|
|
2719
2801
|
});
|
|
2720
2802
|
}
|
|
2721
2803
|
return issues;
|
|
@@ -2989,6 +3071,7 @@ exports.nestElement = nestElement;
|
|
|
2989
3071
|
exports.number = number;
|
|
2990
3072
|
exports.parsePistar = parsePistar;
|
|
2991
3073
|
exports.prop = prop;
|
|
3074
|
+
exports.reference = reference;
|
|
2992
3075
|
exports.removeElement = removeElement;
|
|
2993
3076
|
exports.removeElements = removeElements;
|
|
2994
3077
|
exports.removeLeg = removeLeg;
|