@streetui/graph 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -192,6 +192,7 @@ var ApplicationGraph = class {
|
|
|
192
192
|
this.handlers.delete(`__signal__${ref.signalId}`);
|
|
193
193
|
}
|
|
194
194
|
this.handlers.delete(`__listbuild__${node.id}`);
|
|
195
|
+
this.handlers.delete(`__listplan__${node.id}`);
|
|
195
196
|
}
|
|
196
197
|
// ── Handler registry ──────────────────────────────────────────────────────
|
|
197
198
|
registerHandler(key, fn) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/graph-node.ts","../src/graph.ts"],"sourcesContent":["export * from './graph-node.js';\nexport * from './graph.js';\n","/**\n * Semantic Application Graph nodes.\n *\n * Every element in a StreetUI application is represented as a GraphNode.\n * Nodes form a tree: each has an optional parent and an ordered list of children.\n */\n\nimport { generateNodeId, type NodeId, type SemanticNodeType } from '@streetui/core';\n\n// ── Property values ───────────────────────────────────────────────────────────\n\nexport type PropValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | string[]\n | number[]\n | Record<string, unknown>;\n\nexport type Props = Record<string, PropValue>;\n\n// ── Event descriptors ─────────────────────────────────────────────────────────\n\nexport interface EventDescriptor {\n readonly type: string;\n /** Reference key into the application's handler registry. */\n readonly handlerKey: string;\n}\n\n// ── State references ──────────────────────────────────────────────────────────\n\nexport interface StateRef {\n /** ID of the signal/store this node's property is bound to. */\n readonly signalId: string;\n /** The prop key on this node that is bound. */\n readonly propKey: string;\n}\n\n// ── The core graph node ───────────────────────────────────────────────────────\n\nexport interface GraphNodeData {\n readonly id: NodeId;\n readonly type: SemanticNodeType;\n readonly key: string | undefined;\n props: Props;\n events: EventDescriptor[];\n stateRefs: StateRef[];\n children: GraphNode[];\n parent: GraphNode | null;\n}\n\nexport class GraphNode implements GraphNodeData {\n readonly id: NodeId;\n readonly type: SemanticNodeType;\n readonly key: string | undefined;\n props: Props;\n events: EventDescriptor[];\n stateRefs: StateRef[];\n children: GraphNode[];\n parent: GraphNode | null;\n\n constructor(\n type: SemanticNodeType,\n options: {\n id?: NodeId;\n key?: string;\n props?: Props;\n events?: EventDescriptor[];\n stateRefs?: StateRef[];\n } = {},\n ) {\n this.type = type;\n this.id = options.id ?? generateNodeId(type);\n this.key = options.key;\n this.props = options.props ?? {};\n this.events = options.events ?? [];\n this.stateRefs = options.stateRefs ?? [];\n this.children = [];\n this.parent = null;\n }\n\n // ── Child management ────────────────────────────────────────────────────────\n\n appendChild(child: GraphNode): void {\n if (child.parent !== null) {\n child.parent.removeChild(child);\n }\n child.parent = this;\n this.children.push(child);\n }\n\n insertBefore(child: GraphNode, reference: GraphNode): void {\n const idx = this.children.indexOf(reference);\n if (idx === -1) {\n this.appendChild(child);\n return;\n }\n if (child.parent !== null) {\n child.parent.removeChild(child);\n }\n child.parent = this;\n this.children.splice(idx, 0, child);\n }\n\n removeChild(child: GraphNode): void {\n const idx = this.children.indexOf(child);\n if (idx === -1) return;\n this.children.splice(idx, 1);\n child.parent = null;\n }\n\n replaceChild(newChild: GraphNode, oldChild: GraphNode): void {\n const idx = this.children.indexOf(oldChild);\n if (idx === -1) {\n throw new Error(`GraphNode.replaceChild: oldChild is not a child of this node`);\n }\n if (newChild.parent !== null) {\n newChild.parent.removeChild(newChild);\n }\n oldChild.parent = null;\n newChild.parent = this;\n this.children.splice(idx, 1, newChild);\n }\n\n // ── Prop helpers ────────────────────────────────────────────────────────────\n\n setProp(key: string, value: PropValue): void {\n this.props = { ...this.props, [key]: value };\n }\n\n getProp<T extends PropValue = PropValue>(key: string): T | undefined {\n return this.props[key] as T | undefined;\n }\n\n // ── Event helpers ───────────────────────────────────────────────────────────\n\n addEvent(descriptor: EventDescriptor): void {\n this.events.push(descriptor);\n }\n\n removeEvent(type: string): void {\n this.events = this.events.filter(e => e.type !== type);\n }\n\n // ── Queries ─────────────────────────────────────────────────────────────────\n\n get isLeaf(): boolean {\n return this.children.length === 0;\n }\n\n get depth(): number {\n let d = 0;\n let node: GraphNode | null = this.parent;\n while (node !== null) {\n d++;\n node = node.parent;\n }\n return d;\n }\n\n get root(): GraphNode {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let node: GraphNode = this;\n while (node.parent !== null) {\n node = node.parent;\n }\n return node;\n }\n\n /** Shallow clone — does not clone children. */\n shallowClone(): GraphNode {\n const opts: { key?: string; props: Props; events: EventDescriptor[]; stateRefs: StateRef[] } = {\n props: { ...this.props },\n events: [...this.events],\n stateRefs: [...this.stateRefs],\n };\n if (this.key !== undefined) opts.key = this.key;\n return new GraphNode(this.type, opts);\n }\n}\n","/**\n * The Semantic Application Graph.\n *\n * Holds the application root node and all its descendants.\n * Supports traversal, lookup by ID, validation, and serialization.\n */\n\nimport { type NodeId, DiagnosticCollector } from '@streetui/core';\nimport { GraphNode, type Props, type EventDescriptor } from './graph-node.js';\n\nexport interface ApplicationGraphOptions {\n readonly name: string;\n readonly version?: string;\n}\n\nexport interface HandlerFn {\n (...args: unknown[]): unknown;\n}\n\nexport class ApplicationGraph {\n readonly root: GraphNode;\n readonly name: string;\n readonly version: string;\n private readonly _nodeIndex: Map<NodeId, GraphNode> = new Map();\n /** Handler registry — maps handlerKey → actual function */\n readonly handlers: Map<string, HandlerFn> = new Map();\n\n constructor(options: ApplicationGraphOptions) {\n this.name = options.name;\n this.version = options.version ?? '0.0.1';\n this.root = new GraphNode('application', { props: { name: options.name } });\n this._nodeIndex.set(this.root.id, this.root);\n }\n\n // ── Node creation & attachment ────────────────────────────────────────────\n\n createNode(\n type: GraphNode['type'],\n options: { key?: string; props?: Props; parent?: GraphNode } = {},\n ): GraphNode {\n const nodeOpts: { key?: string; props?: Props } = {};\n if (options.key !== undefined) nodeOpts.key = options.key;\n if (options.props !== undefined) nodeOpts.props = options.props;\n const node = new GraphNode(type, nodeOpts);\n this._nodeIndex.set(node.id, node);\n if (options.parent !== undefined) {\n options.parent.appendChild(node);\n }\n return node;\n }\n\n attachNode(node: GraphNode, parent: GraphNode): void {\n this._nodeIndex.set(node.id, node);\n parent.appendChild(node);\n }\n\n detachNode(node: GraphNode): void {\n if (node.parent !== null) {\n node.parent.removeChild(node);\n }\n this._removeFromIndex(node);\n }\n\n private _removeFromIndex(node: GraphNode): void {\n this._nodeIndex.delete(node.id);\n this._unregisterNodeHandlers(node);\n for (const child of node.children) {\n this._removeFromIndex(child);\n }\n }\n\n /**\n * Remove every handler-registry entry owned by a single node. A node owns:\n * - one entry per event descriptor (its `handlerKey`),\n * - one `__signal__<signalId>` entry per state ref (signalIds are namespaced\n * by node id, so they are never shared between nodes), and\n * - a `__listbuild__<id>` entry if it is a reactive-list.\n * Called for every node in a detached subtree so removing list items (or\n * discarding freshly-built-but-unadopted item subtrees) leaves no stale\n * registrations behind.\n */\n private _unregisterNodeHandlers(node: GraphNode): void {\n for (const event of node.events) {\n this.handlers.delete(event.handlerKey);\n }\n for (const ref of node.stateRefs) {\n this.handlers.delete(`__signal__${ref.signalId}`);\n }\n this.handlers.delete(`__listbuild__${node.id}`);\n }\n\n // ── Handler registry ──────────────────────────────────────────────────────\n\n registerHandler(key: string, fn: HandlerFn): void {\n this.handlers.set(key, fn);\n }\n\n getHandler(key: string): HandlerFn | undefined {\n return this.handlers.get(key);\n }\n\n /** True if a handler is currently registered under `key`. Inspection helper. */\n hasHandler(key: string): boolean {\n return this.handlers.has(key);\n }\n\n /** Number of currently-registered handlers. Inspection helper. */\n get handlerCount(): number {\n return this.handlers.size;\n }\n\n // ── Lookup ────────────────────────────────────────────────────────────────\n\n findById(id: NodeId): GraphNode | undefined {\n return this._nodeIndex.get(id);\n }\n\n findAll(predicate: (node: GraphNode) => boolean): GraphNode[] {\n const results: GraphNode[] = [];\n this._walk(this.root, node => {\n if (predicate(node)) results.push(node);\n });\n return results;\n }\n\n findByType(type: GraphNode['type']): GraphNode[] {\n return this.findAll(n => n.type === type);\n }\n\n // ── Traversal ─────────────────────────────────────────────────────────────\n\n walk(visitor: (node: GraphNode, depth: number) => void): void {\n this._walk(this.root, visitor, 0);\n }\n\n private _walk(\n node: GraphNode,\n visitor: (node: GraphNode, depth: number) => void,\n depth: number = 0,\n ): void {\n visitor(node, depth);\n for (const child of node.children) {\n this._walk(child, visitor, depth + 1);\n }\n }\n\n get nodeCount(): number {\n return this._nodeIndex.size;\n }\n\n // ── Validation ────────────────────────────────────────────────────────────\n\n validate(): DiagnosticCollector {\n const dc = new DiagnosticCollector();\n\n this.walk((node) => {\n // Validate event handler references exist\n for (const event of node.events) {\n if (!this.handlers.has(event.handlerKey)) {\n dc.warn(\n 'GRAPH_MISSING_HANDLER',\n `Node \"${node.id}\" references handler \"${event.handlerKey}\" which is not registered`,\n { nodeId: node.id },\n );\n }\n }\n\n // Validate page nodes are direct children of root\n if (node.type === 'page' && node.parent?.type !== 'application') {\n dc.error(\n 'GRAPH_PAGE_DEPTH',\n `Page node \"${node.id}\" must be a direct child of the application root`,\n { nodeId: node.id },\n );\n }\n });\n\n return dc;\n }\n\n // ── Serialization ─────────────────────────────────────────────────────────\n\n serialize(): SerializedGraph {\n return {\n name: this.name,\n version: this.version,\n root: this._serializeNode(this.root),\n };\n }\n\n private _serializeNode(node: GraphNode): SerializedNode {\n const result: SerializedNode = {\n id: node.id,\n type: node.type,\n key: node.key,\n props: node.props,\n events: node.events,\n stateRefs: node.stateRefs,\n children: node.children.map(c => this._serializeNode(c)),\n };\n return result;\n }\n}\n\nexport interface SerializedNode {\n readonly id: string;\n readonly type: string;\n readonly key: string | undefined;\n readonly props: Props;\n readonly events: EventDescriptor[];\n readonly stateRefs: unknown[];\n readonly children: SerializedNode[];\n}\n\nexport interface SerializedGraph {\n readonly name: string;\n readonly version: string;\n readonly root: SerializedNode;\n}\n"],"mappings":"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","names":["import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/graph-node.ts","../src/graph.ts"],"sourcesContent":["export * from './graph-node.js';\nexport * from './graph.js';\n","/**\n * Semantic Application Graph nodes.\n *\n * Every element in a StreetUI application is represented as a GraphNode.\n * Nodes form a tree: each has an optional parent and an ordered list of children.\n */\n\nimport { generateNodeId, type NodeId, type SemanticNodeType } from '@streetui/core';\n\n// ── Property values ───────────────────────────────────────────────────────────\n\nexport type PropValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | string[]\n | number[]\n | Record<string, unknown>;\n\nexport type Props = Record<string, PropValue>;\n\n// ── Event descriptors ─────────────────────────────────────────────────────────\n\nexport interface EventDescriptor {\n readonly type: string;\n /** Reference key into the application's handler registry. */\n readonly handlerKey: string;\n}\n\n// ── State references ──────────────────────────────────────────────────────────\n\nexport interface StateRef {\n /** ID of the signal/store this node's property is bound to. */\n readonly signalId: string;\n /** The prop key on this node that is bound. */\n readonly propKey: string;\n}\n\n// ── The core graph node ───────────────────────────────────────────────────────\n\nexport interface GraphNodeData {\n readonly id: NodeId;\n readonly type: SemanticNodeType;\n readonly key: string | undefined;\n props: Props;\n events: EventDescriptor[];\n stateRefs: StateRef[];\n children: GraphNode[];\n parent: GraphNode | null;\n}\n\nexport class GraphNode implements GraphNodeData {\n readonly id: NodeId;\n readonly type: SemanticNodeType;\n readonly key: string | undefined;\n props: Props;\n events: EventDescriptor[];\n stateRefs: StateRef[];\n children: GraphNode[];\n parent: GraphNode | null;\n\n constructor(\n type: SemanticNodeType,\n options: {\n id?: NodeId;\n key?: string;\n props?: Props;\n events?: EventDescriptor[];\n stateRefs?: StateRef[];\n } = {},\n ) {\n this.type = type;\n this.id = options.id ?? generateNodeId(type);\n this.key = options.key;\n this.props = options.props ?? {};\n this.events = options.events ?? [];\n this.stateRefs = options.stateRefs ?? [];\n this.children = [];\n this.parent = null;\n }\n\n // ── Child management ────────────────────────────────────────────────────────\n\n appendChild(child: GraphNode): void {\n if (child.parent !== null) {\n child.parent.removeChild(child);\n }\n child.parent = this;\n this.children.push(child);\n }\n\n insertBefore(child: GraphNode, reference: GraphNode): void {\n const idx = this.children.indexOf(reference);\n if (idx === -1) {\n this.appendChild(child);\n return;\n }\n if (child.parent !== null) {\n child.parent.removeChild(child);\n }\n child.parent = this;\n this.children.splice(idx, 0, child);\n }\n\n removeChild(child: GraphNode): void {\n const idx = this.children.indexOf(child);\n if (idx === -1) return;\n this.children.splice(idx, 1);\n child.parent = null;\n }\n\n replaceChild(newChild: GraphNode, oldChild: GraphNode): void {\n const idx = this.children.indexOf(oldChild);\n if (idx === -1) {\n throw new Error(`GraphNode.replaceChild: oldChild is not a child of this node`);\n }\n if (newChild.parent !== null) {\n newChild.parent.removeChild(newChild);\n }\n oldChild.parent = null;\n newChild.parent = this;\n this.children.splice(idx, 1, newChild);\n }\n\n // ── Prop helpers ────────────────────────────────────────────────────────────\n\n setProp(key: string, value: PropValue): void {\n this.props = { ...this.props, [key]: value };\n }\n\n getProp<T extends PropValue = PropValue>(key: string): T | undefined {\n return this.props[key] as T | undefined;\n }\n\n // ── Event helpers ───────────────────────────────────────────────────────────\n\n addEvent(descriptor: EventDescriptor): void {\n this.events.push(descriptor);\n }\n\n removeEvent(type: string): void {\n this.events = this.events.filter(e => e.type !== type);\n }\n\n // ── Queries ─────────────────────────────────────────────────────────────────\n\n get isLeaf(): boolean {\n return this.children.length === 0;\n }\n\n get depth(): number {\n let d = 0;\n let node: GraphNode | null = this.parent;\n while (node !== null) {\n d++;\n node = node.parent;\n }\n return d;\n }\n\n get root(): GraphNode {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let node: GraphNode = this;\n while (node.parent !== null) {\n node = node.parent;\n }\n return node;\n }\n\n /** Shallow clone — does not clone children. */\n shallowClone(): GraphNode {\n const opts: { key?: string; props: Props; events: EventDescriptor[]; stateRefs: StateRef[] } = {\n props: { ...this.props },\n events: [...this.events],\n stateRefs: [...this.stateRefs],\n };\n if (this.key !== undefined) opts.key = this.key;\n return new GraphNode(this.type, opts);\n }\n}\n","/**\n * The Semantic Application Graph.\n *\n * Holds the application root node and all its descendants.\n * Supports traversal, lookup by ID, validation, and serialization.\n */\n\nimport { type NodeId, DiagnosticCollector } from '@streetui/core';\nimport { GraphNode, type Props, type EventDescriptor } from './graph-node.js';\n\nexport interface ApplicationGraphOptions {\n readonly name: string;\n readonly version?: string;\n}\n\nexport interface HandlerFn {\n (...args: unknown[]): unknown;\n}\n\nexport class ApplicationGraph {\n readonly root: GraphNode;\n readonly name: string;\n readonly version: string;\n private readonly _nodeIndex: Map<NodeId, GraphNode> = new Map();\n /** Handler registry — maps handlerKey → actual function */\n readonly handlers: Map<string, HandlerFn> = new Map();\n\n constructor(options: ApplicationGraphOptions) {\n this.name = options.name;\n this.version = options.version ?? '0.0.1';\n this.root = new GraphNode('application', { props: { name: options.name } });\n this._nodeIndex.set(this.root.id, this.root);\n }\n\n // ── Node creation & attachment ────────────────────────────────────────────\n\n createNode(\n type: GraphNode['type'],\n options: { key?: string; props?: Props; parent?: GraphNode } = {},\n ): GraphNode {\n const nodeOpts: { key?: string; props?: Props } = {};\n if (options.key !== undefined) nodeOpts.key = options.key;\n if (options.props !== undefined) nodeOpts.props = options.props;\n const node = new GraphNode(type, nodeOpts);\n this._nodeIndex.set(node.id, node);\n if (options.parent !== undefined) {\n options.parent.appendChild(node);\n }\n return node;\n }\n\n attachNode(node: GraphNode, parent: GraphNode): void {\n this._nodeIndex.set(node.id, node);\n parent.appendChild(node);\n }\n\n detachNode(node: GraphNode): void {\n if (node.parent !== null) {\n node.parent.removeChild(node);\n }\n this._removeFromIndex(node);\n }\n\n private _removeFromIndex(node: GraphNode): void {\n this._nodeIndex.delete(node.id);\n this._unregisterNodeHandlers(node);\n for (const child of node.children) {\n this._removeFromIndex(child);\n }\n }\n\n /**\n * Remove every handler-registry entry owned by a single node. A node owns:\n * - one entry per event descriptor (its `handlerKey`),\n * - one `__signal__<signalId>` entry per state ref (signalIds are namespaced\n * by node id, so they are never shared between nodes), and\n * - a `__listbuild__<id>` entry if it is a reactive-list.\n * Called for every node in a detached subtree so removing list items (or\n * discarding freshly-built-but-unadopted item subtrees) leaves no stale\n * registrations behind.\n */\n private _unregisterNodeHandlers(node: GraphNode): void {\n for (const event of node.events) {\n this.handlers.delete(event.handlerKey);\n }\n for (const ref of node.stateRefs) {\n this.handlers.delete(`__signal__${ref.signalId}`);\n }\n this.handlers.delete(`__listbuild__${node.id}`);\n this.handlers.delete(`__listplan__${node.id}`);\n }\n\n // ── Handler registry ──────────────────────────────────────────────────────\n\n registerHandler(key: string, fn: HandlerFn): void {\n this.handlers.set(key, fn);\n }\n\n getHandler(key: string): HandlerFn | undefined {\n return this.handlers.get(key);\n }\n\n /** True if a handler is currently registered under `key`. Inspection helper. */\n hasHandler(key: string): boolean {\n return this.handlers.has(key);\n }\n\n /** Number of currently-registered handlers. Inspection helper. */\n get handlerCount(): number {\n return this.handlers.size;\n }\n\n // ── Lookup ────────────────────────────────────────────────────────────────\n\n findById(id: NodeId): GraphNode | undefined {\n return this._nodeIndex.get(id);\n }\n\n findAll(predicate: (node: GraphNode) => boolean): GraphNode[] {\n const results: GraphNode[] = [];\n this._walk(this.root, node => {\n if (predicate(node)) results.push(node);\n });\n return results;\n }\n\n findByType(type: GraphNode['type']): GraphNode[] {\n return this.findAll(n => n.type === type);\n }\n\n // ── Traversal ─────────────────────────────────────────────────────────────\n\n walk(visitor: (node: GraphNode, depth: number) => void): void {\n this._walk(this.root, visitor, 0);\n }\n\n private _walk(\n node: GraphNode,\n visitor: (node: GraphNode, depth: number) => void,\n depth: number = 0,\n ): void {\n visitor(node, depth);\n for (const child of node.children) {\n this._walk(child, visitor, depth + 1);\n }\n }\n\n get nodeCount(): number {\n return this._nodeIndex.size;\n }\n\n // ── Validation ────────────────────────────────────────────────────────────\n\n validate(): DiagnosticCollector {\n const dc = new DiagnosticCollector();\n\n this.walk((node) => {\n // Validate event handler references exist\n for (const event of node.events) {\n if (!this.handlers.has(event.handlerKey)) {\n dc.warn(\n 'GRAPH_MISSING_HANDLER',\n `Node \"${node.id}\" references handler \"${event.handlerKey}\" which is not registered`,\n { nodeId: node.id },\n );\n }\n }\n\n // Validate page nodes are direct children of root\n if (node.type === 'page' && node.parent?.type !== 'application') {\n dc.error(\n 'GRAPH_PAGE_DEPTH',\n `Page node \"${node.id}\" must be a direct child of the application root`,\n { nodeId: node.id },\n );\n }\n });\n\n return dc;\n }\n\n // ── Serialization ─────────────────────────────────────────────────────────\n\n serialize(): SerializedGraph {\n return {\n name: this.name,\n version: this.version,\n root: this._serializeNode(this.root),\n };\n }\n\n private _serializeNode(node: GraphNode): SerializedNode {\n const result: SerializedNode = {\n id: node.id,\n type: node.type,\n key: node.key,\n props: node.props,\n events: node.events,\n stateRefs: node.stateRefs,\n children: node.children.map(c => this._serializeNode(c)),\n };\n return result;\n }\n}\n\nexport interface SerializedNode {\n readonly id: string;\n readonly type: string;\n readonly key: string | undefined;\n readonly props: Props;\n readonly events: EventDescriptor[];\n readonly stateRefs: unknown[];\n readonly children: SerializedNode[];\n}\n\nexport interface SerializedGraph {\n readonly name: string;\n readonly version: string;\n readonly root: SerializedNode;\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/index.js
CHANGED
|
@@ -165,6 +165,7 @@ var ApplicationGraph = class {
|
|
|
165
165
|
this.handlers.delete(`__signal__${ref.signalId}`);
|
|
166
166
|
}
|
|
167
167
|
this.handlers.delete(`__listbuild__${node.id}`);
|
|
168
|
+
this.handlers.delete(`__listplan__${node.id}`);
|
|
168
169
|
}
|
|
169
170
|
// ── Handler registry ──────────────────────────────────────────────────────
|
|
170
171
|
registerHandler(key, fn) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/graph-node.ts","../src/graph.ts"],"sourcesContent":["/**\n * Semantic Application Graph nodes.\n *\n * Every element in a StreetUI application is represented as a GraphNode.\n * Nodes form a tree: each has an optional parent and an ordered list of children.\n */\n\nimport { generateNodeId, type NodeId, type SemanticNodeType } from '@streetui/core';\n\n// ── Property values ───────────────────────────────────────────────────────────\n\nexport type PropValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | string[]\n | number[]\n | Record<string, unknown>;\n\nexport type Props = Record<string, PropValue>;\n\n// ── Event descriptors ─────────────────────────────────────────────────────────\n\nexport interface EventDescriptor {\n readonly type: string;\n /** Reference key into the application's handler registry. */\n readonly handlerKey: string;\n}\n\n// ── State references ──────────────────────────────────────────────────────────\n\nexport interface StateRef {\n /** ID of the signal/store this node's property is bound to. */\n readonly signalId: string;\n /** The prop key on this node that is bound. */\n readonly propKey: string;\n}\n\n// ── The core graph node ───────────────────────────────────────────────────────\n\nexport interface GraphNodeData {\n readonly id: NodeId;\n readonly type: SemanticNodeType;\n readonly key: string | undefined;\n props: Props;\n events: EventDescriptor[];\n stateRefs: StateRef[];\n children: GraphNode[];\n parent: GraphNode | null;\n}\n\nexport class GraphNode implements GraphNodeData {\n readonly id: NodeId;\n readonly type: SemanticNodeType;\n readonly key: string | undefined;\n props: Props;\n events: EventDescriptor[];\n stateRefs: StateRef[];\n children: GraphNode[];\n parent: GraphNode | null;\n\n constructor(\n type: SemanticNodeType,\n options: {\n id?: NodeId;\n key?: string;\n props?: Props;\n events?: EventDescriptor[];\n stateRefs?: StateRef[];\n } = {},\n ) {\n this.type = type;\n this.id = options.id ?? generateNodeId(type);\n this.key = options.key;\n this.props = options.props ?? {};\n this.events = options.events ?? [];\n this.stateRefs = options.stateRefs ?? [];\n this.children = [];\n this.parent = null;\n }\n\n // ── Child management ────────────────────────────────────────────────────────\n\n appendChild(child: GraphNode): void {\n if (child.parent !== null) {\n child.parent.removeChild(child);\n }\n child.parent = this;\n this.children.push(child);\n }\n\n insertBefore(child: GraphNode, reference: GraphNode): void {\n const idx = this.children.indexOf(reference);\n if (idx === -1) {\n this.appendChild(child);\n return;\n }\n if (child.parent !== null) {\n child.parent.removeChild(child);\n }\n child.parent = this;\n this.children.splice(idx, 0, child);\n }\n\n removeChild(child: GraphNode): void {\n const idx = this.children.indexOf(child);\n if (idx === -1) return;\n this.children.splice(idx, 1);\n child.parent = null;\n }\n\n replaceChild(newChild: GraphNode, oldChild: GraphNode): void {\n const idx = this.children.indexOf(oldChild);\n if (idx === -1) {\n throw new Error(`GraphNode.replaceChild: oldChild is not a child of this node`);\n }\n if (newChild.parent !== null) {\n newChild.parent.removeChild(newChild);\n }\n oldChild.parent = null;\n newChild.parent = this;\n this.children.splice(idx, 1, newChild);\n }\n\n // ── Prop helpers ────────────────────────────────────────────────────────────\n\n setProp(key: string, value: PropValue): void {\n this.props = { ...this.props, [key]: value };\n }\n\n getProp<T extends PropValue = PropValue>(key: string): T | undefined {\n return this.props[key] as T | undefined;\n }\n\n // ── Event helpers ───────────────────────────────────────────────────────────\n\n addEvent(descriptor: EventDescriptor): void {\n this.events.push(descriptor);\n }\n\n removeEvent(type: string): void {\n this.events = this.events.filter(e => e.type !== type);\n }\n\n // ── Queries ─────────────────────────────────────────────────────────────────\n\n get isLeaf(): boolean {\n return this.children.length === 0;\n }\n\n get depth(): number {\n let d = 0;\n let node: GraphNode | null = this.parent;\n while (node !== null) {\n d++;\n node = node.parent;\n }\n return d;\n }\n\n get root(): GraphNode {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let node: GraphNode = this;\n while (node.parent !== null) {\n node = node.parent;\n }\n return node;\n }\n\n /** Shallow clone — does not clone children. */\n shallowClone(): GraphNode {\n const opts: { key?: string; props: Props; events: EventDescriptor[]; stateRefs: StateRef[] } = {\n props: { ...this.props },\n events: [...this.events],\n stateRefs: [...this.stateRefs],\n };\n if (this.key !== undefined) opts.key = this.key;\n return new GraphNode(this.type, opts);\n }\n}\n","/**\n * The Semantic Application Graph.\n *\n * Holds the application root node and all its descendants.\n * Supports traversal, lookup by ID, validation, and serialization.\n */\n\nimport { type NodeId, DiagnosticCollector } from '@streetui/core';\nimport { GraphNode, type Props, type EventDescriptor } from './graph-node.js';\n\nexport interface ApplicationGraphOptions {\n readonly name: string;\n readonly version?: string;\n}\n\nexport interface HandlerFn {\n (...args: unknown[]): unknown;\n}\n\nexport class ApplicationGraph {\n readonly root: GraphNode;\n readonly name: string;\n readonly version: string;\n private readonly _nodeIndex: Map<NodeId, GraphNode> = new Map();\n /** Handler registry — maps handlerKey → actual function */\n readonly handlers: Map<string, HandlerFn> = new Map();\n\n constructor(options: ApplicationGraphOptions) {\n this.name = options.name;\n this.version = options.version ?? '0.0.1';\n this.root = new GraphNode('application', { props: { name: options.name } });\n this._nodeIndex.set(this.root.id, this.root);\n }\n\n // ── Node creation & attachment ────────────────────────────────────────────\n\n createNode(\n type: GraphNode['type'],\n options: { key?: string; props?: Props; parent?: GraphNode } = {},\n ): GraphNode {\n const nodeOpts: { key?: string; props?: Props } = {};\n if (options.key !== undefined) nodeOpts.key = options.key;\n if (options.props !== undefined) nodeOpts.props = options.props;\n const node = new GraphNode(type, nodeOpts);\n this._nodeIndex.set(node.id, node);\n if (options.parent !== undefined) {\n options.parent.appendChild(node);\n }\n return node;\n }\n\n attachNode(node: GraphNode, parent: GraphNode): void {\n this._nodeIndex.set(node.id, node);\n parent.appendChild(node);\n }\n\n detachNode(node: GraphNode): void {\n if (node.parent !== null) {\n node.parent.removeChild(node);\n }\n this._removeFromIndex(node);\n }\n\n private _removeFromIndex(node: GraphNode): void {\n this._nodeIndex.delete(node.id);\n this._unregisterNodeHandlers(node);\n for (const child of node.children) {\n this._removeFromIndex(child);\n }\n }\n\n /**\n * Remove every handler-registry entry owned by a single node. A node owns:\n * - one entry per event descriptor (its `handlerKey`),\n * - one `__signal__<signalId>` entry per state ref (signalIds are namespaced\n * by node id, so they are never shared between nodes), and\n * - a `__listbuild__<id>` entry if it is a reactive-list.\n * Called for every node in a detached subtree so removing list items (or\n * discarding freshly-built-but-unadopted item subtrees) leaves no stale\n * registrations behind.\n */\n private _unregisterNodeHandlers(node: GraphNode): void {\n for (const event of node.events) {\n this.handlers.delete(event.handlerKey);\n }\n for (const ref of node.stateRefs) {\n this.handlers.delete(`__signal__${ref.signalId}`);\n }\n this.handlers.delete(`__listbuild__${node.id}`);\n }\n\n // ── Handler registry ──────────────────────────────────────────────────────\n\n registerHandler(key: string, fn: HandlerFn): void {\n this.handlers.set(key, fn);\n }\n\n getHandler(key: string): HandlerFn | undefined {\n return this.handlers.get(key);\n }\n\n /** True if a handler is currently registered under `key`. Inspection helper. */\n hasHandler(key: string): boolean {\n return this.handlers.has(key);\n }\n\n /** Number of currently-registered handlers. Inspection helper. */\n get handlerCount(): number {\n return this.handlers.size;\n }\n\n // ── Lookup ────────────────────────────────────────────────────────────────\n\n findById(id: NodeId): GraphNode | undefined {\n return this._nodeIndex.get(id);\n }\n\n findAll(predicate: (node: GraphNode) => boolean): GraphNode[] {\n const results: GraphNode[] = [];\n this._walk(this.root, node => {\n if (predicate(node)) results.push(node);\n });\n return results;\n }\n\n findByType(type: GraphNode['type']): GraphNode[] {\n return this.findAll(n => n.type === type);\n }\n\n // ── Traversal ─────────────────────────────────────────────────────────────\n\n walk(visitor: (node: GraphNode, depth: number) => void): void {\n this._walk(this.root, visitor, 0);\n }\n\n private _walk(\n node: GraphNode,\n visitor: (node: GraphNode, depth: number) => void,\n depth: number = 0,\n ): void {\n visitor(node, depth);\n for (const child of node.children) {\n this._walk(child, visitor, depth + 1);\n }\n }\n\n get nodeCount(): number {\n return this._nodeIndex.size;\n }\n\n // ── Validation ────────────────────────────────────────────────────────────\n\n validate(): DiagnosticCollector {\n const dc = new DiagnosticCollector();\n\n this.walk((node) => {\n // Validate event handler references exist\n for (const event of node.events) {\n if (!this.handlers.has(event.handlerKey)) {\n dc.warn(\n 'GRAPH_MISSING_HANDLER',\n `Node \"${node.id}\" references handler \"${event.handlerKey}\" which is not registered`,\n { nodeId: node.id },\n );\n }\n }\n\n // Validate page nodes are direct children of root\n if (node.type === 'page' && node.parent?.type !== 'application') {\n dc.error(\n 'GRAPH_PAGE_DEPTH',\n `Page node \"${node.id}\" must be a direct child of the application root`,\n { nodeId: node.id },\n );\n }\n });\n\n return dc;\n }\n\n // ── Serialization ─────────────────────────────────────────────────────────\n\n serialize(): SerializedGraph {\n return {\n name: this.name,\n version: this.version,\n root: this._serializeNode(this.root),\n };\n }\n\n private _serializeNode(node: GraphNode): SerializedNode {\n const result: SerializedNode = {\n id: node.id,\n type: node.type,\n key: node.key,\n props: node.props,\n events: node.events,\n stateRefs: node.stateRefs,\n children: node.children.map(c => this._serializeNode(c)),\n };\n return result;\n }\n}\n\nexport interface SerializedNode {\n readonly id: string;\n readonly type: string;\n readonly key: string | undefined;\n readonly props: Props;\n readonly events: EventDescriptor[];\n readonly stateRefs: unknown[];\n readonly children: SerializedNode[];\n}\n\nexport interface SerializedGraph {\n readonly name: string;\n readonly version: string;\n readonly root: SerializedNode;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/graph-node.ts","../src/graph.ts"],"sourcesContent":["/**\n * Semantic Application Graph nodes.\n *\n * Every element in a StreetUI application is represented as a GraphNode.\n * Nodes form a tree: each has an optional parent and an ordered list of children.\n */\n\nimport { generateNodeId, type NodeId, type SemanticNodeType } from '@streetui/core';\n\n// ── Property values ───────────────────────────────────────────────────────────\n\nexport type PropValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | string[]\n | number[]\n | Record<string, unknown>;\n\nexport type Props = Record<string, PropValue>;\n\n// ── Event descriptors ─────────────────────────────────────────────────────────\n\nexport interface EventDescriptor {\n readonly type: string;\n /** Reference key into the application's handler registry. */\n readonly handlerKey: string;\n}\n\n// ── State references ──────────────────────────────────────────────────────────\n\nexport interface StateRef {\n /** ID of the signal/store this node's property is bound to. */\n readonly signalId: string;\n /** The prop key on this node that is bound. */\n readonly propKey: string;\n}\n\n// ── The core graph node ───────────────────────────────────────────────────────\n\nexport interface GraphNodeData {\n readonly id: NodeId;\n readonly type: SemanticNodeType;\n readonly key: string | undefined;\n props: Props;\n events: EventDescriptor[];\n stateRefs: StateRef[];\n children: GraphNode[];\n parent: GraphNode | null;\n}\n\nexport class GraphNode implements GraphNodeData {\n readonly id: NodeId;\n readonly type: SemanticNodeType;\n readonly key: string | undefined;\n props: Props;\n events: EventDescriptor[];\n stateRefs: StateRef[];\n children: GraphNode[];\n parent: GraphNode | null;\n\n constructor(\n type: SemanticNodeType,\n options: {\n id?: NodeId;\n key?: string;\n props?: Props;\n events?: EventDescriptor[];\n stateRefs?: StateRef[];\n } = {},\n ) {\n this.type = type;\n this.id = options.id ?? generateNodeId(type);\n this.key = options.key;\n this.props = options.props ?? {};\n this.events = options.events ?? [];\n this.stateRefs = options.stateRefs ?? [];\n this.children = [];\n this.parent = null;\n }\n\n // ── Child management ────────────────────────────────────────────────────────\n\n appendChild(child: GraphNode): void {\n if (child.parent !== null) {\n child.parent.removeChild(child);\n }\n child.parent = this;\n this.children.push(child);\n }\n\n insertBefore(child: GraphNode, reference: GraphNode): void {\n const idx = this.children.indexOf(reference);\n if (idx === -1) {\n this.appendChild(child);\n return;\n }\n if (child.parent !== null) {\n child.parent.removeChild(child);\n }\n child.parent = this;\n this.children.splice(idx, 0, child);\n }\n\n removeChild(child: GraphNode): void {\n const idx = this.children.indexOf(child);\n if (idx === -1) return;\n this.children.splice(idx, 1);\n child.parent = null;\n }\n\n replaceChild(newChild: GraphNode, oldChild: GraphNode): void {\n const idx = this.children.indexOf(oldChild);\n if (idx === -1) {\n throw new Error(`GraphNode.replaceChild: oldChild is not a child of this node`);\n }\n if (newChild.parent !== null) {\n newChild.parent.removeChild(newChild);\n }\n oldChild.parent = null;\n newChild.parent = this;\n this.children.splice(idx, 1, newChild);\n }\n\n // ── Prop helpers ────────────────────────────────────────────────────────────\n\n setProp(key: string, value: PropValue): void {\n this.props = { ...this.props, [key]: value };\n }\n\n getProp<T extends PropValue = PropValue>(key: string): T | undefined {\n return this.props[key] as T | undefined;\n }\n\n // ── Event helpers ───────────────────────────────────────────────────────────\n\n addEvent(descriptor: EventDescriptor): void {\n this.events.push(descriptor);\n }\n\n removeEvent(type: string): void {\n this.events = this.events.filter(e => e.type !== type);\n }\n\n // ── Queries ─────────────────────────────────────────────────────────────────\n\n get isLeaf(): boolean {\n return this.children.length === 0;\n }\n\n get depth(): number {\n let d = 0;\n let node: GraphNode | null = this.parent;\n while (node !== null) {\n d++;\n node = node.parent;\n }\n return d;\n }\n\n get root(): GraphNode {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let node: GraphNode = this;\n while (node.parent !== null) {\n node = node.parent;\n }\n return node;\n }\n\n /** Shallow clone — does not clone children. */\n shallowClone(): GraphNode {\n const opts: { key?: string; props: Props; events: EventDescriptor[]; stateRefs: StateRef[] } = {\n props: { ...this.props },\n events: [...this.events],\n stateRefs: [...this.stateRefs],\n };\n if (this.key !== undefined) opts.key = this.key;\n return new GraphNode(this.type, opts);\n }\n}\n","/**\n * The Semantic Application Graph.\n *\n * Holds the application root node and all its descendants.\n * Supports traversal, lookup by ID, validation, and serialization.\n */\n\nimport { type NodeId, DiagnosticCollector } from '@streetui/core';\nimport { GraphNode, type Props, type EventDescriptor } from './graph-node.js';\n\nexport interface ApplicationGraphOptions {\n readonly name: string;\n readonly version?: string;\n}\n\nexport interface HandlerFn {\n (...args: unknown[]): unknown;\n}\n\nexport class ApplicationGraph {\n readonly root: GraphNode;\n readonly name: string;\n readonly version: string;\n private readonly _nodeIndex: Map<NodeId, GraphNode> = new Map();\n /** Handler registry — maps handlerKey → actual function */\n readonly handlers: Map<string, HandlerFn> = new Map();\n\n constructor(options: ApplicationGraphOptions) {\n this.name = options.name;\n this.version = options.version ?? '0.0.1';\n this.root = new GraphNode('application', { props: { name: options.name } });\n this._nodeIndex.set(this.root.id, this.root);\n }\n\n // ── Node creation & attachment ────────────────────────────────────────────\n\n createNode(\n type: GraphNode['type'],\n options: { key?: string; props?: Props; parent?: GraphNode } = {},\n ): GraphNode {\n const nodeOpts: { key?: string; props?: Props } = {};\n if (options.key !== undefined) nodeOpts.key = options.key;\n if (options.props !== undefined) nodeOpts.props = options.props;\n const node = new GraphNode(type, nodeOpts);\n this._nodeIndex.set(node.id, node);\n if (options.parent !== undefined) {\n options.parent.appendChild(node);\n }\n return node;\n }\n\n attachNode(node: GraphNode, parent: GraphNode): void {\n this._nodeIndex.set(node.id, node);\n parent.appendChild(node);\n }\n\n detachNode(node: GraphNode): void {\n if (node.parent !== null) {\n node.parent.removeChild(node);\n }\n this._removeFromIndex(node);\n }\n\n private _removeFromIndex(node: GraphNode): void {\n this._nodeIndex.delete(node.id);\n this._unregisterNodeHandlers(node);\n for (const child of node.children) {\n this._removeFromIndex(child);\n }\n }\n\n /**\n * Remove every handler-registry entry owned by a single node. A node owns:\n * - one entry per event descriptor (its `handlerKey`),\n * - one `__signal__<signalId>` entry per state ref (signalIds are namespaced\n * by node id, so they are never shared between nodes), and\n * - a `__listbuild__<id>` entry if it is a reactive-list.\n * Called for every node in a detached subtree so removing list items (or\n * discarding freshly-built-but-unadopted item subtrees) leaves no stale\n * registrations behind.\n */\n private _unregisterNodeHandlers(node: GraphNode): void {\n for (const event of node.events) {\n this.handlers.delete(event.handlerKey);\n }\n for (const ref of node.stateRefs) {\n this.handlers.delete(`__signal__${ref.signalId}`);\n }\n this.handlers.delete(`__listbuild__${node.id}`);\n this.handlers.delete(`__listplan__${node.id}`);\n }\n\n // ── Handler registry ──────────────────────────────────────────────────────\n\n registerHandler(key: string, fn: HandlerFn): void {\n this.handlers.set(key, fn);\n }\n\n getHandler(key: string): HandlerFn | undefined {\n return this.handlers.get(key);\n }\n\n /** True if a handler is currently registered under `key`. Inspection helper. */\n hasHandler(key: string): boolean {\n return this.handlers.has(key);\n }\n\n /** Number of currently-registered handlers. Inspection helper. */\n get handlerCount(): number {\n return this.handlers.size;\n }\n\n // ── Lookup ────────────────────────────────────────────────────────────────\n\n findById(id: NodeId): GraphNode | undefined {\n return this._nodeIndex.get(id);\n }\n\n findAll(predicate: (node: GraphNode) => boolean): GraphNode[] {\n const results: GraphNode[] = [];\n this._walk(this.root, node => {\n if (predicate(node)) results.push(node);\n });\n return results;\n }\n\n findByType(type: GraphNode['type']): GraphNode[] {\n return this.findAll(n => n.type === type);\n }\n\n // ── Traversal ─────────────────────────────────────────────────────────────\n\n walk(visitor: (node: GraphNode, depth: number) => void): void {\n this._walk(this.root, visitor, 0);\n }\n\n private _walk(\n node: GraphNode,\n visitor: (node: GraphNode, depth: number) => void,\n depth: number = 0,\n ): void {\n visitor(node, depth);\n for (const child of node.children) {\n this._walk(child, visitor, depth + 1);\n }\n }\n\n get nodeCount(): number {\n return this._nodeIndex.size;\n }\n\n // ── Validation ────────────────────────────────────────────────────────────\n\n validate(): DiagnosticCollector {\n const dc = new DiagnosticCollector();\n\n this.walk((node) => {\n // Validate event handler references exist\n for (const event of node.events) {\n if (!this.handlers.has(event.handlerKey)) {\n dc.warn(\n 'GRAPH_MISSING_HANDLER',\n `Node \"${node.id}\" references handler \"${event.handlerKey}\" which is not registered`,\n { nodeId: node.id },\n );\n }\n }\n\n // Validate page nodes are direct children of root\n if (node.type === 'page' && node.parent?.type !== 'application') {\n dc.error(\n 'GRAPH_PAGE_DEPTH',\n `Page node \"${node.id}\" must be a direct child of the application root`,\n { nodeId: node.id },\n );\n }\n });\n\n return dc;\n }\n\n // ── Serialization ─────────────────────────────────────────────────────────\n\n serialize(): SerializedGraph {\n return {\n name: this.name,\n version: this.version,\n root: this._serializeNode(this.root),\n };\n }\n\n private _serializeNode(node: GraphNode): SerializedNode {\n const result: SerializedNode = {\n id: node.id,\n type: node.type,\n key: node.key,\n props: node.props,\n events: node.events,\n stateRefs: node.stateRefs,\n children: node.children.map(c => this._serializeNode(c)),\n };\n return result;\n }\n}\n\nexport interface SerializedNode {\n readonly id: string;\n readonly type: string;\n readonly key: string | undefined;\n readonly props: Props;\n readonly events: EventDescriptor[];\n readonly stateRefs: unknown[];\n readonly children: SerializedNode[];\n}\n\nexport interface SerializedGraph {\n readonly name: string;\n readonly version: string;\n readonly root: SerializedNode;\n}\n"],"mappings":";AAOA,SAAS,sBAA0D;AA8C5D,IAAM,YAAN,MAAM,WAAmC;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YACE,MACA,UAMI,CAAC,GACL;AACA,SAAK,OAAO;AACZ,SAAK,KAAK,QAAQ,MAAM,eAAe,IAAI;AAC3C,SAAK,MAAM,QAAQ;AACnB,SAAK,QAAQ,QAAQ,SAAS,CAAC;AAC/B,SAAK,SAAS,QAAQ,UAAU,CAAC;AACjC,SAAK,YAAY,QAAQ,aAAa,CAAC;AACvC,SAAK,WAAW,CAAC;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAIA,YAAY,OAAwB;AAClC,QAAI,MAAM,WAAW,MAAM;AACzB,YAAM,OAAO,YAAY,KAAK;AAAA,IAChC;AACA,UAAM,SAAS;AACf,SAAK,SAAS,KAAK,KAAK;AAAA,EAC1B;AAAA,EAEA,aAAa,OAAkB,WAA4B;AACzD,UAAM,MAAM,KAAK,SAAS,QAAQ,SAAS;AAC3C,QAAI,QAAQ,IAAI;AACd,WAAK,YAAY,KAAK;AACtB;AAAA,IACF;AACA,QAAI,MAAM,WAAW,MAAM;AACzB,YAAM,OAAO,YAAY,KAAK;AAAA,IAChC;AACA,UAAM,SAAS;AACf,SAAK,SAAS,OAAO,KAAK,GAAG,KAAK;AAAA,EACpC;AAAA,EAEA,YAAY,OAAwB;AAClC,UAAM,MAAM,KAAK,SAAS,QAAQ,KAAK;AACvC,QAAI,QAAQ,GAAI;AAChB,SAAK,SAAS,OAAO,KAAK,CAAC;AAC3B,UAAM,SAAS;AAAA,EACjB;AAAA,EAEA,aAAa,UAAqB,UAA2B;AAC3D,UAAM,MAAM,KAAK,SAAS,QAAQ,QAAQ;AAC1C,QAAI,QAAQ,IAAI;AACd,YAAM,IAAI,MAAM,8DAA8D;AAAA,IAChF;AACA,QAAI,SAAS,WAAW,MAAM;AAC5B,eAAS,OAAO,YAAY,QAAQ;AAAA,IACtC;AACA,aAAS,SAAS;AAClB,aAAS,SAAS;AAClB,SAAK,SAAS,OAAO,KAAK,GAAG,QAAQ;AAAA,EACvC;AAAA;AAAA,EAIA,QAAQ,KAAa,OAAwB;AAC3C,SAAK,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,GAAG,MAAM;AAAA,EAC7C;AAAA,EAEA,QAAyC,KAA4B;AACnE,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB;AAAA;AAAA,EAIA,SAAS,YAAmC;AAC1C,SAAK,OAAO,KAAK,UAAU;AAAA,EAC7B;AAAA,EAEA,YAAY,MAAoB;AAC9B,SAAK,SAAS,KAAK,OAAO,OAAO,OAAK,EAAE,SAAS,IAAI;AAAA,EACvD;AAAA;AAAA,EAIA,IAAI,SAAkB;AACpB,WAAO,KAAK,SAAS,WAAW;AAAA,EAClC;AAAA,EAEA,IAAI,QAAgB;AAClB,QAAI,IAAI;AACR,QAAI,OAAyB,KAAK;AAClC,WAAO,SAAS,MAAM;AACpB;AACA,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,OAAkB;AAEpB,QAAI,OAAkB;AACtB,WAAO,KAAK,WAAW,MAAM;AAC3B,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,eAA0B;AACxB,UAAM,OAAyF;AAAA,MAC7F,OAAO,EAAE,GAAG,KAAK,MAAM;AAAA,MACvB,QAAQ,CAAC,GAAG,KAAK,MAAM;AAAA,MACvB,WAAW,CAAC,GAAG,KAAK,SAAS;AAAA,IAC/B;AACA,QAAI,KAAK,QAAQ,OAAW,MAAK,MAAM,KAAK;AAC5C,WAAO,IAAI,WAAU,KAAK,MAAM,IAAI;AAAA,EACtC;AACF;;;AC9KA,SAAsB,2BAA2B;AAY1C,IAAM,mBAAN,MAAuB;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACQ,aAAqC,oBAAI,IAAI;AAAA;AAAA,EAErD,WAAmC,oBAAI,IAAI;AAAA,EAEpD,YAAY,SAAkC;AAC5C,SAAK,OAAO,QAAQ;AACpB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,OAAO,IAAI,UAAU,eAAe,EAAE,OAAO,EAAE,MAAM,QAAQ,KAAK,EAAE,CAAC;AAC1E,SAAK,WAAW,IAAI,KAAK,KAAK,IAAI,KAAK,IAAI;AAAA,EAC7C;AAAA;AAAA,EAIA,WACE,MACA,UAA+D,CAAC,GACrD;AACX,UAAM,WAA4C,CAAC;AACnD,QAAI,QAAQ,QAAQ,OAAW,UAAS,MAAM,QAAQ;AACtD,QAAI,QAAQ,UAAU,OAAW,UAAS,QAAQ,QAAQ;AAC1D,UAAM,OAAO,IAAI,UAAU,MAAM,QAAQ;AACzC,SAAK,WAAW,IAAI,KAAK,IAAI,IAAI;AACjC,QAAI,QAAQ,WAAW,QAAW;AAChC,cAAQ,OAAO,YAAY,IAAI;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,MAAiB,QAAyB;AACnD,SAAK,WAAW,IAAI,KAAK,IAAI,IAAI;AACjC,WAAO,YAAY,IAAI;AAAA,EACzB;AAAA,EAEA,WAAW,MAAuB;AAChC,QAAI,KAAK,WAAW,MAAM;AACxB,WAAK,OAAO,YAAY,IAAI;AAAA,IAC9B;AACA,SAAK,iBAAiB,IAAI;AAAA,EAC5B;AAAA,EAEQ,iBAAiB,MAAuB;AAC9C,SAAK,WAAW,OAAO,KAAK,EAAE;AAC9B,SAAK,wBAAwB,IAAI;AACjC,eAAW,SAAS,KAAK,UAAU;AACjC,WAAK,iBAAiB,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,wBAAwB,MAAuB;AACrD,eAAW,SAAS,KAAK,QAAQ;AAC/B,WAAK,SAAS,OAAO,MAAM,UAAU;AAAA,IACvC;AACA,eAAW,OAAO,KAAK,WAAW;AAChC,WAAK,SAAS,OAAO,aAAa,IAAI,QAAQ,EAAE;AAAA,IAClD;AACA,SAAK,SAAS,OAAO,gBAAgB,KAAK,EAAE,EAAE;AAC9C,SAAK,SAAS,OAAO,eAAe,KAAK,EAAE,EAAE;AAAA,EAC/C;AAAA;AAAA,EAIA,gBAAgB,KAAa,IAAqB;AAChD,SAAK,SAAS,IAAI,KAAK,EAAE;AAAA,EAC3B;AAAA,EAEA,WAAW,KAAoC;AAC7C,WAAO,KAAK,SAAS,IAAI,GAAG;AAAA,EAC9B;AAAA;AAAA,EAGA,WAAW,KAAsB;AAC/B,WAAO,KAAK,SAAS,IAAI,GAAG;AAAA,EAC9B;AAAA;AAAA,EAGA,IAAI,eAAuB;AACzB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA,EAIA,SAAS,IAAmC;AAC1C,WAAO,KAAK,WAAW,IAAI,EAAE;AAAA,EAC/B;AAAA,EAEA,QAAQ,WAAsD;AAC5D,UAAM,UAAuB,CAAC;AAC9B,SAAK,MAAM,KAAK,MAAM,UAAQ;AAC5B,UAAI,UAAU,IAAI,EAAG,SAAQ,KAAK,IAAI;AAAA,IACxC,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,MAAsC;AAC/C,WAAO,KAAK,QAAQ,OAAK,EAAE,SAAS,IAAI;AAAA,EAC1C;AAAA;AAAA,EAIA,KAAK,SAAyD;AAC5D,SAAK,MAAM,KAAK,MAAM,SAAS,CAAC;AAAA,EAClC;AAAA,EAEQ,MACN,MACA,SACA,QAAgB,GACV;AACN,YAAQ,MAAM,KAAK;AACnB,eAAW,SAAS,KAAK,UAAU;AACjC,WAAK,MAAM,OAAO,SAAS,QAAQ,CAAC;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA,EAIA,WAAgC;AAC9B,UAAM,KAAK,IAAI,oBAAoB;AAEnC,SAAK,KAAK,CAAC,SAAS;AAElB,iBAAW,SAAS,KAAK,QAAQ;AAC/B,YAAI,CAAC,KAAK,SAAS,IAAI,MAAM,UAAU,GAAG;AACxC,aAAG;AAAA,YACD;AAAA,YACA,SAAS,KAAK,EAAE,yBAAyB,MAAM,UAAU;AAAA,YACzD,EAAE,QAAQ,KAAK,GAAG;AAAA,UACpB;AAAA,QACF;AAAA,MACF;AAGA,UAAI,KAAK,SAAS,UAAU,KAAK,QAAQ,SAAS,eAAe;AAC/D,WAAG;AAAA,UACD;AAAA,UACA,cAAc,KAAK,EAAE;AAAA,UACrB,EAAE,QAAQ,KAAK,GAAG;AAAA,QACpB;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,YAA6B;AAC3B,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,MAAM,KAAK,eAAe,KAAK,IAAI;AAAA,IACrC;AAAA,EACF;AAAA,EAEQ,eAAe,MAAiC;AACtD,UAAM,SAAyB;AAAA,MAC7B,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,KAAK,KAAK;AAAA,MACV,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,UAAU,KAAK,SAAS,IAAI,OAAK,KAAK,eAAe,CAAC,CAAC;AAAA,IACzD;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streetui/graph",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "StreetUI Semantic Application Graph — nodes, relationships, traversal, validation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"clean": "rm -rf dist"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@streetui/core": "1.
|
|
28
|
+
"@streetui/core": "1.3.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"typescript": "*",
|