@istar-ts/core 0.1.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/LICENSE +44 -0
- package/README.md +178 -0
- package/dist/index.d.ts +578 -0
- package/dist/index.js +1425 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vieira Neto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Portions of this software are derived from piStar (https://github.com/jhcp/piStar),
|
|
26
|
+
© João Pimentel and contributors, MIT License:
|
|
27
|
+
|
|
28
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
29
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
30
|
+
in the Software without restriction, including without limitation the rights
|
|
31
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
32
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
33
|
+
furnished to do so, subject to the following conditions:
|
|
34
|
+
|
|
35
|
+
The above copyright notice and this permission notice shall be included in all
|
|
36
|
+
copies or substantial portions of the Software.
|
|
37
|
+
|
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
39
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
40
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
41
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
42
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
43
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
44
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# @istar-ts/core
|
|
2
|
+
|
|
3
|
+
Framework-free TypeScript library for [iStar 2.0](https://istarwiki.org/) goal models: a typed
|
|
4
|
+
metamodel, piStar-compatible JSON load/save, iStar link constraints, an immutable undoable model
|
|
5
|
+
store, and optional typed schemas for `customProperties`. No DOM or React dependency — **ESM
|
|
6
|
+
only**.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm add @istar-ts/core
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Loading and saving piStar files
|
|
13
|
+
|
|
14
|
+
piStar save files are JSON. Use `parsePistar` to read them into an `IstarModel`, and `toPistar` (or
|
|
15
|
+
`toPistarObject`) to write back.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { parsePistar, toPistar, validateModel } from '@istar-ts/core';
|
|
19
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
20
|
+
|
|
21
|
+
const text = readFileSync('model.pistar', 'utf8');
|
|
22
|
+
const model = parsePistar(text);
|
|
23
|
+
|
|
24
|
+
const issues = validateModel(model); // link constraint violations, like piStar on load
|
|
25
|
+
if (issues.length) console.warn(issues);
|
|
26
|
+
|
|
27
|
+
writeFileSync('model.pistar', toPistar(model));
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Round-trip guarantees:**
|
|
31
|
+
|
|
32
|
+
- Unknown JSON keys are kept on `extra`, `extraDisplay`, and per-element/link `extra`, and written
|
|
33
|
+
back unchanged.
|
|
34
|
+
- Every `customProperties` value stays a **string** on disk (as in piStar).
|
|
35
|
+
- Files saved by the piStar web tool round-trip **byte-identically** through
|
|
36
|
+
`toPistar(parsePistar(file))` when you do not edit the model.
|
|
37
|
+
- Pass `{ saveDate: new Date() }` when saving an edited model (piStar updates `saveDate` on save).
|
|
38
|
+
Omit it to preserve the model’s existing `saveDate` on a load/save cycle.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
toPistar(editedModel, { saveDate: new Date() });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Metamodel
|
|
45
|
+
|
|
46
|
+
Kind names use the `istar.` prefix, matching piStar save files. Constants group actors, nodes, and
|
|
47
|
+
links:
|
|
48
|
+
|
|
49
|
+
| Constants | Role |
|
|
50
|
+
| ----------------------- | -------------------------------------------------------------------- |
|
|
51
|
+
| `ACTOR_KINDS` | `istar.Actor`, `istar.Agent`, `istar.Role` |
|
|
52
|
+
| `NODE_KINDS` | `istar.Goal`, `istar.Quality`, `istar.Resource`, `istar.Task` |
|
|
53
|
+
| `ACTOR_LINK_KINDS` | `istar.IsALink`, `istar.ParticipatesInLink` |
|
|
54
|
+
| `DEPENDENCY_LINK_KINDS` | `istar.DependencyLink` |
|
|
55
|
+
| `NODE_LINK_KINDS` | refinements, `NeededByLink`, `QualificationLink`, `ContributionLink` |
|
|
56
|
+
|
|
57
|
+
`LINK_KIND_INFO` describes each link kind (fixed labels, whether direction is retried when adding,
|
|
58
|
+
contribution labels). `CONTRIBUTION_LABELS` is `['make', 'help', 'hurt', 'break']` — the allowed
|
|
59
|
+
values for a Contribution link’s `label`.
|
|
60
|
+
|
|
61
|
+
Type guards such as `isElementKind`, `isLinkKind`, and `isContributionLabel` narrow string values
|
|
62
|
+
from parsed files.
|
|
63
|
+
|
|
64
|
+
## Link constraints
|
|
65
|
+
|
|
66
|
+
`canLink(model, source, target, kind)` checks whether a link may be added. `source` and `target` may
|
|
67
|
+
be element ids or `IstarElement` objects (always resolved against the current model).
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { addElement, canLink, createEmptyModel } from '@istar-ts/core';
|
|
71
|
+
|
|
72
|
+
let model = createEmptyModel();
|
|
73
|
+
const { model: m1, element: actor } = addElement(model, { kind: 'istar.Actor', x: 0, y: 0 });
|
|
74
|
+
model = m1;
|
|
75
|
+
|
|
76
|
+
const check = canLink(model, actor.id, actor.id, 'istar.IsALink');
|
|
77
|
+
if (!check.ok) {
|
|
78
|
+
console.log(check.code, check.reason); // e.g. 'self-link', 'you cannot make Is-A links...'
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
On success the result is `{ ok: true }`. On failure: `{ ok: false; code: ConstraintCode; reason: string }`.
|
|
83
|
+
|
|
84
|
+
The model store exposes the same check against its current snapshot: `store.canLink(source, target, kind)`.
|
|
85
|
+
|
|
86
|
+
## Model store
|
|
87
|
+
|
|
88
|
+
`createModelStore(initial?)` wraps an immutable `IstarModel` with editing APIs, undo/redo, and
|
|
89
|
+
change events.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createModelStore } from '@istar-ts/core';
|
|
93
|
+
|
|
94
|
+
const store = createModelStore();
|
|
95
|
+
const actor = store.addElement({ kind: 'istar.Actor', x: 100, y: 100 });
|
|
96
|
+
const goal = store.addElement({
|
|
97
|
+
kind: 'istar.Goal',
|
|
98
|
+
x: 150,
|
|
99
|
+
y: 150,
|
|
100
|
+
parent: actor.id,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
store.connect({ kind: 'istar.AndRefinementLink', source: goal.id, target: goal.id }); // rejected
|
|
104
|
+
|
|
105
|
+
const dep = store.addDependency({
|
|
106
|
+
depender: goal.id,
|
|
107
|
+
dependee: actor.id,
|
|
108
|
+
dependum: { kind: 'istar.Resource', name: 'Data' },
|
|
109
|
+
});
|
|
110
|
+
if (dep.ok) {
|
|
111
|
+
// dep.dependum, dep.links — two DependencyLink halves around the dependum
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
store.undo();
|
|
115
|
+
store.redo();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- **Elements:** `addElement`, `updateElement`, `moveElement`, `nestElement`, `removeElement` /
|
|
119
|
+
`removeElements`, `setCollapsed`.
|
|
120
|
+
- **Links:** `connect`, `addDependency`, `updateLink`, `disconnect` (removing one dependency half
|
|
121
|
+
removes the whole dependency).
|
|
122
|
+
- **`transaction(fn)`** groups edits into one undo step and one event; rolls back if `fn` throws.
|
|
123
|
+
- **`subscribe(listener)`** + **`getModel()`** match React’s `useSyncExternalStore` contract.
|
|
124
|
+
- **`replace(model)`** swaps the model as a single undoable edit; **`load(model)`** replaces it and
|
|
125
|
+
clears history (like opening a file in piStar).
|
|
126
|
+
|
|
127
|
+
For controlled editors, the **`operations`** module exposes the same edits as pure functions (`addElement`,
|
|
128
|
+
`connect`, `addDependency`, …) that return a new `IstarModel` without mutating the input.
|
|
129
|
+
|
|
130
|
+
## Typed custom properties
|
|
131
|
+
|
|
132
|
+
Core does not hardcode domain-specific properties. Optional schemas layer typed read/write on top
|
|
133
|
+
of string maps via `defineProperties` and `prop.*` builders (`string`, `number`, `boolean`, `enum`).
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import {
|
|
137
|
+
defineProperties,
|
|
138
|
+
prop,
|
|
139
|
+
validateModelProperties,
|
|
140
|
+
type InferProperties,
|
|
141
|
+
} from '@istar-ts/core';
|
|
142
|
+
|
|
143
|
+
const resourceSchema = defineProperties(
|
|
144
|
+
'istar.Resource',
|
|
145
|
+
{
|
|
146
|
+
type: prop.enum(['bool', 'int'] as const),
|
|
147
|
+
initialValue: prop.string(),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
validate: (v) =>
|
|
151
|
+
v.type === 'bool' &&
|
|
152
|
+
v.initialValue !== undefined &&
|
|
153
|
+
!['true', 'false'].includes(v.initialValue)
|
|
154
|
+
? [{ key: 'initialValue', message: 'must be true or false' }]
|
|
155
|
+
: v.type === 'int' && v.initialValue !== undefined && !/^-?\d+$/.test(v.initialValue)
|
|
156
|
+
? [{ key: 'initialValue', message: 'must be an integer' }]
|
|
157
|
+
: [],
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const { values, issues } = resourceSchema.read({ type: 'bool', initialValue: 'false' });
|
|
162
|
+
const next = resourceSchema.write(
|
|
163
|
+
{ type: 'bool', initialValue: 'false', legacy: 'keep' },
|
|
164
|
+
{ initialValue: 'true' },
|
|
165
|
+
);
|
|
166
|
+
const defaults = resourceSchema.defaults();
|
|
167
|
+
|
|
168
|
+
const modelIssues = validateModelProperties(model, [resourceSchema]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`read` / `validate` accept `customProperties`, a full element/link, or `undefined`. `write` merges
|
|
172
|
+
patches, preserves unknown keys, and serializes typed values back to strings.
|
|
173
|
+
|
|
174
|
+
## Credits
|
|
175
|
+
|
|
176
|
+
The metamodel, link constraints, element shapes, and file format are derived from
|
|
177
|
+
[piStar](https://github.com/jhcp/piStar) by João Pimentel and contributors, released under the MIT
|
|
178
|
+
License.
|