0.11.0: arc42 and ArchiMate examples; ref in every diagram kind
Six example models next to the three C4 ones: arc42 (Cinema Tickets, IoT Telemetry, Team Wiki) by section, ArchiMate (Insurance Claims, HR Onboarding, Logistics Tracking) by layer, each validated with 0 errors and 0 warnings. resolveRefs and the editor schemas now take ref: in workflow and dataflow nodes, sequence participants and lifecycle states, as the skill already did. Example tests check each framework's folders.
This commit is contained in:
1 parent
f4df6a0834
commit
0745de822a
85 files changed
+2945
-67
No files matched your search
@@ -4,7 +4,8 @@ import { modelIdFromName, titleFromName } from './templates';
|
||||
/**
|
||||
* Example models shipped with the extension (templates/models/<folder>).
|
||||
* "Archify | New Model…" offers them; each is a complete model folder:
|
||||
* main.archify, registry/*.archify-registry and diagrams by level.
|
||||
* main.archify, registry/*.archify-registry, decisions and diagrams in the
|
||||
* framework's folders: C4 levels, arc42 sections or ArchiMate layers.
|
||||
*/
|
||||
export interface ModelExample {
|
||||
/** Folder under templates/models and the default name of the new model folder. */
|
||||
@@ -17,8 +18,17 @@ export const MODEL_EXAMPLES: readonly ModelExample[] = [
|
||||
{ folder: 'c4-standard-web-app', label: 'C4 - Standard Web App', description: 'Single-page app, API, database, cache, file storage, identity provider' },
|
||||
{ folder: 'c4-e-shop', label: 'C4 - E-Shop', description: 'Web shop, API, worker, queue, payment and email providers' },
|
||||
{ folder: 'c4-event-based-integration', label: 'C4 - Event-Based Integration', description: 'Event bus, router, dead-letter queue, event store, consumers' },
|
||||
{ folder: 'arc42-cinema-tickets', label: 'arc42 - Cinema Tickets', description: 'Context, building blocks, runtime and deployment views; seat booking with payment' },
|
||||
{ folder: 'arc42-iot-telemetry', label: 'arc42 - IoT Telemetry', description: 'Devices over MQTT, ingest, time-series store, alerting, dashboards' },
|
||||
{ folder: 'arc42-team-wiki', label: 'arc42 - Team Wiki', description: 'A small self-hosted wiki: the arc42 sections at their smallest' },
|
||||
{ folder: 'archimate-insurance-claims', label: 'ArchiMate - Insurance Claims', description: 'Business, application and technology layers of claim handling' },
|
||||
{ folder: 'archimate-hr-onboarding', label: 'ArchiMate - HR Onboarding', description: 'Onboarding a new employee across HR, identity and IT services' },
|
||||
{ folder: 'archimate-logistics-tracking', label: 'ArchiMate - Logistics Tracking', description: 'Shipment tracking from pick-up to proof of delivery, layer by layer' },
|
||||
];
|
||||
|
||||
/** The framework prefix of an example folder; the model id is the folder without it. */
|
||||
export const EXAMPLE_PREFIX = /^(c4|arc42|archimate)-/;
|
||||
|
||||
/**
|
||||
* The example's main.archify for a new model folder. When the folder keeps
|
||||
* the example's name the file is used as written; otherwise id, name and the
|
||||
|
||||
+23
-11
@@ -163,13 +163,21 @@ export interface DecisionRef {
|
||||
|
||||
/** Collections whose items may carry `decisions` (and `diagram` links), per diagram kind. */
|
||||
const NODE_COLLECTIONS = ['components', 'nodes', 'participants', 'states'] as const;
|
||||
/** The list that holds a diagram kind's nodes, where `ref` is resolved. */
|
||||
const NODE_COLLECTION_OF: Record<string, (typeof NODE_COLLECTIONS)[number]> = {
|
||||
architecture: 'components',
|
||||
workflow: 'nodes',
|
||||
dataflow: 'nodes',
|
||||
sequence: 'participants',
|
||||
lifecycle: 'states',
|
||||
};
|
||||
const EDGE_COLLECTIONS = ['connections', 'edges', 'flows', 'transitions', 'messages'] as const;
|
||||
|
||||
export interface ResolvedDiagram {
|
||||
/** The diagram as Archify expects it: refs expanded, model block and links removed. */
|
||||
diagram: Obj;
|
||||
problems: ModelProblem[];
|
||||
/** Component index → the registry element it came from. */
|
||||
/** Node index (in the kind's node list) → the registry element it came from. */
|
||||
refs: Map<number, RegistryElement>;
|
||||
/** Node id → diagram path (relative to the model root) it links to. */
|
||||
links: Map<string, string>;
|
||||
@@ -253,10 +261,14 @@ export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiag
|
||||
delete diagram.model;
|
||||
const decisionRefs = takeDecisionRefs(diagram, options, problems);
|
||||
const { registry } = options;
|
||||
const components = Array.isArray(diagram.components) ? diagram.components : undefined;
|
||||
if (!components) return { diagram, problems, refs, links, used, descriptions, decisionRefs };
|
||||
// Each kind keeps its nodes in its own list; sequence participants have no tag.
|
||||
const kind = str(diagram.diagram_type);
|
||||
const collection = (kind && NODE_COLLECTION_OF[kind]) ?? NODE_COLLECTIONS.find((key) => Array.isArray(diagram[key]));
|
||||
const nodes = collection && Array.isArray(diagram[collection]) ? (diagram[collection] as unknown[]) : undefined;
|
||||
if (!collection || !nodes) return { diagram, problems, refs, links, used, descriptions, decisionRefs };
|
||||
const copied: readonly string[] = kind === 'sequence' ? COPIED_FIELDS.filter((key) => key !== 'tag') : COPIED_FIELDS;
|
||||
|
||||
diagram.components = components.map((raw, index) => {
|
||||
diagram[collection] = nodes.map((raw, index) => {
|
||||
if (!isObj(raw)) return raw;
|
||||
const ref = str(raw.ref);
|
||||
const linkFromComponent = str(raw.diagram);
|
||||
@@ -275,7 +287,7 @@ export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiag
|
||||
? `Unknown element "${ref}": no registry of this model defines it.`
|
||||
: `"${ref}" cannot be resolved: ${options.fileName.split(/[\\/]/).pop()} is not inside a model (no ${MAIN_FILE} above it).`,
|
||||
severity: 'error',
|
||||
pointer: `/components/${index}/ref`,
|
||||
pointer: `/${collection}/${index}/ref`,
|
||||
});
|
||||
return { type: 'external', label: ref, ...own, id };
|
||||
}
|
||||
@@ -283,19 +295,19 @@ export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiag
|
||||
refs.set(index, element);
|
||||
if (element.description) descriptions.set(id, element.description.trim());
|
||||
for (const key of Object.keys(own)) {
|
||||
if ((COPIED_FIELDS as readonly string[]).includes(key) && !(OVERRIDABLE_FIELDS as readonly string[]).includes(key)) {
|
||||
if (copied.includes(key) && !(OVERRIDABLE_FIELDS as readonly string[]).includes(key)) {
|
||||
problems.push({
|
||||
message: `"${key}" comes from the registry element "${ref}" and cannot be changed in a diagram.`,
|
||||
severity: 'error',
|
||||
pointer: `/components/${index}/${key}`,
|
||||
pointer: `/${collection}/${index}/${key}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
const merged: Obj = { id };
|
||||
for (const key of COPIED_FIELDS) if (element[key] !== undefined) merged[key] = element[key];
|
||||
for (const key of copied) if (element[key as (typeof COPIED_FIELDS)[number]] !== undefined) merged[key] = element[key as (typeof COPIED_FIELDS)[number]];
|
||||
for (const [key, value] of Object.entries(own)) {
|
||||
if (key === 'id') continue;
|
||||
if (!(COPIED_FIELDS as readonly string[]).includes(key) || (OVERRIDABLE_FIELDS as readonly string[]).includes(key)) merged[key] = value;
|
||||
if (!copied.includes(key) || (OVERRIDABLE_FIELDS as readonly string[]).includes(key)) merged[key] = value;
|
||||
}
|
||||
const link = linkFromComponent ?? element.diagram;
|
||||
if (link) links.set(id, link);
|
||||
@@ -353,8 +365,8 @@ export function summarizeDiagram(text: string, relativePath: string): DiagramSum
|
||||
}
|
||||
const obj = isObj(doc) ? doc : {};
|
||||
const meta = isObj(obj.meta) ? obj.meta : {};
|
||||
const components = Array.isArray(obj.components) ? obj.components : [];
|
||||
const used = [...new Set(components.map((c) => (isObj(c) ? str(c.ref) : undefined)).filter((r): r is string => !!r))];
|
||||
const items = NODE_COLLECTIONS.flatMap((key) => (Array.isArray(obj[key]) ? (obj[key] as unknown[]) : []));
|
||||
const used = [...new Set(items.map((c) => (isObj(c) ? str(c.ref) : undefined)).filter((r): r is string => !!r))];
|
||||
const decisions = takeDecisionRefs({ ...obj }, { registry: { elements: new Map(), files: [], problems: [] }, fileName: relativePath }, []);
|
||||
const nodes: DiagramNode[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
Reference in new issue
Block a user