@istar-ts/core 0.11.0 → 0.13.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 CHANGED
@@ -216,8 +216,9 @@ const model = parsePistar(text, { metamodel: RATIONAL_AGENTS });
216
216
  Either way, an optional `check(context)` predicate runs last. Dependency-category kinds must
217
217
  behave like `istar.DependencyLink`; `addDependency({ …, linkKind })` creates them.
218
218
 
219
- - **Names.** Kinds are namespaced (`rationalAgents.Plan`). `istar.` is reserved for iStar 2.0, and a
220
- name or `pistarType` that collides with an existing one throws `MetamodelError`. Extensions
219
+ - **Names.** Kinds are namespaced (`rationalAgents.Plan`). The root metamodel's prefix (`istar.` for
220
+ iStar 2.0) is reserved for it, and a name or `pistarType` that collides with an existing one
221
+ throws `MetamodelError`. Extensions
221
222
  stack, and a later one may build on an earlier one's kinds.
222
223
  - **The model remembers its metamodel.** `parsePistar`, `createEmptyModel(undefined, { metamodel })`
223
224
  and `createModelStore(undefined, { metamodel })` associate it, and every operation and store
@@ -251,6 +252,137 @@ rather than writing a type that can't be read back.
251
252
 
252
253
  See [docs/metamodel-extensions.md](../../docs/metamodel-extensions.md) for the design.
253
254
 
