@utaba/deep-memory-storage-neo4j 0.19.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/LICENSE +190 -0
- package/README.md +337 -0
- package/dist/index.cjs +3324 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +430 -0
- package/dist/index.d.ts +430 -0
- package/dist/index.js +3299 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Neo4jStorageProvider.ts","../src/Neo4jTraversalExecutor.ts","../src/mapping.ts","../src/Neo4jConnection.ts","../src/usageScope.ts","../src/errors.ts","../src/queries/bulk.ts","../src/queries/entity.ts","../src/queries/relationship.ts","../src/queries/repository.ts","../src/queries/timeline.ts","../src/queries/vocabulary.ts","../src/schema.ts"],"sourcesContent":["// Neo4jStorageProvider — Neo4j implementation of @utaba/deep-memory's\n// StorageProvider. CRUD methods are added incrementally; the `implements\n// StorageProvider` declaration is added once the surface is complete.\n\nimport type {\n EnsureSchemaResult,\n EntityReadOptions,\n GraphTraversalCapabilities,\n} from '@utaba/deep-memory/providers';\nimport type {\n BulkImportOptions,\n BulkImportResult,\n DeleteProgressCallback,\n ExportChunk,\n ImportChunk,\n MemoryVocabulary,\n PaginatedResult,\n PaginationOptions,\n QueryMetadata,\n RelationshipQueryOptions,\n RepositoryFilter,\n RepositoryStats,\n RepositoryUpdate,\n StorageExploreOptions,\n StorageFindQuery,\n StorageNeighborhood,\n StorageNeighborhoodLayer,\n StoragePath,\n StoragePathOptions,\n StoragePathResult,\n StorageRepositoryConfig,\n StorageTimelineOptions,\n StorageTimelineResult,\n StoredEntity,\n StoredEntityUpdate,\n StoredRelationship,\n StoredRepository,\n StoredRepositorySummary,\n TraversalResult,\n TraversalSpec,\n TraversalStep,\n UsageSink,\n VocabularyChangeRecord,\n} from '@utaba/deep-memory/types';\nimport {\n ProviderError,\n RepositoryNotFoundError,\n createSafeSink,\n matchesPropertyFilters,\n projectEntity,\n} from '@utaba/deep-memory';\nimport { Neo4jTraversalExecutor } from './Neo4jTraversalExecutor.js';\nimport type { RawTraversalResult } from './Neo4jTraversalExecutor.js';\nimport { Neo4jConnection, type Neo4jConnectionConfig } from './Neo4jConnection.js';\nimport { mapDriverError } from './errors.js';\nimport {\n bigintToSafeNumber,\n repositoryCreateParams,\n repositoryFromRecord,\n repositorySummaryFromRecord,\n} from './mapping.js';\nimport * as bulkQueries from './queries/bulk.js';\nimport * as entityQueries from './queries/entity.js';\nimport * as relationshipQueries from './queries/relationship.js';\nimport * as repositoryQueries from './queries/repository.js';\nimport * as timelineQueries from './queries/timeline.js';\nimport * as vocabQueries from './queries/vocabulary.js';\nimport { getSchemaCypher, SCHEMA_VERSION } from './schema.js';\nimport {\n buildUsageDetails,\n createUsageScope,\n runInUsageScope,\n} from './usageScope.js';\n\nconst PROVIDER_NAME = 'neo4j';\nconst DELETE_BATCH_SIZE = 500;\n\n/**\n * Lifetime of an entry in the per-process vocabulary cache. The vocabulary is\n * compile-time context for traversal — it changes on the order of once per\n * session, but a naïve read pays one round-trip per call on the hot path.\n * 60 s bounds cross-process staleness; writes inside this process invalidate\n * immediately via `invalidateVocabularyCache`. Direct port of the Cosmos\n * `VOCABULARY_CACHE_TTL_MS` constant.\n */\nconst VOCABULARY_CACHE_TTL_MS = 60_000;\n\n/**\n * Public methods that emit a usage record per call. The value extracts the\n * `repositoryId` from the method's argument list — `undefined` when the\n * operation is not scoped to a single repository (e.g. `ensureSchema`,\n * `listRepositories`).\n *\n * The map mirrors the SQL Server precedent: methods on `StorageProvider`\n * mostly take the `repositoryId` as their first positional argument, so the\n * canonical extractor is `(args) => args[0] as string`. Methods that operate\n * across repositories (e.g. `ensureSchema`, `listRepositories`) return\n * `undefined` so the sink record omits `repositoryId`.\n */\nconst TRACKED_METHODS: Record<string, (args: unknown[]) => string | undefined> = {\n ensureSchema: () => undefined,\n createRepository: (args) => {\n const cfg = args[0] as { repositoryId?: string } | undefined;\n return cfg?.repositoryId;\n },\n getRepository: (args) => args[0] as string,\n listRepositories: () => undefined,\n updateRepository: (args) => args[0] as string,\n deleteRepository: (args) => args[0] as string,\n deleteAllContents: (args) => args[0] as string,\n getVocabulary: (args) => args[0] as string,\n saveVocabulary: (args) => args[0] as string,\n getVocabularyChangeLog: (args) => args[0] as string,\n createEntity: (args) => args[0] as string,\n getEntity: (args) => args[0] as string,\n getEntityBySlug: (args) => args[0] as string,\n getEntities: (args) => args[0] as string,\n updateEntity: (args) => args[0] as string,\n deleteEntity: (args) => args[0] as string,\n deleteEntities: (args) => args[0] as string,\n deleteEntitiesByType: (args) => args[0] as string,\n findEntities: (args) => args[0] as string,\n createRelationship: (args) => args[0] as string,\n getRelationship: (args) => args[0] as string,\n getEntityRelationships: (args) => args[0] as string,\n deleteRelationship: (args) => args[0] as string,\n deleteRelationships: (args) => args[0] as string,\n deleteRelationshipsByType: (args) => args[0] as string,\n traverse: (args) => args[0] as string,\n exploreNeighborhood: (args) => args[0] as string,\n findPaths: (args) => args[0] as string,\n getTimeline: (args) => args[0] as string,\n getRepositoryStats: (args) => args[0] as string,\n importBulk: (args) => args[0] as string,\n // `executeNativeQuery` is cross-repository by design. The first positional\n // argument is `repositoryId` for interface symmetry but is intentionally\n // not stamped on the sink record — the call is not scoped to one repository.\n executeNativeQuery: () => undefined,\n // `exportAll` is intentionally omitted from this map. The Proxy emits one\n // sink record at promise resolution; an `AsyncIterable` returns\n // synchronously and is consumed across an arbitrary number of awaits, so\n // a single emit at method-return would fire BEFORE any chunk had streamed\n // and the sink would carry zero round-trips. `exportAll` opens its own\n // usage scope via `trackIterable` and emits when the iterator drains.\n};\n\n/** Configuration for `Neo4jStorageProvider`. */\nexport interface Neo4jStorageProviderConfig extends Neo4jConnectionConfig {\n /**\n * Optional usage sink. When provided, the provider emits one\n * `OperationUsage` record per public method call. The record's `value` is\n * the aggregated `summary.resultConsumedAfter` (server-side ms) across\n * every Bolt round-trip the operation produced; `unit` is `'server_ms'`.\n *\n * The sink is **never** plumbed through to AI-agent-facing surfaces — MCP\n * tools must not expose RU / server-time figures to model responses.\n */\n reportUsage?: UsageSink;\n /**\n * When `true`, prepend `PROFILE` to every compiled traversal query and\n * surface the resulting plan summary under `details.profile` on the sink\n * record. Defaults to `false` — `PROFILE` more than doubles wall-clock on\n * short traversals (see plan §D14 and the Phase 9 probe results), so the\n * cost is worth paying only when an operator is actively investigating\n * planner behaviour.\n */\n profileTraversals?: boolean;\n}\n\n/**\n * Schema-version row stored on the singleton `_Meta` node. Written by\n * `ensureSchema` and only read by `ensureSchema` — no other code path\n * touches it.\n */\nconst META_KEY = 'schema';\n\nexport class Neo4jStorageProvider {\n private readonly connection: Neo4jConnection;\n private readonly traversalExecutor: Neo4jTraversalExecutor;\n private initialized = false;\n /**\n * In-process vocabulary cache. Reads hit this map first; writes inside this\n * process invalidate the entry so cache hits stay coherent with the local\n * write. Cross-process staleness is bounded by `VOCABULARY_CACHE_TTL_MS`.\n */\n private readonly vocabularyCache = new Map<\n string,\n { vocab: MemoryVocabulary; expiresAt: number }\n >();\n /**\n * Captured at construction so `exportAll`'s `trackIterable` can emit\n * outside the Proxy's promise-resolution path. The Proxy itself relies on\n * the same `safeSink` captured by closure; this field exists for the\n * streaming-iterator case only.\n */\n private readonly reportUsage: UsageSink | undefined;\n\n constructor(config: Neo4jStorageProviderConfig) {\n this.connection = new Neo4jConnection(config);\n this.traversalExecutor = new Neo4jTraversalExecutor(this.connection, {\n profileTraversals: config.profileTraversals === true,\n });\n\n const safeSink = createSafeSink(config.reportUsage);\n this.reportUsage = safeSink;\n if (safeSink) {\n // Wrap the instance in a Proxy that opens a per-operation `UsageScope`\n // before invoking each tracked method, then emits one `OperationUsage`\n // record at completion. The chokepoint (`Neo4jConnection`) writes into\n // the active scope on every round-trip — the Proxy is the only place\n // sink records are constructed.\n //\n // Mirror of the SQL Server precedent, but with the recorded `value`\n // sourced from aggregated `summary.resultConsumedAfter` (`unit:\n // 'server_ms'`) rather than wall-clock `Date.now()`.\n // eslint-disable-next-line no-constructor-return\n return new Proxy(this, {\n get(target, prop, receiver): unknown {\n const value = Reflect.get(target, prop, receiver);\n if (typeof prop !== 'string' || typeof value !== 'function') return value;\n const extractRepoId = TRACKED_METHODS[prop];\n if (!extractRepoId) return value;\n const method = value as (...a: unknown[]) => unknown;\n return (...args: unknown[]): unknown => {\n const scope = createUsageScope();\n const repositoryId = extractRepoId(args);\n const emit = (): void => {\n safeSink({\n provider: PROVIDER_NAME,\n operation: prop,\n unit: 'server_ms',\n value: scope.serverMs,\n ...(repositoryId !== undefined ? { repositoryId } : {}),\n timestamp: new Date(),\n details: buildUsageDetails(scope),\n });\n };\n return runInUsageScope(scope, () => {\n let result: unknown;\n try {\n result = method.apply(target, args);\n } catch (err) {\n emit();\n throw err;\n }\n if (result && typeof (result as { then?: unknown }).then === 'function') {\n return (result as Promise<unknown>).then(\n (v) => {\n emit();\n return v;\n },\n (err) => {\n emit();\n throw err;\n },\n );\n }\n emit();\n return result;\n });\n };\n },\n });\n }\n }\n\n // ─── Lifecycle ─────────────────────────────────────────────────────\n\n public async initialize(): Promise<void> {\n if (this.initialized) return;\n await this.connection.verifyConnectivity();\n this.initialized = true;\n }\n\n public async dispose(): Promise<void> {\n await this.connection.close();\n this.initialized = false;\n }\n\n // ─── Schema ────────────────────────────────────────────────────────\n\n /**\n * Idempotent constraint / index DDL plus a single `_Meta` schema-version\n * handshake. Safe to call repeatedly — `CREATE ... IF NOT EXISTS` makes\n * each statement a no-op on subsequent runs.\n *\n * Neo4j Community has no per-tenant database concept — `databaseCreated`\n * is always `false`. Operators are responsible for the target database\n * existing before the provider connects.\n */\n public async ensureSchema(): Promise<EnsureSchemaResult> {\n const currentVersion = await this.readSchemaVersion();\n\n if (currentVersion !== null && currentVersion > SCHEMA_VERSION) {\n throw new ProviderError(\n `Database schema version ${currentVersion} is newer than provider version ${SCHEMA_VERSION}. ` +\n 'Update the @utaba/deep-memory-storage-neo4j package.',\n );\n }\n\n if (currentVersion === SCHEMA_VERSION) {\n return {\n databaseCreated: false,\n schemaCreated: false,\n alreadyUpToDate: true,\n schemaVersion: SCHEMA_VERSION,\n };\n }\n\n for (const statement of getSchemaCypher()) {\n await this.connection.executeSystemDdl(statement);\n }\n await this.writeSchemaVersion(SCHEMA_VERSION);\n\n return {\n databaseCreated: false,\n schemaCreated: true,\n alreadyUpToDate: false,\n schemaVersion: SCHEMA_VERSION,\n };\n }\n\n private async readSchemaVersion(): Promise<number | null> {\n // The _Meta node is global — schema versioning is a property of the\n // database, not a single repository. Cross-repository is correct here.\n const result = await this.connection.executeSystemQuery<{ schemaVersion: bigint | number }>(\n 'MATCH (m:_Meta {key: $key}) RETURN m.schemaVersion AS schemaVersion',\n { key: META_KEY },\n { crossRepository: true, routing: 'READ' },\n );\n const record = result.records[0];\n if (record === undefined) return null;\n const raw = record.get('schemaVersion');\n if (typeof raw === 'bigint') {\n if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new ProviderError(\n `_Meta.schemaVersion (${raw.toString()}) exceeds Number.MAX_SAFE_INTEGER.`,\n );\n }\n return Number(raw);\n }\n if (typeof raw === 'number') return raw;\n if (raw === null) return null;\n throw new ProviderError(\n `_Meta.schemaVersion has unexpected type ${typeof raw}; expected bigint or number.`,\n );\n }\n\n private async writeSchemaVersion(version: number): Promise<void> {\n // Cross-repository: the _Meta node is global. The MERGE keeps ensureSchema\n // idempotent across invocations.\n await this.connection.executeSystemQuery(\n 'MERGE (m:_Meta {key: $key}) SET m.schemaVersion = $version',\n { key: META_KEY, version },\n { crossRepository: true },\n );\n }\n\n // ─── Repository ────────────────────────────────────────────────────\n\n /**\n * Create a new repository. Fixed-shape `CREATE` template — every optional\n * field is bound on every call so the server plan-caches one entry across\n * all repository creates. Optional fields bound as `null` are not persisted\n * (Cypher drops null property values on write — symmetric with read).\n *\n * The `(:_Repository) REQUIRE n.repositoryId IS UNIQUE` constraint surfaces\n * duplicates as `Neo.ClientError.Schema.ConstraintValidationFailed`, which\n * `mapDriverError({ kind: 'repository', ... })` routes to\n * `DuplicateRepositoryError`.\n */\n public async createRepository(config: StorageRepositoryConfig): Promise<StoredRepository> {\n try {\n await this.connection.executeQuery(\n `CREATE (r:_Repository {\n repositoryId: $rid,\n type: $type,\n label: $label,\n description: $description,\n legal: $legal,\n owner: $owner,\n governanceConfig: $governanceConfig,\n metadata: $metadata,\n createdAt: $createdAt,\n createdBy: $createdBy\n })`,\n repositoryCreateParams(config),\n { repositoryId: config.repositoryId },\n );\n } catch (err) {\n mapDriverError(err, {\n kind: 'repository',\n repositoryId: config.repositoryId,\n operation: 'createRepository',\n });\n }\n\n const result: StoredRepository = {\n repositoryId: config.repositoryId,\n label: config.label,\n governanceConfig: config.governanceConfig,\n createdAt: config.createdAt,\n createdBy: config.createdBy,\n };\n if (config.type !== undefined) result.type = config.type;\n if (config.description !== undefined) result.description = config.description;\n if (config.legal !== undefined) result.legal = config.legal;\n if (config.owner !== undefined) result.owner = config.owner;\n if (config.metadata !== undefined) result.metadata = config.metadata;\n return result;\n }\n\n public async getRepository(repositoryId: string): Promise<StoredRepository | null> {\n const result = await this.connection.executeQuery(\n 'MATCH (r:_Repository {repositoryId: $rid}) RETURN r',\n {},\n { repositoryId, routing: 'READ' },\n );\n const record = result.records[0];\n if (record === undefined) return null;\n return repositoryFromRecord(record);\n }\n\n public async listRepositories(\n filter?: RepositoryFilter,\n ): Promise<PaginatedResult<StoredRepositorySummary>> {\n const limit = filter?.limit ?? 20;\n const offset = filter?.offset ?? 0;\n const typeFilter = filter?.type;\n\n const wherePredicates: string[] = [];\n // SKIP / LIMIT take Cypher INTEGER; passing a JS number sends a FLOAT and\n // the planner rejects it with `Neo.ClientError.Statement.ArgumentError`.\n // With `useBigInt: true` on the driver, BigInt round-trips as INTEGER.\n const params: Record<string, unknown> = { offset: BigInt(offset), limit: BigInt(limit) };\n if (typeFilter !== undefined) {\n wherePredicates.push('r.type = $filterType');\n params['filterType'] = typeFilter;\n }\n const whereClause = wherePredicates.length > 0 ? `WHERE ${wherePredicates.join(' AND ')}` : '';\n\n // listRepositories is cross-repository by definition (D18 — no sentinel\n // index, direct scan against the dm_repository_unique constraint's\n // backing index). Both queries route through executeSystemQuery; the\n // composite scan is cheap because there is no partition fan-out cost on\n // Neo4j and the constraint's auto-index covers _Repository lookups.\n const [dataResult, countResult] = await Promise.all([\n this.connection.executeSystemQuery(\n `MATCH (r:_Repository) ${whereClause} RETURN r ORDER BY r.repositoryId SKIP $offset LIMIT $limit`,\n params,\n { crossRepository: true, routing: 'READ' },\n ),\n this.connection.executeSystemQuery(\n `MATCH (r:_Repository) ${whereClause} RETURN count(r) AS total`,\n typeFilter !== undefined ? { filterType: typeFilter } : {},\n { crossRepository: true, routing: 'READ' },\n ),\n ]);\n\n const items = dataResult.records.map((record) => repositorySummaryFromRecord(record));\n const totalRaw = countResult.records[0]?.get('total');\n const total = totalRaw === undefined ? 0 : bigintToSafeNumber(totalRaw);\n\n return {\n items,\n total,\n hasMore: offset + items.length < total,\n limit,\n offset,\n };\n }\n\n /**\n * Variable-shape Cypher (per D23 trade-off — repository writes are rare so\n * the plan-cache cost is negligible). Projection-on-write returns the\n * updated row in one round-trip; empty-rowset → `RepositoryNotFoundError`.\n */\n public async updateRepository(\n repositoryId: string,\n updates: RepositoryUpdate,\n ): Promise<StoredRepository> {\n const setClauses: string[] = [];\n const params: Record<string, unknown> = {};\n\n if (updates.label !== undefined) {\n setClauses.push('r.label = $label');\n params['label'] = updates.label;\n }\n if (updates.description !== undefined) {\n setClauses.push('r.description = $description');\n params['description'] = updates.description;\n }\n if (updates.type !== undefined) {\n setClauses.push('r.type = $type');\n params['type'] = updates.type;\n }\n if (updates.legal !== undefined) {\n setClauses.push('r.legal = $legal');\n params['legal'] = updates.legal;\n }\n if (updates.owner !== undefined) {\n setClauses.push('r.owner = $owner');\n params['owner'] = updates.owner;\n }\n if (updates.governanceConfig !== undefined) {\n setClauses.push('r.governanceConfig = $governanceConfig');\n params['governanceConfig'] = JSON.stringify(updates.governanceConfig);\n }\n if (updates.metadata !== undefined) {\n // Shallow merge with the existing metadata bag — same contract as the\n // SQL Server and Cosmos providers. Requires reading the current value\n // first so the merge happens server-side via the SET clause.\n const existing = await this.getRepository(repositoryId);\n if (existing === null) throw new RepositoryNotFoundError(repositoryId);\n const merged = { ...existing.metadata, ...updates.metadata };\n setClauses.push('r.metadata = $metadata');\n params['metadata'] = JSON.stringify(merged);\n }\n\n if (setClauses.length === 0) {\n const existing = await this.getRepository(repositoryId);\n if (existing === null) throw new RepositoryNotFoundError(repositoryId);\n return existing;\n }\n\n const cypher = `MATCH (r:_Repository {repositoryId: $rid}) SET ${setClauses.join(', ')} RETURN r`;\n const result = await this.connection.executeQuery(cypher, params, { repositoryId });\n const record = result.records[0];\n if (record === undefined) throw new RepositoryNotFoundError(repositoryId);\n return repositoryFromRecord(record);\n }\n\n /**\n * Drop every node and relationship scoped to `repositoryId`, including the\n * `_Repository` node itself. Two-stage chunked wipe driven by app-side loops\n * so the progress callback fires at a useful cadence:\n *\n * 1. Drain relationships in batches via `CALL ( ) { ... } IN TRANSACTIONS`.\n * 2. Drain nodes (entities + system) in batches via the same form with\n * `DETACH DELETE` (catches any straggler edges).\n *\n * `IN TRANSACTIONS` can only run on auto-commit sessions — `executeWrite`\n * fails with `Neo.DatabaseError.Transaction.TransactionStartFailed` per\n * probe P13. The chokepoint's `executeImplicitInTransactions` is the only\n * legitimate entry point for this pattern.\n */\n public async deleteRepository(\n repositoryId: string,\n onProgress?: DeleteProgressCallback,\n ): Promise<void> {\n const { totalEntities, totalRelationships } = await this.countRepositoryContents(repositoryId);\n\n let relationshipsDeleted = 0;\n let entitiesDeleted = 0;\n\n while (true) {\n const summary = await this.connection.executeImplicitInTransactions(\n `CALL () {\n MATCH ()-[r {repositoryId: $rid}]-()\n WITH r LIMIT $batchSize\n DELETE r\n } IN TRANSACTIONS OF $batchSize ROWS`,\n // BigInt so the Cypher LIMIT clause sees a Cypher INTEGER, not FLOAT.\n { batchSize: BigInt(DELETE_BATCH_SIZE) },\n { repositoryId },\n );\n const stats = summary.counters.updates();\n const deletedThisBatch = stats['relationshipsDeleted'] ?? 0;\n if (deletedThisBatch === 0) break;\n relationshipsDeleted = Math.min(relationshipsDeleted + deletedThisBatch, totalRelationships);\n await onProgress?.({ entitiesDeleted, relationshipsDeleted, totalEntities, totalRelationships });\n }\n\n while (true) {\n const summary = await this.connection.executeImplicitInTransactions(\n `CALL () {\n MATCH (n {repositoryId: $rid})\n WITH n LIMIT $batchSize\n DETACH DELETE n\n } IN TRANSACTIONS OF $batchSize ROWS`,\n // BigInt so the Cypher LIMIT clause sees a Cypher INTEGER, not FLOAT.\n { batchSize: BigInt(DELETE_BATCH_SIZE) },\n { repositoryId },\n );\n const stats = summary.counters.updates();\n const deletedThisBatch = stats['nodesDeleted'] ?? 0;\n if (deletedThisBatch === 0) break;\n // The match drains _Entity, _Vocabulary, _VocabularyChangeLog AND the\n // _Repository node itself — system nodes inflate the raw counter past\n // the user-facing entity total. Cap so the callback never reports more\n // than it promised.\n entitiesDeleted = Math.min(entitiesDeleted + deletedThisBatch, totalEntities);\n await onProgress?.({ entitiesDeleted, relationshipsDeleted, totalEntities, totalRelationships });\n }\n }\n\n /**\n * Drop every entity and relationship scoped to `repositoryId` but preserve\n * the `_Repository` and `_Vocabulary` / `_VocabularyChangeLog` system nodes.\n * Same chunked-wipe contract as `deleteRepository`, restricted to the\n * `:_Entity` umbrella label for nodes.\n */\n public async deleteAllContents(\n repositoryId: string,\n onProgress?: DeleteProgressCallback,\n ): Promise<{ deletedEntities: number; deletedRelationships: number }> {\n const { totalEntities, totalRelationships } = await this.countRepositoryContents(repositoryId);\n\n let relationshipsDeleted = 0;\n let entitiesDeleted = 0;\n\n while (true) {\n const summary = await this.connection.executeImplicitInTransactions(\n `CALL () {\n MATCH (:_Entity {repositoryId: $rid})-[r {repositoryId: $rid}]-(:_Entity {repositoryId: $rid})\n WITH r LIMIT $batchSize\n DELETE r\n } IN TRANSACTIONS OF $batchSize ROWS`,\n // BigInt so the Cypher LIMIT clause sees a Cypher INTEGER, not FLOAT.\n { batchSize: BigInt(DELETE_BATCH_SIZE) },\n { repositoryId },\n );\n const stats = summary.counters.updates();\n const deletedThisBatch = stats['relationshipsDeleted'] ?? 0;\n if (deletedThisBatch === 0) break;\n relationshipsDeleted = Math.min(relationshipsDeleted + deletedThisBatch, totalRelationships);\n await onProgress?.({ entitiesDeleted, relationshipsDeleted, totalEntities, totalRelationships });\n }\n\n while (true) {\n const summary = await this.connection.executeImplicitInTransactions(\n `CALL () {\n MATCH (n:_Entity {repositoryId: $rid})\n WITH n LIMIT $batchSize\n DETACH DELETE n\n } IN TRANSACTIONS OF $batchSize ROWS`,\n // BigInt so the Cypher LIMIT clause sees a Cypher INTEGER, not FLOAT.\n { batchSize: BigInt(DELETE_BATCH_SIZE) },\n { repositoryId },\n );\n const stats = summary.counters.updates();\n const deletedThisBatch = stats['nodesDeleted'] ?? 0;\n if (deletedThisBatch === 0) break;\n entitiesDeleted = Math.min(entitiesDeleted + deletedThisBatch, totalEntities);\n await onProgress?.({ entitiesDeleted, relationshipsDeleted, totalEntities, totalRelationships });\n }\n\n return { deletedEntities: totalEntities, deletedRelationships: totalRelationships };\n }\n\n /**\n * One-shot pre-count used by `deleteRepository` / `deleteAllContents`. The\n * entity count uses the umbrella `:_Entity` label so system nodes\n * (`_Repository` / `_Vocabulary`) are excluded — that matches the\n * user-facing semantics of the progress callback.\n *\n * The relationship count uses the **directed** pattern `()-[r]->()`: every\n * edge is written in its stored direction (D5), so the directed pattern\n * binds each edge to exactly one row. An undirected `-` pattern would\n * double-count every edge whose endpoints are distinct vertices, because\n * Cypher enumerates the pattern from both directions.\n */\n private async countRepositoryContents(\n repositoryId: string,\n ): Promise<{ totalEntities: number; totalRelationships: number }> {\n const [entitiesResult, relationshipsResult] = await Promise.all([\n this.connection.executeQuery(\n 'MATCH (n:_Entity {repositoryId: $rid}) RETURN count(n) AS total',\n {},\n { repositoryId, routing: 'READ' },\n ),\n this.connection.executeQuery(\n 'MATCH ()-[r {repositoryId: $rid}]->() RETURN count(r) AS total',\n {},\n { repositoryId, routing: 'READ' },\n ),\n ]);\n const totalEntities = bigintToSafeNumber(entitiesResult.records[0]?.get('total') ?? 0);\n const totalRelationships = bigintToSafeNumber(relationshipsResult.records[0]?.get('total') ?? 0);\n return { totalEntities, totalRelationships };\n }\n\n // ─── Vocabulary ────────────────────────────────────────────────────\n\n /**\n * Read the vocabulary for a repository. Cache-aware: cache hits return\n * synchronously with zero Bolt round-trips. The TRACKED_METHODS proxy still\n * fires for every call — the sink record on a cache hit carries\n * `details.calls === 0` and `value === 0`, which is the contract the sink\n * expects to express \"this operation ran but did no server work\".\n */\n public async getVocabulary(repositoryId: string): Promise<MemoryVocabulary> {\n return this.getVocabularyCached(repositoryId);\n }\n\n /**\n * Cached vocabulary read used by `getVocabulary` and (in later phases) by\n * traversal compilation. The vocabulary is compile-time context for the\n * Cypher compiler — it changes on the order of once per session, but the\n * traversal hot path would otherwise pay one round-trip per call. The cache\n * flips that to one round-trip per TTL window.\n *\n * Reads inside an active usage scope still record a round-trip when a fetch\n * actually happens (cache miss); cache hits emit no round-trip and therefore\n * contribute nothing to the scope.\n */\n private async getVocabularyCached(repositoryId: string): Promise<MemoryVocabulary> {\n const now = Date.now();\n const cached = this.vocabularyCache.get(repositoryId);\n if (cached && cached.expiresAt > now) {\n return cached.vocab;\n }\n const vocab = await vocabQueries.getVocabulary(this.connection, repositoryId);\n this.vocabularyCache.set(repositoryId, {\n vocab,\n expiresAt: now + VOCABULARY_CACHE_TTL_MS,\n });\n return vocab;\n }\n\n /** Drop the cache entry for a repository — call after every vocabulary write. */\n private invalidateVocabularyCache(repositoryId: string): void {\n this.vocabularyCache.delete(repositoryId);\n }\n\n /**\n * Upsert the vocabulary for a repository. Invalidates the in-process cache\n * on success so subsequent reads observe the new state immediately within\n * this process (cross-process staleness is bounded by the 60 s TTL).\n */\n public async saveVocabulary(\n repositoryId: string,\n vocabulary: MemoryVocabulary,\n ): Promise<void> {\n await vocabQueries.saveVocabulary(this.connection, repositoryId, vocabulary);\n this.invalidateVocabularyCache(repositoryId);\n }\n\n /**\n * Page the vocabulary change-log newest first. Writes land in\n * `proposeVocabularyExtension` (out of scope here) — this method only reads\n * the `_VocabularyChangeLog` nodes back, ordered by `proposedAt` to match\n * the audit semantic on `VocabularyChangeRecord`.\n */\n public async getVocabularyChangeLog(\n repositoryId: string,\n options?: PaginationOptions,\n ): Promise<PaginatedResult<VocabularyChangeRecord>> {\n return vocabQueries.getVocabularyChangeLog(this.connection, repositoryId, options);\n }\n\n // ─── Entities ──────────────────────────────────────────────────────\n\n /**\n * Create a new entity via fixed-shape `CREATE` + catch on the uniqueness\n * constraint. A `MERGE`-with-discriminator alternative is marginally faster\n * on the happy path but mutates the existing node on collisions, writing\n * a discriminator property onto durable graph state the caller never\n * requested — correctness wins over the marginal perf delta.\n */\n public async createEntity(\n repositoryId: string,\n entity: StoredEntity,\n ): Promise<StoredEntity> {\n return entityQueries.createEntity(this.connection, repositoryId, entity);\n }\n\n /** Read a single entity by id; `null` when not found. */\n public async getEntity(\n repositoryId: string,\n entityId: string,\n options?: EntityReadOptions,\n ): Promise<StoredEntity | null> {\n return entityQueries.getEntity(this.connection, repositoryId, entityId, options);\n }\n\n /** Read a single entity by slug; `null` when not found. */\n public async getEntityBySlug(\n repositoryId: string,\n slug: string,\n options?: EntityReadOptions,\n ): Promise<StoredEntity | null> {\n return entityQueries.getEntityBySlug(this.connection, repositoryId, slug, options);\n }\n\n /**\n * Batch read by ids. Absent ids do not appear in the returned `Map`; empty\n * input returns an empty map without a round-trip.\n */\n public async getEntities(\n repositoryId: string,\n entityIds: string[],\n options?: EntityReadOptions,\n ): Promise<Map<string, StoredEntity>> {\n return entityQueries.getEntities(this.connection, repositoryId, entityIds, options);\n }\n\n /**\n * Variable-shape projection-on-write update (D23) — single round-trip\n * MATCH+SET+RETURN. Empty record array → `EntityNotFoundError`.\n */\n public async updateEntity(\n repositoryId: string,\n entityId: string,\n updates: StoredEntityUpdate,\n ): Promise<StoredEntity> {\n return entityQueries.updateEntity(this.connection, repositoryId, entityId, updates);\n }\n\n /**\n * Delete a single entity (and its incident relationships via `DETACH\n * DELETE`). Throws `EntityNotFoundError` when the match returns zero rows.\n */\n public async deleteEntity(\n repositoryId: string,\n entityId: string,\n ): Promise<void> {\n return entityQueries.deleteEntity(this.connection, repositoryId, entityId);\n }\n\n /**\n * Bulk delete by ids — single round-trip. Returns the ids actually\n * deleted; missing ids land in `notFound`.\n */\n public async deleteEntities(\n repositoryId: string,\n ids: string[],\n ): Promise<{ deleted: string[]; notFound: string[] }> {\n return entityQueries.deleteEntities(this.connection, repositoryId, ids);\n }\n\n /**\n * Delete every entity of a type plus their incident relationships, with\n * exact counts (entity + relationship) returned in one round-trip — a\n * strict improvement over Cosmos's `deletedRelationships: undefined` path\n * (Gremlin would fan out across every partition the type touches).\n */\n public async deleteEntitiesByType(\n repositoryId: string,\n entityType: string,\n ): Promise<{ deletedEntities: number; deletedRelationships: number | undefined }> {\n return entityQueries.deleteEntitiesByType(this.connection, repositoryId, entityType);\n }\n\n /**\n * Page entities matching a `StorageFindQuery`. Parallel data + count Cypher\n * pair; `total` is always exact because every filter (entity-type, property\n * equality, search term, provenance) is server-side via either a typed\n * predicate or the `dm_entity_text` fulltext index. Search-term queries\n * order by Lucene score descending; non-search queries order by `n.id` to\n * pin pagination determinism across slices.\n */\n public async findEntities(\n repositoryId: string,\n query: StorageFindQuery,\n options?: EntityReadOptions,\n ): Promise<PaginatedResult<StoredEntity>> {\n return entityQueries.findEntities(this.connection, repositoryId, query, options);\n }\n\n // ─── Relationships ─────────────────────────────────────────────────\n\n /**\n * Create a relationship. Both endpoint entities are matched under the\n * repository scope before the edge is created, so cross-repository edges\n * are structurally impossible to write (D3b layer 3). A missing endpoint\n * surfaces as `EntityNotFoundError` carrying the absent id.\n */\n public async createRelationship(\n repositoryId: string,\n relationship: StoredRelationship,\n ): Promise<StoredRelationship> {\n return relationshipQueries.createRelationship(this.connection, repositoryId, relationship);\n }\n\n /** Read a single relationship by id; `null` when not found. */\n public async getRelationship(\n repositoryId: string,\n relationshipId: string,\n ): Promise<StoredRelationship | null> {\n return relationshipQueries.getRelationship(this.connection, repositoryId, relationshipId);\n }\n\n /**\n * Page an entity's incident relationships. `direction: 'out' | 'in'`\n * additionally surfaces edges flagged `bidirectional: true` from the\n * opposite endpoint, mirroring the Cosmos read-time duplication of bidir\n * edges. `propertyFilters` is applied client-side and reports\n * `total: undefined` in that branch — same trade-off as the Cosmos\n * provider, because relationship `properties` is a JSON blob with no\n * per-key index.\n */\n public async getEntityRelationships(\n repositoryId: string,\n entityId: string,\n options?: RelationshipQueryOptions,\n ): Promise<PaginatedResult<StoredRelationship>> {\n return relationshipQueries.getEntityRelationships(\n this.connection,\n repositoryId,\n entityId,\n options,\n );\n }\n\n /** Drop a single relationship by id. No-op when the id does not match. */\n public async deleteRelationship(\n repositoryId: string,\n relationshipId: string,\n ): Promise<void> {\n return relationshipQueries.deleteRelationship(this.connection, repositoryId, relationshipId);\n }\n\n /**\n * Bulk drop by ids — single round-trip. Returns the ids actually deleted;\n * missing ids land in `notFound`.\n */\n public async deleteRelationships(\n repositoryId: string,\n ids: string[],\n ): Promise<{ deleted: string[]; notFound: string[] }> {\n return relationshipQueries.deleteRelationships(this.connection, repositoryId, ids);\n }\n\n /**\n * Drop every relationship of a type in the repository. Returns an exact\n * delete count in a single round-trip.\n */\n public async deleteRelationshipsByType(\n repositoryId: string,\n relationshipType: string,\n ): Promise<{ deletedRelationships: number }> {\n return relationshipQueries.deleteRelationshipsByType(\n this.connection,\n repositoryId,\n relationshipType,\n );\n }\n\n // ─── Graph Traversal ───────────────────────────────────────────────\n\n /**\n * Capabilities surface used by the dispatcher to decide whether a given\n * `TraversalSpec` shape is supported natively. Strict improvement over the\n * Cosmos provider in two cells: `supportsAggregation` is `true` (Cypher's\n * native aggregation makes `count` / per-key projection a one-statement\n * shape) and the runtime supports every other traversal lever.\n */\n public getCapabilities(): GraphTraversalCapabilities {\n return {\n supportsNativeQuery: true,\n nativeQueryLanguage: 'cypher',\n maxTraversalDepth: 10,\n supportsRelationshipPropertyFilters: true,\n supportsEntityPropertyFilters: true,\n supportsAggregation: true,\n supportsRepeat: true,\n supportsDedup: true,\n supportsRelationshipSummary: false,\n };\n }\n\n /**\n * Execute a `TraversalSpec` against this repository's subgraph. The\n * provider owns Cypher compilation; the spec stays language-agnostic.\n * `track('traverse')` opens the per-operation usage scope so every\n * `executeQuery` round-trip the executor performs aggregates into a single\n * `OperationUsage` record.\n */\n public async traverse(\n repositoryId: string,\n spec: TraversalSpec,\n ): Promise<TraversalResult> {\n return this.traverseInternal(repositoryId, spec);\n }\n\n /**\n * Internal compile → submit → project pipeline. Shared by the public\n * `traverse` method and by the compiler-model rewrites of\n * `exploreNeighborhood` / `findPaths`, which both consume the raw stored\n * shape to rebuild their storage-level outputs.\n */\n private async traverseInternal(\n repositoryId: string,\n spec: TraversalSpec,\n ): Promise<TraversalResult> {\n const raw = await this.executeRawTraversal(repositoryId, spec);\n\n const detailLevel = spec.detailLevel ?? 'summary';\n type ProjectedEntity = TraversalResult['entities'][number];\n type ProjectedRelationship = NonNullable<TraversalResult['relationships']>[number];\n\n const projectStoredEntity = (stored: StoredEntity): ProjectedEntity => {\n const projected = projectEntity(stored, detailLevel) as ProjectedEntity;\n if (!spec.includeProvenance) {\n delete (projected as unknown as Record<string, unknown>)['provenance'];\n }\n return projected;\n };\n\n // Walk direction stamping is mode-specific:\n // 'all' — relationships have no walk context (the row tuple has no\n // anchor), so the stored topology direction is reported as\n // `'out'` and callers derive walk direction relative to any\n // anchor via sourceEntityId / targetEntityId.\n // 'path' — per-segment, computed inside the executor.\n const projectStoredRelationship = (\n rel: StoredRelationship,\n direction: 'out' | 'in' = 'out',\n ): ProjectedRelationship => ({\n id: rel.id,\n type: rel.relationshipType,\n sourceEntityId: rel.sourceEntityId,\n targetEntityId: rel.targetEntityId,\n direction,\n properties: rel.properties,\n });\n\n let entities: ProjectedEntity[] = [];\n let relationships: ProjectedRelationship[] | undefined;\n let paths: NonNullable<TraversalResult['paths']> | undefined;\n\n // The compiler emits projection-aware RETURN only when returnMode is\n // terminal (or default). On that path the raw result carries aggregations\n // and no entity rows — bypass the entity/path mapping entirely.\n //\n // `projection.includeEntities: true` is not supported alongside server-side\n // projection: a single grouped/distinct RETURN cannot also stream the\n // un-aggregated Node objects. Callers that need both should issue two\n // queries (one with projection, one without). The flag is documented as\n // returning a lightweight aggregation-only response on this backend.\n const aggregations = raw.aggregations;\n const projectionEmitted = aggregations !== undefined;\n\n if (projectionEmitted) {\n // entities / relationships / paths stay empty/undefined\n } else if (spec.returnMode === 'terminal') {\n entities = raw.terminalEntities.map(projectStoredEntity);\n relationships = undefined;\n } else if (spec.returnMode === 'all') {\n // Greedy-expand is unnecessary on Cypher: each row of the 'all' emission\n // is a (n0, ..., nD, r0, ..., r(D-1)) tuple binding every relationship\n // to its endpoint nodes at MATCH time. A `LIMIT` slices whole rows; it\n // cannot orphan a relationship's endpoint within a row. The Cosmos\n // provider needs the back-fill because Gremlin's union-of-vertices-and-\n // edges stream can drop an edge's endpoint via `.range()`.\n entities = raw.allEntities.map(projectStoredEntity);\n relationships = raw.allRelationships.map((r) => projectStoredRelationship(r));\n } else {\n paths = raw.pathRows.map((row) => ({\n length: Math.max(row.entityIds.length - 1, 0),\n entities: row.entityIds.map((id) => {\n const stored = raw.entityMap.get(id);\n if (!stored) {\n throw new ProviderError(\n 'Unpacking Cypher path: entity referenced by path is missing from the result.',\n 'Inspect compiledQuery — this indicates a path emission shape mismatch.',\n );\n }\n return projectStoredEntity(stored);\n }),\n relationships: row.relationshipIds.map((id, i) => {\n const stored = raw.relationshipMap.get(id);\n if (!stored) {\n throw new ProviderError(\n 'Unpacking Cypher path: relationship referenced by path is missing from the result.',\n 'Inspect compiledQuery — this indicates a path emission shape mismatch.',\n );\n }\n return projectStoredRelationship(stored, row.relationshipDirections[i] ?? 'out');\n }),\n }));\n relationships = Array.from(raw.relationshipMap.values()).map((rel) =>\n projectStoredRelationship(rel, raw.pathRelFirstDirection.get(rel.id) ?? 'out'),\n );\n }\n\n const limit = spec.limit ?? 50;\n let total: number;\n if (projectionEmitted) {\n // Projection rows are the visible page — one row per group (count /\n // distinct) or per matched entity (values, non-distinct).\n total = aggregations!.length;\n } else if (spec.returnMode === 'path') {\n total = paths?.length ?? 0;\n } else if (spec.returnMode === 'all') {\n // 'all' mode returns an interleaved entity+edge union — total counts both\n // arrays so callers see the true page size.\n total = entities.length + (relationships?.length ?? 0);\n } else {\n total = entities.length;\n }\n\n const truncated = total >= limit;\n\n const queryMetadata: QueryMetadata = {\n executionTimeMs: raw.executionTimeMs,\n resourceCost: { units: 'server_ms', value: raw.serverMs },\n compiledQuery: raw.compiledQuery,\n compiledQueryLanguage: 'cypher',\n appliedLimits: {\n maxResults: limit,\n ...(spec.steps !== undefined ? { maxDepth: spec.steps.length } : {}),\n },\n truncated,\n ...(truncated ? { truncationReason: 'result_limit' as const } : {}),\n };\n\n return {\n entities,\n ...(relationships !== undefined ? { relationships } : {}),\n ...(paths !== undefined ? { paths } : {}),\n ...(aggregations !== undefined ? { aggregations } : {}),\n total,\n returned: total,\n hasMore: truncated,\n queryMetadata,\n };\n }\n\n /**\n * Lower-level compile + submit + parse helper. Fetches the cached\n * vocabulary once (D16) and hands it to the executor; the executor handles\n * the repositoryId-scope rewrite, optional PROFILE prefix, and Path-object\n * parsing.\n */\n private async executeRawTraversal(\n repositoryId: string,\n spec: TraversalSpec,\n ): Promise<RawTraversalResult> {\n const vocabulary = await this.getVocabularyCached(repositoryId);\n return this.traversalExecutor.execute(repositoryId, spec, vocabulary);\n }\n\n /**\n * BFS-like neighbourhood exploration. For each depth `d` from 1 to\n * `options.depth`, compile a cumulative `'all'`-mode spec with `d` discrete\n * `'both'`-direction steps, run it through the executor, and walk one BFS\n * layer client-side from the previous frontier using the returned edges.\n *\n * Round-trips per call: `options.depth`. Server-side step direction is\n * fixed to `'both'` (catches every edge in either direction); the\n * directional + bidirectional filter and entity-type filter run client-side\n * during layer reconstruction — both to preserve the observable contract\n * shared with the Cosmos provider and because the compiler's prefix walk at\n * each depth is intentionally unfiltered so deeper layers stay reachable\n * through any intermediate.\n */\n public async exploreNeighborhood(\n repositoryId: string,\n entityId: string,\n options: StorageExploreOptions,\n ): Promise<StorageNeighborhood> {\n const layers: StorageNeighborhoodLayer[] = [];\n const visited = new Set<string>([entityId]);\n let frontier = new Set<string>([entityId]);\n\n for (let d = 1; d <= options.depth; d++) {\n if (frontier.size === 0) break;\n\n const spec: TraversalSpec = {\n start: { entityId },\n steps: buildExploreSteps(d, options),\n returnMode: 'all',\n // The cumulative-d query fetches every node and edge reachable in ≤d\n // hops in either direction. Size the limit generously so a single\n // round-trip can hold the layer's full graph regardless of fan-out.\n limit: 10_000,\n detailLevel: 'full',\n includeProvenance: true,\n };\n const raw = await this.executeRawTraversal(repositoryId, spec);\n\n const edgesByVertex = new Map<string, StoredRelationship[]>();\n for (const rel of raw.allRelationships) {\n const a = edgesByVertex.get(rel.sourceEntityId);\n if (a) a.push(rel);\n else edgesByVertex.set(rel.sourceEntityId, [rel]);\n const b = edgesByVertex.get(rel.targetEntityId);\n if (b) b.push(rel);\n else edgesByVertex.set(rel.targetEntityId, [rel]);\n }\n\n const layer: StorageNeighborhoodLayer = {};\n const nextFrontier = new Set<string>();\n // Dedup connected entities per (relationship-type) bucket within a single\n // layer. The same entity can be reached via multiple stored edges of the\n // same type (e.g. a logically-bidirectional relationship modelled as two\n // directed half-edges) — count it once, not once per traversed edge.\n const layerBucketSeen = new Map<string, Set<string>>();\n\n for (const fv of frontier) {\n const incident = edgesByVertex.get(fv) ?? [];\n for (const rel of incident) {\n const isSource = rel.sourceEntityId === fv;\n const isTarget = rel.targetEntityId === fv;\n let matchesDirection = false;\n let connectedId: string | undefined;\n\n if (isSource && (options.direction === 'out' || options.direction === 'both')) {\n matchesDirection = true;\n connectedId = rel.targetEntityId;\n } else if (isTarget && (options.direction === 'in' || options.direction === 'both')) {\n matchesDirection = true;\n connectedId = rel.sourceEntityId;\n } else if (rel.bidirectional) {\n // bidirectional flag exposes the edge in the opposite direction\n // without doubling the stored topology.\n if (isSource && options.direction === 'in') {\n matchesDirection = true;\n connectedId = rel.targetEntityId;\n } else if (isTarget && options.direction === 'out') {\n matchesDirection = true;\n connectedId = rel.sourceEntityId;\n }\n }\n if (!matchesDirection || !connectedId) continue;\n if (visited.has(connectedId)) continue;\n\n if (\n options.relationshipPropertyFilters &&\n options.relationshipPropertyFilters.length > 0\n ) {\n if (!matchesPropertyFilters(rel.properties, options.relationshipPropertyFilters))\n continue;\n }\n\n const connectedEntity = raw.entityMap.get(connectedId);\n if (!connectedEntity) continue;\n\n if (\n options.entityTypes &&\n options.entityTypes.length > 0 &&\n !options.entityTypes.includes(connectedEntity.entityType)\n ) {\n continue;\n }\n\n const relType = rel.relationshipType;\n let bucketSeen = layerBucketSeen.get(relType);\n if (!bucketSeen) {\n bucketSeen = new Set<string>();\n layerBucketSeen.set(relType, bucketSeen);\n }\n if (bucketSeen.has(connectedId)) continue;\n bucketSeen.add(connectedId);\n\n if (!layer[relType]) {\n layer[relType] = { total: 0, entities: [], relationships: [] };\n }\n layer[relType]!.entities.push(connectedEntity);\n layer[relType]!.relationships.push(rel);\n layer[relType]!.total = layer[relType]!.entities.length;\n nextFrontier.add(connectedId);\n }\n }\n\n // Per-type pagination — `total` reflects the full pre-slice count so\n // callers can page later without re-issuing the traversal.\n for (const relType of Object.keys(layer)) {\n const group = layer[relType]!;\n const start = options.offsetPerType;\n const end = start + options.limitPerType;\n group.entities = group.entities.slice(start, end);\n group.relationships = group.relationships.slice(start, end);\n }\n\n if (Object.keys(layer).length > 0) {\n layers.push(layer);\n }\n\n // Promote the next frontier into `visited` only after the whole layer is\n // processed — keeps a single entity available under multiple relationship\n // types within the same layer (same semantic as the Cosmos provider).\n for (const id of nextFrontier) visited.add(id);\n frontier = nextFrontier;\n }\n\n return { centerId: entityId, layers };\n }\n\n /**\n * Path finding between two entities. Single round-trip via a variable-length\n * `MATCH p = (s)-[*1..N]-(t)` pattern; the compiler's path-binding emission\n * lets the executor recover ordered nodes and relationships via `nodes(p)`\n * / `relationships(p)`. The default `DIFFERENT RELATIONSHIPS` match mode in\n * Cypher 25 prevents edge reuse within a single path — no explicit dedup\n * filter is needed.\n *\n * The traversal walks the graph topologically regardless of relationship\n * directionality (a path is defined by reachability, not semantic\n * direction); entity-type and relationship-property filters apply during\n * compilation, target filtering happens post-fetch.\n */\n public async findPaths(\n repositoryId: string,\n sourceId: string,\n targetId: string,\n options: StoragePathOptions,\n ): Promise<StoragePathResult> {\n if (sourceId === targetId) {\n return { paths: [{ entityIds: [sourceId], relationshipIds: [] }], totalPaths: 1 };\n }\n\n const step: TraversalStep = {\n direction: 'both',\n repeat: { maxDepth: options.maxDepth, emitIntermediates: true },\n };\n if (options.relationshipTypes && options.relationshipTypes.length > 0) {\n step.relationshipTypes = options.relationshipTypes;\n }\n if (options.relationshipPropertyFilters && options.relationshipPropertyFilters.length > 0) {\n step.relationshipFilter = options.relationshipPropertyFilters;\n }\n\n const spec: TraversalSpec = {\n start: { entityId: sourceId },\n steps: [step],\n returnMode: 'path',\n // Pull a generous candidate pool so the post-fetch filter (paths ending\n // at targetId) has enough rows to paginate from. The variable-length\n // pattern returns every walk of length ≤ maxDepth in one round-trip.\n limit: Math.max(options.limit + options.offset, options.limit) * 10,\n detailLevel: 'full',\n includeProvenance: true,\n };\n\n const raw = await this.executeRawTraversal(repositoryId, spec);\n\n const matchingPaths: StoragePath[] = [];\n for (const row of raw.pathRows) {\n const last = row.entityIds[row.entityIds.length - 1];\n if (last !== targetId) continue;\n // Enforce simple paths — no vertex appears twice. Cypher's variable-\n // length pattern emits every walk of length ≤ maxDepth, and the default\n // `DIFFERENT RELATIONSHIPS` match mode only prevents edge reuse; vertex\n // reuse is still allowed. Without this filter the walk\n // source → … → target → other → target\n // counts as a valid path to the caller and inflates `totalPaths` with\n // detours that loop back through the destination.\n if (new Set(row.entityIds).size !== row.entityIds.length) continue;\n if (options.entityTypes && options.entityTypes.length > 0) {\n // Entity-type filter applies only to intermediate vertices — source\n // and target are always allowed regardless of the filter, mirroring\n // the Cosmos contract.\n let rejected = false;\n for (let i = 1; i < row.entityIds.length - 1; i++) {\n const intermediate = raw.entityMap.get(row.entityIds[i]!);\n if (!intermediate) {\n rejected = true;\n break;\n }\n if (!options.entityTypes.includes(intermediate.entityType)) {\n rejected = true;\n break;\n }\n }\n if (rejected) continue;\n }\n matchingPaths.push({\n entityIds: [...row.entityIds],\n relationshipIds: [...row.relationshipIds],\n });\n }\n\n const paginated = matchingPaths.slice(options.offset, options.offset + options.limit);\n\n return {\n paths: paginated,\n totalPaths: matchingPaths.length,\n };\n }\n\n // ─── Timeline ──────────────────────────────────────────────────────\n\n /**\n * Reconstruct the timeline event stream for an entity. One server\n * round-trip: the centre entity's provenance scalars plus every incident\n * edge's id + createdAt arrive in a single tuple via `OPTIONAL MATCH +\n * collect()`. The provider walks the row client-side to emit\n * `entity:created` / `entity:updated` / `relationship:created` events.\n *\n * Cosmos pays two round-trips for the same information because Gremlin\n * cannot bind an aggregated edge list to a vertex projection in one shot —\n * a platform divergence, not an inherent trade-off.\n */\n public async getTimeline(\n repositoryId: string,\n entityId: string,\n options: StorageTimelineOptions,\n ): Promise<StorageTimelineResult> {\n return timelineQueries.getTimeline(this.connection, repositoryId, entityId, options);\n }\n\n // ─── Bulk Operations ───────────────────────────────────────────────\n\n /**\n * Stream every entity and every relationship in the repository as\n * cursor-paginated chunks. Entities come first, then relationships; each\n * chunk carries a monotonic `sequence` and an `isLast` flag.\n *\n * The Proxy-based tracking flow does not apply here. Tracked methods emit\n * one sink record at promise resolution; an `AsyncIterable` returns\n * synchronously, before any chunk has streamed. Instead, `trackIterable`\n * wraps the underlying generator in its own `UsageScope` that opens at\n * iterator creation, records each round-trip as the consumer pulls the\n * next chunk, and emits one sink record when the iterator drains — so the\n * resulting record aggregates server time across every chunk fetch.\n */\n public exportAll(repositoryId: string): AsyncIterable<ExportChunk> {\n return this.trackIterable(\n 'exportAll',\n repositoryId,\n bulkQueries.exportAll(this.connection, repositoryId),\n );\n }\n\n /**\n * Run a bulk import: every entity then every relationship from the input\n * chunks lands in the repository. `skipExistenceCheck: true` uses CREATE\n * for the absolute peak throughput; `false` (default) uses MERGE for\n * idempotent re-imports.\n *\n * Returns a single aggregate `BulkImportResult` spanning every chunk.\n * Per-row failures land in `result.errors`; surviving rows still count\n * toward `entitiesImported` / `relationshipsImported`.\n */\n public async importBulk(\n repositoryId: string,\n data: ImportChunk[],\n options?: BulkImportOptions,\n ): Promise<BulkImportResult> {\n return bulkQueries.importBulk(this.connection, repositoryId, data, options);\n }\n\n /**\n * Wrap an `AsyncIterable` so every chunk it produces is generated inside a\n * single shared `UsageScope`. The scope aggregates `summary.resultConsumedAfter`\n * across every round-trip the iterator performs; one sink record fires when\n * the iterator drains (or is closed / thrown into).\n *\n * Implementation mirrors the Cosmos `trackIterable` precedent: each\n * `iter.next()` call re-enters the scope via `runInUsageScope`. The\n * `AsyncLocalStorage` chain links async work performed inside the\n * generator body to the scope for the duration of that step. Between\n * `next()` calls the scope is dormant — the consumer's awaits do not\n * accumulate into it, which is exactly the desired semantics.\n *\n * When the sink is absent the wrap degenerates to a pass-through; the\n * Proxy's `createSafeSink` short-circuit covers the no-sink case at\n * construction time, so this method is only reached when `reportUsage` is\n * present.\n */\n private trackIterable<T>(\n operation: string,\n repositoryId: string,\n source: AsyncIterable<T>,\n ): AsyncIterable<T> {\n // The sink may be undefined when the provider was constructed without\n // `reportUsage`; the Proxy is then absent and this method is reached\n // directly. Pass-through in that case so the streaming consumer pays no\n // wrapper overhead.\n const sink = this.reportUsage;\n if (sink === undefined) return source;\n return {\n [Symbol.asyncIterator]: (): AsyncIterator<T> => {\n const iter = source[Symbol.asyncIterator]();\n const scope = createUsageScope();\n let emitted = false;\n const emit = (): void => {\n if (emitted) return;\n emitted = true;\n sink({\n provider: PROVIDER_NAME,\n operation,\n unit: 'server_ms',\n value: scope.serverMs,\n repositoryId,\n timestamp: new Date(),\n details: buildUsageDetails(scope),\n });\n };\n return {\n async next(): Promise<IteratorResult<T>> {\n const step = await runInUsageScope(scope, () => iter.next());\n if (step.done) emit();\n return step;\n },\n async return(value?: T): Promise<IteratorResult<T>> {\n emit();\n if (iter.return) return iter.return(value);\n return { done: true, value: value as T };\n },\n async throw(err?: unknown): Promise<IteratorResult<T>> {\n emit();\n if (iter.throw) return iter.throw(err);\n throw err;\n },\n };\n },\n };\n }\n\n // ─── Stats ─────────────────────────────────────────────────────────\n\n /**\n * Aggregate repository statistics — entity / relationship totals, per-type\n * breakdowns, vocabulary version. Two parallel native-aggregation round-\n * trips (`count(n)` per `entityType`, `count(r)` per `type(r)`); the\n * vocabulary version comes from the cached `_Vocabulary` node so a warm\n * cache costs exactly two round-trips total.\n *\n * Strict improvement over Cosmos's Gremlin `.group().by().by(count())`\n * shape — Cypher's native aggregation collapses each metric to a one-\n * statement plan that hits the `(repositoryId, entityType)` and\n * relationship-property indexes directly.\n */\n public async getRepositoryStats(repositoryId: string): Promise<RepositoryStats> {\n const vocabulary = await this.getVocabularyCached(repositoryId);\n return repositoryQueries.getRepositoryStats(this.connection, repositoryId, vocabulary);\n }\n\n // ─── Native Query ──────────────────────────────────────────────────\n\n /**\n * Execute a raw Cypher statement with caller-supplied bindings.\n *\n * ⚠️ ELEVATED PRIVILEGE — SYSTEM-LEVEL OPERATION ⚠️\n *\n * This method is an unscoped pass-through: it does not filter by\n * repository, does not inject the `$rid` binding, and performs no\n * validation on the Cypher string. A single call can read or mutate any\n * node or relationship in the database regardless of which repository it\n * belongs to.\n *\n * DO NOT expose this method to AI agents, end users, or any untrusted\n * caller. It is intended for:\n * - administrative tooling (migrations, diagnostics, repairs)\n * - internal library operations that need cross-repository reach\n *\n * `repositoryId` is accepted for interface symmetry but is intentionally\n * ignored here — the caller is trusted to scope the query themselves.\n * Because the call is cross-repository by design, the emitted usage\n * record carries no `repositoryId` (see `TRACKED_METHODS`).\n *\n * For agent-facing graph queries use {@link traverse}, which enforces the\n * repositoryId scope predicate.\n */\n public async executeNativeQuery(\n _repositoryId: string,\n query: string,\n params?: Record<string, unknown>,\n ): Promise<unknown[]> {\n const result = await this.connection.executeSystemQuery(query, params ?? {}, {\n crossRepository: true,\n });\n // Each driver `Record` is mapped to a plain object keyed by RETURN /\n // YIELD column name. Driver-specific value shapes (Node, Relationship,\n // BigInt) flow through untouched — admin tooling is responsible for\n // interpreting them.\n return result.records.map((record) => record.toObject());\n }\n}\n\n/**\n * Build the per-step `TraversalSpec` steps for `exploreNeighborhood` at a\n * given cumulative depth. Server-side step direction is fixed to `'both'`;\n * the directional + bidirectional filter and entity-type filter are applied\n * client-side during layer reconstruction to preserve the observable\n * contract (deeper layers stay reachable through any intermediate).\n *\n * `relationshipTypes` is pushed to the server — the compiler emits it as\n * `-[r:TYPE1|TYPE2]-` which IS part of the prefix walk at every depth.\n */\nfunction buildExploreSteps(\n depth: number,\n options: StorageExploreOptions,\n): TraversalStep[] {\n const base: TraversalStep = { direction: 'both' };\n if (options.relationshipTypes && options.relationshipTypes.length > 0) {\n base.relationshipTypes = options.relationshipTypes;\n }\n const steps: TraversalStep[] = [];\n for (let i = 0; i < depth; i++) {\n steps.push({ ...base });\n }\n return steps;\n}\n","// Neo4jTraversalExecutor — compile + submit + parse pipeline shared by\n// `traverse`, `exploreNeighborhood`, and `findPaths`.\n//\n// Mirrors the Cosmos `executeTraversal` private — the language differs (Cypher\n// vs Gremlin), the wire shape differs (typed `Node` / `Relationship` / `Path`\n// objects vs projected `Map`s with a synthetic `__kind` field), but the\n// discriminated-output contract is the same: `'terminal'` yields a flat row\n// stream of node-only records, `'all'` yields a row-tuple of nodes and\n// relationships per match, `'path'` yields full `Path` objects.\n//\n// Repository scope is injected at the compiled-query boundary by rewriting the\n// first `MATCH (n0)` (or `MATCH p = (n0)`) the compiler emits to include the\n// `:_Entity {repositoryId: $rid}` qualifier. The chokepoint's `$rid` assertion\n// then passes structurally. The compiler does not itself emit repositoryId\n// because the compiler is provider-agnostic — the rewrite lives here.\n\nimport type {\n MemoryVocabulary,\n StoredEntity,\n StoredRelationship,\n TraversalAggregation,\n TraversalSpec,\n} from '@utaba/deep-memory/types';\nimport { CypherCompiler, ProviderError } from '@utaba/deep-memory';\nimport type { Neo4jConnection } from './Neo4jConnection.js';\nimport { entityFromProperties, relationshipFromProperties } from './mapping.js';\n\n/**\n * Raw, un-projected output of `Neo4jTraversalExecutor.execute`. Only the\n * fields relevant to the spec's `returnMode` are populated; the rest stay\n * empty. The public `traverseInternal` projects this bag into the\n * `TraversalResult` shape; `exploreNeighborhoodImpl` and `findPathsImpl`\n * consume `entityMap` / `relationshipMap` / `pathRows` directly.\n */\nexport interface RawTraversalResult {\n /** Populated for `returnMode === 'terminal'`. Row order preserved. */\n terminalEntities: StoredEntity[];\n /** Populated for `returnMode === 'all'`. Deduped by entity id across rows. */\n allEntities: StoredEntity[];\n /** Populated for `returnMode === 'all'`. Deduped by relationship id across rows. */\n allRelationships: StoredRelationship[];\n /** Populated for `returnMode === 'path'`. One entry per matching path. */\n pathRows: Array<{\n entityIds: string[];\n relationshipIds: string[];\n relationshipDirections: Array<'out' | 'in'>;\n }>;\n /**\n * Populated when the spec carries `projection` and the compiler emitted a\n * projection-aware RETURN. The rows are aggregated server-side; the\n * provider returns this verbatim on `TraversalResult.aggregations`.\n * Mutually exclusive with `terminalEntities` / `allEntities` / `pathRows` —\n * the compiler picks one emission shape per query.\n */\n aggregations?: TraversalAggregation[];\n /** Lookup table for every entity that appeared in any returned row. */\n entityMap: Map<string, StoredEntity>;\n /** Lookup table for every relationship that appeared in any returned row. */\n relationshipMap: Map<string, StoredRelationship>;\n /**\n * First-seen walk direction per deduped edge id. Populated for `'path'`\n * mode only — `'all'` mode's row tuples have no walk context, only stored\n * topology.\n */\n pathRelFirstDirection: Map<string, 'out' | 'in'>;\n executionTimeMs: number;\n /** `summary.resultConsumedAfter` in ms — the cost number for the sink record. */\n serverMs: number;\n /** The Cypher actually shipped, after the repositoryId-scope rewrite. */\n compiledQuery: string;\n /** Captured when `profileTraversals: true`; otherwise `undefined`. */\n profile?: {\n totalDbHits: number;\n rootOperator: string;\n };\n}\n\n/**\n * Configuration the executor reads from the surrounding provider. The flag is\n * driver-wide so a single provider instance picks one behaviour — switching\n * it on/off per call would change query strings and pollute the plan cache.\n */\nexport interface Neo4jTraversalExecutorConfig {\n /**\n * When `true`, every compiled traversal query is prepended with `PROFILE`\n * and `summary.profile` is harvested into `RawTraversalResult.profile`. Off\n * by default (probe P17 measured ~129 % wall-clock overhead vs plain).\n */\n profileTraversals: boolean;\n}\n\n/**\n * Result-summary subset the executor reads. The driver's runtime type is\n * richer; the structural subset here lets the executor stay loosely typed.\n */\ninterface SummaryLike {\n resultConsumedAfter?: unknown;\n profile?: PlanLike;\n}\n\ninterface PlanLike {\n operatorType?: string;\n dbHits?: unknown;\n children?: PlanLike[];\n}\n\ninterface NodeLike {\n elementId: string;\n labels: string[];\n properties: Record<string, unknown>;\n}\n\ninterface RelationshipLike {\n elementId: string;\n type: string;\n startNodeElementId: string;\n endNodeElementId: string;\n properties: Record<string, unknown>;\n}\n\nexport class Neo4jTraversalExecutor {\n private readonly compiler = new CypherCompiler();\n\n constructor(\n private readonly connection: Neo4jConnection,\n private readonly config: Neo4jTraversalExecutorConfig,\n ) {}\n\n /**\n * Compile, scope, submit, and parse a traversal spec. The provider's vocab\n * cache is the source of `vocabulary` — `traverse` / `exploreNeighborhood`\n * / `findPaths` look it up once and pass it down.\n */\n public async execute(\n repositoryId: string,\n spec: TraversalSpec,\n vocabulary: MemoryVocabulary,\n ): Promise<RawTraversalResult> {\n const startTime = Date.now();\n const compiled = this.compiler.compile(spec, vocabulary);\n const scopedQuery = this.scopeQuery(compiled.query);\n const finalQuery = this.config.profileTraversals ? `PROFILE ${scopedQuery}` : scopedQuery;\n // The compiler emits `SKIP / LIMIT $pN` with JS-number bindings; Cypher's\n // pagination clauses require a Cypher INTEGER, and with `useBigInt: true`\n // a plain number arrives as FLOAT (`Neo.ClientError.Statement.ArgumentError:\n // '50.0' is not a valid value...`). Coerce those specific bindings to\n // BigInt at the boundary so the compiler stays language-pure.\n const params = coerceSkipLimitToBigInt(finalQuery, compiled.params);\n\n let result;\n try {\n result = await this.connection.executeQuery(\n finalQuery,\n params,\n { repositoryId, routing: 'READ' },\n );\n } catch (err: unknown) {\n throw new ProviderError(\n `Neo4j traversal failed: ${err instanceof Error ? err.message : String(err)}`,\n 'Inspect the compiled Cypher in queryMetadata.compiledQuery and confirm the spec passed validation.',\n );\n }\n\n const executionTimeMs = Date.now() - startTime;\n const summary = result.summary as unknown as SummaryLike;\n const serverMs = bigintLike(summary.resultConsumedAfter);\n\n const raw: RawTraversalResult = {\n terminalEntities: [],\n allEntities: [],\n allRelationships: [],\n pathRows: [],\n entityMap: new Map(),\n relationshipMap: new Map(),\n pathRelFirstDirection: new Map(),\n executionTimeMs,\n serverMs,\n compiledQuery: finalQuery,\n };\n\n if (this.config.profileTraversals && summary.profile !== undefined) {\n raw.profile = summariseProfile(summary.profile);\n }\n\n // Projection rows have a different shape (scalar columns, no Node objects)\n // so they bypass the entity/relationship parsers entirely. The compiler\n // only emits the projection RETURN when returnMode is terminal/default,\n // mirroring that gate here.\n const emitsProjection =\n spec.projection !== undefined && spec.returnMode !== 'path' && spec.returnMode !== 'all';\n\n if (emitsProjection) {\n this.parseProjectionRows(result.records, raw, spec.projection!.properties, spec.projection!.mode ?? 'values');\n } else if (spec.returnMode === 'terminal') {\n this.parseTerminalRows(result.records, raw);\n } else if (spec.returnMode === 'all') {\n this.parseAllRows(result.records, raw);\n } else {\n this.parsePathRows(result.records, raw);\n }\n\n return raw;\n }\n\n /**\n * Parse the server-aggregated projection rows. Each row has one column per\n * projected property and, when mode is 'count', an additional `count` column.\n * Values arrive as native scalars / strings / BigInts; counts arrive as\n * BigInt (per the driver's `useBigInt: true` configuration) and coerce to\n * Number for the JSON wire shape.\n */\n private parseProjectionRows(\n records: ReadonlyArray<{ keys: ReadonlyArray<PropertyKey>; get(key: string): unknown }>,\n raw: RawTraversalResult,\n propertyNames: ReadonlyArray<string>,\n mode: 'values' | 'count',\n ): void {\n const aggregations: TraversalAggregation[] = [];\n for (const record of records) {\n const values: Record<string, unknown> = {};\n for (const prop of propertyNames) {\n values[prop] = normaliseScalar(record.get(prop));\n }\n if (mode === 'count') {\n const rawCount = record.get('count');\n const count = typeof rawCount === 'bigint' ? Number(rawCount)\n : typeof rawCount === 'number' ? rawCount\n : 0;\n aggregations.push({ values, count });\n } else {\n aggregations.push({ values });\n }\n }\n raw.aggregations = aggregations;\n }\n\n /**\n * Inject the repositoryId scope onto the start node so the chokepoint's\n * `$rid` assertion is satisfied structurally and the planner uses the\n * `(repositoryId, id)` unique constraint's backing index for the seek.\n *\n * The compiler always emits `(n0)` as the first match part (it never adds a\n * label or property map there) — we rewrite that single occurrence. The\n * provider's defence-in-depth (D3b layer 3) prevents cross-repository edges\n * from existing in the first place, so scoping just the start node is\n * sufficient — once the planner anchors on a node in this repo, every\n * reachable node is also in this repo.\n */\n private scopeQuery(query: string): string {\n // Two cases the compiler can emit at the start of the MATCH:\n // 'MATCH (n0)...' (terminal / all modes)\n // 'MATCH p = (n0)...' (path mode)\n // Either way the literal substring `(n0)` is the start-node form.\n const scope = '(n0:_Entity {repositoryId: $rid})';\n const replaced = query.replace('(n0)', scope);\n if (replaced === query) {\n throw new ProviderError(\n 'Neo4jTraversalExecutor: compiled query did not contain the expected `(n0)` start-node form; cannot inject repositoryId scope.',\n 'This indicates a CypherCompiler emission change that the provider has not been updated for.',\n );\n }\n return replaced;\n }\n\n private parseTerminalRows(\n records: ReadonlyArray<{ keys: ReadonlyArray<PropertyKey>; get(key: string): unknown }>,\n raw: RawTraversalResult,\n ): void {\n // 'terminal' mode emits one node alias (the last hop's target). Walk every\n // column on every row and pick up the first Node-shaped value — the\n // compiler's RETURN has exactly one column in terminal mode.\n for (const record of records) {\n for (const key of record.keys) {\n if (typeof key !== 'string') continue;\n const value = record.get(key);\n if (isNode(value)) {\n const stored = entityFromNode(value);\n raw.terminalEntities.push(stored);\n if (!raw.entityMap.has(stored.id)) raw.entityMap.set(stored.id, stored);\n }\n }\n }\n }\n\n private parseAllRows(\n records: ReadonlyArray<{ keys: ReadonlyArray<PropertyKey>; get(key: string): unknown }>,\n raw: RawTraversalResult,\n ): void {\n // 'all' mode rows are tuples like (n0, n1, ..., nD, r0, ..., r(D-1)). Each\n // column may be a Node or a Relationship — discriminate by shape and dedup\n // by entity/relationship id across rows. Each id contributes to the\n // entities / relationships arrays exactly once.\n for (const record of records) {\n for (const key of record.keys) {\n if (typeof key !== 'string') continue;\n const value = record.get(key);\n if (isNode(value)) {\n const stored = entityFromNode(value);\n if (!raw.entityMap.has(stored.id)) {\n raw.entityMap.set(stored.id, stored);\n raw.allEntities.push(stored);\n }\n } else if (isRelationship(value)) {\n const stored = relationshipFromRelationship(value);\n if (!raw.relationshipMap.has(stored.id)) {\n raw.relationshipMap.set(stored.id, stored);\n raw.allRelationships.push(stored);\n }\n }\n // Other column shapes (BigInt counters, list-typed relationships from\n // variable-length patterns) are not expected in non-repeat 'all'\n // emission; ignore them so the parser stays forward-compatible.\n }\n }\n }\n\n private parsePathRows(\n records: ReadonlyArray<{ keys: ReadonlyArray<PropertyKey>; get(key: string): unknown }>,\n raw: RawTraversalResult,\n ): void {\n // 'path' mode returns `pathNodes` (list of Node), `pathRels` (list of\n // Relationship), `pathLength` (Integer). The variable-length pattern\n // produces one row per matching walk — including the 0-hop start-only\n // \"path\" — and `nodes(p)` / `relationships(p)` are ordered along the walk.\n // Walk direction per segment is recovered by comparing each relationship's\n // `startNodeElementId` to the preceding node's `elementId`.\n for (const record of records) {\n const pathNodes = record.get('pathNodes');\n const pathRels = record.get('pathRels');\n if (!Array.isArray(pathNodes) || !Array.isArray(pathRels)) continue;\n\n const pathEntityIds: string[] = [];\n const pathRelIds: string[] = [];\n const pathRelDirections: Array<'out' | 'in'> = [];\n let previousElementId: string | undefined;\n\n for (const node of pathNodes) {\n if (!isNode(node)) continue;\n const stored = entityFromNode(node);\n if (!raw.entityMap.has(stored.id)) raw.entityMap.set(stored.id, stored);\n pathEntityIds.push(stored.id);\n }\n\n for (let i = 0; i < pathRels.length; i++) {\n const rel = pathRels[i];\n if (!isRelationship(rel)) continue;\n const stored = relationshipFromRelationship(rel);\n if (!raw.relationshipMap.has(stored.id)) raw.relationshipMap.set(stored.id, stored);\n pathRelIds.push(stored.id);\n\n // Determine walk direction at this segment by aligning the relationship's\n // stored topology with the path's node ordering. The node at index `i`\n // is the source of segment `i`; if the relationship's stored start\n // matches that node, the walk crossed source → target (`'out'`),\n // otherwise it crossed target → source (`'in'`).\n const segmentStartNode = pathNodes[i];\n const segmentStartElementId = isNode(segmentStartNode)\n ? segmentStartNode.elementId\n : previousElementId;\n const direction: 'out' | 'in' =\n segmentStartElementId !== undefined &&\n rel.startNodeElementId === segmentStartElementId\n ? 'out'\n : 'in';\n pathRelDirections.push(direction);\n if (!raw.pathRelFirstDirection.has(stored.id)) {\n raw.pathRelFirstDirection.set(stored.id, direction);\n }\n const segmentEndNode = pathNodes[i + 1];\n if (isNode(segmentEndNode)) previousElementId = segmentEndNode.elementId;\n }\n\n raw.pathRows.push({\n entityIds: pathEntityIds,\n relationshipIds: pathRelIds,\n relationshipDirections: pathRelDirections,\n });\n }\n }\n}\n\nfunction isNode(value: unknown): value is NodeLike {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as { elementId?: unknown; labels?: unknown; properties?: unknown };\n return (\n typeof v.elementId === 'string' &&\n Array.isArray(v.labels) &&\n typeof v.properties === 'object' &&\n v.properties !== null\n );\n}\n\nfunction isRelationship(value: unknown): value is RelationshipLike {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as {\n elementId?: unknown;\n type?: unknown;\n startNodeElementId?: unknown;\n endNodeElementId?: unknown;\n properties?: unknown;\n };\n return (\n typeof v.elementId === 'string' &&\n typeof v.type === 'string' &&\n typeof v.startNodeElementId === 'string' &&\n typeof v.endNodeElementId === 'string' &&\n typeof v.properties === 'object' &&\n v.properties !== null\n );\n}\n\nfunction entityFromNode(node: NodeLike): StoredEntity {\n return entityFromProperties(node.properties);\n}\n\nfunction relationshipFromRelationship(rel: RelationshipLike): StoredRelationship {\n // The vocabulary relationship type is stored on the edge's properties bag\n // — `r.relationshipType` matches the slug; the Cypher edge label\n // (`type(r)`) is the upper-cased Cypher identifier the compiler emitted.\n // The property is authoritative because it round-trips verbatim across\n // CRUD; the Cypher label is consumed only by Cypher pattern matching.\n return relationshipFromProperties(rel.properties);\n}\n\nfunction bigintLike(value: unknown): number {\n if (typeof value === 'bigint') return Number(value);\n if (typeof value === 'number') return value;\n return 0;\n}\n\n/**\n * Normalise a server-projected scalar to a JSON-wire shape. Neo4j's driver\n * returns INTEGER values as BigInt (config-wide `useBigInt: true` keeps every\n * other integer round-trip exact); coerce those to Number so projection\n * `values` survive `JSON.stringify`. Other scalars / strings / booleans /\n * null pass through.\n */\nfunction normaliseScalar(value: unknown): unknown {\n if (typeof value === 'bigint') return Number(value);\n return value;\n}\n\nconst SKIP_LIMIT_PARAM_PATTERN = /\\b(?:SKIP|LIMIT)\\s+\\$(\\w+)/gi;\n\nfunction coerceSkipLimitToBigInt(\n cypher: string,\n params: Record<string, unknown>,\n): Record<string, unknown> {\n const targetKeys = new Set<string>();\n for (const match of cypher.matchAll(SKIP_LIMIT_PARAM_PATTERN)) {\n if (match[1] !== undefined) targetKeys.add(match[1]);\n }\n if (targetKeys.size === 0) return params;\n const out: Record<string, unknown> = { ...params };\n for (const key of targetKeys) {\n const value = out[key];\n if (typeof value === 'number' && Number.isFinite(value)) {\n out[key] = BigInt(Math.trunc(value));\n }\n }\n return out;\n}\n\nfunction summariseProfile(plan: PlanLike): { totalDbHits: number; rootOperator: string } {\n let total = 0;\n const visit = (node: PlanLike | undefined): void => {\n if (node === undefined) return;\n const hits = node.dbHits;\n if (typeof hits === 'bigint') total += Number(hits);\n else if (typeof hits === 'number') total += hits;\n for (const child of node.children ?? []) visit(child);\n };\n visit(plan);\n return { totalDbHits: total, rootOperator: plan.operatorType ?? 'unknown' };\n}\n\n","// Mapping — Neo4j driver records → StoredEntity / StoredRelationship.\n//\n// The mapper is the boundary that:\n// - Coerces `BigInt` integer values to safe `number` (per D6b — useBigInt is\n// enabled on the driver to avoid silent precision loss).\n// - Reads from either form of result row:\n// a) `RETURN n` — the alias resolves to a `Node` with a `.properties` map.\n// b) explicit projection (`RETURN n.id AS id, ...`) — the record carries\n// one entry per projected field.\n// - Treats the JSON-stringified `properties` blob as the source of truth for\n// `StoredEntity.properties` per the O1 resolution. Per-scalar properties\n// exist for query-time predicates only.\n//\n// This file deliberately does NOT import `neo4j-driver` — the driver chokepoint\n// lives in `Neo4jConnection`. The minimal `DriverRecord` and node-like checks\n// here are structurally compatible with `neo4j-driver`'s public types without\n// pulling them in.\n\nimport { ProviderError } from '@utaba/deep-memory';\nimport type { StoredEntity } from '@utaba/deep-memory/types';\nimport type { StoredRelationship } from '@utaba/deep-memory/types';\nimport type { Provenance } from '@utaba/deep-memory/types';\nimport type {\n GovernanceConfig,\n RepositoryMetadata,\n StorageRepositoryConfig,\n StoredRepository,\n StoredRepositorySummary,\n} from '@utaba/deep-memory/types';\nimport type { VocabularyChangeRecord } from '@utaba/deep-memory/types';\n\nconst MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);\nconst MIN_SAFE_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);\n\n/**\n * Minimal structural shape compatible with `neo4j-driver`'s `Record` class.\n * Keeping the type local lets the mapper compile without importing the driver,\n * which the isolation grep test forbids outside `Neo4jConnection.ts`.\n *\n * `keys` is `PropertyKey[]` rather than `string[]` because the driver's\n * `Record<T extends RecordShape>` types `keys` as `(keyof T)[]`, which TS\n * widens to `PropertyKey`. All of our projections use string-keyed Cypher\n * aliases, so the helpers filter to string keys at consumption time.\n */\nexport interface DriverRecord {\n keys: ReadonlyArray<PropertyKey>;\n get(key: string): unknown;\n}\n\n/**\n * Convert a `bigint` or `number` to a safe `number`, throwing `ProviderError`\n * when the value cannot be represented without precision loss. Used at every\n * public-API seam where a count, total, or server-time figure exits the\n * provider.\n *\n * The probe P2 results (local-tests/baseline/neo4j-phase3-probes-results.md)\n * confirm that `count(n)` aggregations and `summary.resultConsumedAfter` are\n * the only fields the boundary needs to coerce — scalar string / array\n * properties round-trip without `BigInt` wrapping.\n */\nexport function bigintToSafeNumber(value: unknown): number {\n if (typeof value === 'number') return value;\n if (typeof value === 'bigint') {\n if (value > MAX_SAFE_BIGINT || value < MIN_SAFE_BIGINT) {\n throw new ProviderError(\n `Neo4j integer value ${value.toString()} exceeds Number.MAX_SAFE_INTEGER — refusing to truncate.`,\n );\n }\n return Number(value);\n }\n throw new ProviderError(\n `bigintToSafeNumber expected bigint | number, received ${typeof value}.`,\n );\n}\n\n/**\n * Map a driver record to a `StoredEntity`. Accepts both `RETURN n` (alias\n * resolves to a `Node` with `.properties`) and explicit projection forms.\n *\n * `alias` defaults to `'n'`; pass an explicit alias if the query returned the\n * entity under a different name (e.g. `'node'` for the fulltext-index branch).\n */\nexport function entityFromRecord(record: DriverRecord, alias = 'n'): StoredEntity {\n return entityFromProperties(extractPropertyBag(record, alias));\n}\n\n/**\n * Map a driver record to a `StoredRelationship`. Accepts both `RETURN r` and\n * explicit projection forms. `alias` defaults to `'r'`.\n */\nexport function relationshipFromRecord(record: DriverRecord, alias = 'r'): StoredRelationship {\n return relationshipFromProperties(extractPropertyBag(record, alias));\n}\n\n/**\n * Map a flat property bag (i.e. the `properties` map of a Neo4j `Node`, or the\n * union of an explicit projection) to a `StoredEntity`.\n *\n * Exposed for tests and for callers that already have a property bag in hand\n * (e.g. when iterating `UNWIND` result rows).\n */\nexport function entityFromProperties(props: Record<string, unknown>): StoredEntity {\n const summary = optionalString(props, 'summary');\n const data = optionalString(props, 'data');\n const dataFormat = optionalString(props, 'dataFormat');\n const embedding = parseEmbedding(props);\n const entity: StoredEntity = {\n id: requireString(props, 'id'),\n slug: requireString(props, 'slug'),\n entityType: requireString(props, 'entityType'),\n label: requireString(props, 'label'),\n properties: parsePropertiesBlob(props),\n provenance: provenanceFromProperties(props),\n };\n if (summary !== undefined) entity.summary = summary;\n if (data !== undefined) entity.data = data;\n if (dataFormat !== undefined) entity.dataFormat = dataFormat;\n if (embedding !== undefined) entity.embedding = embedding;\n return entity;\n}\n\n/**\n * Map a flat property bag to a `StoredRelationship`. Mirror of\n * `entityFromProperties` for relationships.\n */\nexport function relationshipFromProperties(\n props: Record<string, unknown>,\n): StoredRelationship {\n return {\n id: requireString(props, 'id'),\n relationshipType: requireString(props, 'relationshipType'),\n sourceEntityId: requireString(props, 'sourceEntityId'),\n targetEntityId: requireString(props, 'targetEntityId'),\n properties: parsePropertiesBlob(props),\n bidirectional: requireBoolean(props, 'bidirectional'),\n provenance: provenanceFromProperties(props),\n };\n}\n\n/**\n * Map a driver record to a `StoredRepository`. Accepts both `RETURN r` (alias\n * resolves to a `Node` with `.properties`) and explicit projection forms.\n *\n * `governanceConfig` and `metadata` are stored as JSON strings on the node;\n * read paths parse them back here so the public surface always sees the live\n * object shape. Optional string fields (`type`, `description`, `legal`,\n * `owner`) round-trip through `optionalString`.\n */\nexport function repositoryFromRecord(record: DriverRecord, alias = 'r'): StoredRepository {\n return repositoryFromProperties(extractPropertyBag(record, alias));\n}\n\n/**\n * Lighter projection used by `listRepositories` — omits `legal`, `owner`,\n * `metadata`, `createdAt`, `createdBy` per the `StoredRepositorySummary`\n * contract. Same JSON-parse-on-read for `governanceConfig`.\n */\nexport function repositorySummaryFromRecord(\n record: DriverRecord,\n alias = 'r',\n): StoredRepositorySummary {\n return repositorySummaryFromProperties(extractPropertyBag(record, alias));\n}\n\nexport function repositoryFromProperties(props: Record<string, unknown>): StoredRepository {\n const type = optionalString(props, 'type');\n const description = optionalString(props, 'description');\n const legal = optionalString(props, 'legal');\n const owner = optionalString(props, 'owner');\n const metadata = parseOptionalJsonObject<RepositoryMetadata>(props, 'metadata');\n const repo: StoredRepository = {\n repositoryId: requireString(props, 'repositoryId'),\n label: requireString(props, 'label'),\n governanceConfig: parseRequiredJsonObject<GovernanceConfig>(props, 'governanceConfig'),\n createdAt: requireString(props, 'createdAt'),\n createdBy: requireString(props, 'createdBy'),\n };\n if (type !== undefined) repo.type = type;\n if (description !== undefined) repo.description = description;\n if (legal !== undefined) repo.legal = legal;\n if (owner !== undefined) repo.owner = owner;\n if (metadata !== undefined) repo.metadata = metadata;\n return repo;\n}\n\nexport function repositorySummaryFromProperties(\n props: Record<string, unknown>,\n): StoredRepositorySummary {\n const type = optionalString(props, 'type');\n const description = optionalString(props, 'description');\n const summary: StoredRepositorySummary = {\n repositoryId: requireString(props, 'repositoryId'),\n label: requireString(props, 'label'),\n governanceConfig: parseRequiredJsonObject<GovernanceConfig>(props, 'governanceConfig'),\n };\n if (type !== undefined) summary.type = type;\n if (description !== undefined) summary.description = description;\n return summary;\n}\n\n/**\n * Map a driver record to a `VocabularyChangeRecord`. Accepts both\n * `RETURN e` (alias resolves to a `Node` with `.properties`) and explicit\n * projection forms; `alias` defaults to `'e'` to match the change-log query.\n *\n * The four optional traceability fields (`previousVersion`, `approvedBy`,\n * `approvedAt`, plus the user-supplied `reason`) round-trip via the same\n * `optionalString` / `requireString` helpers used elsewhere. The provenance\n * fields are intentionally NOT flattened into the surrounding provenance bag —\n * proposed/approved are a separate audit semantic from createdBy/modifiedBy.\n */\nexport function changeRecordFromRecord(\n record: DriverRecord,\n alias = 'e',\n): VocabularyChangeRecord {\n return changeRecordFromProperties(extractPropertyBag(record, alias));\n}\n\nexport function changeRecordFromProperties(\n props: Record<string, unknown>,\n): VocabularyChangeRecord {\n const previousVersion = optionalString(props, 'previousVersion');\n const approvedBy = optionalString(props, 'approvedBy');\n const approvedAt = optionalString(props, 'approvedAt');\n const record: VocabularyChangeRecord = {\n changeId: requireString(props, 'changeId'),\n changeType: requireChangeType(props, 'changeType'),\n typeName: requireString(props, 'typeName'),\n newVersion: requireString(props, 'newVersion'),\n proposedBy: requireString(props, 'proposedBy'),\n proposedAt: requireString(props, 'proposedAt'),\n reason: requireString(props, 'reason'),\n };\n if (previousVersion !== undefined) record.previousVersion = previousVersion;\n if (approvedBy !== undefined) record.approvedBy = approvedBy;\n if (approvedAt !== undefined) record.approvedAt = approvedAt;\n return record;\n}\n\n/**\n * Field list projected by entity read paths — `getEntity`, `getEntityBySlug`,\n * `getEntities`, `findEntities`, `updateEntity` (projection-on-write).\n *\n * The list deliberately excludes `embedding` and `repositoryId`:\n * - `embedding` is a heavy native `list<float>` carried only when callers\n * opt in via `EntityReadOptions.loadEmbeddings`. `buildEntityProjection`\n * appends it when requested.\n * - `repositoryId` is the scope discriminator, not a `StoredEntity` field.\n *\n * Keeping this list in lockstep with `entityFromProperties` is enforced by the\n * co-located mapping tests — adding a new field requires updating both.\n */\nexport const STORED_ENTITY_FIELDS = [\n 'id',\n 'entityType',\n 'label',\n 'slug',\n 'summary',\n 'properties',\n 'data',\n 'dataFormat',\n 'createdBy',\n 'createdByType',\n 'createdAt',\n 'createdInConversation',\n 'createdFromMessage',\n 'modifiedBy',\n 'modifiedByType',\n 'modifiedAt',\n 'modifiedInConversation',\n 'modifiedFromMessage',\n] as const;\n\n/**\n * Build a `RETURN` projection chain for an entity read. The result is a\n * comma-separated list of `n.<field> AS <field>` clauses that the caller\n * appends after `RETURN ` (or after `SET ... RETURN ` for projection-on-write).\n *\n * `embedding` is opt-in: pass `loadEmbeddings: true` to add `n.embedding AS\n * embedding` to the tail. `RETURN n.<field> AS <field>` after a `SET` ships\n * the post-SET state in the same round-trip, so `updateEntity` reuses this\n * projection for projection-on-write without a re-MATCH.\n *\n * `alias` defaults to `'n'`; the fulltext-index search branch uses `'node'`.\n */\nexport function buildEntityProjection(options?: {\n loadEmbeddings?: boolean;\n alias?: string;\n}): string {\n const alias = options?.alias ?? 'n';\n const parts = STORED_ENTITY_FIELDS.map((field) => `${alias}.${field} AS ${field}`);\n if (options?.loadEmbeddings === true) {\n parts.push(`${alias}.embedding AS embedding`);\n }\n return parts.join(', ');\n}\n\n/**\n * Schema-managed property names on `:_Entity` nodes. User-supplied\n * `entity.properties` keys cannot collide with these — colliding would clobber\n * a schema-managed scalar via `SET n += $userProperties` and break round-trip.\n *\n * Kept as a frozen `Set` for O(1) membership checks on the write hot path.\n */\nexport const RESERVED_ENTITY_PROPERTY_KEYS: ReadonlySet<string> = new Set([\n 'id',\n 'repositoryId',\n 'entityType',\n 'label',\n 'slug',\n 'summary',\n 'properties',\n 'data',\n 'dataFormat',\n 'embedding',\n 'createdBy',\n 'createdByType',\n 'createdAt',\n 'createdInConversation',\n 'createdFromMessage',\n 'modifiedBy',\n 'modifiedByType',\n 'modifiedAt',\n 'modifiedInConversation',\n 'modifiedFromMessage',\n]);\n\n/**\n * User-supplied property keys are interpolated into the Cypher string at\n * REMOVE-time (Cypher 25 cannot REMOVE a property whose key is bound at\n * run-time) and used in the predicate slot of `findEntities`. Restrict them\n * to the bare Cypher identifier shape so the interpolation cannot widen the\n * injection surface — same guard `assertSafeRelationshipType` applies to the\n * relationship-type slot.\n */\nconst USER_PROPERTY_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Validate a user-supplied entity property key. Throws `ProviderError` when\n * the key is not a bare Cypher identifier or collides with a schema-managed\n * field name. Both checks run on every write and every predicate emission —\n * the cost is one regex test plus one set lookup per key per call.\n */\nexport function assertSafeUserPropertyKey(key: string): string {\n if (!USER_PROPERTY_KEY_PATTERN.test(key)) {\n throw new ProviderError(\n `Entity property key \"${key}\" is not a valid Cypher identifier — must match ` +\n `${USER_PROPERTY_KEY_PATTERN.source}. User-property keys are interpolated into ` +\n `Cypher REMOVE / predicate slots (the key slot cannot be parameterised), so an ` +\n `unsafe value would widen the injection surface.`,\n );\n }\n if (RESERVED_ENTITY_PROPERTY_KEYS.has(key)) {\n throw new ProviderError(\n `Entity property key \"${key}\" collides with a schema-managed field. ` +\n `Reserved names: ${Array.from(RESERVED_ENTITY_PROPERTY_KEYS).join(', ')}.`,\n );\n }\n return key;\n}\n\n/**\n * Decide whether a value can be stored as a native Neo4j scalar property.\n * Native scalars are the predicate-queryable surface; non-storable values\n * (nested objects, `null`, heterogeneous arrays, arrays of objects) live only\n * inside the JSON-stringified `properties` blob and are NOT predicate-queryable.\n *\n * The set covers what Cypher's literal property syntax accepts uniformly:\n * `string`, finite `number`, `boolean`, and homogeneous arrays of those. The\n * driver passes these through Bolt without wrapping; everything else either\n * fails Bolt encoding or coerces to a shape Cypher cannot index.\n */\nexport function isNativeStorableValue(value: unknown): boolean {\n if (typeof value === 'string') return true;\n if (typeof value === 'boolean') return true;\n if (typeof value === 'number') return Number.isFinite(value);\n if (Array.isArray(value)) {\n if (value.length === 0) return true;\n const first = value[0];\n const t = typeof first;\n if (t !== 'string' && t !== 'boolean' && (t !== 'number' || !Number.isFinite(first))) {\n return false;\n }\n for (const v of value) {\n if (typeof v !== t) return false;\n if (t === 'number' && !Number.isFinite(v as number)) return false;\n }\n return true;\n }\n return false;\n}\n\n/**\n * Build the parameter map for the schema-managed slots of the fixed-shape\n * entity `CREATE` template. Every schema field gets a binding on every call so\n * the planner reuses one cached plan across all entity creates (D15 — plan\n * cache friendliness).\n *\n * User-supplied `entity.properties` are NOT in this bag — they bind through\n * the separate `entityUserPropertyParams` helper and feed the\n * `SET n += $userProperties` clause that runs alongside the fixed CREATE. The\n * split keeps the CREATE plan-cache-keyed on a single Cypher string while\n * letting user-property keys live as native predicate-queryable scalars on\n * the node in addition to the JSON blob.\n *\n * Optional fields bind as `null`. Neo4j drops null properties on write —\n * symmetric with the read mapping where absent properties round-trip as\n * `undefined` via `optionalString`. The umbrella `:_Entity` label is the only\n * label written on the node; writing a per-type label as well would add\n * cold-compile and steady-state overhead with no offsetting benefit, since\n * every provider read filters via the indexed `n.entityType` property.\n *\n * `embedding` is bound as the native `number[]` — the Neo4j JS driver maps\n * `LIST<FLOAT>` directly to a JS `Array` with no wrapping, so no\n * JSON-stringify step — or `null` when absent.\n */\nexport function entityToParams(entity: StoredEntity): Record<string, unknown> {\n const p = entity.provenance;\n return {\n id: entity.id,\n entityType: entity.entityType,\n label: entity.label,\n slug: entity.slug,\n summary: entity.summary ?? null,\n properties: JSON.stringify(entity.properties),\n data: entity.data ?? null,\n dataFormat: entity.dataFormat ?? null,\n embedding: entity.embedding ?? null,\n createdBy: p.createdBy,\n createdByType: p.createdByType,\n createdAt: p.createdAt,\n createdInConversation: p.createdInConversation ?? null,\n createdFromMessage: p.createdFromMessage ?? null,\n modifiedBy: p.modifiedBy,\n modifiedByType: p.modifiedByType,\n modifiedAt: p.modifiedAt,\n modifiedInConversation: p.modifiedInConversation ?? null,\n modifiedFromMessage: p.modifiedFromMessage ?? null,\n };\n}\n\n/**\n * Project `entity.properties` to the native-scalar map fed to\n * `SET n += $userProperties`. Validates every key (no reserved-name\n * collision, identifier shape) and silently drops values that Neo4j cannot\n * store natively — those live only inside the JSON blob. The blob remains\n * authoritative for `entity.properties` round-trip via `entityFromProperties`,\n * so dropping a value here loses only its predicate-queryable shape, not its\n * read shape.\n *\n * The return is the bare map that binds to `$userProperties`; callers append\n * `SET n += $userProperties` to the CREATE / UPDATE template and bind this\n * value verbatim. An empty map is the no-op shape (`SET n += {}` is legal\n * Cypher and resets nothing), so the caller can emit the SET unconditionally.\n */\nexport function entityUserPropertyParams(\n properties: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(properties)) {\n assertSafeUserPropertyKey(key);\n if (isNativeStorableValue(value)) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Field list projected by relationship read paths. Mirror of\n * `STORED_ENTITY_FIELDS` for `StoredRelationship` — `repositoryId` is the\n * scope discriminator, not a public field, so it is intentionally excluded\n * from the projection.\n *\n * Keeping this list in lockstep with `relationshipFromProperties` is enforced\n * by the co-located mapping tests — adding a new field requires updating both.\n */\nexport const STORED_RELATIONSHIP_FIELDS = [\n 'id',\n 'relationshipType',\n 'sourceEntityId',\n 'targetEntityId',\n 'properties',\n 'bidirectional',\n 'createdBy',\n 'createdByType',\n 'createdAt',\n 'createdInConversation',\n 'createdFromMessage',\n 'modifiedBy',\n 'modifiedByType',\n 'modifiedAt',\n 'modifiedInConversation',\n 'modifiedFromMessage',\n] as const;\n\n/**\n * Build a `RETURN` projection chain for a relationship read. The result is a\n * comma-separated list of `r.<field> AS <field>` clauses; the caller appends\n * after `RETURN ` (or after UNION ALL branches that all project the same\n * shape).\n *\n * `alias` defaults to `'r'`. Each UNION branch must use the same alias and\n * the same field-name aliases to keep the union column-compatible.\n */\nexport function buildRelationshipProjection(options?: { alias?: string }): string {\n const alias = options?.alias ?? 'r';\n return STORED_RELATIONSHIP_FIELDS.map((field) => `${alias}.${field} AS ${field}`).join(', ');\n}\n\n/**\n * Build the parameter map for the fixed-shape relationship `CREATE` template.\n * Every field gets a binding on every call so the planner reuses one cached\n * plan per relationship type (D15) — the type slot is interpolated into the\n * Cypher string at compile time (the type slot cannot be parameterised in\n * Cypher 25), so the plan cache holds one entry per distinct vocabulary type.\n *\n * Optional fields bind as `null`. Neo4j drops null properties on write, so\n * absent fields are symmetric with their read-side `undefined`.\n *\n * `properties` is JSON-stringified into a single Neo4j property — relationship\n * properties are not indexed and are not predicate-queried server-side\n * (per O1/D6: indexed scalars live on entities only), so the blob round-trip\n * is sufficient.\n */\nexport function relationshipToParams(rel: StoredRelationship): Record<string, unknown> {\n const p = rel.provenance;\n return {\n id: rel.id,\n relationshipType: rel.relationshipType,\n sourceEntityId: rel.sourceEntityId,\n targetEntityId: rel.targetEntityId,\n properties: JSON.stringify(rel.properties ?? {}),\n bidirectional: rel.bidirectional,\n createdBy: p.createdBy,\n createdByType: p.createdByType,\n createdAt: p.createdAt,\n createdInConversation: p.createdInConversation ?? null,\n createdFromMessage: p.createdFromMessage ?? null,\n modifiedBy: p.modifiedBy,\n modifiedByType: p.modifiedByType,\n modifiedAt: p.modifiedAt,\n modifiedInConversation: p.modifiedInConversation ?? null,\n modifiedFromMessage: p.modifiedFromMessage ?? null,\n };\n}\n\n/**\n * Cypher 25 forbids parameterising the relationship-type slot (Cypher\n * identifier, not a value), so the type slug is concatenated directly into\n * the query string at compile time. This guard pins the value to the bare\n * identifier shape Cypher accepts unquoted, which is also the shape vocabulary\n * slugs already emit (UPPER_SNAKE_CASE per D5).\n *\n * Anything failing the guard is a programming error in the caller — either a\n * vocabulary value that bypassed slug normalisation, or a value handed in\n * directly without going through the relationship-create surface. Surfacing\n * it as `ProviderError` catches injection-shaped values at the chokepoint.\n */\nconst RELATIONSHIP_TYPE_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport function assertSafeRelationshipType(value: string): string {\n if (!RELATIONSHIP_TYPE_PATTERN.test(value)) {\n throw new ProviderError(\n `Relationship type \"${value}\" is not a valid Cypher identifier — must match ` +\n `${RELATIONSHIP_TYPE_PATTERN.source}. Cypher 25 does not allow parameterising ` +\n `the relationship-type slot, so the value is interpolated directly into the ` +\n `query string and an unsafe value would otherwise widen the injection surface.`,\n );\n }\n return value;\n}\n\n/**\n * Build the parameter map for `createRepository`'s fixed-shape `CREATE` Cypher.\n * Optional fields become `null` (Neo4j drops null properties on write, so the\n * resulting node has no property by that name — symmetric with read where\n * absent properties map to `undefined`).\n */\nexport function repositoryCreateParams(\n config: StorageRepositoryConfig,\n): Record<string, unknown> {\n return {\n type: config.type ?? null,\n label: config.label,\n description: config.description ?? null,\n legal: config.legal ?? null,\n owner: config.owner ?? null,\n governanceConfig: JSON.stringify(config.governanceConfig),\n metadata: config.metadata !== undefined ? JSON.stringify(config.metadata) : null,\n createdAt: config.createdAt,\n createdBy: config.createdBy,\n };\n}\n\n// ─── Internal helpers ────────────────────────────────────────────────\n\nfunction extractPropertyBag(record: DriverRecord, alias: string): Record<string, unknown> {\n if (record.keys.includes(alias)) {\n const value = record.get(alias);\n if (isNodeLike(value)) return value.properties;\n if (isRelationshipLike(value)) return value.properties;\n }\n const bag: Record<string, unknown> = {};\n for (const key of record.keys) {\n if (typeof key !== 'string') continue;\n bag[key] = record.get(key);\n }\n return bag;\n}\n\nfunction isNodeLike(value: unknown): value is { properties: Record<string, unknown> } {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as { properties?: unknown; labels?: unknown };\n return (\n typeof v.properties === 'object' &&\n v.properties !== null &&\n !Array.isArray(v.properties) &&\n Array.isArray(v.labels)\n );\n}\n\nfunction isRelationshipLike(\n value: unknown,\n): value is { properties: Record<string, unknown> } {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as { properties?: unknown; type?: unknown };\n return (\n typeof v.properties === 'object' &&\n v.properties !== null &&\n !Array.isArray(v.properties) &&\n typeof v.type === 'string'\n );\n}\n\nfunction provenanceFromProperties(props: Record<string, unknown>): Provenance {\n const provenance: Provenance = {\n createdBy: requireString(props, 'createdBy'),\n createdByType: requireActorType(props, 'createdByType'),\n createdAt: requireString(props, 'createdAt'),\n modifiedBy: requireString(props, 'modifiedBy'),\n modifiedByType: requireActorType(props, 'modifiedByType'),\n modifiedAt: requireString(props, 'modifiedAt'),\n };\n const createdInConversation = optionalString(props, 'createdInConversation');\n const createdFromMessage = optionalString(props, 'createdFromMessage');\n const modifiedInConversation = optionalString(props, 'modifiedInConversation');\n const modifiedFromMessage = optionalString(props, 'modifiedFromMessage');\n if (createdInConversation !== undefined) provenance.createdInConversation = createdInConversation;\n if (createdFromMessage !== undefined) provenance.createdFromMessage = createdFromMessage;\n if (modifiedInConversation !== undefined) provenance.modifiedInConversation = modifiedInConversation;\n if (modifiedFromMessage !== undefined) provenance.modifiedFromMessage = modifiedFromMessage;\n return provenance;\n}\n\nfunction requireString(props: Record<string, unknown>, key: string): string {\n const value = props[key];\n if (typeof value !== 'string') {\n throw new ProviderError(\n `Neo4j record is missing required string field \"${key}\" (got ${describeType(value)}).`,\n );\n }\n return value;\n}\n\nfunction requireActorType(props: Record<string, unknown>, key: string): 'user' | 'agent' {\n const value = requireString(props, key);\n if (value !== 'user' && value !== 'agent') {\n throw new ProviderError(\n `Neo4j record field \"${key}\" must be \"user\" or \"agent\" (got ${JSON.stringify(value)}).`,\n );\n }\n return value;\n}\n\nconst CHANGE_TYPES = [\n 'entity_type_added',\n 'relationship_type_added',\n 'entity_type_modified',\n 'relationship_type_modified',\n 'entity_type_removed',\n 'relationship_type_removed',\n] as const satisfies ReadonlyArray<VocabularyChangeRecord['changeType']>;\n\nfunction requireChangeType(\n props: Record<string, unknown>,\n key: string,\n): VocabularyChangeRecord['changeType'] {\n const value = requireString(props, key);\n if (!(CHANGE_TYPES as readonly string[]).includes(value)) {\n throw new ProviderError(\n `Neo4j record field \"${key}\" must be one of the VocabularyChangeRecord change types ` +\n `(got ${JSON.stringify(value)}).`,\n );\n }\n return value as VocabularyChangeRecord['changeType'];\n}\n\nfunction requireBoolean(props: Record<string, unknown>, key: string): boolean {\n const value = props[key];\n if (typeof value !== 'boolean') {\n throw new ProviderError(\n `Neo4j record is missing required boolean field \"${key}\" (got ${describeType(value)}).`,\n );\n }\n return value;\n}\n\nfunction optionalString(props: Record<string, unknown>, key: string): string | undefined {\n const value = props[key];\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value !== 'string') {\n throw new ProviderError(\n `Neo4j record field \"${key}\" must be a string or empty (got ${describeType(value)}).`,\n );\n }\n return value;\n}\n\nfunction parsePropertiesBlob(props: Record<string, unknown>): Record<string, unknown> {\n const raw = props['properties'];\n if (raw === undefined || raw === null || raw === '') return {};\n if (typeof raw !== 'string') {\n throw new ProviderError(\n `Neo4j record field \"properties\" must be a JSON string (got ${describeType(raw)}).`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new ProviderError(`Neo4j record field \"properties\" is not valid JSON: ${detail}.`);\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new ProviderError(\n `Neo4j record field \"properties\" must decode to a JSON object (got ${describeType(parsed)}).`,\n );\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction parseRequiredJsonObject<T>(props: Record<string, unknown>, key: string): T {\n const raw = props[key];\n if (typeof raw !== 'string' || raw === '') {\n throw new ProviderError(\n `Neo4j record field \"${key}\" must be a non-empty JSON string (got ${describeType(raw)}).`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new ProviderError(`Neo4j record field \"${key}\" is not valid JSON: ${detail}.`);\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new ProviderError(\n `Neo4j record field \"${key}\" must decode to a JSON object (got ${describeType(parsed)}).`,\n );\n }\n return parsed as T;\n}\n\nfunction parseOptionalJsonObject<T>(props: Record<string, unknown>, key: string): T | undefined {\n const raw = props[key];\n if (raw === undefined || raw === null || raw === '') return undefined;\n if (typeof raw !== 'string') {\n throw new ProviderError(\n `Neo4j record field \"${key}\" must be a JSON string (got ${describeType(raw)}).`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new ProviderError(`Neo4j record field \"${key}\" is not valid JSON: ${detail}.`);\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new ProviderError(\n `Neo4j record field \"${key}\" must decode to a JSON object (got ${describeType(parsed)}).`,\n );\n }\n return parsed as T;\n}\n\nfunction parseEmbedding(props: Record<string, unknown>): number[] | undefined {\n const raw = props['embedding'];\n if (raw === undefined || raw === null) return undefined;\n if (!Array.isArray(raw)) {\n throw new ProviderError(\n `Neo4j record field \"embedding\" must be an array of numbers (got ${describeType(raw)}).`,\n );\n }\n for (const v of raw) {\n if (typeof v !== 'number') {\n throw new ProviderError(\n `Neo4j record field \"embedding\" must contain only numbers (got ${describeType(v)}).`,\n );\n }\n }\n return raw as number[];\n}\n\nfunction describeType(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n","// Neo4jConnection — the single chokepoint for every Bolt round-trip in this\n// provider. No other source file under src/ is allowed to import `neo4j-driver`\n// directly or call `driver.session()` / `driver.executeQuery()` — see D3b\n// (Isolation guarantee) in plans/neo4j-provider.md and the grep test that\n// enforces it.\n\nimport neo4j from 'neo4j-driver';\nimport type {\n Driver,\n EagerResult,\n ManagedTransaction,\n RecordShape,\n ResultSummary,\n RoutingControl,\n ServerInfo,\n} from 'neo4j-driver';\nimport { ProviderError } from '@utaba/deep-memory';\nimport { recordRoundTrip } from './usageScope.js';\n\n/**\n * Cypher parameters are arbitrary Bolt-encodable values keyed by name.\n * TypeScript cannot statically validate the shape against Cypher's runtime\n * contract, so the boundary type is intentionally `unknown` — narrower\n * \"real types\" only exist call-site by call-site.\n */\nexport type CypherParams = Record<string, unknown>;\n\n/** Connection configuration. */\nexport interface Neo4jConnectionConfig {\n /** Bolt URI, e.g. `bolt://localhost:7687` or `neo4j+s://aura-host`. */\n uri: string;\n /** Username for basic auth. */\n username: string;\n /** Password for basic auth. */\n password: string;\n /**\n * Database name. Per the driver manual, this should be specified explicitly\n * even on single-database Community instances. Defaults to `'neo4j'`.\n */\n database?: string;\n /** User-agent string sent on the Bolt handshake. */\n userAgent?: string;\n /** Maximum time (ms) the driver will retry a managed transaction. */\n maxTransactionRetryTime?: number;\n}\n\n/** Options for `executeQuery` — required `repositoryId` enforces D3b layer 2. */\nexport interface ExecuteQueryOptions {\n repositoryId: string;\n routing?: RoutingControl;\n}\n\n/** Options for `executeSystemQuery` — the explicit cross-repository allowlist. */\nexport interface ExecuteSystemQueryOptions {\n /** Must be `true`. Forces every caller to spell out the elevation. */\n crossRepository: true;\n routing?: RoutingControl;\n}\n\nconst DEFAULT_DATABASE = 'neo4j';\nconst DEFAULT_USER_AGENT = '@utaba/deep-memory-storage-neo4j';\nconst RID_TOKEN_PATTERN = /\\$rid\\b/;\nconst NOTIFICATION_QUERY_TRUNCATION = 200;\n\n/**\n * Wrapper around the official `neo4j-driver`. One instance per provider —\n * driver creation is expensive and the driver itself is documented as\n * \"immutable, thread-safe\" and meant to be shared. The instance is `private`\n * on the provider; the only legitimate entry points are the helpers below.\n */\nexport class Neo4jConnection {\n private readonly driver: Driver;\n private readonly database: string;\n\n constructor(config: Neo4jConnectionConfig) {\n this.database = config.database ?? DEFAULT_DATABASE;\n this.driver = neo4j.driver(\n config.uri,\n neo4j.auth.basic(config.username, config.password),\n {\n // BigInt for INTEGER avoids silent precision loss on counts that may\n // exceed Number.MAX_SAFE_INTEGER — see D6b. The mapping layer\n // narrows BigInt → Number at the public-API boundary.\n useBigInt: true,\n userAgent: config.userAgent ?? DEFAULT_USER_AGENT,\n ...(config.maxTransactionRetryTime !== undefined\n ? { maxTransactionRetryTime: config.maxTransactionRetryTime }\n : {}),\n },\n );\n }\n\n /** Verify the driver can reach the server. Throws on failure. */\n public async verifyConnectivity(): Promise<void> {\n await this.driver.verifyConnectivity({ database: this.database });\n }\n\n /** Returns server version / edition metadata for diagnostics. */\n public async getServerInfo(): Promise<ServerInfo> {\n return this.driver.getServerInfo({ database: this.database });\n }\n\n /** Close the driver and release all connections. */\n public async close(): Promise<void> {\n await this.driver.close();\n }\n\n /**\n * Run a single-statement query scoped to a repository.\n *\n * Required `repositoryId` binds `$rid` for the caller. The Cypher string\n * MUST reference `$rid` somewhere in a predicate or property map; absence\n * throws `ProviderError` as a programming error (D3b layer 2).\n */\n public async executeQuery<T extends RecordShape = RecordShape>(\n cypher: string,\n params: CypherParams,\n options: ExecuteQueryOptions,\n ): Promise<EagerResult<T>> {\n this.assertRepositoryId(options.repositoryId);\n this.assertScoped(cypher);\n const result = await this.driver.executeQuery<EagerResult<T>>(\n cypher,\n { ...params, rid: options.repositoryId },\n {\n database: this.database,\n ...(options.routing !== undefined ? { routing: options.routing } : {}),\n },\n );\n recordRoundTrip(result.summary, result.records.length);\n this.surfaceNotifications(cypher, result.summary);\n return result;\n }\n\n /**\n * Run a managed write transaction scoped to a repository. Use only when\n * more than one Cypher statement must commit atomically — single-statement\n * writes go through `executeQuery` so the driver handles transient-error\n * retry uniformly (D2b).\n *\n * Every `tx.run` call inside `txFn` is wrapped to inject `$rid` and assert\n * scope, so the same isolation enforcement applies as on the default path.\n * The transaction function MUST be idempotent (the driver retries on\n * transient errors) and MUST NOT return the raw `Result` — process records\n * inside the function and return mapped data.\n */\n public async executeWrite<T>(\n repositoryId: string,\n txFn: (tx: ScopedTransaction) => Promise<T>,\n ): Promise<T> {\n return this.runManaged('write', repositoryId, txFn);\n }\n\n /**\n * Run a managed read transaction scoped to a repository. Same isolation\n * contract as `executeWrite`.\n */\n public async executeRead<T>(\n repositoryId: string,\n txFn: (tx: ScopedTransaction) => Promise<T>,\n ): Promise<T> {\n return this.runManaged('read', repositoryId, txFn);\n }\n\n /**\n * Cross-repository escape hatch. Reserved for the small allowlist of\n * legitimate cross-repository operations: `ensureSchema` (DDL),\n * `listRepositories`, `_Meta` schema-version reads, server-info probes.\n * Does NOT inject `$rid`. Every call site MUST add a one-line comment\n * justifying why cross-repository access is correct (D3b).\n */\n public async executeSystemQuery<T extends RecordShape = RecordShape>(\n cypher: string,\n params: CypherParams,\n options: ExecuteSystemQueryOptions,\n ): Promise<EagerResult<T>> {\n if (options.crossRepository !== true) {\n throw new ProviderError(\n 'executeSystemQuery requires crossRepository: true — use executeQuery for repository-scoped Cypher.',\n );\n }\n const result = await this.driver.executeQuery<EagerResult<T>>(cypher, params, {\n database: this.database,\n ...(options.routing !== undefined ? { routing: options.routing } : {}),\n });\n recordRoundTrip(result.summary, result.records.length);\n this.surfaceNotifications(cypher, result.summary);\n return result;\n }\n\n /**\n * Run a single DDL statement on a session (auto-commit). Used for schema\n * setup — `CREATE CONSTRAINT` / `CREATE INDEX` statements cannot run inside\n * a managed transaction in Cypher 25 and must be issued one per call.\n * Cross-repository by definition.\n */\n public async executeSystemDdl(cypher: string): Promise<ResultSummary> {\n const session = this.driver.session({ database: this.database });\n try {\n const result = await session.run(cypher);\n recordRoundTrip(result.summary, result.records.length);\n this.surfaceNotifications(cypher, result.summary);\n return result.summary;\n } finally {\n await session.close();\n }\n }\n\n /**\n * Run a repository-scoped Cypher statement that uses `CALL ( ) { ... } IN\n * TRANSACTIONS OF $batchSize ROWS` semantics.\n *\n * `IN TRANSACTIONS` is only valid in implicit (auto-commit) transactions —\n * the managed `executeWrite` / `executeRead` helpers open an explicit\n * transaction internally and the server rejects the statement with\n * `Neo.DatabaseError.Transaction.TransactionStartFailed`. Probe P13\n * (local-tests/baseline/neo4j-call-in-transactions-results.md) captured the\n * exact failure mode and the empty-match / partial-batch behaviour the\n * chunked-wipe path relies on.\n *\n * Same `$rid` injection + scope assertion as `executeQuery`, so the chunked\n * delete still flows through the D3b layer 2 lifeline. Returns only the\n * summary because the chunked-wipe subquery yields no rows; counters live on\n * `summary.counters.updates()`.\n */\n public async executeImplicitInTransactions(\n cypher: string,\n params: CypherParams,\n options: { repositoryId: string },\n ): Promise<ResultSummary> {\n this.assertRepositoryId(options.repositoryId);\n this.assertScoped(cypher);\n const session = this.driver.session({ database: this.database });\n try {\n const result = await session.run(cypher, { ...params, rid: options.repositoryId });\n recordRoundTrip(result.summary, result.records.length);\n this.surfaceNotifications(cypher, result.summary);\n return result.summary;\n } finally {\n await session.close();\n }\n }\n\n private async runManaged<T>(\n mode: 'read' | 'write',\n repositoryId: string,\n txFn: (tx: ScopedTransaction) => Promise<T>,\n ): Promise<T> {\n this.assertRepositoryId(repositoryId);\n const session = this.driver.session({ database: this.database });\n try {\n const wrapped = (managed: ManagedTransaction): Promise<T> =>\n txFn(new ScopedTransaction(managed, repositoryId, this.surfaceNotifications.bind(this)));\n return mode === 'write'\n ? await session.executeWrite(wrapped)\n : await session.executeRead(wrapped);\n } finally {\n await session.close();\n }\n }\n\n private assertRepositoryId(repositoryId: string): void {\n if (typeof repositoryId !== 'string' || repositoryId.length === 0) {\n throw new ProviderError(\n 'Neo4jConnection: repositoryId is required for scoped queries (D3b isolation guarantee).',\n );\n }\n }\n\n private assertScoped(cypher: string): void {\n if (!RID_TOKEN_PATTERN.test(cypher)) {\n throw new ProviderError(\n 'Neo4jConnection: Cypher omits required $rid binding. ' +\n 'Repository-scoped queries MUST reference $rid in a predicate or property map. ' +\n 'Use executeSystemQuery for cross-repository calls (D3b allowlist).',\n );\n }\n }\n\n private surfaceNotifications(cypher: string, summary: ResultSummary): void {\n const items = collectNonInformationNotifications(summary);\n if (items.length === 0) return;\n const truncated =\n cypher.length > NOTIFICATION_QUERY_TRUNCATION\n ? `${cypher.slice(0, NOTIFICATION_QUERY_TRUNCATION)}…`\n : cypher;\n // One warn per emission keeps the signal scannable; the full notifications\n // array intentionally does NOT flow into the usage sink (D14).\n // eslint-disable-next-line no-console\n console.warn('[neo4j] notifications', { cypher: truncated, notifications: items });\n }\n}\n\n/**\n * `tx.run` wrapper used inside managed transactions. Carries the same\n * isolation enforcement as `Neo4jConnection.executeQuery` so transaction\n * functions cannot bypass the chokepoint.\n */\nexport class ScopedTransaction {\n constructor(\n private readonly tx: ManagedTransaction,\n private readonly repositoryId: string,\n private readonly notify: (cypher: string, summary: ResultSummary) => void,\n ) {}\n\n /**\n * Run a single statement inside the managed transaction. Injects `$rid`\n * and asserts the Cypher string references it.\n */\n public async run<T extends RecordShape = RecordShape>(\n cypher: string,\n params: CypherParams,\n ): Promise<{ records: Array<import('neo4j-driver').Record<T>>; summary: ResultSummary }> {\n if (!RID_TOKEN_PATTERN.test(cypher)) {\n throw new ProviderError(\n 'ScopedTransaction.run: Cypher omits required $rid binding. ' +\n 'Multi-statement writes still flow through the isolation chokepoint.',\n );\n }\n const queryResult = await this.tx.run<T>(cypher, { ...params, rid: this.repositoryId });\n const records = queryResult.records;\n const summary = queryResult.summary;\n recordRoundTrip(summary, records.length);\n this.notify(cypher, summary);\n return { records, summary };\n }\n}\n\ninterface CollectedNotification {\n severity: string;\n category: string;\n code: string;\n title: string;\n description: string;\n}\n\n/**\n * Surface real warnings from a `ResultSummary` without drowning callers in\n * the always-present \"successful completion\" sentinels.\n *\n * Per probe P15 (local-tests/baseline/neo4j-phase3-probes-results.md):\n *\n * - `summary.notifications` is the legacy server-side filtered surface; if it\n * is non-empty the server already decided the entries are worth flagging\n * (`UNRECOGNIZED`, `PERFORMANCE`, `DEPRECATION`, etc.). Prefer it.\n * - `summary.gqlStatusObjects` always includes a `severity=UNKNOWN\n * classification=UNKNOWN` \"successful completion\" entry, plus tags many\n * PERFORMANCE warnings (e.g. cartesian product) as `severity=INFORMATION`\n * which a naive filter on `severity !== 'INFORMATION'` would silently drop.\n *\n * Strategy: dedup the legacy `notifications` first; supplement with\n * `gqlStatusObjects` entries whose severity is `WARNING` or `ERROR` (the\n * post-Cypher-25 GQL severities worth raising).\n */\nfunction collectNonInformationNotifications(summary: ResultSummary): CollectedNotification[] {\n type LegacyLike = {\n severity?: string;\n category?: string;\n code?: string;\n title?: string;\n description?: string;\n };\n type GqlLike = {\n severity?: string;\n classification?: string;\n gqlStatus?: string;\n title?: string;\n description?: string;\n statusDescription?: string;\n };\n const summaryLike = summary as unknown as {\n gqlStatusObjects?: GqlLike[];\n notifications?: LegacyLike[];\n };\n const out: CollectedNotification[] = [];\n const seen = new Set<string>();\n for (const item of summaryLike.notifications ?? []) {\n const entry: CollectedNotification = {\n severity: item.severity ?? '',\n category: item.category ?? '',\n code: item.code ?? '',\n title: item.title ?? '',\n description: item.description ?? '',\n };\n const key = entry.code || entry.title;\n if (key && seen.has(key)) continue;\n if (key) seen.add(key);\n out.push(entry);\n }\n for (const item of summaryLike.gqlStatusObjects ?? []) {\n const severity = item.severity ?? '';\n if (severity !== 'WARNING' && severity !== 'ERROR') continue;\n const entry: CollectedNotification = {\n severity,\n category: item.classification ?? '',\n code: item.gqlStatus ?? '',\n title: item.title ?? '',\n description: item.description ?? item.statusDescription ?? '',\n };\n const key = entry.code || entry.title || entry.description;\n if (key && seen.has(key)) continue;\n if (key) seen.add(key);\n out.push(entry);\n }\n return out;\n}\n","// Per-operation usage scope — the bridge between the chokepoint\n// (`Neo4jConnection`, which observes each round-trip's `ResultSummary`) and\n// the Proxy on `Neo4jStorageProvider` (which emits one `OperationUsage`\n// record per public method call).\n//\n// Threaded through async boundaries via `AsyncLocalStorage` so individual\n// `Neo4jConnection` methods do not need to thread a scope argument through\n// their public signature. The trade-off: scopes only carry across awaits\n// within the same async context, which is exactly how every CRUD method here\n// is structured.\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { bigintToSafeNumber } from './mapping.js';\n\n/**\n * Mutable bag accumulated as round-trips complete inside a single tracked\n * operation. The provider Proxy creates one of these, runs the method inside\n * `runInUsageScope`, then reads the bag to emit the sink record.\n */\nexport interface UsageScope {\n /** Number of round-trips inside the operation. */\n calls: number;\n /** Sum of records returned across every round-trip. */\n recordCount: number;\n /**\n * Sum of `summary.resultConsumedAfter` in milliseconds — the primary cost\n * signal exposed as `OperationUsage.value` with `unit: 'server_ms'`.\n */\n serverMs: number;\n /**\n * Sum of `summary.resultAvailableAfter` in milliseconds. Surfaced under\n * `details` for operators who want to see how soon the first row arrived\n * vs the full stream.\n */\n availableAfterMs: number;\n /**\n * Aggregated `QueryStatistics` counters across every round-trip. Keys are\n * the canonical names emitted by the driver: `nodesCreated`,\n * `nodesDeleted`, `relationshipsCreated`, `relationshipsDeleted`,\n * `propertiesSet`, `labelsAdded`, `labelsRemoved`, `indexesAdded`,\n * `indexesRemoved`, `constraintsAdded`, `constraintsRemoved`.\n *\n * Probe P1 confirmed counters arrive as plain `number` under `useBigInt`,\n * so straight addition is safe.\n */\n counters: Record<string, number>;\n}\n\nconst COUNTER_KEYS = [\n 'nodesCreated',\n 'nodesDeleted',\n 'relationshipsCreated',\n 'relationshipsDeleted',\n 'propertiesSet',\n 'labelsAdded',\n 'labelsRemoved',\n 'indexesAdded',\n 'indexesRemoved',\n 'constraintsAdded',\n 'constraintsRemoved',\n] as const;\n\nconst store = new AsyncLocalStorage<UsageScope>();\n\n/** Create a fresh scope with zeroed accumulators. */\nexport function createUsageScope(): UsageScope {\n return {\n calls: 0,\n recordCount: 0,\n serverMs: 0,\n availableAfterMs: 0,\n counters: {},\n };\n}\n\n/**\n * Run `fn` with `scope` as the active usage scope. Round-trips executed via\n * `Neo4jConnection` while `fn` runs will write into the scope; reads via\n * `getCurrentUsageScope` outside the function return `undefined`.\n *\n * Mirrors `AsyncLocalStorage.run` directly — exposed as a thin wrapper so\n * callers don't need to import `node:async_hooks`.\n */\nexport function runInUsageScope<T>(scope: UsageScope, fn: () => T): T {\n return store.run(scope, fn);\n}\n\n/**\n * Look up the active scope, or `undefined` if none is set. The Connection\n * uses this to no-op when the caller did not arrange a scope (e.g. lifecycle\n * methods like `verifyConnectivity`).\n */\nexport function getCurrentUsageScope(): UsageScope | undefined {\n return store.getStore();\n}\n\n/**\n * Minimal shape this module needs from the driver's `ResultSummary` /\n * `QueryStatistics`. The two integer fields are widened to `unknown` because\n * the driver's static signature is `ResultSummary<Integer>` where `Integer`\n * is the legacy class; at runtime with `useBigInt: true` they are `BigInt`s.\n * `bigintToSafeNumber` performs the narrowing.\n *\n * Kept local so this file does not import `neo4j-driver` directly, preserving\n * the chokepoint enforcement (D3b layer 2).\n */\nexport interface RoundTripSummary {\n resultAvailableAfter?: unknown;\n resultConsumedAfter?: unknown;\n counters?: {\n updates(): Record<string, number>;\n };\n}\n\n/**\n * Add one round-trip's worth of cost to the active scope. Called by\n * `Neo4jConnection` after every successful query. Silently no-ops when no\n * scope is active.\n *\n * `recordCount` is the size of the returned record array — passed in\n * separately because `summary` does not carry it directly.\n */\nexport function recordRoundTrip(summary: RoundTripSummary, recordCount: number): void {\n const scope = store.getStore();\n if (scope === undefined) return;\n scope.calls += 1;\n scope.recordCount += recordCount;\n if (summary.resultConsumedAfter !== undefined) {\n scope.serverMs += bigintToSafeNumber(summary.resultConsumedAfter);\n }\n if (summary.resultAvailableAfter !== undefined) {\n scope.availableAfterMs += bigintToSafeNumber(summary.resultAvailableAfter);\n }\n const stats = summary.counters !== undefined ? summary.counters.updates() : undefined;\n if (stats !== undefined) {\n for (const key of COUNTER_KEYS) {\n const value = stats[key];\n if (typeof value === 'number' && value !== 0) {\n scope.counters[key] = (scope.counters[key] ?? 0) + value;\n }\n }\n }\n}\n\n/**\n * Build the `details` payload for an `OperationUsage` record from a scope.\n *\n * Always emits `calls`, `recordCount`, `availableAfterMs`. Counter fields are\n * included only when they accumulated non-zero values — keeps the record\n * compact for the read-heavy paths.\n */\nexport function buildUsageDetails(scope: UsageScope): Record<string, number> {\n const details: Record<string, number> = {\n calls: scope.calls,\n recordCount: scope.recordCount,\n availableAfterMs: scope.availableAfterMs,\n };\n for (const [key, value] of Object.entries(scope.counters)) {\n if (value !== 0) details[key] = value;\n }\n return details;\n}\n","// Error mapping — driver `error.code` → typed errors from\n// `packages/core/src/core/errors.ts`. Keyed off the documented Neo4j status\n// code taxonomy (see D10 in plans/neo4j-provider.md and the P3 probe results\n// at local-tests/baseline/neo4j-phase3-probes-results.md).\n//\n// The \"not found\" cases are NOT handled here — they come from result-set\n// inspection by individual callers (e.g. a `MATCH ... SET ... RETURN n` that\n// returns zero rows). This helper only translates driver-level error codes.\n\nimport {\n DuplicateEntityError,\n DuplicateRelationshipError,\n DuplicateRepositoryError,\n ProviderError,\n} from '@utaba/deep-memory';\n\nconst CONSTRAINT_VIOLATION_CODE = 'Neo.ClientError.Schema.ConstraintValidationFailed';\nconst SYNTAX_ERROR_CODE = 'Neo.ClientError.Statement.SyntaxError';\n\n/**\n * Context describing what the caller was trying to do when the driver error\n * fired. Used so a constraint violation can be turned into a useful\n * `DuplicateEntityError(id)` / `DuplicateRelationshipError(id)` /\n * `DuplicateRepositoryError(id)` rather than a generic `ProviderError`.\n *\n * Callers pass whichever identifiers they already have in scope — the helper\n * picks the right typed error based on `kind`. When no id is available, the\n * helper falls back to `ProviderError` with the original code preserved in the\n * suggestion message.\n */\nexport interface DriverErrorContext {\n /** What the caller was operating on when the driver error fired. */\n kind?: 'entity' | 'relationship' | 'repository';\n /** Entity id (when `kind === 'entity'`). */\n entityId?: string;\n /** Relationship id (when `kind === 'relationship'`). */\n relationshipId?: string;\n /** Repository id (when `kind === 'repository'`). */\n repositoryId?: string;\n /** Free-form operation label (e.g. `'createEntity'`) — included in the message. */\n operation?: string;\n}\n\ninterface DriverError {\n code?: unknown;\n message?: unknown;\n}\n\n/**\n * Convert a driver-thrown error into one of the project's typed errors.\n *\n * - `'Neo.ClientError.Schema.ConstraintValidationFailed'` →\n * `DuplicateEntityError` / `DuplicateRelationshipError` /\n * `DuplicateRepositoryError` based on `context.kind`. When `kind` is\n * omitted, the helper inspects the constraint-violation message to pick\n * the right branch (the message names the offending label —\n * `_Repository` / `_Entity` / a relationship type — see probe P3).\n * - `'Neo.ClientError.Statement.SyntaxError'` → `ProviderError` (programming\n * bug in this package; should never surface to end users).\n * - Anything else → `ProviderError` with the original error attached as\n * `cause` so root-cause analysis still works.\n *\n * Re-throws inputs that are already instances of the project's typed error\n * hierarchy unchanged.\n */\nexport function mapDriverError(error: unknown, context: DriverErrorContext = {}): never {\n if (\n error instanceof DuplicateEntityError ||\n error instanceof DuplicateRelationshipError ||\n error instanceof DuplicateRepositoryError ||\n error instanceof ProviderError\n ) {\n throw error;\n }\n\n const driverError = (error ?? {}) as DriverError;\n const code = typeof driverError.code === 'string' ? driverError.code : '';\n const message = typeof driverError.message === 'string' ? driverError.message : '';\n\n if (code === CONSTRAINT_VIOLATION_CODE) {\n const kind = inferConstraintKind(context, message);\n if (kind === 'repository' && context.repositoryId !== undefined) {\n throw new DuplicateRepositoryError(context.repositoryId);\n }\n if (kind === 'entity' && context.entityId !== undefined) {\n throw new DuplicateEntityError(context.entityId);\n }\n if (kind === 'relationship' && context.relationshipId !== undefined) {\n throw new DuplicateRelationshipError(context.relationshipId);\n }\n throw new ProviderError(\n `Neo4j constraint violation${formatOperation(context)}: ${message || code}`,\n 'Inspect the failing Cypher and the schema constraints — the affected unique key already exists.',\n );\n }\n\n if (code === SYNTAX_ERROR_CODE) {\n throw new ProviderError(\n `Neo4j syntax error${formatOperation(context)}: ${message || code}`,\n 'This is a programming error inside @utaba/deep-memory-storage-neo4j — please file an issue.',\n );\n }\n\n const prefix = `Neo4j driver error${formatOperation(context)}`;\n const codeSuffix = code ? ` [${code}]` : '';\n const messageSuffix = message ? `: ${message}` : '';\n throw new ProviderError(`${prefix}${codeSuffix}${messageSuffix}`);\n}\n\nfunction inferConstraintKind(\n context: DriverErrorContext,\n message: string,\n): 'entity' | 'relationship' | 'repository' {\n if (context.kind !== undefined) return context.kind;\n // Neo4j formats constraint-violation messages as\n // \"Node(...) already exists with label `_Entity` ...\" OR\n // \"Node(...) already exists with label `_Repository` ...\" OR\n // \"Relationship(...) already exists with type `KNOWS` ...\"\n // We discriminate on the leading noun and (for nodes) the label embedded in\n // the message. Probe P3 captured the exact format on neo4j:5.26-community.\n if (message.startsWith('Relationship')) return 'relationship';\n if (message.includes('`_Repository`')) return 'repository';\n return 'entity';\n}\n\nfunction formatOperation(context: DriverErrorContext): string {\n return context.operation ? ` in ${context.operation}` : '';\n}\n","// Bulk export / import Cypher queries.\n//\n// Storage shape and isolation rules carry over from entity / relationship\n// CRUD; this module differs only in batching strategy:\n//\n// Export:\n// - Cursor-based pagination via `WHERE n.id > $cursor ORDER BY n.id LIMIT\n// $batchSize`. Cursor cost stays flat (or trends down) as offset grows\n// because the `(repositoryId, id)` uniqueness constraint's backing index\n// range-scans from the seek point. `SKIP $offset` is O(n) per page and\n// O(n²) across the full sweep; on a 100k-entity repository the gap is the\n// difference between a flat-line export and a quadratic blow-up.\n// - Embedding INCLUDED in the projection so a re-import is field-for-field\n// faithful. This is the one read path that intentionally keeps the heavy\n// `embedding` column on the wire — every other entity read defaults to\n// `loadEmbeddings: false`.\n// - Async generator yields 100-record `ExportChunk`s, entities first then\n// relationships. The cursor resets between phases. Each chunk's `isLast`\n// is determined by `records.length < batchSize`. An empty repository\n// yields one empty terminal chunk so consumers always see at least one\n// yield (matches the Cosmos shape).\n//\n// Import:\n// - Fixed-shape `UNWIND $rows AS row …` templates so the planner caches one\n// plan per template regardless of `$rows` contents. Plan-cache footprint\n// is four entries total (entity CREATE, entity MERGE, relationship CREATE,\n// relationship MERGE) across every import in the system.\n// - `skipExistenceCheck: true` uses the CREATE branch. A duplicate row\n// causes the whole chunk to fail with\n// `Neo.ClientError.Schema.ConstraintValidationFailed`; the import falls\n// back to a per-row CREATE for that chunk so good rows still land. The\n// downside is one extra round-trip per failed-chunk; the upside is that\n// the success path is one round-trip per chunk regardless of chunk size.\n// - `skipExistenceCheck: false` uses the MERGE branch. `NodeUniqueIndexSeek`\n// on the `(repositoryId, id)` constraint backs the MERGE so the per-row\n// cost is O(log n) — re-imports are idempotent without paying a full scan.\n// - Default chunk size is 500. The throughput sweep showed the per-chunk\n// wall-time is flat from 50 → 500 rows, so chunkSize 500 amortises the\n// round-trip across 5× the rows of the Cosmos default without inflating\n// latency. Callers override via `BulkImportOptions.chunkSize`. (Public\n// surface — the `BulkImportOptions` interface currently exposes\n// adaptive-concurrency knobs only; chunk size lives in a private\n// extension here.)\n// - Bounded parallelism via `runBounded` — a hand-rolled minimal pool with\n// default concurrency 8. Neo4j Community has no throttle signal; the\n// adaptive-concurrency controller the Cosmos provider needs has no\n// analog. Chunks complete independently; their results aggregate into a\n// single `BulkImportResult` at the end. Per-row errors are collected per\n// chunk, never swallowed.\n\nimport type {\n BulkImportOptions,\n BulkImportResult,\n ExportChunk,\n ImportChunk,\n StoredEntity,\n StoredRelationship,\n} from '@utaba/deep-memory/types';\nimport type { Neo4jConnection } from '../Neo4jConnection.js';\nimport {\n assertSafeRelationshipType,\n bigintToSafeNumber,\n buildEntityProjection,\n buildRelationshipProjection,\n entityFromRecord,\n entityToParams,\n entityUserPropertyParams,\n relationshipFromRecord,\n relationshipToParams,\n} from '../mapping.js';\nimport { ProviderError } from '@utaba/deep-memory';\n\n/**\n * Streaming-export chunk size. Each yielded `ExportChunk` carries at most\n * `EXPORT_BATCH_SIZE` records. Matches the Cosmos provider default — the\n * consumer is an async iterable, smaller chunks reduce peak memory pressure\n * while the cursor pagination keeps server cost flat regardless of position\n * in the sweep.\n */\nconst EXPORT_BATCH_SIZE = 100;\n\n/**\n * Default chunk size for `importBulk`. The throughput sweep against\n * `neo4j:5-community` defaults found per-chunk wall time flat from 50 →\n * 500 rows; chunkSize 500 amortises the round-trip across 5× the rows of\n * the Cosmos default (100) with no per-chunk latency penalty. Callers can\n * override via the private `chunkSize` extension on `BulkImportOptions`.\n */\nconst DEFAULT_IMPORT_CHUNK_SIZE = 500;\n\n/**\n * Default bounded-parallelism level for `importBulk`. Neo4j Community has\n * no per-query throttle signal so the adaptive controller used by the Cosmos\n * provider has no analog. A fixed pool of 8 in-flight chunks matches the\n * driver's default connection pool capacity and saturates the server\n * comfortably without overwhelming the local instance during verify runs.\n */\nconst DEFAULT_IMPORT_CONCURRENCY = 8;\n\n/**\n * Internal extension to `BulkImportOptions` accepted by this provider only.\n * The public `BulkImportOptions` interface omits these knobs because they\n * are Neo4j-specific; callers passing them through the public `importBulk`\n * surface land on the right branch via the `options as` cast in the\n * implementation below. Documented here so the extension is discoverable\n * from the source.\n */\ninterface Neo4jBulkImportExtension {\n /** Rows per `UNWIND` chunk. Default 500. */\n chunkSize?: number;\n /** In-flight chunks. Default 8. */\n concurrency?: number;\n}\n\n// ─── Export ──────────────────────────────────────────────────────────\n\nconst EXPORT_ENTITY_PROJECTION = buildEntityProjection({ loadEmbeddings: true });\nconst EXPORT_RELATIONSHIP_PROJECTION = buildRelationshipProjection();\n\nconst EXPORT_ENTITIES_FIRST_PAGE = `\nMATCH (n:_Entity {repositoryId: $rid})\nRETURN ${EXPORT_ENTITY_PROJECTION}\nORDER BY n.id\nLIMIT $batchSize\n`;\n\nconst EXPORT_ENTITIES_NEXT_PAGE = `\nMATCH (n:_Entity {repositoryId: $rid})\nWHERE n.id > $cursor\nRETURN ${EXPORT_ENTITY_PROJECTION}\nORDER BY n.id\nLIMIT $batchSize\n`;\n\nconst EXPORT_RELATIONSHIPS_FIRST_PAGE = `\nMATCH (:_Entity {repositoryId: $rid})-[r {repositoryId: $rid}]->(:_Entity {repositoryId: $rid})\nRETURN ${EXPORT_RELATIONSHIP_PROJECTION}\nORDER BY r.id\nLIMIT $batchSize\n`;\n\nconst EXPORT_RELATIONSHIPS_NEXT_PAGE = `\nMATCH (:_Entity {repositoryId: $rid})-[r {repositoryId: $rid}]->(:_Entity {repositoryId: $rid})\nWHERE r.id > $cursor\nRETURN ${EXPORT_RELATIONSHIP_PROJECTION}\nORDER BY r.id\nLIMIT $batchSize\n`;\n\n/**\n * Stream every entity then every relationship in the repository, in\n * cursor-ordered pages of `EXPORT_BATCH_SIZE`. Each yielded chunk includes a\n * monotonic `sequence` and an `isLast` flag (true on the final entity chunk\n * and the final relationship chunk). An empty repository still yields one\n * terminal entity chunk so consumers always observe at least one item — the\n * Cosmos provider behaves the same way.\n *\n * The projection includes the embedding because a faithful round-trip is\n * the export-side contract; every other entity read defaults to embedding-off.\n */\nexport async function* exportAll(\n conn: Neo4jConnection,\n repositoryId: string,\n): AsyncIterable<ExportChunk> {\n let sequence = 0;\n\n let cursor: string | undefined;\n while (true) {\n const query = cursor === undefined ? EXPORT_ENTITIES_FIRST_PAGE : EXPORT_ENTITIES_NEXT_PAGE;\n const params: Record<string, unknown> =\n cursor === undefined\n ? { batchSize: BigInt(EXPORT_BATCH_SIZE) }\n : { cursor, batchSize: BigInt(EXPORT_BATCH_SIZE) };\n const result = await conn.executeQuery(query, params, { repositoryId, routing: 'READ' });\n const entities = result.records.map((record) => entityFromRecord(record));\n const isLast = entities.length < EXPORT_BATCH_SIZE;\n if (entities.length > 0) {\n cursor = entities[entities.length - 1]!.id;\n yield {\n type: 'entities',\n data: entities,\n sequence: sequence++,\n isLast,\n };\n }\n if (isLast) break;\n }\n\n let relCursor: string | undefined;\n while (true) {\n const query =\n relCursor === undefined ? EXPORT_RELATIONSHIPS_FIRST_PAGE : EXPORT_RELATIONSHIPS_NEXT_PAGE;\n const params: Record<string, unknown> =\n relCursor === undefined\n ? { batchSize: BigInt(EXPORT_BATCH_SIZE) }\n : { cursor: relCursor, batchSize: BigInt(EXPORT_BATCH_SIZE) };\n const result = await conn.executeQuery(query, params, { repositoryId, routing: 'READ' });\n const relationships = result.records.map((record) => relationshipFromRecord(record));\n const isLast = relationships.length < EXPORT_BATCH_SIZE;\n if (relationships.length > 0) {\n relCursor = relationships[relationships.length - 1]!.id;\n yield {\n type: 'relationships',\n data: relationships,\n sequence: sequence++,\n isLast,\n };\n }\n if (isLast) break;\n }\n\n if (sequence === 0) {\n yield {\n type: 'entities',\n data: [],\n sequence: 0,\n isLast: true,\n };\n }\n}\n\n// ─── Import — fixed-shape templates ──────────────────────────────────\n\n/**\n * `INSERT_ENTITIES_QUERY` — `skipExistenceCheck: true` branch. One CREATE\n * per row, no existence check; the caller asserts rows are fresh. Constraint\n * violations surface as `Neo.ClientError.Schema.ConstraintValidationFailed`\n * on chunk commit. The Cypher is byte-identical across every chunk in every\n * import, so the planner caches one plan total.\n *\n * User-supplied scalar properties are written through a separate `SET n +=\n * row.userProperties` clause — the row payload carries the user-property\n * map under a single binding so the Cypher string stays fixed regardless of\n * which keys are populated. The JSON-stringified `properties` blob remains\n * authoritative for `entity.properties` round-trip (per O1); the user-property\n * scalars exist for `findEntities` predicate queries only.\n */\nconst INSERT_ENTITIES_QUERY = `\nUNWIND $rows AS row\nCREATE (n:_Entity)\nSET\n n.id = row.id,\n n.repositoryId = $rid,\n n.entityType = row.entityType,\n n.label = row.label,\n n.slug = row.slug,\n n.summary = row.summary,\n n.properties = row.properties,\n n.data = row.data,\n n.dataFormat = row.dataFormat,\n n.embedding = row.embedding,\n n.createdBy = row.createdBy,\n n.createdByType = row.createdByType,\n n.createdAt = row.createdAt,\n n.createdInConversation = row.createdInConversation,\n n.createdFromMessage = row.createdFromMessage,\n n.modifiedBy = row.modifiedBy,\n n.modifiedByType = row.modifiedByType,\n n.modifiedAt = row.modifiedAt,\n n.modifiedInConversation = row.modifiedInConversation,\n n.modifiedFromMessage = row.modifiedFromMessage\nSET n += row.userProperties\n`;\n\n/**\n * `UPSERT_ENTITIES_QUERY` — `skipExistenceCheck: false` branch. MERGE keyed\n * on `(repositoryId, id)` so re-imports are idempotent. The\n * `NodeUniqueIndexSeek(Locking)` operator on the uniqueness constraint's\n * backing index makes the per-row cost O(log n).\n *\n * `ON CREATE` and `ON MATCH` SET the same field set — re-import overwrites\n * the existing row with the imported values, matching the Cosmos provider's\n * coalesce-style upsert semantic. The user-property `SET n += row.userProperties`\n * lives unconditionally so the Cypher string stays fixed; an empty\n * user-property map is a Cypher no-op.\n */\nconst UPSERT_ENTITIES_QUERY = `\nUNWIND $rows AS row\nMERGE (n:_Entity {repositoryId: $rid, id: row.id})\nON CREATE SET\n n.entityType = row.entityType,\n n.label = row.label,\n n.slug = row.slug,\n n.summary = row.summary,\n n.properties = row.properties,\n n.data = row.data,\n n.dataFormat = row.dataFormat,\n n.embedding = row.embedding,\n n.createdBy = row.createdBy,\n n.createdByType = row.createdByType,\n n.createdAt = row.createdAt,\n n.createdInConversation = row.createdInConversation,\n n.createdFromMessage = row.createdFromMessage,\n n.modifiedBy = row.modifiedBy,\n n.modifiedByType = row.modifiedByType,\n n.modifiedAt = row.modifiedAt,\n n.modifiedInConversation = row.modifiedInConversation,\n n.modifiedFromMessage = row.modifiedFromMessage\nON MATCH SET\n n.entityType = row.entityType,\n n.label = row.label,\n n.slug = row.slug,\n n.summary = row.summary,\n n.properties = row.properties,\n n.data = row.data,\n n.dataFormat = row.dataFormat,\n n.embedding = row.embedding,\n n.createdBy = row.createdBy,\n n.createdByType = row.createdByType,\n n.createdAt = row.createdAt,\n n.createdInConversation = row.createdInConversation,\n n.createdFromMessage = row.createdFromMessage,\n n.modifiedBy = row.modifiedBy,\n n.modifiedByType = row.modifiedByType,\n n.modifiedAt = row.modifiedAt,\n n.modifiedInConversation = row.modifiedInConversation,\n n.modifiedFromMessage = row.modifiedFromMessage\nSET n += row.userProperties\n`;\n\n/**\n * Build the relationship import Cypher for a given relationship type. Cypher\n * 25 cannot parameterise the relationship-type slot, so each distinct type\n * compiles to its own plan-cache entry. The vocabulary bounds the per-type\n * cardinality (same trade-off as `createRelationship` in Phase 7).\n *\n * `MATCH (s)` and `MATCH (t)` find the endpoint entities under the repository\n * scope before the edge is created; an endpoint outside the repository fails\n * the match and the row is silently skipped. The skipped row is then surfaced\n * to the caller via the row-count comparison in `importRelationshipChunk` so\n * the import result still reports the missing-endpoint condition.\n */\nfunction buildInsertRelationshipsQuery(relationshipType: string): string {\n const safe = assertSafeRelationshipType(relationshipType);\n return `\nUNWIND $rows AS row\nMATCH (s:_Entity {repositoryId: $rid, id: row.sourceEntityId})\nMATCH (t:_Entity {repositoryId: $rid, id: row.targetEntityId})\nCREATE (s)-[r:${safe} {\n repositoryId: $rid,\n id: row.id,\n relationshipType: row.relationshipType,\n sourceEntityId: row.sourceEntityId,\n targetEntityId: row.targetEntityId,\n properties: row.properties,\n bidirectional: row.bidirectional,\n createdBy: row.createdBy,\n createdByType: row.createdByType,\n createdAt: row.createdAt,\n createdInConversation: row.createdInConversation,\n createdFromMessage: row.createdFromMessage,\n modifiedBy: row.modifiedBy,\n modifiedByType: row.modifiedByType,\n modifiedAt: row.modifiedAt,\n modifiedInConversation: row.modifiedInConversation,\n modifiedFromMessage: row.modifiedFromMessage\n}]->(t)\nRETURN row.id AS id\n`;\n}\n\nfunction buildUpsertRelationshipsQuery(relationshipType: string): string {\n const safe = assertSafeRelationshipType(relationshipType);\n return `\nUNWIND $rows AS row\nMATCH (s:_Entity {repositoryId: $rid, id: row.sourceEntityId})\nMATCH (t:_Entity {repositoryId: $rid, id: row.targetEntityId})\nMERGE (s)-[r:${safe} {repositoryId: $rid, id: row.id}]->(t)\nON CREATE SET\n r.relationshipType = row.relationshipType,\n r.sourceEntityId = row.sourceEntityId,\n r.targetEntityId = row.targetEntityId,\n r.properties = row.properties,\n r.bidirectional = row.bidirectional,\n r.createdBy = row.createdBy,\n r.createdByType = row.createdByType,\n r.createdAt = row.createdAt,\n r.createdInConversation = row.createdInConversation,\n r.createdFromMessage = row.createdFromMessage,\n r.modifiedBy = row.modifiedBy,\n r.modifiedByType = row.modifiedByType,\n r.modifiedAt = row.modifiedAt,\n r.modifiedInConversation = row.modifiedInConversation,\n r.modifiedFromMessage = row.modifiedFromMessage\nON MATCH SET\n r.properties = row.properties,\n r.bidirectional = row.bidirectional,\n r.modifiedBy = row.modifiedBy,\n r.modifiedByType = row.modifiedByType,\n r.modifiedAt = row.modifiedAt,\n r.modifiedInConversation = row.modifiedInConversation,\n r.modifiedFromMessage = row.modifiedFromMessage\nRETURN row.id AS id\n`;\n}\n\n// ─── Import — public entry ───────────────────────────────────────────\n\n/**\n * Run a bulk import. Returns a single aggregate result spanning every chunk\n * and every entity / relationship row across the entire input.\n *\n * Concurrency: chunks within a phase (entities or relationships) run through\n * a bounded pool. Entities are imported strictly before relationships across\n * the whole input — relationship MATCH on the source/target entities requires\n * those entities to exist, so cross-phase parallelism is not safe.\n *\n * Error policy: a chunk-level failure (e.g. one duplicate row in a CREATE\n * chunk under `skipExistenceCheck: true`) falls back to per-row CREATE so\n * the surviving rows still land. Per-row errors land in `result.errors`;\n * successful rows count toward `entitiesImported` / `relationshipsImported`.\n */\nexport async function importBulk(\n conn: Neo4jConnection,\n repositoryId: string,\n data: ImportChunk[],\n options?: BulkImportOptions,\n): Promise<BulkImportResult> {\n const skipCheck = options?.skipExistenceCheck === true;\n const ext = (options ?? {}) as BulkImportOptions & Neo4jBulkImportExtension;\n const chunkSize = ext.chunkSize ?? DEFAULT_IMPORT_CHUNK_SIZE;\n const concurrency = ext.concurrency ?? DEFAULT_IMPORT_CONCURRENCY;\n\n const allEntities: StoredEntity[] = [];\n const allRelationships: StoredRelationship[] = [];\n for (const chunk of data) {\n if (chunk.entities) allEntities.push(...chunk.entities);\n if (chunk.relationships) allRelationships.push(...chunk.relationships);\n }\n\n const errors: Array<{ item: string; error: string }> = [];\n\n // Entity phase. Slice the flat list into UNWIND chunks; submit through the\n // bounded pool. The pool size is fixed (no adaptive controller — Neo4j has\n // no throttle signal to learn from).\n const entityChunks = sliceIntoChunks(allEntities, chunkSize);\n const entityResults = await runBounded(\n entityChunks,\n concurrency,\n (chunk) => importEntityChunk(conn, repositoryId, chunk, skipCheck),\n );\n\n let entitiesImported = 0;\n for (const res of entityResults) {\n entitiesImported += res.imported;\n errors.push(...res.errors);\n }\n\n const relationshipChunks = groupRelationshipsByTypeIntoChunks(allRelationships, chunkSize);\n const relationshipResults = await runBounded(\n relationshipChunks,\n concurrency,\n (group) => importRelationshipChunk(conn, repositoryId, group, skipCheck),\n );\n\n let relationshipsImported = 0;\n for (const res of relationshipResults) {\n relationshipsImported += res.imported;\n errors.push(...res.errors);\n }\n\n return { entitiesImported, relationshipsImported, errors };\n}\n\ninterface ChunkResult {\n imported: number;\n errors: Array<{ item: string; error: string }>;\n}\n\ninterface RelationshipChunk {\n relationshipType: string;\n rows: StoredRelationship[];\n}\n\nasync function importEntityChunk(\n conn: Neo4jConnection,\n repositoryId: string,\n entities: StoredEntity[],\n skipCheck: boolean,\n): Promise<ChunkResult> {\n if (entities.length === 0) return { imported: 0, errors: [] };\n\n // `repositoryId` is bound globally via the chokepoint's `$rid`; the row\n // map only carries per-entity fields. Keeping it off the row keeps the\n // Bolt payload smaller on large chunks.\n const rows = entities.map((entity) => ({\n ...entityToParams(entity),\n userProperties: entityUserPropertyParams(entity.properties),\n }));\n const query = skipCheck ? INSERT_ENTITIES_QUERY : UPSERT_ENTITIES_QUERY;\n\n try {\n await conn.executeQuery(query, { rows }, { repositoryId });\n return { imported: entities.length, errors: [] };\n } catch (err) {\n return fallbackPerEntity(conn, repositoryId, entities, skipCheck, err);\n }\n}\n\n/**\n * When a whole-chunk write fails (e.g. one constraint violation aborts the\n * entire MERGE/CREATE transaction), retry the chunk row-by-row so the rows\n * that would have succeeded still land. The per-row path is slower per call\n * but only runs when a chunk actually failed.\n */\nasync function fallbackPerEntity(\n conn: Neo4jConnection,\n repositoryId: string,\n entities: StoredEntity[],\n skipCheck: boolean,\n chunkError: unknown,\n): Promise<ChunkResult> {\n const errors: Array<{ item: string; error: string }> = [];\n let imported = 0;\n const query = skipCheck ? INSERT_ENTITIES_QUERY : UPSERT_ENTITIES_QUERY;\n for (const entity of entities) {\n try {\n const rows = [\n {\n ...entityToParams(entity),\n userProperties: entityUserPropertyParams(entity.properties),\n },\n ];\n await conn.executeQuery(query, { rows }, { repositoryId });\n imported++;\n } catch (rowErr) {\n errors.push({\n item: `entity:${entity.id}`,\n error: rowErr instanceof Error ? rowErr.message : String(rowErr),\n });\n }\n }\n // If we successfully fell back and at least one row failed AND no errors\n // were collected, attach the original chunk error so the caller still gets\n // a signal. Otherwise the per-row errors are the actionable surface.\n if (errors.length === 0 && imported < entities.length) {\n errors.push({\n item: `entity-chunk`,\n error: chunkError instanceof Error ? chunkError.message : String(chunkError),\n });\n }\n return { imported, errors };\n}\n\nasync function importRelationshipChunk(\n conn: Neo4jConnection,\n repositoryId: string,\n group: RelationshipChunk,\n skipCheck: boolean,\n): Promise<ChunkResult> {\n if (group.rows.length === 0) return { imported: 0, errors: [] };\n\n // `repositoryId` is bound globally via the chokepoint's `$rid`; rows carry\n // only per-edge fields.\n const rows = group.rows.map((rel) => relationshipToParams(rel));\n const query = skipCheck\n ? buildInsertRelationshipsQuery(group.relationshipType)\n : buildUpsertRelationshipsQuery(group.relationshipType);\n\n try {\n const result = await conn.executeQuery(query, { rows }, { repositoryId });\n const importedIds = new Set<string>();\n for (const record of result.records) {\n const id = record.get('id');\n if (typeof id === 'string') importedIds.add(id);\n }\n const errors: Array<{ item: string; error: string }> = [];\n for (const rel of group.rows) {\n if (!importedIds.has(rel.id)) {\n errors.push({\n item: `relationship:${rel.id}`,\n error: `endpoint not found in repository (source=${rel.sourceEntityId}, target=${rel.targetEntityId})`,\n });\n }\n }\n return { imported: importedIds.size, errors };\n } catch (err) {\n return fallbackPerRelationship(conn, repositoryId, group, skipCheck, err);\n }\n}\n\nasync function fallbackPerRelationship(\n conn: Neo4jConnection,\n repositoryId: string,\n group: RelationshipChunk,\n skipCheck: boolean,\n chunkError: unknown,\n): Promise<ChunkResult> {\n const errors: Array<{ item: string; error: string }> = [];\n let imported = 0;\n const query = skipCheck\n ? buildInsertRelationshipsQuery(group.relationshipType)\n : buildUpsertRelationshipsQuery(group.relationshipType);\n for (const rel of group.rows) {\n try {\n const rows = [relationshipToParams(rel)];\n const result = await conn.executeQuery(query, { rows }, { repositoryId });\n if (result.records.length === 1) {\n imported++;\n } else {\n errors.push({\n item: `relationship:${rel.id}`,\n error: `endpoint not found in repository (source=${rel.sourceEntityId}, target=${rel.targetEntityId})`,\n });\n }\n } catch (rowErr) {\n errors.push({\n item: `relationship:${rel.id}`,\n error: rowErr instanceof Error ? rowErr.message : String(rowErr),\n });\n }\n }\n if (errors.length === 0 && imported < group.rows.length) {\n errors.push({\n item: `relationship-chunk:${group.relationshipType}`,\n error: chunkError instanceof Error ? chunkError.message : String(chunkError),\n });\n }\n return { imported, errors };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction sliceIntoChunks<T>(items: T[], chunkSize: number): T[][] {\n if (items.length === 0) return [];\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += chunkSize) {\n out.push(items.slice(i, i + chunkSize));\n }\n return out;\n}\n\n/**\n * Group relationships by their type, then slice each group into chunks of\n * `chunkSize`. Cypher 25 cannot parameterise the relationship-type slot, so\n * a single UNWIND chunk must hold rows of one type — the relationship-type\n * appears in the compiled Cypher string. Mixing types in one chunk would\n * mean one round-trip per type per chunk; the grouping turns that into one\n * round-trip per chunk.\n */\nfunction groupRelationshipsByTypeIntoChunks(\n relationships: StoredRelationship[],\n chunkSize: number,\n): RelationshipChunk[] {\n const grouped = new Map<string, StoredRelationship[]>();\n for (const rel of relationships) {\n const list = grouped.get(rel.relationshipType);\n if (list) list.push(rel);\n else grouped.set(rel.relationshipType, [rel]);\n }\n const out: RelationshipChunk[] = [];\n for (const [relationshipType, rows] of grouped) {\n for (let i = 0; i < rows.length; i += chunkSize) {\n out.push({ relationshipType, rows: rows.slice(i, i + chunkSize) });\n }\n }\n return out;\n}\n\n/**\n * Hand-rolled bounded-parallelism helper — equivalent to `p-limit`'s\n * `Promise.all` over a worker pool, without the dependency. Items run in\n * order but complete in whatever order the server returns them; the result\n * array preserves the input order so callers can pair results with inputs.\n *\n * Concurrency is the maximum number of in-flight tasks at any point. With\n * concurrency 1 this degenerates to sequential execution. Errors thrown by\n * `fn` reject the returned promise; the caller is responsible for catching\n * within `fn` if partial success is desired.\n */\nexport async function runBounded<T, R>(\n items: T[],\n concurrency: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n if (items.length === 0) return [];\n if (concurrency < 1) {\n throw new ProviderError(`runBounded: concurrency must be >= 1 (got ${concurrency}).`);\n }\n const cap = Math.min(concurrency, items.length);\n const results: R[] = new Array(items.length);\n let nextIndex = 0;\n const workers: Array<Promise<void>> = [];\n for (let w = 0; w < cap; w++) {\n workers.push(\n (async (): Promise<void> => {\n while (true) {\n const i = nextIndex++;\n if (i >= items.length) return;\n results[i] = await fn(items[i]!);\n }\n })(),\n );\n }\n await Promise.all(workers);\n return results;\n}\n\n// `bigintToSafeNumber` is re-exported indirectly via the mapping module; this\n// module only needs it through its callers. The import keeps the dependency\n// graph explicit for the unit test that grep-checks against `driver.session`.\nexport { bigintToSafeNumber };\n","// Entity CRUD Cypher queries.\n//\n// Storage shape (per D4 / D6 / D15 / D22):\n// - Every entity is a `(:_Entity)` node carrying `repositoryId`, `id`,\n// `entityType`, `slug`, `label`, plus the optional / provenance scalars\n// bound by `entityToParams`. The `:_Entity` umbrella label is the ONLY\n// label written on entity nodes — writing a per-type label as well would\n// add cold-compile and steady-state overhead per distinct type (the label\n// slot cannot be parameterised, so each type produces its own plan-cache\n// entry) with no offsetting benefit, because every provider read filters\n// by the indexed `n.entityType` property.\n// - The `properties` JSON blob is the source of truth for user-supplied\n// entity properties round-trip (O1). User-supplied keys are ALSO written\n// as native Neo4j scalar properties on the node alongside the blob, so\n// `findEntities` can emit server-side `n.<key> = $val` predicates against\n// them and keep `total` exact. The CREATE template stays plan-cache-keyed\n// on a single Cypher string because `SET n += $userProperties` is one\n// fixed clause regardless of which keys are bound in the map.\n// - Values that Neo4j cannot store natively (nested objects, `null`,\n// arrays of objects, heterogeneous arrays) stay only inside the JSON\n// blob — they round-trip on `entity.properties` but are not\n// predicate-queryable. `findEntities` rejects filters against such values\n// rather than silently missing matches.\n//\n// Strategy:\n// - `createEntity` uses `CREATE` + catch on\n// `Neo.ClientError.Schema.ConstraintValidationFailed`, translated by\n// `mapDriverError({ kind: 'entity', ... })` to `DuplicateEntityError`.\n// A `MERGE`-with-discriminator alternative is marginally faster on the\n// happy path but mutates the existing node on every collision (writes a\n// discriminator property onto durable graph state that the caller never\n// requested) — correctness wins over the marginal perf delta.\n// - `getEntity` / `getEntityBySlug` / `getEntities` use explicit projection\n// via `buildEntityProjection` so embedding stays off the wire unless the\n// caller opts in via `EntityReadOptions.loadEmbeddings`. User-property\n// scalars on the node are not projected — `entity.properties` round-trips\n// from the JSON blob.\n// - `updateEntity` is variable-shape (D23) projection-on-write: build a\n// `SET n.<field> = $param` list from the dirty fields, then `RETURN\n// <projection>` to ship the post-SET state in one round-trip. When\n// `updates.properties !== undefined` the update pays an extra read of\n// the existing user-property key set so static REMOVE clauses can drop\n// keys that left the new shape — Cypher 25 cannot REMOVE a property\n// whose key is bound at run-time without APOC, and the provider\n// deliberately does not depend on APOC. Updates are not on the hot\n// read path.\n// - Bulk deletes return the affected ids in the same round-trip — the\n// caller computes the `notFound` set client-side from set difference,\n// avoiding a per-id existence pre-check.\n\nimport type { Neo4jConnection } from '../Neo4jConnection.js';\nimport type {\n PaginatedResult,\n StorageFindQuery,\n StoredEntity,\n StoredEntityUpdate,\n} from '@utaba/deep-memory/types';\nimport type { EntityReadOptions } from '@utaba/deep-memory/providers';\nimport {\n assertSafeUserPropertyKey,\n bigintToSafeNumber,\n buildEntityProjection,\n entityFromRecord,\n entityToParams,\n entityUserPropertyParams,\n isNativeStorableValue,\n RESERVED_ENTITY_PROPERTY_KEYS,\n} from '../mapping.js';\nimport { mapDriverError } from '../errors.js';\nimport { EntityNotFoundError, ProviderError } from '@utaba/deep-memory';\n\n/**\n * Fixed-shape `CREATE` template — same Cypher string for every entity create\n * regardless of which optional fields are populated. The planner caches one\n * plan across every entity create in the system (D15). Computed once at\n * module load so the constant string is what the planner keys off.\n *\n * `SET n += $userProperties` writes user-supplied entity properties as native\n * Neo4j scalars in addition to the JSON-stringified `properties` blob — the\n * blob remains authoritative for round-trip while the scalars make\n * `findEntities` property predicates server-side exact. The Cypher string is\n * byte-identical regardless of which user-property keys appear in the map,\n * so the plan cache footprint stays at one entry.\n *\n * Returns `n.id AS id` only — the caller already holds the `StoredEntity` it\n * passed in and does not need the round-trip to re-materialise it.\n */\nconst ENTITY_CREATE_QUERY = `\nCREATE (n:_Entity {\n repositoryId: $rid,\n id: $id,\n entityType: $entityType,\n label: $label,\n slug: $slug,\n summary: $summary,\n properties: $properties,\n data: $data,\n dataFormat: $dataFormat,\n embedding: $embedding,\n createdBy: $createdBy,\n createdByType: $createdByType,\n createdAt: $createdAt,\n createdInConversation: $createdInConversation,\n createdFromMessage: $createdFromMessage,\n modifiedBy: $modifiedBy,\n modifiedByType: $modifiedByType,\n modifiedAt: $modifiedAt,\n modifiedInConversation: $modifiedInConversation,\n modifiedFromMessage: $modifiedFromMessage\n})\nSET n += $userProperties\nRETURN n.id AS id\n`;\n\n// Read-projection chains are constant — compute once at module load so the\n// query string fed to the planner is byte-identical across calls. Two\n// variants: without and with embedding. The fulltext-index branch of\n// findEntities builds its own projection (alias `node`) so it lives there.\nconst ENTITY_PROJECTION_LIGHT = buildEntityProjection();\nconst ENTITY_PROJECTION_FULL = buildEntityProjection({ loadEmbeddings: true });\n\nconst ENTITY_GET_QUERY_LIGHT = `MATCH (n:_Entity {repositoryId: $rid, id: $id}) RETURN ${ENTITY_PROJECTION_LIGHT}`;\nconst ENTITY_GET_QUERY_FULL = `MATCH (n:_Entity {repositoryId: $rid, id: $id}) RETURN ${ENTITY_PROJECTION_FULL}`;\n\nconst ENTITY_GET_BY_SLUG_QUERY_LIGHT = `MATCH (n:_Entity {repositoryId: $rid, slug: $slug}) RETURN ${ENTITY_PROJECTION_LIGHT}`;\nconst ENTITY_GET_BY_SLUG_QUERY_FULL = `MATCH (n:_Entity {repositoryId: $rid, slug: $slug}) RETURN ${ENTITY_PROJECTION_FULL}`;\n\n// Batch get — single round-trip via `WHERE n.id IN $ids`. Caller drives any\n// not-found discrimination via map lookup (the public `getEntities` contract\n// returns a `Map<string, StoredEntity>` whose absent keys signal not-found).\nconst ENTITY_GET_MANY_QUERY_LIGHT = `MATCH (n:_Entity {repositoryId: $rid}) WHERE n.id IN $ids RETURN ${ENTITY_PROJECTION_LIGHT}`;\nconst ENTITY_GET_MANY_QUERY_FULL = `MATCH (n:_Entity {repositoryId: $rid}) WHERE n.id IN $ids RETURN ${ENTITY_PROJECTION_FULL}`;\n\n/**\n * Create a new entity via fixed-shape `CREATE` + catch on the uniqueness\n * constraint. Constraint-violation paths (duplicate `(repositoryId, id)` or\n * `(repositoryId, slug)`) surface as\n * `Neo.ClientError.Schema.ConstraintValidationFailed`, which\n * `mapDriverError` translates to `DuplicateEntityError`. The error mapping\n * picks the right kind from the `{ kind: 'entity', entityId }` context.\n */\nexport async function createEntity(\n conn: Neo4jConnection,\n repositoryId: string,\n entity: StoredEntity,\n): Promise<StoredEntity> {\n // Validate + project user properties to the native-scalar map before the\n // round-trip. A reserved-key collision or a malformed identifier throws\n // `ProviderError` here so the surface never reaches the server.\n const userProperties = entityUserPropertyParams(entity.properties);\n try {\n await conn.executeQuery(\n ENTITY_CREATE_QUERY,\n { ...entityToParams(entity), userProperties },\n { repositoryId },\n );\n } catch (err) {\n mapDriverError(err, {\n kind: 'entity',\n entityId: entity.id,\n operation: 'createEntity',\n });\n }\n return entity;\n}\n\n/**\n * Read a single entity by id. Returns `null` when no row matches — the public\n * contract is `null`-on-miss, not throw.\n */\nexport async function getEntity(\n conn: Neo4jConnection,\n repositoryId: string,\n entityId: string,\n options?: EntityReadOptions,\n): Promise<StoredEntity | null> {\n const query =\n options?.loadEmbeddings === true ? ENTITY_GET_QUERY_FULL : ENTITY_GET_QUERY_LIGHT;\n const result = await conn.executeQuery(\n query,\n { id: entityId },\n { repositoryId, routing: 'READ' },\n );\n const record = result.records[0];\n if (record === undefined) return null;\n return entityFromRecord(record);\n}\n\n/**\n * Read a single entity by slug. Slugs are unique within a repository via the\n * `dm_entity_slug_unique` constraint, so the lookup hits the constraint's\n * backing index. Returns `null` when no row matches.\n */\nexport async function getEntityBySlug(\n conn: Neo4jConnection,\n repositoryId: string,\n slug: string,\n options?: EntityReadOptions,\n): Promise<StoredEntity | null> {\n const query =\n options?.loadEmbeddings === true\n ? ENTITY_GET_BY_SLUG_QUERY_FULL\n : ENTITY_GET_BY_SLUG_QUERY_LIGHT;\n const result = await conn.executeQuery(\n query,\n { slug },\n { repositoryId, routing: 'READ' },\n );\n const record = result.records[0];\n if (record === undefined) return null;\n return entityFromRecord(record);\n}\n\n/**\n * Batch read by ids. Single round-trip via `WHERE n.id IN $ids`; absent ids\n * simply don't appear in the returned `Map`. Empty input → empty map, no\n * round-trip.\n */\nexport async function getEntities(\n conn: Neo4jConnection,\n repositoryId: string,\n entityIds: string[],\n options?: EntityReadOptions,\n): Promise<Map<string, StoredEntity>> {\n const map = new Map<string, StoredEntity>();\n if (entityIds.length === 0) return map;\n\n const query =\n options?.loadEmbeddings === true\n ? ENTITY_GET_MANY_QUERY_FULL\n : ENTITY_GET_MANY_QUERY_LIGHT;\n const result = await conn.executeQuery(\n query,\n { ids: entityIds },\n { repositoryId, routing: 'READ' },\n );\n for (const record of result.records) {\n const entity = entityFromRecord(record);\n map.set(entity.id, entity);\n }\n return map;\n}\n\n/**\n * Variable-shape projection-on-write update (D23). Builds a `SET n.<field> =\n * $param` clause per dirty field, then projects the post-SET state in the\n * same round-trip — `MATCH ... SET ... RETURN <projection>` ships the\n * post-SET values without a re-MATCH.\n *\n * Tri-state semantics map directly to Neo4j: `undefined` skips the field,\n * `null` sets the property to `null` (which Neo4j removes from the node, so\n * clearing the property is symmetric with absence on read), a value sets\n * the new value.\n *\n * When `updates.properties !== undefined` the path costs one extra read\n * round-trip ahead of the write: Cypher 25 cannot REMOVE a property whose\n * key is bound at run-time without APOC, so the TS layer must learn the\n * pre-update user-key set in order to emit static REMOVE clauses for keys\n * that left the new shape. The read+write pair is not wrapped in a managed\n * transaction — concurrent updates to the same entity can leave the native\n * scalars and the JSON blob temporarily divergent (last writer wins,\n * convergence on the next consistent write). The blob remains authoritative\n * for `entity.properties` round-trip, so the divergence affects only\n * predicate-match shape, not read shape.\n *\n * Empty record array → `EntityNotFoundError`.\n */\nexport async function updateEntity(\n conn: Neo4jConnection,\n repositoryId: string,\n entityId: string,\n updates: StoredEntityUpdate,\n): Promise<StoredEntity> {\n // Validate + project the new user-property shape before any round-trip.\n // The validation throws `ProviderError` on reserved-name collision or\n // malformed identifier — the read-side round-trip is wasted work if the\n // write would have failed anyway.\n let userProperties: Record<string, unknown> | null = null;\n if (updates.properties !== undefined) {\n userProperties = entityUserPropertyParams(updates.properties);\n }\n\n // Native scalar keys to REMOVE on update: pre-update user-property keys\n // minus the new user-property keys that will survive as native scalars.\n // Keys that move from native-storable → non-storable (e.g. string → nested\n // object) also land in `keysToRemove` so the stale native scalar leaves.\n let keysToRemove: string[] = [];\n if (userProperties !== null) {\n const readResult = await conn.executeQuery(\n 'MATCH (n:_Entity {repositoryId: $rid, id: $id}) RETURN properties(n) AS props',\n { id: entityId },\n { repositoryId, routing: 'READ' },\n );\n const readRecord = readResult.records[0];\n if (readRecord === undefined) throw new EntityNotFoundError(entityId);\n const props = readRecord.get('props');\n const existingProps =\n typeof props === 'object' && props !== null && !Array.isArray(props)\n ? (props as Record<string, unknown>)\n : {};\n const existingUserKeys = Object.keys(existingProps).filter(\n (k) => !RESERVED_ENTITY_PROPERTY_KEYS.has(k),\n );\n keysToRemove = existingUserKeys.filter((k) => !(k in userProperties!));\n }\n\n const setParts: string[] = [];\n const params: Record<string, unknown> = { id: entityId };\n\n const setField = (key: string, paramName: string, value: unknown): void => {\n setParts.push(`n.${key} = $${paramName}`);\n params[paramName] = value;\n };\n\n if (updates.entityType !== undefined) setField('entityType', 'entityType', updates.entityType);\n if (updates.label !== undefined) setField('label', 'label', updates.label);\n if (updates.slug !== undefined) setField('slug', 'slug', updates.slug);\n // `summary === null` clears the property (Neo4j removes null-valued props).\n if (updates.summary !== undefined) setField('summary', 'summary', updates.summary);\n if (updates.properties !== undefined) {\n setField('properties', 'properties', JSON.stringify(updates.properties));\n }\n if (updates.data !== undefined) setField('data', 'data', updates.data);\n if (updates.dataFormat !== undefined) setField('dataFormat', 'dataFormat', updates.dataFormat);\n if (updates.embedding !== undefined) setField('embedding', 'embedding', updates.embedding);\n\n // Provenance always lands — modifications carry the updated `modifiedBy*` /\n // `modifiedAt` regardless of which content fields changed. The optional\n // conversation/message fields use the same `undefined` skips / value sets\n // / null clears semantic; absence on the input preserves the existing\n // property unchanged.\n const p = updates.provenance;\n setField('modifiedBy', 'modifiedBy', p.modifiedBy);\n setField('modifiedByType', 'modifiedByType', p.modifiedByType);\n setField('modifiedAt', 'modifiedAt', p.modifiedAt);\n if (p.modifiedInConversation !== undefined) {\n setField('modifiedInConversation', 'modifiedInConversation', p.modifiedInConversation);\n }\n if (p.modifiedFromMessage !== undefined) {\n setField('modifiedFromMessage', 'modifiedFromMessage', p.modifiedFromMessage);\n }\n\n // User properties land via `SET n += $userProperties` regardless of how\n // many keys are in the map (an empty map is a no-op). The REMOVE clause\n // drops keys that left the new shape; keys are static identifiers because\n // Cypher 25 cannot bind a property name at run-time. Each key is\n // re-validated through `assertSafeUserPropertyKey` even though it came\n // from `properties(n)` — defence in depth against a pre-validation write\n // that somehow bypassed the chokepoint.\n let userPropsClause = '';\n if (userProperties !== null) {\n params['userProperties'] = userProperties;\n userPropsClause = ' SET n += $userProperties';\n }\n let removeClause = '';\n if (keysToRemove.length > 0) {\n const safeKeys = keysToRemove.map((k) => `n.${assertSafeUserPropertyKey(k)}`);\n removeClause = ` REMOVE ${safeKeys.join(', ')}`;\n }\n\n const cypher =\n `MATCH (n:_Entity {repositoryId: $rid, id: $id}) ` +\n `SET ${setParts.join(', ')}` +\n `${userPropsClause}` +\n `${removeClause} ` +\n `RETURN ${ENTITY_PROJECTION_LIGHT}`;\n\n const result = await conn.executeQuery(cypher, params, { repositoryId });\n const record = result.records[0];\n if (record === undefined) throw new EntityNotFoundError(entityId);\n return entityFromRecord(record);\n}\n\n/**\n * Delete a single entity and its incident relationships (`DETACH DELETE`).\n *\n * `RETURN count(n)` distinguishes match-not-found (0) from match-and-delete\n * (1) without a separate existence check. Counts ≠ 1 → `EntityNotFoundError`\n * — covers both the no-match case and the (impossible-in-practice) duplicate\n * case if the uniqueness constraint were somehow circumvented.\n */\nexport async function deleteEntity(\n conn: Neo4jConnection,\n repositoryId: string,\n entityId: string,\n): Promise<void> {\n const result = await conn.executeQuery(\n 'MATCH (n:_Entity {repositoryId: $rid, id: $id}) DETACH DELETE n RETURN count(n) AS deleted',\n { id: entityId },\n { repositoryId },\n );\n const deleted = bigintToSafeNumber(result.records[0]?.get('deleted') ?? 0);\n if (deleted !== 1) throw new EntityNotFoundError(entityId);\n}\n\n/**\n * Bulk delete by ids — single round-trip. Returns the ids actually deleted\n * (drawn from the `DETACH DELETE` operator's RETURN slice); the caller\n * computes the `notFound` set via set difference against the input ids.\n *\n * Empty input → empty result, no round-trip.\n */\nexport async function deleteEntities(\n conn: Neo4jConnection,\n repositoryId: string,\n ids: string[],\n): Promise<{ deleted: string[]; notFound: string[] }> {\n if (ids.length === 0) return { deleted: [], notFound: [] };\n const result = await conn.executeQuery(\n 'MATCH (n:_Entity {repositoryId: $rid}) WHERE n.id IN $ids ' +\n 'WITH n, n.id AS id DETACH DELETE n RETURN id AS deleted',\n { ids },\n { repositoryId },\n );\n const deleted: string[] = [];\n for (const record of result.records) {\n const id = record.get('deleted');\n if (typeof id === 'string') deleted.push(id);\n }\n const deletedSet = new Set(deleted);\n const notFound = ids.filter((id) => !deletedSet.has(id));\n return { deleted, notFound };\n}\n\n// Read-projection chains reused by `findEntities`. The non-search branch\n// projects from alias `n` (`MATCH (n:_Entity) ...`); the fulltext branch\n// projects from alias `node` (`CALL db.index.fulltext.queryNodes(...) YIELD\n// node, score`). Both forms are precomputed at module load so the planner\n// keys off byte-identical strings across calls.\nconst FIND_PROJECTION_LIGHT = buildEntityProjection({ alias: 'n' });\nconst FIND_PROJECTION_FULL = buildEntityProjection({ alias: 'n', loadEmbeddings: true });\nconst FIND_PROJECTION_LIGHT_FT = buildEntityProjection({ alias: 'node' });\nconst FIND_PROJECTION_FULL_FT = buildEntityProjection({ alias: 'node', loadEmbeddings: true });\n\n/**\n * Compile a `StorageFindQuery` to the shared WHERE-clause fragment and its\n * parameter bag. The same fragment feeds the data and count queries so they\n * are guaranteed to count the same set by construction.\n *\n * `$rid` is bound by the chokepoint, not here. The non-search branch always\n * includes `<alias>.repositoryId = $rid` as the first predicate so the planner\n * picks the `(repositoryId, id)` uniqueness constraint's backing index. The\n * fulltext branch routes through `CALL db.index.fulltext.queryNodes(...) YIELD\n * node` first and adds `node.repositoryId = $rid` immediately after the YIELD,\n * so this helper omits the repository predicate in fulltext mode (the caller\n * emits it inline with the YIELD clause to keep the planner's per-fulltext\n * optimisations in scope).\n *\n * **Property filter semantics.** Every public field on the entity surface\n * (entity type, slug, modifiedAt, provenance scalars, AND user-supplied\n * `entity.properties` keys) is stored as a native Neo4j scalar on the node,\n * so every filter emits an exact server-side predicate. The `n.properties`\n * JSON blob round-trips the full `entity.properties` shape including values\n * Neo4j cannot store natively (nested objects, `null`, arrays of objects,\n * heterogeneous arrays); those keys are NOT predicate-queryable. Filters\n * against non-native-storable values throw `ProviderError` rather than\n * silently missing matches.\n */\nexport function buildFindEntitiesWhere(\n query: StorageFindQuery,\n options: { alias: string; includeRepositoryPredicate: boolean },\n): { cypherWhere: string; params: Record<string, unknown> } {\n const { alias, includeRepositoryPredicate } = options;\n const params: Record<string, unknown> = {};\n const predicates: string[] = [];\n\n if (includeRepositoryPredicate) {\n predicates.push(`${alias}.repositoryId = $rid`);\n }\n\n if (query.entityTypes && query.entityTypes.length > 0) {\n predicates.push(`${alias}.entityType IN $entityTypes`);\n params['entityTypes'] = query.entityTypes;\n }\n\n if (query.properties) {\n let i = 0;\n for (const [key, value] of Object.entries(query.properties)) {\n // The key is interpolated into the predicate slot (Cypher 25 cannot\n // parameterise property names), so re-validate against the reserved\n // set and the bare-identifier shape on every emission.\n assertSafeUserPropertyKey(key);\n if (!isNativeStorableValue(value)) {\n throw new ProviderError(\n `findEntities property filter \"${key}\" has a value that Neo4j cannot store ` +\n `as a native scalar — filters must be strings, finite numbers, booleans, ` +\n `or homogeneous arrays of those. Nested objects and null values live only ` +\n `inside the JSON properties blob and are not predicate-queryable.`,\n );\n }\n const paramName = `prop${i}`;\n predicates.push(`${alias}.${key} = $${paramName}`);\n params[paramName] = value;\n i++;\n }\n }\n\n if (query.provenance) {\n if (query.provenance.conversationIds && query.provenance.conversationIds.length > 0) {\n predicates.push(\n `(${alias}.createdInConversation IN $convIds OR ${alias}.modifiedInConversation IN $convIds)`,\n );\n params['convIds'] = query.provenance.conversationIds;\n }\n if (query.provenance.actors && query.provenance.actors.length > 0) {\n predicates.push(\n `(${alias}.createdBy IN $actors OR ${alias}.modifiedBy IN $actors)`,\n );\n params['actors'] = query.provenance.actors;\n }\n if (query.provenance.dateRange) {\n predicates.push(\n `((${alias}.createdAt >= $dateFrom AND ${alias}.createdAt <= $dateTo) ` +\n `OR (${alias}.modifiedAt >= $dateFrom AND ${alias}.modifiedAt <= $dateTo))`,\n );\n // Timestamps are ISO-8601 strings (D6); lexicographic compare is\n // chronologically correct for the canonical Z-suffixed form.\n params['dateFrom'] = query.provenance.dateRange.from;\n params['dateTo'] = query.provenance.dateRange.to;\n }\n }\n\n const cypherWhere = predicates.length > 0 ? `WHERE ${predicates.join(' AND ')}` : '';\n return { cypherWhere, params };\n}\n\n/**\n * Find entities matching a `StorageFindQuery`. Returns one page plus an exact\n * total via a `Promise.all([data, count])` round-trip pair — the parallel\n * shape saves ~1.5 ms over sequential and keeps each query's plan-cache\n * footprint to a single entry per query shape.\n *\n * Branches:\n * - **Search-term branch** — when `query.searchTerm` is set, both queries route\n * through the `dm_entity_text` fulltext index via\n * `CALL db.index.fulltext.queryNodes(...) YIELD node, score`. The fulltext\n * index is unfiltered by repository, so the next predicate is always\n * `node.repositoryId = $rid`. Page ordering is by score descending. A\n * property-CONTAINS substring fallback for searchTerm was measured against\n * the fulltext path and rejected: the fulltext path is uniformly faster at\n * 10k+ entities and only marginally slower at 1k; carrying a dual-path\n * branch is not worth the code surface.\n * - **Non-search branch** — `MATCH (n:_Entity) WHERE n.repositoryId = $rid AND\n * <predicates>` backed by the `(repositoryId, entityType)` and\n * `(repositoryId, id)` indexes. Page ordering is by `n.id` to pin pagination\n * determinism.\n *\n * Every filter — `entityTypes`, `properties`, `provenance.*`, `searchTerm` —\n * resolves to a server-side exact predicate against either an indexed scalar,\n * a native user-property scalar, or the fulltext index. `total` is always\n * exact. Property filters whose value Neo4j cannot represent as a native\n * scalar (nested objects, `null`) throw `ProviderError` at predicate-build\n * time rather than silently missing matches.\n */\nexport async function findEntities(\n conn: Neo4jConnection,\n repositoryId: string,\n query: StorageFindQuery,\n options?: EntityReadOptions,\n): Promise<PaginatedResult<StoredEntity>> {\n const loadEmbeddings = options?.loadEmbeddings === true;\n const skipLimitParams = {\n skip: BigInt(query.offset),\n limit: BigInt(query.limit),\n };\n\n let records: ReadonlyArray<{ keys: ReadonlyArray<PropertyKey>; get(k: string): unknown }>;\n let countResult: { records: ReadonlyArray<{ get(k: string): unknown }> };\n\n if (query.searchTerm !== undefined && query.searchTerm !== '') {\n const projection = loadEmbeddings ? FIND_PROJECTION_FULL_FT : FIND_PROJECTION_LIGHT_FT;\n const where = buildFindEntitiesWhere(query, {\n alias: 'node',\n includeRepositoryPredicate: false,\n });\n // The fulltext call yields a node + score per matching document; the\n // repository predicate lands immediately after YIELD so the planner can\n // narrow the candidate set before evaluating optional predicates.\n const repoPredicate = 'node.repositoryId = $rid';\n const combinedWhere =\n where.cypherWhere.length > 0\n ? `WHERE ${repoPredicate} AND ${where.cypherWhere.slice('WHERE '.length)}`\n : `WHERE ${repoPredicate}`;\n const dataCypher =\n `CALL db.index.fulltext.queryNodes('dm_entity_text', $term) YIELD node, score ` +\n `${combinedWhere} ` +\n `RETURN ${projection} ` +\n `ORDER BY score DESC SKIP $skip LIMIT $limit`;\n const countCypher =\n `CALL db.index.fulltext.queryNodes('dm_entity_text', $term) YIELD node ` +\n `${combinedWhere} ` +\n `RETURN count(node) AS total`;\n const termParam = { term: query.searchTerm };\n const [dataResult, count] = await Promise.all([\n conn.executeQuery(\n dataCypher,\n { ...where.params, ...termParam, ...skipLimitParams },\n { repositoryId, routing: 'READ' },\n ),\n conn.executeQuery(\n countCypher,\n { ...where.params, ...termParam },\n { repositoryId, routing: 'READ' },\n ),\n ]);\n records = dataResult.records;\n countResult = count;\n } else {\n const projection = loadEmbeddings ? FIND_PROJECTION_FULL : FIND_PROJECTION_LIGHT;\n const where = buildFindEntitiesWhere(query, {\n alias: 'n',\n includeRepositoryPredicate: true,\n });\n const dataCypher =\n `MATCH (n:_Entity) ${where.cypherWhere} ` +\n `RETURN ${projection} ` +\n `ORDER BY n.id SKIP $skip LIMIT $limit`;\n const countCypher = `MATCH (n:_Entity) ${where.cypherWhere} RETURN count(n) AS total`;\n const [dataResult, count] = await Promise.all([\n conn.executeQuery(\n dataCypher,\n { ...where.params, ...skipLimitParams },\n { repositoryId, routing: 'READ' },\n ),\n conn.executeQuery(countCypher, where.params, { repositoryId, routing: 'READ' }),\n ]);\n records = dataResult.records;\n countResult = count;\n }\n\n const items = records.map((record) => entityFromRecord(record));\n const totalRaw = countResult.records[0]?.get('total');\n const total = totalRaw === undefined ? 0 : bigintToSafeNumber(totalRaw);\n const hasMore = query.offset + items.length < total;\n\n return {\n items,\n total,\n hasMore,\n limit: query.limit,\n offset: query.offset,\n };\n}\n\n/**\n * Delete every entity of a type plus their incident relationships, returning\n * exact counts in a single round-trip — strict improvement over the\n * `deletedRelationships: undefined` path Cosmos has to live with (Gremlin\n * `bothE().count()` would fan out across every partition the type touches).\n *\n * Counting via `sum(count{(n)-[]-()})` double-counts any edge whose two\n * endpoints are both in the matched same-type set, because each endpoint\n * sees and counts the edge independently. `OPTIONAL MATCH (n)-[r]-()` with\n * `count(DISTINCT r)` aggregates across the whole match and dedupes by\n * relationship identity, so every edge is counted exactly once.\n *\n * Aggregating before the delete (Cypher loses access to deleted variables in\n * a downstream RETURN) requires running the delete inside a `CALL` subquery\n * so the outer query can still RETURN the pre-aggregated counts.\n */\nexport async function deleteEntitiesByType(\n conn: Neo4jConnection,\n repositoryId: string,\n entityType: string,\n): Promise<{ deletedEntities: number; deletedRelationships: number | undefined }> {\n const result = await conn.executeQuery(\n `MATCH (n:_Entity {repositoryId: $rid, entityType: $entityType})\n OPTIONAL MATCH (n)-[r]-()\n WITH collect(DISTINCT n) AS nodes,\n count(DISTINCT n) AS entities,\n count(DISTINCT r) AS rels\n CALL (nodes) {\n UNWIND nodes AS node\n DETACH DELETE node\n }\n RETURN entities, rels`,\n { entityType },\n { repositoryId },\n );\n const record = result.records[0];\n if (record === undefined) {\n return { deletedEntities: 0, deletedRelationships: 0 };\n }\n const deletedEntities = bigintToSafeNumber(record.get('entities') ?? 0);\n const deletedRelationships = bigintToSafeNumber(record.get('rels') ?? 0);\n return { deletedEntities, deletedRelationships };\n}\n","// Relationship CRUD Cypher queries.\n//\n// Storage shape (per D5 / D7):\n// - Each relationship is a directed Cypher edge whose type is the\n// vocabulary slug uppercased per Cypher convention (D5 — `WORKS_AT`,\n// `KNOWS`, …). The relationship-type slot cannot be parameterised in\n// Cypher 25, so the slug is interpolated into the query string after\n// passing `assertSafeRelationshipType` — vocabulary cardinality bounds\n// the per-type plan-cache footprint (cheat sheet \"Indexes\" / Phase plan\n// D7).\n// - `bidirectional: true` is a read-time hint: the edge is still stored as\n// a single directed edge, and `getEntityRelationships` exposes it from\n// both ends by UNION-ing the inverse-direction match for bidirectional\n// edges. Writers do not duplicate the edge.\n// - Relationship `properties` round-trip as a JSON blob on the\n// `r.properties` field (no per-scalar storage and no relationship index\n// by property — relationships are not the indexed surface). Server-side\n// filtering by property therefore requires JSON parsing inside Cypher\n// (APOC), so `propertyFilters` is applied client-side after fetch and\n// `total` is reported as `undefined` in that case, matching the Cosmos\n// contract for the same pattern.\n//\n// Isolation invariant (D3b layer 3):\n// - `createRelationship` MATCHes both endpoint nodes under the scoping\n// `$rid` predicate before issuing `CREATE`. A cross-repository edge is\n// therefore structurally unwritable: the endpoint MATCH for the\n// out-of-scope side returns zero rows and the `FOREACH` conditional\n// skips the CREATE.\n// - Every read / delete carries the `$rid` predicate on the relationship\n// property map so reachability is bounded by the scope discriminator,\n// not the graph topology.\n\nimport type { Neo4jConnection } from '../Neo4jConnection.js';\nimport type {\n PaginatedResult,\n RelationshipQueryOptions,\n StoredRelationship,\n} from '@utaba/deep-memory/types';\nimport {\n EntityNotFoundError,\n matchesPropertyFilters,\n} from '@utaba/deep-memory';\nimport {\n assertSafeRelationshipType,\n bigintToSafeNumber,\n buildRelationshipProjection,\n relationshipFromRecord,\n relationshipToParams,\n} from '../mapping.js';\n\n// Shared projections — computed once at module load so the planner keys off\n// byte-identical strings regardless of which read path emits the query.\nconst RELATIONSHIP_PROJECTION = buildRelationshipProjection();\n\n/**\n * Create a relationship. Single-round-trip pattern: `OPTIONAL MATCH` both\n * endpoints under the repository scope, then `FOREACH` the `CREATE` only when\n * both are present, finally `RETURN` existence flags so the caller can\n * discriminate missing-source vs missing-target without a follow-up query.\n *\n * The relationship-type slot is interpolated into the Cypher string after\n * validation by `assertSafeRelationshipType` — Cypher 25 cannot parameterise\n * the type slot, and unbounded values would widen the injection surface. The\n * vocabulary's bounded cardinality keeps the plan cache footprint linear in\n * the type count.\n *\n * Cross-repository edges are structurally impossible: an endpoint in a\n * different repository fails its `(repositoryId, id)` MATCH and lands in the\n * `sMissing`/`tMissing` branch as if the entity did not exist at all.\n */\nexport async function createRelationship(\n conn: Neo4jConnection,\n repositoryId: string,\n relationship: StoredRelationship,\n): Promise<StoredRelationship> {\n const relType = assertSafeRelationshipType(relationship.relationshipType);\n\n const cypher = `\nOPTIONAL MATCH (s:_Entity {repositoryId: $rid, id: $sourceEntityId})\nOPTIONAL MATCH (t:_Entity {repositoryId: $rid, id: $targetEntityId})\nWITH s, t\nFOREACH (_ IN CASE WHEN s IS NOT NULL AND t IS NOT NULL THEN [1] ELSE [] END |\n CREATE (s)-[r:${relType} {\n repositoryId: $rid,\n id: $id,\n relationshipType: $relationshipType,\n sourceEntityId: $sourceEntityId,\n targetEntityId: $targetEntityId,\n properties: $properties,\n bidirectional: $bidirectional,\n createdBy: $createdBy,\n createdByType: $createdByType,\n createdAt: $createdAt,\n createdInConversation: $createdInConversation,\n createdFromMessage: $createdFromMessage,\n modifiedBy: $modifiedBy,\n modifiedByType: $modifiedByType,\n modifiedAt: $modifiedAt,\n modifiedInConversation: $modifiedInConversation,\n modifiedFromMessage: $modifiedFromMessage\n }]->(t)\n)\nRETURN s IS NULL AS sMissing, t IS NULL AS tMissing\n`;\n\n const result = await conn.executeQuery(\n cypher,\n relationshipToParams(relationship),\n { repositoryId },\n );\n\n const record = result.records[0];\n if (record === undefined) {\n // The OPTIONAL MATCH + FOREACH path always emits exactly one row.\n // Reaching this branch indicates the driver dropped the row, which\n // would be a driver-layer fault rather than a data-model condition.\n throw new EntityNotFoundError(relationship.sourceEntityId);\n }\n const sMissing = record.get('sMissing') === true;\n const tMissing = record.get('tMissing') === true;\n if (sMissing) throw new EntityNotFoundError(relationship.sourceEntityId);\n if (tMissing) throw new EntityNotFoundError(relationship.targetEntityId);\n return relationship;\n}\n\n/**\n * Look up a relationship by id. The implicit-direction pattern\n * `()-[r {...}]-()` finds the edge from either endpoint; the\n * `(repositoryId, id)` predicate is the application-level dedup key (D7).\n * Returns `null` on miss — contract is `null`-on-miss, not throw.\n */\nexport async function getRelationship(\n conn: Neo4jConnection,\n repositoryId: string,\n relationshipId: string,\n): Promise<StoredRelationship | null> {\n // Edges are written directionally (D5) — the `->` pattern matches each\n // relationship exactly once. The undirected `-[r]-` variant enumerates both\n // endpoint perspectives and yields each edge twice, which is wasted work\n // for a unique-by-id lookup.\n const result = await conn.executeQuery(\n `MATCH ()-[r {repositoryId: $rid, id: $relId}]->() RETURN ${RELATIONSHIP_PROJECTION}`,\n { relId: relationshipId },\n { repositoryId, routing: 'READ' },\n );\n const record = result.records[0];\n if (record === undefined) return null;\n return relationshipFromRecord(record);\n}\n\n/**\n * Page an entity's incident relationships with direction + type + property\n * filters. Direction semantics relative to `entityId`:\n *\n * - `'both'` — every incident edge (single MATCH, no UNION required).\n * - `'out'` — outbound edges, plus inbound edges flagged\n * `bidirectional: true` (read-time exposure of the bidir\n * hint; writers store one directed edge).\n * - `'in'` — inbound edges, plus outbound `bidirectional: true` edges.\n *\n * Property filters apply client-side because relationship `properties` is a\n * single JSON blob (no per-scalar storage on the relationship surface).\n * When `propertyFilters` is set, `total` is reported as `undefined` —\n * mirrors the Cosmos contract for the same pattern.\n *\n * Data and count round-trips run in parallel under one Bolt connection;\n * the driver multiplexes so wall-clock latency approximates one round-trip.\n */\nexport async function getEntityRelationships(\n conn: Neo4jConnection,\n repositoryId: string,\n entityId: string,\n options?: RelationshipQueryOptions,\n): Promise<PaginatedResult<StoredRelationship>> {\n const limit = options?.limit ?? 50;\n const offset = options?.offset ?? 0;\n const direction = options?.direction ?? 'both';\n const propertyFilters = options?.propertyFilters;\n const hasPropertyFilters = propertyFilters != null && propertyFilters.length > 0;\n\n // SKIP / LIMIT take Cypher INTEGER; under `useBigInt: true` plain JS numbers\n // round-trip as FLOAT and the planner rejects with\n // `Neo.ClientError.Statement.ArgumentError`. BigInt round-trips as INTEGER\n // — same fix the vocabulary change-log query already applies.\n const params: Record<string, unknown> = {\n eid: entityId,\n offset: BigInt(offset),\n limit: BigInt(limit),\n };\n const typeFilter: string = options?.relationshipTypes != null && options.relationshipTypes.length > 0\n ? buildTypeFilter(options.relationshipTypes, params)\n : '';\n\n const { dataCypher, countCypher } = buildEntityRelationshipQueries(direction, typeFilter);\n\n const [dataResult, countResult] = await Promise.all([\n conn.executeQuery(dataCypher, params, { repositoryId, routing: 'READ' }),\n hasPropertyFilters\n ? Promise.resolve(null)\n : conn.executeQuery(countCypher, params, { repositoryId, routing: 'READ' }),\n ]);\n\n let items = dataResult.records.map((record) => relationshipFromRecord(record));\n if (hasPropertyFilters) {\n items = items.filter((rel) => matchesPropertyFilters(rel.properties, propertyFilters));\n }\n\n let total: number | undefined;\n if (countResult !== null) {\n const totalRecord = countResult.records[0];\n total = totalRecord !== undefined\n ? bigintToSafeNumber(totalRecord.get('total') ?? 0)\n : 0;\n }\n const hasMore =\n total !== undefined ? offset + dataResult.records.length < total : dataResult.records.length === limit;\n\n return { items, total, hasMore, limit, offset };\n}\n\n/**\n * Drop a single relationship by id. No-op on miss — mirrors the Cosmos\n * contract (the public surface returns `void`, not a deleted-row count).\n */\nexport async function deleteRelationship(\n conn: Neo4jConnection,\n repositoryId: string,\n relationshipId: string,\n): Promise<void> {\n // Directional pattern: edges are stored directionally so `->` matches each\n // relationship once. `-[r]-` would enumerate both endpoint perspectives\n // and re-issue DELETE against an already-removed edge, which is wasted\n // work even though the operation remains idempotent.\n await conn.executeQuery(\n 'MATCH ()-[r {repositoryId: $rid, id: $relId}]->() DELETE r',\n { relId: relationshipId },\n { repositoryId },\n );\n}\n\n/**\n * Bulk delete by ids — single round-trip. Returns the ids actually deleted\n * (drawn from the `DELETE` operator's RETURN slice); the caller computes\n * the `notFound` set via set difference against the input ids. Empty input\n * short-circuits without a round-trip.\n */\nexport async function deleteRelationships(\n conn: Neo4jConnection,\n repositoryId: string,\n ids: string[],\n): Promise<{ deleted: string[]; notFound: string[] }> {\n if (ids.length === 0) return { deleted: [], notFound: [] };\n // Directional pattern — edges are stored directionally, so `->` matches\n // each relationship exactly once. `-[r]-` would double-yield each edge\n // (once per endpoint perspective), producing duplicate ids in the\n // returned `deleted` set.\n const result = await conn.executeQuery(\n 'MATCH ()-[r {repositoryId: $rid}]->() WHERE r.id IN $ids ' +\n 'WITH r, r.id AS id DELETE r RETURN id AS deleted',\n { ids },\n { repositoryId },\n );\n const deleted: string[] = [];\n for (const record of result.records) {\n const id = record.get('deleted');\n if (typeof id === 'string') deleted.push(id);\n }\n const deletedSet = new Set(deleted);\n const notFound = ids.filter((id) => !deletedSet.has(id));\n return { deleted, notFound };\n}\n\n/**\n * Drop every relationship of a type in the repository, returning an exact\n * count in a single round-trip. The type slot is interpolated after\n * `assertSafeRelationshipType` (Cypher 25 cannot parameterise it); the\n * `repositoryId` predicate on the edge property map bounds the match.\n */\nexport async function deleteRelationshipsByType(\n conn: Neo4jConnection,\n repositoryId: string,\n relationshipType: string,\n): Promise<{ deletedRelationships: number }> {\n const relType = assertSafeRelationshipType(relationshipType);\n // Directional pattern — edges are stored directionally, so `->` matches\n // each relationship exactly once. `-[r]-` would double-count by visiting\n // each edge from both endpoint perspectives.\n const result = await conn.executeQuery(\n `MATCH ()-[r:${relType} {repositoryId: $rid}]->() WITH r, r.id AS id DELETE r RETURN id`,\n {},\n { repositoryId },\n );\n return { deletedRelationships: result.records.length };\n}\n\n// ─── Internal helpers ───────────────────────────────────────────────\n\n/**\n * Build the `WHERE type(r) IN $relTypes` predicate fragment and bind the\n * `relTypes` parameter. `type(r)` (the Cypher built-in returning the\n * relationship's stored type) is preferred over `r.relationshipType` so the\n * planner can use the relationship-type index path directly.\n *\n * Returns the predicate **with leading whitespace** so the caller can\n * concatenate after an existing `WHERE` or as a standalone clause without\n * conditional whitespace logic at the seam.\n */\nfunction buildTypeFilter(\n relationshipTypes: string[],\n params: Record<string, unknown>,\n): string {\n params['relTypes'] = relationshipTypes;\n return ' WHERE type(r) IN $relTypes';\n}\n\n/**\n * Assemble the data + count Cypher for a given direction. `'both'` collapses\n * to a single MATCH; the directional cases UNION ALL the natural-direction\n * edges with the inverse-direction bidirectional edges so the bidir read-time\n * hint is exposed from both endpoints without writers duplicating the edge.\n *\n * Count queries wrap the same UNION ALL in a `CALL ( ) { ... }` subquery so\n * `count(*)` aggregates over the unioned row set.\n */\nfunction buildEntityRelationshipQueries(\n direction: 'out' | 'in' | 'both',\n typeFilter: string,\n): { dataCypher: string; countCypher: string } {\n if (direction === 'both') {\n const dataCypher =\n `MATCH (e:_Entity {repositoryId: $rid, id: $eid})-[r {repositoryId: $rid}]-()${typeFilter} ` +\n `RETURN ${RELATIONSHIP_PROJECTION} ORDER BY id SKIP $offset LIMIT $limit`;\n const countCypher =\n `MATCH (e:_Entity {repositoryId: $rid, id: $eid})-[r {repositoryId: $rid}]-()${typeFilter} ` +\n `RETURN count(r) AS total`;\n return { dataCypher, countCypher };\n }\n\n // direction === 'out' or 'in'. The natural branch matches edges where `e`\n // sits on the queried end; the bidir branch matches edges where `e` sits\n // on the opposite end AND the bidirectional flag is set.\n const naturalPattern =\n direction === 'out'\n ? '(e:_Entity {repositoryId: $rid, id: $eid})-[r {repositoryId: $rid}]->()'\n : '()-[r {repositoryId: $rid}]->(e:_Entity {repositoryId: $rid, id: $eid})';\n const bidirPattern =\n direction === 'out'\n ? '()-[r {repositoryId: $rid, bidirectional: true}]->(e:_Entity {repositoryId: $rid, id: $eid})'\n : '(e:_Entity {repositoryId: $rid, id: $eid})-[r {repositoryId: $rid, bidirectional: true}]->()';\n\n const dataCypher =\n `CALL () {\\n` +\n ` MATCH ${naturalPattern}${typeFilter} RETURN ${RELATIONSHIP_PROJECTION}\\n` +\n ` UNION ALL\\n` +\n ` MATCH ${bidirPattern}${typeFilter} RETURN ${RELATIONSHIP_PROJECTION}\\n` +\n `}\\n` +\n `RETURN ${reprojectFromCallScope()} ORDER BY id SKIP $offset LIMIT $limit`;\n\n const countCypher =\n `CALL () {\\n` +\n ` MATCH ${naturalPattern}${typeFilter} RETURN r.id AS rid\\n` +\n ` UNION ALL\\n` +\n ` MATCH ${bidirPattern}${typeFilter} RETURN r.id AS rid\\n` +\n `}\\n` +\n `RETURN count(rid) AS total`;\n\n return { dataCypher, countCypher };\n}\n\n/**\n * After a `CALL ( ) { ... }` subquery that returns the projection columns\n * directly, the outer query sees each projected name as a top-level\n * variable. The outer `RETURN` re-projects those variables verbatim so the\n * driver record carries the same shape as the non-UNION path — keeping the\n * mapper agnostic to whether the result came from a single MATCH or a\n * unioned set.\n */\nfunction reprojectFromCallScope(): string {\n return RELATIONSHIP_PROJECTION\n .split(',')\n .map((part) => {\n const segments = part.trim().split(' AS ');\n const fieldName = (segments[1] ?? segments[0] ?? '').trim();\n return `${fieldName} AS ${fieldName}`;\n })\n .join(', ');\n}\n","// Repository-level Cypher queries that depend on the broader storage shape\n// (entity counts, relationship counts by type). Repository CRUD lives inline\n// on the provider; this module is reserved for read paths that aggregate\n// across the repository.\n\nimport type { Neo4jConnection } from '../Neo4jConnection.js';\nimport type { MemoryVocabulary, RepositoryStats } from '@utaba/deep-memory/types';\nimport { bigintToSafeNumber } from '../mapping.js';\n\n/**\n * Entities-by-type breakdown. One row per distinct `entityType`. The single\n * `:_Entity` umbrella label decision (P5 lock) is load-bearing here — the\n * planner aggregates over `n.entityType` rather than walking per-type labels,\n * so the plan is byte-identical regardless of which entity types live in the\n * repository.\n */\nconst ENTITY_STATS_QUERY = `\nMATCH (n:_Entity {repositoryId: $rid})\nRETURN n.entityType AS type, count(n) AS count\n`;\n\n/**\n * Relationships-by-type breakdown. `type(r)` returns the Cypher relationship\n * type slug exactly as written by `createRelationship`. The pattern endpoints\n * carry the repository predicate explicitly so the planner narrows via the\n * `(repositoryId, id)` constraint's backing index — fanning out across every\n * relationship in the database first would defeat the per-repository scope.\n */\nconst RELATIONSHIP_STATS_QUERY = `\nMATCH (:_Entity {repositoryId: $rid})-[r {repositoryId: $rid}]->(:_Entity {repositoryId: $rid})\nRETURN type(r) AS type, count(r) AS count\n`;\n\n/**\n * Aggregate repository statistics: entity / relationship totals, per-type\n * breakdowns, vocabulary version.\n *\n * Two server round-trips fire in parallel (`Promise.all`) — the JS driver\n * multiplexes Bolt connections so the queries run concurrently rather than\n * serially. The vocabulary version comes from the caller-supplied\n * `MemoryVocabulary` value, which the provider sources from its 60 s\n * vocabulary cache; on a warm cache the stats path costs exactly two round-\n * trips total.\n *\n * `count(n)` and `count(r)` come back as `BigInt` under `useBigInt: true`\n * (D6b lock — confirmed by the Phase 10 probe results). The mapping helper\n * `bigintToSafeNumber` throws when a value exceeds `Number.MAX_SAFE_INTEGER`,\n * so callers never see a silent precision loss on extreme counts.\n *\n * Empty repository: both queries return zero rows; the returned breakdowns\n * are empty maps and the totals are 0. Cross-repository isolation is\n * structural — the `repositoryId` predicate on both endpoints scopes the\n * relationship pattern to this repository regardless of any overlapping\n * entity IDs in adjacent repositories.\n */\nexport async function getRepositoryStats(\n conn: Neo4jConnection,\n repositoryId: string,\n vocabulary: MemoryVocabulary,\n): Promise<RepositoryStats> {\n const [entityResult, relationshipResult] = await Promise.all([\n conn.executeQuery(ENTITY_STATS_QUERY, {}, { repositoryId, routing: 'READ' }),\n conn.executeQuery(RELATIONSHIP_STATS_QUERY, {}, { repositoryId, routing: 'READ' }),\n ]);\n\n const entityTypeBreakdown: Record<string, number> = {};\n let entityCount = 0;\n for (const record of entityResult.records) {\n const type = record.get('type');\n if (typeof type !== 'string') continue;\n const count = bigintToSafeNumber(record.get('count') ?? 0);\n entityTypeBreakdown[type] = count;\n entityCount += count;\n }\n\n const relationshipTypeBreakdown: Record<string, number> = {};\n let relationshipCount = 0;\n for (const record of relationshipResult.records) {\n const type = record.get('type');\n if (typeof type !== 'string') continue;\n const count = bigintToSafeNumber(record.get('count') ?? 0);\n relationshipTypeBreakdown[type] = count;\n relationshipCount += count;\n }\n\n return {\n entityCount,\n relationshipCount,\n vocabularyVersion: vocabulary.version,\n entityTypeBreakdown,\n relationshipTypeBreakdown,\n };\n}\n","// Timeline Cypher queries.\n//\n// The timeline event stream for an entity is the union of:\n// - one `entity:created` event at `n.createdAt`,\n// - one `entity:updated` event at `n.modifiedAt` when `modifiedAt !==\n// createdAt`,\n// - one `relationship:created` event per incident edge (in either direction),\n// keyed by the edge's `createdAt`.\n//\n// Cypher's `OPTIONAL MATCH + collect()` lifts the union into a single\n// round-trip: the centre entity row carries an aggregated list of every\n// incident edge regardless of direction. The Cosmos provider needs two\n// round-trips for the same information because Gremlin cannot bind an\n// aggregated edge list to a vertex projection in one shot — this is a\n// platform divergence, not an inherent trade-off.\n//\n// Event-type strings match the InMemory provider's contract (`entity:created`,\n// `entity:updated`, `relationship:created`) — `MemoryRepository.getTimeline`\n// reads the storage-level event type unchanged when it builds the\n// public-facing description and uses `e.eventType === 'entity:created'` as\n// the discriminator for the description string.\n\nimport type { Neo4jConnection } from '../Neo4jConnection.js';\nimport type {\n StorageTimelineEvent,\n StorageTimelineOptions,\n StorageTimelineResult,\n} from '@utaba/deep-memory/types';\n\nconst ENTITY_CREATED = 'entity:created';\nconst ENTITY_UPDATED = 'entity:updated';\nconst RELATIONSHIP_CREATED = 'relationship:created';\n\n/**\n * One-shot Cypher: the centre entity's provenance scalars plus every incident\n * edge's id + createdAt. `[x IN rels WHERE x IS NOT NULL | …]` is load-bearing:\n * `OPTIONAL MATCH` produces a single null sentinel inside `collect()` when no\n * row matches, and the inline filter strips it before the result reaches the\n * client (otherwise the no-edges case would surface as `[null]` rather than\n * `[]`).\n *\n * `collect(DISTINCT r)` dedupes the rare self-loop case where the same edge\n * matches the undirected pattern as both source and target.\n */\nconst TIMELINE_QUERY = `\nMATCH (n:_Entity {repositoryId: $rid, id: $id})\nOPTIONAL MATCH (n)-[r {repositoryId: $rid}]-()\nWITH n, collect(DISTINCT r) AS rels\nRETURN\n n.createdAt AS createdAt,\n n.modifiedAt AS modifiedAt,\n [x IN rels WHERE x IS NOT NULL | {id: x.id, createdAt: x.createdAt}] AS rels\n`;\n\n/**\n * Build the timeline event stream for an entity. Returns an empty result when\n * the centre entity does not exist (matching the Cosmos contract — the\n * InMemory provider throws `EntityNotFoundError`, but the storage-level\n * contract leaves the error to higher layers; `MemoryRepository.getTimeline`\n * re-fetches the centre via `getEntity` afterwards regardless).\n *\n * Filter ordering matches the InMemory precedent — `timeRange` first, then\n * `eventTypes`, then descending-timestamp sort, then page. `provenance` is\n * not applied at the storage layer because it requires re-fetching the\n * underlying entity / relationship for each event; `MemoryRepository` handles\n * that enrichment after the storage call returns. This mirrors the Cosmos\n * provider's surface — the storage call returns the raw event stream;\n * provenance filtering lives in the higher layer.\n */\nexport async function getTimeline(\n conn: Neo4jConnection,\n repositoryId: string,\n entityId: string,\n options: StorageTimelineOptions,\n): Promise<StorageTimelineResult> {\n const result = await conn.executeQuery(\n TIMELINE_QUERY,\n { id: entityId },\n { repositoryId, routing: 'READ' },\n );\n const record = result.records[0];\n if (record === undefined) {\n return { events: [], total: 0 };\n }\n\n const createdAt = record.get('createdAt');\n const modifiedAt = record.get('modifiedAt');\n const relsRaw = record.get('rels');\n\n const events: StorageTimelineEvent[] = [];\n\n if (typeof createdAt === 'string' && createdAt.length > 0) {\n events.push({ timestamp: createdAt, eventType: ENTITY_CREATED, entityId });\n }\n if (\n typeof modifiedAt === 'string' &&\n modifiedAt.length > 0 &&\n modifiedAt !== createdAt\n ) {\n events.push({ timestamp: modifiedAt, eventType: ENTITY_UPDATED, entityId });\n }\n\n if (Array.isArray(relsRaw)) {\n for (const item of relsRaw) {\n if (item === null || typeof item !== 'object') continue;\n const rel = item as { id?: unknown; createdAt?: unknown };\n const relId = rel.id;\n const relCreatedAt = rel.createdAt;\n if (typeof relId !== 'string' || typeof relCreatedAt !== 'string') continue;\n events.push({\n timestamp: relCreatedAt,\n eventType: RELATIONSHIP_CREATED,\n entityId,\n relationshipId: relId,\n });\n }\n }\n\n let filtered = events;\n if (options.timeRange) {\n const from = options.timeRange.from;\n const to = options.timeRange.to;\n // ISO-8601 Z-suffixed timestamps compare lexicographically in chronological\n // order, same shape used elsewhere (e.g. findEntities provenance.dateRange).\n filtered = filtered.filter((e) => e.timestamp >= from && e.timestamp <= to);\n }\n if (options.eventTypes && options.eventTypes.length > 0) {\n const allow = new Set(options.eventTypes);\n filtered = filtered.filter((e) => allow.has(e.eventType));\n }\n\n filtered.sort((a, b) => (a.timestamp < b.timestamp ? 1 : a.timestamp > b.timestamp ? -1 : 0));\n\n const total = filtered.length;\n const paged = filtered.slice(options.offset, options.offset + options.limit);\n\n return { events: paged, total };\n}\n","// Vocabulary Cypher queries.\n//\n// Storage shape (per D4 / D6 / D7):\n// - A single `(:_Vocabulary {repositoryId})` node holds the JSON-stringified\n// vocabulary blob in `vocabulary`. No uniqueness constraint exists for this\n// label — `saveVocabulary` upserts via `MERGE` keyed on `repositoryId`.\n// - Change-log entries live as `(:_VocabularyChangeLog {repositoryId, ...})`\n// nodes. Writes are append-only and land in `proposeVocabularyExtension`\n// (out of scope here); this module only reads them back, ordered by\n// `proposedAt DESC` to match the `VocabularyChangeRecord` audit semantic.\n\nimport type { MemoryVocabulary, VocabularyChangeRecord } from '@utaba/deep-memory/types';\nimport type { PaginationOptions, PaginatedResult } from '@utaba/deep-memory/types';\nimport type { Neo4jConnection } from '../Neo4jConnection.js';\nimport { bigintToSafeNumber, changeRecordFromRecord } from '../mapping.js';\n\nfunction emptyVocabulary(): MemoryVocabulary {\n return {\n version: '0.0.0',\n lastModified: new Date().toISOString(),\n modifiedBy: 'system',\n entityTypes: [],\n relationshipTypes: [],\n };\n}\n\n/**\n * Read the vocabulary for a repository. Returns an empty vocabulary when no\n * `_Vocabulary` node exists yet — mirrors the Cosmos and SQL Server providers'\n * forgiving-read contract so callers can compose `getVocabulary` into traversal\n * compilation without pre-seeding the node.\n *\n * Only the JSON `vocabulary` property is projected — the per-node `repositoryId`\n * and label are not needed by callers.\n */\nexport async function getVocabulary(\n conn: Neo4jConnection,\n repositoryId: string,\n): Promise<MemoryVocabulary> {\n const result = await conn.executeQuery(\n 'MATCH (v:_Vocabulary {repositoryId: $rid}) RETURN v.vocabulary AS json',\n {},\n { repositoryId, routing: 'READ' },\n );\n const record = result.records[0];\n if (record === undefined) return emptyVocabulary();\n const raw = record.get('json');\n if (typeof raw !== 'string' || raw === '') return emptyVocabulary();\n try {\n return JSON.parse(raw) as MemoryVocabulary;\n } catch {\n return emptyVocabulary();\n }\n}\n\n/**\n * Upsert the vocabulary for a repository. One round-trip — the D7 decision to\n * skip a uniqueness constraint on `_Vocabulary` means `MERGE` keyed on\n * `(label, repositoryId)` is the entire idempotency story; no separate\n * existence check is needed.\n *\n * Cache invalidation is the caller's responsibility (the provider's\n * `saveVocabulary` wrapper handles it so cache hits stay coherent with writes).\n */\nexport async function saveVocabulary(\n conn: Neo4jConnection,\n repositoryId: string,\n vocabulary: MemoryVocabulary,\n): Promise<void> {\n await conn.executeQuery(\n 'MERGE (v:_Vocabulary {repositoryId: $rid}) SET v.vocabulary = $json',\n { json: JSON.stringify(vocabulary) },\n { repositoryId },\n );\n}\n\n/**\n * Page the vocabulary change-log for a repository, newest first.\n *\n * Data and count round-trips are independent — fire them in parallel. There\n * are no property filters beyond the repository scope, so the count is always\n * exact.\n *\n * `proposedAt` is the canonical \"when\" field on `VocabularyChangeRecord`\n * (matches the Cosmos `'order().by('proposedAt', decr)'` and SQL Server\n * `ORDER BY proposed_at DESC` precedents). `SKIP` / `LIMIT` take Cypher\n * `INTEGER`; plain JS numbers send `FLOAT` and the planner rejects them with\n * `Neo.ClientError.Statement.ArgumentError`. With `useBigInt: true` on the\n * driver, `BigInt` round-trips as `INTEGER` — same fix Phase 4 applied to\n * `listRepositories`.\n */\nexport async function getVocabularyChangeLog(\n conn: Neo4jConnection,\n repositoryId: string,\n options?: PaginationOptions,\n): Promise<PaginatedResult<VocabularyChangeRecord>> {\n const limit = options?.limit ?? 10;\n const offset = options?.offset ?? 0;\n\n const [dataResult, countResult] = await Promise.all([\n conn.executeQuery(\n `MATCH (e:_VocabularyChangeLog {repositoryId: $rid})\n RETURN e\n ORDER BY e.proposedAt DESC\n SKIP $offset LIMIT $limit`,\n { offset: BigInt(offset), limit: BigInt(limit) },\n { repositoryId, routing: 'READ' },\n ),\n conn.executeQuery(\n 'MATCH (e:_VocabularyChangeLog {repositoryId: $rid}) RETURN count(e) AS total',\n {},\n { repositoryId, routing: 'READ' },\n ),\n ]);\n\n const items = dataResult.records.map((record) => changeRecordFromRecord(record, 'e'));\n const totalRaw = countResult.records[0]?.get('total');\n const total = totalRaw === undefined ? 0 : bigintToSafeNumber(totalRaw);\n\n return {\n items,\n total,\n hasMore: offset + items.length < total,\n limit,\n offset,\n };\n}\n","// Neo4j schema for @utaba/deep-memory StorageProvider\n//\n// Label and property conventions (per D4–D7 of plans/neo4j-provider.md):\n// System labels: _Entity, _Repository, _Vocabulary, _VocabularyChangeLog, _Meta\n// Entity nodes: :_Entity (umbrella) only. The entity-type discriminator\n// lives in `n.entityType` and is indexed by\n// `dm_entity_type_lookup`. Writing a per-type label as well\n// adds measurable cold-compile overhead per distinct type\n// plus a steady-state per-call cost (the label slot cannot\n// be parameterised, so each distinct type produces its own\n// plan-cache entry) with no offsetting benefit — every\n// provider read filters by `n.entityType` regardless.\n// Multi-tenancy: every node and relationship carries a `repositoryId` property;\n// every composite constraint / index leads with `repositoryId`.\n//\n// All statements are valid Cypher 25. Each statement runs as its own round-trip\n// because Neo4j parses one statement per `executeQuery` / `tx.run` call.\n\nexport const SCHEMA_VERSION = 1;\n\n/**\n * Returns the constraint / index DDL for the deep-memory Neo4j schema as an\n * array of single statements. Each statement is idempotent via\n * `IF NOT EXISTS`. Callers (the provider's `ensureSchema`, or operators\n * running out-of-band against a managed Neo4j) execute the statements one by\n * one.\n *\n * Composite uniqueness and lookup indexes all lead with `repositoryId` so the\n * planner uses it as the cheap discriminator — see D3b layer 1 in the plan.\n * The fulltext index is unfiltered; callers that query it must add an\n * explicit `WHERE node.repositoryId = $rid` post-filter.\n */\nexport function getSchemaCypher(): readonly string[] {\n return [\n // Composite node-property uniqueness on (repositoryId, id) — the load-bearing\n // primary discriminator for every entity-scoped query.\n `CREATE CONSTRAINT dm_entity_unique IF NOT EXISTS\nFOR (n:_Entity) REQUIRE (n.repositoryId, n.id) IS UNIQUE`,\n\n // Composite uniqueness on (repositoryId, slug) — backs getEntityBySlug\n // and guarantees stable URL-style addressing within a repository.\n `CREATE CONSTRAINT dm_entity_slug_unique IF NOT EXISTS\nFOR (n:_Entity) REQUIRE (n.repositoryId, n.slug) IS UNIQUE`,\n\n // Repository uniqueness — backs getRepository and listRepositories.\n `CREATE CONSTRAINT dm_repository_unique IF NOT EXISTS\nFOR (n:_Repository) REQUIRE n.repositoryId IS UNIQUE`,\n\n // Range index (default for `CREATE INDEX` without a type keyword) covering\n // (repositoryId, entityType) — backs findEntities type-filter and\n // deleteEntitiesByType.\n `CREATE INDEX dm_entity_type_lookup IF NOT EXISTS\nFOR (n:_Entity) ON (n.repositoryId, n.entityType)`,\n\n // Range index on (repositoryId, modifiedAt) — backs timeline and recency\n // ordering. Date is stored as ISO-8601 string (D6), lexicographic order\n // matches chronological order.\n `CREATE INDEX dm_entity_modified IF NOT EXISTS\nFOR (n:_Entity) ON (n.repositoryId, n.modifiedAt)`,\n\n // Fulltext index on entity label + summary — backs the findEntities\n // searchTerm branch via CALL db.index.fulltext.queryNodes(...). Unfiltered\n // by design; query callers must post-filter by repositoryId (see D3b).\n `CREATE FULLTEXT INDEX dm_entity_text IF NOT EXISTS\nFOR (n:_Entity) ON EACH [n.label, n.summary]`,\n ];\n}\n"],"mappings":";AA4CA;AAAA,EACE,iBAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA,0BAAAC;AAAA,EACA;AAAA,OACK;;;AC3BP,SAAS,gBAAgB,iBAAAC,sBAAqB;;;ACL9C,SAAS,qBAAqB;AAa9B,IAAM,kBAAkB,OAAO,OAAO,gBAAgB;AACtD,IAAM,kBAAkB,OAAO,OAAO,gBAAgB;AA4B/C,SAAS,mBAAmB,OAAwB;AACzD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,QAAQ,mBAAmB,QAAQ,iBAAiB;AACtD,YAAM,IAAI;AAAA,QACR,uBAAuB,MAAM,SAAS,CAAC;AAAA,MACzC;AAAA,IACF;AACA,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,IAAI;AAAA,IACR,yDAAyD,OAAO,KAAK;AAAA,EACvE;AACF;AASO,SAAS,iBAAiB,QAAsB,QAAQ,KAAmB;AAChF,SAAO,qBAAqB,mBAAmB,QAAQ,KAAK,CAAC;AAC/D;AAMO,SAAS,uBAAuB,QAAsB,QAAQ,KAAyB;AAC5F,SAAO,2BAA2B,mBAAmB,QAAQ,KAAK,CAAC;AACrE;AASO,SAAS,qBAAqB,OAA8C;AACjF,QAAM,UAAU,eAAe,OAAO,SAAS;AAC/C,QAAM,OAAO,eAAe,OAAO,MAAM;AACzC,QAAM,aAAa,eAAe,OAAO,YAAY;AACrD,QAAM,YAAY,eAAe,KAAK;AACtC,QAAM,SAAuB;AAAA,IAC3B,IAAI,cAAc,OAAO,IAAI;AAAA,IAC7B,MAAM,cAAc,OAAO,MAAM;AAAA,IACjC,YAAY,cAAc,OAAO,YAAY;AAAA,IAC7C,OAAO,cAAc,OAAO,OAAO;AAAA,IACnC,YAAY,oBAAoB,KAAK;AAAA,IACrC,YAAY,yBAAyB,KAAK;AAAA,EAC5C;AACA,MAAI,YAAY,OAAW,QAAO,UAAU;AAC5C,MAAI,SAAS,OAAW,QAAO,OAAO;AACtC,MAAI,eAAe,OAAW,QAAO,aAAa;AAClD,MAAI,cAAc,OAAW,QAAO,YAAY;AAChD,SAAO;AACT;AAMO,SAAS,2BACd,OACoB;AACpB,SAAO;AAAA,IACL,IAAI,cAAc,OAAO,IAAI;AAAA,IAC7B,kBAAkB,cAAc,OAAO,kBAAkB;AAAA,IACzD,gBAAgB,cAAc,OAAO,gBAAgB;AAAA,IACrD,gBAAgB,cAAc,OAAO,gBAAgB;AAAA,IACrD,YAAY,oBAAoB,KAAK;AAAA,IACrC,eAAe,eAAe,OAAO,eAAe;AAAA,IACpD,YAAY,yBAAyB,KAAK;AAAA,EAC5C;AACF;AAWO,SAAS,qBAAqB,QAAsB,QAAQ,KAAuB;AACxF,SAAO,yBAAyB,mBAAmB,QAAQ,KAAK,CAAC;AACnE;AAOO,SAAS,4BACd,QACA,QAAQ,KACiB;AACzB,SAAO,gCAAgC,mBAAmB,QAAQ,KAAK,CAAC;AAC1E;AAEO,SAAS,yBAAyB,OAAkD;AACzF,QAAM,OAAO,eAAe,OAAO,MAAM;AACzC,QAAM,cAAc,eAAe,OAAO,aAAa;AACvD,QAAM,QAAQ,eAAe,OAAO,OAAO;AAC3C,QAAM,QAAQ,eAAe,OAAO,OAAO;AAC3C,QAAM,WAAW,wBAA4C,OAAO,UAAU;AAC9E,QAAM,OAAyB;AAAA,IAC7B,cAAc,cAAc,OAAO,cAAc;AAAA,IACjD,OAAO,cAAc,OAAO,OAAO;AAAA,IACnC,kBAAkB,wBAA0C,OAAO,kBAAkB;AAAA,IACrF,WAAW,cAAc,OAAO,WAAW;AAAA,IAC3C,WAAW,cAAc,OAAO,WAAW;AAAA,EAC7C;AACA,MAAI,SAAS,OAAW,MAAK,OAAO;AACpC,MAAI,gBAAgB,OAAW,MAAK,cAAc;AAClD,MAAI,UAAU,OAAW,MAAK,QAAQ;AACtC,MAAI,UAAU,OAAW,MAAK,QAAQ;AACtC,MAAI,aAAa,OAAW,MAAK,WAAW;AAC5C,SAAO;AACT;AAEO,SAAS,gCACd,OACyB;AACzB,QAAM,OAAO,eAAe,OAAO,MAAM;AACzC,QAAM,cAAc,eAAe,OAAO,aAAa;AACvD,QAAM,UAAmC;AAAA,IACvC,cAAc,cAAc,OAAO,cAAc;AAAA,IACjD,OAAO,cAAc,OAAO,OAAO;AAAA,IACnC,kBAAkB,wBAA0C,OAAO,kBAAkB;AAAA,EACvF;AACA,MAAI,SAAS,OAAW,SAAQ,OAAO;AACvC,MAAI,gBAAgB,OAAW,SAAQ,cAAc;AACrD,SAAO;AACT;AAaO,SAAS,uBACd,QACA,QAAQ,KACgB;AACxB,SAAO,2BAA2B,mBAAmB,QAAQ,KAAK,CAAC;AACrE;AAEO,SAAS,2BACd,OACwB;AACxB,QAAM,kBAAkB,eAAe,OAAO,iBAAiB;AAC/D,QAAM,aAAa,eAAe,OAAO,YAAY;AACrD,QAAM,aAAa,eAAe,OAAO,YAAY;AACrD,QAAM,SAAiC;AAAA,IACrC,UAAU,cAAc,OAAO,UAAU;AAAA,IACzC,YAAY,kBAAkB,OAAO,YAAY;AAAA,IACjD,UAAU,cAAc,OAAO,UAAU;AAAA,IACzC,YAAY,cAAc,OAAO,YAAY;AAAA,IAC7C,YAAY,cAAc,OAAO,YAAY;AAAA,IAC7C,YAAY,cAAc,OAAO,YAAY;AAAA,IAC7C,QAAQ,cAAc,OAAO,QAAQ;AAAA,EACvC;AACA,MAAI,oBAAoB,OAAW,QAAO,kBAAkB;AAC5D,MAAI,eAAe,OAAW,QAAO,aAAa;AAClD,MAAI,eAAe,OAAW,QAAO,aAAa;AAClD,SAAO;AACT;AAeO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAcO,SAAS,sBAAsB,SAG3B;AACT,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,QAAQ,qBAAqB,IAAI,CAAC,UAAU,GAAG,KAAK,IAAI,KAAK,OAAO,KAAK,EAAE;AACjF,MAAI,SAAS,mBAAmB,MAAM;AACpC,UAAM,KAAK,GAAG,KAAK,yBAAyB;AAAA,EAC9C;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AASO,IAAM,gCAAqD,oBAAI,IAAI;AAAA,EACxE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAUD,IAAM,4BAA4B;AAQ3B,SAAS,0BAA0B,KAAqB;AAC7D,MAAI,CAAC,0BAA0B,KAAK,GAAG,GAAG;AACxC,UAAM,IAAI;AAAA,MACR,wBAAwB,GAAG,wDACtB,0BAA0B,MAAM;AAAA,IAGvC;AAAA,EACF;AACA,MAAI,8BAA8B,IAAI,GAAG,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,wBAAwB,GAAG,2DACN,MAAM,KAAK,6BAA6B,EAAE,KAAK,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF;AACA,SAAO;AACT;AAaO,SAAS,sBAAsB,OAAyB;AAC7D,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK;AAC3D,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,IAAI,OAAO;AACjB,QAAI,MAAM,YAAY,MAAM,cAAc,MAAM,YAAY,CAAC,OAAO,SAAS,KAAK,IAAI;AACpF,aAAO;AAAA,IACT;AACA,eAAW,KAAK,OAAO;AACrB,UAAI,OAAO,MAAM,EAAG,QAAO;AAC3B,UAAI,MAAM,YAAY,CAAC,OAAO,SAAS,CAAW,EAAG,QAAO;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AA0BO,SAAS,eAAe,QAA+C;AAC5E,QAAM,IAAI,OAAO;AACjB,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,YAAY,OAAO;AAAA,IACnB,OAAO,OAAO;AAAA,IACd,MAAM,OAAO;AAAA,IACb,SAAS,OAAO,WAAW;AAAA,IAC3B,YAAY,KAAK,UAAU,OAAO,UAAU;AAAA,IAC5C,MAAM,OAAO,QAAQ;AAAA,IACrB,YAAY,OAAO,cAAc;AAAA,IACjC,WAAW,OAAO,aAAa;AAAA,IAC/B,WAAW,EAAE;AAAA,IACb,eAAe,EAAE;AAAA,IACjB,WAAW,EAAE;AAAA,IACb,uBAAuB,EAAE,yBAAyB;AAAA,IAClD,oBAAoB,EAAE,sBAAsB;AAAA,IAC5C,YAAY,EAAE;AAAA,IACd,gBAAgB,EAAE;AAAA,IAClB,YAAY,EAAE;AAAA,IACd,wBAAwB,EAAE,0BAA0B;AAAA,IACpD,qBAAqB,EAAE,uBAAuB;AAAA,EAChD;AACF;AAgBO,SAAS,yBACd,YACyB;AACzB,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACrD,8BAA0B,GAAG;AAC7B,QAAI,sBAAsB,KAAK,GAAG;AAChC,UAAI,GAAG,IAAI;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;AAWO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,SAAS,4BAA4B,SAAsC;AAChF,QAAM,QAAQ,SAAS,SAAS;AAChC,SAAO,2BAA2B,IAAI,CAAC,UAAU,GAAG,KAAK,IAAI,KAAK,OAAO,KAAK,EAAE,EAAE,KAAK,IAAI;AAC7F;AAiBO,SAAS,qBAAqB,KAAkD;AACrF,QAAM,IAAI,IAAI;AACd,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,kBAAkB,IAAI;AAAA,IACtB,gBAAgB,IAAI;AAAA,IACpB,gBAAgB,IAAI;AAAA,IACpB,YAAY,KAAK,UAAU,IAAI,cAAc,CAAC,CAAC;AAAA,IAC/C,eAAe,IAAI;AAAA,IACnB,WAAW,EAAE;AAAA,IACb,eAAe,EAAE;AAAA,IACjB,WAAW,EAAE;AAAA,IACb,uBAAuB,EAAE,yBAAyB;AAAA,IAClD,oBAAoB,EAAE,sBAAsB;AAAA,IAC5C,YAAY,EAAE;AAAA,IACd,gBAAgB,EAAE;AAAA,IAClB,YAAY,EAAE;AAAA,IACd,wBAAwB,EAAE,0BAA0B;AAAA,IACpD,qBAAqB,EAAE,uBAAuB;AAAA,EAChD;AACF;AAcA,IAAM,4BAA4B;AAE3B,SAAS,2BAA2B,OAAuB;AAChE,MAAI,CAAC,0BAA0B,KAAK,KAAK,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,sBAAsB,KAAK,wDACtB,0BAA0B,MAAM;AAAA,IAGvC;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,uBACd,QACyB;AACzB,SAAO;AAAA,IACL,MAAM,OAAO,QAAQ;AAAA,IACrB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO,eAAe;AAAA,IACnC,OAAO,OAAO,SAAS;AAAA,IACvB,OAAO,OAAO,SAAS;AAAA,IACvB,kBAAkB,KAAK,UAAU,OAAO,gBAAgB;AAAA,IACxD,UAAU,OAAO,aAAa,SAAY,KAAK,UAAU,OAAO,QAAQ,IAAI;AAAA,IAC5E,WAAW,OAAO;AAAA,IAClB,WAAW,OAAO;AAAA,EACpB;AACF;AAIA,SAAS,mBAAmB,QAAsB,OAAwC;AACxF,MAAI,OAAO,KAAK,SAAS,KAAK,GAAG;AAC/B,UAAM,QAAQ,OAAO,IAAI,KAAK;AAC9B,QAAI,WAAW,KAAK,EAAG,QAAO,MAAM;AACpC,QAAI,mBAAmB,KAAK,EAAG,QAAO,MAAM;AAAA,EAC9C;AACA,QAAM,MAA+B,CAAC;AACtC,aAAW,OAAO,OAAO,MAAM;AAC7B,QAAI,OAAO,QAAQ,SAAU;AAC7B,QAAI,GAAG,IAAI,OAAO,IAAI,GAAG;AAAA,EAC3B;AACA,SAAO;AACT;AAEA,SAAS,WAAW,OAAkE;AACpF,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,IAAI;AACV,SACE,OAAO,EAAE,eAAe,YACxB,EAAE,eAAe,QACjB,CAAC,MAAM,QAAQ,EAAE,UAAU,KAC3B,MAAM,QAAQ,EAAE,MAAM;AAE1B;AAEA,SAAS,mBACP,OACkD;AAClD,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,IAAI;AACV,SACE,OAAO,EAAE,eAAe,YACxB,EAAE,eAAe,QACjB,CAAC,MAAM,QAAQ,EAAE,UAAU,KAC3B,OAAO,EAAE,SAAS;AAEtB;AAEA,SAAS,yBAAyB,OAA4C;AAC5E,QAAM,aAAyB;AAAA,IAC7B,WAAW,cAAc,OAAO,WAAW;AAAA,IAC3C,eAAe,iBAAiB,OAAO,eAAe;AAAA,IACtD,WAAW,cAAc,OAAO,WAAW;AAAA,IAC3C,YAAY,cAAc,OAAO,YAAY;AAAA,IAC7C,gBAAgB,iBAAiB,OAAO,gBAAgB;AAAA,IACxD,YAAY,cAAc,OAAO,YAAY;AAAA,EAC/C;AACA,QAAM,wBAAwB,eAAe,OAAO,uBAAuB;AAC3E,QAAM,qBAAqB,eAAe,OAAO,oBAAoB;AACrE,QAAM,yBAAyB,eAAe,OAAO,wBAAwB;AAC7E,QAAM,sBAAsB,eAAe,OAAO,qBAAqB;AACvE,MAAI,0BAA0B,OAAW,YAAW,wBAAwB;AAC5E,MAAI,uBAAuB,OAAW,YAAW,qBAAqB;AACtE,MAAI,2BAA2B,OAAW,YAAW,yBAAyB;AAC9E,MAAI,wBAAwB,OAAW,YAAW,sBAAsB;AACxE,SAAO;AACT;AAEA,SAAS,cAAc,OAAgC,KAAqB;AAC1E,QAAM,QAAQ,MAAM,GAAG;AACvB,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI;AAAA,MACR,kDAAkD,GAAG,UAAU,aAAa,KAAK,CAAC;AAAA,IACpF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,OAAgC,KAA+B;AACvF,QAAM,QAAQ,cAAc,OAAO,GAAG;AACtC,MAAI,UAAU,UAAU,UAAU,SAAS;AACzC,UAAM,IAAI;AAAA,MACR,uBAAuB,GAAG,oCAAoC,KAAK,UAAU,KAAK,CAAC;AAAA,IACrF;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,kBACP,OACA,KACsC;AACtC,QAAM,QAAQ,cAAc,OAAO,GAAG;AACtC,MAAI,CAAE,aAAmC,SAAS,KAAK,GAAG;AACxD,UAAM,IAAI;AAAA,MACR,uBAAuB,GAAG,iEAChB,KAAK,UAAU,KAAK,CAAC;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,OAAgC,KAAsB;AAC5E,QAAM,QAAQ,MAAM,GAAG;AACvB,MAAI,OAAO,UAAU,WAAW;AAC9B,UAAM,IAAI;AAAA,MACR,mDAAmD,GAAG,UAAU,aAAa,KAAK,CAAC;AAAA,IACrF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,OAAgC,KAAiC;AACvF,QAAM,QAAQ,MAAM,GAAG;AACvB,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,QAAO;AAClE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI;AAAA,MACR,uBAAuB,GAAG,oCAAoC,aAAa,KAAK,CAAC;AAAA,IACnF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAyD;AACpF,QAAM,MAAM,MAAM,YAAY;AAC9B,MAAI,QAAQ,UAAa,QAAQ,QAAQ,QAAQ,GAAI,QAAO,CAAC;AAC7D,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,IAAI;AAAA,MACR,8DAA8D,aAAa,GAAG,CAAC;AAAA,IACjF;AAAA,EACF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,cAAc,sDAAsD,MAAM,GAAG;AAAA,EACzF;AACA,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI;AAAA,MACR,qEAAqE,aAAa,MAAM,CAAC;AAAA,IAC3F;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,wBAA2B,OAAgC,KAAgB;AAClF,QAAM,MAAM,MAAM,GAAG;AACrB,MAAI,OAAO,QAAQ,YAAY,QAAQ,IAAI;AACzC,UAAM,IAAI;AAAA,MACR,uBAAuB,GAAG,0CAA0C,aAAa,GAAG,CAAC;AAAA,IACvF;AAAA,EACF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,cAAc,uBAAuB,GAAG,wBAAwB,MAAM,GAAG;AAAA,EACrF;AACA,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI;AAAA,MACR,uBAAuB,GAAG,uCAAuC,aAAa,MAAM,CAAC;AAAA,IACvF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,wBAA2B,OAAgC,KAA4B;AAC9F,QAAM,MAAM,MAAM,GAAG;AACrB,MAAI,QAAQ,UAAa,QAAQ,QAAQ,QAAQ,GAAI,QAAO;AAC5D,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,IAAI;AAAA,MACR,uBAAuB,GAAG,gCAAgC,aAAa,GAAG,CAAC;AAAA,IAC7E;AAAA,EACF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,cAAc,uBAAuB,GAAG,wBAAwB,MAAM,GAAG;AAAA,EACrF;AACA,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI;AAAA,MACR,uBAAuB,GAAG,uCAAuC,aAAa,MAAM,CAAC;AAAA,IACvF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,OAAsD;AAC5E,QAAM,MAAM,MAAM,WAAW;AAC7B,MAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO;AAC9C,MAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,UAAM,IAAI;AAAA,MACR,mEAAmE,aAAa,GAAG,CAAC;AAAA,IACtF;AAAA,EACF;AACA,aAAW,KAAK,KAAK;AACnB,QAAI,OAAO,MAAM,UAAU;AACzB,YAAM,IAAI;AAAA,QACR,iEAAiE,aAAa,CAAC,CAAC;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,OAAwB;AAC5C,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO,OAAO;AAChB;;;ADjrBO,IAAM,yBAAN,MAA6B;AAAA,EAGlC,YACmB,YACA,QACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EAJF,WAAW,IAAI,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY/C,MAAa,QACX,cACA,MACA,YAC6B;AAC7B,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,WAAW,KAAK,SAAS,QAAQ,MAAM,UAAU;AACvD,UAAM,cAAc,KAAK,WAAW,SAAS,KAAK;AAClD,UAAM,aAAa,KAAK,OAAO,oBAAoB,WAAW,WAAW,KAAK;AAM9E,UAAM,SAAS,wBAAwB,YAAY,SAAS,MAAM;AAElE,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,KAAK,WAAW;AAAA,QAC7B;AAAA,QACA;AAAA,QACA,EAAE,cAAc,SAAS,OAAO;AAAA,MAClC;AAAA,IACF,SAAS,KAAc;AACrB,YAAM,IAAIC;AAAA,QACR,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAEA,UAAM,kBAAkB,KAAK,IAAI,IAAI;AACrC,UAAM,UAAU,OAAO;AACvB,UAAM,WAAW,WAAW,QAAQ,mBAAmB;AAEvD,UAAM,MAA0B;AAAA,MAC9B,kBAAkB,CAAC;AAAA,MACnB,aAAa,CAAC;AAAA,MACd,kBAAkB,CAAC;AAAA,MACnB,UAAU,CAAC;AAAA,MACX,WAAW,oBAAI,IAAI;AAAA,MACnB,iBAAiB,oBAAI,IAAI;AAAA,MACzB,uBAAuB,oBAAI,IAAI;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,eAAe;AAAA,IACjB;AAEA,QAAI,KAAK,OAAO,qBAAqB,QAAQ,YAAY,QAAW;AAClE,UAAI,UAAU,iBAAiB,QAAQ,OAAO;AAAA,IAChD;AAMA,UAAM,kBACJ,KAAK,eAAe,UAAa,KAAK,eAAe,UAAU,KAAK,eAAe;AAErF,QAAI,iBAAiB;AACnB,WAAK,oBAAoB,OAAO,SAAS,KAAK,KAAK,WAAY,YAAY,KAAK,WAAY,QAAQ,QAAQ;AAAA,IAC9G,WAAW,KAAK,eAAe,YAAY;AACzC,WAAK,kBAAkB,OAAO,SAAS,GAAG;AAAA,IAC5C,WAAW,KAAK,eAAe,OAAO;AACpC,WAAK,aAAa,OAAO,SAAS,GAAG;AAAA,IACvC,OAAO;AACL,WAAK,cAAc,OAAO,SAAS,GAAG;AAAA,IACxC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,oBACN,SACA,KACA,eACA,MACM;AACN,UAAM,eAAuC,CAAC;AAC9C,eAAW,UAAU,SAAS;AAC5B,YAAM,SAAkC,CAAC;AACzC,iBAAW,QAAQ,eAAe;AAChC,eAAO,IAAI,IAAI,gBAAgB,OAAO,IAAI,IAAI,CAAC;AAAA,MACjD;AACA,UAAI,SAAS,SAAS;AACpB,cAAM,WAAW,OAAO,IAAI,OAAO;AACnC,cAAM,QAAQ,OAAO,aAAa,WAAW,OAAO,QAAQ,IACxD,OAAO,aAAa,WAAW,WAC/B;AACJ,qBAAa,KAAK,EAAE,QAAQ,MAAM,CAAC;AAAA,MACrC,OAAO;AACL,qBAAa,KAAK,EAAE,OAAO,CAAC;AAAA,MAC9B;AAAA,IACF;AACA,QAAI,eAAe;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,WAAW,OAAuB;AAKxC,UAAM,QAAQ;AACd,UAAM,WAAW,MAAM,QAAQ,QAAQ,KAAK;AAC5C,QAAI,aAAa,OAAO;AACtB,YAAM,IAAIA;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,kBACN,SACA,KACM;AAIN,eAAW,UAAU,SAAS;AAC5B,iBAAW,OAAO,OAAO,MAAM;AAC7B,YAAI,OAAO,QAAQ,SAAU;AAC7B,cAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,YAAI,OAAO,KAAK,GAAG;AACjB,gBAAM,SAAS,eAAe,KAAK;AACnC,cAAI,iBAAiB,KAAK,MAAM;AAChC,cAAI,CAAC,IAAI,UAAU,IAAI,OAAO,EAAE,EAAG,KAAI,UAAU,IAAI,OAAO,IAAI,MAAM;AAAA,QACxE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aACN,SACA,KACM;AAKN,eAAW,UAAU,SAAS;AAC5B,iBAAW,OAAO,OAAO,MAAM;AAC7B,YAAI,OAAO,QAAQ,SAAU;AAC7B,cAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,YAAI,OAAO,KAAK,GAAG;AACjB,gBAAM,SAAS,eAAe,KAAK;AACnC,cAAI,CAAC,IAAI,UAAU,IAAI,OAAO,EAAE,GAAG;AACjC,gBAAI,UAAU,IAAI,OAAO,IAAI,MAAM;AACnC,gBAAI,YAAY,KAAK,MAAM;AAAA,UAC7B;AAAA,QACF,WAAW,eAAe,KAAK,GAAG;AAChC,gBAAM,SAAS,6BAA6B,KAAK;AACjD,cAAI,CAAC,IAAI,gBAAgB,IAAI,OAAO,EAAE,GAAG;AACvC,gBAAI,gBAAgB,IAAI,OAAO,IAAI,MAAM;AACzC,gBAAI,iBAAiB,KAAK,MAAM;AAAA,UAClC;AAAA,QACF;AAAA,MAIF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cACN,SACA,KACM;AAON,eAAW,UAAU,SAAS;AAC5B,YAAM,YAAY,OAAO,IAAI,WAAW;AACxC,YAAM,WAAW,OAAO,IAAI,UAAU;AACtC,UAAI,CAAC,MAAM,QAAQ,SAAS,KAAK,CAAC,MAAM,QAAQ,QAAQ,EAAG;AAE3D,YAAM,gBAA0B,CAAC;AACjC,YAAM,aAAuB,CAAC;AAC9B,YAAM,oBAAyC,CAAC;AAChD,UAAI;AAEJ,iBAAW,QAAQ,WAAW;AAC5B,YAAI,CAAC,OAAO,IAAI,EAAG;AACnB,cAAM,SAAS,eAAe,IAAI;AAClC,YAAI,CAAC,IAAI,UAAU,IAAI,OAAO,EAAE,EAAG,KAAI,UAAU,IAAI,OAAO,IAAI,MAAM;AACtE,sBAAc,KAAK,OAAO,EAAE;AAAA,MAC9B;AAEA,eAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,cAAM,MAAM,SAAS,CAAC;AACtB,YAAI,CAAC,eAAe,GAAG,EAAG;AAC1B,cAAM,SAAS,6BAA6B,GAAG;AAC/C,YAAI,CAAC,IAAI,gBAAgB,IAAI,OAAO,EAAE,EAAG,KAAI,gBAAgB,IAAI,OAAO,IAAI,MAAM;AAClF,mBAAW,KAAK,OAAO,EAAE;AAOzB,cAAM,mBAAmB,UAAU,CAAC;AACpC,cAAM,wBAAwB,OAAO,gBAAgB,IACjD,iBAAiB,YACjB;AACJ,cAAM,YACJ,0BAA0B,UAC1B,IAAI,uBAAuB,wBACvB,QACA;AACN,0BAAkB,KAAK,SAAS;AAChC,YAAI,CAAC,IAAI,sBAAsB,IAAI,OAAO,EAAE,GAAG;AAC7C,cAAI,sBAAsB,IAAI,OAAO,IAAI,SAAS;AAAA,QACpD;AACA,cAAM,iBAAiB,UAAU,IAAI,CAAC;AACtC,YAAI,OAAO,cAAc,EAAG,qBAAoB,eAAe;AAAA,MACjE;AAEA,UAAI,SAAS,KAAK;AAAA,QAChB,WAAW;AAAA,QACX,iBAAiB;AAAA,QACjB,wBAAwB;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,OAAO,OAAmC;AACjD,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,IAAI;AACV,SACE,OAAO,EAAE,cAAc,YACvB,MAAM,QAAQ,EAAE,MAAM,KACtB,OAAO,EAAE,eAAe,YACxB,EAAE,eAAe;AAErB;AAEA,SAAS,eAAe,OAA2C;AACjE,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,IAAI;AAOV,SACE,OAAO,EAAE,cAAc,YACvB,OAAO,EAAE,SAAS,YAClB,OAAO,EAAE,uBAAuB,YAChC,OAAO,EAAE,qBAAqB,YAC9B,OAAO,EAAE,eAAe,YACxB,EAAE,eAAe;AAErB;AAEA,SAAS,eAAe,MAA8B;AACpD,SAAO,qBAAqB,KAAK,UAAU;AAC7C;AAEA,SAAS,6BAA6B,KAA2C;AAM/E,SAAO,2BAA2B,IAAI,UAAU;AAClD;AAEA,SAAS,WAAW,OAAwB;AAC1C,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO;AACT;AASA,SAAS,gBAAgB,OAAyB;AAChD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAClD,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,wBACP,QACA,QACyB;AACzB,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,SAAS,OAAO,SAAS,wBAAwB,GAAG;AAC7D,QAAI,MAAM,CAAC,MAAM,OAAW,YAAW,IAAI,MAAM,CAAC,CAAC;AAAA,EACrD;AACA,MAAI,WAAW,SAAS,EAAG,QAAO;AAClC,QAAM,MAA+B,EAAE,GAAG,OAAO;AACjD,aAAW,OAAO,YAAY;AAC5B,UAAM,QAAQ,IAAI,GAAG;AACrB,QAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,UAAI,GAAG,IAAI,OAAO,KAAK,MAAM,KAAK,CAAC;AAAA,IACrC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAA+D;AACvF,MAAI,QAAQ;AACZ,QAAM,QAAQ,CAAC,SAAqC;AAClD,QAAI,SAAS,OAAW;AACxB,UAAM,OAAO,KAAK;AAClB,QAAI,OAAO,SAAS,SAAU,UAAS,OAAO,IAAI;AAAA,aACzC,OAAO,SAAS,SAAU,UAAS;AAC5C,eAAW,SAAS,KAAK,YAAY,CAAC,EAAG,OAAM,KAAK;AAAA,EACtD;AACA,QAAM,IAAI;AACV,SAAO,EAAE,aAAa,OAAO,cAAc,KAAK,gBAAgB,UAAU;AAC5E;;;AEpdA,OAAO,WAAW;AAUlB,SAAS,iBAAAC,sBAAqB;;;ACL9B,SAAS,yBAAyB;AAqClC,IAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,QAAQ,IAAI,kBAA8B;AAGzC,SAAS,mBAA+B;AAC7C,SAAO;AAAA,IACL,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,UAAU,CAAC;AAAA,EACb;AACF;AAUO,SAAS,gBAAmB,OAAmB,IAAgB;AACpE,SAAO,MAAM,IAAI,OAAO,EAAE;AAC5B;AAqCO,SAAS,gBAAgB,SAA2B,aAA2B;AACpF,QAAM,QAAQ,MAAM,SAAS;AAC7B,MAAI,UAAU,OAAW;AACzB,QAAM,SAAS;AACf,QAAM,eAAe;AACrB,MAAI,QAAQ,wBAAwB,QAAW;AAC7C,UAAM,YAAY,mBAAmB,QAAQ,mBAAmB;AAAA,EAClE;AACA,MAAI,QAAQ,yBAAyB,QAAW;AAC9C,UAAM,oBAAoB,mBAAmB,QAAQ,oBAAoB;AAAA,EAC3E;AACA,QAAM,QAAQ,QAAQ,aAAa,SAAY,QAAQ,SAAS,QAAQ,IAAI;AAC5E,MAAI,UAAU,QAAW;AACvB,eAAW,OAAO,cAAc;AAC9B,YAAM,QAAQ,MAAM,GAAG;AACvB,UAAI,OAAO,UAAU,YAAY,UAAU,GAAG;AAC5C,cAAM,SAAS,GAAG,KAAK,MAAM,SAAS,GAAG,KAAK,KAAK;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AACF;AASO,SAAS,kBAAkB,OAA2C;AAC3E,QAAM,UAAkC;AAAA,IACtC,OAAO,MAAM;AAAA,IACb,aAAa,MAAM;AAAA,IACnB,kBAAkB,MAAM;AAAA,EAC1B;AACA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,QAAQ,GAAG;AACzD,QAAI,UAAU,EAAG,SAAQ,GAAG,IAAI;AAAA,EAClC;AACA,SAAO;AACT;;;ADtGA,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB;AAC1B,IAAM,gCAAgC;AAQ/B,IAAM,kBAAN,MAAsB;AAAA,EACV;AAAA,EACA;AAAA,EAEjB,YAAY,QAA+B;AACzC,SAAK,WAAW,OAAO,YAAY;AACnC,SAAK,SAAS,MAAM;AAAA,MAClB,OAAO;AAAA,MACP,MAAM,KAAK,MAAM,OAAO,UAAU,OAAO,QAAQ;AAAA,MACjD;AAAA;AAAA;AAAA;AAAA,QAIE,WAAW;AAAA,QACX,WAAW,OAAO,aAAa;AAAA,QAC/B,GAAI,OAAO,4BAA4B,SACnC,EAAE,yBAAyB,OAAO,wBAAwB,IAC1D,CAAC;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAa,qBAAoC;AAC/C,UAAM,KAAK,OAAO,mBAAmB,EAAE,UAAU,KAAK,SAAS,CAAC;AAAA,EAClE;AAAA;AAAA,EAGA,MAAa,gBAAqC;AAChD,WAAO,KAAK,OAAO,cAAc,EAAE,UAAU,KAAK,SAAS,CAAC;AAAA,EAC9D;AAAA;AAAA,EAGA,MAAa,QAAuB;AAClC,UAAM,KAAK,OAAO,MAAM;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAa,aACX,QACA,QACA,SACyB;AACzB,SAAK,mBAAmB,QAAQ,YAAY;AAC5C,SAAK,aAAa,MAAM;AACxB,UAAM,SAAS,MAAM,KAAK,OAAO;AAAA,MAC/B;AAAA,MACA,EAAE,GAAG,QAAQ,KAAK,QAAQ,aAAa;AAAA,MACvC;AAAA,QACE,UAAU,KAAK;AAAA,QACf,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,MACtE;AAAA,IACF;AACA,oBAAgB,OAAO,SAAS,OAAO,QAAQ,MAAM;AACrD,SAAK,qBAAqB,QAAQ,OAAO,OAAO;AAChD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAa,aACX,cACA,MACY;AACZ,WAAO,KAAK,WAAW,SAAS,cAAc,IAAI;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,YACX,cACA,MACY;AACZ,WAAO,KAAK,WAAW,QAAQ,cAAc,IAAI;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAa,mBACX,QACA,QACA,SACyB;AACzB,QAAI,QAAQ,oBAAoB,MAAM;AACpC,YAAM,IAAIC;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,MAAM,KAAK,OAAO,aAA6B,QAAQ,QAAQ;AAAA,MAC5E,UAAU,KAAK;AAAA,MACf,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,IACtE,CAAC;AACD,oBAAgB,OAAO,SAAS,OAAO,QAAQ,MAAM;AACrD,SAAK,qBAAqB,QAAQ,OAAO,OAAO;AAChD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,iBAAiB,QAAwC;AACpE,UAAM,UAAU,KAAK,OAAO,QAAQ,EAAE,UAAU,KAAK,SAAS,CAAC;AAC/D,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,IAAI,MAAM;AACvC,sBAAgB,OAAO,SAAS,OAAO,QAAQ,MAAM;AACrD,WAAK,qBAAqB,QAAQ,OAAO,OAAO;AAChD,aAAO,OAAO;AAAA,IAChB,UAAE;AACA,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAa,8BACX,QACA,QACA,SACwB;AACxB,SAAK,mBAAmB,QAAQ,YAAY;AAC5C,SAAK,aAAa,MAAM;AACxB,UAAM,UAAU,KAAK,OAAO,QAAQ,EAAE,UAAU,KAAK,SAAS,CAAC;AAC/D,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,IAAI,QAAQ,EAAE,GAAG,QAAQ,KAAK,QAAQ,aAAa,CAAC;AACjF,sBAAgB,OAAO,SAAS,OAAO,QAAQ,MAAM;AACrD,WAAK,qBAAqB,QAAQ,OAAO,OAAO;AAChD,aAAO,OAAO;AAAA,IAChB,UAAE;AACA,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAc,WACZ,MACA,cACA,MACY;AACZ,SAAK,mBAAmB,YAAY;AACpC,UAAM,UAAU,KAAK,OAAO,QAAQ,EAAE,UAAU,KAAK,SAAS,CAAC;AAC/D,QAAI;AACF,YAAM,UAAU,CAAC,YACf,KAAK,IAAI,kBAAkB,SAAS,cAAc,KAAK,qBAAqB,KAAK,IAAI,CAAC,CAAC;AACzF,aAAO,SAAS,UACZ,MAAM,QAAQ,aAAa,OAAO,IAClC,MAAM,QAAQ,YAAY,OAAO;AAAA,IACvC,UAAE;AACA,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,mBAAmB,cAA4B;AACrD,QAAI,OAAO,iBAAiB,YAAY,aAAa,WAAW,GAAG;AACjE,YAAM,IAAIA;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aAAa,QAAsB;AACzC,QAAI,CAAC,kBAAkB,KAAK,MAAM,GAAG;AACnC,YAAM,IAAIA;AAAA,QACR;AAAA,MAGF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,qBAAqB,QAAgB,SAA8B;AACzE,UAAM,QAAQ,mCAAmC,OAAO;AACxD,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,YACJ,OAAO,SAAS,gCACZ,GAAG,OAAO,MAAM,GAAG,6BAA6B,CAAC,WACjD;AAIN,YAAQ,KAAK,yBAAyB,EAAE,QAAQ,WAAW,eAAe,MAAM,CAAC;AAAA,EACnF;AACF;AAOO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,YACmB,IACA,cACA,QACjB;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAHgB;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnB,MAAa,IACX,QACA,QACuF;AACvF,QAAI,CAAC,kBAAkB,KAAK,MAAM,GAAG;AACnC,YAAM,IAAIA;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,UAAM,cAAc,MAAM,KAAK,GAAG,IAAO,QAAQ,EAAE,GAAG,QAAQ,KAAK,KAAK,aAAa,CAAC;AACtF,UAAM,UAAU,YAAY;AAC5B,UAAM,UAAU,YAAY;AAC5B,oBAAgB,SAAS,QAAQ,MAAM;AACvC,SAAK,OAAO,QAAQ,OAAO;AAC3B,WAAO,EAAE,SAAS,QAAQ;AAAA,EAC5B;AACF;AA4BA,SAAS,mCAAmC,SAAiD;AAgB3F,QAAM,cAAc;AAIpB,QAAM,MAA+B,CAAC;AACtC,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,YAAY,iBAAiB,CAAC,GAAG;AAClD,UAAM,QAA+B;AAAA,MACnC,UAAU,KAAK,YAAY;AAAA,MAC3B,UAAU,KAAK,YAAY;AAAA,MAC3B,MAAM,KAAK,QAAQ;AAAA,MACnB,OAAO,KAAK,SAAS;AAAA,MACrB,aAAa,KAAK,eAAe;AAAA,IACnC;AACA,UAAM,MAAM,MAAM,QAAQ,MAAM;AAChC,QAAI,OAAO,KAAK,IAAI,GAAG,EAAG;AAC1B,QAAI,IAAK,MAAK,IAAI,GAAG;AACrB,QAAI,KAAK,KAAK;AAAA,EAChB;AACA,aAAW,QAAQ,YAAY,oBAAoB,CAAC,GAAG;AACrD,UAAM,WAAW,KAAK,YAAY;AAClC,QAAI,aAAa,aAAa,aAAa,QAAS;AACpD,UAAM,QAA+B;AAAA,MACnC;AAAA,MACA,UAAU,KAAK,kBAAkB;AAAA,MACjC,MAAM,KAAK,aAAa;AAAA,MACxB,OAAO,KAAK,SAAS;AAAA,MACrB,aAAa,KAAK,eAAe,KAAK,qBAAqB;AAAA,IAC7D;AACA,UAAM,MAAM,MAAM,QAAQ,MAAM,SAAS,MAAM;AAC/C,QAAI,OAAO,KAAK,IAAI,GAAG,EAAG;AAC1B,QAAI,IAAK,MAAK,IAAI,GAAG;AACrB,QAAI,KAAK,KAAK;AAAA,EAChB;AACA,SAAO;AACT;;;AE5YA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAAC;AAAA,OACK;AAEP,IAAM,4BAA4B;AAClC,IAAM,oBAAoB;AAgDnB,SAAS,eAAe,OAAgB,UAA8B,CAAC,GAAU;AACtF,MACE,iBAAiB,wBACjB,iBAAiB,8BACjB,iBAAiB,4BACjB,iBAAiBA,gBACjB;AACA,UAAM;AAAA,EACR;AAEA,QAAM,cAAe,SAAS,CAAC;AAC/B,QAAM,OAAO,OAAO,YAAY,SAAS,WAAW,YAAY,OAAO;AACvE,QAAM,UAAU,OAAO,YAAY,YAAY,WAAW,YAAY,UAAU;AAEhF,MAAI,SAAS,2BAA2B;AACtC,UAAM,OAAO,oBAAoB,SAAS,OAAO;AACjD,QAAI,SAAS,gBAAgB,QAAQ,iBAAiB,QAAW;AAC/D,YAAM,IAAI,yBAAyB,QAAQ,YAAY;AAAA,IACzD;AACA,QAAI,SAAS,YAAY,QAAQ,aAAa,QAAW;AACvD,YAAM,IAAI,qBAAqB,QAAQ,QAAQ;AAAA,IACjD;AACA,QAAI,SAAS,kBAAkB,QAAQ,mBAAmB,QAAW;AACnE,YAAM,IAAI,2BAA2B,QAAQ,cAAc;AAAA,IAC7D;AACA,UAAM,IAAIA;AAAA,MACR,6BAA6B,gBAAgB,OAAO,CAAC,KAAK,WAAW,IAAI;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,mBAAmB;AAC9B,UAAM,IAAIA;AAAA,MACR,qBAAqB,gBAAgB,OAAO,CAAC,KAAK,WAAW,IAAI;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,qBAAqB,gBAAgB,OAAO,CAAC;AAC5D,QAAM,aAAa,OAAO,KAAK,IAAI,MAAM;AACzC,QAAM,gBAAgB,UAAU,KAAK,OAAO,KAAK;AACjD,QAAM,IAAIA,eAAc,GAAG,MAAM,GAAG,UAAU,GAAG,aAAa,EAAE;AAClE;AAEA,SAAS,oBACP,SACA,SAC0C;AAC1C,MAAI,QAAQ,SAAS,OAAW,QAAO,QAAQ;AAO/C,MAAI,QAAQ,WAAW,cAAc,EAAG,QAAO;AAC/C,MAAI,QAAQ,SAAS,eAAe,EAAG,QAAO;AAC9C,SAAO;AACT;AAEA,SAAS,gBAAgB,SAAqC;AAC5D,SAAO,QAAQ,YAAY,OAAO,QAAQ,SAAS,KAAK;AAC1D;;;ACzDA,SAAS,iBAAAC,sBAAqB;AAS9B,IAAM,oBAAoB;AAS1B,IAAM,4BAA4B;AASlC,IAAM,6BAA6B;AAmBnC,IAAM,2BAA2B,sBAAsB,EAAE,gBAAgB,KAAK,CAAC;AAC/E,IAAM,iCAAiC,4BAA4B;AAEnE,IAAM,6BAA6B;AAAA;AAAA,SAE1B,wBAAwB;AAAA;AAAA;AAAA;AAKjC,IAAM,4BAA4B;AAAA;AAAA;AAAA,SAGzB,wBAAwB;AAAA;AAAA;AAAA;AAKjC,IAAM,kCAAkC;AAAA;AAAA,SAE/B,8BAA8B;AAAA;AAAA;AAAA;AAKvC,IAAM,iCAAiC;AAAA;AAAA;AAAA,SAG9B,8BAA8B;AAAA;AAAA;AAAA;AAgBvC,gBAAuB,UACrB,MACA,cAC4B;AAC5B,MAAI,WAAW;AAEf,MAAI;AACJ,SAAO,MAAM;AACX,UAAM,QAAQ,WAAW,SAAY,6BAA6B;AAClE,UAAM,SACJ,WAAW,SACP,EAAE,WAAW,OAAO,iBAAiB,EAAE,IACvC,EAAE,QAAQ,WAAW,OAAO,iBAAiB,EAAE;AACrD,UAAM,SAAS,MAAM,KAAK,aAAa,OAAO,QAAQ,EAAE,cAAc,SAAS,OAAO,CAAC;AACvF,UAAM,WAAW,OAAO,QAAQ,IAAI,CAAC,WAAW,iBAAiB,MAAM,CAAC;AACxE,UAAM,SAAS,SAAS,SAAS;AACjC,QAAI,SAAS,SAAS,GAAG;AACvB,eAAS,SAAS,SAAS,SAAS,CAAC,EAAG;AACxC,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,UAAU;AAAA,QACV;AAAA,MACF;AAAA,IACF;AACA,QAAI,OAAQ;AAAA,EACd;AAEA,MAAI;AACJ,SAAO,MAAM;AACX,UAAM,QACJ,cAAc,SAAY,kCAAkC;AAC9D,UAAM,SACJ,cAAc,SACV,EAAE,WAAW,OAAO,iBAAiB,EAAE,IACvC,EAAE,QAAQ,WAAW,WAAW,OAAO,iBAAiB,EAAE;AAChE,UAAM,SAAS,MAAM,KAAK,aAAa,OAAO,QAAQ,EAAE,cAAc,SAAS,OAAO,CAAC;AACvF,UAAM,gBAAgB,OAAO,QAAQ,IAAI,CAAC,WAAW,uBAAuB,MAAM,CAAC;AACnF,UAAM,SAAS,cAAc,SAAS;AACtC,QAAI,cAAc,SAAS,GAAG;AAC5B,kBAAY,cAAc,cAAc,SAAS,CAAC,EAAG;AACrD,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,UAAU;AAAA,QACV;AAAA,MACF;AAAA,IACF;AACA,QAAI,OAAQ;AAAA,EACd;AAEA,MAAI,aAAa,GAAG;AAClB,UAAM;AAAA,MACJ,MAAM;AAAA,MACN,MAAM,CAAC;AAAA,MACP,UAAU;AAAA,MACV,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAkBA,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuC9B,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwD9B,SAAS,8BAA8B,kBAAkC;AACvE,QAAM,OAAO,2BAA2B,gBAAgB;AACxD,SAAO;AAAA;AAAA;AAAA;AAAA,gBAIO,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBpB;AAEA,SAAS,8BAA8B,kBAAkC;AACvE,QAAM,OAAO,2BAA2B,gBAAgB;AACxD,SAAO;AAAA;AAAA;AAAA;AAAA,eAIM,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BnB;AAkBA,eAAsB,WACpB,MACA,cACA,MACA,SAC2B;AAC3B,QAAM,YAAY,SAAS,uBAAuB;AAClD,QAAM,MAAO,WAAW,CAAC;AACzB,QAAM,YAAY,IAAI,aAAa;AACnC,QAAM,cAAc,IAAI,eAAe;AAEvC,QAAM,cAA8B,CAAC;AACrC,QAAM,mBAAyC,CAAC;AAChD,aAAW,SAAS,MAAM;AACxB,QAAI,MAAM,SAAU,aAAY,KAAK,GAAG,MAAM,QAAQ;AACtD,QAAI,MAAM,cAAe,kBAAiB,KAAK,GAAG,MAAM,aAAa;AAAA,EACvE;AAEA,QAAM,SAAiD,CAAC;AAKxD,QAAM,eAAe,gBAAgB,aAAa,SAAS;AAC3D,QAAM,gBAAgB,MAAM;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,CAAC,UAAU,kBAAkB,MAAM,cAAc,OAAO,SAAS;AAAA,EACnE;AAEA,MAAI,mBAAmB;AACvB,aAAW,OAAO,eAAe;AAC/B,wBAAoB,IAAI;AACxB,WAAO,KAAK,GAAG,IAAI,MAAM;AAAA,EAC3B;AAEA,QAAM,qBAAqB,mCAAmC,kBAAkB,SAAS;AACzF,QAAM,sBAAsB,MAAM;AAAA,IAChC;AAAA,IACA;AAAA,IACA,CAAC,UAAU,wBAAwB,MAAM,cAAc,OAAO,SAAS;AAAA,EACzE;AAEA,MAAI,wBAAwB;AAC5B,aAAW,OAAO,qBAAqB;AACrC,6BAAyB,IAAI;AAC7B,WAAO,KAAK,GAAG,IAAI,MAAM;AAAA,EAC3B;AAEA,SAAO,EAAE,kBAAkB,uBAAuB,OAAO;AAC3D;AAYA,eAAe,kBACb,MACA,cACA,UACA,WACsB;AACtB,MAAI,SAAS,WAAW,EAAG,QAAO,EAAE,UAAU,GAAG,QAAQ,CAAC,EAAE;AAK5D,QAAM,OAAO,SAAS,IAAI,CAAC,YAAY;AAAA,IACrC,GAAG,eAAe,MAAM;AAAA,IACxB,gBAAgB,yBAAyB,OAAO,UAAU;AAAA,EAC5D,EAAE;AACF,QAAM,QAAQ,YAAY,wBAAwB;AAElD,MAAI;AACF,UAAM,KAAK,aAAa,OAAO,EAAE,KAAK,GAAG,EAAE,aAAa,CAAC;AACzD,WAAO,EAAE,UAAU,SAAS,QAAQ,QAAQ,CAAC,EAAE;AAAA,EACjD,SAAS,KAAK;AACZ,WAAO,kBAAkB,MAAM,cAAc,UAAU,WAAW,GAAG;AAAA,EACvE;AACF;AAQA,eAAe,kBACb,MACA,cACA,UACA,WACA,YACsB;AACtB,QAAM,SAAiD,CAAC;AACxD,MAAI,WAAW;AACf,QAAM,QAAQ,YAAY,wBAAwB;AAClD,aAAW,UAAU,UAAU;AAC7B,QAAI;AACF,YAAM,OAAO;AAAA,QACX;AAAA,UACE,GAAG,eAAe,MAAM;AAAA,UACxB,gBAAgB,yBAAyB,OAAO,UAAU;AAAA,QAC5D;AAAA,MACF;AACA,YAAM,KAAK,aAAa,OAAO,EAAE,KAAK,GAAG,EAAE,aAAa,CAAC;AACzD;AAAA,IACF,SAAS,QAAQ;AACf,aAAO,KAAK;AAAA,QACV,MAAM,UAAU,OAAO,EAAE;AAAA,QACzB,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,MACjE,CAAC;AAAA,IACH;AAAA,EACF;AAIA,MAAI,OAAO,WAAW,KAAK,WAAW,SAAS,QAAQ;AACrD,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,OAAO,sBAAsB,QAAQ,WAAW,UAAU,OAAO,UAAU;AAAA,IAC7E,CAAC;AAAA,EACH;AACA,SAAO,EAAE,UAAU,OAAO;AAC5B;AAEA,eAAe,wBACb,MACA,cACA,OACA,WACsB;AACtB,MAAI,MAAM,KAAK,WAAW,EAAG,QAAO,EAAE,UAAU,GAAG,QAAQ,CAAC,EAAE;AAI9D,QAAM,OAAO,MAAM,KAAK,IAAI,CAAC,QAAQ,qBAAqB,GAAG,CAAC;AAC9D,QAAM,QAAQ,YACV,8BAA8B,MAAM,gBAAgB,IACpD,8BAA8B,MAAM,gBAAgB;AAExD,MAAI;AACF,UAAM,SAAS,MAAM,KAAK,aAAa,OAAO,EAAE,KAAK,GAAG,EAAE,aAAa,CAAC;AACxE,UAAM,cAAc,oBAAI,IAAY;AACpC,eAAW,UAAU,OAAO,SAAS;AACnC,YAAM,KAAK,OAAO,IAAI,IAAI;AAC1B,UAAI,OAAO,OAAO,SAAU,aAAY,IAAI,EAAE;AAAA,IAChD;AACA,UAAM,SAAiD,CAAC;AACxD,eAAW,OAAO,MAAM,MAAM;AAC5B,UAAI,CAAC,YAAY,IAAI,IAAI,EAAE,GAAG;AAC5B,eAAO,KAAK;AAAA,UACV,MAAM,gBAAgB,IAAI,EAAE;AAAA,UAC5B,OAAO,4CAA4C,IAAI,cAAc,YAAY,IAAI,cAAc;AAAA,QACrG,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO,EAAE,UAAU,YAAY,MAAM,OAAO;AAAA,EAC9C,SAAS,KAAK;AACZ,WAAO,wBAAwB,MAAM,cAAc,OAAO,WAAW,GAAG;AAAA,EAC1E;AACF;AAEA,eAAe,wBACb,MACA,cACA,OACA,WACA,YACsB;AACtB,QAAM,SAAiD,CAAC;AACxD,MAAI,WAAW;AACf,QAAM,QAAQ,YACV,8BAA8B,MAAM,gBAAgB,IACpD,8BAA8B,MAAM,gBAAgB;AACxD,aAAW,OAAO,MAAM,MAAM;AAC5B,QAAI;AACF,YAAM,OAAO,CAAC,qBAAqB,GAAG,CAAC;AACvC,YAAM,SAAS,MAAM,KAAK,aAAa,OAAO,EAAE,KAAK,GAAG,EAAE,aAAa,CAAC;AACxE,UAAI,OAAO,QAAQ,WAAW,GAAG;AAC/B;AAAA,MACF,OAAO;AACL,eAAO,KAAK;AAAA,UACV,MAAM,gBAAgB,IAAI,EAAE;AAAA,UAC5B,OAAO,4CAA4C,IAAI,cAAc,YAAY,IAAI,cAAc;AAAA,QACrG,CAAC;AAAA,MACH;AAAA,IACF,SAAS,QAAQ;AACf,aAAO,KAAK;AAAA,QACV,MAAM,gBAAgB,IAAI,EAAE;AAAA,QAC5B,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,MACjE,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,OAAO,WAAW,KAAK,WAAW,MAAM,KAAK,QAAQ;AACvD,WAAO,KAAK;AAAA,MACV,MAAM,sBAAsB,MAAM,gBAAgB;AAAA,MAClD,OAAO,sBAAsB,QAAQ,WAAW,UAAU,OAAO,UAAU;AAAA,IAC7E,CAAC;AAAA,EACH;AACA,SAAO,EAAE,UAAU,OAAO;AAC5B;AAIA,SAAS,gBAAmB,OAAY,WAA0B;AAChE,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,MAAa,CAAC;AACpB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;AAChD,QAAI,KAAK,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;AAAA,EACxC;AACA,SAAO;AACT;AAUA,SAAS,mCACP,eACA,WACqB;AACrB,QAAM,UAAU,oBAAI,IAAkC;AACtD,aAAW,OAAO,eAAe;AAC/B,UAAM,OAAO,QAAQ,IAAI,IAAI,gBAAgB;AAC7C,QAAI,KAAM,MAAK,KAAK,GAAG;AAAA,QAClB,SAAQ,IAAI,IAAI,kBAAkB,CAAC,GAAG,CAAC;AAAA,EAC9C;AACA,QAAM,MAA2B,CAAC;AAClC,aAAW,CAAC,kBAAkB,IAAI,KAAK,SAAS;AAC9C,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,WAAW;AAC/C,UAAI,KAAK,EAAE,kBAAkB,MAAM,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;AAAA,IACnE;AAAA,EACF;AACA,SAAO;AACT;AAaA,eAAsB,WACpB,OACA,aACA,IACc;AACd,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,MAAI,cAAc,GAAG;AACnB,UAAM,IAAIA,eAAc,6CAA6C,WAAW,IAAI;AAAA,EACtF;AACA,QAAM,MAAM,KAAK,IAAI,aAAa,MAAM,MAAM;AAC9C,QAAM,UAAe,IAAI,MAAM,MAAM,MAAM;AAC3C,MAAI,YAAY;AAChB,QAAM,UAAgC,CAAC;AACvC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,YAAQ;AAAA,OACL,YAA2B;AAC1B,eAAO,MAAM;AACX,gBAAM,IAAI;AACV,cAAI,KAAK,MAAM,OAAQ;AACvB,kBAAQ,CAAC,IAAI,MAAM,GAAG,MAAM,CAAC,CAAE;AAAA,QACjC;AAAA,MACF,GAAG;AAAA,IACL;AAAA,EACF;AACA,QAAM,QAAQ,IAAI,OAAO;AACzB,SAAO;AACT;;;ACnnBA,SAAS,qBAAqB,iBAAAC,sBAAqB;AAkBnD,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+B5B,IAAM,0BAA0B,sBAAsB;AACtD,IAAM,yBAAyB,sBAAsB,EAAE,gBAAgB,KAAK,CAAC;AAE7E,IAAM,yBAAyB,0DAA0D,uBAAuB;AAChH,IAAM,wBAAwB,0DAA0D,sBAAsB;AAE9G,IAAM,iCAAiC,8DAA8D,uBAAuB;AAC5H,IAAM,gCAAgC,8DAA8D,sBAAsB;AAK1H,IAAM,8BAA8B,oEAAoE,uBAAuB;AAC/H,IAAM,6BAA6B,oEAAoE,sBAAsB;AAU7H,eAAsB,aACpB,MACA,cACA,QACuB;AAIvB,QAAM,iBAAiB,yBAAyB,OAAO,UAAU;AACjE,MAAI;AACF,UAAM,KAAK;AAAA,MACT;AAAA,MACA,EAAE,GAAG,eAAe,MAAM,GAAG,eAAe;AAAA,MAC5C,EAAE,aAAa;AAAA,IACjB;AAAA,EACF,SAAS,KAAK;AACZ,mBAAe,KAAK;AAAA,MAClB,MAAM;AAAA,MACN,UAAU,OAAO;AAAA,MACjB,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAMA,eAAsB,UACpB,MACA,cACA,UACA,SAC8B;AAC9B,QAAM,QACJ,SAAS,mBAAmB,OAAO,wBAAwB;AAC7D,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IACA,EAAE,IAAI,SAAS;AAAA,IACf,EAAE,cAAc,SAAS,OAAO;AAAA,EAClC;AACA,QAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,MAAI,WAAW,OAAW,QAAO;AACjC,SAAO,iBAAiB,MAAM;AAChC;AAOA,eAAsB,gBACpB,MACA,cACA,MACA,SAC8B;AAC9B,QAAM,QACJ,SAAS,mBAAmB,OACxB,gCACA;AACN,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IACA,EAAE,KAAK;AAAA,IACP,EAAE,cAAc,SAAS,OAAO;AAAA,EAClC;AACA,QAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,MAAI,WAAW,OAAW,QAAO;AACjC,SAAO,iBAAiB,MAAM;AAChC;AAOA,eAAsB,YACpB,MACA,cACA,WACA,SACoC;AACpC,QAAM,MAAM,oBAAI,IAA0B;AAC1C,MAAI,UAAU,WAAW,EAAG,QAAO;AAEnC,QAAM,QACJ,SAAS,mBAAmB,OACxB,6BACA;AACN,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IACA,EAAE,KAAK,UAAU;AAAA,IACjB,EAAE,cAAc,SAAS,OAAO;AAAA,EAClC;AACA,aAAW,UAAU,OAAO,SAAS;AACnC,UAAM,SAAS,iBAAiB,MAAM;AACtC,QAAI,IAAI,OAAO,IAAI,MAAM;AAAA,EAC3B;AACA,SAAO;AACT;AA0BA,eAAsB,aACpB,MACA,cACA,UACA,SACuB;AAKvB,MAAI,iBAAiD;AACrD,MAAI,QAAQ,eAAe,QAAW;AACpC,qBAAiB,yBAAyB,QAAQ,UAAU;AAAA,EAC9D;AAMA,MAAI,eAAyB,CAAC;AAC9B,MAAI,mBAAmB,MAAM;AAC3B,UAAM,aAAa,MAAM,KAAK;AAAA,MAC5B;AAAA,MACA,EAAE,IAAI,SAAS;AAAA,MACf,EAAE,cAAc,SAAS,OAAO;AAAA,IAClC;AACA,UAAM,aAAa,WAAW,QAAQ,CAAC;AACvC,QAAI,eAAe,OAAW,OAAM,IAAI,oBAAoB,QAAQ;AACpE,UAAM,QAAQ,WAAW,IAAI,OAAO;AACpC,UAAM,gBACJ,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC9D,QACD,CAAC;AACP,UAAM,mBAAmB,OAAO,KAAK,aAAa,EAAE;AAAA,MAClD,CAAC,MAAM,CAAC,8BAA8B,IAAI,CAAC;AAAA,IAC7C;AACA,mBAAe,iBAAiB,OAAO,CAAC,MAAM,EAAE,KAAK,eAAgB;AAAA,EACvE;AAEA,QAAM,WAAqB,CAAC;AAC5B,QAAM,SAAkC,EAAE,IAAI,SAAS;AAEvD,QAAM,WAAW,CAAC,KAAa,WAAmB,UAAyB;AACzE,aAAS,KAAK,KAAK,GAAG,OAAO,SAAS,EAAE;AACxC,WAAO,SAAS,IAAI;AAAA,EACtB;AAEA,MAAI,QAAQ,eAAe,OAAW,UAAS,cAAc,cAAc,QAAQ,UAAU;AAC7F,MAAI,QAAQ,UAAU,OAAW,UAAS,SAAS,SAAS,QAAQ,KAAK;AACzE,MAAI,QAAQ,SAAS,OAAW,UAAS,QAAQ,QAAQ,QAAQ,IAAI;AAErE,MAAI,QAAQ,YAAY,OAAW,UAAS,WAAW,WAAW,QAAQ,OAAO;AACjF,MAAI,QAAQ,eAAe,QAAW;AACpC,aAAS,cAAc,cAAc,KAAK,UAAU,QAAQ,UAAU,CAAC;AAAA,EACzE;AACA,MAAI,QAAQ,SAAS,OAAW,UAAS,QAAQ,QAAQ,QAAQ,IAAI;AACrE,MAAI,QAAQ,eAAe,OAAW,UAAS,cAAc,cAAc,QAAQ,UAAU;AAC7F,MAAI,QAAQ,cAAc,OAAW,UAAS,aAAa,aAAa,QAAQ,SAAS;AAOzF,QAAM,IAAI,QAAQ;AAClB,WAAS,cAAc,cAAc,EAAE,UAAU;AACjD,WAAS,kBAAkB,kBAAkB,EAAE,cAAc;AAC7D,WAAS,cAAc,cAAc,EAAE,UAAU;AACjD,MAAI,EAAE,2BAA2B,QAAW;AAC1C,aAAS,0BAA0B,0BAA0B,EAAE,sBAAsB;AAAA,EACvF;AACA,MAAI,EAAE,wBAAwB,QAAW;AACvC,aAAS,uBAAuB,uBAAuB,EAAE,mBAAmB;AAAA,EAC9E;AASA,MAAI,kBAAkB;AACtB,MAAI,mBAAmB,MAAM;AAC3B,WAAO,gBAAgB,IAAI;AAC3B,sBAAkB;AAAA,EACpB;AACA,MAAI,eAAe;AACnB,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,WAAW,aAAa,IAAI,CAAC,MAAM,KAAK,0BAA0B,CAAC,CAAC,EAAE;AAC5E,mBAAe,WAAW,SAAS,KAAK,IAAI,CAAC;AAAA,EAC/C;AAEA,QAAM,SACJ,uDACO,SAAS,KAAK,IAAI,CAAC,GACvB,eAAe,GACf,YAAY,WACL,uBAAuB;AAEnC,QAAM,SAAS,MAAM,KAAK,aAAa,QAAQ,QAAQ,EAAE,aAAa,CAAC;AACvE,QAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,MAAI,WAAW,OAAW,OAAM,IAAI,oBAAoB,QAAQ;AAChE,SAAO,iBAAiB,MAAM;AAChC;AAUA,eAAsB,aACpB,MACA,cACA,UACe;AACf,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IACA,EAAE,IAAI,SAAS;AAAA,IACf,EAAE,aAAa;AAAA,EACjB;AACA,QAAM,UAAU,mBAAmB,OAAO,QAAQ,CAAC,GAAG,IAAI,SAAS,KAAK,CAAC;AACzE,MAAI,YAAY,EAAG,OAAM,IAAI,oBAAoB,QAAQ;AAC3D;AASA,eAAsB,eACpB,MACA,cACA,KACoD;AACpD,MAAI,IAAI,WAAW,EAAG,QAAO,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE;AACzD,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IAEA,EAAE,IAAI;AAAA,IACN,EAAE,aAAa;AAAA,EACjB;AACA,QAAM,UAAoB,CAAC;AAC3B,aAAW,UAAU,OAAO,SAAS;AACnC,UAAM,KAAK,OAAO,IAAI,SAAS;AAC/B,QAAI,OAAO,OAAO,SAAU,SAAQ,KAAK,EAAE;AAAA,EAC7C;AACA,QAAM,aAAa,IAAI,IAAI,OAAO;AAClC,QAAM,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;AACvD,SAAO,EAAE,SAAS,SAAS;AAC7B;AAOA,IAAM,wBAAwB,sBAAsB,EAAE,OAAO,IAAI,CAAC;AAClE,IAAM,uBAAuB,sBAAsB,EAAE,OAAO,KAAK,gBAAgB,KAAK,CAAC;AACvF,IAAM,2BAA2B,sBAAsB,EAAE,OAAO,OAAO,CAAC;AACxE,IAAM,0BAA0B,sBAAsB,EAAE,OAAO,QAAQ,gBAAgB,KAAK,CAAC;AA0BtF,SAAS,uBACd,OACA,SAC0D;AAC1D,QAAM,EAAE,OAAO,2BAA2B,IAAI;AAC9C,QAAM,SAAkC,CAAC;AACzC,QAAM,aAAuB,CAAC;AAE9B,MAAI,4BAA4B;AAC9B,eAAW,KAAK,GAAG,KAAK,sBAAsB;AAAA,EAChD;AAEA,MAAI,MAAM,eAAe,MAAM,YAAY,SAAS,GAAG;AACrD,eAAW,KAAK,GAAG,KAAK,6BAA6B;AACrD,WAAO,aAAa,IAAI,MAAM;AAAA,EAChC;AAEA,MAAI,MAAM,YAAY;AACpB,QAAI,IAAI;AACR,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,UAAU,GAAG;AAI3D,gCAA0B,GAAG;AAC7B,UAAI,CAAC,sBAAsB,KAAK,GAAG;AACjC,cAAM,IAAIA;AAAA,UACR,iCAAiC,GAAG;AAAA,QAItC;AAAA,MACF;AACA,YAAM,YAAY,OAAO,CAAC;AAC1B,iBAAW,KAAK,GAAG,KAAK,IAAI,GAAG,OAAO,SAAS,EAAE;AACjD,aAAO,SAAS,IAAI;AACpB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM,YAAY;AACpB,QAAI,MAAM,WAAW,mBAAmB,MAAM,WAAW,gBAAgB,SAAS,GAAG;AACnF,iBAAW;AAAA,QACT,IAAI,KAAK,yCAAyC,KAAK;AAAA,MACzD;AACA,aAAO,SAAS,IAAI,MAAM,WAAW;AAAA,IACvC;AACA,QAAI,MAAM,WAAW,UAAU,MAAM,WAAW,OAAO,SAAS,GAAG;AACjE,iBAAW;AAAA,QACT,IAAI,KAAK,4BAA4B,KAAK;AAAA,MAC5C;AACA,aAAO,QAAQ,IAAI,MAAM,WAAW;AAAA,IACtC;AACA,QAAI,MAAM,WAAW,WAAW;AAC9B,iBAAW;AAAA,QACT,KAAK,KAAK,+BAA+B,KAAK,8BACrC,KAAK,gCAAgC,KAAK;AAAA,MACrD;AAGA,aAAO,UAAU,IAAI,MAAM,WAAW,UAAU;AAChD,aAAO,QAAQ,IAAI,MAAM,WAAW,UAAU;AAAA,IAChD;AAAA,EACF;AAEA,QAAM,cAAc,WAAW,SAAS,IAAI,SAAS,WAAW,KAAK,OAAO,CAAC,KAAK;AAClF,SAAO,EAAE,aAAa,OAAO;AAC/B;AA8BA,eAAsB,aACpB,MACA,cACA,OACA,SACwC;AACxC,QAAM,iBAAiB,SAAS,mBAAmB;AACnD,QAAM,kBAAkB;AAAA,IACtB,MAAM,OAAO,MAAM,MAAM;AAAA,IACzB,OAAO,OAAO,MAAM,KAAK;AAAA,EAC3B;AAEA,MAAI;AACJ,MAAI;AAEJ,MAAI,MAAM,eAAe,UAAa,MAAM,eAAe,IAAI;AAC7D,UAAM,aAAa,iBAAiB,0BAA0B;AAC9D,UAAM,QAAQ,uBAAuB,OAAO;AAAA,MAC1C,OAAO;AAAA,MACP,4BAA4B;AAAA,IAC9B,CAAC;AAID,UAAM,gBAAgB;AACtB,UAAM,gBACJ,MAAM,YAAY,SAAS,IACvB,SAAS,aAAa,QAAQ,MAAM,YAAY,MAAM,SAAS,MAAM,CAAC,KACtE,SAAS,aAAa;AAC5B,UAAM,aACJ,gFACG,aAAa,WACN,UAAU;AAEtB,UAAM,cACJ,yEACG,aAAa;AAElB,UAAM,YAAY,EAAE,MAAM,MAAM,WAAW;AAC3C,UAAM,CAAC,YAAY,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC5C,KAAK;AAAA,QACH;AAAA,QACA,EAAE,GAAG,MAAM,QAAQ,GAAG,WAAW,GAAG,gBAAgB;AAAA,QACpD,EAAE,cAAc,SAAS,OAAO;AAAA,MAClC;AAAA,MACA,KAAK;AAAA,QACH;AAAA,QACA,EAAE,GAAG,MAAM,QAAQ,GAAG,UAAU;AAAA,QAChC,EAAE,cAAc,SAAS,OAAO;AAAA,MAClC;AAAA,IACF,CAAC;AACD,cAAU,WAAW;AACrB,kBAAc;AAAA,EAChB,OAAO;AACL,UAAM,aAAa,iBAAiB,uBAAuB;AAC3D,UAAM,QAAQ,uBAAuB,OAAO;AAAA,MAC1C,OAAO;AAAA,MACP,4BAA4B;AAAA,IAC9B,CAAC;AACD,UAAM,aACJ,qBAAqB,MAAM,WAAW,WAC5B,UAAU;AAEtB,UAAM,cAAc,qBAAqB,MAAM,WAAW;AAC1D,UAAM,CAAC,YAAY,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC5C,KAAK;AAAA,QACH;AAAA,QACA,EAAE,GAAG,MAAM,QAAQ,GAAG,gBAAgB;AAAA,QACtC,EAAE,cAAc,SAAS,OAAO;AAAA,MAClC;AAAA,MACA,KAAK,aAAa,aAAa,MAAM,QAAQ,EAAE,cAAc,SAAS,OAAO,CAAC;AAAA,IAChF,CAAC;AACD,cAAU,WAAW;AACrB,kBAAc;AAAA,EAChB;AAEA,QAAM,QAAQ,QAAQ,IAAI,CAAC,WAAW,iBAAiB,MAAM,CAAC;AAC9D,QAAM,WAAW,YAAY,QAAQ,CAAC,GAAG,IAAI,OAAO;AACpD,QAAM,QAAQ,aAAa,SAAY,IAAI,mBAAmB,QAAQ;AACtE,QAAM,UAAU,MAAM,SAAS,MAAM,SAAS;AAE9C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,EAChB;AACF;AAkBA,eAAsB,qBACpB,MACA,cACA,YACgF;AAChF,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,EAAE,WAAW;AAAA,IACb,EAAE,aAAa;AAAA,EACjB;AACA,QAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,MAAI,WAAW,QAAW;AACxB,WAAO,EAAE,iBAAiB,GAAG,sBAAsB,EAAE;AAAA,EACvD;AACA,QAAM,kBAAkB,mBAAmB,OAAO,IAAI,UAAU,KAAK,CAAC;AACtE,QAAM,uBAAuB,mBAAmB,OAAO,IAAI,MAAM,KAAK,CAAC;AACvE,SAAO,EAAE,iBAAiB,qBAAqB;AACjD;;;ACxoBA;AAAA,EACE,uBAAAC;AAAA,EACA;AAAA,OACK;AAWP,IAAM,0BAA0B,4BAA4B;AAkB5D,eAAsB,mBACpB,MACA,cACA,cAC6B;AAC7B,QAAM,UAAU,2BAA2B,aAAa,gBAAgB;AAExE,QAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,kBAKC,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBvB,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IACA,qBAAqB,YAAY;AAAA,IACjC,EAAE,aAAa;AAAA,EACjB;AAEA,QAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,MAAI,WAAW,QAAW;AAIxB,UAAM,IAAIC,qBAAoB,aAAa,cAAc;AAAA,EAC3D;AACA,QAAM,WAAW,OAAO,IAAI,UAAU,MAAM;AAC5C,QAAM,WAAW,OAAO,IAAI,UAAU,MAAM;AAC5C,MAAI,SAAU,OAAM,IAAIA,qBAAoB,aAAa,cAAc;AACvE,MAAI,SAAU,OAAM,IAAIA,qBAAoB,aAAa,cAAc;AACvE,SAAO;AACT;AAQA,eAAsB,gBACpB,MACA,cACA,gBACoC;AAKpC,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB,4DAA4D,uBAAuB;AAAA,IACnF,EAAE,OAAO,eAAe;AAAA,IACxB,EAAE,cAAc,SAAS,OAAO;AAAA,EAClC;AACA,QAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,MAAI,WAAW,OAAW,QAAO;AACjC,SAAO,uBAAuB,MAAM;AACtC;AAoBA,eAAsB,uBACpB,MACA,cACA,UACA,SAC8C;AAC9C,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,SAAS,SAAS,UAAU;AAClC,QAAM,YAAY,SAAS,aAAa;AACxC,QAAM,kBAAkB,SAAS;AACjC,QAAM,qBAAqB,mBAAmB,QAAQ,gBAAgB,SAAS;AAM/E,QAAM,SAAkC;AAAA,IACtC,KAAK;AAAA,IACL,QAAQ,OAAO,MAAM;AAAA,IACrB,OAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,aAAqB,SAAS,qBAAqB,QAAQ,QAAQ,kBAAkB,SAAS,IAChG,gBAAgB,QAAQ,mBAAmB,MAAM,IACjD;AAEJ,QAAM,EAAE,YAAY,YAAY,IAAI,+BAA+B,WAAW,UAAU;AAExF,QAAM,CAAC,YAAY,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,IAClD,KAAK,aAAa,YAAY,QAAQ,EAAE,cAAc,SAAS,OAAO,CAAC;AAAA,IACvE,qBACI,QAAQ,QAAQ,IAAI,IACpB,KAAK,aAAa,aAAa,QAAQ,EAAE,cAAc,SAAS,OAAO,CAAC;AAAA,EAC9E,CAAC;AAED,MAAI,QAAQ,WAAW,QAAQ,IAAI,CAAC,WAAW,uBAAuB,MAAM,CAAC;AAC7E,MAAI,oBAAoB;AACtB,YAAQ,MAAM,OAAO,CAAC,QAAQ,uBAAuB,IAAI,YAAY,eAAe,CAAC;AAAA,EACvF;AAEA,MAAI;AACJ,MAAI,gBAAgB,MAAM;AACxB,UAAM,cAAc,YAAY,QAAQ,CAAC;AACzC,YAAQ,gBAAgB,SACpB,mBAAmB,YAAY,IAAI,OAAO,KAAK,CAAC,IAChD;AAAA,EACN;AACA,QAAM,UACJ,UAAU,SAAY,SAAS,WAAW,QAAQ,SAAS,QAAQ,WAAW,QAAQ,WAAW;AAEnG,SAAO,EAAE,OAAO,OAAO,SAAS,OAAO,OAAO;AAChD;AAMA,eAAsB,mBACpB,MACA,cACA,gBACe;AAKf,QAAM,KAAK;AAAA,IACT;AAAA,IACA,EAAE,OAAO,eAAe;AAAA,IACxB,EAAE,aAAa;AAAA,EACjB;AACF;AAQA,eAAsB,oBACpB,MACA,cACA,KACoD;AACpD,MAAI,IAAI,WAAW,EAAG,QAAO,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE;AAKzD,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IAEA,EAAE,IAAI;AAAA,IACN,EAAE,aAAa;AAAA,EACjB;AACA,QAAM,UAAoB,CAAC;AAC3B,aAAW,UAAU,OAAO,SAAS;AACnC,UAAM,KAAK,OAAO,IAAI,SAAS;AAC/B,QAAI,OAAO,OAAO,SAAU,SAAQ,KAAK,EAAE;AAAA,EAC7C;AACA,QAAM,aAAa,IAAI,IAAI,OAAO;AAClC,QAAM,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;AACvD,SAAO,EAAE,SAAS,SAAS;AAC7B;AAQA,eAAsB,0BACpB,MACA,cACA,kBAC2C;AAC3C,QAAM,UAAU,2BAA2B,gBAAgB;AAI3D,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB,eAAe,OAAO;AAAA,IACtB,CAAC;AAAA,IACD,EAAE,aAAa;AAAA,EACjB;AACA,SAAO,EAAE,sBAAsB,OAAO,QAAQ,OAAO;AACvD;AAcA,SAAS,gBACP,mBACA,QACQ;AACR,SAAO,UAAU,IAAI;AACrB,SAAO;AACT;AAWA,SAAS,+BACP,WACA,YAC6C;AAC7C,MAAI,cAAc,QAAQ;AACxB,UAAMC,cACJ,+EAA+E,UAAU,WAC/E,uBAAuB;AACnC,UAAMC,eACJ,+EAA+E,UAAU;AAE3F,WAAO,EAAE,YAAAD,aAAY,aAAAC,aAAY;AAAA,EACnC;AAKA,QAAM,iBACJ,cAAc,QACV,4EACA;AACN,QAAM,eACJ,cAAc,QACV,iGACA;AAEN,QAAM,aACJ;AAAA,UACW,cAAc,GAAG,UAAU,WAAW,uBAAuB;AAAA;AAAA,UAE7D,YAAY,GAAG,UAAU,WAAW,uBAAuB;AAAA;AAAA,SAE5D,uBAAuB,CAAC;AAEpC,QAAM,cACJ;AAAA,UACW,cAAc,GAAG,UAAU;AAAA;AAAA,UAE3B,YAAY,GAAG,UAAU;AAAA;AAAA;AAItC,SAAO,EAAE,YAAY,YAAY;AACnC;AAUA,SAAS,yBAAiC;AACxC,SAAO,wBACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS;AACb,UAAM,WAAW,KAAK,KAAK,EAAE,MAAM,MAAM;AACzC,UAAM,aAAa,SAAS,CAAC,KAAK,SAAS,CAAC,KAAK,IAAI,KAAK;AAC1D,WAAO,GAAG,SAAS,OAAO,SAAS;AAAA,EACrC,CAAC,EACA,KAAK,IAAI;AACd;;;AClXA,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAY3B,IAAM,2BAA2B;AAAA;AAAA;AAAA;AA2BjC,eAAsB,mBACpB,MACA,cACA,YAC0B;AAC1B,QAAM,CAAC,cAAc,kBAAkB,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC3D,KAAK,aAAa,oBAAoB,CAAC,GAAG,EAAE,cAAc,SAAS,OAAO,CAAC;AAAA,IAC3E,KAAK,aAAa,0BAA0B,CAAC,GAAG,EAAE,cAAc,SAAS,OAAO,CAAC;AAAA,EACnF,CAAC;AAED,QAAM,sBAA8C,CAAC;AACrD,MAAI,cAAc;AAClB,aAAW,UAAU,aAAa,SAAS;AACzC,UAAM,OAAO,OAAO,IAAI,MAAM;AAC9B,QAAI,OAAO,SAAS,SAAU;AAC9B,UAAM,QAAQ,mBAAmB,OAAO,IAAI,OAAO,KAAK,CAAC;AACzD,wBAAoB,IAAI,IAAI;AAC5B,mBAAe;AAAA,EACjB;AAEA,QAAM,4BAAoD,CAAC;AAC3D,MAAI,oBAAoB;AACxB,aAAW,UAAU,mBAAmB,SAAS;AAC/C,UAAM,OAAO,OAAO,IAAI,MAAM;AAC9B,QAAI,OAAO,SAAS,SAAU;AAC9B,UAAM,QAAQ,mBAAmB,OAAO,IAAI,OAAO,KAAK,CAAC;AACzD,8BAA0B,IAAI,IAAI;AAClC,yBAAqB;AAAA,EACvB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,mBAAmB,WAAW;AAAA,IAC9B;AAAA,IACA;AAAA,EACF;AACF;;;AC/DA,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AACvB,IAAM,uBAAuB;AAa7B,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBvB,eAAsB,YACpB,MACA,cACA,UACA,SACgC;AAChC,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IACA,EAAE,IAAI,SAAS;AAAA,IACf,EAAE,cAAc,SAAS,OAAO;AAAA,EAClC;AACA,QAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,MAAI,WAAW,QAAW;AACxB,WAAO,EAAE,QAAQ,CAAC,GAAG,OAAO,EAAE;AAAA,EAChC;AAEA,QAAM,YAAY,OAAO,IAAI,WAAW;AACxC,QAAM,aAAa,OAAO,IAAI,YAAY;AAC1C,QAAM,UAAU,OAAO,IAAI,MAAM;AAEjC,QAAM,SAAiC,CAAC;AAExC,MAAI,OAAO,cAAc,YAAY,UAAU,SAAS,GAAG;AACzD,WAAO,KAAK,EAAE,WAAW,WAAW,WAAW,gBAAgB,SAAS,CAAC;AAAA,EAC3E;AACA,MACE,OAAO,eAAe,YACtB,WAAW,SAAS,KACpB,eAAe,WACf;AACA,WAAO,KAAK,EAAE,WAAW,YAAY,WAAW,gBAAgB,SAAS,CAAC;AAAA,EAC5E;AAEA,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,eAAW,QAAQ,SAAS;AAC1B,UAAI,SAAS,QAAQ,OAAO,SAAS,SAAU;AAC/C,YAAM,MAAM;AACZ,YAAM,QAAQ,IAAI;AAClB,YAAM,eAAe,IAAI;AACzB,UAAI,OAAO,UAAU,YAAY,OAAO,iBAAiB,SAAU;AACnE,aAAO,KAAK;AAAA,QACV,WAAW;AAAA,QACX,WAAW;AAAA,QACX;AAAA,QACA,gBAAgB;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,WAAW;AACf,MAAI,QAAQ,WAAW;AACrB,UAAM,OAAO,QAAQ,UAAU;AAC/B,UAAM,KAAK,QAAQ,UAAU;AAG7B,eAAW,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,EAAE,aAAa,EAAE;AAAA,EAC5E;AACA,MAAI,QAAQ,cAAc,QAAQ,WAAW,SAAS,GAAG;AACvD,UAAM,QAAQ,IAAI,IAAI,QAAQ,UAAU;AACxC,eAAW,SAAS,OAAO,CAAC,MAAM,MAAM,IAAI,EAAE,SAAS,CAAC;AAAA,EAC1D;AAEA,WAAS,KAAK,CAAC,GAAG,MAAO,EAAE,YAAY,EAAE,YAAY,IAAI,EAAE,YAAY,EAAE,YAAY,KAAK,CAAE;AAE5F,QAAM,QAAQ,SAAS;AACvB,QAAM,QAAQ,SAAS,MAAM,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,KAAK;AAE3E,SAAO,EAAE,QAAQ,OAAO,MAAM;AAChC;;;ACzHA,SAAS,kBAAoC;AAC3C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC,YAAY;AAAA,IACZ,aAAa,CAAC;AAAA,IACd,mBAAmB,CAAC;AAAA,EACtB;AACF;AAWA,eAAsB,cACpB,MACA,cAC2B;AAC3B,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,IACA,CAAC;AAAA,IACD,EAAE,cAAc,SAAS,OAAO;AAAA,EAClC;AACA,QAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,MAAI,WAAW,OAAW,QAAO,gBAAgB;AACjD,QAAM,MAAM,OAAO,IAAI,MAAM;AAC7B,MAAI,OAAO,QAAQ,YAAY,QAAQ,GAAI,QAAO,gBAAgB;AAClE,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO,gBAAgB;AAAA,EACzB;AACF;AAWA,eAAsB,eACpB,MACA,cACA,YACe;AACf,QAAM,KAAK;AAAA,IACT;AAAA,IACA,EAAE,MAAM,KAAK,UAAU,UAAU,EAAE;AAAA,IACnC,EAAE,aAAa;AAAA,EACjB;AACF;AAiBA,eAAsB,uBACpB,MACA,cACA,SACkD;AAClD,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,SAAS,SAAS,UAAU;AAElC,QAAM,CAAC,YAAY,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,IAClD,KAAK;AAAA,MACH;AAAA;AAAA;AAAA;AAAA,MAIA,EAAE,QAAQ,OAAO,MAAM,GAAG,OAAO,OAAO,KAAK,EAAE;AAAA,MAC/C,EAAE,cAAc,SAAS,OAAO;AAAA,IAClC;AAAA,IACA,KAAK;AAAA,MACH;AAAA,MACA,CAAC;AAAA,MACD,EAAE,cAAc,SAAS,OAAO;AAAA,IAClC;AAAA,EACF,CAAC;AAED,QAAM,QAAQ,WAAW,QAAQ,IAAI,CAAC,WAAW,uBAAuB,QAAQ,GAAG,CAAC;AACpF,QAAM,WAAW,YAAY,QAAQ,CAAC,GAAG,IAAI,OAAO;AACpD,QAAM,QAAQ,aAAa,SAAY,IAAI,mBAAmB,QAAQ;AAEtE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,SAAS,MAAM,SAAS;AAAA,IACjC;AAAA,IACA;AAAA,EACF;AACF;;;AC5GO,IAAM,iBAAiB;AAcvB,SAAS,kBAAqC;AACnD,SAAO;AAAA;AAAA;AAAA,IAGL;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA;AAAA;AAAA,IAIA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA,EAEF;AACF;;;AZQA,IAAM,gBAAgB;AACtB,IAAM,oBAAoB;AAU1B,IAAM,0BAA0B;AAchC,IAAM,kBAA2E;AAAA,EAC/E,cAAc,MAAM;AAAA,EACpB,kBAAkB,CAAC,SAAS;AAC1B,UAAM,MAAM,KAAK,CAAC;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EACA,eAAe,CAAC,SAAS,KAAK,CAAC;AAAA,EAC/B,kBAAkB,MAAM;AAAA,EACxB,kBAAkB,CAAC,SAAS,KAAK,CAAC;AAAA,EAClC,kBAAkB,CAAC,SAAS,KAAK,CAAC;AAAA,EAClC,mBAAmB,CAAC,SAAS,KAAK,CAAC;AAAA,EACnC,eAAe,CAAC,SAAS,KAAK,CAAC;AAAA,EAC/B,gBAAgB,CAAC,SAAS,KAAK,CAAC;AAAA,EAChC,wBAAwB,CAAC,SAAS,KAAK,CAAC;AAAA,EACxC,cAAc,CAAC,SAAS,KAAK,CAAC;AAAA,EAC9B,WAAW,CAAC,SAAS,KAAK,CAAC;AAAA,EAC3B,iBAAiB,CAAC,SAAS,KAAK,CAAC;AAAA,EACjC,aAAa,CAAC,SAAS,KAAK,CAAC;AAAA,EAC7B,cAAc,CAAC,SAAS,KAAK,CAAC;AAAA,EAC9B,cAAc,CAAC,SAAS,KAAK,CAAC;AAAA,EAC9B,gBAAgB,CAAC,SAAS,KAAK,CAAC;AAAA,EAChC,sBAAsB,CAAC,SAAS,KAAK,CAAC;AAAA,EACtC,cAAc,CAAC,SAAS,KAAK,CAAC;AAAA,EAC9B,oBAAoB,CAAC,SAAS,KAAK,CAAC;AAAA,EACpC,iBAAiB,CAAC,SAAS,KAAK,CAAC;AAAA,EACjC,wBAAwB,CAAC,SAAS,KAAK,CAAC;AAAA,EACxC,oBAAoB,CAAC,SAAS,KAAK,CAAC;AAAA,EACpC,qBAAqB,CAAC,SAAS,KAAK,CAAC;AAAA,EACrC,2BAA2B,CAAC,SAAS,KAAK,CAAC;AAAA,EAC3C,UAAU,CAAC,SAAS,KAAK,CAAC;AAAA,EAC1B,qBAAqB,CAAC,SAAS,KAAK,CAAC;AAAA,EACrC,WAAW,CAAC,SAAS,KAAK,CAAC;AAAA,EAC3B,aAAa,CAAC,SAAS,KAAK,CAAC;AAAA,EAC7B,oBAAoB,CAAC,SAAS,KAAK,CAAC;AAAA,EACpC,YAAY,CAAC,SAAS,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA,EAI5B,oBAAoB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAO5B;AA8BA,IAAM,WAAW;AAEV,IAAM,uBAAN,MAA2B;AAAA,EACf;AAAA,EACA;AAAA,EACT,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAML,kBAAkB,oBAAI,IAGrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOe;AAAA,EAEjB,YAAY,QAAoC;AAC9C,SAAK,aAAa,IAAI,gBAAgB,MAAM;AAC5C,SAAK,oBAAoB,IAAI,uBAAuB,KAAK,YAAY;AAAA,MACnE,mBAAmB,OAAO,sBAAsB;AAAA,IAClD,CAAC;AAED,UAAM,WAAW,eAAe,OAAO,WAAW;AAClD,SAAK,cAAc;AACnB,QAAI,UAAU;AAWZ,aAAO,IAAI,MAAM,MAAM;AAAA,QACrB,IAAI,QAAQ,MAAM,UAAmB;AACnC,gBAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AAChD,cAAI,OAAO,SAAS,YAAY,OAAO,UAAU,WAAY,QAAO;AACpE,gBAAM,gBAAgB,gBAAgB,IAAI;AAC1C,cAAI,CAAC,cAAe,QAAO;AAC3B,gBAAM,SAAS;AACf,iBAAO,IAAI,SAA6B;AACtC,kBAAM,QAAQ,iBAAiB;AAC/B,kBAAM,eAAe,cAAc,IAAI;AACvC,kBAAM,OAAO,MAAY;AACvB,uBAAS;AAAA,gBACP,UAAU;AAAA,gBACV,WAAW;AAAA,gBACX,MAAM;AAAA,gBACN,OAAO,MAAM;AAAA,gBACb,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,gBACrD,WAAW,oBAAI,KAAK;AAAA,gBACpB,SAAS,kBAAkB,KAAK;AAAA,cAClC,CAAC;AAAA,YACH;AACA,mBAAO,gBAAgB,OAAO,MAAM;AAClC,kBAAI;AACJ,kBAAI;AACF,yBAAS,OAAO,MAAM,QAAQ,IAAI;AAAA,cACpC,SAAS,KAAK;AACZ,qBAAK;AACL,sBAAM;AAAA,cACR;AACA,kBAAI,UAAU,OAAQ,OAA8B,SAAS,YAAY;AACvE,uBAAQ,OAA4B;AAAA,kBAClC,CAAC,MAAM;AACL,yBAAK;AACL,2BAAO;AAAA,kBACT;AAAA,kBACA,CAAC,QAAQ;AACP,yBAAK;AACL,0BAAM;AAAA,kBACR;AAAA,gBACF;AAAA,cACF;AACA,mBAAK;AACL,qBAAO;AAAA,YACT,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA,EAIA,MAAa,aAA4B;AACvC,QAAI,KAAK,YAAa;AACtB,UAAM,KAAK,WAAW,mBAAmB;AACzC,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAa,UAAyB;AACpC,UAAM,KAAK,WAAW,MAAM;AAC5B,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAa,eAA4C;AACvD,UAAM,iBAAiB,MAAM,KAAK,kBAAkB;AAEpD,QAAI,mBAAmB,QAAQ,iBAAiB,gBAAgB;AAC9D,YAAM,IAAIC;AAAA,QACR,2BAA2B,cAAc,mCAAmC,cAAc;AAAA,MAE5F;AAAA,IACF;AAEA,QAAI,mBAAmB,gBAAgB;AACrC,aAAO;AAAA,QACL,iBAAiB;AAAA,QACjB,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,eAAe;AAAA,MACjB;AAAA,IACF;AAEA,eAAW,aAAa,gBAAgB,GAAG;AACzC,YAAM,KAAK,WAAW,iBAAiB,SAAS;AAAA,IAClD;AACA,UAAM,KAAK,mBAAmB,cAAc;AAE5C,WAAO;AAAA,MACL,iBAAiB;AAAA,MACjB,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,MAAc,oBAA4C;AAGxD,UAAM,SAAS,MAAM,KAAK,WAAW;AAAA,MACnC;AAAA,MACA,EAAE,KAAK,SAAS;AAAA,MAChB,EAAE,iBAAiB,MAAM,SAAS,OAAO;AAAA,IAC3C;AACA,UAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,QAAI,WAAW,OAAW,QAAO;AACjC,UAAM,MAAM,OAAO,IAAI,eAAe;AACtC,QAAI,OAAO,QAAQ,UAAU;AAC3B,UAAI,MAAM,OAAO,OAAO,gBAAgB,GAAG;AACzC,cAAM,IAAIA;AAAA,UACR,wBAAwB,IAAI,SAAS,CAAC;AAAA,QACxC;AAAA,MACF;AACA,aAAO,OAAO,GAAG;AAAA,IACnB;AACA,QAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAI,QAAQ,KAAM,QAAO;AACzB,UAAM,IAAIA;AAAA,MACR,2CAA2C,OAAO,GAAG;AAAA,IACvD;AAAA,EACF;AAAA,EAEA,MAAc,mBAAmB,SAAgC;AAG/D,UAAM,KAAK,WAAW;AAAA,MACpB;AAAA,MACA,EAAE,KAAK,UAAU,QAAQ;AAAA,MACzB,EAAE,iBAAiB,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAa,iBAAiB,QAA4D;AACxF,QAAI;AACF,YAAM,KAAK,WAAW;AAAA,QACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYA,uBAAuB,MAAM;AAAA,QAC7B,EAAE,cAAc,OAAO,aAAa;AAAA,MACtC;AAAA,IACF,SAAS,KAAK;AACZ,qBAAe,KAAK;AAAA,QAClB,MAAM;AAAA,QACN,cAAc,OAAO;AAAA,QACrB,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAEA,UAAM,SAA2B;AAAA,MAC/B,cAAc,OAAO;AAAA,MACrB,OAAO,OAAO;AAAA,MACd,kBAAkB,OAAO;AAAA,MACzB,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,IACpB;AACA,QAAI,OAAO,SAAS,OAAW,QAAO,OAAO,OAAO;AACpD,QAAI,OAAO,gBAAgB,OAAW,QAAO,cAAc,OAAO;AAClE,QAAI,OAAO,UAAU,OAAW,QAAO,QAAQ,OAAO;AACtD,QAAI,OAAO,UAAU,OAAW,QAAO,QAAQ,OAAO;AACtD,QAAI,OAAO,aAAa,OAAW,QAAO,WAAW,OAAO;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,cAAc,cAAwD;AACjF,UAAM,SAAS,MAAM,KAAK,WAAW;AAAA,MACnC;AAAA,MACA,CAAC;AAAA,MACD,EAAE,cAAc,SAAS,OAAO;AAAA,IAClC;AACA,UAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,QAAI,WAAW,OAAW,QAAO;AACjC,WAAO,qBAAqB,MAAM;AAAA,EACpC;AAAA,EAEA,MAAa,iBACX,QACmD;AACnD,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,aAAa,QAAQ;AAE3B,UAAM,kBAA4B,CAAC;AAInC,UAAM,SAAkC,EAAE,QAAQ,OAAO,MAAM,GAAG,OAAO,OAAO,KAAK,EAAE;AACvF,QAAI,eAAe,QAAW;AAC5B,sBAAgB,KAAK,sBAAsB;AAC3C,aAAO,YAAY,IAAI;AAAA,IACzB;AACA,UAAM,cAAc,gBAAgB,SAAS,IAAI,SAAS,gBAAgB,KAAK,OAAO,CAAC,KAAK;AAO5F,UAAM,CAAC,YAAY,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,MAClD,KAAK,WAAW;AAAA,QACd,yBAAyB,WAAW;AAAA,QACpC;AAAA,QACA,EAAE,iBAAiB,MAAM,SAAS,OAAO;AAAA,MAC3C;AAAA,MACA,KAAK,WAAW;AAAA,QACd,yBAAyB,WAAW;AAAA,QACpC,eAAe,SAAY,EAAE,YAAY,WAAW,IAAI,CAAC;AAAA,QACzD,EAAE,iBAAiB,MAAM,SAAS,OAAO;AAAA,MAC3C;AAAA,IACF,CAAC;AAED,UAAM,QAAQ,WAAW,QAAQ,IAAI,CAAC,WAAW,4BAA4B,MAAM,CAAC;AACpF,UAAM,WAAW,YAAY,QAAQ,CAAC,GAAG,IAAI,OAAO;AACpD,UAAM,QAAQ,aAAa,SAAY,IAAI,mBAAmB,QAAQ;AAEtE,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,SAAS,SAAS,MAAM,SAAS;AAAA,MACjC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAa,iBACX,cACA,SAC2B;AAC3B,UAAM,aAAuB,CAAC;AAC9B,UAAM,SAAkC,CAAC;AAEzC,QAAI,QAAQ,UAAU,QAAW;AAC/B,iBAAW,KAAK,kBAAkB;AAClC,aAAO,OAAO,IAAI,QAAQ;AAAA,IAC5B;AACA,QAAI,QAAQ,gBAAgB,QAAW;AACrC,iBAAW,KAAK,8BAA8B;AAC9C,aAAO,aAAa,IAAI,QAAQ;AAAA,IAClC;AACA,QAAI,QAAQ,SAAS,QAAW;AAC9B,iBAAW,KAAK,gBAAgB;AAChC,aAAO,MAAM,IAAI,QAAQ;AAAA,IAC3B;AACA,QAAI,QAAQ,UAAU,QAAW;AAC/B,iBAAW,KAAK,kBAAkB;AAClC,aAAO,OAAO,IAAI,QAAQ;AAAA,IAC5B;AACA,QAAI,QAAQ,UAAU,QAAW;AAC/B,iBAAW,KAAK,kBAAkB;AAClC,aAAO,OAAO,IAAI,QAAQ;AAAA,IAC5B;AACA,QAAI,QAAQ,qBAAqB,QAAW;AAC1C,iBAAW,KAAK,wCAAwC;AACxD,aAAO,kBAAkB,IAAI,KAAK,UAAU,QAAQ,gBAAgB;AAAA,IACtE;AACA,QAAI,QAAQ,aAAa,QAAW;AAIlC,YAAM,WAAW,MAAM,KAAK,cAAc,YAAY;AACtD,UAAI,aAAa,KAAM,OAAM,IAAI,wBAAwB,YAAY;AACrE,YAAM,SAAS,EAAE,GAAG,SAAS,UAAU,GAAG,QAAQ,SAAS;AAC3D,iBAAW,KAAK,wBAAwB;AACxC,aAAO,UAAU,IAAI,KAAK,UAAU,MAAM;AAAA,IAC5C;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,YAAM,WAAW,MAAM,KAAK,cAAc,YAAY;AACtD,UAAI,aAAa,KAAM,OAAM,IAAI,wBAAwB,YAAY;AACrE,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,kDAAkD,WAAW,KAAK,IAAI,CAAC;AACtF,UAAM,SAAS,MAAM,KAAK,WAAW,aAAa,QAAQ,QAAQ,EAAE,aAAa,CAAC;AAClF,UAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,QAAI,WAAW,OAAW,OAAM,IAAI,wBAAwB,YAAY;AACxE,WAAO,qBAAqB,MAAM;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAa,iBACX,cACA,YACe;AACf,UAAM,EAAE,eAAe,mBAAmB,IAAI,MAAM,KAAK,wBAAwB,YAAY;AAE7F,QAAI,uBAAuB;AAC3B,QAAI,kBAAkB;AAEtB,WAAO,MAAM;AACX,YAAM,UAAU,MAAM,KAAK,WAAW;AAAA,QACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA,EAAE,WAAW,OAAO,iBAAiB,EAAE;AAAA,QACvC,EAAE,aAAa;AAAA,MACjB;AACA,YAAM,QAAQ,QAAQ,SAAS,QAAQ;AACvC,YAAM,mBAAmB,MAAM,sBAAsB,KAAK;AAC1D,UAAI,qBAAqB,EAAG;AAC5B,6BAAuB,KAAK,IAAI,uBAAuB,kBAAkB,kBAAkB;AAC3F,YAAM,aAAa,EAAE,iBAAiB,sBAAsB,eAAe,mBAAmB,CAAC;AAAA,IACjG;AAEA,WAAO,MAAM;AACX,YAAM,UAAU,MAAM,KAAK,WAAW;AAAA,QACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA,EAAE,WAAW,OAAO,iBAAiB,EAAE;AAAA,QACvC,EAAE,aAAa;AAAA,MACjB;AACA,YAAM,QAAQ,QAAQ,SAAS,QAAQ;AACvC,YAAM,mBAAmB,MAAM,cAAc,KAAK;AAClD,UAAI,qBAAqB,EAAG;AAK5B,wBAAkB,KAAK,IAAI,kBAAkB,kBAAkB,aAAa;AAC5E,YAAM,aAAa,EAAE,iBAAiB,sBAAsB,eAAe,mBAAmB,CAAC;AAAA,IACjG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,kBACX,cACA,YACoE;AACpE,UAAM,EAAE,eAAe,mBAAmB,IAAI,MAAM,KAAK,wBAAwB,YAAY;AAE7F,QAAI,uBAAuB;AAC3B,QAAI,kBAAkB;AAEtB,WAAO,MAAM;AACX,YAAM,UAAU,MAAM,KAAK,WAAW;AAAA,QACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA,EAAE,WAAW,OAAO,iBAAiB,EAAE;AAAA,QACvC,EAAE,aAAa;AAAA,MACjB;AACA,YAAM,QAAQ,QAAQ,SAAS,QAAQ;AACvC,YAAM,mBAAmB,MAAM,sBAAsB,KAAK;AAC1D,UAAI,qBAAqB,EAAG;AAC5B,6BAAuB,KAAK,IAAI,uBAAuB,kBAAkB,kBAAkB;AAC3F,YAAM,aAAa,EAAE,iBAAiB,sBAAsB,eAAe,mBAAmB,CAAC;AAAA,IACjG;AAEA,WAAO,MAAM;AACX,YAAM,UAAU,MAAM,KAAK,WAAW;AAAA,QACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA,EAAE,WAAW,OAAO,iBAAiB,EAAE;AAAA,QACvC,EAAE,aAAa;AAAA,MACjB;AACA,YAAM,QAAQ,QAAQ,SAAS,QAAQ;AACvC,YAAM,mBAAmB,MAAM,cAAc,KAAK;AAClD,UAAI,qBAAqB,EAAG;AAC5B,wBAAkB,KAAK,IAAI,kBAAkB,kBAAkB,aAAa;AAC5E,YAAM,aAAa,EAAE,iBAAiB,sBAAsB,eAAe,mBAAmB,CAAC;AAAA,IACjG;AAEA,WAAO,EAAE,iBAAiB,eAAe,sBAAsB,mBAAmB;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAc,wBACZ,cACgE;AAChE,UAAM,CAAC,gBAAgB,mBAAmB,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC9D,KAAK,WAAW;AAAA,QACd;AAAA,QACA,CAAC;AAAA,QACD,EAAE,cAAc,SAAS,OAAO;AAAA,MAClC;AAAA,MACA,KAAK,WAAW;AAAA,QACd;AAAA,QACA,CAAC;AAAA,QACD,EAAE,cAAc,SAAS,OAAO;AAAA,MAClC;AAAA,IACF,CAAC;AACD,UAAM,gBAAgB,mBAAmB,eAAe,QAAQ,CAAC,GAAG,IAAI,OAAO,KAAK,CAAC;AACrF,UAAM,qBAAqB,mBAAmB,oBAAoB,QAAQ,CAAC,GAAG,IAAI,OAAO,KAAK,CAAC;AAC/F,WAAO,EAAE,eAAe,mBAAmB;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAa,cAAc,cAAiD;AAC1E,WAAO,KAAK,oBAAoB,YAAY;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,oBAAoB,cAAiD;AACjF,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,SAAS,KAAK,gBAAgB,IAAI,YAAY;AACpD,QAAI,UAAU,OAAO,YAAY,KAAK;AACpC,aAAO,OAAO;AAAA,IAChB;AACA,UAAM,QAAQ,MAAmB,cAAc,KAAK,YAAY,YAAY;AAC5E,SAAK,gBAAgB,IAAI,cAAc;AAAA,MACrC;AAAA,MACA,WAAW,MAAM;AAAA,IACnB,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,0BAA0B,cAA4B;AAC5D,SAAK,gBAAgB,OAAO,YAAY;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAa,eACX,cACA,YACe;AACf,UAAmB,eAAe,KAAK,YAAY,cAAc,UAAU;AAC3E,SAAK,0BAA0B,YAAY;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,uBACX,cACA,SACkD;AAClD,WAAoB,uBAAuB,KAAK,YAAY,cAAc,OAAO;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAa,aACX,cACA,QACuB;AACvB,WAAqB,aAAa,KAAK,YAAY,cAAc,MAAM;AAAA,EACzE;AAAA;AAAA,EAGA,MAAa,UACX,cACA,UACA,SAC8B;AAC9B,WAAqB,UAAU,KAAK,YAAY,cAAc,UAAU,OAAO;AAAA,EACjF;AAAA;AAAA,EAGA,MAAa,gBACX,cACA,MACA,SAC8B;AAC9B,WAAqB,gBAAgB,KAAK,YAAY,cAAc,MAAM,OAAO;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,YACX,cACA,WACA,SACoC;AACpC,WAAqB,YAAY,KAAK,YAAY,cAAc,WAAW,OAAO;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,aACX,cACA,UACA,SACuB;AACvB,WAAqB,aAAa,KAAK,YAAY,cAAc,UAAU,OAAO;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,aACX,cACA,UACe;AACf,WAAqB,aAAa,KAAK,YAAY,cAAc,QAAQ;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,eACX,cACA,KACoD;AACpD,WAAqB,eAAe,KAAK,YAAY,cAAc,GAAG;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,qBACX,cACA,YACgF;AAChF,WAAqB,qBAAqB,KAAK,YAAY,cAAc,UAAU;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,aACX,cACA,OACA,SACwC;AACxC,WAAqB,aAAa,KAAK,YAAY,cAAc,OAAO,OAAO;AAAA,EACjF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,mBACX,cACA,cAC6B;AAC7B,WAA2B,mBAAmB,KAAK,YAAY,cAAc,YAAY;AAAA,EAC3F;AAAA;AAAA,EAGA,MAAa,gBACX,cACA,gBACoC;AACpC,WAA2B,gBAAgB,KAAK,YAAY,cAAc,cAAc;AAAA,EAC1F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAa,uBACX,cACA,UACA,SAC8C;AAC9C,WAA2B;AAAA,MACzB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAa,mBACX,cACA,gBACe;AACf,WAA2B,mBAAmB,KAAK,YAAY,cAAc,cAAc;AAAA,EAC7F;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,oBACX,cACA,KACoD;AACpD,WAA2B,oBAAoB,KAAK,YAAY,cAAc,GAAG;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,0BACX,cACA,kBAC2C;AAC3C,WAA2B;AAAA,MACzB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWO,kBAA8C;AACnD,WAAO;AAAA,MACL,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,mBAAmB;AAAA,MACnB,qCAAqC;AAAA,MACrC,+BAA+B;AAAA,MAC/B,qBAAqB;AAAA,MACrB,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,6BAA6B;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAa,SACX,cACA,MAC0B;AAC1B,WAAO,KAAK,iBAAiB,cAAc,IAAI;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,iBACZ,cACA,MAC0B;AAC1B,UAAM,MAAM,MAAM,KAAK,oBAAoB,cAAc,IAAI;AAE7D,UAAM,cAAc,KAAK,eAAe;AAIxC,UAAM,sBAAsB,CAAC,WAA0C;AACrE,YAAM,YAAY,cAAc,QAAQ,WAAW;AACnD,UAAI,CAAC,KAAK,mBAAmB;AAC3B,eAAQ,UAAiD,YAAY;AAAA,MACvE;AACA,aAAO;AAAA,IACT;AAQA,UAAM,4BAA4B,CAChC,KACA,YAA0B,WACC;AAAA,MAC3B,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,gBAAgB,IAAI;AAAA,MACpB,gBAAgB,IAAI;AAAA,MACpB;AAAA,MACA,YAAY,IAAI;AAAA,IAClB;AAEA,QAAI,WAA8B,CAAC;AACnC,QAAI;AACJ,QAAI;AAWJ,UAAM,eAAe,IAAI;AACzB,UAAM,oBAAoB,iBAAiB;AAE3C,QAAI,mBAAmB;AAAA,IAEvB,WAAW,KAAK,eAAe,YAAY;AACzC,iBAAW,IAAI,iBAAiB,IAAI,mBAAmB;AACvD,sBAAgB;AAAA,IAClB,WAAW,KAAK,eAAe,OAAO;AAOpC,iBAAW,IAAI,YAAY,IAAI,mBAAmB;AAClD,sBAAgB,IAAI,iBAAiB,IAAI,CAAC,MAAM,0BAA0B,CAAC,CAAC;AAAA,IAC9E,OAAO;AACL,cAAQ,IAAI,SAAS,IAAI,CAAC,SAAS;AAAA,QACjC,QAAQ,KAAK,IAAI,IAAI,UAAU,SAAS,GAAG,CAAC;AAAA,QAC5C,UAAU,IAAI,UAAU,IAAI,CAAC,OAAO;AAClC,gBAAM,SAAS,IAAI,UAAU,IAAI,EAAE;AACnC,cAAI,CAAC,QAAQ;AACX,kBAAM,IAAIA;AAAA,cACR;AAAA,cACA;AAAA,YACF;AAAA,UACF;AACA,iBAAO,oBAAoB,MAAM;AAAA,QACnC,CAAC;AAAA,QACD,eAAe,IAAI,gBAAgB,IAAI,CAAC,IAAI,MAAM;AAChD,gBAAM,SAAS,IAAI,gBAAgB,IAAI,EAAE;AACzC,cAAI,CAAC,QAAQ;AACX,kBAAM,IAAIA;AAAA,cACR;AAAA,cACA;AAAA,YACF;AAAA,UACF;AACA,iBAAO,0BAA0B,QAAQ,IAAI,uBAAuB,CAAC,KAAK,KAAK;AAAA,QACjF,CAAC;AAAA,MACH,EAAE;AACF,sBAAgB,MAAM,KAAK,IAAI,gBAAgB,OAAO,CAAC,EAAE;AAAA,QAAI,CAAC,QAC5D,0BAA0B,KAAK,IAAI,sBAAsB,IAAI,IAAI,EAAE,KAAK,KAAK;AAAA,MAC/E;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,SAAS;AAC5B,QAAI;AACJ,QAAI,mBAAmB;AAGrB,cAAQ,aAAc;AAAA,IACxB,WAAW,KAAK,eAAe,QAAQ;AACrC,cAAQ,OAAO,UAAU;AAAA,IAC3B,WAAW,KAAK,eAAe,OAAO;AAGpC,cAAQ,SAAS,UAAU,eAAe,UAAU;AAAA,IACtD,OAAO;AACL,cAAQ,SAAS;AAAA,IACnB;AAEA,UAAM,YAAY,SAAS;AAE3B,UAAM,gBAA+B;AAAA,MACnC,iBAAiB,IAAI;AAAA,MACrB,cAAc,EAAE,OAAO,aAAa,OAAO,IAAI,SAAS;AAAA,MACxD,eAAe,IAAI;AAAA,MACnB,uBAAuB;AAAA,MACvB,eAAe;AAAA,QACb,YAAY;AAAA,QACZ,GAAI,KAAK,UAAU,SAAY,EAAE,UAAU,KAAK,MAAM,OAAO,IAAI,CAAC;AAAA,MACpE;AAAA,MACA;AAAA,MACA,GAAI,YAAY,EAAE,kBAAkB,eAAwB,IAAI,CAAC;AAAA,IACnE;AAEA,WAAO;AAAA,MACL;AAAA,MACA,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,MACvD,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,MACvC,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,MACrD;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,oBACZ,cACA,MAC6B;AAC7B,UAAM,aAAa,MAAM,KAAK,oBAAoB,YAAY;AAC9D,WAAO,KAAK,kBAAkB,QAAQ,cAAc,MAAM,UAAU;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAa,oBACX,cACA,UACA,SAC8B;AAC9B,UAAM,SAAqC,CAAC;AAC5C,UAAM,UAAU,oBAAI,IAAY,CAAC,QAAQ,CAAC;AAC1C,QAAI,WAAW,oBAAI,IAAY,CAAC,QAAQ,CAAC;AAEzC,aAAS,IAAI,GAAG,KAAK,QAAQ,OAAO,KAAK;AACvC,UAAI,SAAS,SAAS,EAAG;AAEzB,YAAM,OAAsB;AAAA,QAC1B,OAAO,EAAE,SAAS;AAAA,QAClB,OAAO,kBAAkB,GAAG,OAAO;AAAA,QACnC,YAAY;AAAA;AAAA;AAAA;AAAA,QAIZ,OAAO;AAAA,QACP,aAAa;AAAA,QACb,mBAAmB;AAAA,MACrB;AACA,YAAM,MAAM,MAAM,KAAK,oBAAoB,cAAc,IAAI;AAE7D,YAAM,gBAAgB,oBAAI,IAAkC;AAC5D,iBAAW,OAAO,IAAI,kBAAkB;AACtC,cAAM,IAAI,cAAc,IAAI,IAAI,cAAc;AAC9C,YAAI,EAAG,GAAE,KAAK,GAAG;AAAA,YACZ,eAAc,IAAI,IAAI,gBAAgB,CAAC,GAAG,CAAC;AAChD,cAAM,IAAI,cAAc,IAAI,IAAI,cAAc;AAC9C,YAAI,EAAG,GAAE,KAAK,GAAG;AAAA,YACZ,eAAc,IAAI,IAAI,gBAAgB,CAAC,GAAG,CAAC;AAAA,MAClD;AAEA,YAAM,QAAkC,CAAC;AACzC,YAAM,eAAe,oBAAI,IAAY;AAKrC,YAAM,kBAAkB,oBAAI,IAAyB;AAErD,iBAAW,MAAM,UAAU;AACzB,cAAM,WAAW,cAAc,IAAI,EAAE,KAAK,CAAC;AAC3C,mBAAW,OAAO,UAAU;AAC1B,gBAAM,WAAW,IAAI,mBAAmB;AACxC,gBAAM,WAAW,IAAI,mBAAmB;AACxC,cAAI,mBAAmB;AACvB,cAAI;AAEJ,cAAI,aAAa,QAAQ,cAAc,SAAS,QAAQ,cAAc,SAAS;AAC7E,+BAAmB;AACnB,0BAAc,IAAI;AAAA,UACpB,WAAW,aAAa,QAAQ,cAAc,QAAQ,QAAQ,cAAc,SAAS;AACnF,+BAAmB;AACnB,0BAAc,IAAI;AAAA,UACpB,WAAW,IAAI,eAAe;AAG5B,gBAAI,YAAY,QAAQ,cAAc,MAAM;AAC1C,iCAAmB;AACnB,4BAAc,IAAI;AAAA,YACpB,WAAW,YAAY,QAAQ,cAAc,OAAO;AAClD,iCAAmB;AACnB,4BAAc,IAAI;AAAA,YACpB;AAAA,UACF;AACA,cAAI,CAAC,oBAAoB,CAAC,YAAa;AACvC,cAAI,QAAQ,IAAI,WAAW,EAAG;AAE9B,cACE,QAAQ,+BACR,QAAQ,4BAA4B,SAAS,GAC7C;AACA,gBAAI,CAACC,wBAAuB,IAAI,YAAY,QAAQ,2BAA2B;AAC7E;AAAA,UACJ;AAEA,gBAAM,kBAAkB,IAAI,UAAU,IAAI,WAAW;AACrD,cAAI,CAAC,gBAAiB;AAEtB,cACE,QAAQ,eACR,QAAQ,YAAY,SAAS,KAC7B,CAAC,QAAQ,YAAY,SAAS,gBAAgB,UAAU,GACxD;AACA;AAAA,UACF;AAEA,gBAAM,UAAU,IAAI;AACpB,cAAI,aAAa,gBAAgB,IAAI,OAAO;AAC5C,cAAI,CAAC,YAAY;AACf,yBAAa,oBAAI,IAAY;AAC7B,4BAAgB,IAAI,SAAS,UAAU;AAAA,UACzC;AACA,cAAI,WAAW,IAAI,WAAW,EAAG;AACjC,qBAAW,IAAI,WAAW;AAE1B,cAAI,CAAC,MAAM,OAAO,GAAG;AACnB,kBAAM,OAAO,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG,eAAe,CAAC,EAAE;AAAA,UAC/D;AACA,gBAAM,OAAO,EAAG,SAAS,KAAK,eAAe;AAC7C,gBAAM,OAAO,EAAG,cAAc,KAAK,GAAG;AACtC,gBAAM,OAAO,EAAG,QAAQ,MAAM,OAAO,EAAG,SAAS;AACjD,uBAAa,IAAI,WAAW;AAAA,QAC9B;AAAA,MACF;AAIA,iBAAW,WAAW,OAAO,KAAK,KAAK,GAAG;AACxC,cAAM,QAAQ,MAAM,OAAO;AAC3B,cAAM,QAAQ,QAAQ;AACtB,cAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAM,WAAW,MAAM,SAAS,MAAM,OAAO,GAAG;AAChD,cAAM,gBAAgB,MAAM,cAAc,MAAM,OAAO,GAAG;AAAA,MAC5D;AAEA,UAAI,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AACjC,eAAO,KAAK,KAAK;AAAA,MACnB;AAKA,iBAAW,MAAM,aAAc,SAAQ,IAAI,EAAE;AAC7C,iBAAW;AAAA,IACb;AAEA,WAAO,EAAE,UAAU,UAAU,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAa,UACX,cACA,UACA,UACA,SAC4B;AAC5B,QAAI,aAAa,UAAU;AACzB,aAAO,EAAE,OAAO,CAAC,EAAE,WAAW,CAAC,QAAQ,GAAG,iBAAiB,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE;AAAA,IAClF;AAEA,UAAM,OAAsB;AAAA,MAC1B,WAAW;AAAA,MACX,QAAQ,EAAE,UAAU,QAAQ,UAAU,mBAAmB,KAAK;AAAA,IAChE;AACA,QAAI,QAAQ,qBAAqB,QAAQ,kBAAkB,SAAS,GAAG;AACrE,WAAK,oBAAoB,QAAQ;AAAA,IACnC;AACA,QAAI,QAAQ,+BAA+B,QAAQ,4BAA4B,SAAS,GAAG;AACzF,WAAK,qBAAqB,QAAQ;AAAA,IACpC;AAEA,UAAM,OAAsB;AAAA,MAC1B,OAAO,EAAE,UAAU,SAAS;AAAA,MAC5B,OAAO,CAAC,IAAI;AAAA,MACZ,YAAY;AAAA;AAAA;AAAA;AAAA,MAIZ,OAAO,KAAK,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,KAAK,IAAI;AAAA,MACjE,aAAa;AAAA,MACb,mBAAmB;AAAA,IACrB;AAEA,UAAM,MAAM,MAAM,KAAK,oBAAoB,cAAc,IAAI;AAE7D,UAAM,gBAA+B,CAAC;AACtC,eAAW,OAAO,IAAI,UAAU;AAC9B,YAAM,OAAO,IAAI,UAAU,IAAI,UAAU,SAAS,CAAC;AACnD,UAAI,SAAS,SAAU;AAQvB,UAAI,IAAI,IAAI,IAAI,SAAS,EAAE,SAAS,IAAI,UAAU,OAAQ;AAC1D,UAAI,QAAQ,eAAe,QAAQ,YAAY,SAAS,GAAG;AAIzD,YAAI,WAAW;AACf,iBAAS,IAAI,GAAG,IAAI,IAAI,UAAU,SAAS,GAAG,KAAK;AACjD,gBAAM,eAAe,IAAI,UAAU,IAAI,IAAI,UAAU,CAAC,CAAE;AACxD,cAAI,CAAC,cAAc;AACjB,uBAAW;AACX;AAAA,UACF;AACA,cAAI,CAAC,QAAQ,YAAY,SAAS,aAAa,UAAU,GAAG;AAC1D,uBAAW;AACX;AAAA,UACF;AAAA,QACF;AACA,YAAI,SAAU;AAAA,MAChB;AACA,oBAAc,KAAK;AAAA,QACjB,WAAW,CAAC,GAAG,IAAI,SAAS;AAAA,QAC5B,iBAAiB,CAAC,GAAG,IAAI,eAAe;AAAA,MAC1C,CAAC;AAAA,IACH;AAEA,UAAM,YAAY,cAAc,MAAM,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,KAAK;AAEpF,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY,cAAc;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAa,YACX,cACA,UACA,SACgC;AAChC,WAAuB,YAAY,KAAK,YAAY,cAAc,UAAU,OAAO;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBO,UAAU,cAAkD;AACjE,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACY,UAAU,KAAK,YAAY,YAAY;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAa,WACX,cACA,MACA,SAC2B;AAC3B,WAAmB,WAAW,KAAK,YAAY,cAAc,MAAM,OAAO;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBQ,cACN,WACA,cACA,QACkB;AAKlB,UAAM,OAAO,KAAK;AAClB,QAAI,SAAS,OAAW,QAAO;AAC/B,WAAO;AAAA,MACL,CAAC,OAAO,aAAa,GAAG,MAAwB;AAC9C,cAAM,OAAO,OAAO,OAAO,aAAa,EAAE;AAC1C,cAAM,QAAQ,iBAAiB;AAC/B,YAAI,UAAU;AACd,cAAM,OAAO,MAAY;AACvB,cAAI,QAAS;AACb,oBAAU;AACV,eAAK;AAAA,YACH,UAAU;AAAA,YACV;AAAA,YACA,MAAM;AAAA,YACN,OAAO,MAAM;AAAA,YACb;AAAA,YACA,WAAW,oBAAI,KAAK;AAAA,YACpB,SAAS,kBAAkB,KAAK;AAAA,UAClC,CAAC;AAAA,QACH;AACA,eAAO;AAAA,UACL,MAAM,OAAmC;AACvC,kBAAM,OAAO,MAAM,gBAAgB,OAAO,MAAM,KAAK,KAAK,CAAC;AAC3D,gBAAI,KAAK,KAAM,MAAK;AACpB,mBAAO;AAAA,UACT;AAAA,UACA,MAAM,OAAO,OAAuC;AAClD,iBAAK;AACL,gBAAI,KAAK,OAAQ,QAAO,KAAK,OAAO,KAAK;AACzC,mBAAO,EAAE,MAAM,MAAM,MAAkB;AAAA,UACzC;AAAA,UACA,MAAM,MAAM,KAA2C;AACrD,iBAAK;AACL,gBAAI,KAAK,MAAO,QAAO,KAAK,MAAM,GAAG;AACrC,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAa,mBAAmB,cAAgD;AAC9E,UAAM,aAAa,MAAM,KAAK,oBAAoB,YAAY;AAC9D,WAAyB,mBAAmB,KAAK,YAAY,cAAc,UAAU;AAAA,EACvF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BA,MAAa,mBACX,eACA,OACA,QACoB;AACpB,UAAM,SAAS,MAAM,KAAK,WAAW,mBAAmB,OAAO,UAAU,CAAC,GAAG;AAAA,MAC3E,iBAAiB;AAAA,IACnB,CAAC;AAKD,WAAO,OAAO,QAAQ,IAAI,CAAC,WAAW,OAAO,SAAS,CAAC;AAAA,EACzD;AACF;AAYA,SAAS,kBACP,OACA,SACiB;AACjB,QAAM,OAAsB,EAAE,WAAW,OAAO;AAChD,MAAI,QAAQ,qBAAqB,QAAQ,kBAAkB,SAAS,GAAG;AACrE,SAAK,oBAAoB,QAAQ;AAAA,EACnC;AACA,QAAM,QAAyB,CAAC;AAChC,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAM,KAAK,EAAE,GAAG,KAAK,CAAC;AAAA,EACxB;AACA,SAAO;AACT;","names":["ProviderError","matchesPropertyFilters","ProviderError","ProviderError","ProviderError","ProviderError","ProviderError","ProviderError","ProviderError","EntityNotFoundError","EntityNotFoundError","dataCypher","countCypher","ProviderError","matchesPropertyFilters"]}
|