kaabalah
Version:
The de-facto library for any esoteric calculations and tooling
853 lines (850 loc) • 25.1 kB
JavaScript
import { SPHERES, id, createTree, parseId, makeCorrespondenceId } from './chunk-5K4OQ4UB.mjs';
// src/core/topology.ts
var TREE_TOPOLOGY_SPHERE_NAMES = [
SPHERES.KETHER,
SPHERES.CHOKHMAH,
SPHERES.BINAH,
SPHERES.DAATH,
SPHERES.CHESED,
SPHERES.GEBURAH,
SPHERES.TIPHARETH,
SPHERES.NETZACH,
SPHERES.HOD,
SPHERES.YESOD,
SPHERES.MALKUTH
];
var TREE_TOPOLOGY_SPHERE_IDS = TREE_TOPOLOGY_SPHERE_NAMES.map(
(name) => id("sphere" /* SPHERE */, name)
);
var TREE_TOPOLOGY_PATH_IDS = Array.from(
{ length: 22 },
(_, index) => id("path" /* PATH */, index + 1)
);
var TREE_TOPOLOGY_SPHERE_NUMBERS = {
[SPHERES.KETHER]: 1,
[SPHERES.CHOKHMAH]: 2,
[SPHERES.BINAH]: 3,
[SPHERES.DAATH]: 11,
[SPHERES.CHESED]: 4,
[SPHERES.GEBURAH]: 5,
[SPHERES.TIPHARETH]: 6,
[SPHERES.NETZACH]: 7,
[SPHERES.HOD]: 8,
[SPHERES.YESOD]: 9,
[SPHERES.MALKUTH]: 10
};
var TREE_TOPOLOGY_LIGHTNING_SPHERE_NAMES = [
SPHERES.KETHER,
SPHERES.CHOKHMAH,
SPHERES.BINAH,
SPHERES.CHESED,
SPHERES.GEBURAH,
SPHERES.TIPHARETH,
SPHERES.NETZACH,
SPHERES.HOD,
SPHERES.YESOD,
SPHERES.MALKUTH
];
function isNodeType(node, type) {
return node.type === type;
}
function cloneSphereData(data) {
return data ? { ...data } : void 0;
}
function clonePathData(data) {
return { ...data };
}
function parsePathNumber(pathId) {
const parsed = Number.parseInt(parseId(pathId), 10);
if (!Number.isInteger(parsed)) {
throw new Error(`Invalid tree path id: ${pathId}`);
}
return parsed;
}
function normalizeName(value) {
return value.trim().toLowerCase();
}
function pairKey(left, right) {
return [String(left), String(right)].sort().join("|");
}
function routeNames() {
return [...TREE_TOPOLOGY_LIGHTNING_SPHERE_NAMES];
}
function routeDescriptor(key) {
return key === "lightning" ? {
name: "Lightning Path",
direction: "descending"
} : {
name: "Serpent Path",
direction: "ascending"
};
}
function routeTargetList(segments) {
if (segments.length === 0) {
return [];
}
const targets = [
{ targetId: segments[0].from.id, targetType: "sphere" }
];
for (const segment of segments) {
if (segment.path) {
targets.push({ targetId: segment.path.id, targetType: "path" });
}
targets.push({ targetId: segment.to.id, targetType: "sphere" });
}
return targets;
}
var TreeTopology = class {
constructor({
system,
spheres,
paths
}) {
this.spheresById = /* @__PURE__ */ new Map();
this.spheresByName = /* @__PURE__ */ new Map();
this.spheresByNumber = /* @__PURE__ */ new Map();
this.pathsById = /* @__PURE__ */ new Map();
this.pathsByNumber = /* @__PURE__ */ new Map();
this.pathsBySpherePair = /* @__PURE__ */ new Map();
this.routesByKey = /* @__PURE__ */ new Map();
this.system = system;
this.spheres = spheres.map(
(sphere) => Object.freeze({
...sphere,
data: cloneSphereData(sphere.data)
})
);
for (const sphere of this.spheres) {
this.spheresById.set(sphere.id, sphere);
this.spheresByName.set(normalizeName(sphere.name), sphere);
this.spheresByNumber.set(sphere.number, sphere);
}
this.paths = paths.map((path) => {
const from = this.spheresById.get(path.from.id);
const to = this.spheresById.get(path.to.id);
if (!from || !to) {
throw new Error(
`Tree topology path ${path.id} references unknown spheres.`
);
}
return Object.freeze({
...path,
from,
to,
data: clonePathData(path.data)
});
});
for (const path of this.paths) {
this.pathsById.set(path.id, path);
this.pathsByNumber.set(path.number, path);
this.pathsBySpherePair.set(pairKey(path.from.id, path.to.id), path);
}
for (const key of ["lightning", "serpent"]) {
this.routesByKey.set(key, this.buildRoute(key));
}
}
getSpheres(options = {}) {
const includeDaath = options.includeDaath ?? true;
return includeDaath ? [...this.spheres] : this.spheres.filter((sphere) => sphere.name !== SPHERES.DAATH);
}
getPrimarySpheres() {
return this.getSpheres({ includeDaath: false });
}
getSphere(lookup) {
if (typeof lookup === "string") {
if (lookup.startsWith(`${"sphere" /* SPHERE */}:`)) {
return this.spheresById.get(lookup);
}
return this.spheresByName.get(normalizeName(lookup));
}
if (lookup.id) {
return this.spheresById.get(lookup.id);
}
if (lookup.number) {
return this.spheresByNumber.get(lookup.number);
}
if (lookup.name) {
return this.spheresByName.get(normalizeName(lookup.name));
}
return void 0;
}
getPaths() {
return [...this.paths];
}
getPath(lookup) {
if (typeof lookup === "number") {
return this.pathsByNumber.get(lookup);
}
if (typeof lookup === "string") {
return this.pathsById.get(lookup);
}
if (lookup.id) {
return this.pathsById.get(lookup.id);
}
if (lookup.number) {
return this.pathsByNumber.get(lookup.number);
}
if (lookup.between) {
return this.getPathBetween(lookup.between[0], lookup.between[1]);
}
return void 0;
}
getPathBetween(first, second) {
const firstSphere = this.getSphere(first);
const secondSphere = this.getSphere(second);
if (!firstSphere || !secondSphere) {
return void 0;
}
return this.pathsBySpherePair.get(pairKey(firstSphere.id, secondSphere.id));
}
getAdjacentSpheres(sphereLookup) {
const sphere = this.getSphere(sphereLookup);
if (!sphere) {
return [];
}
return this.paths.filter((path) => path.from.id === sphere.id || path.to.id === sphere.id).map((path) => {
const isForward = path.from.id === sphere.id;
return {
sphere: isForward ? path.to : path.from,
path,
direction: isForward ? "forward" : "reverse"
};
});
}
getRoutes() {
return [...this.routesByKey.values()];
}
getRoute(key) {
return this.routesByKey.get(key);
}
nextInRoute(routeKey, sphereLookup) {
const route = this.getRoute(routeKey);
const sphere = this.getSphere(sphereLookup);
if (!route || !sphere) {
return void 0;
}
const index = route.spheres.findIndex(
(routeSphere) => routeSphere.id === sphere.id
);
return index >= 0 ? route.spheres[index + 1] : void 0;
}
previousInRoute(routeKey, sphereLookup) {
const route = this.getRoute(routeKey);
const sphere = this.getSphere(sphereLookup);
if (!route || !sphere) {
return void 0;
}
const index = route.spheres.findIndex(
(routeSphere) => routeSphere.id === sphere.id
);
return index > 0 ? route.spheres[index - 1] : void 0;
}
buildRoute(key) {
const descriptor = routeDescriptor(key);
const names = routeNames();
const orderedNames = key === "serpent" ? names.reverse() : names;
const spheres = orderedNames.map((name) => {
const sphere = this.getSphere(name);
if (!sphere) {
throw new Error(
`Tree topology route "${key}" references missing sphere "${name}" in ${this.system}.`
);
}
return sphere;
});
const segments = [];
for (let index = 0; index < spheres.length - 1; index++) {
const from = spheres[index];
const to = spheres[index + 1];
const path = this.getPathBetween(from.name, to.name);
segments.push({
index,
from,
to,
path,
isConnected: Boolean(path)
});
}
const targets = routeTargetList(segments);
const missingSegments = segments.filter((segment) => !segment.isConnected);
return Object.freeze({
key,
name: descriptor.name,
direction: descriptor.direction,
spheres: Object.freeze([...spheres]),
segments: Object.freeze(segments),
isFullyConnected: missingSegments.length === 0,
missingSegments: Object.freeze(missingSegments),
targets: Object.freeze(targets),
targetIds: Object.freeze(targets.map((target) => target.targetId))
});
}
};
function getTreeTopology(options = {}) {
const system = options.system ?? options.tree?.activeSystem ?? "kaabalah";
const tree = options.tree ?? createTree({ system });
const spheres = [];
for (const sphereId of TREE_TOPOLOGY_SPHERE_IDS) {
const sphereNode = tree.getNode(sphereId);
if (!sphereNode) {
continue;
}
const name = parseId(sphereId);
spheres.push({
id: sphereId,
name,
number: TREE_TOPOLOGY_SPHERE_NUMBERS[name],
role: name === SPHERES.DAATH ? "hidden" : "sephirah",
data: cloneSphereData(sphereNode.data)
});
}
const spheresById = new Map(spheres.map((sphere) => [sphere.id, sphere]));
const paths = tree.getNodes().filter(
(node) => isNodeType(node, "path" /* PATH */)
).map((pathNode) => {
if (!pathNode.data?.from || !pathNode.data?.to) {
throw new Error(`Tree topology path ${pathNode.id} is missing endpoints.`);
}
const from = spheresById.get(pathNode.data.from);
const to = spheresById.get(pathNode.data.to);
if (!from || !to) {
throw new Error(
`Tree topology path ${pathNode.id} references unknown spheres.`
);
}
return {
id: pathNode.id,
number: parsePathNumber(pathNode.id),
from,
to,
data: clonePathData(pathNode.data)
};
}).sort((left, right) => left.number - right.number);
return new TreeTopology({
system,
spheres,
paths
});
}
// src/core/workspace.ts
function cloneNode(node) {
return Object.freeze({
id: node.id,
type: node.type,
name: node.name,
data: node.data && typeof node.data === "object" ? { ...node.data } : node.data
});
}
function cloneMetadata(metadata) {
if (!metadata) {
return void 0;
}
return {
...metadata,
tags: metadata.tags ? [...metadata.tags] : void 0,
attributes: metadata.attributes ? { ...metadata.attributes } : void 0
};
}
function cloneSource(source) {
if (source.kind === "bridge") {
return {
...source,
parts: [...source.parts]
};
}
return { ...source };
}
function cloneEdge(edge) {
return Object.freeze({
...edge,
metadata: cloneMetadata(edge.metadata),
sources: edge.sources.map(cloneSource)
});
}
function cloneNote(note) {
return Object.freeze({
...note,
target: { ...note.target },
metadata: note.metadata ? { ...note.metadata } : void 0
});
}
function mergeMetadata(current, next) {
if (!current) {
return cloneMetadata(next);
}
if (!next) {
return cloneMetadata(current);
}
return {
...current,
...next,
tags: [.../* @__PURE__ */ new Set([...current.tags ?? [], ...next.tags ?? []])],
attributes: {
...current.attributes ?? {},
...next.attributes ?? {}
}
};
}
function mergeSources(current, next) {
const seen = /* @__PURE__ */ new Set();
const merged = [];
for (const source of [...current, next]) {
const key = JSON.stringify(source);
if (seen.has(key)) {
continue;
}
seen.add(key);
merged.push(cloneSource(source));
}
return merged;
}
function normalizeTypes(type) {
if (!type) {
return void 0;
}
return new Set(Array.isArray(type) ? type : [type]);
}
function sortNodes(nodes) {
return [...nodes].sort((left, right) => {
if (left.type !== right.type) {
return left.type.localeCompare(right.type);
}
const leftLabel = String(left.name ?? left.id);
const rightLabel = String(right.name ?? right.id);
return leftLabel.localeCompare(rightLabel);
});
}
function sortMatches(matches) {
return [...matches].sort((left, right) => {
if (left.distance !== right.distance) {
return left.distance - right.distance;
}
if (left.node.type !== right.node.type) {
return left.node.type.localeCompare(right.node.type);
}
const leftLabel = String(left.node.name ?? left.node.id);
const rightLabel = String(right.node.name ?? right.node.id);
return leftLabel.localeCompare(rightLabel);
});
}
function createGraphData(descriptor, nodes, edges, notes) {
const nodesById = /* @__PURE__ */ new Map();
const nodeIdsByType = /* @__PURE__ */ new Map();
const edgesById = /* @__PURE__ */ new Map();
const edgeIdsByNodeId = /* @__PURE__ */ new Map();
const notesById = /* @__PURE__ */ new Map();
const noteIdsByNodeId = /* @__PURE__ */ new Map();
const noteIdsByCorrespondenceId = /* @__PURE__ */ new Map();
for (const node of nodes) {
const clonedNode = cloneNode(node);
nodesById.set(clonedNode.id, clonedNode);
const typedIds = nodeIdsByType.get(clonedNode.type) ?? [];
typedIds.push(clonedNode.id);
nodeIdsByType.set(clonedNode.type, typedIds);
}
for (const edge of edges) {
const clonedEdge = cloneEdge(edge);
edgesById.set(clonedEdge.id, clonedEdge);
const leftEdges = edgeIdsByNodeId.get(clonedEdge.left) ?? [];
leftEdges.push(clonedEdge.id);
edgeIdsByNodeId.set(clonedEdge.left, leftEdges);
const rightEdges = edgeIdsByNodeId.get(clonedEdge.right) ?? [];
rightEdges.push(clonedEdge.id);
edgeIdsByNodeId.set(clonedEdge.right, rightEdges);
}
for (const note of notes) {
const clonedNote = cloneNote(note);
notesById.set(clonedNote.id, clonedNote);
if (clonedNote.target.kind === "node") {
const nodeNoteIds = noteIdsByNodeId.get(clonedNote.target.nodeId) ?? [];
nodeNoteIds.push(clonedNote.id);
noteIdsByNodeId.set(clonedNote.target.nodeId, nodeNoteIds);
continue;
}
const edgeId = makeCorrespondenceId(
clonedNote.target.left,
clonedNote.target.right
);
const correspondenceNoteIds = noteIdsByCorrespondenceId.get(edgeId) ?? [];
correspondenceNoteIds.push(clonedNote.id);
noteIdsByCorrespondenceId.set(edgeId, correspondenceNoteIds);
}
for (const ids of nodeIdsByType.values()) {
ids.sort((left, right) => String(left).localeCompare(String(right)));
}
for (const ids of edgeIdsByNodeId.values()) {
ids.sort((left, right) => left.localeCompare(right));
}
for (const ids of noteIdsByNodeId.values()) {
ids.sort((left, right) => left.localeCompare(right));
}
for (const ids of noteIdsByCorrespondenceId.values()) {
ids.sort((left, right) => left.localeCompare(right));
}
return {
descriptor: Object.freeze({ ...descriptor }),
nodesById,
nodeIdsByType,
edgesById,
edgeIdsByNodeId,
notesById,
noteIdsByNodeId,
noteIdsByCorrespondenceId
};
}
function normalizeBase(base) {
if (!base) {
return {
descriptor: {},
nodes: [],
edges: [],
notes: []
};
}
if (base instanceof TreeWorkspace) {
return {
descriptor: base.descriptor,
nodes: base.getNodes(),
edges: base.getEdges(),
notes: base.getNotes()
};
}
return {
descriptor: {
system: base.activeSystem ?? void 0,
parts: base.loadedParts.filter((part) => part !== "base")
},
nodes: base.getNodes(),
edges: base.getEdges(),
notes: []
};
}
function applyOverlays(base, overlays) {
const nodesById = /* @__PURE__ */ new Map();
const edgesById = /* @__PURE__ */ new Map();
const notes = [];
for (const node of base.nodes) {
nodesById.set(node.id, cloneNode(node));
}
for (const edge of base.edges) {
edgesById.set(edge.id, cloneEdge(edge));
}
for (const note of base.notes) {
notes.push(cloneNote(note));
}
for (const overlay of overlays) {
for (const node of overlay.nodes ?? []) {
const existing = nodesById.get(node.id);
if (existing && existing.type !== node.type) {
throw new Error(
`Overlay ${overlay.id} tried to upsert ${node.id} with a different type`
);
}
nodesById.set(
node.id,
cloneNode(
existing ? {
...existing,
...node,
data: {
...existing.data ?? {},
...node.data ?? {}
}
} : node
)
);
}
for (const correspondence of overlay.correspondences ?? []) {
if (!nodesById.has(correspondence.left) || !nodesById.has(correspondence.right)) {
throw new Error(
`Overlay ${overlay.id} references unknown nodes in ${correspondence.left} <-> ${correspondence.right}`
);
}
const edgeId = makeCorrespondenceId(
correspondence.left,
correspondence.right
);
if (correspondence.op === "remove") {
edgesById.delete(edgeId);
continue;
}
const existingEdge = edgesById.get(edgeId);
const overlaySource = {
kind: "overlay",
overlayId: overlay.id,
label: overlay.name
};
if (!existingEdge) {
if (correspondence.op !== "add") {
throw new Error(
`Overlay ${overlay.id} cannot annotate missing correspondence ${edgeId}`
);
}
edgesById.set(
edgeId,
cloneEdge({
id: edgeId,
left: correspondence.left,
right: correspondence.right,
metadata: cloneMetadata(correspondence.metadata),
sources: [overlaySource]
})
);
continue;
}
edgesById.set(
edgeId,
cloneEdge({
...existingEdge,
metadata: mergeMetadata(existingEdge.metadata, correspondence.metadata),
sources: mergeSources(existingEdge.sources, overlaySource)
})
);
}
for (const note of overlay.notes ?? []) {
notes.push(
cloneNote({
...note,
metadata: note.metadata ? { ...note.metadata } : void 0
})
);
}
}
const filteredNotes = notes.filter((note) => {
if (note.target.kind === "node") {
return nodesById.has(note.target.nodeId);
}
return edgesById.has(
makeCorrespondenceId(note.target.left, note.target.right)
);
});
return {
nodes: [...nodesById.values()],
edges: [...edgesById.values()],
notes: filteredNotes
};
}
function reconstructPath(startId, targetId, previous, edgesById) {
const steps = [];
let current = targetId;
while (current !== startId) {
const link = previous.get(current);
if (!link) {
break;
}
const edge = edgesById.get(link.edgeId);
if (!edge) {
break;
}
steps.push({
from: link.from,
to: current,
edge
});
current = link.from;
}
return steps.reverse();
}
function buildCanonicalDescriptor(opts, cacheKey) {
return {
id: `canonical:${cacheKey}`,
name: "Canonical Tree",
system: opts.system,
parts: [...opts.parts ?? []],
cacheKey
};
}
var canonicalTreeCache = /* @__PURE__ */ new Map();
var TreeWorkspace = class {
constructor(graph, visualResolvers = []) {
this.graph = graph;
this.visualResolvers = visualResolvers;
this.descriptor = graph.descriptor;
}
getNode(id2) {
return this.graph.nodesById.get(id2);
}
hasNode(id2) {
return this.graph.nodesById.has(id2);
}
getNodes(type) {
if (!type) {
return sortNodes(
[...this.graph.nodesById.values()]
);
}
const ids = this.graph.nodeIdsByType.get(type) ?? [];
return ids.map((id2) => this.graph.nodesById.get(id2)).filter((node) => Boolean(node));
}
findNodes(options = {}) {
const typeFilter = normalizeTypes(options.type);
const search = options.search?.trim().toLowerCase();
const candidates = options.ids ? options.ids.map((id2) => this.graph.nodesById.get(id2)).filter((node) => Boolean(node)) : typeFilter ? [...typeFilter].flatMap((type) => this.getNodes(type)) : this.getNodes();
return sortNodes(
candidates.filter((node) => {
if (typeFilter && !typeFilter.has(node.type)) {
return false;
}
if (search) {
const haystacks = [
String(node.id).toLowerCase(),
String(node.name ?? "").toLowerCase()
];
if (!haystacks.some((value) => value.includes(search))) {
return false;
}
}
return options.predicate ? options.predicate(node) : true;
})
);
}
getEdge(left, right) {
return this.graph.edgesById.get(makeCorrespondenceId(left, right));
}
getEdges(nodeId) {
if (!nodeId) {
return [...this.graph.edgesById.values()];
}
return (this.graph.edgeIdsByNodeId.get(nodeId) ?? []).map((edgeId) => this.graph.edgesById.get(edgeId)).filter((edge) => Boolean(edge));
}
getNotes(target) {
if (!target) {
return [...this.graph.notesById.values()];
}
if (target.kind === "node") {
return (this.graph.noteIdsByNodeId.get(target.nodeId) ?? []).map((noteId) => this.graph.notesById.get(noteId)).filter((note) => Boolean(note));
}
const edgeId = makeCorrespondenceId(target.left, target.right);
return (this.graph.noteIdsByCorrespondenceId.get(edgeId) ?? []).map((noteId) => this.graph.notesById.get(noteId)).filter((note) => Boolean(note));
}
getCorrespondences(nodeId, options = {}) {
if (!this.graph.nodesById.has(nodeId)) {
return [];
}
const typeFilter = normalizeTypes(options.type);
const includeSelf = options.includeSelf ?? false;
const depth = Math.max(1, options.depth ?? 1);
const limit = options.limit ?? Infinity;
const previous = /* @__PURE__ */ new Map();
const visited = /* @__PURE__ */ new Set([nodeId]);
const queue = [
{
nodeId,
distance: 0
}
];
const matches = [];
while (queue.length > 0 && matches.length < limit) {
const current = queue.shift();
if (current.distance >= depth) {
continue;
}
for (const edgeId of this.graph.edgeIdsByNodeId.get(current.nodeId) ?? []) {
const edge = this.graph.edgesById.get(edgeId);
if (!edge) {
continue;
}
const nextNodeId = edge.left === current.nodeId ? edge.right : edge.left;
if (visited.has(nextNodeId)) {
continue;
}
visited.add(nextNodeId);
previous.set(nextNodeId, {
from: current.nodeId,
edgeId
});
const distance = current.distance + 1;
const nextNode = this.graph.nodesById.get(nextNodeId);
if (!nextNode) {
continue;
}
if ((!typeFilter || typeFilter.has(nextNode.type)) && distance > 0) {
matches.push({
node: nextNode,
distance,
path: reconstructPath(
nodeId,
nextNodeId,
previous,
this.graph.edgesById
)
});
}
queue.push({
nodeId: nextNodeId,
distance
});
}
}
if (includeSelf) {
const sourceNode = this.graph.nodesById.get(nodeId);
if (sourceNode && (!typeFilter || typeFilter.has(sourceNode.type)) && matches.length < limit) {
matches.unshift({
node: sourceNode,
distance: 0,
path: []
});
}
}
return sortMatches(matches).slice(0, limit);
}
getCorrespondenceMap(nodeId, options = {}) {
const grouped = {};
for (const match of this.getCorrespondences(nodeId, options)) {
const typeKey = match.node.type;
const typeMatches = grouped[typeKey] ?? [];
typeMatches.push(match);
grouped[typeKey] = typeMatches;
}
return grouped;
}
resolveVisual(request) {
for (const resolver of this.visualResolvers) {
const resolved = resolver({
request,
workspace: this
});
if (resolved) {
return resolved;
}
}
return void 0;
}
};
function createTreeWorkspace(options = {}) {
const base = normalizeBase(options.base);
const overlays = options.overlays ?? [];
const applied = applyOverlays(base, overlays);
const descriptor = {
...base.descriptor,
...options.descriptor
};
return new TreeWorkspace(
createGraphData(descriptor, applied.nodes, applied.edges, applied.notes),
options.visualResolvers ?? []
);
}
function getCanonicalTree(opts = {
system: "kaabalah",
parts: []
}) {
const parts = [...opts.parts ?? []];
const cacheKey = `${opts.system}:${parts.join(",")}`;
const cachedTree = canonicalTreeCache.get(cacheKey);
if (cachedTree) {
return cachedTree;
}
const tree = createTree({
system: opts.system,
parts
});
const canonicalTree = createTreeWorkspace({
base: tree,
descriptor: buildCanonicalDescriptor(opts, cacheKey)
});
canonicalTreeCache.set(cacheKey, canonicalTree);
return canonicalTree;
}
export { TREE_TOPOLOGY_LIGHTNING_SPHERE_NAMES, TREE_TOPOLOGY_PATH_IDS, TREE_TOPOLOGY_SPHERE_IDS, TREE_TOPOLOGY_SPHERE_NAMES, TreeTopology, TreeWorkspace, createTreeWorkspace, getCanonicalTree, getTreeTopology };
//# sourceMappingURL=out.js.map
//# sourceMappingURL=chunk-6N5YC2WD.mjs.map