UNPKG

@oselvar/c4

Version:

Test helpers for Cloudflare Workers

283 lines (280 loc) 7.38 kB
// src/core/strings.ts function callKey(call) { return `${call.callerName}-${call.calleeName}-${call.operationName}`; } function objectKey(object) { return makeObjectKey(object.type, object.name); } function makeObjectKey(type, name) { return camelCase(`${type} ${name}`); } function camelCase(input) { const noApos = input.replace(/['’]/g, ""); const segments = noApos.split(/[^A-Za-z0-9]+/).filter(Boolean); return segments.map((seg, i) => { const lower = seg.toLowerCase(); if (i === 0) return lower; return lower[0].toUpperCase() + lower.slice(1); }).join(""); } // src/core/getUniqueCalls.ts function getUniqueCalls(callchains) { const calls = Object.fromEntries( callchains.flatMap((callchain) => callchain.calls).map((call) => [callKey(call), call]) ); return Object.values(calls).toSorted( (a, b) => callKey(a).localeCompare(callKey(b)) ); } // src/core/C4ModelBuilder.ts import { trace } from "@opentelemetry/api"; import { closest } from "fastest-levenshtein"; var C4ModelBuilder = class { constructor(c4Model = { objects: {}, callchains: [] }, ready) { this.ready = ready; this.objectByName = new Map( Object.entries(c4Model.objects).map(([, object]) => [ object.name, object ]) ); for (const object of Object.values(c4Model.objects)) { if (object.parentName) { this.getObject(object.parentName); } } for (const call of getUniqueCalls(c4Model.callchains)) { this.getObject(call.callerName); this.getObject(call.calleeName); } this.callchains = [...c4Model.callchains]; } objectByName = /* @__PURE__ */ new Map(); callchains; callchain = null; endSpan = () => { }; /** * Add a person to the model. */ addPerson(name, params) { const type = "person"; this.objectByName.set(name, { type, name, tags: params?.tags || [], parentName: params?.parentName }); this.ready.then(() => { const tracer = trace.getTracer("@oselvar/c4"); tracer.startSpan(type, { attributes: { name, tags: [...params?.tags || []], parentName: params?.parentName } }).end(); }); return name; } /** * Add a group to the model. */ addGroup(name, params) { const type = "group"; this.objectByName.set(name, { type, name, tags: params?.tags || [], parentName: params?.parentName || null }); this.ready.then(() => { const tracer = trace.getTracer("@oselvar/c4"); tracer.startSpan(type, { attributes: { name, tags: [...params?.tags || []], parentName: params?.parentName } }).end(); }); return name; } /** * Add a software system to the model. */ addSoftwareSystem(name, params) { const type = "softwareSystem"; this.objectByName.set(name, { type, name, tags: params?.tags || [], parentName: params?.parentName || null }); this.ready.then(() => { const tracer = trace.getTracer("@oselvar/c4"); tracer.startSpan(type, { attributes: { name, tags: [...params?.tags || []], parentName: params?.parentName } }).end(); }); return name; } /** * Add a container to the model. */ addContainer(name, params) { const type = "container"; this.objectByName.set(name, { type, name, tags: params?.tags || [], parentName: params.softwareSystem }); this.ready.then(() => { const tracer = trace.getTracer("@oselvar/c4"); tracer.startSpan(type, { attributes: { name, tags: [...params?.tags || []], parentName: params?.softwareSystem } }).end(); }); return name; } addComponent(name, params) { const type = "component"; this.objectByName.set(name, { type, name, tags: params?.tags || [], parentName: params.container }); this.ready.then(() => { const tracer = trace.getTracer("@oselvar/c4"); tracer.startSpan(type, { attributes: { name, tags: [...params?.tags || []], parentName: params?.container } }).end(); }); return name; } startCallchain(name) { const callchain = { name, calls: [] }; this.callchains.push(callchain); this.callchain = callchain; this.endSpan(); const tracer = trace.getTracer("@oselvar/c4"); tracer.startActiveSpan( `callchain`, { attributes: { name } }, (span) => { this.endSpan = () => span.end(); } ); } /** * Add a dependency between two objects. */ addCall(callerName, calleeName, operationName) { const caller = this.getObject(callerName); const callee = this.getObject(calleeName); const call = { callerName: caller.name, calleeName: callee.name, operationName }; if (!this.callchain) { throw new Error("Callchain not started"); } this.callchain.calls.push(call); } hasObject(name) { return this.objectByName.has(name); } /** * Get an object by id. */ getObject(name, type) { const c4Object = this.objectByName.get(name); if (!c4Object) { const c4Objects = Array.from(this.objectByName.keys()); const maybe = closest(name, c4Objects); const didYouMean = maybe ? ` Did you mean "${maybe}"?` : ""; throw new Error( `C4 object "${name}" not found.${didYouMean} Make sure this object is registered in the C4Model. Registered objects: ${JSON.stringify( c4Objects, null, 2 )}` ); } if (type && c4Object.type !== type) { throw new Error( `C4 object "${name}" is a ${c4Object.type}, not a ${type}` ); } return c4Object; } calls(c4Object) { return getUniqueCalls(this.callchains).filter((dependency) => dependency.callerName === c4Object.name).toSorted((a, b) => callKey(a).localeCompare(callKey(b))); } children(c4Object) { return Array.from(this.objectByName.values()).filter((object) => object.parentName === c4Object.name).toSorted((a, b) => objectKey(a).localeCompare(objectKey(b))); } nestedChildren(c4Object) { const directChildren = this.children(c4Object); return [ ...directChildren, ...directChildren.flatMap((child) => this.nestedChildren(child)) ]; } isChildOf(c4Object, parentId) { if (c4Object.parentName === parentId) return true; if (c4Object.parentName === null) return false; return this.isChildOf(this.getObject(c4Object.parentName), parentId); } rootObjects() { return Array.from(this.objectByName.values()).filter((object) => object.parentName === null).toSorted((a, b) => objectKey(a).localeCompare(objectKey(b))); } /** * Build the final C4 model */ build() { const objects = Object.fromEntries( this.objectByName.entries() ); return { objects, callchains: this.callchains.filter( (callchain) => callchain.calls.length > 0 ) }; } }; export { callKey, objectKey, makeObjectKey, camelCase, getUniqueCalls, C4ModelBuilder }; //# sourceMappingURL=chunk-ZMBFSHSD.js.map