yarramate 1.19.1 → 1.21.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/adapters/likec4-prepare.js +1 -1
- package/dist/adapters/visual/protocol-contract.d.ts +28 -0
- package/dist/adapters/visual/session-server.js +45 -12
- package/dist/adapters/visual/wire.d.ts +23 -0
- package/dist/adapters/visual/workspace-model.d.ts +4 -4
- package/dist/adapters/visual/workspace-model.js +28 -5
- package/dist/adapters/visual-graph-entry.d.ts +1 -0
- package/dist/adapters/visual-graph-entry.js +6 -0
- package/dist/ask-command.js +2 -2
- package/dist/check-command.js +12 -1
- package/dist/compiler.d.ts +21 -0
- package/dist/compiler.js +19 -0
- package/dist/export-command.js +4 -1
- package/dist/fold-tree.d.ts +169 -0
- package/dist/fold-tree.js +295 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/projection.d.ts +68 -4
- package/dist/projection.js +185 -9
- package/dist/schema-validators.generated.js +355 -272
- package/dist/subject-references.js +8 -0
- package/dist/visual-app/assets/index-Ch27Sr5z.js +394 -0
- package/dist/visual-app/assets/index-DF3anVfS.css +1 -0
- package/dist/visual-app/index.html +2 -2
- package/dist/visual-app-lib/editor.js +28809 -28147
- package/dist/visual-app-lib/styles.css +1 -1
- package/dist/visual-app-lib/types/adapters/visual/protocol-contract.d.ts +28 -0
- package/dist/visual-app-lib/types/adapters/visual/wire.d.ts +23 -0
- package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +4 -4
- package/dist/visual-app-lib/types/compiler.d.ts +21 -0
- package/dist/visual-app-lib/types/fold-tree.d.ts +169 -0
- package/dist/visual-app-lib/types/projection.d.ts +68 -4
- package/dist/visual-app-lib/types/visual-app/badges.d.ts +1 -0
- package/dist/visual-app-lib/types/visual-app/context-menu-model.d.ts +65 -0
- package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +37 -3
- package/dist/visual-app-lib/types/visual-app/session-client.d.ts +7 -1
- package/dist/visual-app-lib/types/visual-app/slots-model.d.ts +58 -0
- package/dist/visual-app-lib/types/visual-app/state.d.ts +2 -0
- package/dist/visual-app-lib/types/visual-app/view-tree-model.d.ts +18 -1
- package/dist/visual-app-lib/types/visual-app/view-tree.d.ts +4 -1
- package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +34 -0
- package/docs/CONSUMING-YARRAMATE.md +23 -1
- package/package.json +1 -1
- package/schema/yarramate-projection.schema.json +13 -0
- package/schema/yarramate-visual-layout.schema.json +16 -0
- package/dist/visual-app/assets/index-Bd-3k5UB.css +0 -1
- package/dist/visual-app/assets/index-CvTDYpur.js +0 -394
|
@@ -141,7 +141,7 @@ export function prepareLikeC4Export(input) {
|
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
-
const projectionResult = evaluateProjection(compilation.graph, projection.projection, compilation.profileContext);
|
|
144
|
+
const projectionResult = evaluateProjection(compilation.graph, projection.projection, compilation.profileContext, compilation.patternMemberships);
|
|
145
145
|
const comparison = input.comparison === undefined
|
|
146
146
|
? undefined
|
|
147
147
|
: compareArchitectureStates(compilation.graph, input.comparison.from, input.comparison.to);
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* `node:path`, or the schema documents into its bundle.
|
|
10
10
|
*/
|
|
11
11
|
import type { CanvasGraph } from "../../graph-projection.js";
|
|
12
|
+
import type { NestingKind } from "../../nesting.js";
|
|
12
13
|
import type { YarramateApplyResult, YarramateOperation } from "../../operations.js";
|
|
13
14
|
import type { ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
|
|
14
15
|
/**
|
|
@@ -163,6 +164,20 @@ export interface VisualKindOption {
|
|
|
163
164
|
}
|
|
164
165
|
export interface VisualFilterQueryPayload {
|
|
165
166
|
readonly query: ProjectionQuery;
|
|
167
|
+
/**
|
|
168
|
+
* The nesting the canvas is drawing with, when the browser knows it (#473
|
|
169
|
+
* phase 2).
|
|
170
|
+
*
|
|
171
|
+
* Only `query.instances` reads it, and it must: the closure of an instance IS
|
|
172
|
+
* the containment tree, so evaluating it under a different nesting than the
|
|
173
|
+
* canvas answers a different question. On the ApertureX reference that is 15
|
|
174
|
+
* subjects against 2.
|
|
175
|
+
*
|
|
176
|
+
* Optional, so an older browser and every filter that names no instance keep
|
|
177
|
+
* working unchanged; absent, the evaluator falls back to the default nesting
|
|
178
|
+
* exactly as it did before.
|
|
179
|
+
*/
|
|
180
|
+
readonly nesting?: readonly NestingKind[];
|
|
166
181
|
}
|
|
167
182
|
export interface VisualFilterResultPayload {
|
|
168
183
|
readonly query: ProjectionQuery;
|
|
@@ -250,6 +265,19 @@ export interface VisualChangesetCommitPayload {
|
|
|
250
265
|
export interface VisualLayoutSavePayload {
|
|
251
266
|
readonly projectionId: string;
|
|
252
267
|
readonly positions: VisualLayoutPositions;
|
|
268
|
+
/**
|
|
269
|
+
* What this view folds, saved beside the positions in ONE document (#473).
|
|
270
|
+
*
|
|
271
|
+
* Full state every time, never a patch. A half-applied fold state draws a
|
|
272
|
+
* box whose contents are somewhere else on the canvas, and the sidecar is
|
|
273
|
+
* written by a browser that may have been reloaded between any two saves.
|
|
274
|
+
*
|
|
275
|
+
* `unfolded` exists because the view's own `presentation.fold` is a default,
|
|
276
|
+
* not a rule: a reader who opened a box must not have it close again when the
|
|
277
|
+
* default is read back.
|
|
278
|
+
*/
|
|
279
|
+
readonly folded?: readonly string[];
|
|
280
|
+
readonly unfolded?: readonly string[];
|
|
253
281
|
}
|
|
254
282
|
/**
|
|
255
283
|
* Terminal event payload. Every reason is the runtime's to choose: only it
|
|
@@ -456,14 +456,15 @@ export const startVisualServer = async (options) => {
|
|
|
456
456
|
// like a broken saved view above — presentation state must never fail a
|
|
457
457
|
// session.
|
|
458
458
|
const layoutDir = resolve(options.cwd, ".yarramate/visual-layout");
|
|
459
|
-
const layouts = (() => {
|
|
460
|
-
const
|
|
459
|
+
const { layouts, folds } = (() => {
|
|
460
|
+
const layouts = {};
|
|
461
|
+
const folds = {};
|
|
461
462
|
let entries;
|
|
462
463
|
try {
|
|
463
464
|
entries = readdirSync(layoutDir);
|
|
464
465
|
}
|
|
465
466
|
catch {
|
|
466
|
-
return
|
|
467
|
+
return { layouts, folds };
|
|
467
468
|
}
|
|
468
469
|
for (const entry of entries) {
|
|
469
470
|
if (extname(entry) !== ".yaml" && extname(entry) !== ".yml")
|
|
@@ -474,13 +475,22 @@ export const startVisualServer = async (options) => {
|
|
|
474
475
|
if (!validateVisualLayout(parsed))
|
|
475
476
|
continue;
|
|
476
477
|
const sidecar = parsed;
|
|
477
|
-
|
|
478
|
+
layouts[sidecar.projectionId] = sidecar.positions;
|
|
479
|
+
// A sidecar written before #473 has neither list, and says nothing
|
|
480
|
+
// about folding rather than saying "fold nothing" - the view's own
|
|
481
|
+
// default decides for it. Only a sidecar that STATES a fold overrides.
|
|
482
|
+
if (sidecar.folded !== undefined || sidecar.unfolded !== undefined) {
|
|
483
|
+
folds[sidecar.projectionId] = {
|
|
484
|
+
folded: [...(sidecar.folded ?? [])],
|
|
485
|
+
unfolded: [...(sidecar.unfolded ?? [])],
|
|
486
|
+
};
|
|
487
|
+
}
|
|
478
488
|
}
|
|
479
489
|
catch {
|
|
480
490
|
// Skipped sidecar: presentation state must never fail a session.
|
|
481
491
|
}
|
|
482
492
|
}
|
|
483
|
-
return
|
|
493
|
+
return { layouts, folds };
|
|
484
494
|
})();
|
|
485
495
|
// `request.initialModel.graph` is the caller's compile (`buildVisualModelGraph`,
|
|
486
496
|
// before invoking `yarramate-visual start`) and is only the fallback below:
|
|
@@ -494,6 +504,7 @@ export const startVisualServer = async (options) => {
|
|
|
494
504
|
documents: [],
|
|
495
505
|
vocabulary: { conceptKinds: [], relationshipKinds: [] },
|
|
496
506
|
layouts,
|
|
507
|
+
...(Object.keys(folds).length === 0 ? {} : { folds }),
|
|
497
508
|
sourceDigests: request.initialModel.sourceDigests,
|
|
498
509
|
// The request's model has no projections in it - `visual-model/v1` carries
|
|
499
510
|
// a graph, not a workspace - so the fallback states nothing rather than
|
|
@@ -679,9 +690,15 @@ export const startVisualServer = async (options) => {
|
|
|
679
690
|
if (!started.ok)
|
|
680
691
|
standingDiagnostics = started.diagnostics;
|
|
681
692
|
}
|
|
682
|
-
const filterMatchedIds = (query) => compiledWorkspace === undefined
|
|
693
|
+
const filterMatchedIds = (query, nesting) => compiledWorkspace === undefined
|
|
683
694
|
? []
|
|
684
|
-
: matchedIdsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext
|
|
695
|
+
: matchedIdsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext,
|
|
696
|
+
// A view's query can name `instances`, and the facet resolves to the
|
|
697
|
+
// instance alone without these (ADR 0144).
|
|
698
|
+
compiledWorkspace.patternMemberships,
|
|
699
|
+
// And it resolves the WRONG closure without the nesting the canvas is
|
|
700
|
+
// drawing with, which is a wrong number rather than a missing one.
|
|
701
|
+
nesting);
|
|
685
702
|
/**
|
|
686
703
|
* Why a query dropped what it dropped, as the editor's "excluded, and why"
|
|
687
704
|
* list reads it (#248).
|
|
@@ -693,9 +710,9 @@ export const startVisualServer = async (options) => {
|
|
|
693
710
|
* the reason the editor shows and the set the canvas draws cannot come from
|
|
694
711
|
* two readings of the same query.
|
|
695
712
|
*/
|
|
696
|
-
const filterExclusions = (query) => compiledWorkspace === undefined
|
|
713
|
+
const filterExclusions = (query, nesting) => compiledWorkspace === undefined
|
|
697
714
|
? []
|
|
698
|
-
: exclusionsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext);
|
|
715
|
+
: exclusionsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext, compiledWorkspace.patternMemberships, nesting);
|
|
699
716
|
let listening = false;
|
|
700
717
|
let bootstrapSpent = false;
|
|
701
718
|
let agentAttached = false;
|
|
@@ -1232,8 +1249,8 @@ export const startVisualServer = async (options) => {
|
|
|
1232
1249
|
kind: "filter-result",
|
|
1233
1250
|
result: {
|
|
1234
1251
|
query: event.payload.query,
|
|
1235
|
-
matchedIds: filterMatchedIds(event.payload.query),
|
|
1236
|
-
excluded: filterExclusions(event.payload.query),
|
|
1252
|
+
matchedIds: filterMatchedIds(event.payload.query, event.payload.nesting),
|
|
1253
|
+
excluded: filterExclusions(event.payload.query, event.payload.nesting),
|
|
1237
1254
|
},
|
|
1238
1255
|
});
|
|
1239
1256
|
return;
|
|
@@ -1407,7 +1424,7 @@ export const startVisualServer = async (options) => {
|
|
|
1407
1424
|
// never `git commit`ed. It never asks the agent anything, so it is
|
|
1408
1425
|
// answered here directly rather than through the pending queue a
|
|
1409
1426
|
// poll would drain.
|
|
1410
|
-
const { projectionId, positions } = event.payload;
|
|
1427
|
+
const { projectionId, positions, folded, unfolded } = event.payload;
|
|
1411
1428
|
if (!views.some((view) => view.id === projectionId)) {
|
|
1412
1429
|
sendFrame(socket, {
|
|
1413
1430
|
kind: "layout-save-result",
|
|
@@ -1424,10 +1441,26 @@ export const startVisualServer = async (options) => {
|
|
|
1424
1441
|
format: "yarramate/visual-layout/v1",
|
|
1425
1442
|
projectionId,
|
|
1426
1443
|
positions,
|
|
1444
|
+
// With the positions, in one document, in full (#473). Same rule
|
|
1445
|
+
// the local host follows, and it has to be the same rule: a
|
|
1446
|
+
// sidecar written by one host is read by the other.
|
|
1447
|
+
...(folded === undefined ? {} : { folded }),
|
|
1448
|
+
...(unfolded === undefined ? {} : { unfolded }),
|
|
1427
1449
|
}), "utf8");
|
|
1428
1450
|
rendered = {
|
|
1429
1451
|
...rendered,
|
|
1430
1452
|
layouts: { ...rendered.layouts, [projectionId]: positions },
|
|
1453
|
+
...(folded === undefined && unfolded === undefined
|
|
1454
|
+
? {}
|
|
1455
|
+
: {
|
|
1456
|
+
folds: {
|
|
1457
|
+
...rendered.folds,
|
|
1458
|
+
[projectionId]: {
|
|
1459
|
+
folded: folded ?? [],
|
|
1460
|
+
unfolded: unfolded ?? [],
|
|
1461
|
+
},
|
|
1462
|
+
},
|
|
1463
|
+
}),
|
|
1431
1464
|
};
|
|
1432
1465
|
sendFrame(socket, {
|
|
1433
1466
|
kind: "layout-save-result",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CanvasGraph } from '../../graph-projection.js';
|
|
2
|
+
import type { PatternMembership, PatternVacancy } from '../../compiler.js';
|
|
2
3
|
import type { VISUAL_PROTOCOL_VERSION, VisualApplyResultPayload, VisualAuthority, VisualBrowserInput, VisualCapabilities, VisualChoicePresentPayload, VisualDiagnostic, VisualFilterResultPayload, VisualFreezeReason, VisualKindOption, VisualLayoutPositions, VisualLayoutSaveResultPayload, VisualResponse, VisualTerminationReason, VisualViewSummary } from './protocol-contract.js';
|
|
3
4
|
/**
|
|
4
5
|
* Transport shapes the session server and the browser application both speak.
|
|
@@ -55,6 +56,28 @@ export interface VisualRenderedModel {
|
|
|
55
56
|
readonly layouts: {
|
|
56
57
|
readonly [projectionId: string]: VisualLayoutPositions;
|
|
57
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* What each view folds, keyed by projection id (#473). A SIBLING of
|
|
61
|
+
* `layouts` rather than a field inside its entries: a layout entry is
|
|
62
|
+
* positions, one shape a host may already be reading, and widening it would
|
|
63
|
+
* make every reader of `layouts[id]` handle a case that did not exist.
|
|
64
|
+
*/
|
|
65
|
+
readonly folds?: {
|
|
66
|
+
readonly [projectionId: string]: {
|
|
67
|
+
readonly folded: readonly string[];
|
|
68
|
+
readonly unfolded: readonly string[];
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Which subject fills which slot of which instance (ADR 0131), and which
|
|
73
|
+
* slots nothing fills (#447), forwarded so the browser can draw containment
|
|
74
|
+
* and answer "what is inside this box" without a second round trip.
|
|
75
|
+
*
|
|
76
|
+
* Optional: a host that never folds and never shows slots need not supply
|
|
77
|
+
* them, and a frame from before #473 has neither.
|
|
78
|
+
*/
|
|
79
|
+
readonly memberships?: readonly PatternMembership[];
|
|
80
|
+
readonly vacancies?: readonly PatternVacancy[];
|
|
58
81
|
/**
|
|
59
82
|
* The sha256 of every workspace source this graph was compiled from, keyed by
|
|
60
83
|
* manifest-relative path — the same map `visual-model/v1` already requires of
|
|
@@ -2,7 +2,7 @@ import type { Diagnostic } from "../../compiler.js";
|
|
|
2
2
|
import type { PendingWrite, SourceStore } from "../../source-store.js";
|
|
3
3
|
import type { ResolvedWorkspace } from "../../workspace.js";
|
|
4
4
|
import type { VisualDiagnostic, VisualViewOperation } from "./protocol-contract.js";
|
|
5
|
-
import type { ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
|
|
5
|
+
import type { NestingKind, ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
|
|
6
6
|
import type { ResolvedProfileContext, SemanticGraph } from "../../compiler.js";
|
|
7
7
|
import { type CataloguePatternMembership, type CataloguePatternVacancy } from "../../interrogate-command.js";
|
|
8
8
|
import type { VisualKindOption, VisualViewSummary } from "./protocol-contract.js";
|
|
@@ -41,7 +41,7 @@ export declare const kindOptionsOf: (lineages: ReadonlyMap<string, readonly stri
|
|
|
41
41
|
* subject count. A view over three components with two relationships between
|
|
42
42
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
43
43
|
*/
|
|
44
|
-
export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext) => number;
|
|
44
|
+
export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => number;
|
|
45
45
|
/**
|
|
46
46
|
* Folds one interrogation report into what the canvas draws (#292).
|
|
47
47
|
*
|
|
@@ -109,9 +109,9 @@ export declare const renderedWorkspaceOf: (compiled: {
|
|
|
109
109
|
readonly views: readonly VisualViewSummary[];
|
|
110
110
|
};
|
|
111
111
|
/** Every subject a query draws, concepts and relationships alike. */
|
|
112
|
-
export declare const matchedIdsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext) => readonly string[];
|
|
112
|
+
export declare const matchedIdsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => readonly string[];
|
|
113
113
|
/** Every concept a query dropped, and the facet that dropped it (#248). */
|
|
114
|
-
export declare const exclusionsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext) => readonly ProjectionExclusion[];
|
|
114
|
+
export declare const exclusionsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => readonly ProjectionExclusion[];
|
|
115
115
|
/**
|
|
116
116
|
* One saved view, as the rail reads it. `subjectCount` is the caller's,
|
|
117
117
|
* because counting needs a compiled graph and a session builds its first list
|
|
@@ -45,7 +45,7 @@ export const kindOptionsOf = (lineages) => [...lineages.keys()].map((id) => ({
|
|
|
45
45
|
* subject count. A view over three components with two relationships between
|
|
46
46
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
47
47
|
*/
|
|
48
|
-
export const conceptCountOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.filter(({ type }) => type === "concept").length;
|
|
48
|
+
export const conceptCountOf = (graph, query, profileContext, memberships, nesting) => evaluateProjection(graph, adHoc(query, nesting), profileContext, memberships).subjects.filter(({ type }) => type === "concept").length;
|
|
49
49
|
export const interrogationOverlayOf = (compiled,
|
|
50
50
|
/**
|
|
51
51
|
* The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
|
|
@@ -118,7 +118,14 @@ dismissed = []) => {
|
|
|
118
118
|
export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismissed) => {
|
|
119
119
|
const refreshedViews = views.map((view) => ({
|
|
120
120
|
...view,
|
|
121
|
-
subjectCount: conceptCountOf(compiled.graph, view.query, compiled.profileContext
|
|
121
|
+
subjectCount: conceptCountOf(compiled.graph, view.query, compiled.profileContext,
|
|
122
|
+
// Without these an `instances` view counts 1 and the rail says so, which
|
|
123
|
+
// is a wrong number rather than a missing one (ADR 0144).
|
|
124
|
+
compiled.patternMemberships,
|
|
125
|
+
// Each view's OWN nesting, because each view's closure is its own. A rail
|
|
126
|
+
// sitting beside the canvas must not count a different tree than the
|
|
127
|
+
// canvas draws.
|
|
128
|
+
view.presentation?.nesting),
|
|
122
129
|
}));
|
|
123
130
|
const interrogation = catalogue === undefined
|
|
124
131
|
? undefined
|
|
@@ -132,23 +139,39 @@ export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismis
|
|
|
132
139
|
relationshipKinds: kindOptionsOf(compiled.profileContext.relationshipKindLineages),
|
|
133
140
|
},
|
|
134
141
|
...(interrogation === undefined ? {} : { interrogation }),
|
|
142
|
+
// Containment context, forwarded rather than re-derived (#473). Both
|
|
143
|
+
// hosts build the model here, so local-host and session-server cannot
|
|
144
|
+
// disagree about what is inside a box - which is the shape of defect
|
|
145
|
+
// that made `patterns` ship non-functional in 1.4.0, ten source lists
|
|
146
|
+
// each dropping the same thing.
|
|
147
|
+
...(compiled.patternMemberships === undefined
|
|
148
|
+
? {}
|
|
149
|
+
: { memberships: compiled.patternMemberships }),
|
|
150
|
+
...(compiled.patternVacancies === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { vacancies: compiled.patternVacancies }),
|
|
135
153
|
},
|
|
136
154
|
views: refreshedViews,
|
|
137
155
|
};
|
|
138
156
|
};
|
|
139
157
|
/** Every subject a query draws, concepts and relationships alike. */
|
|
140
|
-
export const matchedIdsOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.map(({ id }) => id);
|
|
158
|
+
export const matchedIdsOf = (graph, query, profileContext, memberships, nesting) => evaluateProjection(graph, adHoc(query, nesting), profileContext, memberships).subjects.map(({ id }) => id);
|
|
141
159
|
/** Every concept a query dropped, and the facet that dropped it (#248). */
|
|
142
|
-
export const exclusionsOf = (graph, query, profileContext) => explainProjection(graph, adHoc(query), profileContext);
|
|
160
|
+
export const exclusionsOf = (graph, query, profileContext, memberships, nesting) => explainProjection(graph, adHoc(query, nesting), profileContext, memberships);
|
|
143
161
|
/**
|
|
144
162
|
* A query on its own is not a projection, and every evaluator here wants one.
|
|
145
163
|
* The id is a placeholder that never reaches a document.
|
|
146
164
|
*/
|
|
147
|
-
const adHoc = (query) => ({
|
|
165
|
+
const adHoc = (query, nesting) => ({
|
|
148
166
|
format: "yarramate/projection/v1",
|
|
149
167
|
id: "ad-hoc",
|
|
150
168
|
version: "0",
|
|
151
169
|
query,
|
|
170
|
+
// `query.instances` resolves its closure through the view's nesting, so an
|
|
171
|
+
// ad-hoc projection that dropped the nesting would answer a DIFFERENT
|
|
172
|
+
// question than the canvas is drawing: 2 subjects against 15 on the
|
|
173
|
+
// ApertureX reference, with nothing to say it had (#473 phase 2).
|
|
174
|
+
...(nesting === undefined ? {} : { presentation: { nesting } }),
|
|
152
175
|
});
|
|
153
176
|
/**
|
|
154
177
|
* One saved view, as the rail reads it. `subjectCount` is the caller's,
|
|
@@ -1 +1,2 @@
|
|
|
1
1
|
export { projectGraphForCanvas, type CanvasGraph, type CanvasNode, type CanvasEdge, } from '../graph-projection.js';
|
|
2
|
+
export { foldTree, foldGraph, nestingTree, liftedEdgeId, NESTING_KIND_IDS, type FoldInput, type FoldNode, type FoldEdge, type FoldMembership, type FoldTree, type LiftedEdge, type NestingConflict, type SlotWiring, } from '../fold-tree.js';
|
|
@@ -1 +1,7 @@
|
|
|
1
1
|
export { projectGraphForCanvas, } from '../graph-projection.js';
|
|
2
|
+
// What contains what, and what a fold draws instead (#473). Published here
|
|
3
|
+
// rather than only on the canvas because a host that never renders still has to
|
|
4
|
+
// answer both: an interview counts open questions per box, a report says what
|
|
5
|
+
// an application is made of. `fold-tree.ts` imports nothing, so this subpath
|
|
6
|
+
// stays runtime-neutral.
|
|
7
|
+
export { foldTree, foldGraph, nestingTree, liftedEdgeId, NESTING_KIND_IDS, } from '../fold-tree.js';
|
package/dist/ask-command.js
CHANGED
|
@@ -784,7 +784,7 @@ export function runAskCommand(options, cwd) {
|
|
|
784
784
|
});
|
|
785
785
|
if (!loaded.ok)
|
|
786
786
|
continue;
|
|
787
|
-
const membership = evaluateProjection(graph, loaded.projection, compilation.profileContext);
|
|
787
|
+
const membership = evaluateProjection(graph, loaded.projection, compilation.profileContext, compilation.patternMemberships);
|
|
788
788
|
for (const subject of membership.subjects) {
|
|
789
789
|
covered.add(subject.id);
|
|
790
790
|
}
|
|
@@ -857,7 +857,7 @@ export function runAskCommand(options, cwd) {
|
|
|
857
857
|
});
|
|
858
858
|
if (!loaded.ok)
|
|
859
859
|
return failed(loaded.diagnostics);
|
|
860
|
-
const evaluated = evaluateProjection(graph, loaded.projection, compilation.profileContext);
|
|
860
|
+
const evaluated = evaluateProjection(graph, loaded.projection, compilation.profileContext, compilation.patternMemberships);
|
|
861
861
|
const result = {
|
|
862
862
|
format: 'yarramate/ask-result/v1',
|
|
863
863
|
workspace: workspace.id,
|
package/dist/check-command.js
CHANGED
|
@@ -214,7 +214,18 @@ export function runCheckCommand(options, cwd) {
|
|
|
214
214
|
// does not build would bury the real failure under its consequences.
|
|
215
215
|
const referenceDiagnostics = result.ok
|
|
216
216
|
? loadedProjections.flatMap(({ source, loaded }) => loaded.ok
|
|
217
|
-
? projectionReferenceDiagnostics(source, loaded.projection, result.graph, result.profileContext
|
|
217
|
+
? projectionReferenceDiagnostics(source, loaded.projection, result.graph, result.profileContext,
|
|
218
|
+
// Instance-hood from BOTH lists. A membership row exists only
|
|
219
|
+
// for a BOUND slot, so an instance whose slots are all empty
|
|
220
|
+
// has none - and judging it by bindings alone would call a real
|
|
221
|
+
// instance "not an instance" on the day it was authored, before
|
|
222
|
+
// anything was wired into it. The honest question is not "did
|
|
223
|
+
// it bind anything" but "does the model know it as an
|
|
224
|
+
// instance" (rule 2).
|
|
225
|
+
new Set([
|
|
226
|
+
...(result.patternMemberships ?? []).map(({ instance }) => instance),
|
|
227
|
+
...(result.patternVacancies ?? []).map(({ instance }) => instance),
|
|
228
|
+
]))
|
|
218
229
|
: [])
|
|
219
230
|
: [];
|
|
220
231
|
const optionalDiagnostics = sortDiagnostics([
|
package/dist/compiler.d.ts
CHANGED
|
@@ -108,6 +108,27 @@ export interface PatternMembership {
|
|
|
108
108
|
readonly slot: string;
|
|
109
109
|
readonly instance: string;
|
|
110
110
|
readonly pattern: string;
|
|
111
|
+
/**
|
|
112
|
+
* How the pattern's WIRING relates this slot to the instance (#473).
|
|
113
|
+
*
|
|
114
|
+
* - `owned` — a wire runs `self -> slot`. The instance holds the member out:
|
|
115
|
+
* it is a part, and a view that folds instances may draw it inside.
|
|
116
|
+
* - `context` — a wire runs `slot -> self`. The member acts on the instance
|
|
117
|
+
* rather than belonging to it: the upstream API it calls, the plane it runs
|
|
118
|
+
* on. Folding these would swallow half the landscape into whichever box
|
|
119
|
+
* happened to name it.
|
|
120
|
+
* - `unwired` — the pattern declares the slot and wires nothing through it.
|
|
121
|
+
* Still a part; nothing about containment changes.
|
|
122
|
+
*
|
|
123
|
+
* A slot with wires in BOTH directions is `owned`: the instance holding
|
|
124
|
+
* something out is the stronger statement, and it is what a reader means by
|
|
125
|
+
* the box.
|
|
126
|
+
*
|
|
127
|
+
* Optional, so no existing reader breaks. `yarramate/graph/v2` is unchanged:
|
|
128
|
+
* this is compile CONTEXT like the rest of membership (ADR 0131), never a
|
|
129
|
+
* claim an author could have written.
|
|
130
|
+
*/
|
|
131
|
+
readonly wiring?: 'owned' | 'context' | 'unwired';
|
|
111
132
|
}
|
|
112
133
|
/**
|
|
113
134
|
* One slot of one pattern instance that nothing is bound into (#447): the
|
package/dist/compiler.js
CHANGED
|
@@ -6,6 +6,24 @@ import { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue } from './graph-cla
|
|
|
6
6
|
import { shippedPolicyIdentity, shippedPolicySource, } from './shipped-profile.js';
|
|
7
7
|
import { validateDocument, validateProfile, validatePattern } from './schema-validation.js';
|
|
8
8
|
const coreProfile = 'yarramate/core@0.1';
|
|
9
|
+
/**
|
|
10
|
+
* Which way the pattern's wiring runs between an instance and one of its slots
|
|
11
|
+
* (#473). Read from the pattern rather than from the graph, because it is a
|
|
12
|
+
* fact about the SHAPE and holds whether or not the slot is bound.
|
|
13
|
+
*/
|
|
14
|
+
const slotWiringOf = (pattern, slot) => {
|
|
15
|
+
let held = false;
|
|
16
|
+
let acts = false;
|
|
17
|
+
for (const wire of pattern.wiring) {
|
|
18
|
+
if (wire.from === 'self' && wire.to === slot)
|
|
19
|
+
held = true;
|
|
20
|
+
if (wire.from === slot && wire.to === 'self')
|
|
21
|
+
acts = true;
|
|
22
|
+
}
|
|
23
|
+
// Both directions is `owned`: holding something out is the stronger claim,
|
|
24
|
+
// and it is what a reader means by drawing the box around it.
|
|
25
|
+
return held ? 'owned' : acts ? 'context' : 'unwired';
|
|
26
|
+
};
|
|
9
27
|
const immutableMap = (entries) => {
|
|
10
28
|
const backing = new Map(entries);
|
|
11
29
|
const facade = {
|
|
@@ -2471,6 +2489,7 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2471
2489
|
slot,
|
|
2472
2490
|
instance,
|
|
2473
2491
|
pattern: pattern.kindIdentity,
|
|
2492
|
+
wiring: slotWiringOf(pattern, slot),
|
|
2474
2493
|
})))
|
|
2475
2494
|
.sort((left, right) => left.member.localeCompare(right.member) ||
|
|
2476
2495
|
left.pattern.localeCompare(right.pattern) ||
|
package/dist/export-command.js
CHANGED
|
@@ -250,7 +250,10 @@ export function runExportCommand(options, cwd) {
|
|
|
250
250
|
});
|
|
251
251
|
if (!loadedProjection.ok)
|
|
252
252
|
return failed(loadedProjection.diagnostics);
|
|
253
|
-
result = evaluateProjection(compilation.graph, loadedProjection.projection, compilation.profileContext
|
|
253
|
+
result = evaluateProjection(compilation.graph, loadedProjection.projection, compilation.profileContext,
|
|
254
|
+
// An AUTHORED projection can name `instances`, and the facet resolves
|
|
255
|
+
// to the instance alone without these (ADR 0144).
|
|
256
|
+
compilation.patternMemberships);
|
|
254
257
|
}
|
|
255
258
|
if (kind === 'xlsx') {
|
|
256
259
|
// A workbook an architect can work in (#355). It takes a PROJECTION,
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What contains what on a canvas, and what a folded container draws instead of
|
|
3
|
+
* its contents (#473).
|
|
4
|
+
*
|
|
5
|
+
* Two questions, one module, because they are the same question asked twice. A
|
|
6
|
+
* VIEW says which relationships nest (ADR 0101); a PATTERN says which subjects
|
|
7
|
+
* are parts of an instance (ADR 0123). Both produce a parent-of map over the
|
|
8
|
+
* same node ids, and folding reads that one map. Answering them apart would
|
|
9
|
+
* mean two trees that can disagree about who owns a node.
|
|
10
|
+
*
|
|
11
|
+
* Imports nothing but the `NestingKind` type, and that from `./nesting.js`,
|
|
12
|
+
* which itself imports nothing. The same weight argument that split
|
|
13
|
+
* `nesting.ts` out of `projection.ts` applies here and harder: this module is
|
|
14
|
+
* reached from `yarramate/adapter/visual-graph`, the runtime-neutral subpath a
|
|
15
|
+
* Durable Object imports, where `node:module` and Ajv are not available at any
|
|
16
|
+
* price. `test/visual-app-browser-safety.test.ts` is what holds that line.
|
|
17
|
+
*
|
|
18
|
+
* Everything here is a pure function over plain data. The canvas adapts its own
|
|
19
|
+
* shapes to {@link FoldInput}; nothing in this file knows what cytoscape is.
|
|
20
|
+
*/
|
|
21
|
+
import type { NestingKind } from './nesting.js';
|
|
22
|
+
/**
|
|
23
|
+
* A view names which relationships nest, in precedence order (ADR 0101). The
|
|
24
|
+
* short names a projection is authored in resolve to the kind identities the
|
|
25
|
+
* graph carries, in one place, so the schema's vocabulary and the canvas's
|
|
26
|
+
* cannot drift.
|
|
27
|
+
*/
|
|
28
|
+
export declare const NESTING_KIND_IDS: Readonly<Record<NestingKind, string>>;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a view draws pattern instances folded by default (#473).
|
|
31
|
+
*
|
|
32
|
+
* Lives here rather than in `projection.ts` for the reason `nesting.ts` exists:
|
|
33
|
+
* the browser needs the VALUE, and `projection.ts` drags Ajv and the projection
|
|
34
|
+
* schema in behind it. `projection.ts` re-exports both.
|
|
35
|
+
*/
|
|
36
|
+
export type FoldMode = 'instances' | 'none';
|
|
37
|
+
/**
|
|
38
|
+
* What a view folds when it does not say: nothing. Folding hides detail, and a
|
|
39
|
+
* view that hid detail without being asked would be a surprise its author never
|
|
40
|
+
* wrote down.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DEFAULT_FOLD: FoldMode;
|
|
43
|
+
/** One node, reduced to what containment needs to know about it. */
|
|
44
|
+
export interface FoldNode {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
/** The kind as authored, profile-qualified or not. Unused by the rules here. */
|
|
47
|
+
readonly kind: string;
|
|
48
|
+
/**
|
|
49
|
+
* The core-vocabulary kind this resolves to. Every rule below reads THIS and
|
|
50
|
+
* never `kind`: a profile's `mule-api-operation` is an `applicationService`
|
|
51
|
+
* and must be treated as one, and the label it happens to carry is not a
|
|
52
|
+
* fact about what it is.
|
|
53
|
+
*/
|
|
54
|
+
readonly coreKind: string;
|
|
55
|
+
}
|
|
56
|
+
/** One relationship, reduced to what containment needs to know about it. */
|
|
57
|
+
export interface FoldEdge {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly kind: string;
|
|
60
|
+
readonly from: string;
|
|
61
|
+
readonly to: string;
|
|
62
|
+
}
|
|
63
|
+
/** How a pattern's wiring relates a slot to the instance that declares it. */
|
|
64
|
+
export type SlotWiring = 'owned' | 'context' | 'unwired';
|
|
65
|
+
/** One bound slot, as {@link foldTree} needs it. */
|
|
66
|
+
export interface FoldMembership {
|
|
67
|
+
readonly member: string;
|
|
68
|
+
readonly slot: string;
|
|
69
|
+
readonly instance: string;
|
|
70
|
+
readonly wiring?: SlotWiring;
|
|
71
|
+
}
|
|
72
|
+
export interface FoldInput {
|
|
73
|
+
readonly nodes: readonly FoldNode[];
|
|
74
|
+
readonly edges: readonly FoldEdge[];
|
|
75
|
+
readonly memberships: readonly FoldMembership[];
|
|
76
|
+
readonly nesting: readonly NestingKind[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Two parents claiming one child at the same precedence. Returned rather than
|
|
80
|
+
* resolved: picking a winner would hide a real modelling anomaly behind a
|
|
81
|
+
* layout that looks deliberate. The caller renders the child unnested and says
|
|
82
|
+
* so, which is what composition alone already did.
|
|
83
|
+
*/
|
|
84
|
+
export interface NestingConflict {
|
|
85
|
+
readonly child: string;
|
|
86
|
+
readonly claims: readonly {
|
|
87
|
+
readonly edgeId: string;
|
|
88
|
+
readonly kind: string;
|
|
89
|
+
readonly from: string;
|
|
90
|
+
}[];
|
|
91
|
+
}
|
|
92
|
+
export interface FoldTree {
|
|
93
|
+
readonly parentOf: ReadonlyMap<string, string>;
|
|
94
|
+
readonly consumedEdgeIds: ReadonlySet<string>;
|
|
95
|
+
readonly conflicts: readonly NestingConflict[];
|
|
96
|
+
/** Ids left unnested because their parent chain loops. */
|
|
97
|
+
readonly cycleMembers: readonly string[];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The parent-of map a view's nesting kinds imply.
|
|
101
|
+
*
|
|
102
|
+
* The compiler's `YM501` rule rejects one pair declaring both composition and
|
|
103
|
+
* aggregation; it does not reject two different compositions naming one child,
|
|
104
|
+
* which a single-parent field cannot represent either, nor a composition chain
|
|
105
|
+
* that loops. Both are real modelling anomalies, surfaced here rather than
|
|
106
|
+
* silently resolved: affected subjects come back unnested and every edge naming
|
|
107
|
+
* them stays an ordinary line, so the conflicting claims remain visible.
|
|
108
|
+
*/
|
|
109
|
+
export declare function nestingTree(edges: readonly FoldEdge[], nesting: readonly NestingKind[], coreKindOf: (id: string) => string): FoldTree;
|
|
110
|
+
/**
|
|
111
|
+
* The containment tree: what a view nests, plus what a pattern owns.
|
|
112
|
+
*
|
|
113
|
+
* A slot member joins the tree only when all three hold, and each condition is
|
|
114
|
+
* a different way of getting the answer wrong:
|
|
115
|
+
*
|
|
116
|
+
* - **Exclusive.** A subject bound into two instances has two owners, and a
|
|
117
|
+
* single-parent tree would silently pick one. Shared subjects stay outside.
|
|
118
|
+
* - **`owned` or `unwired`, never `context`.** A context slot names something
|
|
119
|
+
* the instance USES and does not contain — the upstream API it calls, the
|
|
120
|
+
* plane it runs on. Folding those would swallow half the landscape into
|
|
121
|
+
* whichever box happened to reference it.
|
|
122
|
+
* - **Not a ruling.** See {@link RULING_CORE_KINDS}.
|
|
123
|
+
*
|
|
124
|
+
* A view's own nesting wins where both apply: the view is the more specific
|
|
125
|
+
* statement, and a reader who wrote `nesting: [composition]` meant it.
|
|
126
|
+
*/
|
|
127
|
+
export declare function foldTree(input: FoldInput): FoldTree;
|
|
128
|
+
/** An edge that stands for one or more relationships hidden inside a fold. */
|
|
129
|
+
export interface LiftedEdge {
|
|
130
|
+
readonly id: string;
|
|
131
|
+
readonly kind: string;
|
|
132
|
+
readonly from: string;
|
|
133
|
+
readonly to: string;
|
|
134
|
+
readonly count: number;
|
|
135
|
+
readonly relationshipIds: readonly string[];
|
|
136
|
+
}
|
|
137
|
+
/** The id a lifted edge takes. Deterministic, so a re-render is stable. */
|
|
138
|
+
export declare const liftedEdgeId: (from: string, to: string, kind: string) => string;
|
|
139
|
+
/**
|
|
140
|
+
* What the canvas draws once some instances are folded.
|
|
141
|
+
*
|
|
142
|
+
* A folded instance KEEPS its own node — it is still a subject, still
|
|
143
|
+
* selectable, still the thing a question is about — and gains what it is
|
|
144
|
+
* standing in for. Its descendants leave the output, and every edge with an end
|
|
145
|
+
* inside it is lifted to the box.
|
|
146
|
+
*
|
|
147
|
+
* Lifted edges of one kind between one ordered pair merge into a single edge
|
|
148
|
+
* carrying `count` and the ids it stands for, so seven `serving` relationships
|
|
149
|
+
* between two applications draw as one line labelled ×7 rather than as seven
|
|
150
|
+
* lines the reader has to count. An edge whose ends fold into the SAME box
|
|
151
|
+
* vanishes: it is internal, and the box is the statement now.
|
|
152
|
+
*/
|
|
153
|
+
export declare function foldGraph<N extends {
|
|
154
|
+
readonly id: string;
|
|
155
|
+
}, E extends {
|
|
156
|
+
readonly id: string;
|
|
157
|
+
readonly kind: string;
|
|
158
|
+
readonly from: string;
|
|
159
|
+
readonly to: string;
|
|
160
|
+
}>(graph: {
|
|
161
|
+
readonly nodes: readonly N[];
|
|
162
|
+
readonly edges: readonly E[];
|
|
163
|
+
}, tree: Pick<FoldTree, 'parentOf'>, folded: ReadonlySet<string>): {
|
|
164
|
+
readonly nodes: (N & {
|
|
165
|
+
readonly folded: boolean;
|
|
166
|
+
readonly insideIds: readonly string[];
|
|
167
|
+
})[];
|
|
168
|
+
readonly edges: (E | LiftedEdge)[];
|
|
169
|
+
};
|