255
+ ## Root metamodels
256
+
257
+ iStar 2.0 is one root metamodel: a whole modelling language rather than a dialect of another.
258
+ `defineMetamodel` builds others, such as the KAOS and NFR languages of
259
+ [DSM3-goals](https://github.com/vieirin/istar-ts/issues/7), from the same element and link
260
+ declarations that extensions use. `ISTAR_2_0` is itself `defineMetamodel(ISTAR_2_0_DECLARATION)`.
261
+
262
+ ```ts
263
+ import { createEmptyModel, defineMetamodel, extendMetamodel, parsePistar } from '@istar-ts/core';
264
+
265
+ const KAOS = defineMetamodel({
266
+ name: 'kaos', // the metamodel's name and framework
267
+ prefix: 'kaos', // its namespace, reserved against extensions
268
+ elements: [
269
+ { kind: 'kaos.Goal', category: 'node', size: { width: 135, height: 36 } },
270
+ { kind: 'kaos.Softgoal', behavesLike: 'kaos.Goal' },
271
+ { kind: 'kaos.Agent', category: 'node' },
272
+ ],
273
+ links: [
274
+ {
275
+ kind: 'kaos.ResponsibilityAssignment',
276
+ rules: { sources: ['kaos.Agent'], targets: ['node'], sameActor: false },
277
+ },
278
+ ],
279
+ file: {
280
+ tool: 'dsm3-goals',
281
+ extra: { metamodelVersion: '1.0' },
282
+ keys: [
283
+ 'actors',
284
+ 'orphans',
285
+ 'dependencies',
286
+ 'links',
287
+ 'display',
288
+ 'tool',
289
+ 'metamodelVersion',
290
+ 'saveDate',
291
+ 'diagram',
292
+ ],
293
+ },
294
+ });
295
+
296
+ const model = parsePistar(text, { metamodel: KAOS });
297
+ const blank = createEmptyModel(undefined, { metamodel: KAOS }); // tool "dsm3-goals", no "istar"
298
+ ```
299
+
300
+ - **Declarations.** Kinds are declared in order, with the same validation and errors as
301
+ `extendMetamodel`. `behavesLike` and link rules refer to kinds declared before them. A root may
302
+ use any namespace. Nodes default to DSM3-goals' containment rules: inner element, dependum and
303
+ on paper. A root's link kind may give neither `rules` nor `behavesLike`, as iStar 2.0's do:
304
+ their rules are built into the library, and any other such kind accepts no link.
305
+ - **Identity.** `metamodel.framework` is the root's name (`istar-2.0`, `kaos`). `prefix` is its
306
+ reserved namespace, so `extendMetamodel(KAOS, …)` rejects `kaos.*` kinds. Extensions keep both,
307
+ and `file`; `name` gains the extensions' names (`kaos+ops`).
308
+ - **Files.** `file` describes the files of models that weren't read from one. `createEmptyModel`
309
+ records `file.tool` (default `pistar.2.1.0`), `file.istar` only when set (iStar 2.0 sets
310
+ `"2.0"`), and `file.extra` as extra top-level keys. `toPistar` orders top-level keys like the
311
+ source file, else like `file.keys`, else like piStar.
312
+ - **Which root a file uses.** `detectFramework(text, [ISTAR_2_0, KAOS, NFR])` picks the root whose
313
+ `prefix` is the namespace of the file's first `type` (`detectPrefix`). For a file without
314
+ types, it picks the only root whose `file.tool` is the file's `tool`; otherwise it returns
315
+ `undefined`.
316
+
317
+ ### Hub links
318
+
319
+ A link kind with `arity: 'n'` has several sources and one target, like KAOS's AND-refinement. In a
320
+ model it is a hub element, a leg from each source to the hub, and an end from the hub to the
321
+ target, as DSM3-goals draws and saves it. The declaration generates the three kinds from the
322
+ base kind's name: `<kind>Hub`, `<kind>Start` (legs) and `<kind>End`.
323
+
324
+ ```ts
325
+ const KAOS = defineMetamodel<KaosElementKind, KaosLinkKind>({
326
+ // …
327
+ links: [
328
+ {
329
+ kind: 'kaos.RefinementLink',
330
+ arity: 'n',
331
+ rules: {
332
+ sources: ['kaos.BehavioralGoal', 'kaos.Softgoal'],
333
+ targets: ['node'],
334
+ sameActor: false,
335
+ },
336
+ },
337
+ ],
338
+ });
339
+
340
+ const made = store.connect({ kind: 'kaos.RefinementLink', source: goalA, target: parent });
341
+ if (made.ok && made.hub) {
342
+ // made.hub: { hub, legs: [leg], end }. The hub sits centred between goalA and parent.
343
+ store.connect({ kind: 'kaos.RefinementLink', source: goalB, target: made.hub.hub.id }); // 2nd leg
344
+ }
345
+ ```
346
+
347
+ - **Kinds.** The hub kind has `role: 'hub'` and `of` (the base kind), a 20×20 circle shape and
348
+ `canBeOnPaper`. Legs and ends have `role: 'leg' | 'end'`. `parts` renames them and `hubSize`
349
+ resizes the hub. Generated kinds aren't inferred into the metamodel's kind types, so name
350
+ them in `defineMetamodel`'s type arguments if you need them typed.
351
+ - **Rules.** The `node`, `actor` and `*` selectors never match a hub. A link of another kind
352
+ towards a hub fails with code `hub-target`. A source gets one leg per hub, and a hub has one
353
+ end. The base kind's `sameActor` holds between each leg's source and the end's target.
354
+ - **Operations.** `connect` with the base kind creates the hub, a leg and the end, or adds a leg
355
+ when the target is one of its hubs. `input.id` and `customProperties` go to the hub; `label` and
356
+ `name` go to the end. `addLeg(model, hubId, source)` and `removeLeg(model, legId)` exist in
357
+ operations and on the store. Removing a hub's end or its last leg removes the hub. Removing the
358
+ hub removes its legs and end.
359
+ - **Queries.** Use `hubLinkOf(model, hubId)`, `hubOfLink(model, linkId)`,
360
+ `isHubElement(metamodel, element)`, `elementCenter` and `defaultHubPosition`.
361
+ `validateModel` reports a hub without an end or legs as `incomplete-hub`, with the hub's id as
362
+ `linkId`.
363
+ - **On disk.** An entry in `links` holds `{ hub, starts, end }`, with no id of its own and no
364
+ `text` on the hub, as DSM3-goals writes it. The entry's unknown keys are kept on the hub's
365
+ `extra[HUB_ENTRY_KEY]` and written back. Hubs never appear in `orphans`.
366
+
367
+ ### Links that target links
368
+
369
+ A link kind may point at another link, like NFR's Argument, which is a Claim about a Contribution.
370
+ Its `rules.targets` holds `link` (any link) or link kind names:
371
+
372
+ ```ts
373
+ { kind: 'nfr.ArgumentLink', rules: { sources: ['nfr.Claim'], targets: ['link'], sameActor: false } }
374
+ ```
375
+
376
+ - **Checks.** `canLink(model, claim, contributionLinkId, 'nfr.ArgumentLink')` looks the target up
377
+ among elements, then links. Element selectors never match a link, and link selectors never
378
+ match an element. The self-link, dependum and same-actor rules don't apply to a link target.
379
+ iStar 2.0's built-in rules refuse one.
380
+ - **Predicates.** `LinkRuleContext.target` is an `IstarElement` or an `IstarLink`, so narrow it
381
+ with `isLinkRef`. `linkTargetOf(model, link)` returns what a link points at.
382
+ - **Models.** `validateModel` checks links that target links after the others, whatever their
383
+ order in the file. Removing a link removes the links that target it. `linksOf(model, linkId)`
384
+ lists them.
385
+
254
386
  ## A model's own metamodel
255
387
 
256
388
  A file can declare the constructs it adds to iStar in a top-level `"metamodel"` block, so they
@@ -304,6 +436,15 @@ toPistar(extended); // writes the "metamodel" block
304
436
  into a store (`store.load` or `store.replace`) to keep editing.
305
437
  - `pistarType` works as for any extension, so `istar.<Name>` types on disk still read.
306
438
 
439
+ ## Diagnostics
440
+
441
+ `GoalDiagnostic` (`{ elementId, key?, severity, message, source?, range?, code?, data? }`) is the
442
+ data shape for findings about elements and links. Analysers publish them per source to a
443
+ `createDiagnosticsStore()`; `getAll()` returns the union of every source, deduplicated by
444
+ `(elementId, key, message)`. `fromLspDiagnostics` and `fromNodeIdDiagnostics` adapt LSP and MutRoSe
445
+ diagnostics. Diagnostics are never part of the model and never serialized. `@istar-ts/react`
446
+ shows them as badges and in the inspector (see its README).
447
+
307
448
  ## Credits
308
449
 
309
450
  The metamodel, link constraints, element shapes, and file format are derived from