@streetui/graph 1.6.0 → 1.7.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 CHANGED
@@ -193,6 +193,7 @@ var ApplicationGraph = class {
193
193
  }
194
194
  this.handlers.delete(`__listbuild__${node.id}`);
195
195
  this.handlers.delete(`__listplan__${node.id}`);
196
+ this.handlers.delete(`__overlay__${node.id}`);
196
197
  }
197
198
  // ── Handler registry ──────────────────────────────────────────────────────
198
199
  registerHandler(key, fn) {
@@ -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 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"]}
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 this.handlers.delete(`__overlay__${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
@@ -166,6 +166,7 @@ var ApplicationGraph = class {
166
166
  }
167
167
  this.handlers.delete(`__listbuild__${node.id}`);
168
168
  this.handlers.delete(`__listplan__${node.id}`);
169
+ this.handlers.delete(`__overlay__${node.id}`);
169
170
  }
170
171
  // ── Handler registry ──────────────────────────────────────────────────────
171
172
  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 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":[]}
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 this.handlers.delete(`__overlay__${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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streetui/graph",
3
- "version": "1.6.0",
3
+ "version": "1.7.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.6.0"
28
+ "@streetui/core": "1.7.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "typescript": "*",