@open-mercato/ai-assistant 0.6.7-develop.6851.1.c1680b00d8 → 0.6.7-develop.6860.1.f796b22e80

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.
@@ -1,4 +1,5 @@
1
1
  import { ReferenceKind } from "@mikro-orm/core";
2
+ import { listEntityMetadata } from "@open-mercato/shared/lib/db/entityMetadata";
2
3
  let cachedGraph = null;
3
4
  function mapReferenceKind(kind, mappedBy) {
4
5
  switch (kind) {
@@ -20,11 +21,10 @@ function getSimpleTypeName(type) {
20
21
  return type;
21
22
  }
22
23
  async function extractEntityGraph(orm) {
23
- const metadata = orm.getMetadata();
24
- const allMetadata = metadata.getAll();
24
+ const allMetadata = listEntityMetadata(orm);
25
25
  const nodes = [];
26
26
  const edges = [];
27
- for (const [, entityMeta] of Object.entries(allMetadata)) {
27
+ for (const entityMeta of allMetadata) {
28
28
  if (entityMeta.abstract || entityMeta.embeddable) continue;
29
29
  if (entityMeta.className.startsWith("MikroORM")) continue;
30
30
  const properties = [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/entity-graph.ts"],
4
- "sourcesContent": ["/**\n * Entity Relationship Graph Extraction\n *\n * Extracts entity relationships from MikroORM metadata and provides\n * them in a format suitable for AI tools to query.\n */\n\nimport { ReferenceKind, type MikroORM } from '@mikro-orm/core'\nimport type { PostgreSqlDriver } from '@mikro-orm/postgresql'\n\n/**\n * Relationship types mapped from MikroORM reference kinds.\n */\nexport type RelationshipType =\n | 'BELONGS_TO' // ManyToOne\n | 'HAS_MANY' // OneToMany\n | 'HAS_ONE' // OneToOne (owner)\n | 'BELONGS_TO_ONE' // OneToOne (inverse)\n | 'HAS_MANY_MANY' // ManyToMany (owner)\n | 'BELONGS_TO_MANY' // ManyToMany (inverse)\n\n/**\n * A relationship triple representing a connection between two entities.\n */\nexport interface EntityTriple {\n source: string\n relationship: RelationshipType\n target: string\n property: string\n nullable?: boolean\n}\n\n/**\n * An entity node with its properties.\n */\nexport interface EntityNode {\n className: string\n tableName: string\n properties: Array<{ name: string; type: string; nullable: boolean }>\n}\n\n/**\n * The complete entity graph with nodes and edges.\n */\nexport interface EntityGraph {\n nodes: EntityNode[]\n edges: EntityTriple[]\n generatedAt: string\n}\n\n/**\n * In-memory cache for the entity graph.\n */\nlet cachedGraph: EntityGraph | null = null\n\n/**\n * Map MikroORM ReferenceKind to our RelationshipType.\n */\nfunction mapReferenceKind(kind: ReferenceKind, mappedBy?: string): RelationshipType {\n switch (kind) {\n case ReferenceKind.MANY_TO_ONE:\n return 'BELONGS_TO'\n case ReferenceKind.ONE_TO_MANY:\n return 'HAS_MANY'\n case ReferenceKind.ONE_TO_ONE:\n // If mappedBy is set, this is the inverse side\n return mappedBy ? 'BELONGS_TO_ONE' : 'HAS_ONE'\n case ReferenceKind.MANY_TO_MANY:\n // If mappedBy is set, this is the inverse side\n return mappedBy ? 'BELONGS_TO_MANY' : 'HAS_MANY_MANY'\n default:\n return 'BELONGS_TO'\n }\n}\n\n/**\n * Get a simple type name from MikroORM property type.\n */\nfunction getSimpleTypeName(type: string | ((...args: unknown[]) => unknown) | undefined): string {\n if (!type) return 'unknown'\n if (typeof type === 'function') return type.name || 'unknown'\n return type\n}\n\n/**\n * Extract the entity graph from MikroORM metadata.\n */\nexport async function extractEntityGraph(orm: MikroORM<PostgreSqlDriver>): Promise<EntityGraph> {\n const metadata = orm.getMetadata()\n const allMetadata = metadata.getAll()\n\n const nodes: EntityNode[] = []\n const edges: EntityTriple[] = []\n\n for (const [, entityMeta] of Object.entries(allMetadata)) {\n // Skip abstract entities and embeddables\n if (entityMeta.abstract || entityMeta.embeddable) continue\n\n // Skip internal MikroORM entities\n if (entityMeta.className.startsWith('MikroORM')) continue\n\n const properties: Array<{ name: string; type: string; nullable: boolean }> = []\n\n for (const prop of entityMeta.props) {\n // Skip internal properties\n if (prop.name.startsWith('_')) continue\n\n // Handle relationships\n if (prop.kind !== undefined && prop.kind !== ReferenceKind.SCALAR) {\n // This is a relationship property\n const targetEntity = prop.type\n if (targetEntity && targetEntity !== entityMeta.className) {\n const relationship = mapReferenceKind(prop.kind, prop.mappedBy)\n\n edges.push({\n source: entityMeta.className,\n relationship,\n target: targetEntity,\n property: prop.name,\n nullable: prop.nullable ?? false,\n })\n }\n } else {\n // Regular scalar property\n properties.push({\n name: prop.name,\n type: getSimpleTypeName(prop.type),\n nullable: prop.nullable ?? false,\n })\n }\n }\n\n nodes.push({\n className: entityMeta.className,\n tableName: entityMeta.tableName,\n properties,\n })\n }\n\n // Sort for consistent output\n nodes.sort((a, b) => a.className.localeCompare(b.className))\n edges.sort((a, b) => {\n const sourceCompare = a.source.localeCompare(b.source)\n if (sourceCompare !== 0) return sourceCompare\n return a.property.localeCompare(b.property)\n })\n\n return {\n nodes,\n edges,\n generatedAt: new Date().toISOString(),\n }\n}\n\n/**\n * Format the entity graph as readable triples.\n *\n * Example output:\n * (CustomerEntity)-[HAS_MANY:deals]->(CustomerDeal)\n * (SalesOrder)-[BELONGS_TO:channel]->(SalesChannel)\n */\nexport function formatGraphAsTriples(graph: EntityGraph): string[] {\n return graph.edges.map((edge) => {\n const nullable = edge.nullable ? '?' : ''\n return `(${edge.source})-[${edge.relationship}${nullable}:${edge.property}]->(${edge.target})`\n })\n}\n\n/**\n * Cache the entity graph in memory.\n */\nexport function cacheEntityGraph(graph: EntityGraph): void {\n cachedGraph = graph\n}\n\n/**\n * Retrieve the cached entity graph.\n */\nexport function getCachedEntityGraph(): EntityGraph | null {\n return cachedGraph\n}\n\n/**\n * Filter graph edges by entity name (source or target).\n */\nexport function filterGraphByEntity(graph: EntityGraph, entityName: string): EntityTriple[] {\n const lowerEntity = entityName.toLowerCase()\n return graph.edges.filter(\n (edge) => edge.source.toLowerCase().includes(lowerEntity) || edge.target.toLowerCase().includes(lowerEntity)\n )\n}\n\n/**\n * Filter graph edges by module (inferred from table name prefix).\n */\nexport function filterGraphByModule(graph: EntityGraph, moduleName: string): EntityTriple[] {\n const lowerModule = moduleName.toLowerCase()\n\n // Find entities that belong to this module (by table name prefix or class name)\n const moduleEntities = new Set<string>()\n for (const node of graph.nodes) {\n if (node.tableName.startsWith(lowerModule) || node.className.toLowerCase().includes(lowerModule)) {\n moduleEntities.add(node.className)\n }\n }\n\n return graph.edges.filter((edge) => moduleEntities.has(edge.source) || moduleEntities.has(edge.target))\n}\n\n/**\n * Filter graph edges by relationship type.\n */\nexport function filterGraphByType(graph: EntityGraph, type: RelationshipType): EntityTriple[] {\n return graph.edges.filter((edge) => edge.relationship === type)\n}\n\n/**\n * Get entity fields for a specific entity.\n */\nexport function getEntityFields(graph: EntityGraph, entityName: string): EntityNode | undefined {\n const lowerEntity = entityName.toLowerCase()\n return graph.nodes.find((node) => node.className.toLowerCase() === lowerEntity)\n}\n\n/**\n * List all entities grouped by inferred module.\n */\nexport function listEntitiesByModule(graph: EntityGraph): Map<string, string[]> {\n const byModule = new Map<string, string[]>()\n\n for (const node of graph.nodes) {\n // Infer module from table name prefix (e.g., 'sales_orders' -> 'sales')\n const module = inferModuleFromEntity(node.className, node.tableName)\n\n const existing = byModule.get(module) ?? []\n existing.push(node.className)\n byModule.set(module, existing)\n }\n\n return byModule\n}\n\n/**\n * Infer module name from entity class name or table name.\n *\n * Patterns:\n * - Table prefix: 'sales_orders' \u2192 'sales'\n * - Class prefix: 'SalesOrder' \u2192 'sales' (PascalCase to module)\n * - Common mappings: CustomerEntity \u2192 'customers', CatalogProduct \u2192 'catalog'\n */\nexport function inferModuleFromEntity(className: string, tableName: string): string {\n // First try table name prefix (most reliable)\n const tableParts = tableName.split('_')\n if (tableParts.length > 1) {\n return tableParts[0]\n }\n\n // Try to extract from class name (e.g., SalesOrder \u2192 sales)\n // Handle common entity suffixes\n const nameWithoutSuffix = className\n .replace(/Entity$/, '')\n .replace(/Model$/, '')\n\n // Extract the first word from PascalCase\n const match = nameWithoutSuffix.match(/^([A-Z][a-z]+)/)\n if (match) {\n const prefix = match[1].toLowerCase()\n // Map common prefixes to module names\n const moduleMap: Record<string, string> = {\n sales: 'sales',\n customer: 'customers',\n catalog: 'catalog',\n product: 'catalog',\n order: 'sales',\n invoice: 'sales',\n quote: 'sales',\n auth: 'auth',\n user: 'auth',\n tenant: 'auth',\n organization: 'auth',\n workflow: 'workflows',\n config: 'configs',\n dictionary: 'dictionaries',\n entity: 'entities',\n search: 'search',\n attachment: 'attachments',\n audit: 'audit_logs',\n api: 'api_keys',\n dashboard: 'dashboards',\n widget: 'widgets',\n feature: 'feature_toggles',\n perspective: 'perspectives',\n currency: 'currencies',\n content: 'content',\n onboarding: 'onboarding',\n }\n if (moduleMap[prefix]) {\n return moduleMap[prefix]\n }\n return prefix\n }\n\n return 'core'\n}\n\n/**\n * Get both outgoing and incoming relationships for an entity.\n */\nexport function getEntityRelationships(\n graph: EntityGraph,\n entityName: string\n): { outgoing: EntityTriple[]; incoming: EntityTriple[] } {\n const outgoing = graph.edges.filter(\n (edge) => edge.source.toLowerCase() === entityName.toLowerCase()\n )\n const incoming = graph.edges.filter(\n (edge) =>\n edge.target.toLowerCase() === entityName.toLowerCase() &&\n edge.source.toLowerCase() !== entityName.toLowerCase()\n )\n return { outgoing, incoming }\n}\n\n/**\n * Format a single relationship triple as a string.\n */\nexport function formatTriple(edge: EntityTriple): string {\n const nullable = edge.nullable ? '?' : ''\n return `(${edge.source})-[${edge.relationship}${nullable}:${edge.property}]->(${edge.target})`\n}\n\n/**\n * Get graph statistics.\n */\nexport function getGraphStats(graph: EntityGraph): {\n totalEntities: number\n totalRelationships: number\n modules: string[]\n} {\n const byModule = listEntitiesByModule(graph)\n return {\n totalEntities: graph.nodes.length,\n totalRelationships: graph.edges.length,\n modules: Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Entity Relationship Graph Extraction\n *\n * Extracts entity relationships from MikroORM metadata and provides\n * them in a format suitable for AI tools to query.\n */\n\nimport { ReferenceKind, type MikroORM } from '@mikro-orm/core'\nimport type { PostgreSqlDriver } from '@mikro-orm/postgresql'\nimport { listEntityMetadata } from '@open-mercato/shared/lib/db/entityMetadata'\n\n/**\n * Relationship types mapped from MikroORM reference kinds.\n */\nexport type RelationshipType =\n | 'BELONGS_TO' // ManyToOne\n | 'HAS_MANY' // OneToMany\n | 'HAS_ONE' // OneToOne (owner)\n | 'BELONGS_TO_ONE' // OneToOne (inverse)\n | 'HAS_MANY_MANY' // ManyToMany (owner)\n | 'BELONGS_TO_MANY' // ManyToMany (inverse)\n\n/**\n * A relationship triple representing a connection between two entities.\n */\nexport interface EntityTriple {\n source: string\n relationship: RelationshipType\n target: string\n property: string\n nullable?: boolean\n}\n\n/**\n * An entity node with its properties.\n */\nexport interface EntityNode {\n className: string\n tableName: string\n properties: Array<{ name: string; type: string; nullable: boolean }>\n}\n\n/**\n * The complete entity graph with nodes and edges.\n */\nexport interface EntityGraph {\n nodes: EntityNode[]\n edges: EntityTriple[]\n generatedAt: string\n}\n\n/**\n * In-memory cache for the entity graph.\n */\nlet cachedGraph: EntityGraph | null = null\n\n/**\n * Map MikroORM ReferenceKind to our RelationshipType.\n */\nfunction mapReferenceKind(kind: ReferenceKind, mappedBy?: string): RelationshipType {\n switch (kind) {\n case ReferenceKind.MANY_TO_ONE:\n return 'BELONGS_TO'\n case ReferenceKind.ONE_TO_MANY:\n return 'HAS_MANY'\n case ReferenceKind.ONE_TO_ONE:\n // If mappedBy is set, this is the inverse side\n return mappedBy ? 'BELONGS_TO_ONE' : 'HAS_ONE'\n case ReferenceKind.MANY_TO_MANY:\n // If mappedBy is set, this is the inverse side\n return mappedBy ? 'BELONGS_TO_MANY' : 'HAS_MANY_MANY'\n default:\n return 'BELONGS_TO'\n }\n}\n\n/**\n * Get a simple type name from MikroORM property type.\n */\nfunction getSimpleTypeName(type: string | ((...args: unknown[]) => unknown) | undefined): string {\n if (!type) return 'unknown'\n if (typeof type === 'function') return type.name || 'unknown'\n return type\n}\n\n/**\n * Extract the entity graph from MikroORM metadata.\n */\nexport async function extractEntityGraph(orm: MikroORM<PostgreSqlDriver>): Promise<EntityGraph> {\n const allMetadata = listEntityMetadata(orm)\n\n const nodes: EntityNode[] = []\n const edges: EntityTriple[] = []\n\n for (const entityMeta of allMetadata) {\n // Skip abstract entities and embeddables\n if (entityMeta.abstract || entityMeta.embeddable) continue\n\n // Skip internal MikroORM entities\n if (entityMeta.className.startsWith('MikroORM')) continue\n\n const properties: Array<{ name: string; type: string; nullable: boolean }> = []\n\n for (const prop of entityMeta.props) {\n // Skip internal properties\n if (prop.name.startsWith('_')) continue\n\n // Handle relationships\n if (prop.kind !== undefined && prop.kind !== ReferenceKind.SCALAR) {\n // This is a relationship property\n const targetEntity = prop.type\n if (targetEntity && targetEntity !== entityMeta.className) {\n const relationship = mapReferenceKind(prop.kind, prop.mappedBy)\n\n edges.push({\n source: entityMeta.className,\n relationship,\n target: targetEntity,\n property: prop.name,\n nullable: prop.nullable ?? false,\n })\n }\n } else {\n // Regular scalar property\n properties.push({\n name: prop.name,\n type: getSimpleTypeName(prop.type),\n nullable: prop.nullable ?? false,\n })\n }\n }\n\n nodes.push({\n className: entityMeta.className,\n tableName: entityMeta.tableName,\n properties,\n })\n }\n\n // Sort for consistent output\n nodes.sort((a, b) => a.className.localeCompare(b.className))\n edges.sort((a, b) => {\n const sourceCompare = a.source.localeCompare(b.source)\n if (sourceCompare !== 0) return sourceCompare\n return a.property.localeCompare(b.property)\n })\n\n return {\n nodes,\n edges,\n generatedAt: new Date().toISOString(),\n }\n}\n\n/**\n * Format the entity graph as readable triples.\n *\n * Example output:\n * (CustomerEntity)-[HAS_MANY:deals]->(CustomerDeal)\n * (SalesOrder)-[BELONGS_TO:channel]->(SalesChannel)\n */\nexport function formatGraphAsTriples(graph: EntityGraph): string[] {\n return graph.edges.map((edge) => {\n const nullable = edge.nullable ? '?' : ''\n return `(${edge.source})-[${edge.relationship}${nullable}:${edge.property}]->(${edge.target})`\n })\n}\n\n/**\n * Cache the entity graph in memory.\n */\nexport function cacheEntityGraph(graph: EntityGraph): void {\n cachedGraph = graph\n}\n\n/**\n * Retrieve the cached entity graph.\n */\nexport function getCachedEntityGraph(): EntityGraph | null {\n return cachedGraph\n}\n\n/**\n * Filter graph edges by entity name (source or target).\n */\nexport function filterGraphByEntity(graph: EntityGraph, entityName: string): EntityTriple[] {\n const lowerEntity = entityName.toLowerCase()\n return graph.edges.filter(\n (edge) => edge.source.toLowerCase().includes(lowerEntity) || edge.target.toLowerCase().includes(lowerEntity)\n )\n}\n\n/**\n * Filter graph edges by module (inferred from table name prefix).\n */\nexport function filterGraphByModule(graph: EntityGraph, moduleName: string): EntityTriple[] {\n const lowerModule = moduleName.toLowerCase()\n\n // Find entities that belong to this module (by table name prefix or class name)\n const moduleEntities = new Set<string>()\n for (const node of graph.nodes) {\n if (node.tableName.startsWith(lowerModule) || node.className.toLowerCase().includes(lowerModule)) {\n moduleEntities.add(node.className)\n }\n }\n\n return graph.edges.filter((edge) => moduleEntities.has(edge.source) || moduleEntities.has(edge.target))\n}\n\n/**\n * Filter graph edges by relationship type.\n */\nexport function filterGraphByType(graph: EntityGraph, type: RelationshipType): EntityTriple[] {\n return graph.edges.filter((edge) => edge.relationship === type)\n}\n\n/**\n * Get entity fields for a specific entity.\n */\nexport function getEntityFields(graph: EntityGraph, entityName: string): EntityNode | undefined {\n const lowerEntity = entityName.toLowerCase()\n return graph.nodes.find((node) => node.className.toLowerCase() === lowerEntity)\n}\n\n/**\n * List all entities grouped by inferred module.\n */\nexport function listEntitiesByModule(graph: EntityGraph): Map<string, string[]> {\n const byModule = new Map<string, string[]>()\n\n for (const node of graph.nodes) {\n // Infer module from table name prefix (e.g., 'sales_orders' -> 'sales')\n const module = inferModuleFromEntity(node.className, node.tableName)\n\n const existing = byModule.get(module) ?? []\n existing.push(node.className)\n byModule.set(module, existing)\n }\n\n return byModule\n}\n\n/**\n * Infer module name from entity class name or table name.\n *\n * Patterns:\n * - Table prefix: 'sales_orders' \u2192 'sales'\n * - Class prefix: 'SalesOrder' \u2192 'sales' (PascalCase to module)\n * - Common mappings: CustomerEntity \u2192 'customers', CatalogProduct \u2192 'catalog'\n */\nexport function inferModuleFromEntity(className: string, tableName: string): string {\n // First try table name prefix (most reliable)\n const tableParts = tableName.split('_')\n if (tableParts.length > 1) {\n return tableParts[0]\n }\n\n // Try to extract from class name (e.g., SalesOrder \u2192 sales)\n // Handle common entity suffixes\n const nameWithoutSuffix = className\n .replace(/Entity$/, '')\n .replace(/Model$/, '')\n\n // Extract the first word from PascalCase\n const match = nameWithoutSuffix.match(/^([A-Z][a-z]+)/)\n if (match) {\n const prefix = match[1].toLowerCase()\n // Map common prefixes to module names\n const moduleMap: Record<string, string> = {\n sales: 'sales',\n customer: 'customers',\n catalog: 'catalog',\n product: 'catalog',\n order: 'sales',\n invoice: 'sales',\n quote: 'sales',\n auth: 'auth',\n user: 'auth',\n tenant: 'auth',\n organization: 'auth',\n workflow: 'workflows',\n config: 'configs',\n dictionary: 'dictionaries',\n entity: 'entities',\n search: 'search',\n attachment: 'attachments',\n audit: 'audit_logs',\n api: 'api_keys',\n dashboard: 'dashboards',\n widget: 'widgets',\n feature: 'feature_toggles',\n perspective: 'perspectives',\n currency: 'currencies',\n content: 'content',\n onboarding: 'onboarding',\n }\n if (moduleMap[prefix]) {\n return moduleMap[prefix]\n }\n return prefix\n }\n\n return 'core'\n}\n\n/**\n * Get both outgoing and incoming relationships for an entity.\n */\nexport function getEntityRelationships(\n graph: EntityGraph,\n entityName: string\n): { outgoing: EntityTriple[]; incoming: EntityTriple[] } {\n const outgoing = graph.edges.filter(\n (edge) => edge.source.toLowerCase() === entityName.toLowerCase()\n )\n const incoming = graph.edges.filter(\n (edge) =>\n edge.target.toLowerCase() === entityName.toLowerCase() &&\n edge.source.toLowerCase() !== entityName.toLowerCase()\n )\n return { outgoing, incoming }\n}\n\n/**\n * Format a single relationship triple as a string.\n */\nexport function formatTriple(edge: EntityTriple): string {\n const nullable = edge.nullable ? '?' : ''\n return `(${edge.source})-[${edge.relationship}${nullable}:${edge.property}]->(${edge.target})`\n}\n\n/**\n * Get graph statistics.\n */\nexport function getGraphStats(graph: EntityGraph): {\n totalEntities: number\n totalRelationships: number\n modules: string[]\n} {\n const byModule = listEntitiesByModule(graph)\n return {\n totalEntities: graph.nodes.length,\n totalRelationships: graph.edges.length,\n modules: Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),\n }\n}\n"],
5
+ "mappings": "AAOA,SAAS,qBAAoC;AAE7C,SAAS,0BAA0B;AA6CnC,IAAI,cAAkC;AAKtC,SAAS,iBAAiB,MAAqB,UAAqC;AAClF,UAAQ,MAAM;AAAA,IACZ,KAAK,cAAc;AACjB,aAAO;AAAA,IACT,KAAK,cAAc;AACjB,aAAO;AAAA,IACT,KAAK,cAAc;AAEjB,aAAO,WAAW,mBAAmB;AAAA,IACvC,KAAK,cAAc;AAEjB,aAAO,WAAW,oBAAoB;AAAA,IACxC;AACE,aAAO;AAAA,EACX;AACF;AAKA,SAAS,kBAAkB,MAAsE;AAC/F,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,OAAO,SAAS,WAAY,QAAO,KAAK,QAAQ;AACpD,SAAO;AACT;AAKA,eAAsB,mBAAmB,KAAuD;AAC9F,QAAM,cAAc,mBAAmB,GAAG;AAE1C,QAAM,QAAsB,CAAC;AAC7B,QAAM,QAAwB,CAAC;AAE/B,aAAW,cAAc,aAAa;AAEpC,QAAI,WAAW,YAAY,WAAW,WAAY;AAGlD,QAAI,WAAW,UAAU,WAAW,UAAU,EAAG;AAEjD,UAAM,aAAuE,CAAC;AAE9E,eAAW,QAAQ,WAAW,OAAO;AAEnC,UAAI,KAAK,KAAK,WAAW,GAAG,EAAG;AAG/B,UAAI,KAAK,SAAS,UAAa,KAAK,SAAS,cAAc,QAAQ;AAEjE,cAAM,eAAe,KAAK;AAC1B,YAAI,gBAAgB,iBAAiB,WAAW,WAAW;AACzD,gBAAM,eAAe,iBAAiB,KAAK,MAAM,KAAK,QAAQ;AAE9D,gBAAM,KAAK;AAAA,YACT,QAAQ,WAAW;AAAA,YACnB;AAAA,YACA,QAAQ;AAAA,YACR,UAAU,KAAK;AAAA,YACf,UAAU,KAAK,YAAY;AAAA,UAC7B,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AAEL,mBAAW,KAAK;AAAA,UACd,MAAM,KAAK;AAAA,UACX,MAAM,kBAAkB,KAAK,IAAI;AAAA,UACjC,UAAU,KAAK,YAAY;AAAA,QAC7B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,KAAK;AAAA,MACT,WAAW,WAAW;AAAA,MACtB,WAAW,WAAW;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAC3D,QAAM,KAAK,CAAC,GAAG,MAAM;AACnB,UAAM,gBAAgB,EAAE,OAAO,cAAc,EAAE,MAAM;AACrD,QAAI,kBAAkB,EAAG,QAAO;AAChC,WAAO,EAAE,SAAS,cAAc,EAAE,QAAQ;AAAA,EAC5C,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtC;AACF;AASO,SAAS,qBAAqB,OAA8B;AACjE,SAAO,MAAM,MAAM,IAAI,CAAC,SAAS;AAC/B,UAAM,WAAW,KAAK,WAAW,MAAM;AACvC,WAAO,IAAI,KAAK,MAAM,MAAM,KAAK,YAAY,GAAG,QAAQ,IAAI,KAAK,QAAQ,OAAO,KAAK,MAAM;AAAA,EAC7F,CAAC;AACH;AAKO,SAAS,iBAAiB,OAA0B;AACzD,gBAAc;AAChB;AAKO,SAAS,uBAA2C;AACzD,SAAO;AACT;AAKO,SAAS,oBAAoB,OAAoB,YAAoC;AAC1F,QAAM,cAAc,WAAW,YAAY;AAC3C,SAAO,MAAM,MAAM;AAAA,IACjB,CAAC,SAAS,KAAK,OAAO,YAAY,EAAE,SAAS,WAAW,KAAK,KAAK,OAAO,YAAY,EAAE,SAAS,WAAW;AAAA,EAC7G;AACF;AAKO,SAAS,oBAAoB,OAAoB,YAAoC;AAC1F,QAAM,cAAc,WAAW,YAAY;AAG3C,QAAM,iBAAiB,oBAAI,IAAY;AACvC,aAAW,QAAQ,MAAM,OAAO;AAC9B,QAAI,KAAK,UAAU,WAAW,WAAW,KAAK,KAAK,UAAU,YAAY,EAAE,SAAS,WAAW,GAAG;AAChG,qBAAe,IAAI,KAAK,SAAS;AAAA,IACnC;AAAA,EACF;AAEA,SAAO,MAAM,MAAM,OAAO,CAAC,SAAS,eAAe,IAAI,KAAK,MAAM,KAAK,eAAe,IAAI,KAAK,MAAM,CAAC;AACxG;AAKO,SAAS,kBAAkB,OAAoB,MAAwC;AAC5F,SAAO,MAAM,MAAM,OAAO,CAAC,SAAS,KAAK,iBAAiB,IAAI;AAChE;AAKO,SAAS,gBAAgB,OAAoB,YAA4C;AAC9F,QAAM,cAAc,WAAW,YAAY;AAC3C,SAAO,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,YAAY,MAAM,WAAW;AAChF;AAKO,SAAS,qBAAqB,OAA2C;AAC9E,QAAM,WAAW,oBAAI,IAAsB;AAE3C,aAAW,QAAQ,MAAM,OAAO;AAE9B,UAAM,SAAS,sBAAsB,KAAK,WAAW,KAAK,SAAS;AAEnE,UAAM,WAAW,SAAS,IAAI,MAAM,KAAK,CAAC;AAC1C,aAAS,KAAK,KAAK,SAAS;AAC5B,aAAS,IAAI,QAAQ,QAAQ;AAAA,EAC/B;AAEA,SAAO;AACT;AAUO,SAAS,sBAAsB,WAAmB,WAA2B;AAElF,QAAM,aAAa,UAAU,MAAM,GAAG;AACtC,MAAI,WAAW,SAAS,GAAG;AACzB,WAAO,WAAW,CAAC;AAAA,EACrB;AAIA,QAAM,oBAAoB,UACvB,QAAQ,WAAW,EAAE,EACrB,QAAQ,UAAU,EAAE;AAGvB,QAAM,QAAQ,kBAAkB,MAAM,gBAAgB;AACtD,MAAI,OAAO;AACT,UAAM,SAAS,MAAM,CAAC,EAAE,YAAY;AAEpC,UAAM,YAAoC;AAAA,MACxC,OAAO;AAAA,MACP,UAAU;AAAA,MACV,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,MACP,SAAS;AAAA,MACT,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,OAAO;AAAA,MACP,KAAK;AAAA,MACL,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,aAAa;AAAA,MACb,UAAU;AAAA,MACV,SAAS;AAAA,MACT,YAAY;AAAA,IACd;AACA,QAAI,UAAU,MAAM,GAAG;AACrB,aAAO,UAAU,MAAM;AAAA,IACzB;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAKO,SAAS,uBACd,OACA,YACwD;AACxD,QAAM,WAAW,MAAM,MAAM;AAAA,IAC3B,CAAC,SAAS,KAAK,OAAO,YAAY,MAAM,WAAW,YAAY;AAAA,EACjE;AACA,QAAM,WAAW,MAAM,MAAM;AAAA,IAC3B,CAAC,SACC,KAAK,OAAO,YAAY,MAAM,WAAW,YAAY,KACrD,KAAK,OAAO,YAAY,MAAM,WAAW,YAAY;AAAA,EACzD;AACA,SAAO,EAAE,UAAU,SAAS;AAC9B;AAKO,SAAS,aAAa,MAA4B;AACvD,QAAM,WAAW,KAAK,WAAW,MAAM;AACvC,SAAO,IAAI,KAAK,MAAM,MAAM,KAAK,YAAY,GAAG,QAAQ,IAAI,KAAK,QAAQ,OAAO,KAAK,MAAM;AAC7F;AAKO,SAAS,cAAc,OAI5B;AACA,QAAM,WAAW,qBAAqB,KAAK;AAC3C,SAAO;AAAA,IACL,eAAe,MAAM,MAAM;AAAA,IAC3B,oBAAoB,MAAM,MAAM;AAAA,IAChC,SAAS,MAAM,KAAK,SAAS,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;AAAA,EAClF;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ai-assistant",
3
- "version": "0.6.7-develop.6851.1.c1680b00d8",
3
+ "version": "0.6.7-develop.6860.1.f796b22e80",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -99,16 +99,16 @@
99
99
  "zod-to-json-schema": "^3.25.2"
100
100
  },
101
101
  "peerDependencies": {
102
- "@open-mercato/shared": "0.6.7-develop.6851.1.c1680b00d8",
103
- "@open-mercato/ui": "0.6.7-develop.6851.1.c1680b00d8",
102
+ "@open-mercato/shared": "0.6.7-develop.6860.1.f796b22e80",
103
+ "@open-mercato/ui": "0.6.7-develop.6860.1.f796b22e80",
104
104
  "react": "^19.0.0",
105
105
  "react-dom": "^19.0.0",
106
106
  "zod": ">=3.23.0"
107
107
  },
108
108
  "devDependencies": {
109
- "@open-mercato/cli": "0.6.7-develop.6851.1.c1680b00d8",
110
- "@open-mercato/shared": "0.6.7-develop.6851.1.c1680b00d8",
111
- "@open-mercato/ui": "0.6.7-develop.6851.1.c1680b00d8",
109
+ "@open-mercato/cli": "0.6.7-develop.6860.1.f796b22e80",
110
+ "@open-mercato/shared": "0.6.7-develop.6860.1.f796b22e80",
111
+ "@open-mercato/ui": "0.6.7-develop.6860.1.f796b22e80",
112
112
  "@types/react": "^19.2.17",
113
113
  "@types/react-dom": "^19.2.3",
114
114
  "react": "19.2.8",
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { ReferenceKind, type MikroORM } from '@mikro-orm/core'
9
9
  import type { PostgreSqlDriver } from '@mikro-orm/postgresql'
10
+ import { listEntityMetadata } from '@open-mercato/shared/lib/db/entityMetadata'
10
11
 
11
12
  /**
12
13
  * Relationship types mapped from MikroORM reference kinds.
@@ -86,13 +87,12 @@ function getSimpleTypeName(type: string | ((...args: unknown[]) => unknown) | un
86
87
  * Extract the entity graph from MikroORM metadata.
87
88
  */
88
89
  export async function extractEntityGraph(orm: MikroORM<PostgreSqlDriver>): Promise<EntityGraph> {
89
- const metadata = orm.getMetadata()
90
- const allMetadata = metadata.getAll()
90
+ const allMetadata = listEntityMetadata(orm)
91
91
 
92
92
  const nodes: EntityNode[] = []
93
93
  const edges: EntityTriple[] = []
94
94
 
95
- for (const [, entityMeta] of Object.entries(allMetadata)) {
95
+ for (const entityMeta of allMetadata) {
96
96
  // Skip abstract entities and embeddables
97
97
  if (entityMeta.abstract || entityMeta.embeddable) continue
98
98