@oselvar/c4
Version:
Test helpers for Cloudflare Workers
283 lines (280 loc) • 7.38 kB
JavaScript
// 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