UNPKG

kaabalah

Version:

The de-facto library for any esoteric calculations and tooling

853 lines (850 loc) 25.1 kB
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