import { z } from 'zod'; import { a5 as NodeType, fI as EdgeType, a4 as AnyEdgeType, G as GraphIdentityConfig, em as ValueType, hP as PredicateExpression, cv as FulltextQueryMode, hQ as VectorMetricType, Q as QueryAst, J as JsonPointer, f1 as FieldRef, gH as ParameterRef, fp as DatabaseExpression, s as SqlDialect, h1 as SqlSchema, d as FulltextStrategy, V as VectorStrategy, hR as VectorSlotMap, hS as RecordedReadBinding, dC as RecursiveTraversalVerdict, ha as TraversalExpansion, g as GraphBackend, hT as Traversal, hU as NodePredicate, hV as ProjectedField, gG as OrderSpec, hW as AggregateOrderSpec, e3 as TemporalMode, R as RecordedInstant, hX as GroupBySpec, f$ as HybridFusionOptions, ge as JsonPointerInput, hY as RecursiveCyclePolicy, e as GraphDef, e$ as TraversalDirection$1, fF as EdgeRegistration, gy as NodeId, ba as CompiledSelectSql, T as TransactionBackend, hZ as SelectiveField, h0 as SortDirection, b9 as CompiledRowsSql, h_ as ComposableQuery, h$ as SetOperationType, i0 as SetOperation, f0 as AggregateExpr, fz as EdgeKinds, f6 as AllNodeTypes, fZ as GraphAnnotations, d1 as KindAnnotations, ao as JsonSchema, eb as UniquenessScope, b4 as Collation, aZ as Cardinality, bX as EndpointExistence, cB as GraphExtension, K as KindEntity, aN as TypeGraphError, gB as NodeKinds, p as NodeRow, fV as ExternalRecordedReadSource, fy as EdgeId, n as EdgeRow, i1 as BackendValidityEndMutation, c$ as JsonScalar, gF as NodeRegistration, e6 as TransactionReadBackend, at as SchemaIdentity, as as SchemaDiff, f as SchemaVersionRow, ab as SerializedSchema, i2 as ReadCoordinate, aV as BackendIdentity, cz as GraphEntityReadBackend, dQ as SchemaReadBackend, dp as QueryExecutionBackend, dU as SqlCompilationBackend, dq as RawQueryExecutionBackend, ep as VectorMetric, a2 as BackendCapabilities, c9 as ExtensionNodeDef, i3 as ExtensionObjectSchema, c3 as ExtensionEdgeDef, i4 as ExtensionEdgeProperties, go as MaterializeIndexesOptions, gp as MaterializeIndexesResult, gq as MaterializeSystemIndexesOptions, C as ContributionDiagnostic, c as ContributionRepairResult, bm as ContributionProbeResult, bp as ContributionRebuildScope, bo as ContributionRebuildResult, e5 as TransactionOptions, A as AdapterBackend } from './types-BynPp5kU.cjs'; import { S as SearchableMetadata, c as EmbeddingValue } from './searchable-C7Xt6g45.cjs'; import { b as BundleVerdictOf, U as UNIQUE_SIDECAR_BATCH, B as BATCH_POINT_READ } from './resolve-CoYqHqno.cjs'; /** * Who allocates recorded-time revisions for a backend: TypeGraph's own * capture relations and clock, or the engine itself through * `GraphBackend.recordedTime` (`./recorded-time.ts`). Lives in its own * module, sibling to `write-fence.ts` and `recorded-time.ts`, because the * derivation is a one-line decision that several construction sites read * and must never re-spell. */ /** Who allocates recorded-time revisions for a backend. See {@link resolveRecordedTimeOwnership}. */ type RecordedTimeOwnership = "typegraph-relations" | "engine-native"; declare const RUNTIME_KIND_TOKEN_BRAND: unique symbol; /** Store-issued evidence for one persisted runtime node kind. */ type RuntimeNodeKind = z.ZodObject> = Readonly<{ entity: "node"; kind: K; [RUNTIME_KIND_TOKEN_BRAND]: S; }>; /** Store-issued evidence for one persisted runtime edge kind. */ type RuntimeEdgeKind = z.ZodObject> = Readonly<{ entity: "edge"; kind: K; [RUNTIME_KIND_TOKEN_BRAND]: S; }>; /** Node type recovered from validated runtime-kind evidence. */ type RuntimeNodeTypeFor = T extends RuntimeNodeKind ? NodeType : never; /** Edge type recovered from validated runtime-kind evidence. */ type RuntimeEdgeTypeFor = T extends RuntimeEdgeKind ? EdgeType : never; /** * KindRegistry holds precomputed closures for ontological reasoning. * * Computed at store initialization and cached for fast query-time lookups. */ declare class KindRegistry { #private; readonly nodeKinds: ReadonlyMap; readonly edgeKinds: ReadonlyMap; /** * Durable graph identity capability; supplies the identity defaults for * every query builder built from this registry (compile-only and * store-bound). */ readonly identity: GraphIdentityConfig | undefined; readonly subClassAncestors: ReadonlyMap>; readonly subClassDescendants: ReadonlyMap>; readonly broaderClosure: ReadonlyMap>; readonly narrowerClosure: ReadonlyMap>; readonly equivalenceSets: ReadonlyMap>; readonly iriToKind: ReadonlyMap; readonly relatedKinds: ReadonlyMap>; readonly disjointPairs: ReadonlySet; readonly partOfClosure: ReadonlyMap>; readonly hasPartClosure: ReadonlyMap>; readonly edgeInverses: ReadonlyMap; readonly edgeImplicationsClosure: ReadonlyMap>; readonly edgeImplyingClosure: ReadonlyMap>; constructor(nodeKinds: ReadonlyMap, edgeKinds: ReadonlyMap, closures: { subClassAncestors: ReadonlyMap>; subClassDescendants: ReadonlyMap>; broaderClosure: ReadonlyMap>; narrowerClosure: ReadonlyMap>; equivalenceSets: ReadonlyMap>; iriToKind: ReadonlyMap; relatedKinds: ReadonlyMap>; disjointPairs: ReadonlySet; partOfClosure: ReadonlyMap>; hasPartClosure: ReadonlyMap>; edgeInverses: ReadonlyMap; edgeImplicationsClosure: ReadonlyMap>; edgeImplyingClosure: ReadonlyMap>; }, identity?: GraphIdentityConfig); /** * Checks if child is a subclass of parent (directly or transitively). */ isSubClassOf(child: string, parent: string): boolean; /** * Expands a kind to include all its subclasses. * Returns [kind, ...subclasses]. */ expandSubClasses(kind: string): readonly string[]; /** * Gets all ancestors of a kind (via subClassOf). */ getAncestors(kind: string): ReadonlySet; /** * Gets all descendants of a kind (via subClassOf). */ getDescendants(kind: string): ReadonlySet; /** * Returns the precomputed undirected `subClassOf` component containing kind, * in code-point order. Every member of one component returns the same array. */ getSubClassComponent(kind: string): readonly string[]; /** * Checks if narrowerConcept is narrower than broaderConcept. */ isNarrowerThan(narrowerConcept: string, broaderConcept: string): boolean; /** * Checks if broaderConcept is broader than narrowerConcept. */ isBroaderThan(broaderConcept: string, narrowerConcept: string): boolean; /** * Expands to include all narrower concepts. */ expandNarrower(kind: string): readonly string[]; /** * Expands to include all broader concepts. */ expandBroader(kind: string): readonly string[]; /** * Checks if two kinds are equivalent. */ areEquivalent(a: string, b: string): boolean; /** * Gets all equivalents of a kind (including external IRIs). */ getEquivalents(kind: string): readonly string[]; /** * Resolves an external IRI to an internal kind name. */ resolveIri(iri: string): string | undefined; /** Gets directly associated kinds declared through symmetric `relatedTo`. */ getRelatedKinds(kind: string): readonly string[]; /** * THE canonical label of an unordered pair of kinds — the form * {@link disjointPairs} stores, and the form a disjointness CLAIM AXIS is * folded from. * * Exposed on the registry because the claim axis needs it: a fence keyed on * a second spelling of this normalization would put the two kinds of one * disjoint pair on two rows that can never collide, which is exactly the * failure the fence exists to prevent. One expression, called by the * membership test, by the pair computation, and by the axis. */ disjointPairLabel(a: string, b: string): string; /** * Checks if two kinds are disjoint. */ areDisjoint(a: string, b: string): boolean; /** * Every declared disjoint pair, once each, as the two kinds it names. * * The inverse of {@link disjointPairLabel} lives in this module alone, so a * caller enumerating the pairs — the fence audit is the one that needs them — * never has to know how a label is spelled. Iterating the kinds and folding * {@link getDisjointKinds} would be a second, order-dependent spelling of * exactly this set. */ disjointKindPairs(): readonly (readonly [string, string])[]; /** * Gets all kinds that are disjoint with the given kind. */ getDisjointKinds(kind: string): readonly string[]; /** * Checks if part is part of whole (directly or transitively). */ isPartOf(part: string, whole: string): boolean; /** * Gets all wholes that contain this part. */ getWholes(part: string): readonly string[]; /** * Gets all parts of this whole. */ getParts(whole: string): readonly string[]; /** * Gets the inverse edge kind for a given edge kind. * If edgeA inverseOf edgeB, then getInverseEdge("edgeA") returns "edgeB". */ getInverseEdge(edgeKind: string): string | undefined; /** * Gets all edges implied by a given edge (transitively). * If A implies B and B implies C, then getImpliedEdges("A") returns ["B", "C"]. */ getImpliedEdges(edgeKind: string): readonly string[]; /** * Gets all edges that imply a given edge (transitively). * If A implies B and B implies C, then getImplyingEdges("C") returns ["A", "B"]. * Used for query-time expansion: when querying for C, also include A and B edges. */ getImplyingEdges(edgeKind: string): readonly string[]; /** * Expands an edge kind to include all edges that imply it. * Returns [edgeKind, ...implyingEdges]. */ expandImplyingEdges(edgeKind: string): readonly string[]; /** * Checks if a concrete kind is assignable to a target kind. * Uses subsumption: Company is assignable to Organization if Company subClassOf Organization. */ isAssignableTo(concreteKind: string, targetKind: string): boolean; /** * Checks if a concrete kind is assignable to at least one of the given * target kinds. Shared by every "is this kind usable where one of these * kinds is expected" check (edge endpoint validation, ontology relation * endpoint compatibility) so they can't independently drift. */ isAssignableToAny(concreteKind: string, targetKinds: readonly string[]): boolean; /** * Validates that a kind exists in the registry. */ hasNodeType(name: string): boolean; /** * Validates that an edge kind exists in the registry. */ hasEdgeType(name: string): boolean; /** * Gets a node kind by name. */ getNodeType(name: string): NodeType | undefined; /** * Gets an edge kind by name. */ getEdgeType(name: string): AnyEdgeType | undefined; } type FieldTypeInfo = Readonly<{ valueType: ValueType; nullable?: boolean; elementType?: ValueType | undefined; elementTypeInfo?: FieldTypeInfo | undefined; shape?: Readonly> | undefined; recordValueType?: FieldTypeInfo | undefined; /** For embedding types: the number of dimensions */ dimensions?: number | undefined; /** For fulltext-searchable string types: field-level metadata */ searchable?: SearchableMetadata | undefined; }>; type SchemaIntrospector = Readonly<{ getFieldTypeInfo: (kindName: string, fieldName: string) => FieldTypeInfo | undefined; getSharedFieldTypeInfo: (kindNames: readonly string[], fieldName: string) => FieldTypeInfo | undefined; getEdgeFieldTypeInfo: (edgeKindName: string, fieldName: string) => FieldTypeInfo | undefined; getSharedEdgeFieldTypeInfo: (edgeKindNames: readonly string[], fieldName: string) => FieldTypeInfo | undefined; /** * True iff at least one kind in `kindNames` DECLARES `fieldName` as an own * key of its schema shape. * * The single owner of "is this name a schema field?", asked by any layer that * must tell a field access apart from something else the name could mean — * today the select-tracking proxies, which would otherwise classify a * DECLARED field named `toString` / `constructor` / `valueOf` as an inherited * `Object.prototype` member and never track it. * * Deliberately ANY rather than every: a name one kind of a polymorphic alias * declares is a field access, not a prototype access. Whether the field has a * usable type across all of them is a separate question, answered by * {@link SchemaIntrospector.getSharedFieldTypeInfo}, which stays `undefined` * unless every kind agrees. */ hasDeclaredField: (kindNames: readonly string[], fieldName: string) => boolean; /** Edge counterpart of {@link SchemaIntrospector.hasDeclaredField}. */ hasDeclaredEdgeField: (edgeKindNames: readonly string[], fieldName: string) => boolean; /** * True iff every kind in `kindNames` has at least one `searchable()` * field. For polymorphic aliases, `$fulltext` is available only when * every resolved kind has searchable content — otherwise `.matches()` * would silently miss some kinds. */ hasSearchableField: (kindNames: readonly string[]) => boolean; }>; /** * Predicate builders for TypeGraph queries. * * Provides a fluent API for building type-safe predicates. */ /** * A chainable predicate that can be combined with AND/OR. */ type Predicate = Readonly<{ __expr: PredicateExpression; and: (other: Predicate) => Predicate; or: (other: Predicate) => Predicate; not: () => Predicate; }>; /** * Creates a named parameter reference for prepared queries. * * Use with `query.prepare()` to create reusable parameterized queries. * Supported in scalar comparison positions (eq, neq, gt, etc.), string * operations, between bounds, and as the WHOLE list of `in()`/`notIn()` — * `field.in(param("ids"))` bound to an array at execute time. A `param()` * among the individual elements of a literal list is rejected. * * @example * ```typescript * const prepared = store.query() * .from("Person", "p") * .whereNode("p", (p) => p.name.eq(param("name"))) * .select((ctx) => ctx.p) * .prepare(); * * const results = await prepared.execute({ name: "Alice" }); * ``` * * @example List-valued parameter — one compiled statement, any list length. * ```typescript * const byIds = store.query() * .from("Person", "p") * .whereNode("p", (p) => p.id.in(param("ids"))) * .select((ctx) => ctx.p) * .prepare(); * * await byIds.execute({ ids: ["a", "b"] }); * await byIds.execute({ ids: ["c"] }); * ``` */ declare function param(name: string): ParameterRef; /** * Type guard for ParameterRef values. */ declare function isParameterRef(value: unknown): value is ParameterRef; /** * Options for the `$fulltext.matches()` predicate. * * Note the term glossary used throughout TypeGraph search: * - **`k`** (the positional argument on `.matches(query, k)`) is the * per-predicate top-k cap applied inside the fulltext CTE. * - **`limit`** (on store-level `search.fulltext` / `search.hybrid` and * on the query builder) is the final result count returned to the * caller after any fusion. */ type MatchesOptions = Readonly<{ /** * Parse mode for the query string. Default: "websearch". * * - "websearch": Google-style syntax (quoted phrases, `-excluded`, `OR`). * Postgres: `websearch_to_tsquery`. SQLite: translated to FTS5 MATCH. * - "phrase": treats the whole query as a phrase. * - "plain": splits on whitespace and ANDs terms. * - "raw": dialect-native syntax passed through unchanged. */ mode?: FulltextQueryMode; /** * Language override for query parsing. Default: the kind's declared * language when every kind in the alias shares one (a plan-time * constant, so PostgreSQL can serve the match from the GIN index); * mixed-language subclass aliases fall back to the per-row language * column. */ language?: string; /** Minimum relevance score to include. */ minScore?: number; }>; /** * Options for the similarTo method. */ type SimilarToOptions = Readonly<{ /** Similarity metric to use. Default: "cosine" */ metric?: VectorMetricType; /** * Minimum similarity score to include results. * For cosine: 0-1 where 1 is identical. * For L2: maximum distance to include. * For inner_product: minimum inner product value. */ minScore?: number; /** * Opt into approximate retrieval: each declaring kind's candidates come * from the engine's native ANN structure (pgvector HNSW/IVFFlat scans, * vec0 `MATCH … k=`, libSQL `vector_top_k`) instead of an exact * distance scan. This is a SEMANTIC change — results are subject to the * index's recall, and predicates composed alongside constrain the ANN * candidate set (exact on pgvector/vec0; bounded by over-fetch on * libSQL). Default: exact. * * Two states cannot serve it, and they are handled differently on purpose: * * - **A slot declared `indexType: "none"`** has no ANN structure, so the * opt-in compiles to the exact scan and results are unchanged. A * DECLARED degradation: the query is still exactly what was asked for, * only faster or slower. * - **A `metric` override that differs from the field's declared metric** * is REFUSED with a `ConfigurationError` naming both metrics. An ANN * structure retrieves only under the metric it was built for, so the two * options state something that cannot both hold — and serving the exact * scan instead would silently drop one of them. Omit `metric` (or pass * the declared one) to keep approximate retrieval, or drop `approximate` * to scan exactly under the overriding metric. */ approximate?: boolean; }>; /** * Creates a field reference. */ type FieldRefOptions = Readonly<{ jsonPointer?: JsonPointer | undefined; valueType?: ValueType | undefined; elementType?: ValueType | undefined; nullable?: boolean | undefined; }>; declare function fieldRef(alias: string, path: readonly string[], options?: FieldRefOptions): FieldRef; /** * Node-level fulltext accessor. Always present on the `NodeAccessor` * type — a runtime check throws a clear error if the node kind has no * `searchable()` fields. `k` is the top-k cap applied inside the * fulltext CTE; defaults to `DEFAULT_FULLTEXT_MATCH_K`. Use a larger * value when feeding the result into RRF (`.fuseWith()` / * `store.search.hybrid()`). */ type FulltextAccessor = Readonly<{ matches: (query: string, k?: number, options?: MatchesOptions) => Predicate; }>; /** * Creates an EXISTS subquery predicate. * Returns true if the subquery returns at least one row. * * @param subquery - The subquery AST to check for existence * * @example * ```typescript * // Find persons who have at least one order * query * .from("Person", "p") * .whereNode("p", () => * exists( * query.from("Order", "o") * .whereNode("o", (o) => o.customerId.eq(field("p.id"))) * .select((ctx) => ({ id: ctx.o.id })) * .toAst() * ) * ) * ``` */ declare function exists(subquery: QueryAst): Predicate; /** * Creates a NOT EXISTS subquery predicate. * Returns true if the subquery returns no rows. * * @param subquery - The subquery AST to check for non-existence */ declare function notExists(subquery: QueryAst): Predicate; /** * Creates an IN subquery predicate. * Returns true if the field value is in the subquery results. * * @param field - The field to check * @param subquery - The subquery AST that returns a single column * * @example * ```typescript * // Find persons whose ID is in the VIP list * query * .from("Person", "p") * .where(() => * inSubquery( * fieldRef("p", ["id"]), * query.from("VIPMember", "v") * .aggregate({ * id: fieldRef("v", ["props", "personId"], { valueType: "string" }), * }) * .toAst() * ) * ) * ``` */ declare function inSubquery(field: FieldRef, subquery: QueryAst): Predicate; /** * Creates a NOT IN subquery predicate. * Returns true if the field value is not in the subquery results. * * @param field - The field to check * @param subquery - The subquery AST that returns a single column */ declare function notInSubquery(field: FieldRef, subquery: QueryAst): Predicate; /** Parameter expressions reused by a query and its typed preparation boundary. */ type PreparedParameterDeclaration = Readonly>; type PreparedBindings = { readonly [Name in keyof Parameters]: Parameters[Name] extends (DatabaseExpression) ? Value : never; }; /** * How a "current" valid-time read instant is emitted into compiled SQL: * * - `"literal"` — bound as a concrete value at compile time * ({@link currentReadInstant}). The instant is frozen into the compiled * statement, so the statement must be recompiled to observe a newer "now". * - `"placeholder"` — bound as a reserved execution-time placeholder * ({@link currentReadInstantPlaceholder}). The statement can be compiled * once and reused across executions, filling a fresh instant each call. * * Defaults to `"literal"` everywhere except the query builder's compiled * template cache, which opts into `"placeholder"` so a reused/prepared query * never freezes "now" the way a cached literal would (the #246 regression). */ type ReadInstantMode = "literal" | "placeholder"; /** * Query Compiler Module * * Main entry point for compiling query ASTs to SQL. * Re-exports individual compiler modules and provides the main compile functions. */ /** * Options for query compilation. */ type CompileQueryOptions = Readonly<{ /** SQL dialect ("sqlite" or "postgres"). Defaults to "sqlite". */ dialect?: SqlDialect | undefined; /** SQL schema configuration from createSqlSchema(...). Defaults to standard names. */ schema?: SqlSchema | undefined; /** * Fulltext strategy override. When set, overrides the dialect's * default fulltext strategy for `$fulltext.matches()` compilation. * Callers typically read this from `resolveBackendFulltext(backend)` so * a backend-declared strategy (e.g. ParadeDB) wins over the dialect * default (tsvector), and `false` (a backend built with * `fulltext: false`) compiles a `matches()` predicate to a typed * refusal instead of the dialect default. */ fulltextStrategy?: FulltextStrategy | false | undefined; /** * Vector strategy override. When set, `field.similarTo(...)` * predicates compile against the strategy's per-`(kind, field)` * tables and distance expression. Callers read it from * `backend.vectorStrategy` so a backend-declared strategy (pgvector, * libsql-native, sqlite-vec) drives the per-field relevance scan. */ vectorStrategy?: VectorStrategy | undefined; /** * Whether the active backend supports SQL window functions such as * `ROW_NUMBER()`. Defaults to true for direct compiler callers. */ windowFunctions?: boolean | undefined; /** Whether ordered scalar collection aggregates are supported. Defaults to true for direct compiler callers. */ orderedAggregates?: boolean | undefined; /** * Declared embedding slots `(kind, fieldPath) -> descriptor` used by * the `field.similarTo(...)` CTE to know which kinds in an alias * declare the field. Callers build it from the graph's node schemas. */ vectorSlots?: VectorSlotMap | undefined; /** Per-kind declared fulltext language (see buildFulltextLanguages). */ fulltextLanguages?: ReadonlyMap | undefined; /** * Recorded read relation to use when the AST carries `recordedAsOf`. * Supplying a recorded timestamp without this binding is rejected so the * compiler never silently swaps to TypeGraph's built-in history tables when * the store was only configured for live/valid-time reads. */ recordedReadBinding?: RecordedReadBinding | undefined; /** * How the "current" valid-time read instant is emitted. Defaults to * `"literal"` (a value frozen at compile time). The query builder's * compiled template cache passes `"placeholder"` so the instant is a * reserved execution-time slot, letting one compiled statement be reused * across executions with a fresh "now" each call. See {@link ReadInstantMode}. */ readInstant?: ReadInstantMode | undefined; /** Equal-id behavior for historical identity traversal reconstruction. */ identitySameIdAcrossKinds?: "fold" | "ignore" | undefined; /** * Whether the active backend can compute a bounded transitive closure in * one round trip. Defaults to {@link COMPILER_DEFAULT_RECURSIVE_TRAVERSAL} * for direct compiler callers, mirroring `windowFunctions`. */ recursiveTraversal?: RecursiveTraversalVerdict | undefined; }>; /** * Dynamic query builder types. * * Backs `fromDynamic` / `traverseDynamic` / `optionalTraverseDynamic` * / `toDynamic` — the string-keyed sibling methods on `QueryBuilder` * and `TraversalBuilder` that admit runtime-declared kinds. Same SQL * compiler under the hood; only the alias-level surface types differ. * * Aliases declared via the dynamic methods carry `DynamicNodeType` / * `DynamicEdgeType` brands. `NodeAccessor` / `EdgeAccessor` / * `SelectableNode` / `SelectableEdge` branch on those brands so a * single query can mix typed and dynamic aliases — a typed alias keeps * `StringFieldAccessor`, etc., while a dynamic alias gets * `DynamicNodeAccessor` with a `.field(name)` discriminator. */ declare const DYNAMIC_NODE_BRAND: unique symbol; declare const DYNAMIC_EDGE_BRAND: unique symbol; /** A runtime-registered node kind whose collection lookup preserved `K`. */ type DynamicNodeKind = K & Readonly<{ [DYNAMIC_NODE_BRAND]: true; }>; type DynamicNodeType = NodeType> & Readonly<{ [DYNAMIC_NODE_BRAND]: true; }>; type DynamicEdgeType = AnyEdgeType & Readonly<{ [DYNAMIC_EDGE_BRAND]: true; }>; type IsDynamicNodeType = N extends Readonly<{ [DYNAMIC_NODE_BRAND]: true; }> ? true : false; type IsDynamicEdgeType = E extends Readonly<{ [DYNAMIC_EDGE_BRAND]: true; }> ? true : false; /** * Type-discriminated field builder for runtime-typed properties. * * `BaseFieldAccessor` methods (`eq`, `isNull`, etc.) are available * directly. Type-specific methods (`gte`, `contains`, `similarTo`, …) * sit behind a discriminator method that asserts the field's type: * * ```ts * (n) => n.field("year").number().gte(2020) * ``` * * The discriminator validates against the registered Zod schema at * query-build time and throws `TypeError` on mismatch — the user can't * accidentally call `.between(...)` on a string field. The discriminator * is a type assertion *and* a runtime check. */ type DynamicFieldBuilder = BaseFieldAccessor & Readonly<{ string: () => StringFieldAccessor; number: () => NumberFieldAccessor; date: () => DateFieldAccessor; array: () => ArrayFieldAccessor; object: () => ObjectFieldAccessor>>; embedding: () => EmbeddingFieldAccessor; }>; /** * Predicate accessor for a runtime-kind alias. * * System fields keep their narrow types. Schema properties are reached * through `.field(name)` — `.field()` validates the property exists on * the registered Zod schema and throws if it doesn't. */ type DynamicNodeAccessor = Readonly<{ id: StringFieldAccessor; kind: StringFieldAccessor; $fulltext: FulltextAccessor; field: (name: string) => DynamicFieldBuilder; }>; type DynamicEdgeAccessor = Readonly<{ id: StringFieldAccessor; kind: StringFieldAccessor; fromId: StringFieldAccessor; toId: StringFieldAccessor; field: (name: string) => DynamicFieldBuilder; }>; type DynamicSelectableNode = Readonly<{ id: string; kind: string; meta: SelectableNodeMeta; }> & Readonly>; type DynamicSelectableEdge = Readonly<{ id: string; kind: string; fromId: string; toId: string; meta: SelectableEdgeMeta; }> & Readonly>; /** * Reads node kinds without an inferred conditional that stays deferred for * arrays of generic node types. Non-array values contribute never. */ type ArrayNodeKinds = T[number & keyof T]["kind" & keyof T[number & keyof T]] & string; /** Projects each declaration separately so mixed arrays/maps retain all kinds. */ type EdgeTargetKinds = T extends unknown ? ArrayNodeKinds | ArrayNodeKinds : never; /** * Shared type definitions for the query builder. * * Contains type definitions used across QueryBuilder, TraversalBuilder, * and ExecutableQuery classes. */ type QueryCoordinateState = "open" | "sealed"; /** * A query deferred for execution inside a `store.batch()` call. * * Both `ExecutableQuery` and `UnionableQuery` satisfy this interface. * The result type `R` is preserved per-query in the batch return tuple. * * Deferring costs nothing and saves nothing on its own: `store.batch()` runs * each query as its own statement (sometimes two — see `ExecutableQuery`'s * `executeOn`), so it does not fold these into a single round trip. Whether * they share one connection is up to the adapter, not to * `backend.capabilities.execution.interactiveTransactions` — see `store.batch()` for the full cost * model. */ type BatchableQuery = Readonly<{ executeOn: (backend: GraphBackend | TransactionBackend) => Promise; }>; /** A read whose result can be embedded in an exact-one-statement batch. */ type OneStatementBatchableQuery = Readonly<{ execute?: () => Promise; /** @internal Resolved by `store.batchOnce()` before execution. */ compileOneStatementBatchItem?: () => Readonly<{ query: CompiledSelectSql; /** Graph and execution target that authorized this compiled read. */ provenance: Readonly<{ graphId: string; executionTarget: object; }>; outputNames: readonly string[]; /** Internal columns carried through the batch envelope for result mapping. */ hiddenOutputNames?: readonly string[]; orderBy: readonly Readonly<{ column: string; direction: "asc" | "desc"; nulls: "first" | "last"; }>[]; mapRows: (rows: readonly Record[]) => R; }>; }>; /** A cold read with a concrete one-statement batch compilation contract. */ type CompiledOneStatementRead = Required, "compileOneStatementBatchItem">>; /** A read that can be embedded in an exact-one-statement batch. */ type EmbeddableOneStatementRead = OneStatementBatchableQuery & (BatchableQuery | CompiledOneStatementRead); /** An embeddable one-statement read that can also execute independently. */ type ExecutableOneStatementRead = CompiledOneStatementRead & Required, "execute">>; /** Preserves each input query's result type in an exact-one-statement batch. */ type OneStatementBatchResults[]> = { -readonly [K in keyof Queries]: Queries[K] extends (EmbeddableOneStatementRead) ? R : never; }; /** Public input accepted by the exact-one-statement batch surface. */ type OneStatementBatchReads = readonly EmbeddableOneStatementRead[]; /** * Maps a tuple of BatchableQuery types to their result types. * * Given `[BatchableQuery, BatchableQuery]`, produces * `[readonly A[], readonly B[]]`. */ type BatchResults[]> = { -readonly [K in keyof Queries]: Queries[K] extends BatchableQuery ? readonly R[] : never; }; /** Provenance and root identity for a set-update candidate source. */ type NodeCandidateSelection = Readonly<{ graphId: string; executionTarget: object | undefined; kind: string; idColumn: string; temporalMode: "current" | "asOf" | "includeEnded" | "includeTombstones"; recordedAsOf: string | undefined; }>; /** A Store-created query that can provide candidate node ids to a set update. */ type NodeCandidateQuery = Readonly<{ compileNodeCandidateIds: (readInstant?: string) => CompiledSelectSql; toNodeCandidateSelection: () => NodeCandidateSelection; }>; /** * Extracts the declared target kinds for an edge traversal. * Outgoing traversals use the full range of an array or source-dependent map; * incoming traversals use the source array. */ type ValidEdgeTargets = G["edges"][EK] extends EdgeRegistration ? Dir extends "out" ? ArrayNodeKinds | EdgeTargetKinds : G["edges"][EK]["from"][number]["kind"] : never; /** * A node alias with its associated type. */ type NodeAlias = Readonly<{ type: K; alias: string; optional: Optional; }>; /** * A map of alias names to their node aliases. */ type AliasMap = Readonly>>; type EmptyAliasMap = Readonly>; /** * An edge alias with its associated type and optional flag. */ type EdgeAlias = Readonly<{ type: E; alias: string; optional: Optional; }>; /** * A map of alias names to their edge aliases. */ type EdgeAliasMap = Readonly>>; type EmptyEdgeAliasMap = Readonly>; /** * A recursive alias marker with its associated type (depth or path). */ type RecursiveAlias = Readonly<{ type: T; pathFormat?: PathFormat; optional?: Optional; }>; type QualifiedRecursivePathNode = Readonly<{ type: "node"; kind: string; id: string; }>; type QualifiedRecursivePathEdge = Readonly<{ type: "edge"; kind: string; id: string; direction: "out" | "in"; }>; type QualifiedRecursivePathElement = QualifiedRecursivePathNode | QualifiedRecursivePathEdge; type QualifiedRecursivePath = readonly QualifiedRecursivePathElement[]; type QualifiedRecursivePathOption = Readonly<{ alias?: string; format: "qualified"; }>; /** * A map of recursive alias names to their types. */ type RecursiveAliasMap = Readonly>>; type EmptyRecursiveAliasMap = Readonly>; /** * Resolves a recursive alias marker to its runtime value type. */ type RequiredRecursiveAliasValue = RA extends RecursiveAlias<"depth", "ids" | "qualified", boolean> ? number : RA extends RecursiveAlias<"path", "qualified", boolean> ? QualifiedRecursivePath : RA extends RecursiveAlias<"path", "ids", boolean> ? readonly string[] : never; type RecursiveAliasValue = RA extends { optional?: true; } ? RequiredRecursiveAliasValue | undefined : RequiredRecursiveAliasValue; /** * Resolves the depth alias name from the recursive config. * If a string is provided, uses it directly. If `true`, defaults to `${A}_depth`. */ type ResolveDepthAlias = DC extends string ? DC : DC extends true ? `${A}_depth` : never; /** * Resolves the path alias name from the recursive config. * If a string is provided, uses it directly. If `true`, defaults to `${A}_path`. */ type ResolvePathAlias = PC extends string ? PC : PC extends true ? `${A}_path` : PC extends QualifiedRecursivePathOption ? PC["alias"] extends string ? PC["alias"] : `${A}_path` : never; type ResolvePathFormat = PC extends QualifiedRecursivePathOption ? "qualified" : "ids"; /** * Builds the recursive alias map from depth/path config and target node alias. */ type BuildRecursiveAliases = ([DC] extends [false] ? {} : Record, RecursiveAlias<"depth", "ids", Optional>>) & ([PC] extends [false] ? {} : Record, RecursiveAlias<"path", ResolvePathFormat, Optional>>); /** * Type utility for compile-time alias collision detection. * * When A already exists in Aliases, this resolves to an error message type * that will cause a type error with a descriptive message. */ type UniqueAlias = A extends keyof Aliases ? `Error: Alias '${A}' is already in use` : A; /** * Creates typed field accessors for a node kind's properties. */ type PropsAccessor = Readonly<{ [K in CommonPropertyKeys>]-?: FieldAccessor[K]>; }>; type NodePropsFor> = N extends Readonly<{ schema: z.ZodType; }> ? z.infer : never; type FieldCategory = T extends unknown ? [ NonNullable ] extends [never] ? never : [NonNullable] extends [EmbeddingValue] ? "embedding" : [NonNullable] extends [string] ? "string" : [NonNullable] extends [number] ? "number" : [NonNullable] extends [boolean] ? "boolean" : [NonNullable] extends [Date] ? "date" : [NonNullable] extends [readonly unknown[]] ? "array" : [NonNullable] extends [Record] ? string extends keyof NonNullable ? "record" : "object" : "unknown" : never; type IsUnion = T extends Whole ? [ Whole ] extends [T] ? false : true : never; /** Keys present on every polymorphic member with one shared accessor category. */ type CommonPropertyKeys = true extends IsUnion ? { [K in Keys]-?: true extends IsUnion> ? never : K; }[Keys] : Keys; /** * A field accessor with type-appropriate predicate methods. * Uses NonNullable to handle optional fields correctly. */ type FieldAccessor = FieldAccessorForType>; /** A value accepted by equality predicates for a schema field. */ type EqualityOperand = T | FieldRef | ParameterRef; /** Values accepted by membership predicates for a schema field. */ type MembershipOperand = readonly T[] | ParameterRef; type NullFieldAccessor = Readonly<{ isNull: () => Predicate; isNotNull: () => Predicate; }>; type FieldAccessorForType = [ T ] extends [EmbeddingValue] ? EmbeddingFieldAccessor : [T] extends [string] ? StringFieldAccessor : [T] extends [number] ? NumberFieldAccessor : [T] extends [boolean] ? BooleanFieldAccessor : [T] extends [Date] ? DateFieldAccessor : [T] extends [readonly (infer U)[]] ? ArrayFieldAccessor : [T] extends [Record] ? keyof T extends never ? BaseFieldAccessor : ObjectFieldAccessor : BaseFieldAccessor; type BaseFieldAccessor = Readonly<{ eq: (value: EqualityOperand) => Predicate; neq: (value: EqualityOperand) => Predicate; isNull: () => Predicate; isNotNull: () => Predicate; in: (values: MembershipOperand) => Predicate; notIn: (values: MembershipOperand) => Predicate; }>; type StringFieldAccessor = BaseFieldAccessor & Readonly<{ gt: (value: string | ParameterRef) => Predicate; gte: (value: string | ParameterRef) => Predicate; lt: (value: string | ParameterRef) => Predicate; lte: (value: string | ParameterRef) => Predicate; contains: (pattern: string | ParameterRef) => Predicate; startsWith: (pattern: string | ParameterRef) => Predicate; endsWith: (pattern: string | ParameterRef) => Predicate; like: (pattern: string | ParameterRef) => Predicate; ilike: (pattern: string | ParameterRef) => Predicate; }>; type NumberFieldAccessor = BaseFieldAccessor & Readonly<{ gt: (value: number | ParameterRef) => Predicate; gte: (value: number | ParameterRef) => Predicate; lt: (value: number | ParameterRef) => Predicate; lte: (value: number | ParameterRef) => Predicate; between: (lower: number | ParameterRef, upper: number | ParameterRef) => Predicate; }>; type BooleanFieldAccessor = BaseFieldAccessor; type DateFieldAccessor = BaseFieldAccessor & Readonly<{ gt: (value: Date | string | ParameterRef) => Predicate; gte: (value: Date | string | ParameterRef) => Predicate; lt: (value: Date | string | ParameterRef) => Predicate; lte: (value: Date | string | ParameterRef) => Predicate; between: (lower: Date | string | ParameterRef, upper: Date | string | ParameterRef) => Predicate; }>; type ArrayFieldAccessor = NullFieldAccessor & Readonly<{ contains: (value: U) => Predicate; containsAny: (values: readonly U[]) => Predicate; containsAll: (values: readonly U[]) => Predicate; length: NumberFieldAccessor; isEmpty: () => Predicate; isNotEmpty: () => Predicate; lengthEq: (length: number) => Predicate; lengthGt: (length: number) => Predicate; lengthGte: (length: number) => Predicate; lengthLt: (length: number) => Predicate; lengthLte: (length: number) => Predicate; }>; type EmbeddingFieldAccessor = NullFieldAccessor & Readonly<{ /** * Finds the k most similar items using vector similarity. * * @param queryEmbedding - The query vector to compare against * @param k - Maximum number of results to return * @param options - Optional metric and minimum score filter */ similarTo: (queryEmbedding: readonly number[], k: number, options?: SimilarToOptions) => Predicate; }>; type ObjectComparisonAccessor = string extends keyof T ? BaseFieldAccessor : NullFieldAccessor; type ObjectFieldAccessor = ObjectComparisonAccessor & Readonly<{ get: & string>(key: K) => T[K] extends Record ? ObjectFieldAccessor : FieldAccessor; hasKey: (key: string) => Predicate; hasPath:

>(pointer: P) => Predicate; pathEquals:

>(pointer: P, value: string | number | boolean | Date) => Predicate; pathContains:

>(pointer: P, value: string | number | boolean | Date) => Predicate; pathIsNull:

>(pointer: P) => Predicate; pathIsNotNull:

>(pointer: P) => Predicate; }>; /** * Node accessor for predicate building. * * Properties are available at the top level for ergonomic access: * - `n.name` instead of `n.props.name` * - System fields: `n.id`, `n.kind` * - Fulltext: `n.$fulltext.matches(...)` — throws at query build time if * the node kind has no `searchable()` fields. Exposed at the type * level on every accessor so refinements like * `searchable().min(1)` do not make it disappear. * * For aliases declared via `fromDynamic` / `toDynamic`, `N` carries the * dynamic brand and this resolves to `DynamicNodeAccessor` — schema * properties go through a `.field(name)` discriminator. */ type NodeAccessor = IsDynamicNodeType extends true ? DynamicNodeAccessor : Readonly<{ id: StringFieldAccessor; kind: StringFieldAccessor; $fulltext: FulltextAccessor; }> & PropsAccessor; /** * Creates typed field accessors for an edge kind's properties. */ type EdgePropsAccessor = Readonly<{ [K in keyof z.infer]-?: FieldAccessor[K]>; }>; /** * Edge accessor for predicate building. * * Properties are available at the top level for ergonomic access: * - `e.role` instead of `e.props.role` * - System fields: `e.id`, `e.kind`, `e.fromId`, `e.toId` * * Dynamic-mode counterpart: see `NodeAccessor` for the brand-detection * pattern. */ type EdgeAccessor = IsDynamicEdgeType extends true ? DynamicEdgeAccessor : Readonly<{ id: StringFieldAccessor; kind: StringFieldAccessor; fromId: StringFieldAccessor; toId: StringFieldAccessor; }> & EdgePropsAccessor; /** * Metadata for a selectable node result. */ type SelectableNodeMeta = Readonly<{ version: number; validFrom: string | undefined; validTo: string | undefined; createdAt: string; updatedAt: string; deletedAt: string | undefined; }>; /** * A selectable node result. * * Properties from the schema are spread at the top level for ergonomic access: * - `node.name` instead of `node.props.name` * - System metadata is under `node.meta.*` * * For aliases declared via `fromDynamic` / `toDynamic` this resolves to * `DynamicSelectableNode` — same wire shape, schema properties typed * `unknown` for narrowing at the call site. * * `id` carries the same `NodeId` brand as `Node` (see `store/types.ts`) * so a projected id can be passed straight back into `getById`/`getByIds` * without a cast. */ type SelectableNode = N extends NodeType ? IsDynamicNodeType extends true ? DynamicSelectableNode : Readonly<{ id: NodeId; kind: N["kind"]; meta: SelectableNodeMeta; }> & Readonly> : never; /** * Metadata for a selectable edge result. */ type SelectableEdgeMeta = Readonly<{ validFrom: string | undefined; validTo: string | undefined; createdAt: string; updatedAt: string; deletedAt: string | undefined; }>; /** * A selectable edge result. * * Properties from the schema are spread at the top level for ergonomic access: * - `edge.role` instead of `edge.props.role` * - System metadata is under `edge.meta.*` * * Dynamic-mode counterpart: see `SelectableNode`. * * `traverse(edgeKind, alias)` defaults to `expand: "inverse"` (see * `GraphAlgorithms`/`QueryBuilder.traverse`'s `defaultTraversalExpansion`), * which UNIONs in rows for the *registered inverse* edge kind alongside the * requested one — `EdgeAlias`'s `E` stays pinned to the single requested * kind regardless, so under the default expansion mode the row backing * `alias` can genuinely be a different edge kind (with different endpoint * kinds and a different props schema — `inverseOf(edgeA, edgeB)` doesn't * require `edgeA`/`edgeB` to share a schema) than `E` says. Three distinct * consequences follow: * * - `kind: E["kind"]` is a **literal type that can already be wrong today** * — not a branding gap, a plain type-accuracy gap. It's typed as the * requested kind (e.g. `"manages"`) but the runtime value can be the * inverse kind (e.g. `"managedBy"`); see the `"proves why edge * id/kind/schema props can't be trusted"` test in * `tests/query-execution.test.ts` for a concrete repro. This predates * and is independent of the id/endpoint branding question below. * - The flattened `z.infer` properties have the same * type-accuracy gap as `kind`, for the same reason: `ctx.e.role` is typed * against the requested kind's schema, but an inverse-branch row's real * props came from a different schema and may not have a `role` field at * all (reading it returns `undefined`, not a type error). * - `id`/`fromId`/`toId` stay plain `string`, unlike `SelectableNode`'s * `id: NodeId` — a real ergonomics gap, but not an accuracy one: `string` * never overclaims. Branding them would compile but be actively wrong: * e.g. `store.edges..getById()` filters on `row.kind !== kind` and * silently returns `undefined` for a mismatched-kind id — worse than the * unsafe cast it would have replaced. * * Proving any of these safe requires tracking `expand` mode (`"none"` vs * not) at the type level through `TraversalBuilder`/`EdgeAlias` — evaluated * for #235 and deliberately not built: the machinery would touch every * traversal-entry overload for a benefit that only applies when a caller * opts out of the default expansion. When you know a traversal is * single-kind (e.g. via `expand: "none"`), re-brand `id`/`fromId`/`toId` * explicitly with `asNodeId`/`asEdgeId` (see #223) — e.g. * `asNodeId(edge.fromId)` for a `Person -> Company` edge — * and don't trust `kind` or schema properties without independently * verifying the traversal can't have expanded. */ type SelectableEdge = IsDynamicEdgeType extends true ? DynamicSelectableEdge : Readonly<{ id: string; kind: E["kind"]; fromId: string; toId: string; meta: SelectableEdgeMeta; }> & Readonly>; /** * Selection context passed to select callback. * * Includes node aliases, edge aliases, and recursive metadata aliases * (depth/path from variable-length traversals). Edge aliases from optional * traversals are nullable. */ type SelectContext, RecursiveAliases extends RecursiveAliasMap = {}> = Readonly<{ [A in keyof Aliases]: Aliases[A]["optional"] extends true ? SelectableNode | undefined : SelectableNode; }> & Readonly<{ [EA in keyof EdgeAliases]: EdgeAliases[EA]["optional"] extends true ? SelectableEdge | undefined : SelectableEdge; }> & Readonly<{ [RA in keyof RecursiveAliases]: RecursiveAliasValue; }>; /** * Result of a paginated query. */ type PaginatedResult = Readonly<{ /** The data items for this page */ data: readonly R[]; /** Cursor to fetch the next page (undefined if no more pages) */ nextCursor: string | undefined; /** Cursor to fetch the previous page (undefined if on first page) */ prevCursor: string | undefined; /** Whether there are more items after this page */ hasNextPage: boolean; /** Whether there are items before this page */ hasPrevPage: boolean; }>; /** * Options for cursor-based pagination. * * Use `first`/`after` for forward pagination, `last`/`before` for backward. */ type PaginateOptions = Readonly<{ /** Number of items to fetch (forward pagination) */ first?: number; /** Cursor to start after (forward pagination) */ after?: string; /** Number of items to fetch (backward pagination) */ last?: number; /** Cursor to start before (backward pagination) */ before?: string; }>; /** * Options for streaming results. */ type StreamOptions = Readonly<{ /** Number of items to fetch per batch (default: 1000) */ batchSize?: number; }>; /** * Options for recursive traversals. */ type RecursiveTraversalOptions = Readonly<{ /** Minimum number of hops before including results (default: 1) */ minHops?: number; /** Maximum number of hops (-1 means unlimited) */ maxHops?: number; /** Cycle handling policy (default: "prevent") */ cyclePolicy?: RecursiveCyclePolicy; /** Include path in output. Pass a string to customize alias. */ path?: boolean | string | QualifiedRecursivePathOption; /** Include depth in output. Pass a string to customize alias. */ depth?: boolean | string; }>; /** * Configuration for the query builder. */ type QueryBuilderConfig = Readonly<{ graphId: string; registry: KindRegistry; schemaIntrospector: SchemaIntrospector; /** Default traversal ontology expansion mode. */ defaultTraversalExpansion: TraversalExpansion; /** Whether this builder's graph enables Operational Identity. */ identityEnabled: boolean; /** Equal-id behavior used by historical identity traversal compilation. */ identitySameIdAcrossKinds: "fold" | "ignore"; backend?: GraphBackend; dialect?: SqlDialect; /** SQL schema configuration from createSqlSchema(...) for custom table names. */ schema?: SqlSchema; }>; /** * Internal state of the query builder. */ type QueryBuilderState = Readonly<{ startAlias: string; startKinds: readonly string[]; /** The current alias (last traversal target, or startAlias if no traversals) */ currentAlias: string; includeSubClasses: boolean; traversals: readonly Traversal[]; predicates: readonly NodePredicate[]; /** Filters completed match rows without restricting optional or recursive expansion. */ resultPredicate?: PredicateExpression; projection: readonly ProjectedField[]; orderBy: readonly OrderSpec[]; /** ORDER BY entries added via `ExecutableAggregateQuery.orderBy()`. */ aggregateOrderBy: readonly AggregateOrderSpec[]; limit: number | undefined; offset: number | undefined; temporalMode: TemporalMode; asOf: string | undefined; recordedAsOf?: RecordedInstant | undefined; groupBy: GroupBySpec | undefined; having: PredicateExpression | undefined; fusion: HybridFusionOptions | undefined; /** * Aliases declared via `fromDynamic` / `toDynamic`. The accessor * factories check membership to decide between typed and dynamic * predicate surfaces. */ dynamicNodeAliases: ReadonlySet; /** Edge aliases declared via `traverseDynamic` / `optionalTraverseDynamic`. */ dynamicEdgeAliases: ReadonlySet; }>; /** * Options for creating a query builder. */ type CreateQueryBuilderOptions = Readonly<{ /** Backend for query execution */ backend?: GraphBackend; /** SQL dialect for compilation */ dialect?: SqlDialect; /** SQL schema configuration from createSqlSchema(...) for custom table names */ schema?: SqlSchema; /** Default traversal ontology expansion mode (default: "inverse"). */ defaultTraversalExpansion?: TraversalExpansion; /** * Overrides whether a builder may compile identity-aware traversals * (`traverse(..., { includeIdentityMembers: true })`). * * Defaults to the graph capability carried by `buildKindRegistry(graph)`. * Passing `true` with a registry from a graph that does not enable identity * throws a `ConfigurationError` instead of producing invalid SQL. */ identityEnabled?: boolean; /** Overrides the graph registry's same-id behavior. */ identitySameIdAcrossKinds?: "fold" | "ignore"; }>; /** * PreparedQuery — a pre-validated, parameterized query. * * Created via `ExecutableQuery.prepare()`. Builds and structurally validates * the query AST once at prepare time (so a malformed query fails fast, before * the first `execute()`). * * Fast path: when the backend can compile and run raw SQL (`compileSql` + * `executeRaw`) AND the statement is raw-executable, it is compiled ONCE * into a cached template whose * "current" read instant and user `param()` refs are reserved placeholders * (see {@link buildReadInstantTemplate}). Every `execute()` fills those * placeholders — a fresh instant plus the call's bindings — and runs the * cached SQL text directly, so a reused prepared query never recompiles and * never freezes "now" the way a cached literal instant would (the #246 * regression). * * Fallback: substitutes parameter refs into the AST, compiles fresh per call, * and executes via the standard `backend.execute` path. Taken in two cases — * a backend without raw execution (custom/async), and a statement that is not * raw-executable because its execution semantics ride on the compiled SQL * OBJECT rather than its text (approximate vector search's iterative-scan * wrapper, `subgraph()`'s force-custom-plan fetches). The second applies even * on PostgreSQL with `executeRaw` available; `isRawExecutable` in * `sql-intent.ts` is the predicate that decides it. */ type PreparedQueryConfig = Readonly<{ ast: QueryAst; unoptimizedAst: QueryAst; backend: GraphBackend; dialect: SqlDialect; graphId: string; compileOptions: CompileQueryOptions; state: QueryBuilderState; selectiveFields: readonly SelectiveField[] | undefined; selectFn: (context: SelectContext) => R; schemaIntrospector: SchemaIntrospector; }>; /** * A pre-validated, parameterized query — see the module doc comment above for * how a reused prepared query runs a cached SQL template with a fresh read * instant filled per call. * * @example * ```typescript * const prepared = store.query() * .from("Person", "p") * .whereNode("p", (p) => p.name.eq(param("name"))) * .select((ctx) => ctx.p) * .prepare(); * * // Execute with different bindings * const alice = await prepared.execute({ name: "Alice" }); * const bob = await prepared.execute({ name: "Bob" }); * ``` */ declare class PreparedQuery { #private; constructor(config: PreparedQueryConfig); /** The set of parameter names required by this prepared query. */ get parameterNames(): ReadonlySet; /** * Executes the prepared query with the given parameter bindings. * * @param bindings - A record mapping parameter names to their values * @returns The query results */ execute(bindings?: Readonly>): Promise; } type RelationColumn = Readonly<{ outputName: string; valueType: ValueType; elementValueType?: ValueType; elementFields?: Readonly>; nullable: boolean; /** Proven graph-node identity carried only from a direct graph field. */ identity?: Readonly<{ component: "id" | "kind"; alias: string; }>; }>; type RelationOrder = Readonly<{ expression: DatabaseExpression; direction: SortDirection; nulls: "first" | "last"; }>; type RelationSource = Readonly<{ kind: "source"; query: QueryAst; graphId: string; options: CompileQueryOptions; }>; type DerivedRelation = Readonly<{ kind: "derived"; source: RelationAst; sourceColumns: readonly RelationColumn[]; projection: readonly Readonly<{ column: RelationColumn; expression: DatabaseExpression; }>[]; predicate?: DatabaseExpression; groupBy?: readonly DatabaseExpression[]; distinct: boolean; orderBy: readonly RelationOrder[]; limit?: number; offset?: number; }>; type SetRelation = Readonly<{ kind: "set"; operator: "union" | "unionAll" | "intersect" | "except"; left: RelationAst; right: RelationAst; columns: readonly RelationColumn[]; }>; type TopPerPartitionRelation = Readonly<{ kind: "topPerPartition"; source: RelationAst; columns: readonly RelationColumn[]; partitionBy: readonly DatabaseExpression[]; orderBy: readonly RelationOrder[]; limit: number; }>; type RelationAst = DerivedRelation | RelationSource | SetRelation | TopPerPartitionRelation; type RelationProjection = Readonly>; type RelationProjectionResult = { -readonly [Key in keyof Fields]: Fields[Key] extends (DatabaseExpression) ? Value : never; }; type RelationColumnContext = { readonly [Key in keyof Fields]: Fields[Key] extends (DatabaseExpression) ? DatabaseExpression : never; }; /** * A scalar ordering term used to choose winners within each partition. * Defaults to ascending with NULLS LAST; descending defaults to NULLS FIRST. * @public */ type TopPerPartitionOrder = Readonly<{ expression: DatabaseExpression; direction?: SortDirection; nulls?: "first" | "last"; }>; /** * Selects up to `limit` rows per partition using explicit scalar keys and ordering. * Include a stable final tie-breaker; tied rows do not expand the positive safe-integer limit. * @public */ type TopPerPartitionOptions = Readonly<{ partitionBy: (columns: RelationColumnContext) => readonly [DatabaseExpression, ...DatabaseExpression[]]; orderBy: (columns: RelationColumnContext) => readonly [TopPerPartitionOrder, ...TopPerPartitionOrder[]]; limit: number; }>; type RelationProvenance = Readonly<{ graphId: string; executionTarget: object | undefined; recordedAsOf: string | undefined; checked: boolean; temporalCoordinate: string; }>; type RelationDefinition = Readonly<{ ast: RelationAst; columns: readonly RelationColumn[]; fields: Fields; config: QueryBuilderConfig; provenance: RelationProvenance; decodeRow: (row: Record) => Result; mapped?: boolean; }>; type RelationState = Readonly<{ predicate?: DatabaseExpression; groupBy?: readonly DatabaseExpression[]; distinct: boolean; orderBy: readonly RelationOrder[]; limit?: number; offset?: number; }>; declare class ExecutableRelationQuery> { #private; constructor(definition: RelationDefinition, state?: RelationState, scopeIdentity?: symbol); where(build: (columns: RelationColumnContext) => DatabaseExpression): ExecutableRelationQuery; groupBy(build: (columns: RelationColumnContext) => readonly DatabaseExpression[]): ExecutableRelationQuery; project(build: (columns: RelationColumnContext) => NextFields): ExecutableRelationQuery; aggregate(build: (columns: RelationColumnContext) => NextFields): ExecutableRelationQuery; orderBy(build: (columns: RelationColumnContext) => DatabaseExpression, direction?: SortDirection, nulls?: "first" | "last"): ExecutableRelationQuery; /** * Selects up to N rows per partition after applying current input modifiers. * Later filters remove winners without replacement. Add ordering after this stage * to control result order. Requires backend window-function support. * @public */ topPerPartition(options: TopPerPartitionOptions): ExecutableRelationQuery; distinct(): ExecutableRelationQuery; distinctNodes(input: Readonly<{ kind: keyof Fields & string; id: keyof Fields & string; }>): ExecutableRelationQuery; limit(value: number): ExecutableRelationQuery; offset(value: number): ExecutableRelationQuery; map(mapper: (row: Result) => Mapped): ExecutableRelationQuery; union>(other: ExecutableRelationQuery): ExecutableRelationQuery; unionAll>(other: ExecutableRelationQuery): ExecutableRelationQuery; intersect>(other: ExecutableRelationQuery): ExecutableRelationQuery; except>(other: ExecutableRelationQuery): ExecutableRelationQuery; compile(): CompiledRowsSql; toSQL(): Readonly<{ sql: string; params: readonly unknown[]; }>; prepare(): Readonly<{ execute: (bindings: Readonly>) => Promise; bind: (bindings: Readonly>) => ExecutableRelationQuery; }>; prepare(parameters: Parameters): Readonly<{ execute: (bindings: PreparedBindings) => Promise; bind: (bindings: PreparedBindings) => ExecutableRelationQuery; }>; execute(): Promise; executeOn(backend: GraphBackend | TransactionBackend): Promise; first(): Promise; count(): Promise; exists(): Promise; page(options: Readonly<{ limit: number; offset?: number; }>): Promise; stream(options?: Readonly<{ pageSize?: number; }>): AsyncIterable; compileOneStatementBatchItem(): { query: CompiledRowsSql; provenance: { graphId: string; executionTarget: object; }; outputNames: string[]; orderBy: { column: string; direction: SortDirection; nulls: "first" | "last"; }[]; mapRows: (rows: readonly Record[]) => Result[]; }; } type CompatibleRelationProjection = Readonly<{ [Key in keyof Fields]: DatabaseExpression[Key]>; }>; /** * UnionableQuery - A query formed by combining multiple queries with set operations. */ interface ExecutableQueryLike { toAst(): QueryAst; /** @internal Set-operation provenance validation. */ oneStatementBatchProvenance(): Readonly<{ graphId: string; executionTarget: object | undefined; }>; } /** * Internal state for unionable query. */ type UnionableQueryState = Readonly<{ left: ComposableQuery; operator: SetOperationType; right: ComposableQuery; limit?: number; offset?: number; startAlias?: string; traversals?: readonly Traversal[]; selectFn?: (context: SelectContext) => unknown; }>; declare class UnionableQuery { #private; constructor(config: QueryBuilderConfig, state: UnionableQueryState); /** * Combines with another query using UNION. */ union(other: ExecutableQueryLike): UnionableQuery; /** * Combines with another query using UNION ALL. */ unionAll(other: ExecutableQueryLike): UnionableQuery; /** * Combines with another query using INTERSECT. */ intersect(other: ExecutableQueryLike): UnionableQuery; /** * Combines with another query using EXCEPT. */ except(other: ExecutableQueryLike): UnionableQuery; /** * Limits the number of results from the combined query. */ limit(n: number): UnionableQuery; /** * Offsets the results from the combined query. */ offset(n: number): UnionableQuery; /** * Builds the set operation AST. */ toAst(): SetOperation; /** @internal Set-operation and batch provenance validation. */ oneStatementBatchProvenance(): Readonly<{ graphId: string; executionTarget: object | undefined; }>; /** * Compiles the query and returns the SQL text and parameters. * * Requires a backend to be configured (the backend determines the SQL dialect). * Use this for debugging, logging, or running the query with a custom executor. */ toSQL(): Readonly<{ sql: string; params: readonly unknown[]; }>; /** * Compiles the set operation to SQL. */ compile(): CompiledSelectSql; /** * Executes the combined query. */ execute(): Promise; /** * Executes the combined query against a provided backend. * * Used by `store.batch()` to run several queries in sequence against one * target — a transaction on backends that have them, the backend itself * otherwise. A set operation compiles to one statement; whether that target * is a shared connection is the adapter's business, not a function of * transaction support. */ executeOn(backend: GraphBackend | TransactionBackend): Promise; /** @internal Embedding contract consumed by `store.batchOnce()`. */ compileOneStatementBatchItem?(): Readonly<{ query: CompiledSelectSql; provenance: Readonly<{ graphId: string; executionTarget: object; }>; outputNames: readonly string[]; orderBy: readonly Readonly<{ column: string; direction: "asc" | "desc"; nulls: "first" | "last"; }>[]; mapRows: (rows: readonly Record[]) => readonly R[]; }>; } type OneStatementReadProvenance = Readonly<{ graphId: string; executionTarget: object | undefined; }>; type ExpressionProjectionEntry = Readonly<{ outputName: string; expression: DatabaseExpression; }>; type ExpressionValue$1 = Expression extends DatabaseExpression ? Value : never; /** Preserves a one-field projection as a tuple so `$scalar()` can reject wider records. */ type ExpressionProjectionEntries>> = keyof Fields extends never ? readonly [] : string extends keyof Fields ? readonly ExpressionProjectionEntry[] : IsUnion extends true ? readonly ExpressionProjectionEntry[] : readonly [ExpressionProjectionEntry>]; type ExpressionSubqueryRelation = Readonly<{ getExpressionProjection: () => Projection; getExpressionScopeIdentity: () => symbol; getOneStatementReadProvenance: () => OneStatementReadProvenance; toAst: () => QueryAst; }>; type ProjectedExpressionSubqueryRelation = ExpressionSubqueryRelation; type ScalarExpressionSubqueryRelation = ExpressionSubqueryRelation]>; type ExpressionSubqueryHelpers = Readonly<{ $exists: (build: (subquery: Builder, outer: OuterContext) => ProjectedExpressionSubqueryRelation) => DatabaseExpression; $scalar: (build: (subquery: Builder, outer: OuterContext) => ScalarExpressionSubqueryRelation) => DatabaseExpression; }>; type DatabaseProjection = Readonly>; type ProjectionResult = { -readonly [Key in keyof Fields]: Fields[Key] extends (DatabaseExpression) ? Value : never; }; /** Explicit SQL results: construction never executes a JavaScript row selector. */ declare class ExecutableProjectionQuery> { #private; constructor(config: QueryBuilderConfig, state: QueryBuilderState, fields: Fields, context: () => Context, mapper?: (row: ProjectionResult) => Result); limit(value: number): ExecutableProjectionQuery; offset(value: number): ExecutableProjectionQuery; orderBy(build: (context: Context) => DatabaseExpression, direction?: SortDirection): ExecutableProjectionQuery; map(mapper: (row: Result) => Mapped): ExecutableProjectionQuery; toAst(): QueryAst; getExpressionScopeIdentity(): symbol; getOneStatementReadProvenance(): { graphId: string; executionTarget: object | undefined; }; getExpressionProjection(): ExpressionProjectionEntries; /** Enters the shared relational composition surface for derived queries and set operations. */ asRelation(): ExecutableRelationQuery; compile(): CompiledRowsSql; toSQL(): Readonly<{ sql: string; params: readonly unknown[]; }>; execute(): Promise; /** Runs on an explicitly supplied target, including store.batch() transactions. */ executeOn(backend: GraphBackend | TransactionBackend): Promise; first(): Promise; count(): Promise; exists(): Promise; prepare(): Readonly<{ execute: (bindings: Readonly>) => Promise; }>; compileOneStatementBatchItem(): { query: CompiledRowsSql; provenance: { graphId: string; executionTarget: object; }; outputNames: string[]; orderBy: { column: string; direction: SortDirection; nulls: "first" | "last"; }[]; mapRows: (rows: readonly Record[]) => Result[]; }; } /** * A query that can be executed. */ declare class ExecutableQuery { #private; constructor(config: QueryBuilderConfig, state: QueryBuilderState, selectFunction: (context: SelectContext) => R); /** * Builds the query AST (memoized — the instance is immutable). */ toAst(): QueryAst; /** * Orders results. */ orderBy(alias: A, field: string, direction?: SortDirection): ExecutableQuery; /** * Limits the number of results. */ limit(n: number): ExecutableQuery; /** * Offsets the results. */ offset(n: number): ExecutableQuery; /** * Applies a query fragment to transform this executable query. * * Useful for applying post-select transformations like ordering, * limits, and offsets from reusable fragments. * * @example * ```typescript * const paginated = (q) => q.orderBy("u", "createdAt", "desc").limit(10); * * const results = await query() * .from("User", "u") * .select((ctx) => ctx.u) * .pipe(paginated) * .execute(); * ``` * * @param fragment - A function that transforms the executable query * @returns The transformed executable query */ pipe(fragment: (query: ExecutableQuery) => ExecutableQuery): ExecutableQuery; /** * Combines this query with another using UNION (removes duplicates). */ union(other: ExecutableQuery): UnionableQuery; /** * Combines this query with another using UNION ALL (keeps duplicates). */ unionAll(other: ExecutableQuery): UnionableQuery; /** * Combines this query with another using INTERSECT. */ intersect(other: ExecutableQuery): UnionableQuery; /** * Combines this query with another using EXCEPT. */ except(other: ExecutableQuery): UnionableQuery; /** * Compiles the query and returns the SQL text and parameters. * * Requires a backend to be configured (the backend determines the SQL dialect). * Use this for debugging, logging, or running the query with a custom executor. */ toSQL(): Readonly<{ sql: string; params: readonly unknown[]; }>; /** * Compiles the query to TypeGraph's database-independent SQL fragment. * * Pass the result to a GraphBackend, or use toSQL() to render SQL text and * parameters for the configured dialect. */ compile(): CompiledSelectSql; /** * Compiles only the root node identity for use by a set-based mutation. * This deliberately ignores the JavaScript selector supplied to * `.select(...)`: candidate identity is always the root id, so changing a * result projection cannot make the mutation reference a missing column. */ compileNodeCandidateIds(readInstant?: string): CompiledSelectSql; /** * Creates a prepared (pre-validated) query that can be executed multiple * times with different parameter bindings. Builds and structurally * validates the AST once (a malformed query fails fast, here, instead of on * first use); the prepared query then compiles once into a reusable template * and fills a fresh read instant per execute() — see PreparedQuery's class * doc comment, which also covers the two cases that recompile per call * instead (no `executeRaw`, or a statement whose semantics ride on the SQL * object rather than its text). * * Use `param("name")` in predicates to create parameterized slots, * then pass values via `prepared.execute({ name: "value" })`. * * @example * ```typescript * import { param } from "@nicia-ai/typegraph"; * * const prepared = store.query() * .from("Person", "p") * .whereNode("p", (p) => p.name.eq(param("name"))) * .select((ctx) => ctx.p) * .prepare(); * * const alice = await prepared.execute({ name: "Alice" }); * const bob = await prepared.execute({ name: "Bob" }); * ``` * * @throws Error if no backend is configured */ prepare(): PreparedQuery; /** * Executes the query and returns typed results. * * Uses smart optimization to detect when only specific fields are accessed * in the select callback. If the callback only accesses simple field * references (no method calls or computations), generates optimized SQL * that only extracts those fields instead of the full props blob. * * @throws Error if no backend is configured */ execute(): Promise; /** Returns the first mapped row, preserving an existing zero limit. */ first(): Promise; /** Counts SQL match rows after grouping, offset, and limit, without running the selector. */ count(): Promise; /** Tests whether the bounded SQL relation has a row, without running the selector. */ exists(): Promise; /** * Reads rows and the active schema version in one statement snapshot. * Throws SchemaChangedError before invoking the selector on stale rows, * including when the query has no matches. Reload the schema and rebuild * the query before retrying. This does not pin subsequent request reads. * Uses a full projection; relevance and recursive queries are refused. */ executeChecked(expectedSchemaVersion: number | undefined): Promise; /** * Executes the query against a provided backend. * * Used by `store.batch()` to run several queries in sequence against one * target — a transaction on backends that have them, the backend itself * otherwise. The full compile → execute → transform pipeline runs * identically to `execute()`, but against the given backend. * * Costs one statement, or two when the selective-field path runs and its * mapping then falls back: `#tryOptimizedExecutionOn` detects that only * after its statement has executed, and the caller re-runs the full fetch. * The fallback clears the fast path for this instance. */ executeOn(backend: GraphBackend | TransactionBackend): Promise; /** @internal Set-operation and batch provenance validation. */ oneStatementBatchProvenance(): Readonly<{ graphId: string; executionTarget: object | undefined; }>; /** * Describes this query as a candidate source for a set-based node update. * Candidate updates use the root node identity, so one concrete root kind is * required even when the query traverses other kinds. */ toNodeCandidateSelection(): NodeCandidateSelection; /** @internal Embedding contract consumed by `store.batchOnce()`. */ compileOneStatementBatchItem?(): Readonly<{ query: CompiledSelectSql; provenance: Readonly<{ graphId: string; executionTarget: object; }>; outputNames: readonly string[]; orderBy: readonly Readonly<{ column: string; direction: "asc" | "desc"; nulls: "first" | "last"; }>[]; mapRows: (rows: readonly Record[]) => readonly R[]; }>; /** * Executes a paginated query using cursor-based keyset pagination. * * Cursor pagination is efficient for large datasets as it avoids OFFSET. * Requires ORDER BY to be specified for deterministic results. * * @param options - Pagination options (first/after for forward, last/before for backward) * @throws ValidationError if ORDER BY is not specified * @throws ValidationError if cursor columns don't match query ORDER BY columns */ paginate(options: PaginateOptions): Promise>; /** * Builds a cold cursor-page read that can execute independently or as one * member of `store.batchOnce()`. */ page(options: PaginateOptions): CompiledOneStatementRead> & Required>, "execute">>; /** * Returns an async iterator that streams results in batches. * * Uses cursor pagination internally for efficient memory usage. * Requires ORDER BY to be specified for deterministic results. * * @param options - Stream options (batchSize defaults to 1000) * @throws ValidationError if ORDER BY is not specified */ stream(options?: StreamOptions): AsyncIterable; } type UndefinedWhenNullish = Extract extends never ? never : undefined; type UndefinedWhenOptional = Optional extends true ? undefined : never; type ExpressionObjectChildren = { readonly [Key in Exclude>, keyof DatabaseExpression | "$get">]-?: ExpressionValue[Key] | UndefinedWhenNullish, Scope>; } & Readonly<{ $get: >>(key: Key) => ExpressionValue[Key] | UndefinedWhenNullish, Scope>; }>; type ExpressionValue = DatabaseExpression | (null extends Value ? undefined : never), Scope> & (NonNullable extends Date | readonly unknown[] ? unknown : NonNullable extends object ? ExpressionObjectChildren : unknown); type ExpressionMetadata = Readonly<{ validFrom: DatabaseExpression; validTo: DatabaseExpression; createdAt: DatabaseExpression, Scope>; updatedAt: DatabaseExpression, Scope>; deletedAt: DatabaseExpression; }>; type AliasExpressions; optional: boolean; }>, Scope extends string> = { readonly [Property in CommonPropertyKeys>]-?: ExpressionValue[Property] | (Entry["optional"] extends true ? undefined : never), Scope>; } & Readonly<{ id: DatabaseExpression; kind: DatabaseExpression; $meta: ExpressionMetadata; }>; type ExpressionAliasContext = { readonly [Alias in keyof Aliases & string]: AliasExpressions; } & { readonly [Alias in keyof Edges & string]: AliasExpressions & Readonly<{ fromId: DatabaseExpression; toId: DatabaseExpression; }>; }; type QueryExpressionContext = ExpressionAliasContext & ExpressionSubqueryHelpers, ExpressionAliasContext, (keyof Aliases | keyof Edges) & string>; /** * TraversalBuilder - Intermediate builder for edge traversals. */ /** * Resolves the edge type for an edge alias based on its kind type parameter. * * For `traverse(...)`, `EK` is a literal like `"authoredBy"` and the * type comes from the typed graph. For string-keyed `traverseDynamic(...)`, * `EK` is widened to `string`, so this falls back to `DynamicEdgeType`. * Store-issued runtime-kind evidence overrides that fallback through the * `TraversalBuilder`'s `ET` parameter. */ type EdgeTypeForKey = string extends EK ? DynamicEdgeType : EK extends keyof G["edges"] & string ? G["edges"][EK]["type"] : DynamicEdgeType; type DynamicNodeTypeFor$1 = T extends RuntimeNodeKind ? RuntimeNodeTypeFor : DynamicNodeType; /** * State for variable-length traversal configuration. */ interface VariableLengthState { enabled: boolean; minDepth: number; maxDepth: number; cyclePolicy: RecursiveCyclePolicy; pathEnabled: boolean; pathAlias?: string; pathFormat?: "qualified"; depthEnabled: boolean; depthAlias?: string; } /** * Intermediate builder for traversal operations. * * Type parameters track the edge kind and direction to constrain * which node kinds are valid targets in the `to()` method. */ declare class TraversalBuilder> { #private; constructor(config: QueryBuilderConfig, state: QueryBuilderState, edgeKinds: readonly string[], edgeAlias: EA, direction: Dir, fromAlias: string, inverseEdgeKinds?: readonly string[], optional?: Optional, variableLength?: VariableLengthState, pendingEdgePredicates?: readonly NodePredicate[], includeIdentityMembers?: boolean); /** * Enables variable-length (recursive) traversal. * Defaults to MAX_RECURSIVE_DEPTH (10) hops with cycle prevention. * Use `maxHops` to override (up to MAX_EXPLICIT_RECURSIVE_DEPTH). */ recursive>(options?: O): TraversalBuilder; /** * Adds a WHERE clause for the edge being traversed. * * @param alias - The edge alias to filter on (must be the current edge alias) * @param predicateFunction - A function that builds predicates using the edge accessor */ whereEdge(alias: EA, predicateFunction: (edge: EdgeAccessor) => Predicate): TraversalBuilder; /** * Specifies the target node kind. * * The kind must be a valid target for this edge based on the traversal direction: * - "out" direction: kind must be in the edge's "to" array * - "in" direction: kind must be in the edge's "from" array * * @param kind - The target node kind * @param alias - A unique alias for this node (compile-time error if duplicate) */ to, A extends string>(kind: K, alias: UniqueAlias, options?: { includeSubClasses?: false; }): QueryBuilder>, EdgeAliases & Record>, RecAliases & BuildRecursiveAliases, CoordinateState>; to, A extends string>(kind: K, alias: UniqueAlias, options: { includeSubClasses: true; }): QueryBuilder>, EdgeAliases & Record>, RecAliases & BuildRecursiveAliases, CoordinateState>; /** * Runtime-kind sibling of `to`; accepts a kind name or Store-issued token. * Throws `KindNotFoundError` if the kind is not registered. */ toDynamic(kind: T, alias: UniqueAlias, options?: { includeSubClasses?: boolean; }): QueryBuilder, Optional>>, EdgeAliases & Record>, RecAliases & BuildRecursiveAliases, CoordinateState>; } /** * Identity-aware traversal option, available only on a graph that declares an * identity configuration. On any other graph the property is typed `never`, so * setting it is a compile error (and a runtime guard rejects it as well). * * When `includeIdentityMembers` is true, the traversal's source hop matches an * edge attached to *any* coordinate-visible member of the source node's * identity class, not just the source node itself. Semantics: * * - Results are physical rows: the nodes and edges returned are the ones * actually stored, never a synthesized merge of the class. * - Identity-class membership is resolved at the query's own coordinate, so a * traversal under `asOf`/`asOfRecorded` follows only the assertions that were * in force at that instant; a retracted assertion stops conducting. * - Within a step, physical edge ids are deduplicated. The one exception is a * self-inverse edge between two folded peers (same id, different kind), which * legitimately matches in both directions and is kept. * - Under recursion, cycle detection keys on (kind, id) rather than id alone, * so passing through two folded peers is not mistaken for a revisit. Path * output is unaffected: it remains an array of bare node ids. */ type IdentityTraversalOption = G["identity"] extends GraphIdentityConfig ? Readonly<{ includeIdentityMembers?: boolean; }> : Readonly<{ includeIdentityMembers?: never; }>; type DynamicNodeTypeFor = T extends RuntimeNodeKind ? RuntimeNodeTypeFor : DynamicNodeType; type DynamicEdgeTypeFor = T extends RuntimeEdgeKind ? RuntimeEdgeTypeFor : DynamicEdgeType; type TemporalMethod = CoordinateState extends "open" ? (mode: TemporalMode, asOf?: string) => QueryBuilder : never; /** * The fluent query builder. * * Type parameters accumulate as methods are chained: * - G: The graph definition * - Aliases: Map of alias names to their node kinds * - EdgeAliases: Map of alias names to their edge kinds (accumulated during traversals) */ declare class QueryBuilder { #private; readonly temporal: TemporalMethod; constructor(config: QueryBuilderConfig, state: QueryBuilderState); /** * Starts a query from one kind or a nonempty explicit list of kinds. * Lists scan exactly the requested kinds; duplicates are normalized. * Properties used in predicates and expressions must be shared by all kinds. * * @param kind - The node kind to start from * @param alias - A unique alias for this node (compile-time error if duplicate) */ from(kinds: Kinds, alias: UniqueAlias): QueryBuilder>, EdgeAliases, RecursiveAliases, CoordinateState>; from(kind: K, alias: UniqueAlias, options?: { includeSubClasses?: false; }): QueryBuilder>, EdgeAliases, RecursiveAliases, CoordinateState>; from(kind: K, alias: UniqueAlias, options: { includeSubClasses: true; }): QueryBuilder, EdgeAliases, RecursiveAliases, CoordinateState>; /** * Runtime-kind sibling of `from`; accepts a kind name or Store-issued token. * Throws `KindNotFoundError` if the kind is not registered. String-keyed * predicates use the `n.field("name").number().gte(...)` discriminator. */ fromDynamic(kind: T, alias: UniqueAlias, options?: { includeSubClasses?: boolean; }): QueryBuilder>>, EdgeAliases, RecursiveAliases, CoordinateState>; /** Stops a recursive branch at a matching endpoint; stopping endpoints are emitted by default. */ stopExpansion(alias: A, build: (node: NodeAccessor) => Predicate, options?: Readonly<{ emitStopNode?: boolean; }>): QueryBuilder; /** Filters completed match rows, preserving optional and recursive expansion semantics. */ where(build: (context: QueryExpressionContext) => DatabaseExpression): QueryBuilder; /** * Adds a WHERE clause for a node. */ whereNode(alias: A, predicateFunction: (n: NodeAccessor, expressions: QueryExpressionContext) => Predicate | DatabaseExpression): QueryBuilder; /** * Adds a WHERE clause for an edge. * * @param alias - The edge alias to filter on * @param predicateFunction - A function that builds predicates using the edge accessor */ whereEdge(alias: EA, predicateFunction: (edge: EdgeAccessor, expressions: QueryExpressionContext) => Predicate | DatabaseExpression): QueryBuilder; /** * Traverses an edge to another node (outgoing direction). * * By default, traverses from the current node (last traversal target, or start node). * Use the `from` option to traverse from a different alias (fan-out pattern). * * @param options.expand - Ontology expansion mode for implying/inverse edges * @param options.from - Alias to traverse from (defaults to current/last traversal target) */ traverse(edgeKind: EK, edgeAlias: EA, options?: { direction?: "out"; expand?: TraversalExpansion; from?: keyof Aliases & string; } & IdentityTraversalOption): TraversalBuilder; /** * Traverses an edge to another node (incoming direction). * * By default, traverses from the current node (last traversal target, or start node). * Use the `from` option to traverse from a different alias (fan-out pattern). * * @param options.direction - Set to "in" for incoming edge traversal * @param options.expand - Ontology expansion mode for implying/inverse edges * @param options.from - Alias to traverse from (defaults to current/last traversal target) */ traverse(edgeKind: EK, edgeAlias: EA, options: { direction: "in"; expand?: TraversalExpansion; from?: keyof Aliases & string; } & IdentityTraversalOption): TraversalBuilder; /** * Runtime-kind sibling of `traverse`; accepts a kind name or Store-issued * token. Throws `KindNotFoundError` if the edge kind is not registered. */ traverseDynamic(edgeKind: T, edgeAlias: EA, options?: { direction?: TraversalDirection$1; expand?: TraversalExpansion; from?: keyof Aliases & string; } & IdentityTraversalOption): TraversalBuilder>>, string, EA, TraversalDirection$1, false, false, false, RecursiveAliases, CoordinateState, DynamicEdgeTypeFor>; /** * Optionally traverses an edge to another node (LEFT JOIN semantics). * If no matching edge/node exists, the result will include null values. * * By default, traverses from the current node (last traversal target, or start node). * Use the `from` option to traverse from a different alias (fan-out pattern). * * @param options.direction - Direction of traversal: "out" (default) or "in" * @param options.expand - Ontology expansion mode for implying/inverse edges * @param options.from - Alias to traverse from (defaults to current/last traversal target) */ optionalTraverse(edgeKind: EK, edgeAlias: EA, options?: { direction?: "out"; expand?: TraversalExpansion; from?: keyof Aliases & string; } & IdentityTraversalOption): TraversalBuilder; optionalTraverse(edgeKind: EK, edgeAlias: EA, options: { direction: "in"; expand?: TraversalExpansion; from?: keyof Aliases & string; } & IdentityTraversalOption): TraversalBuilder; /** * Runtime-kind sibling of `optionalTraverse`; accepts a kind name or * Store-issued token. LEFT JOIN semantics — non-matching rows produce a null * edge alias instead of dropping. Throws `KindNotFoundError` if the edge kind * is not registered. */ optionalTraverseDynamic(edgeKind: T, edgeAlias: EA, options?: { direction?: TraversalDirection$1; expand?: TraversalExpansion; from?: keyof Aliases & string; } & IdentityTraversalOption): TraversalBuilder, true>>, string, EA, TraversalDirection$1, true, false, false, RecursiveAliases, CoordinateState, DynamicEdgeTypeFor>; /** @internal Identifies this query's lexical expression scope. */ getExpressionScopeIdentity(): symbol; /** Projects database expressions; the callback runs once when building the query. */ project>>>(build: (context: QueryExpressionContext) => Fields): ExecutableProjectionQuery>; /** Counts match rows (or groups), preserving offset and limit. */ count(): Promise; /** Tests whether the bounded relation contains a row. */ exists(): Promise; /** * Selects fields to return. */ select(selectFunction: (context: SelectContext) => R): ExecutableQuery; /** * Selects fields including aggregates. * Use with groupBy() for aggregate queries. * * @param fields - Object mapping output names to field refs or aggregate expressions */ aggregate>>>(build: (context: QueryExpressionContext) => Fields): ExecutableProjectionQuery>; aggregate>(fields: R): ExecutableAggregateQuery; /** * Orders results. */ orderBy(build: (context: QueryExpressionContext) => DatabaseExpression, direction?: SortDirection): QueryBuilder; orderBy(alias: A, field: string, direction?: SortDirection): QueryBuilder; /** * Limits the number of results. */ limit(n: number): QueryBuilder; /** * Offsets the results. */ offset(n: number): QueryBuilder; /** * Groups results by the specified field. * Use with aggregate functions like COUNT, SUM, AVG in select(). * * @param alias - The node alias to group by * @param field - The field name to group by */ groupBy(build: (context: QueryExpressionContext) => DatabaseExpression | readonly DatabaseExpression[]): QueryBuilder; groupBy(alias: A, field: string): QueryBuilder; /** * Groups results by the node ID. * Use when you want to group by a complete node rather than a specific field. * * @param alias - The node alias to group by (uses the node's ID) */ groupByNode(alias: A): QueryBuilder; /** * Filters grouped results using aggregate conditions (HAVING clause). * Use after groupBy() to filter based on aggregate values. * * @param predicate - A predicate expression to filter groups */ having(predicateOrBuild: PredicateExpression | ((context: QueryExpressionContext) => DatabaseExpression)): QueryBuilder; /** * Sets fusion parameters for hybrid (vector + fulltext) queries. * * Applies only when the query contains both a `.similarTo()` and a * `.$fulltext.matches()` predicate. Without this call, the default is * RRF with k=60 and equal weights. A mismatch between `.fuseWith()` * configuration and the predicates on the query is caught during * compilation, not here. * * @example * ```typescript * store.query() * .from("Document", "d") * .whereNode("d", d => * d.$fulltext.matches("renewable energy", 50) * .and(d.embedding.similarTo(vec, 50)) * .and(d.tenantId.eq(tenant)) * ) * .fuseWith({ k: 60, weights: { fulltext: 1.5 } }) * .limit(10) * .execute(); * ``` */ fuseWith(options: HybridFusionOptions): QueryBuilder; /** * Applies a query fragment to transform this builder. * * Fragments are reusable query transformations that can add predicates, * traversals, ordering, and other query operations. Use this for * composing complex queries from simpler, reusable parts. * * @example * ```typescript * // Define a reusable fragment * const activeUsers = createFragment()((q) => * q.whereNode("u", ({ status }) => status.eq("active")) * ); * * // Apply the fragment * const results = await query() * .from("User", "u") * .pipe(activeUsers) * .select((ctx) => ctx.u) * .execute(); * ``` * * @param fragment - A function that transforms the builder * @returns The transformed builder */ pipe(fragment: (builder: QueryBuilder) => QueryBuilder): QueryBuilder; } /** Execution choices for a one-statement read batch. */ type BatchOnceOptions = Readonly<{ /** Share hydration among compatible subgraphs. Recommended for overlapping, payload-heavy roots. Defaults to false. */ shareSubgraphs?: boolean; }>; type AggregateAliasMap = Readonly; optional: boolean; }>>>; type AliasValue = Alias extends keyof Aliases ? Aliases[Alias] : never; type AliasSchemaValue = z.infer["type"]["schema"]>; type WithAliasOptionality = AliasValue["optional"] extends true ? Value | undefined : Value; type PropertyValue = Path extends readonly [] ? Value : Path extends (readonly [ infer Head extends PropertyKey, ...infer Tail extends readonly string[] ]) ? Head extends keyof Value ? PropertyValue : unknown : unknown; type FieldResult = Field extends (FieldRef) ? unknown extends Declared ? PropsPath extends readonly ["id"] ? string : PropsPath extends readonly ["kind"] ? AliasValue["type"] extends (Readonly<{ kind: infer Kind; }>) ? Kind : string : WithAliasOptionality, PropsPath>, Aliases, Alias> : Declared : never; type AggregateFieldResult = Expression extends AggregateExpr ? Function extends "count" | "countDistinct" ? number : Function extends "sum" | "avg" ? number | undefined : Function extends "min" | "max" ? unknown extends FieldResult ? unknown : Exclude, undefined> extends (string | number | Date) ? Extract, string | number | Date> | undefined : never : never : never; /** Result type for aggregate queries, including SQL empty-set nullability. */ type AggregateResult, Aliases extends AggregateAliasMap = AggregateAliasMap> = { [K in keyof R]: R[K] extends AggregateExpr ? AggregateFieldResult : R[K] extends FieldRef ? FieldResult : never; }; type AggregateRelationFields, Aliases extends AggregateAliasMap> = { [K in keyof R]: DatabaseExpression[K]>; }; /** * An aggregate query that can be executed. */ declare class ExecutableAggregateQuery> { #private; constructor(config: QueryBuilderConfig, state: QueryBuilderState, fields: R); /** * Builds the query AST. */ toAst(): QueryAst; /** * Orders results by a grouped field or aggregate alias. * * `key` is one of the output names passed to `.aggregate({...})` — either * a grouped field (e.g. `genre`) or an aggregate alias (e.g. `bookCount`). * Both are ordered the same way: by referencing the SELECT-list output * column, since every `.aggregate()` field is projected with an alias. * * Chain multiple calls to sort by more than one key, in call order: * `.orderBy("genre").orderBy("bookCount", "desc")` sorts by genre first, * then by book count within each genre. * * @example * ```typescript * // Top 2 authors by book count * store.query() * .from("Author", "a") * .traverse("wrote", "e") * .to("Book", "b") * .groupByNode("a") * .aggregate({ author: field("a", "name"), bookCount: count("b") }) * .orderBy("bookCount", "desc") * .limit(2) * .execute(); * ``` */ orderBy(key: K, direction?: SortDirection): ExecutableAggregateQuery; /** * Limits the number of results. */ limit(n: number): ExecutableAggregateQuery; /** * Offsets the results. */ offset(n: number): ExecutableAggregateQuery; /** * Compiles the query and returns the SQL text and parameters. * * Requires a backend to be configured (the backend determines the SQL dialect). * Use this for debugging, logging, or running the query with a custom executor. */ toSQL(): Readonly<{ sql: string; params: readonly unknown[]; }>; /** * Compiles the query to TypeGraph's database-independent SQL fragment. */ compile(): CompiledSelectSql; /** Adapts this compatibility aggregate builder to the shared relation API. */ asRelation(): ExecutableRelationQuery, AggregateResult>; /** Runs on a target with the same database and transaction provenance. */ executeOn(backend: GraphBackend | TransactionBackend): Promise[]>; first(): Promise | undefined>; count(): Promise; exists(): Promise; prepare(): Readonly<{ execute: (bindings: Readonly>) => Promise[]>; bind: (bindings: Readonly>) => ExecutableRelationQuery, AggregateResult>; }>; prepare(parameters: Parameters): Readonly<{ execute: (bindings: PreparedBindings) => Promise[]>; bind: (bindings: PreparedBindings) => ExecutableRelationQuery, AggregateResult>; }>; /** @internal Embedding contract consumed by `store.batchOnce()`. */ compileOneStatementBatchItem(): { query: CompiledRowsSql; provenance: { graphId: string; executionTarget: object; }; outputNames: string[]; orderBy: { column: string; direction: SortDirection; nulls: "first" | "last"; }[]; mapRows: (rows: readonly Record[]) => AggregateResult[]; }; /** * Executes the query and returns typed results. * * @throws Error if no backend is configured */ execute(): Promise[]>; } /** * Fluent query builder for TypeGraph. * * Provides a type-safe, chainable API for building queries. * Each method returns a new builder instance with expanded type information. * * This module re-exports from the builder submodules and provides the * createQueryBuilder factory function. */ type InitialQueryBuilder = QueryBuilder; declare function createQueryBuilder(graphId: string, registry: KindRegistry, options?: CreateQueryBuilderOptions): InitialQueryBuilder; /** * StoreView collection surface buckets. * * The live NodeCollection / EdgeCollection partition into buckets a StoreView * treats differently: * * - temporal reads honor the pinned coordinate; * - current reads have no temporal axis and are refused on temporal pins; * - writes are never available on a read-only view; and * - batch reads require store.batch() and are absent from a view. * * These arrays are the single runtime source of truth for the proxy routing and * the type-level source for the derived StoreView collection types. The * StoreView surface-classification test asserts they exactly partition the live * collection methods so new collection methods cannot be silently omitted. */ /** Temporal-aware node read method names. */ declare const NODE_TEMPORAL_READ_NAMES: readonly ["getById", "getByIds", "find", "count"]; /** Current-state-only node reads (constraint / index lookups). */ declare const CURRENT_ONLY_READ_NAMES: readonly ["findByConstraint", "bulkFindByConstraint", "bulkFindByIndex"]; /** Node write method names: never available on a read-only StoreView. */ declare const NODE_WRITE_NAMES: readonly ["create", "createFromRecord", "update", "compareAndSet", "updateWhere", "delete", "hardDelete", "upsertById", "upsertByIdFromRecord", "bulkCreate", "bulkReplaceById", "bulkUpsertById", "bulkInsert", "bulkDelete", "getOrCreateByConstraint", "bulkGetOrCreateByConstraint"]; /** Temporal-aware edge read method names. */ declare const EDGE_TEMPORAL_READ_NAMES: readonly ["getById", "getByIds", "find", "count", "findFrom", "findTo", "bulkFindFrom", "bulkFindTo", "findByEndpoints"]; /** Deferred edge batch-read method names. */ declare const EDGE_BATCH_READ_NAMES: readonly ["batchFindFrom", "batchFindTo", "batchFindByEndpoints"]; /** Edge write method names: never available on a read-only StoreView. */ declare const EDGE_WRITE_NAMES: readonly ["create", "update", "delete", "hardDelete", "bulkCreate", "bulkUpsertById", "bulkInsert", "bulkDelete", "getOrCreateByEndpoints", "bulkGetOrCreateByEndpoints"]; /** Identity facade read method names: available on a read-only StoreView. */ declare const IDENTITY_READ_NAMES: readonly ["representativeOf", "membersOf", "nodesOf", "areSame", "areDifferent", "assertionsOf"]; /** Recorded-time collection point reads that reconstruct safely by id. */ declare const RECORDED_POINT_READ_NAMES: readonly ["getById", "getByIds"]; /** Edge metadata fields accepted by bounded neighbor and subgraph reads. */ type NeighborOrderField = "createdAt" | "id" | "updatedAt" | "validFrom" | "validTo"; /** Node property names accepted by adjacent-node ordering. */ type NeighborNodeOrderField = { [K in keyof G["nodes"] & string]: Exclude, "id" | "kind" | "meta"> & string; }[keyof G["nodes"] & string]; type NeighborOrder = Readonly<{ by?: "edge"; field: NeighborOrderField; direction?: "asc" | "desc"; }> | Readonly<{ by: "node"; field: NeighborNodeOrderField; direction?: "asc" | "desc"; }>; /** Per-edge-kind ordering and bound applied before traversal expands an edge. */ type EdgeReadWindow = Readonly<{ limit: number; /** Direction for this edge kind; defaults to the traversal direction. */ direction?: "both" | "in" | "out"; orderBy?: Readonly<{ field: NeighborOrderField; direction?: "asc" | "desc"; }>; }>; /** Options for reading adjacent edge-node pairs without hydrating all targets. */ type NeighborReadOptionsBoundary> = Readonly<{ edges?: readonly K[]; direction?: "both" | "in" | "out"; orderBy?: NeighborOrder; limit?: number; temporalMode?: TemporalMode; asOf?: string; }>; type NeighborReadOptions> = NeighborReadOptionsBoundary & Required, "edges">>; /** One edge and the node it reaches from the requested source and direction. */ type NeighborResult> = Readonly<{ edge: GraphEdgeForKinds; node: Node>; }>; /** * `store.introspect()` — unified read of the merged schema. * * Returns a coherent snapshot of "what does my schema look like right * now" suitable for schema-management UIs, codegen, and IDE plugins. * The previous surface was fragmented across `registry.hasNodeType`, * `store.deprecatedKinds`, and direct graph poking; `introspect()` * unifies them with explicit `origin` markers distinguishing * compile-time from runtime declarations. * * Pure read — no I/O. Built from the in-memory `GraphDef` and the * persisted-but-already-merged `extension`. `schemaVersion` / * `schemaHash` are populated when the loader cached them at * construction time and `undefined` otherwise; consumers needing a * fresh read should call `backend.getActiveSchema(graphId)` directly. */ type SchemaIntrospection = Readonly<{ graphId: string; /** Active schema version on the backend, when known to the caller. */ schemaVersion: number | undefined; /** Hash of the active schema document, when known to the caller. */ schemaHash: string | undefined; /** Consumer-owned graph-scoped JSON metadata. */ annotations: GraphAnnotations | undefined; kinds: readonly KindIntrospection[]; edges: readonly EdgeIntrospection[]; ontology: readonly OntologyIntrospection[]; deprecatedKinds: ReadonlySet; /** * The persisted graph extension, or `undefined` when the store has * no extensions. Round-trips: passing this value to * `defineGraphExtension` and `evolve` against an empty graph yields * a graph with the same extension kinds. */ extension: GraphExtension | undefined; }>; type KindIntrospection = Readonly<{ name: string; origin: "compile-time" | "runtime"; description: string | undefined; annotations: KindAnnotations | undefined; deprecated: boolean; /** * JSON-Schema view of the kind's properties. For extension kinds the * lower-level `ExtensionPropertyType` shape (with first-class * `searchable` / `embedding` modifiers) is reachable via * `introspection.extension.nodes[name].properties`. */ properties: JsonSchema; unique: readonly UniqueIntrospection[]; }>; type EdgeIntrospection = Readonly<{ name: string; origin: "compile-time" | "runtime"; description: string | undefined; from: readonly string[]; to: readonly string[]; cardinality: Cardinality; endpointExistence: EndpointExistence; properties: JsonSchema; annotations: KindAnnotations | undefined; deprecated: boolean; }>; type OntologyIntrospection = Readonly<{ metaEdge: string; from: string; to: string; origin: "compile-time" | "runtime"; }>; type UniqueIntrospection = Readonly<{ name: string; fields: readonly string[]; scope: UniquenessScope; collation: Collation; }>; type StoreAnalysisSchemaCoordinate = Readonly<{ schemaVersion?: number; schemaHash?: string; /** Fingerprint of both the Store declarations and active schema row. */ schemaFence: string; }>; type PropertyPopulationStatistics = Readonly<{ /** RFC 6901 JSON pointer to a directly addressable declared property. */ path: string; /** Rows in which the property exists, including explicit JSON null. */ presentCount: number; /** Rows in which the property exists and is the JSON null literal. */ nullCount: number; nonNullCount: number; /** `nonNullCount / count`; zero for an empty kind. */ coverage: number; }>; type KindPopulationStatistics = Readonly<{ entity: KindEntity; kind: string; count: number; properties: readonly PropertyPopulationStatistics[]; }>; type StorePopulationStatistics = Readonly<{ /** Schema coordinate observed before and after the aggregate statements. */ snapshot: StoreAnalysisSchemaCoordinate; nodes: readonly KindPopulationStatistics[]; edges: readonly KindPopulationStatistics[]; }>; type StoreDescription = Readonly<{ schema: SchemaIntrospection; statistics: StorePopulationStatistics; }>; type ValidateStoreOptions = Readonly<{ entity: KindEntity; kind: string; /** Number of records to scan. Default 100; maximum 1000. */ pageSize?: number; /** Opaque keyset cursor returned by the preceding page. */ cursor?: string; }>; type StoreValidationFailure = Readonly<{ entity: KindEntity; kind: string; id: string; /** RFC 6901 path; the empty string denotes a whole-record rule. */ path: string; /** Top-level declared property, absent for a whole-record rule. */ property?: string; code: string; reason: string; }>; type StoreValidationPage = Readonly<{ /** Schema coordinate observed before and after this page scan. */ snapshot: StoreAnalysisSchemaCoordinate; /** Number of records scanned, independent of the number of violations. */ scannedCount: number; violations: readonly StoreValidationFailure[]; nextCursor?: string; }>; type StoreAnalysisCursorStaleErrorDetails = Readonly<{ entity: KindEntity; kind: string; expectedSchemaFence: string; actualSchemaFence: string; }>; /** A validation cursor cannot be resumed after the active schema changes. */ declare class StoreAnalysisCursorStaleError extends TypeGraphError { readonly details: StoreAnalysisCursorStaleErrorDetails; constructor(details: StoreAnalysisCursorStaleErrorDetails); } /** * Discriminated union of all Node runtime types in a graph. * * Unlike `AllNodeTypes` which gives the union of *type definitions*, * `AnyNode` gives the union of *runtime node instances*. */ type AnyNode = { [K in NodeKinds]: Node; }[NodeKinds]; /** * Discriminated union of all Edge runtime types in a graph. */ type AnyEdge = { [K in EdgeKinds]: Edge; }[EdgeKinds]; /** * Discriminated union of Node runtime types narrowed to a subset of kinds. */ type SubsetNode> = { [Kind in K]: Node; }[K]; /** * Discriminated union of Edge runtime types narrowed to a subset of kinds. */ type SubsetEdge> = { [Kind in K]: Edge; }[K]; type EmptyShape = Readonly>; type NodeProjectionPropertyKey = Exclude, "id" | "kind" | "meta"> & string; type EdgeProjectionPropertyKey = Exclude, "id" | "kind" | "fromKind" | "fromId" | "toKind" | "toId" | "meta"> & string; type SubgraphNodeProjectionField = NodeProjectionPropertyKey | "meta"; type SubgraphEdgeProjectionField = EdgeProjectionPropertyKey | "meta"; type SubgraphNodeProjectionMap = NodeKinds> = Readonly<{ [K in NodeKinds]?: K extends NK ? readonly SubgraphNodeProjectionField[] : never; }>; type SubgraphEdgeProjectionMap = EdgeKinds> = Readonly<{ [K in EdgeKinds]?: K extends EK ? readonly SubgraphEdgeProjectionField[] : never; }>; type SubgraphProject = NodeKinds, EK extends EdgeKinds = EdgeKinds> = Readonly<{ /** * Node fields to keep per kind. * * Projected nodes always retain `kind` and `id`. * Use `"meta"` to include the full metadata object; omit it to exclude metadata entirely. * Only kinds present in `includeKinds` (or all node kinds when omitted) are valid keys. */ nodes?: SubgraphNodeProjectionMap; /** * Edge fields to keep per kind. * * Projected edges always retain `id`, `kind`, `fromKind`, `fromId`, * `toKind`, and `toId`. * Use `"meta"` to include the full metadata object; omit it to exclude metadata entirely. * Only edge kinds listed in `edges` are valid keys. */ edges?: SubgraphEdgeProjectionMap; }>; /** * Identity function that preserves literal types for reusable projection configs. * * Without this helper, storing a projection in a typed variable widens the * field arrays to `string[]`, defeating compile-time narrowing on results. * * @example * ```ts * const project = defineSubgraphProject(graph)({ * nodes: { Task: ["title", "meta"] }, * edges: { uses_skill: [] }, * }); * const result = await store.subgraph(rootId, { edges: ["uses_skill"], project }); * // result.nodes narrowed correctly — task.status is a type error * ``` */ declare function defineSubgraphProject(_graph: G): >(project: P) => P; type HasMeta = Selection extends readonly string[] ? "meta" extends Selection[number] ? true : false : false; type SelectedNodeProps = Selection extends readonly string[] ? Pick, Extract>> : EmptyShape; type SelectedEdgeProps = Selection extends readonly string[] ? Pick, Extract>> : EmptyShape; type ProjectedNodeResult = Readonly, "id" | "kind">> & Readonly> & (HasMeta extends true ? Readonly<{ meta: NodeMeta; }> : EmptyShape); type ProjectedEdgeResult = Readonly, "id" | "kind" | "fromKind" | "fromId" | "toKind" | "toId">> & Readonly> & (HasMeta extends true ? Readonly<{ meta: EdgeMeta; }> : EmptyShape); type ProjectionSelection = P extends Readonly<{ [K in Key]?: infer Map; }> ? Map extends Readonly> ? Kind extends keyof Map ? Map[Kind] : undefined : undefined : undefined; type SubgraphNodeResultForKind, P> = ProjectionSelection extends readonly string[] ? ProjectedNodeResult> : Node; type SubgraphEdgeResultForKind, P> = ProjectionSelection extends readonly string[] ? ProjectedEdgeResult> : Edge; type SubgraphOptions, NK extends NodeKinds, P extends SubgraphProject | undefined = undefined> = Readonly<{ /** Edge kinds to follow during traversal. Edges not listed are not traversed. */ edges: readonly EK[]; /** Maximum traversal depth from root (default: 10). */ maxDepth?: number; /** * Node kinds to include in the result. Nodes of other kinds are still * traversed through but omitted from the output. When omitted, all * reachable node kinds are included. */ includeKinds?: readonly NK[]; /** Exclude the root node from the result (default: false). */ excludeRoot?: boolean; /** * Edge direction policy (default: "out"). * - "out": follow edges in their defined direction only * - "both": follow edges in both directions (undirected traversal) */ direction?: "out" | "both"; /** Cycle policy — reuse RecursiveCyclePolicy (default: "prevent"). */ cyclePolicy?: RecursiveCyclePolicy; /** * Temporal mode applied to both nodes and edges along the traversal and in * the hydrated result. Defaults to `graph.defaults.temporalMode`. */ temporalMode?: TemporalMode; /** ISO-8601 timestamp used when `temporalMode === "asOf"`. */ asOf?: string; /** @internal Recorded coordinates are supplied by StoreView only. */ recordedAsOf?: never; /** * Optional field-level projection per node/edge kind. * * Projected nodes keep `kind` and `id`; projected edges keep their structural * endpoint fields. Kinds omitted from `project` remain fully hydrated. * Projection applies to every returned entity, including the root node. * * Only kinds present in `includeKinds` (nodes) or `edges` (edges) are valid * projection keys. Specifying a kind outside those sets is a compile-time error. */ project?: P; /** * Per-edge-kind windows applied while traversing and hydrating. Each limit * is partitioned by the current endpoint, so append-only edge histories can * contribute only their newest N targets at every hop. */ edgeWindows?: Readonly>>; }>; /** * Subgraph options as seen by the internal executor: identical to the public * {@link SubgraphOptions} except the recorded/system-time pin is a branded * instant the StoreView seam supplies. The public surface keeps * `recordedAsOf?: never`; * recorded reads reach the executor only through `store.subgraphAtCoordinate`. */ type InternalSubgraphOptions, NK extends NodeKinds, P extends SubgraphProject | undefined = undefined> = Omit, "recordedAsOf"> & Readonly<{ recordedAsOf?: RecordedInstant; }>; /** * Union of all node result types in a subgraph, respecting projection. */ type SubgraphNodeResult = NodeKinds, P = undefined> = { [Kind in NK]: SubgraphNodeResultForKind; }[NK]; /** * Union of all edge result types in a subgraph, respecting projection. */ type SubgraphEdgeResult = EdgeKinds, P = undefined> = { [Kind in EK]: SubgraphEdgeResultForKind; }[EK]; type SubgraphResult = NodeKinds, EK extends EdgeKinds = EdgeKinds, P extends SubgraphProject | undefined = undefined> = Readonly<{ /** The root node, or undefined if the root was not found or excluded. */ root: SubgraphNodeResult | undefined; nodes: ReadonlyMap>; /** Forward adjacency: fromId → edgeKind → edges to targets. */ adjacency: ReadonlyMap[]>>; /** Reverse adjacency: toId → edgeKind → edges from sources. */ reverseAdjacency: ReadonlyMap[]>>; }>; /** * Store types for TypeGraph operations. */ /** * An explicit validity-end mutation. Omission preserves the stored end, * `validTo` sets it, and `clearValidTo` reopens the window. The union keeps the * two write actions mutually exclusive without exposing public `null`. */ type ValidityEndMutation = BackendValidityEndMutation; /** * Compare-and-set absence marker. `undefined` is deliberately not overloaded: * it can disappear while an object is assembled or serialized, whereas this * marker always states the caller's predicate explicitly. */ declare const compareAndSetAbsent: unique symbol; /** Explicitly requires that a compare-and-set property is not stored. */ type CompareAndSetAbsent = typeof compareAndSetAbsent; /** Scalar-only exact predicates accepted by {@link NodeCollection.compareAndSet}. */ type CompareAndSetExpected = Readonly<{ [Property in keyof Props]?: Extract, JsonScalar> | CompareAndSetAbsent; }>; /** * Canonical mapping from snake_case row fields to camelCase meta fields. * * This is the single source of truth for which row columns become metadata. * Both the Meta types and the row-to-meta functions in row-mappers.ts must * stay in sync with this mapping. If you add a temporal/audit column to a * row type, add the mapping here — the compiler will then force you to * update the row mapper functions as well (since their return type is * NodeMeta/EdgeMeta, which is derived from this mapping). */ type TemporalMetaFieldMap = Readonly<{ valid_from: "validFrom"; valid_to: "validTo"; created_at: "createdAt"; updated_at: "updatedAt"; deleted_at: "deletedAt"; }>; /** * Maps row fields to their camelCase meta counterparts, preserving types. */ type MapRowToMeta>, M extends Readonly>> = Readonly<{ [SnakeKey in keyof M as M[SnakeKey] & string]: SnakeKey extends keyof R ? R[SnakeKey] : never; }>; /** * Metadata for a node instance. * Derived from NodeRow via TemporalMetaFieldMap + version. * * Adding a new metadata column requires: * 1. Add the column to NodeRow in backend/types.ts * 2. Add the mapping to TemporalMetaFieldMap above * 3. The compiler will error in rowToNodeMeta() until you add the field there */ type NodeMeta = MapRowToMeta & Readonly<{ version: NodeRow["version"]; }>; /** * A node instance in the graph. * * Properties from the schema are spread at the top level for ergonomic access: * - `node.name` instead of `node.props.name` * - System metadata is under `node.meta.*` */ type Node = Readonly<{ kind: N["kind"]; id: NodeId; meta: NodeMeta; }> & Readonly>; /** * Input for creating a node. */ type CreateNodeInput = Readonly<{ kind: N["kind"]; id?: string; props: z.infer; /** Omit to use the creation default; null explicitly requests no lower bound. */ validFrom?: string | null; validTo?: string; }>; /** One caller-identified member of the closed heterogeneous node upsert batch. */ type HeterogeneousNodeUpsertInput = NodeKinds> = { [P in K]: G["nodes"][P] extends Readonly<{ type: infer N extends NodeType; }> ? Readonly<{ kind: P; id: NodeId; props: z.input; }> : never; }[K]; type HeterogeneousNodeForKind> = G["nodes"][K] extends Readonly<{ type: infer N extends NodeType; }> ? Node : never; /** Ordered postimages returned by the recorded heterogeneous batch. */ type HeterogeneousNodeUpsertResult[]> = { readonly [I in keyof Entries]: Entries[I] extends (Readonly<{ kind: infer K extends NodeKinds; }>) ? HeterogeneousNodeForKind : never; }; /** * Input for updating a node. */ type UpdateNodeInput = Readonly<{ kind: N["kind"]; id: NodeId; props: Partial>; }> & ValidityEndMutation; /** * Metadata for an edge instance. * Derived from EdgeRow via TemporalMetaFieldMap (edges have no version). */ type EdgeMeta = MapRowToMeta; /** * An edge instance in the graph. * * Properties from the schema are spread at the top level for ergonomic access: * - `edge.role` instead of `edge.props.role` * - System metadata is under `edge.meta.*` */ type Edge = Readonly<{ id: EdgeId; kind: E["kind"]; fromKind: From["kind"]; fromId: NodeId; toKind: To["kind"]; toId: NodeId; meta: EdgeMeta; }> & Readonly>; /** * Input for creating an edge. */ type CreateEdgeInput = Readonly<{ kind: E["kind"]; id?: string; fromKind: string; fromId: string; toKind: string; toId: string; props: z.infer; /** Omit to use the creation default; null explicitly requests no lower bound. */ validFrom?: string | null; validTo?: string; }>; /** * Input for updating an edge. */ type UpdateEdgeInput = Readonly<{ id: EdgeId; props: Partial>; }> & ValidityEndMutation; /** * Options for node and edge queries. */ type NoRecordedCoordinate = Readonly<{ /** * Recorded/system-time coordinates are internal-only and supplied by * RecordedStoreView. A public options object carrying this key is a type * error even when the object is pre-bound before the call site. */ recordedAsOf?: never; }>; type QueryOptions = NoRecordedCoordinate & Readonly<{ /** Temporal mode for the query */ temporalMode?: TemporalMode; /** Specific timestamp for asOf queries */ asOf?: string; }>; /** * Context passed to observability hooks. */ type HookContext = Readonly<{ /** Unique ID for this operation */ operationId: string; /** Graph ID */ graphId: string; /** Timestamp when operation started */ startedAt: Date; /** * 1-based try number that produced this context. Always `1` outside a * `store.transaction(fn, { retry: { attempts } })` call; inside one, a * value above 1 marks a replay of the same logical operation after an * earlier attempt hit a transaction conflict, so a listener can tell a * retried attempt from a genuinely new operation. Absent means `1`: a * context built outside the store (a test fixture, a forwarded literal) * need not carry it. */ attempt?: number; }>; /** * Context for one SQL statement a query builder submits to the backend. * * A single logical query can emit more than one context, for example when a * selective projection falls back to a full-row fetch. Backend-internal setup * statements are not exposed as separate query-hook events. */ type QueryHookContext = HookContext & Readonly<{ /** The SQL statement being executed */ sql: string; /** Query parameters */ params: readonly unknown[]; }>; /** * Operation hook context for CRUD operations. */ type OperationHookContext = HookContext & Readonly<{ /** Operation type */ operation: "create" | "update" | "delete"; /** Entity type */ entity: KindEntity; /** Kind of node or edge */ kind: string; /** Entity ID */ id: string; }>; /** * Context for one set-based collection mutation. The `operation` union is the * exhaustive list of what `onBulkOperationStart` / `onBulkOperationEnd` * observe, so a batch method absent from it (every `bulk*`, including * `bulkDelete`) emits no bulk event. */ type BulkOperationHookContext = HookContext & Readonly<{ operation: "compareAndSet" | "updateWhere"; entity: "node"; kind: string; }>; /** * Observability hooks for monitoring store operations. * * Note: Batch operations (`bulkCreate`, `bulkInsert`, `bulkUpsertById`, * `bulkDelete`) skip per-item operation hooks for throughput, and the bulk * hooks below do not stand in for them — those fire only for node * `compareAndSet` and `updateWhere`. A batch method emits no hook events at all, * neither per-item nor bulk. Query hooks still fire normally. * * @example * ```typescript * const hooks: StoreHooks = { * onQueryStart: (ctx) => { * console.log(`[${ctx.operationId}] Query: ${ctx.sql}`); * }, * onQueryEnd: (ctx, result) => { * const duration = Date.now() - ctx.startedAt.getTime(); * console.log(`[${ctx.operationId}] Completed in ${duration}ms`); * }, * onError: (ctx, error) => { * console.error(`[${ctx.operationId}] Error:`, error); * }, * }; * * const store = createStore(graph, backend, { hooks }); * ``` */ type StoreHooks = Readonly<{ /** Called before each query-builder statement is submitted to the backend. */ onQueryStart?: (ctx: QueryHookContext) => void; /** Called after each submitted query-builder statement succeeds. */ onQueryEnd?: (ctx: QueryHookContext, result: Readonly<{ rowCount: number; durationMs: number; }>) => void; /** Called before a CRUD operation starts */ onOperationStart?: (ctx: OperationHookContext) => void; /** Called before a set-based collection mutation starts. */ onBulkOperationStart?: (ctx: BulkOperationHookContext) => void; /** * Called after a CRUD operation completes AND is durably committed. For a * top-level operation that is when its own transaction commits; for an * operation inside `store.transaction`, emission is deferred until the * enclosing transaction commits — if that commit fails, the operation is * reported through `onError` instead. (Inside an adopted transaction — * `withTransaction` / `withRecordedTransaction` — the commit belongs to * the caller and cannot be observed, so this fires when the operation * completes within the still-open transaction.) */ onOperationEnd?: (ctx: OperationHookContext, result: Readonly<{ durationMs: number; /** The authoritative effect, or `unknown` when the command reports none. */ outcome: "written" | "unchanged" | "unknown"; }>) => void; /** * Called after a set-based mutation durably commits. Inside * `store.transaction`, emission is deferred until the enclosing commit. */ onBulkOperationEnd?: (ctx: BulkOperationHookContext, result: Readonly<{ affectedCount: number; durationMs: number; }>) => void; /** * Called when an operation fails — including an operation that completed * inside a `store.transaction` whose commit then failed and rolled it * back. Must not throw: an exception this hook raises while a * transaction's failure is being reported is discarded, so the failure * being reported — not the hook's exception — is what the caller and any * enclosing retry owner receive. */ onError?: (ctx: HookContext, error: Error) => void; }>; type BaseStoreOptions = Readonly<{ /** Observability hooks for monitoring */ hooks?: StoreHooks; /** * Maintain a durable, graph-wide revision anchor for TypeGraph writes: * a random per-graph origin plus a monotonic revision clock. `branch()` uses * this anchor to validate that its base has not moved without * re-fingerprinting every live row, and cannot confuse a coincident clock in * a separately created store for its original base. `history: true` enables * the same tracking automatically through its recorded-time commit clock. * * This is intentionally opt-in for live stores: each successful graph write * advances the anchor inside its write transaction. On PostgreSQL, those * transactions take a graph-scoped advisory lock until commit; writes to the * same graph therefore serialize. Enable it for branchable graphs, but size * high-throughput multi-writer workloads for that trade-off. * * Writes performed directly through a backend — including raw graph-table * writes through `tx.sql` — bypass the anchor and are outside the * revision-tracking contract. */ revisionTracking?: boolean; /** * Automatic planner-statistics refresh after large autocommit bulk * writes (bulkCreate and bulkInsert on nodes and edges). Stale statistics after a * bulk load are a whole class of planner cliffs: the planner keeps * pre-load row estimates until ANALYZE runs. When a single * autocommit bulk write reaches the threshold * ({@link AUTO_REFRESH_STATISTICS_ROW_THRESHOLD} rows by default), * the store runs `refreshStatistics()` after the write commits. * Pass a number to change the threshold, or `false` to disable. * Bulk writes inside a caller-provided transaction never * auto-refresh (statistics cannot see uncommitted rows); refresh * manually after commit. `importGraph` handles its own refresh. */ autoRefreshStatistics?: false | number; /** * Skip the write for an `upsertById`, `bulkUpsertById` item, or endpoint * get-or-create update whose validated props are value-identical to the * existing live row. Default off. * * Enable this for at-least-once / replay materializers. An event log that * re-delivers a byte-identical change would otherwise rewrite the row anyway: * today an `upsertById` on an existing id calls `updateNode` * unconditionally, allocating a fresh recorded instant and a new history row * per re-delivery. * * The win is scoped to re-delivery of the **current** value. A full * replay-from-zero over the current state still writes wherever the stream * superseded a value in place: re-applying an older value over the live row * is a genuine change (and restoring the current value afterwards is * another), so only streams whose rows never supersede each other replay * without churn. A rebuild that should avoid that re-walk belongs in a fresh * store — replay into it and publish it, rather than re-applying the log * over current state. * * When enabled, an upsert that would not change the stored value performs * **no write at all**: no `updateNode`, no recorded-time capture, no history * row, no revision-anchor advance, and no `update` operation hooks (nothing * happened, so nothing is reported). It resolves with the **existing** node, * preserving its original `validFrom` / `updatedAt` / `version`. An endpoint * get-or-create reports action `"found"` when its requested update is * coalesced; `"updated"` always means an UPDATE actually ran. * Node `getOrCreateByConstraint` updates are outside this option's scope and * still write on every `ifExists: "update"` match; replay projectors that * need coalescing should use `upsertById` for nodes. * * Receipt shape is unchanged and needs no new signal: a coalesced upsert * still counts as one write intent (`writes.total` includes it), but * captures nothing (`receipt.recorded` stays `undefined` when it is the only * write) — the same shape a no-op delete already produces, so a consumer * that carries the prior anchor forward on `recorded === undefined` handles * it unchanged. * * A write is coalesced only when **all** of the following hold; otherwise the * normal write happens: * 1. A value to compare against is known for the id: an existing row, or — * for a repeated id in one bulk batch — the value an earlier item in that * batch already queued (a create or an update). * 2. That row is not soft-deleted (a deleted row resurrects — a real * change — and is never coalesced). * 3. Any requested `validFrom` / `validTo` names the window already stored; * a changed or inapplicable temporal request reaches the write path. * 4. The new props, merged over the stored props and run through the * kind's Zod schema (defaults applied, values normalized), are deeply * value-identical to the stored props (key order aside). * * Default-off because some consumers *want* an audit row per re-delivery as * proof the event was reprocessed; coalescing removes that signal. */ coalesceUnchangedUpserts?: boolean; /** SQL schema configuration from createSqlSchema(...) for custom table names */ schema?: SqlSchema; /** Query default behaviors. */ queryDefaults?: Readonly<{ /** Default traversal ontology expansion mode (default: "inverse"). */ traversalExpansion?: TraversalExpansion; }>; }>; /** * Store options without built-in recorded-time capture. A recorded read relation * can still be bound explicitly for hosts that populate history externally. */ type LiveStoreOptions = BaseStoreOptions & Readonly<{ history?: false | undefined; recordedRead?: ExternalRecordedReadSource | undefined; }>; /** Live-store options that do not bind a recorded-read relation. */ type UnboundLiveStoreOptions = LiveStoreOptions & Readonly<{ recordedRead?: undefined; }>; /** Live-store options with an explicitly bound recorded-read relation. */ type RecordedReadStoreOptions = LiveStoreOptions & Readonly<{ recordedRead: NonNullable; }>; /** * Store options with TypeGraph-managed recorded-time capture. `history: true` * captures TypeGraph writes and binds TypeGraph's built-in recorded relation * internally. Externally populated recorded read sources are read-only bindings * and are intentionally accepted only by {@link LiveStoreOptions}. */ type HistoryStoreOptions = BaseStoreOptions & Readonly<{ history: true; recordedRead?: never; }>; /** * Options for creating a store. */ type StoreOptions = LiveStoreOptions | HistoryStoreOptions; /** * The subset of a store's construction options that describe its own * observable behavior rather than its recorded-time configuration: hooks, * upsert coalescing, the SQL schema (custom table names), the auto-refresh- * statistics threshold, query defaults, and an externally-bound recorded-read * relation. `Store.workingCopyOptions` is the one place these are read off a * live store, so a working-copy strategy that needs them never re-derives * them from private construction state. * * `history` and `revisionTracking` are deliberately excluded: a working-copy * strategy decides those for itself (a fork mirrors the base's own * `historyEnabled`/`revisionTrackingEnabled`; a clone documents why it keeps * a narrower subset — see `cloneWorkingCopyStrategy`'s doc comment). */ type WorkingCopyOptions = Omit; /** * A mutable handle to the current `Store`, used by `store.evolve(...)` * so long-lived consumers can dereference through the ref and pick up * the new Store after each evolve call. When the ref is passed via * `evolve(extension, { ref })`, `current` is overwritten with the replacement * before a successful call resolves. * * **Request semantics.** Dereference once at request entry only when that * request will not change the schema. A schema-changing call such as * `evolve()` returns the Store for the resulting schema and updates * `ref.current`; a Store captured before that call remains pinned to the old * schema. Use the returned Store (or dereference `ref.current` again) for every * subsequent operation in the same request: * * ```ts * async function handleRequest(): Promise { * const store = ref.current; * const evolved = await store.evolve(extension, { ref }); * await evolved.materializeIndexes(); * // ref.current === evolved * } * ``` * * A `StoreRef` tracks only schema changes made by calls that receive that ref. * When another process or isolate can commit a schema version, compare * `getCommittedSchemaVersion()` with the cached version before reuse and call * `createVerifiedStore()` or `createVerifiedAdapterStore()` when it changes. * * Pure dereferenceable handle — no event/subscription machinery. If * consumers need eventing, they wrap the ref themselves. * * Generic over the held value (typically `Store`) so the store * module can refer to it without importing the `Store` class into * `types.ts`. The type parameter is deliberately invariant: evolution writes * a replacement into `current`, so a ref for an adapter-only or history-only * Store must never be accepted by a Store that cannot preserve that surface. */ interface StoreRef { current: T; } /** * Result plus write summary. Returned by `store.transactionWithReceipt(fn)`, * `store.withRecordedTransaction(externalTx, fn)` (the adopted-commit path), and * `tx.measure(fn)` (a scoped sub-receipt). See {@link TransactionReceipt}. */ type TransactionOutcome = Readonly<{ result: T; receipt: TransactionReceipt; }>; /** * Transaction write summary. * * Receipt counts are completed write intents at the collection surface, not * rows affected: * * 1. Every successful completion of a write method on `tx.nodes.*` / * `tx.edges.*` counts. The authoritative method list is * {@link NodeWrites} / {@link EdgeWrites}. * 2. Bulk methods count by input length; an empty bulk call (`bulkCreate([])`) * counts 0. * 3. Single-row methods count 1 on resolve — including `delete` of an absent * id and `getOrCreate*` that found an existing row. Consumers that need * "did anything actually change" semantics apply their own per-operation * policy. * 4. A method that rejects counts 0 — even when the backend applied part of a * bulk input before failing. On SQLite a failed statement does not abort * the surrounding transaction, so a caller that catches the rejection and * commits can persist rows the receipt never counted. Do not read the * receipt as rows-affected in that scenario. * 5. A node `delete` under `cascade` / `disconnect` removes connected edges * through the backend, not the edge-collection surface; those removals do * not appear in `edges`. * 6. Rows-affected fidelity is intentionally out of scope for this first * version; a future extension could ask backends to return row counts. */ type TransactionReceipt = Readonly<{ writes: Readonly<{ /** Completed node write intents by node kind. */ nodes: Readonly>; /** Completed edge write intents by edge kind. */ edges: Readonly>; /** Completed identity assertion and retraction write intents. */ identity: IdentityWriteSummary; /** Sum of all node, edge, and identity write intents. */ total: number; }>; /** * The recorded commit instant allocated for this store's graph by this * transaction. Under TypeGraph-owned capture, an explicit * {@link RecordedRevisionRequest} allocates this instant without entity * writes. Otherwise it is undefined when history capture is off, the * transaction is read-only, or no captured writes were flushed. **Always undefined on a * scoped receipt from {@link ScopedMeasure}** (`tx.measure`) — the recorded * instant is a per-transaction flush concern allocated once when the whole * transaction's capture flushes, unknowable mid-transaction. * * Engine-native history does not support explicit revision requests. Under * an engine-native store (one whose backend tracks recorded time * itself; see {@link GraphBackend.recordedTime}), "no captured writes were * flushed" instead means no node, edge, or identity write inside the * transaction actually changed a row: a delete of a missing id, an * `insertNodeIfAbsent` that found the row, and a coalesced no-op upsert * all leave this undefined, matching a read-only transaction, even though * each reached the collection surface as a completed write intent. A * transaction whose only effect is a raw `tx.sql` statement also leaves * this undefined, since the engine's own revision advancing is not * something a graph-entity write observed. */ recorded?: RecordedInstant; }>; /** * Behavior when a get-or-create operation matches an existing record. */ type IfExistsMode = "return" | "update"; /** * Action taken by a get-or-create operation. */ type GetOrCreateAction = "created" | "found" | "updated" | "resurrected"; /** * Result of a node getOrCreateByConstraint operation. */ type NodeGetOrCreateByConstraintResult = Readonly<{ node: Node; action: GetOrCreateAction; }>; /** * Options for node getOrCreateByConstraint operations. */ type NodeGetOrCreateByConstraintOptions = Readonly<{ /** Existing record behavior. Default: "return" */ ifExists?: IfExistsMode; }>; /** * Options for node bulkFindByIndex operations. */ type NodeBulkFindByIndexOptions = Readonly<{ /** * Maximum number of candidate nodes returned per input item. When omitted, * each input's candidate set is unbounded. Must be a positive integer. * * Candidates are ordered deterministically by node id, so the cap is stable * across calls. Use this to bound fan-out on low-selectivity index keys. */ limitPerInput?: number; }>; /** * Per-endpoint fan-out cap shared by the live and view forms of the edge * bulk endpoint reads. */ type EdgeBulkFindOptions = Readonly<{ /** * Maximum number of edges returned for each input endpoint. When omitted, * every input's edge set is unbounded. Must be a positive integer. * * The cap keeps each input's leading edges under the same ordering * `findFrom` / `findTo` return, so it bounds fan-out without reordering * anything. */ limitPerInput?: number; }>; /** * Options for the edge bulk endpoint reads: the temporal coordinate every * edge read accepts, plus the per-endpoint fan-out cap. */ type EdgeBulkFindEndpointOptions = QueryOptions & EdgeBulkFindOptions; /** A kind-grouped source list for a heterogeneous bulk edge read. */ type BulkEdgeSourceGroup = { [K in NodeKinds]: Readonly<{ kind: K; ids: readonly NodeId[]; }>; }[NodeKinds]; /** A node reference whose kind and branded id remain correlated. */ type GraphNodeReference = { [K in NodeKinds]: Readonly<{ kind: K; id: NodeId; }>; }[NodeKinds]; /** Runtime edge union narrowed to the selected graph edge kinds. */ type GraphEdgeForKinds> = { [P in K]: Edge) ? N : NodeType>; }[K]; /** Input for {@link Store.bulkFindEdgesFrom}. */ type BulkFindEdgesFromParams> = Readonly<{ sources: readonly BulkEdgeSourceGroup[]; edgeKinds: readonly K[]; }>; /** One source bucket returned by {@link Store.bulkFindEdgesFrom}. */ type BulkFindEdgesFromResult> = Readonly<{ source: GraphNodeReference; edges: readonly GraphEdgeForKinds[]; }>; /** Input for {@link Store.bulkFindEdgesTo}. */ type BulkFindEdgesToParams> = Readonly<{ targets: readonly BulkEdgeSourceGroup[]; edgeKinds: readonly K[]; }>; /** One target bucket returned by {@link Store.bulkFindEdgesTo}. */ type BulkFindEdgesToResult> = Readonly<{ target: GraphNodeReference; edges: readonly GraphEdgeForKinds[]; }>; /** * Result of an edge getOrCreateByEndpoints operation. */ type EdgeGetOrCreateByEndpointsResult = Readonly<{ edge: Edge; action: GetOrCreateAction; }>; /** * Options for edge findByEndpoints operations. */ type EdgeFindByEndpointsOptions = Readonly<{ /** * Edge property fields to include in the match alongside the (from, to) endpoints. * When omitted, matches on endpoints only (returns the first edge at the read coordinate). */ matchOn?: readonly (keyof z.input)[]; /** Property values to match against when matchOn is specified. */ props?: Partial>; }>; /** * Options for edge getOrCreateByEndpoints operations. */ type EdgeGetOrCreateByEndpointsOptions = Readonly<{ /** * Edge property fields to include in the match key alongside the (from, to) endpoints. * Default: `[]` — match on endpoints only. */ matchOn?: readonly (keyof z.input)[]; /** Existing record behavior. Default: "return" */ ifExists?: IfExistsMode; /** * Valid-time start for a created or RESURRECTED edge — on a resurrection it * asserts the complete window, so an omitted `validTo` reopens the revived * row. * * A live edge's stored lower bound is history, so an in-place update * (`ifExists: "update"`) cannot store this: one naming a different instant is * REFUSED with `IMMUTABLE_VALIDITY_LOWER_BOUND_CODE`, and one restating the * bound the edge holds is accepted. Ignored when an existing edge is returned * (`ifExists: "return"`), which writes nothing at all — there the window * describes the edge to create if none is found. */ validFrom?: string | null; /** * How an `ifExists: "update"` write treats a stated `validFrom` that differs * from the live edge's stored lower bound. Default: `"refuse"`. * * `"preserve"` makes `validFrom` create/resurrection-only input: it is still * validated, but a live update keeps the stored lower bound while applying * properties and `validTo`. */ onImmutableLowerBound?: "preserve" | "refuse"; }> & ValidityEndMutation; /** * A collection of nodes of a specific type. * * Provides ergonomic CRUD operations for a single node type. */ type NodeCollection = Readonly<{ /** * Create a new node. * * `validFrom` defaults to the operation's creation timestamp when omitted — * unless a stated `validTo` at or before that instant would leave the row * readable at no coordinate, in which case the row is stored with NO lower * bound ("ended at T, start unknown") and `meta.validFrom` reads back as * `undefined`. A future `validTo` is unaffected. */ create: (props: z.input, options?: Readonly<{ id?: string; validFrom?: string | null; validTo?: string; }>) => Promise>; /** Get a node by ID */ getById: (id: NodeId, options?: QueryOptions) => Promise | undefined>; /** Get multiple nodes by ID, preserving input order (undefined for missing) */ getByIds: (ids: readonly NodeId[], options?: QueryOptions) => Promise | undefined)[]>; /** * Update a node's properties and optionally set or clear its validity end. * Omitting both end options preserves the stored window. */ update: (id: NodeId, props: Partial>, options?: ValidityEndMutation) => Promise>; /** * Applies a property patch only while the current live row still has * caller-supplied exact property values. The identity and values participate in * the same set-based statement as the update, so a preceding read cannot * open a compare-and-set race. Returns `false` on a missing row or predicate * mismatch; neither case writes history or advances the row version. * * This is the narrow escape hatch for exceptional state recovery. It does * not widen schema-declared transition rules: callers state the exceptional * precondition at the call site and the complete after-image still passes * ordinary TypeGraph validation, uniqueness, history, and sidecar handling. */ compareAndSet: (id: NodeId, params: Readonly<{ expected: CompareAndSetExpected>; patch: Partial>; }>) => Promise; /** * Updates every current node selected by the supplied selectors in one * set-based write. A same-store candidate query can provide correlated * cross-kind selection; relationship clauses are independent EXISTS * predicates and are ANDed with each other and with `where`. */ updateWhere: (params: Readonly<{ patch: Partial>; /** * A Store-created query selecting candidate nodes of this collection's * kind. The query is intersected with `where`/`exists` when supplied. */ candidates?: NodeCandidateQuery; where?: (accessor: string extends N["kind"] ? DynamicNodeAccessor : NodeAccessor) => Predicate; exists?: readonly Readonly<{ edgeKind: string; direction: "out" | "in"; relatedKind: string; whereEdge?: (accessor: DynamicEdgeAccessor) => Predicate; whereRelated?: (accessor: DynamicNodeAccessor) => Predicate; }>[]; all?: true; }>) => Promise>; /** Delete a node (soft delete - sets deletedAt timestamp) */ delete: (id: NodeId) => Promise; /** * Permanently delete a node from the database. * * Unlike `delete()` which performs a soft delete, this permanently * removes the node and its associated data (uniqueness entries, embeddings). * * **Warning:** This operation is irreversible and should be used carefully. * Consider using soft delete (`delete()`) for most use cases. * * @throws Error if edges are still connected to this node (delete edges first) */ hardDelete: (id: NodeId) => Promise; /** * Find nodes matching criteria. * * Supports predicate filtering via the `where` option for SQL-level filtering. * For simple queries. Use store.query() for complex traversals. */ find: (filter?: Readonly<{ where?: (accessor: NodeAccessor) => Predicate; limit?: number; offset?: number; }>, temporal?: QueryOptions) => Promise[]>; /** Count nodes matching criteria */ count: (temporal?: QueryOptions) => Promise; /** * Create a node from untyped data, relying on runtime Zod validation. * * Use this for dynamic dispatch (changesets, migrations, imports) where * the data shape is determined at runtime, not compile time. * The return type is fully typed — only the input gate is relaxed. * * `validFrom` defaults to the operation's creation timestamp when omitted — * unless a stated `validTo` at or before that instant would leave the row * readable at no coordinate, in which case the row is stored with NO lower * bound ("ended at T, start unknown") and `meta.validFrom` reads back as * `undefined`. A future `validTo` is unaffected. */ createFromRecord: (data: Record, options?: Readonly<{ id?: string; validFrom?: string | null; validTo?: string; }>) => Promise>; /** * Create or update a node. * * If a node with the given ID exists, updates it with the provided props. * Otherwise, creates a new node with that ID. * * `validFrom` applies when the upsert CREATES the row and when it RESURRECTS * a tombstoned one — both write a fresh validity window — defaulting to the * operation's timestamp when omitted. That default is dropped when a stated * `validTo` at or before the write instant would leave the row readable at no * coordinate: NO lower bound is stored ("ended at T, start unknown") and * `meta.validFrom` reads back as `undefined`. A RESURRECTION takes the same * exception, decided against the instant it samples, so one stated window * reaches ONE stored shape whether the id is fresh or names a tombstone. An * update to a LIVE row stores no lower bound, because that row's is already * history, so one naming a different instant is REFUSED (`ValidationError` * carrying * `IMMUTABLE_VALIDITY_LOWER_BOUND_CODE`) rather than ignored. Restating the * bound the row already holds is accepted and changes nothing. Set * `onImmutableLowerBound: "preserve"` for event materializers whose * `validFrom` is create/resurrection input: a live update then preserves the * stored lower bound while still applying props and `validTo`. */ upsertById: (id: string, props: z.input, options?: Readonly<{ validFrom?: string | null; onImmutableLowerBound?: "preserve" | "refuse"; }> & ValidityEndMutation) => Promise>; /** * Upsert a node from untyped data, relying on runtime Zod validation. * * Use this for dynamic dispatch (changesets, migrations, imports) where * the data shape is determined at runtime, not compile time. * The return type is fully typed — only the input gate is relaxed. * * `validFrom` applies when the upsert CREATES the row and when it RESURRECTS * a tombstoned one — both write a fresh validity window — defaulting to the * operation's timestamp when omitted. That default is dropped when a stated * `validTo` at or before the write instant would leave the row readable at no * coordinate: NO lower bound is stored ("ended at T, start unknown") and * `meta.validFrom` reads back as `undefined`. A RESURRECTION takes the same * exception, decided against the instant it samples, so one stated window * reaches ONE stored shape whether the id is fresh or names a tombstone. An * update to a LIVE row stores no lower bound, because that row's is already * history, so one naming a different instant is REFUSED (`ValidationError` * carrying * `IMMUTABLE_VALIDITY_LOWER_BOUND_CODE`) rather than ignored. Restating the * bound the row already holds is accepted and changes nothing. Set * `onImmutableLowerBound: "preserve"` for create/resurrection-only input. */ upsertByIdFromRecord: (id: string, data: Record, options?: Readonly<{ validFrom?: string | null; onImmutableLowerBound?: "preserve" | "refuse"; }> & ValidityEndMutation) => Promise>; /** * Create multiple nodes in a batch. * * More efficient than calling create() multiple times. * Use `bulkInsert` for the dedicated fast path that skips returning results. * * `validFrom` defaults to the operation's creation timestamp when omitted — * unless a stated `validTo` at or before that instant would leave the row * readable at no coordinate, in which case the row is stored with NO lower * bound ("ended at T, start unknown") and `meta.validFrom` reads back as * `undefined`. A future `validTo` is unaffected. */ bulkCreate: (items: readonly Readonly<{ props: z.input; id?: string; validFrom?: string | null; validTo?: string; }>[]) => Promise[]>; /** * Create or update multiple nodes in a batch. * * For each item, if a node with the given ID exists, updates it. * Otherwise, creates a new node with that ID. * * Items are applied in order, so a **repeated id** within one batch is * last-write-wins: the first item creates or updates the row, and every later * copy is an update over the value the earlier item wrote — the same final row * the equivalent sequence of `upsertById` calls produces. This holds whether * or not the row existed before the batch. * * `validFrom` applies when the upsert CREATES the row and when it RESURRECTS * a tombstoned one — both write a fresh validity window — defaulting to the * operation's timestamp when omitted. That default is dropped when a stated * `validTo` at or before the write instant would leave the row readable at no * coordinate: NO lower bound is stored ("ended at T, start unknown") and * `meta.validFrom` reads back as `undefined`. A RESURRECTION takes the same * exception, decided against the instant it samples, so one stated window * reaches ONE stored shape whether the id is fresh or names a tombstone. An update to a LIVE row stores no lower * bound, because that row's is already history, so one naming a different * instant is REFUSED (`ValidationError` carrying * `IMMUTABLE_VALIDITY_LOWER_BOUND_CODE`) rather than ignored. Restating the * bound the row already holds is accepted and changes nothing. Per-item * `onImmutableLowerBound: "preserve"` makes the bound create/resurrection * input while a live update preserves the stored lower bound. * * **Limitation — a batch cannot hand a unique value from one row to * another.** Item order settles which value each id ends up with, but the * writes themselves are grouped: every create runs before every update. So a * batch where one item RELEASES a `unique` constraint value and a later item * CLAIMS it — `[{ id: "a", props: { email: "moved@x" } }, { id: "b", props: * { email: "shared@x" } }]` where `a` currently holds `"shared@x"` and `b` is * new — checks `b`'s create while `a` still reserves the value, and the whole * batch fails with a `UniquenessError`. The equivalent sequence of * single `upsertById` calls succeeds. A batch states the set of rows it wants, * not a script to reach them by; the failure is loud rather than silent, and * the workaround is to split the handoff across two batches (release, then * claim) or to apply those items as sequential `upsertById` calls. */ bulkUpsertById: (items: readonly (Readonly<{ id: string; props: z.input; validFrom?: string | null; onImmutableLowerBound?: "preserve" | "refuse"; }> & ValidityEndMutation)[]) => Promise[]>; /** * Replaces complete node documents by id. * * Missing ids are created. Tombstones are resurrected with a freshly stamped * validity window. Live rows preserve their stored validity window. Unlike * `bulkUpsertById`, every `props` value is a complete replacement: omitted * optional fields are removed rather than merged from the stored document. * Duplicate ids are refused because replacement describes a set, not an * ordered script. */ bulkReplaceById: (items: readonly Readonly<{ id: string; props: z.input; }>[]) => Promise[]>; /** * Insert multiple nodes without returning results. * * This is the dedicated fast path for bulk inserts. Unlike `bulkCreate` * with `returnResults: false`, the intent is unambiguous: no results * are returned and the operation is wrapped in a transaction. * * `validFrom` defaults to the operation's creation timestamp when omitted — * unless a stated `validTo` at or before that instant would leave the row * readable at no coordinate, in which case the row is stored with NO lower * bound ("ended at T, start unknown") and `meta.validFrom` reads back as * `undefined`. A future `validTo` is unaffected. */ bulkInsert: (items: readonly Readonly<{ props: z.input; id?: string; validFrom?: string | null; validTo?: string; }>[]) => Promise; /** * Delete multiple nodes by ID. * * Atomic when the backend supports transactions. Silently ignores IDs * that don't exist. */ bulkDelete: (ids: readonly NodeId[]) => Promise; /** * Find a node by uniqueness constraint. * * Looks up a live node by the named constraint key computed from `props`. * Returns the node if found, or undefined. Soft-deleted nodes are excluded. * * @param constraintName - Name of the uniqueness constraint to match on * @param props - Properties to compute the constraint key from */ findByConstraint: (constraintName: CN, props: z.input) => Promise | undefined>; /** * Batch version of findByConstraint. * * Results are returned in the same order as the input items. * Returns undefined for entries that don't match. */ bulkFindByConstraint: (constraintName: CN, items: readonly Readonly<{ props: z.input; }>[]) => Promise<(Node | undefined)[]>; /** * Batched candidate retrieval against a declared node index. * * For each input item, TypeGraph computes the index lookup key from * `index.fields` (JSON-pointer extraction, partial-`where` applied to * stored rows, null-safe matching) and returns the live, non-soft-deleted * nodes that share that key. Unlike {@link bulkFindByConstraint}, the index * may be non-unique, so each input yields a (possibly empty) array. * * Results preserve input order; each inner array is ordered by node id. * Empty input returns `[]`. An unknown index name throws * `NodeIndexNotFoundError`; a type-incompatible indexed field throws * `ValidationError`. This is candidate retrieval, not a uniqueness or * identity guarantee. * * @param indexName - Name of the declared node index to match on * @param items - Records whose `props` supply the indexed-field values * @param options - Optional `limitPerInput` to bound per-input fan-out */ bulkFindByIndex: (indexName: string, items: readonly Readonly<{ props: Partial>; }>[], options?: NodeBulkFindByIndexOptions) => Promise[][]>; /** * Get an existing node by uniqueness constraint, or create a new one. * * Looks up a node by the named constraint key computed from `props`. * If found, returns it (optionally updating with `ifExists: "update"`). * If not found, creates a new node. Soft-deleted matches are always resurrected. * * @param constraintName - Name of the uniqueness constraint to match on * @param props - Full properties for create, or merge source for update * @param options - Existing record behavior (default: "return") */ getOrCreateByConstraint: (constraintName: CN, props: z.input, options?: NodeGetOrCreateByConstraintOptions) => Promise>; /** * Batch version of getOrCreateByConstraint. * * Results are returned in the same order as the input items. * Atomic when the backend supports transactions. */ bulkGetOrCreateByConstraint: (constraintName: CN, items: readonly Readonly<{ props: z.input; }>[], options?: NodeGetOrCreateByConstraintOptions) => Promise[]>; }>; /** * Reference to a node of a specific kind. * * Accepts either: * - A Node instance of the correct kind * - An explicit { kind, id } object with the correct kind name * * This provides compile-time checking that edge endpoints match the * allowed node kinds defined in the edge registration. */ type NodeRef = Node | Readonly<{ kind: N["kind"]; id: string; }>; /** * Options for creating an edge. */ type EdgeCreateOptions = Readonly<{ id?: string; validFrom?: string | null; validTo?: string; }>; /** * Arguments for edge creation, with props optional when schema allows empty object. * * Uses `{}` to check if an empty object literal satisfies the schema input type. */ type EdgeCreateArguments = {} extends z.input ? [ props?: z.input, options?: EdgeCreateOptions ] : [props: z.input, options?: EdgeCreateOptions]; /** * A collection of edges of a specific type. * * Provides ergonomic CRUD operations for a single edge type. * The From and To type parameters enforce that edge endpoints * match the allowed node types at compile time. * * @example * ```typescript * // Create an edge - pass Node objects directly * const edge = await store.edges.worksAt.create(alice, acme, { role: "Engineer" }); * * // TypeScript error: Company is not a valid 'from' type for worksAt * // store.edges.worksAt.create(acme, alice, { role: "Engineer" }); * * // For edges with empty schemas, props is optional * await store.edges.wrote.create(author, book); * * // Find edges from a node * const edges = await store.edges.worksAt.findFrom(alice); * ``` */ type EdgeEndpointPairTypes = Readonly<{ from: NodeType; to: NodeType; }>; type EdgeBulkCreateItem> = Pairs extends EdgeEndpointPairTypes ? Readonly<{ from: NodeRef; to: NodeRef; props?: z.input; id?: string; validFrom?: string | null; validTo?: string; }> : never; type EdgeBulkUpsertItem> = Pairs extends EdgeEndpointPairTypes ? Readonly<{ id: Id; from: NodeRef; to: NodeRef; props?: z.input; validFrom?: string | null; }> & ValidityEndMutation : never; type EdgeBulkInsertItem> = Pairs extends EdgeEndpointPairTypes ? Readonly<{ from: NodeRef; to: NodeRef; props?: z.input; id?: string; validFrom?: string | null; validTo?: string; }> : never; type EdgeBulkGetOrCreateItem = Pairs extends EdgeEndpointPairTypes ? Readonly<{ from: NodeRef; to: NodeRef; props: z.input; validFrom?: string | null; onImmutableLowerBound?: "preserve" | "refuse"; }> & ValidityEndMutation : never; type EdgeCreateArgumentsTuple = Pairs extends EdgeEndpointPairTypes ? [ from: NodeRef, to: NodeRef, ...args: EdgeCreateArguments ] : never; type EdgeFindByEndpointsArguments = Pairs extends EdgeEndpointPairTypes ? [ from: NodeRef, to: NodeRef, options?: EdgeFindByEndpointsOptions, temporal?: QueryOptions ] : never; type EdgeGetOrCreateByEndpointsArguments = Pairs extends EdgeEndpointPairTypes ? [ from: NodeRef, to: NodeRef, props: z.input, options?: EdgeGetOrCreateByEndpointsOptions ] : never; /** * Typed edge operations. InputId controls accepted ID arguments only; returned * edges retain their schema-derived ID brand. Prefer DynamicEdgeCollection * for runtime endpoint dispatch instead of spelling its parameters manually. */ type EdgeCollection> = Readonly<{ /** * Create a new edge. * * `validFrom` defaults to the operation's creation timestamp when omitted — * unless a stated `validTo` at or before that instant would leave the row * readable at no coordinate, in which case the row is stored with NO lower * bound ("ended at T, start unknown") and `meta.validFrom` reads back as * `undefined`. A future `validTo` is unaffected. * * @param from - Source node (must be one of the allowed 'from' types) * @param to - Target node (must be one of the allowed 'to' types) * @param args - Edge properties (optional if schema is empty) and creation options */ create: (...args: EdgeCreateArgumentsTuple) => Promise>; /** Get an edge by ID */ getById: (id: InputId, options?: QueryOptions) => Promise | undefined>; /** Get multiple edges by ID, preserving input order (undefined for missing) */ getByIds: (ids: readonly InputId[], options?: QueryOptions) => Promise | undefined)[]>; /** * Update an edge's properties and optionally set or clear its validity end. * Reopening a `oneActive` edge rechecks cardinality before the write. */ update: (id: InputId, props: Partial>, options?: ValidityEndMutation) => Promise>; /** * Find edges from a specific node. * * Honors the same temporal model as `getById` / `find`: with no * `options`, the graph's default `temporalMode` applies (excluding * soft-deleted edges and, in `current` / `asOf` modes, edges outside * their validity window). Pass `temporalMode` / `asOf` to read the * endpoint's edges at another temporal coordinate. */ findFrom: (from: NodeRef, options?: QueryOptions) => Promise[]>; /** * Find edges to a specific node. * * Temporal semantics mirror {@link EdgeCollection.findFrom}. */ findTo: (to: NodeRef, options?: QueryOptions) => Promise[]>; /** * Find the edges from a SET of source nodes in one read. * * `findFrom` with a widened endpoint predicate and nothing else: the same * temporal model, the same soft-delete filtering, and the same per-source * ordering — but `from_id IN (...)` instead of `from_id = ?`, so a page of * N sources costs one statement per distinct source kind and bind-budget * chunk rather than N singleton statements. * * Results are grouped per input: the outer array is parallel to `froms` * (index `i` holds the edges of `froms[i]`), and repeated inputs each get * their own copy of the same edge set. Empty input returns `[]`; a source * with no edges gets an empty array. Large inputs are split across * statements to respect the backend's bound-parameter budget, which is * invisible in the result. * * Requires a backend implementing `findEdgesByEndpointSet` — both bundled * Drizzle backends do. On one that does not, this **refuses** with a * `ConfigurationError` rather than looping `findFrom` per input: asking for * a bulk read is asking for set-oriented statements, and silently issuing N * singleton statements is the cost surprise the method exists to remove. * * @param froms - Source nodes to read the edges of * @param options - Temporal coordinate plus optional `limitPerInput` * @throws {ConfigurationError} when the backend cannot read an endpoint set */ bulkFindFrom: (froms: readonly NodeRef[], options?: EdgeBulkFindEndpointOptions) => Promise[][]>; /** * Find the edges into a SET of target nodes in one read. * * Mirrors {@link EdgeCollection.bulkFindFrom} on the `to` endpoint. */ bulkFindTo: (tos: readonly NodeRef[], options?: EdgeBulkFindEndpointOptions) => Promise[][]>; /** * Deferred variant of `findFrom` for use with `store.batch()`. * * Returns a `BatchableQuery` instead of executing immediately. Accepts * the same temporal `options` as {@link EdgeCollection.findFrom}. * * Batching these still issues one statement per call — it does not merge * the reads, and whether they share a connection is up to the adapter. It * is not a snapshot: PostgreSQL's default read-committed isolation lets a * later read observe a commit the earlier ones did not. To read edges for * many sources in one statement, traverse from them in a single query. */ batchFindFrom: (from: NodeRef, options?: QueryOptions) => BatchableQuery>; /** * Deferred variant of `findTo` for use with `store.batch()`. * * Returns a `BatchableQuery` instead of executing immediately. Accepts * the same temporal `options` as {@link EdgeCollection.findTo}. Costs one * statement per call, like {@link EdgeCollection.batchFindFrom}. */ batchFindTo: (to: NodeRef, options?: QueryOptions) => BatchableQuery>; /** * Deferred variant of `findByEndpoints` for use with `store.batch()`. * * Returns a `BatchableQuery` that yields a 0-or-1 element array * (matching `findByEndpoints`' at-most-one semantics). Costs one statement * per call, like {@link EdgeCollection.batchFindFrom}. */ batchFindByEndpoints: (...args: EdgeFindByEndpointsArguments) => BatchableQuery>; /** Delete an edge (soft delete - sets deletedAt timestamp) */ delete: (id: InputId) => Promise; /** * Permanently delete an edge from the database. * * Unlike `delete()` which performs a soft delete, this permanently * removes the edge record. * * **Warning:** This operation is irreversible and should be used carefully. * Consider using soft delete (`delete()`) for most use cases. */ hardDelete: (id: InputId) => Promise; /** Find edges matching endpoint and pagination criteria */ find: (filter?: Readonly<{ from?: NodeRef; to?: NodeRef; limit?: number; offset?: number; }>, temporal?: QueryOptions) => Promise[]>; /** Count edges matching criteria */ count: (filter?: Readonly<{ from?: NodeRef; to?: NodeRef; }>, temporal?: QueryOptions) => Promise; /** * Create multiple edges in a batch. * * More efficient than calling create() multiple times. * Use `bulkInsert` for the dedicated fast path that skips returning results. * * `validFrom` defaults to the operation's creation timestamp when omitted — * unless a stated `validTo` at or before that instant would leave the row * readable at no coordinate, in which case the row is stored with NO lower * bound ("ended at T, start unknown") and `meta.validFrom` reads back as * `undefined`. A future `validTo` is unaffected. */ bulkCreate: (items: readonly EdgeBulkCreateItem[]) => Promise[]>; /** * Create or update multiple edges in a batch. * * For each item, if an edge with the given ID exists, updates it. * Otherwise, creates a new edge with that ID. * * Items are applied in order, so a **repeated id** within one batch is * last-write-wins: the first item creates or updates the edge, and every later * copy is an update over the value the earlier item wrote. This holds whether * or not the edge existed before the batch. An update never repoints an edge, * so every item's `from` / `to` must exactly restate the endpoints already * owned by that id (including an earlier item in the same batch). A mismatch * is refused with `ValidationError` carrying * `EDGE_IDENTITY_MISMATCH_CODE`; it is never silently ignored. * * `validFrom` applies when the upsert CREATES the edge and when it RESURRECTS * a tombstoned one. A create writes a fresh validity window, defaulting to the * operation's timestamp when omitted — unless a stated `validTo` at or before * that instant would leave the row readable at no coordinate, in which case NO * lower bound is stored ("ended at T, start unknown") and `meta.validFrom` * reads back as `undefined`. A resurrection stamps nothing: an edge RETAINS * its stored lower bound unless the item names a new one, so a `validTo` * before the retained bound is REFUSED rather than stamped over. An update to * a LIVE row stores no lower bound, because that row's is already history, so * one naming a different instant is REFUSED (`ValidationError` carrying * `IMMUTABLE_VALIDITY_LOWER_BOUND_CODE`) rather than ignored. Restating the * bound the row already holds is accepted and changes nothing. * * **Limitation — a batch cannot hand a constrained slot from one edge to * another.** Item order settles the final props, but the writes are grouped: * every create runs before every update. So a batch where one item frees the * slot a `cardinality` constraint allows and a later item claims it — ending * the lone `oneActive` edge from a source while creating its replacement — * checks the create while the old edge is still active, and the whole batch * fails with a `CardinalityError`. Applying the update first, as * separate `update` / `create` calls, succeeds. A batch states the set of * edges it wants, not a script to reach them by; the failure is loud rather * than silent, and the workaround is to split the handoff across two batches * (free the slot, then claim it) or to apply those items individually. */ bulkUpsertById: (items: readonly EdgeBulkUpsertItem[]) => Promise[]>; /** * Insert multiple edges without returning results. * * This is the dedicated fast path for bulk inserts. Unlike `bulkCreate` * with `returnResults: false`, the intent is unambiguous: no results * are returned and the operation is wrapped in a transaction. * * `validFrom` defaults to the operation's creation timestamp when omitted — * unless a stated `validTo` at or before that instant would leave the row * readable at no coordinate, in which case the row is stored with NO lower * bound ("ended at T, start unknown") and `meta.validFrom` reads back as * `undefined`. A future `validTo` is unaffected. */ bulkInsert: (items: readonly EdgeBulkInsertItem[]) => Promise; /** * Delete multiple edges by ID. * * Atomic when the backend supports transactions. Silently ignores IDs * that don't exist. * * The batch runs as one transaction, checking each id's kind against the * authoritative row as it goes. An id owned by another edge kind is * refused with `ValidationError` carrying `EDGE_IDENTITY_MISMATCH_CODE`; * because the whole batch is one transaction, that refusal rolls back * every delete already applied for an earlier id in the same batch. */ bulkDelete: (ids: readonly InputId[]) => Promise; /** * Find an edge by endpoints and optional property fields. * * Returns the first matching edge at the read coordinate, or undefined. * Honors the temporal model like `findFrom` / `findTo`: by default * (`current` mode) soft-deleted and out-of-window edges are excluded; under * `includeTombstones` a soft-deleted edge can be returned. * * @param from - Source node * @param to - Target node * @param options - Match criteria (matchOn fields and property values) * @param temporal - Temporal coordinate. With no `temporal`, the graph's * default `temporalMode` applies (so under the default `"current"` mode, * edges outside their validity window are excluded). Pass * `temporalMode` / `asOf` to read the edge as of another coordinate. */ findByEndpoints: (...args: EdgeFindByEndpointsArguments) => Promise | undefined>; /** * Get an existing edge by endpoints and optional property fields, or create a new one. * * Matches edges of this kind between `(from, to)`. When `matchOn` specifies * property fields, only edges whose properties match on those fields are considered. * Soft-deleted matches are resurrected when cardinality allows. * * `validFrom` applies on the create and RESURRECT branches. A create defaults * it to the operation's creation timestamp when omitted — unless a stated * `validTo` at or before that instant would leave the row readable at no * coordinate, in which case no lower bound is stored ("ended at T, start * unknown"). A resurrection stamps nothing: an edge RETAINS its stored lower * bound unless the call names a new one, so a `validTo` before the retained * bound is REFUSED rather than stamped over. On the `ifExists: "update"` * branch a live edge's lower bound is history and cannot be stored, so a * `validFrom` naming a different instant is REFUSED (`ValidationError` * carrying `IMMUTABLE_VALIDITY_LOWER_BOUND_CODE`); restating the bound the * edge holds is accepted. A returned existing edge (`ifExists: "return"`) * writes nothing and judges nothing — the window options describe the row to * create if none is found. `validTo` applies on create, update, and * resurrection, but not when an existing edge is returned without a write. * * @param from - Source node * @param to - Target node * @param props - Full properties for create, or merge source for update * @param options - Match criteria and conflict resolution */ getOrCreateByEndpoints: (...args: EdgeGetOrCreateByEndpointsArguments) => Promise>; /** * Batch version of getOrCreateByEndpoints. * * Results are returned in the same order as the input items. * Atomic when the backend supports transactions. */ bulkGetOrCreateByEndpoints: (items: readonly EdgeBulkGetOrCreateItem[], options?: Pick, "matchOn" | "ifExists">) => Promise[]>; }>; /** * Extract uniqueness constraint names from a NodeRegistration. * * - Returns `never` when no uniqueness constraints are configured. * - Returns a literal union when names are inferred via `defineGraph` const params. * - Falls back to `string` when names are widened/dynamic. */ type ConstraintNames = "unique" extends keyof R ? R["unique"] extends readonly { readonly name: infer N; }[] ? N & string : string : never; /** * Extract the union of 'from' node types from an EdgeRegistration. */ type EdgeFromTypes = R["from"] extends readonly (infer N)[] ? N : never; type ExtractTargets = T extends readonly (infer N extends NodeType)[] ? N : T extends Record ? N : never; /** * Extract the union of 'to' node types from an EdgeRegistration. */ type EdgeToTypes = ExtractTargets; type ExtractAllowedPairs = To extends readonly (infer ToNode extends NodeType)[] ? { from: From extends readonly (infer FromNode extends NodeType)[] ? FromNode : NodeType; to: ToNode; } : To extends Record ? { [K in keyof To & string]: { from: Extract; to: To[K] extends readonly (infer ToNode extends NodeType)[] ? ToNode : NodeType; }; }[keyof To & string] : { from: NodeType; to: NodeType; }; /** * Extract the allowed endpoint pairs from an EdgeRegistration. */ type EdgeAllowedPairs = ExtractAllowedPairs; /** * Create a type-safe EdgeCollection from an EdgeRegistration. * Extracts the edge type and from/to node types automatically. */ type TypedEdgeCollection = EdgeCollection extends NodeType ? EdgeFromTypes : NodeType, EdgeToTypes extends NodeType ? EdgeToTypes : NodeType, EdgeAllowedPairs>; /** Mapped type of all node collections for a graph. */ type GraphNodeCollections = { [K in keyof G["nodes"] & string]-?: NodeCollection>; }; /** Mapped type of all edge collections for a graph. */ type GraphEdgeCollections = { [K in keyof G["edges"] & string]-?: TypedEdgeCollection; }; /** Temporal-aware node reads — a {@link StoreView} pins these. */ type NodeTemporalReads = Pick, (typeof NODE_TEMPORAL_READ_NAMES)[number]>; /** * Current-state-only node reads (constraint / index lookups). No temporal * axis, so a {@link StoreView} delegates them on a `current` pin and refuses * them on a temporal pin. */ type NodeCurrentReads = Pick, (typeof CURRENT_ONLY_READ_NAMES)[number]>; /** Node writes — never available on a read-only {@link StoreView}. */ type NodeWrites = Pick, (typeof NODE_WRITE_NAMES)[number]>; /** Temporal-aware edge reads — a {@link StoreView} pins these. */ type EdgeTemporalReads = Pick, (typeof EDGE_TEMPORAL_READ_NAMES)[number]>; /** * Deferred edge reads for `store.batch()` — absent from a {@link StoreView}, * which has no batch context. */ type EdgeBatchReads = Pick, (typeof EDGE_BATCH_READ_NAMES)[number]>; /** Edge writes — never available on a read-only {@link StoreView}. */ type EdgeWrites = Pick, (typeof EDGE_WRITE_NAMES)[number]>; /** * The read-only node surface a {@link StoreView} exposes for one node kind. * The temporal reads drop the per-call temporal argument — the pin owns the * axis; the current reads ({@link NodeCurrentReads}) are exposed as-is * (delegated on a `current` view, refused on a temporal pin). Writes live on * the live `Store`. The conformance test asserts the temporal part equals the * pinned form of {@link NodeTemporalReads}. */ type StoreViewNodeCollection = Readonly<{ /** Get a node by ID at the view's pinned coordinate. */ getById: (id: NodeId) => Promise | undefined>; /** Get multiple nodes by ID, preserving input order (undefined for missing). */ getByIds: (ids: readonly NodeId[]) => Promise | undefined)[]>; /** Find nodes matching criteria at the view's pinned coordinate. */ find: (filter?: Readonly<{ where?: (accessor: NodeAccessor) => Predicate; limit?: number; offset?: number; }>) => Promise[]>; /** Count nodes at the view's pinned coordinate. */ count: () => Promise; }> & NodeCurrentReads; /** * The read-only edge surface a {@link StoreView} exposes for one edge * kind, mirroring {@link StoreViewNodeCollection}. Every edge read — * including `findByEndpoints` — honors the pin via the same temporal model, * so (unlike nodes) there are no current-state-only edge reads. */ type StoreViewEdgeCollection = Readonly<{ /** Get an edge by ID at the view's pinned coordinate. */ getById: (id: EdgeId) => Promise | undefined>; /** Get multiple edges by ID, preserving input order (undefined for missing). */ getByIds: (ids: readonly EdgeId[]) => Promise | undefined)[]>; /** Find edges matching endpoint and pagination criteria. */ find: (filter?: Readonly<{ from?: NodeRef; to?: NodeRef; limit?: number; offset?: number; }>) => Promise[]>; /** Count edges matching criteria at the view's pinned coordinate. */ count: (filter?: Readonly<{ from?: NodeRef; to?: NodeRef; }>) => Promise; /** Find edges from a specific node at the view's pinned coordinate. */ findFrom: (from: NodeRef) => Promise[]>; /** Find edges to a specific node at the view's pinned coordinate. */ findTo: (to: NodeRef) => Promise[]>; /** Find the edges from a set of source nodes at the view's pinned coordinate. */ bulkFindFrom: (froms: readonly NodeRef[], options?: EdgeBulkFindOptions) => Promise[][]>; /** Find the edges into a set of target nodes at the view's pinned coordinate. */ bulkFindTo: (tos: readonly NodeRef[], options?: EdgeBulkFindOptions) => Promise[][]>; /** Find the edge between two endpoints at the view's pinned coordinate. */ findByEndpoints: (...args: Pairs extends EdgeEndpointPairTypes ? [ from: NodeRef, to: NodeRef, options?: EdgeFindByEndpointsOptions ] : never) => Promise | undefined>; }>; /** * Read-only view edge collection derived from an `EdgeRegistration`, * extracting the edge type and from/to node types — the read-only * counterpart of {@link TypedEdgeCollection}. */ type TypedStoreViewEdgeCollection = StoreViewEdgeCollection extends NodeType ? EdgeFromTypes : NodeType, EdgeToTypes extends NodeType ? EdgeToTypes : NodeType, EdgeAllowedPairs>; /** Mapped type of all read-only view node collections for a graph. */ type StoreViewNodeCollections = { [K in keyof G["nodes"] & string]-?: StoreViewNodeCollection; }; /** Mapped type of all read-only view edge collections for a graph. */ type StoreViewEdgeCollections = { [K in keyof G["edges"] & string]-?: TypedStoreViewEdgeCollection; }; /** Options for one bounded, forward-only recorded-time collection scan. */ type RecordedScanOptions = Readonly<{ /** Maximum entities to return. Defaults to 1,000 and cannot exceed 1,000. */ limit?: number; /** Opaque cursor returned by the preceding page. */ after?: string; }>; /** One page from a deterministic recorded-time collection scan. */ type RecordedScanPage = Readonly<{ /** Entities ordered by canonical id ascending. */ data: readonly T[]; /** Cursor for the next page, or `undefined` when the scan is complete. */ nextCursor: string | undefined; /** Whether another page exists after this one. */ hasNextPage: boolean; }>; /** Recorded-time reconstructing reads for one node kind. */ type RecordedStoreViewNodeCollection = Pick, (typeof RECORDED_POINT_READ_NAMES)[number]> & Readonly<{ /** Scan one bounded page at the view's pinned coordinate. */ scan: (options?: RecordedScanOptions) => Promise>>; }>; /** Recorded-time reconstructing reads for one edge kind. */ type RecordedStoreViewEdgeCollection = Pick, (typeof RECORDED_POINT_READ_NAMES)[number]> & Readonly<{ /** Scan one bounded page at the view's pinned coordinate. */ scan: (options?: RecordedScanOptions) => Promise>>; }>; /** Recorded-time edge collection derived from an `EdgeRegistration`. */ type TypedRecordedStoreViewEdgeCollection = RecordedStoreViewEdgeCollection extends NodeType ? EdgeFromTypes : NodeType, EdgeToTypes extends NodeType ? EdgeToTypes : NodeType, EdgeAllowedPairs>; /** Mapped type of all recorded-time node reconstructing-read collections. */ type RecordedStoreViewNodeCollections = { [K in keyof G["nodes"] & string]-?: RecordedStoreViewNodeCollection; }; /** Mapped type of all recorded-time edge reconstructing-read collections. */ type RecordedStoreViewEdgeCollections = { [K in keyof G["edges"] & string]-?: TypedRecordedStoreViewEdgeCollection; }; /** * Whether — and why — `tx.sql` can be used on a transaction context. A single * required discriminant covering exactly the four states raw-SQL access can be * in, so an adapter caller branches on capability instead of truthiness-testing * `tx.sql`. The non-available variants omit `sql` entirely, so even reading the * handle requires first narrowing `sqlAvailability` to `"available"`. The * runtime object keeps a fail-loud getter under history / revision tracking for * JavaScript and type-suppressed callers. See * {@link AdapterTransactionContext} for the per-value semantics. */ type SqlAvailability = "available" | "history" | "revisionTracking" | "unavailable"; type AdapterTransactionSqlAccess = Readonly<{ sql: TNativeTransaction; sqlAvailability: "available"; }> | Readonly<{ sqlAvailability: "history" | "revisionTracking"; }> | Readonly<{ sqlAvailability: "unavailable"; }>; declare const TRANSACTION_RUNTIME: unique symbol; type TransactionRuntime = Readonly<{ backend: TransactionBackend; runNodeOperationHooks: (operation: "create" | "update" | "delete", kind: string, id: string, fn: () => Promise) => Promise; }>; /** * The portable transaction context mirrors the Store's typed graph operations * while exposing only a read-only backend projection bound to the transaction. * Adapter-native handles are available only through * {@link AdapterTransactionContext}; arbitrary backend writes are absent from * every public transaction context. TypeGraph's non-enumerable symbol port is * an unsupported implementation detail, not a JavaScript security boundary: * reflective code can still discover symbol properties. * * @example * ```typescript * await store.transaction(async (tx) => { * const person = await tx.nodes.Person.create({ name: "Alice" }); * const company = await tx.nodes.Company.create({ name: "Acme" }); * await tx.edges.worksAt.create(person, company, { role: "Engineer" }); * }); * ``` */ type TransactionCollections = Readonly<{ [TRANSACTION_RUNTIME]: TransactionRuntime; nodes: GraphNodeCollections; edges: GraphEdgeCollections; /** Runtime endpoint validation with the selected edge's property schema retained. */ getEdgeCollection: EdgeCollectionLookup; /** Like getEdgeCollection, throwing KindNotFoundError when absent. */ getEdgeCollectionOrThrow: RequiredEdgeCollectionLookup; /** Read-only backend projection bound to the same graph transaction. */ backend: TransactionReadBackend; /** * Runtime string-keyed node collection access, mirroring * `Store.getNodeCollection`. Returns `undefined` when `kind` is not * registered in this graph. */ getNodeCollection: (kind: K) => DynamicNodeCollection | undefined; }> & (G["identity"] extends GraphIdentityConfig ? Readonly<{ identity: IdentityFacade; }> : Readonly>); /** * A portable transaction context containing TypeGraph-owned collections and * transaction-bound graph reads. Managed Stores use this surface so * adapter-native handles never enter their public contract. */ type TransactionContext = TransactionCollections & Readonly<{ query: () => InitialQueryBuilder; /** Describes current population through this transaction's pinned session. */ describe: () => Promise; /** Validates current records through this transaction's pinned session. */ validateStore: (options: ValidateStoreOptions) => Promise; batchOnce: (build: (read: BatchReadBuilder) => Queries, options?: BatchOnceOptions) => Promise>; neighbors: >(source: GraphNodeReference, options: NeighborReadOptions) => Promise[]>; countNeighbors: >(source: GraphNodeReference, options: Omit, "limit" | "orderBy">) => Promise; subgraph: , const NK extends NodeKinds = NodeKinds, const P extends SubgraphProject | undefined = undefined>(rootId: NodeId>, options: SubgraphOptions) => Promise>; }>; /** * Requests a recorded revision even when a history transaction makes no entity * changes. Repeated requests are idempotent. Receipt-enabled transactions * expose the allocated instant on their terminal {@link TransactionReceipt}, * after capture flushes. */ type RecordedRevisionRequest = Readonly<{ requestRecordedRevision: () => void; }>; /** The narrow, one-statement write envelope available only to recorded transactions. */ type RecordedHeterogeneousNodeWriteBatch = Readonly<{ writeNodeUpsertBatch: []>(entries: Entries) => Promise>; }>; /** A portable transaction context bound to a history-enabled Store. */ type HistoryTransactionContext = TransactionContext & RecordedRevisionRequest & RecordedHeterogeneousNodeWriteBatch; /** * A transaction context exposed by an {@link AdapterStore}. In addition to the * portable graph collections, it carries the adapter-native handle when that * capability is available. The TypeGraph backend remains the same runtime * read projection as the portable context; adapter-native writes intentionally * go through `sql`, making that escape hatch explicit. */ type AdapterTransactionContext = TransactionContext & AdapterTransactionSqlAccess; /** * Scoped write measurement, available only on the receipt-enabled transaction * contexts (`transactionWithReceipt`, `withRecordedTransaction`). Runs `fn`, * passing it a **scoped context** — a second view over the same transaction — and * returns a {@link TransactionOutcome} whose receipt counts exactly the writes * made *through that scoped context* (`scoped.nodes` / `scoped.edges`). * * Attribution is by **which context you write through**, not by timing. A write * through the scoped context counts in both the scope and the outer receipt (it * happened in the transaction); a write through the outer `tx` during the scope * counts only in the outer receipt. This makes overlapping and concurrent * measures safe by construction — two scopes running under `Promise.all`, each * writing through its own scoped context, never cross-count. Nesting composes: * `scoped.measure(...)` opens a child scope that counts in itself, every * ancestor scope, and the outer receipt. * * Counts otherwise inherit {@link TransactionReceipt} (bulk by input length, a * rejected write counts 0). The returned receipt's `recorded` is **always * `undefined`**: the recorded commit instant is a per-transaction flush concern, * unknowable mid-transaction. */ type ScopedMeasure = (fn: (scoped: Context) => Promise) => Promise>; /** * A {@link TransactionContext} that also exposes {@link ScopedMeasure}. Only the * receipt-enabled entry points (`transactionWithReceipt`, * `withRecordedTransaction`) hand a callback this type; plain `transaction()` * contexts have no recorder and therefore no `measure`, keeping that path * zero-overhead. Assignable to {@link TransactionContext}, so a projector helper * typed `(tx: TransactionContext) => ...` accepts a measurable context. The * scoped context handed to `measure` is itself measurable, so scopes nest. */ type MeasurableTransactionContext = TransactionContext & Readonly<{ measure: ScopedMeasure>; }>; /** Receipt-enabled transaction context for a history-enabled Store. */ type MeasurableHistoryTransactionContext = HistoryTransactionContext & Readonly<{ measure: ScopedMeasure>; }>; /** Receipt-enabled transaction context for an {@link AdapterStore}. */ type MeasurableAdapterTransactionContext = AdapterTransactionContext & Readonly<{ measure: ScopedMeasure>; }>; /** * A node returned by a runtime string-keyed collection. * * Its nominal node-type brand proves the value passed through the dynamic * collection API while its properties remain runtime-schema-shaped. */ type DynamicNode = Node>; declare const DYNAMIC_NODE_REFERENCE_BRAND: unique symbol; /** A nominal lightweight reference returned by runtime-aware identity reads. */ type DynamicNodeReference = Readonly<{ kind: DynamicNodeKind; id: NodeId>; [DYNAMIC_NODE_REFERENCE_BRAND]: true; }>; /** * Replace branded `NodeId` / `EdgeId` with plain `string` in each * method's parameter list. Return types are preserved unchanged. * * Handles three shapes: * 1. Direct branded ID parameter → `string` * 2. `readonly NodeId[]` / `readonly EdgeId[]` → `readonly string[]` * 3. Branded IDs nested one level inside bulk-item object arrays */ type WidenBrandedIds = { readonly [K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: { [P in keyof A]: UnbrandParam; }) => R : T[K]; }; /** Replace a branded ID with `string`, recursing one level into arrays. */ type UnbrandParam = T extends NodeId ? string : T extends EdgeId ? string : T extends readonly NodeId[] ? readonly string[] : T extends readonly EdgeId[] ? readonly string[] : T extends readonly (infer Item extends Record)[] ? readonly UnbrandRecord[] : T; /** Replace branded ID values in object properties (does not recurse into nested structures). */ type UnbrandRecord> = { readonly [K in keyof T]: T[K] extends NodeId ? string : T[K] extends EdgeId ? string : T[K]; }; /** * A node collection with widened generics for runtime string-keyed access. * * This is the return type of `store.getNodeCollection(kind)`. It exposes * the full `NodeCollection` API but with a nominal `DynamicNodeType` and * `string` constraint names instead of the specific generic parameters, since * the concrete type is not known at compile time. * * ID parameters accept plain `string` instead of branded `NodeId`, since * the dynamic path typically receives IDs from edge metadata, snapshots, * or external input where the brand is not available. */ type DynamicNodeCollection = WidenBrandedIds, string>>; /** * An edge collection with widened generics for runtime string-keyed access. * * This is the return type of `store.getEdgeCollection(kind)`. It exposes * the full `EdgeCollection` API but with `NodeType` endpoint types, since * the concrete from/to types are not known at compile time. * * ID parameters accept plain `string` instead of branded `EdgeId`, since * the dynamic path typically receives IDs from edge metadata, snapshots, * or external input where the brand is not available. */ type DynamicEdgeCollection = Omit, "create"> & Readonly<{ /** Create with runtime-validated endpoints and schema-typed properties. */ create: (from: NodeRef, to: NodeRef, ...args: EdgeCreateArguments) => Promise>; }>; /** Runtime endpoint reads at a view's pinned coordinate; no writes or temporal overrides. */ type DynamicStoreViewEdgeCollection = Omit, "getById" | "getByIds"> & Readonly<{ getById: (id: string) => Promise | undefined>; getByIds: (ids: readonly string[]) => Promise | undefined)[]>; }>; /** A node collection narrowed by Store-issued runtime-kind evidence. */ type RuntimeNodeCollection = WidenBrandedIds, string>>; /** An edge collection narrowed by Store-issued runtime-kind evidence. */ type RuntimeEdgeCollection = DynamicEdgeCollection>; /** One kind-grouped source input for a token-validated heterogeneous read. */ type RuntimeBulkEdgeSourceGroup = T extends RuntimeNodeKind ? Readonly<{ kind: T; ids: readonly string[]; }> : never; /** A source reference narrowed to the node token that licensed it. */ type RuntimeNodeReferenceFor = T extends RuntimeNodeKind ? Readonly<{ kind: T["kind"]; id: NodeId>; }> : never; /** An edge value narrowed to the edge token that licensed its read. */ type RuntimeEdgeFor = T extends RuntimeEdgeKind ? Edge, NodeType, NodeType> : never; /** Input for {@link Store.bulkFindRuntimeEdgesFrom}. */ type BulkFindRuntimeEdgesFromParams = Readonly<{ sources: readonly RuntimeBulkEdgeSourceGroup[]; edgeKinds: readonly ET[]; }>; /** One source bucket returned by {@link Store.bulkFindRuntimeEdgesFrom}. */ type BulkFindRuntimeEdgesFromResult = Readonly<{ source: RuntimeNodeReferenceFor; edges: readonly RuntimeEdgeFor[]; }>; /** * A type-level projection of a store's surface onto a subset of its * node and edge collections. * * Node collections are projected with constraint names erased (`never`), * so constraint-based methods like `findByConstraint` become uncallable. * This is intentional: unique constraints are graph-registration-level * details that differ between graphs sharing the same node types. * * @example * ```typescript * type CoreStore = StoreProjection< * typeof myGraph, * "Document" | "Chunk", * "hasChunk" * >; * * async function ingestChunk( * store: CoreStore, * document: Node, * text: string, * ) { * const chunk = await store.nodes.Chunk.create({ text }); * await store.edges.hasChunk.create(document, chunk); * return chunk; * } * ``` * * Both `Store` and `TransactionContext` are structurally assignable * to a `StoreProjection` whose keys are a subset of `G`. */ type StoreProjection = Readonly<{ nodes: { [K in N]-?: NodeCollection; }; edges: Pick, E>; }>; /** * The accepted *input* form for every identity facade method: a whole node or * a `{ kind, id }` pair for any compile-time graph kind, plus a proof-bearing * node or reference produced through the runtime collection lane. Runtime * values stay nominal so accepting them does not make an arbitrary * `{ kind: string, id: string }` object type-safe. * * Identity results use {@link IdentityNodeReference}, which honestly includes * runtime kinds because an evolved kind can belong to a class reached from a * compile-time node. */ type IdentityNodeRefInput = NodeRef> | DynamicNode | DynamicNodeReference; /** * A node reference returned by Operational Identity. * * Identity classes can contain runtime-evolved kinds even when a read starts * from a compile-time node, so results honestly include both lanes. */ type IdentityNodeReference = GraphNodeReference | DynamicNodeReference; /** A hydrated compile-time or runtime identity member. */ type IdentityNode = { [K in NodeKinds]: Node; }[NodeKinds] | DynamicNode; declare const __identityAssertionId: unique symbol; type IdentityAssertionId = string & Readonly<{ [__identityAssertionId]: true; }>; /** * Brands a non-empty string as an {@link IdentityAssertionId}. * * Use this when a persisted identity assertion id has round-tripped through * untyped storage or an external boundary and must be passed back to a * retraction surface such as `retractAssertion` or `bulkRetractAssertions`. * Mirrors the `asNodeId` / `asEdgeId` precedent. * * @throws {ValidationError} when `value` is empty. */ declare function asIdentityAssertionId(value: string): IdentityAssertionId; /** * What one assertion claims about a pair of nodes: `"same"` merges them into * one equivalence set, `"different"` records a disjointness that a later * `"same"` claim must not contradict. */ type IdentityRelation = "same" | "different"; /** * One persisted identity claim about an ordered pair of nodes. Returned by the * assertion writers and by `identity.assertionsOf(...)`; `validTo` is set when * the assertion has been retracted, so a historical read can still see it. */ type IdentityAssertion = Readonly<{ id: IdentityAssertionId; relation: IdentityRelation; a: IdentityNodeReference; b: IdentityNodeReference; validFrom: string; validTo?: string; }>; /** Result of an idempotent assertion write. */ type IdentityAssertionResult = Readonly<{ assertion: IdentityAssertion; action: "created" | "existing"; }>; /** The half-open effective-time window of an identity assertion. */ type IdentityValidityWindow = Readonly<{ validFrom?: string; validTo?: string; }>; /** One ordered node pair handed to `bulkAssertSame` / `bulkAssertDifferent`. */ type IdentityPair = Readonly<{ a: IdentityNodeRefInput; b: IdentityNodeRefInput; }> & IdentityValidityWindow; /** * The read half of the identity surface: equivalence-set membership, * representative selection, and the assertions behind them. * * Obtained from a read-only lens — `store.asOf(...).identity`, * `store.snapshot().identity` — where it answers at that view's coordinate. * The full read+write surface is {@link IdentityFacade}. */ type IdentityReadFacade = Readonly<{ representativeOf: (ref: IdentityNodeRefInput) => Promise | undefined>; membersOf: (ref: IdentityNodeRefInput) => Promise[]>; nodesOf: (ref: IdentityNodeRefInput) => Promise[]>; areSame: (a: IdentityNodeRefInput, b: IdentityNodeRefInput) => Promise; areDifferent: (a: IdentityNodeRefInput, b: IdentityNodeRefInput) => Promise; assertionsOf: (ref: IdentityNodeRefInput) => Promise[]>; }>; /** * The full TypeGraph Identity Profile surface: {@link IdentityReadFacade} plus * the assertion writers and retractions. * * Obtained from `store.identity` or `tx.identity`, and present only on graphs * that declared `identity: { ... }` in `defineGraph`. Every writer is * idempotent — re-asserting an existing claim returns it with * `action: "existing"` rather than duplicating it. */ type IdentityFacade = IdentityReadFacade & Readonly<{ assertSame: (a: IdentityNodeRefInput, b: IdentityNodeRefInput, window?: IdentityValidityWindow) => Promise>; assertDifferent: (a: IdentityNodeRefInput, b: IdentityNodeRefInput, window?: IdentityValidityWindow) => Promise>; bulkAssertSame: (pairs: readonly IdentityPair[]) => Promise[]>; bulkAssertDifferent: (pairs: readonly IdentityPair[]) => Promise[]>; retractAssertion: (id: IdentityAssertionId) => Promise | undefined>; retractSameAssertion: (a: IdentityNodeRefInput, b: IdentityNodeRefInput) => Promise | undefined>; retractDifferentAssertion: (a: IdentityNodeRefInput, b: IdentityNodeRefInput) => Promise | undefined>; bulkRetractAssertions: (ids: readonly IdentityAssertionId[]) => Promise[]>; }>; /** * The assertion-only write surface of the TypeGraph Identity Profile. * * This deliberately excludes reads and retractions so constrained staging * handles can accept incoming identity claims without exposing the broader * operational identity surface. */ type IdentityAssertionWriteFacade = Pick, "assertSame" | "assertDifferent" | "bulkAssertSame" | "bulkAssertDifferent">; /** * Per-transaction identity write counts carried by a transaction receipt. * `total` is the sum of the three preceding counters. */ type IdentityWriteSummary = Readonly<{ sameAssertions: number; differentAssertions: number; retractions: number; total: number; }>; /** Pure preparation for a schema evolution; database-dependent guards run at apply time. */ /** A planned schema delta, database-dependent check, or provisioning requirement. */ type EvolutionRequirement = Readonly<{ kind: "require-empty"; entity: "node" | "edge"; kindName: string; }> | Readonly<{ /** A kind added by this delta; does not imply a pending removal or cleanup work. */ kind: "new-kind"; entity: "node" | "edge"; kindName: string; }> | Readonly<{ kind: "vector-slot"; nodeKind: string; fieldPath: string; }> | Readonly<{ kind: "identity"; nodeKinds: readonly string[]; }>; /** Ordered requirements exposed by a change plan. */ type EvolutionRequirements = readonly EvolutionRequirement[]; declare const evolutionPlanBrand: unique symbol; type EvolutionPlanBase = Readonly<{ graphId: string; baseline: SchemaIdentity; result: SchemaIdentity; /** Only this module can mint a plan accepted by withEvolvedTransaction. */ [evolutionPlanBrand]: true; }>; /** * A prepared schema change. Plans are module-bound, nonserializable values: * object spreads, clones, and reconstructed data cannot be applied. */ type EvolutionPlan = (EvolutionPlanBase & Readonly<{ status: "noop"; }>) | (EvolutionPlanBase & Readonly<{ status: "change"; requirements: EvolutionRequirements; }>); /** * Parses and validates a serialized schema document from the database. * * Uses the Zod schema to validate the full nested structure, catching * corruption, incompatible schema versions, or truncated JSON at the * parse boundary rather than letting invalid data propagate silently. */ declare function parseSerializedSchema(json: string): SerializedSchema; /** * Reads the active schema row, bootstrapping the base tables on the * first call against an empty database. * * Deliberately does NOT materialize runtime contributions (fulltext) * here. Contribution DDL is derived from the *current code graph*; when * the persisted schema is behind by a breaking change, running it here * would apply vN+1 DDL against the vN table shape before `ensureSchema` * computes the diff and throws `MigrationError`. On Postgres the first * failing statement poisons the surrounding transaction, so the error * that escapes is the idempotent marker-table * `CREATE TABLE IF NOT EXISTS` (collateral damage) rather than a clean * `MigrationError` — breaking the documented migrate-on-`MigrationError` * recovery path (#143). `createStoreWithSchema` is the single canonical * durable-marker writer (#135) and materializes runtime contributions * only AFTER the schema gate has run, so the breaking-change check is * always reached first. */ declare function loadActiveSchemaWithBootstrap(backend: GraphBackend, graphId: string): Promise; /** * Reads the active schema, parses it, and folds any persisted graph-extension * document into the supplied compile-time graph. Returns the * merged graph alongside the prefetched row + parsed schema so the * caller can pass them through to `ensureSchema` without paying for a * second `getActiveSchema` round trip or a second * `serializedSchemaZod` walk. * * Throws `ConfigurationError` if the persisted graph-extension document * references a compile-time kind that no longer exists (the * startup-conflict case). */ declare function loadAndMergeGraphExtensionDocument(backend: GraphBackend, graph: G): Promise>; /** * Returns a graph carrying the supplied deprecated-kind names. Used by * the loader to propagate `SerializedSchema.deprecatedKinds` onto the * `GraphDef` that the Store sees, and by `Store.deprecateKinds` / * `Store.undeprecateKinds` to construct the next graph. * * Returns the original graph reference when the desired set already * matches `graph.deprecatedKinds` — covers both the no-deprecations * load path (empty equals empty) and the loader's restart-with-same- * persisted-set hot path. Skips a Set allocation + spread + freeze. */ declare function applyDeprecatedKinds(graph: G, names: Iterable | undefined): G; /** * Result of schema validation. * * The `initialized` and `migrated` statuses carry the committed * `SchemaVersionRow` directly so callers building post-commit metadata * (e.g. `Store.deprecateKinds`) can skip a `getActiveSchema` round-trip. */ type SchemaValidationResult = { status: "initialized"; version: number; committedRow: SchemaVersionRow; } | { status: "unchanged"; version: number; } | { status: "migrated"; fromVersion: number; toVersion: number; diff: SchemaDiff; committedRow: SchemaVersionRow; } | { status: "pending"; version: number; diff: SchemaDiff; } | { status: "breaking"; diff: SchemaDiff; actions: readonly string[]; }; /** * Context passed to migration lifecycle hooks. * * Hooks are intended for observability (logging, metrics, alerts), * not for data transformations. Use an explicit migration runner * for backfill scripts — see the schema evolution guide. */ type MigrationHookContext = Readonly<{ graphId: string; fromVersion: number; toVersion: number; diff: SchemaDiff; }>; /** * Options for schema management. */ type SchemaManagerOptions = Readonly<{ /** If true, auto-migrate safe changes. Default: true */ autoMigrate?: boolean; /** If true, throw on breaking changes. Default: true */ throwOnBreaking?: boolean; /** * Whether `createStoreWithSchema` brings the base-relation system * indexes up to the running library version at boot. Default: * `"materialize"`. Pass `"skip"` when a boot must not run potentially * long index builds inline (e.g. a large PostgreSQL deployment behind a * readiness probe) — then run `store.materializeSystemIndexes()` * out-of-band after upgrading. */ systemIndexes?: "materialize" | "skip"; /** Called before a safe auto-migration is applied. For observability only. */ onBeforeMigrate?: (context: MigrationHookContext) => void | Promise; /** Called after a safe auto-migration is applied. For observability only. */ onAfterMigrate?: (context: MigrationHookContext) => void | Promise; /** * The effective `SqlSchema` (custom table names) the graph's Store reads. * Identity schema commits derive their mandatory closure preflight from it; * the preflight itself is never accepted from callers, so it cannot be * substituted or suppressed. */ schema?: SqlSchema; }>; /** * Ensures the schema is initialized and up-to-date. * * This is the main entry point for schema management. It: * 1. Initializes the schema if this is the first run (version 1) * 2. Returns "unchanged" if the schema matches the current graph * 3. Auto-migrates safe changes if autoMigrate is true * 4. Throws MigrationError for breaking changes if throwOnBreaking is true * * @param backend - The database backend * @param graph - The current graph definition * @param options - Schema management options * @returns The result of schema validation * @throws MigrationError if breaking changes detected and throwOnBreaking is true */ declare function ensureSchema(backend: GraphBackend, graph: G, options?: SchemaManagerOptions & { /** * Pre-fetched active row + parsed stored schema. When the loader * (`createStoreWithSchema`) has already paid for `getActiveSchema` * and `parseSerializedSchema` to peek at `extension`, it * passes the results through here so `ensureSchema` doesn't repeat * the round trip + Zod walk on every Store boot. */ preloaded?: Readonly<{ activeRow: SchemaVersionRow | undefined; storedSchema: SerializedSchema | undefined; }>; }): Promise; /** * Verifies the database is at the same schema version as the code * graph, **without** running DDL, bootstrapping tables, or writing * markers. The runtime-side counterpart of `ensureSchema` for the * least-privilege deployment model documented in "Database roles & * least privilege": `createStoreWithSchema` (run once under a privileged * role, optionally after applying generated migration SQL externally) is * responsible for advancing the schema; runtimes assert it. * * @throws BaseSchemaMigrationError if deployment-wide base storage is not at * the version required by the backend. * @throws ConfigurationError if no schema has been initialized. * @throws MigrationError if the persisted schema is behind the code * graph by any change (safe or breaking). * @throws StoreNotInitializedError if the schema is current but the * runtime-contribution markers are missing/stale/failed (the * privileged migrator has not materialized strategy-owned storage for * this graph on this connection). */ declare function assertSchemaCurrent(backend: GraphBackend, graph: G): Promise; declare function initializeSchema(backend: GraphBackend, graph: G, options?: Readonly<{ /** * The effective `SqlSchema` (custom table names) the graph's Store will * read. The identity enablement preflight is always derived internally — * it is deliberately not a parameter, so no caller can commit version 1 * of an identity-enabled graph without the fold scan, contradiction * validation, and closure build. */ schema?: SqlSchema; }>): Promise; type MigrateSchemaOptions = Readonly<{ /** * Commit even when a dropped kind still holds rows. * * **This does not preserve those rows.** They are immediately unreachable — * nothing references the kind any more — and they are not safe from * deletion either: `materializeRemovals` re-derives removals by walking * schema-version history, so the next reconcile finds the dropped kind and * hard-deletes its rows, including soft-deleted ones. The flag buys a * committed schema, not retained data. * * If you need the rows, copy them out **before** committing. If you want * them removed, prefer `Store.removeKinds()`, which queues the cleanup * explicitly instead of relying on history reconciliation. * * Dropping an *empty* kind needs no flag — it strands nothing, and it is * the last step of the documented three-deploy kind-removal flow. * * @defaultValue false */ discardDroppedKindRows?: boolean; /** * The effective `SqlSchema` (custom table names) the graph's Store reads. * The identity closure preflight an identity-enabled migration commits is * derived from it and cannot be substituted by callers. */ schema?: SqlSchema; }>; /** * Migrates the schema to match the current graph definition. * * Creates a new schema version and atomically activates it via the * `commitSchemaVersion` backend primitive — insert and activate happen * in a single transactional unit with optimistic compare-and-swap on * the currently-active version. If another writer has advanced the * active version since `currentVersion` was read, this throws * `StaleVersionError`; the caller should refetch and retry. * * Folds the persisted graph extension into `graph` first, like every other * commit path — kinds committed at runtime by `Store.evolve()` live in * `schema_doc.extension`, so committing the caller's graph verbatim would * erase them while leaving their rows behind. Property-level breaking * changes (the documented "force the contract deploy" use) are unaffected. * * @param backend - The database backend * @param graph - The current graph definition * @param currentVersion - The current active schema version * @param options - See {@link MigrateSchemaOptions} * @returns The new version number * @throws MigrationError with `reason: "kind-removal"` when the commit would * drop a kind that still holds rows and `discardDroppedKindRows` is not set. */ declare function migrateSchema(backend: GraphBackend, graph: G, currentVersion: number, options?: MigrateSchemaOptions): Promise; /** * Rolls back the active schema to a previous version. * * The target version must already exist in the version history. * This does not delete newer versions — it simply switches the active pointer. * * Uses the `setActiveVersion` backend primitive, which performs the flip * atomically with optimistic compare-and-swap on the currently-active * version. Concurrent rollbacks or commits surface as * `StaleVersionError`. * * @param backend - The database backend * @param graphId - The graph ID * @param targetVersion - The version to roll back to * @throws MigrationError if the target version does not exist * @throws StaleVersionError if another writer changed the active version concurrently */ declare function rollbackSchema(backend: GraphBackend, graphId: string, targetVersion: number): Promise; /** * Gets the current active schema for a graph — the committed document itself, * with the `nodes` / `edges` / `ontology` maps the database actually holds. * * This is the answer to "what kinds does this database already have?". Use * {@link getCommittedSchemaVersion} when only the version number is needed. * * @param backend - The database backend * @param graphId - The graph ID * @returns The active schema or undefined if not initialized */ declare function getActiveSchema(backend: GraphBackend, graphId: string): Promise; /** * Checks if a graph's schema has been initialized. * * @param backend - The database backend * @param graphId - The graph ID * @returns True if the schema has been initialized */ declare function isSchemaInitialized(backend: GraphBackend, graphId: string): Promise; /** * Gets the schema diff between the stored schema and current graph. * * @param backend - The database backend * @param graph - The current graph definition * @returns The diff, or undefined if schema not initialized */ declare function getSchemaChanges(backend: GraphBackend, graph: G): Promise; /** * Whether committing `graph` would require a schema migration — a SELECT-only * pre-flight with no DDL and no writes. * * Returns `true` when the schema has not been initialized yet (the privileged * bootstrap is required) and when the committed schema is behind `graph`. * This is the predicate a least-privilege runtime checks to route to the * privileged path *before* a write discovers the migration wall mid-request. * * For the additive-vs-incompatible distinction, use `getSchemaChanges` and * {@link classifySchemaChanges} instead — this collapses both to `true`. * * @param backend - The database backend * @param graph - The current graph definition * @returns Whether a privileged migration/bootstrap is required. */ declare function requiresMigration(backend: GraphBackend, graph: G): Promise; /** * Reads the committed schema version for a graph in a single round-trip — no * schema reconcile, no diff, no materialization-marker reads. * * This is the cross-isolate invalidation probe for a cached reconciled schema: * compare the returned version against the one a verified open recorded * (`store.reconciledSchema.version`); when it has moved, another process * committed a schema change and the cached reconciliation must be refreshed via * `createVerifiedAdapterStore`. One read replaces the three-query verified open * on the steady-state (unchanged) path — the round-trip that saturated the * connection pool under fan-out. * * It reads the active schema *row* (via `backend.getActiveSchema`), so the * committed `schema_doc` is transferred and normalized even though only the * version is used. A version-only backend query would shrink the payload * further; it is a backward-compatible follow-up, not required for the * round-trip win above. * * Returns only the version; for the document it names, see * {@link getActiveSchema}. * * @param backend - The database backend * @param graphId - The graph ID * @returns The active committed version, or `undefined` if the schema has not * been initialized for this graph. */ declare function getCommittedSchemaVersion(backend: GraphBackend, graphId: string): Promise; /** * Shared types for Tier 1 graph algorithms. * * Algorithms operate over one or more edge kinds and may traverse edges * in the forward direction ("out"), reverse ("in"), or undirected ("both"). */ /** * Direction of edge traversal. * * - `"out"` — follow edges from source to target (default) * - `"in"` — follow edges from target back to source * - `"both"` — undirected traversal over either endpoint */ type TraversalDirection = "out" | "in" | "both"; /** * Cycle-handling option retained for compatibility with recursive query-builder * traversals. Store algorithms are set-based and visit each node once at its * minimum depth, so `"prevent"` and `"allow"` produce the same algorithm * result. Aliased to `RecursiveCyclePolicy` so both APIs share one union. */ type AlgorithmCyclePolicy = RecursiveCyclePolicy; /** * Temporal filter options shared by every algorithm. * * Algorithms honor the same temporal model as the rest of the store: both * nodes and edges are filtered according to the resolved mode. `asOf` is * required when the resolved mode is `"asOf"` and rejected for every other * mode. If neither option is supplied, the algorithm falls back to * `graph.defaults.temporalMode`. */ type TemporalAlgorithmOptions = NoRecordedCoordinate & Readonly<{ /** Temporal mode. Defaults to the graph's configured default. */ temporalMode?: TemporalMode; /** * ISO-8601 timestamp pinning the read. Required when `temporalMode` is * `"asOf"`; rejected (throws `ValidationError`) for every other mode. */ asOf?: string; }>; type InternalTemporalAlgorithmOptions = Omit & Readonly<{ recordedAsOf?: RecordedInstant; }>; /** * Opt-in, transaction-scoped override of the session's working memory for * iterative graph rounds. * * When set, PostgreSQL applies it with `SET LOCAL work_mem` semantics inside * the operation's own transaction — the override ends with the transaction * and the session and server settings are never modified. When omitted (the * default), the operation inherits the server's configured `work_mem`. * * `work_mem` is a threshold each sort/hash operator (and each parallel * worker) may allocate up to, NOT a per-operation budget: a single round can * allocate several multiples of it, and concurrent algorithm calls multiply * again. Raise it deliberately — e.g. `"64MB"` for large single-tenant * analytical runs where the configured default spills whole-graph sorts to * disk — not as a blanket setting on a shared cluster. * * The value must be a plain integer with a `kB`, `MB`, or `GB` suffix within * PostgreSQL's accepted `work_mem` range (`64kB` to `2147483647kB`); both * backends reject malformed or out-of-range values identically. SQLite has * no equivalent setting and otherwise ignores the option. */ type IterativeMemoryOptions = Readonly<{ /** * Transaction-scoped `work_mem` override, e.g. `"64MB"`. Omit to inherit * the server's configured setting. */ workingMemory?: string; }>; /** * Base options for traversal-style algorithms. */ type BaseTraversalOptions = TemporalAlgorithmOptions & IterativeMemoryOptions & Readonly<{ /** Edge kinds to follow. At least one kind is required. */ edges: readonly EdgeKinds[]; /** * Maximum number of hops to traverse. Defaults to 10. * Must be between 1 and 1000. */ maxHops?: number; /** Direction of traversal (default: `"out"`). */ direction?: TraversalDirection; /** Compatibility option; set-based algorithms always visit a node once. */ cyclePolicy?: AlgorithmCyclePolicy; }>; type InternalBaseTraversalOptions = InternalTemporalAlgorithmOptions & Omit, keyof TemporalAlgorithmOptions>; /** * A node reached during traversal, annotated with the shortest depth at which * it was first discovered. */ type ReachableNode = Readonly<{ id: string; kind: string; /** Number of edges traversed from the source. 0 for the source itself. */ depth: number; }>; /** * A node along a shortest path. Endpoints are included. */ type PathNode = Readonly<{ id: string; kind: string; }>; /** * Result of `shortestPath`: the ordered node sequence and its length in hops. */ type ShortestPathResult = Readonly<{ /** Ordered nodes from source to target (inclusive). */ nodes: readonly PathNode[]; /** Number of edges traversed. Equals `nodes.length - 1`. */ depth: number; }>; /** * Options for `shortestPath` and `canReach`. Reuses the base traversal * options; cycle policy defaults to `"prevent"` since both algorithms only * care about the first time a target is reached. */ type ShortestPathOptions = BaseTraversalOptions; type InternalShortestPathOptions = InternalBaseTraversalOptions; /** * Options for `weightedShortestPath`. * * Each traversed edge contributes the value of `weightProperty` — a JSON * number stored on the edge — to the path's total weight. The traversal * fails fast with `InvalidEdgeWeightError` (before any rounds run) when any * visible edge of the selected kinds has a negative, non-numeric, or * out-of-range weight, or is missing the property with no `defaultWeight` * configured. Weight arithmetic uses IEEE 754 doubles on both backends: * total weights are always backend-identical, and — unless the `edges` list * is large enough to exceed the backend's bind-parameter budget (hundreds * of kinds in one call, where equal-weight predecessor ties may resolve * differently) — so is the returned node sequence. * * Unlike `shortestPath`, there is no `maxHops`: cost-ordered discovery does * not settle nodes in hop order, so a hop bound is not a natural stopping * rule. `maxIterations` caps relaxation rounds purely as a runaway backstop * — the algorithm normally converges (and prunes against the best known * target distance) long before reaching it. */ type WeightedShortestPathOptions = Omit, "maxHops" | "cyclePolicy"> & Readonly<{ /** * Top-level edge property supplying each edge's non-negative numeric * weight. */ weightProperty: string; /** * Weight substituted for edges missing `weightProperty`. Must be a * non-negative number within the same upper bound the audit applies to * stored weights (~9.7e289, so accumulated path sums can never * overflow). Without it, a missing weight throws * `InvalidEdgeWeightError`. */ defaultWeight?: number; /** Maximum relaxation rounds before throwing. Defaults to 1000. */ maxIterations?: number; }>; type InternalWeightedShortestPathOptions = InternalTemporalAlgorithmOptions & Omit, keyof TemporalAlgorithmOptions>; /** * Result of `weightedShortestPath`: the minimum-total-weight node sequence * from source to target. */ type WeightedShortestPathResult = Readonly<{ /** Ordered nodes from source to target (inclusive). */ nodes: readonly PathNode[]; /** Number of edges traversed. Equals `nodes.length - 1`. */ depth: number; /** Sum of the traversed edges' weights. 0 for a self-path. */ totalWeight: number; }>; /** * Options for `reachable`. * * Returns every node reachable from the source within `maxHops`, each * annotated with its minimum discovered depth. `depth: 0` refers to the * source itself and is included unless `excludeSource` is `true`. */ type ReachableOptions = BaseTraversalOptions & Readonly<{ /** Exclude the source node from the result set (default: `false`). */ excludeSource?: boolean; }>; type InternalReachableOptions = InternalBaseTraversalOptions & Omit, keyof BaseTraversalOptions>; /** * Options for `neighbors`. * * Like `reachable`, but the parameter is named `depth` for readability — * "2-hop neighbors" reads more naturally than "reachable with maxHops=2". * The source is always excluded. */ type NeighborsOptions = TemporalAlgorithmOptions & IterativeMemoryOptions & Readonly<{ /** Edge kinds to follow. At least one kind is required. */ edges: readonly EdgeKinds[]; /** Maximum neighborhood depth (default: 1). Must be between 1 and 1000. */ depth?: number; /** Direction of traversal (default: `"out"`). */ direction?: TraversalDirection; /** Compatibility option; set-based algorithms always visit a node once. */ cyclePolicy?: AlgorithmCyclePolicy; }>; type InternalNeighborsOptions = InternalTemporalAlgorithmOptions & Omit, keyof TemporalAlgorithmOptions>; /** * Options for `degree`. * * Counts active edges incident to a node. With `direction: "both"`, an edge * that happens to be a self-loop (from === to) is counted once, not twice. */ type DegreeOptions = TemporalAlgorithmOptions & Readonly<{ /** * Edge kinds to count. If omitted, counts across all edge kinds in the * graph. */ edges?: readonly EdgeKinds[]; /** Direction (default: `"both"`). */ direction?: TraversalDirection; }>; type InternalDegreeOptions = InternalTemporalAlgorithmOptions & Omit, keyof TemporalAlgorithmOptions>; /** * Options for exact weakly connected components. * * Selected edges are treated as undirected, regardless of their declared * direction. By default every visible graph node is returned; `nodeKinds` * restricts the operation to the induced subgraph over those kinds. Nodes in * scope with no selected incident edge form singleton components. */ type WeaklyConnectedComponentsOptions = TemporalAlgorithmOptions & IterativeMemoryOptions & Readonly<{ /** Edge kinds whose undirected projection defines connectivity. */ edges: readonly EdgeKinds[]; /** Optional node kinds defining the induced subgraph to analyze. */ nodeKinds?: readonly NodeKinds[]; /** * Return only components containing at least this many nodes. Must be a * positive safe integer. */ minComponentSize?: number; /** Maximum label-propagation rounds before throwing. Defaults to 1000. */ maxIterations?: number; }>; type InternalWeaklyConnectedComponentsOptions = InternalTemporalAlgorithmOptions & Omit, keyof TemporalAlgorithmOptions>; /** One node's membership in an exact weakly connected component. */ type WeaklyConnectedComponentMembership = Readonly<{ id: string; kind: string; /** Deterministic minimum node id in this component. */ componentId: string; /** Kind paired with `componentId`; node identity is `(kind, id)`. */ componentKind: string; /** Number of visible nodes in this component. */ size: number; }>; /** * Options for deterministic Community Detection using Label Propagation * (CDLP). * * Selected edges are treated as undirected. Every synchronous round assigns * each node the most frequent label among its visible neighbors from the * previous round; ties resolve to the minimum `(id, kind)` label under binary * ordering. Isolated nodes retain their own identity as a singleton label. */ type LabelPropagationOptions = TemporalAlgorithmOptions & IterativeMemoryOptions & Readonly<{ /** Edge kinds whose undirected projection supplies neighbor votes. */ edges: readonly EdgeKinds[]; /** Optional node kinds defining the induced subgraph to analyze. */ nodeKinds?: readonly NodeKinds[]; /** Maximum synchronous rounds. Defaults to `1000`. */ maxIterations?: number; /** * Completion contract when `maxIterations` rounds elapse without * convergence. `"throw"` (default) raises * `GraphAlgorithmConvergenceError` — immediately once a periodic * oscillation is detected, since no budget can converge it. `"return"` * yields the exact labeling after `maxIterations` synchronous rounds, * matching fixed-round Graphalytics CDLP; synchronous rounds are * deterministic, so that labeling is identical on every backend. */ onMaxIterations?: "throw" | "return"; }>; type InternalLabelPropagationOptions = InternalTemporalAlgorithmOptions & Omit, keyof TemporalAlgorithmOptions>; /** One visible node's membership in a deterministic CDLP community. */ type LabelPropagationMembership = Readonly<{ id: string; kind: string; /** Final propagated label id. */ labelId: string; /** Kind paired with `labelId`; node identity is `(kind, id)`. */ labelKind: string; }>; /** Options shared by global and personalized PageRank. */ type PageRankOptions = TemporalAlgorithmOptions & IterativeMemoryOptions & Readonly<{ /** Edge kinds defining the transition graph. */ edges: readonly EdgeKinds[]; /** Optional node kinds defining the induced subgraph to rank. */ nodeKinds?: readonly NodeKinds[]; /** Transition direction. Defaults to `"out"`. */ direction?: TraversalDirection; /** Probability of following an edge. Defaults to `0.85`. */ dampingFactor?: number; /** Maximum accepted per-node score change. Defaults to `1e-8`. */ tolerance?: number; /** Maximum power-iteration rounds before throwing. Defaults to `1000`. */ maxIterations?: number; /** * Return only the first this many scores after deterministic ordering. * Must be a positive safe integer. */ topK?: number; }>; type InternalPageRankOptions = InternalTemporalAlgorithmOptions & Omit, keyof TemporalAlgorithmOptions>; /** One weighted teleport target for personalized PageRank. */ type PersonalizedPageRankSeed = Readonly<{ id: string; kind: NodeKinds; /** Relative teleport weight. Defaults to `1`; must be finite and positive. */ weight?: number; }>; /** Personalized PageRank options with one or more weighted teleport seeds. */ type PersonalizedPageRankOptions = PageRankOptions & Readonly<{ seeds: readonly PersonalizedPageRankSeed[]; }>; type InternalPersonalizedPageRankOptions = InternalTemporalAlgorithmOptions & Omit, keyof TemporalAlgorithmOptions>; /** One visible node's normalized PageRank score. */ type PageRankScore = Readonly<{ id: string; kind: string; score: number; }>; /** * Raw node id or any object with an `id: string` field. Covers `Node`, * `NodeRef`, and the lightweight `ReachableNode` / `PathNode` shapes * returned by the algorithms themselves. * * Deliberately kind-agnostic: graph algorithms don't constrain the source * node's kind — you can start a traversal from any node reachable via the * given edge kinds. `NodeRef` exists for the edge-endpoint case where * kind *is* load-bearing; using it here would paint a constraint onto a * contract that doesn't need one and would reject common patterns like * passing `ReachableNode` / cache entries / `{ id }` records straight * through. */ type NodeIdentifier = string | Readonly<{ id: string; }>; type GraphAlgorithms = Readonly<{ /** * Finds the shortest directed path from `from` to `to` using the given * edge kinds. Returns `undefined` when no path exists within `maxHops`. * * @example * ```typescript * const path = await store.algorithms.shortestPath(alice, bob, { * edges: ["knows"], * maxHops: 6, * }); * if (path) { * console.log(`${path.depth} hops via`, path.nodes.map((n) => n.id)); * } * ``` */ shortestPath: (from: NodeIdentifier, to: NodeIdentifier, options: ShortestPathOptions) => Promise; /** * Finds the minimum-total-weight path from `from` to `to`, weighting each * traversed edge by the numeric `weightProperty` stored on it. Returns * `undefined` when no path exists. Weights must be non-negative; a * negative, non-numeric, or (without `defaultWeight`) missing weight on * any visible edge of the selected kinds throws `InvalidEdgeWeightError` * before traversal starts. * * @example * ```typescript * const path = await store.algorithms.weightedShortestPath(alice, bob, { * edges: ["knows"], * weightProperty: "interactionCost", * direction: "both", * }); * if (path) { * console.log(`total weight ${path.totalWeight} over ${path.depth} hops`); * } * ``` */ weightedShortestPath: (from: NodeIdentifier, to: NodeIdentifier, options: WeightedShortestPathOptions) => Promise; /** * Returns every node reachable from `from` within `maxHops` edges of the * allowed kinds. Each node carries its minimum discovered depth. */ reachable: (from: NodeIdentifier, options: ReachableOptions) => Promise; /** * Fast boolean check: is `to` reachable from `from` within `maxHops` * edges? Uses bidirectional BFS and stops when the frontiers meet. */ canReach: (from: NodeIdentifier, to: NodeIdentifier, options: BaseTraversalOptions) => Promise; /** * Returns the k-hop neighborhood of a node. The source is always * excluded. `depth` defaults to 1, matching the common "immediate * neighbors" interpretation. */ neighbors: (node: NodeIdentifier, options: NeighborsOptions) => Promise; /** * Counts active edges incident to `node`. * * With `direction: "both"` (default), self-loops contribute once. */ degree: (node: NodeIdentifier, options?: DegreeOptions) => Promise; /** * Runs deterministic synchronous label propagation over an undirected * projection of the selected edge kinds. */ labelPropagation: (options: LabelPropagationOptions) => Promise; /** * Computes exact weakly connected components over the selected edge kinds. * * Iterative: runs multiple SQL rounds in one snapshot and requires * `backend.capabilities.graphAnalytics.supported`. */ weaklyConnectedComponents: (options: WeaklyConnectedComponentsOptions) => Promise; /** * Computes global PageRank over the visible induced graph. Scores sum to * approximately one and are returned from highest to lowest. */ pageRank: (options: PageRankOptions) => Promise; /** * Computes PageRank with teleport mass distributed across weighted seeds. */ personalizedPageRank: (options: PersonalizedPageRankOptions) => Promise; }>; type InternalGraphAlgorithms = Readonly<{ shortestPath: (from: NodeIdentifier, to: NodeIdentifier, options: InternalShortestPathOptions) => Promise; weightedShortestPath: (from: NodeIdentifier, to: NodeIdentifier, options: InternalWeightedShortestPathOptions) => Promise; reachable: (from: NodeIdentifier, options: InternalReachableOptions) => Promise; canReach: (from: NodeIdentifier, to: NodeIdentifier, options: InternalBaseTraversalOptions) => Promise; neighbors: (node: NodeIdentifier, options: InternalNeighborsOptions) => Promise; degree: (node: NodeIdentifier, options?: InternalDegreeOptions) => Promise; labelPropagation: (options: InternalLabelPropagationOptions) => Promise; weaklyConnectedComponents: (options: InternalWeaklyConnectedComponentsOptions) => Promise; pageRank: (options: InternalPageRankOptions) => Promise; personalizedPageRank: (options: InternalPersonalizedPageRankOptions) => Promise; }>; /** The relation a claim row lives in. */ type ClaimRelation = "uniques" | "edgeClaims"; /** A claim row named in full — the row a statement is about to lock. */ type ClaimTarget = Readonly<{ relation: ClaimRelation; graphId: string; axis: string; /** * `uniques.constraint_name`. The edge claim relation keys on * `(graph_id, axis, key)` and has no such column, so its targets omit it and * sort as the empty string. Absent means "this relation does not key on a * constraint name", never "not known yet". */ constraintName?: string; key: string; }>; /** * WHO holds a claim. A node, not an id: ids are unique only per kind, so * `(concrete_kind, node_id)` is the smallest thing that identifies a claimant. */ type ClaimOwner = Readonly<{ concreteKind: string; nodeId: string; }>; /** * The read-only fence diagnostic: which claim axes are ALREADY contended. * * A claim relation refuses a second claimant from the first post-upgrade write * onward, but it repairs nothing that is already there. A database that carried * two live siblings sharing a scoped key, or two live `cardinality: "one"` edges * from one source, keeps carrying them — the next write that touches such an * axis is refused with the ordinary typed error naming the incumbent, and until * then nothing says so out loud. This module is what says so. * * It reports; it never repairs. Choosing which of two live claimants keeps the * axis is a data-loss decision that belongs to the operator, not to a * diagnostic. * * Every axis and key it names is built by the SAME function the fence writes * with — {@link uniquenessClaimTarget}, {@link disjointnessClaimAxis}, * {@link edgeCardinalityClaimTarget} — so a report row names the row a writer * would actually contend for. A second spelling here would produce a report * about axes the fence does not use. */ /** * One claim axis more than one live claimant holds. * * Discriminated on the family because the two claim relations record their * holders differently: a `uniques` axis is held by an OWNER PAIR (ids are * unique only per kind), an edge-claim axis by an edge id. `target` is the * claim row itself, so a reader can go straight to the row a writer contends * for rather than reconstructing it from the family's own vocabulary. */ type ConstraintFenceViolation = Readonly<{ family: "nodeUniqueness" | "nodeDisjointness"; target: ClaimTarget; owners: readonly ClaimOwner[]; }> | Readonly<{ family: "edgeCardinality"; target: ClaimTarget; edgeIds: readonly string[]; }>; /** Selects the snapshot used to plan without taking a schema write fence. */ type PlanEvolutionOptions = Readonly<{ /** Defaults to a fresh, read-only active-schema lookup. */ source?: "database" | "cached"; }>; /** Read-only reconciliation after the caller has committed its transaction. */ type RefreshSchemaOptions = Readonly<{ ref?: StoreRef; /** Minimum committed version expected; a matching cached snapshot needs no SQL. */ minVersion?: number; }>; /** Options for applying a precomputed evolution plan on a caller transaction. */ type EvolvedTransactionOptions = Readonly<{ /** Finite exclusive schema-fence acquisition budget for change plans, in milliseconds. */ waitBudgetMs?: number; }>; /** Schema metadata is provisional until the caller commits the outer transaction. */ type EvolvedTransactionOutcome = Readonly<{ result: TransactionOutcome["result"]; receipt: TransactionOutcome["receipt"] & Readonly<{ schema: SchemaIdentity; }>; }>; /** * Members that are safe to expose through a history-enabled adapter Store. * * Graph entity writes remain because the source is the capture-wrapped * backend. Direct raw SQL, native import, graph clearing, and nested backend * transactions stay internal because each can mutate live rows without a * corresponding capture flush. */ declare const HISTORY_STORE_BACKEND_KEYS: readonly ["assertRuntimeContributionsInitialized", "assertVectorSlotInitialized", "assertVectorSlotsInitialized", "bootstrapTables", "capabilities", "catalog", "lineage", "recordedTime", "checkUnique", "checkUniqueBatch", "claimEdgeCardinality", "claimEdgeCardinalityGuarded", "claimEdgeCardinalityBatch", "claimIndexMaterialization", "close", "commitSchemaVersion", "commitSchemaVersionIfKindsEmpty", "lockSchemaVersionForWrite", "lockSchemaVersionAndGraphWrite", "compileSql", "countEdgesByKind", "countEdgesFrom", "countNodesByKind", "createVectorIndex", "deleteEdge", "deleteEdgesBatch", "deleteEmbedding", "deleteEmbeddingBatch", "deleteFulltext", "deleteFulltextBatch", "deleteNode", "deleteUnique", "hardDeleteUniquesByNodeIds", "deleteVectorSlotContribution", "dialect", "dropVectorIndex", "fenceSql", "adoptBaseSchema", "assertBaseSchemaCurrent", "edgeExistsBetween", "ensureContributionMaterializationsTable", "ensureExtension", "ensureEdgeMatchIdentityStorage", "ensureFulltextTable", "ensureIndexMaterializationsTable", "ensureKindRemovalsTable", "ensureReconciliationMarkersTable", "ensureRevisionOriginsTable", "ensureRuntimeContributions", "ensureTrigramExtension", "ensureVectorSlotContribution", "ensureVectorSlotContributions", "execute", "executeTemporaryStatement", "findEdgesByKind", "findEdgesByEndpointSet", "findEdgesByHeterogeneousEndpointSet", "findEdgesConnectedTo", "findNodesByKind", "fulltextSearch", "fulltextStrategy", "getActiveSchema", "getAllKindRemovals", "getContributionMaterialization", "getEdge", "getEdges", "getIndexMaterialization", "getIndexMaterializations", "getNode", "getNodes", "getPendingKindRemovals", "getReconciliationMarker", "getSchemaVersion", "hardDeleteEdge", "hardDeleteEdgesBatch", "hardDeleteNode", "hardDeleteUniquesByConcreteKind", "hardDeleteUniquesByNodeIds", "hybridSearch", "insertEdge", "commands", "insertEdgeNoReturn", "insertEdgesBatch", "insertEdgesBatchReturning", "insertEdgesDurableBatchReturning", "insertNode", "insertNodeIfAbsent", "insertNodeIfAbsentWithSchemaFence", "insertNodeWithSchemaFence", "insertNodeNoReturn", "insertNodesBatch", "insertNodesBatchReturning", "insertUnique", "insertUniqueBatch", "probeContributions", "purgeEdgeClaims", "readConstraintFenceViolations", "recordContributionMaterialization", "recordIndexMaterialization", "recordKindRemoval", "refreshStatistics", "releaseIndexMaterializationClaim", "setActiveVersion", "setReconciliationMarker", "tableNames", "updateEdge", "updateNode", "upsertHeterogeneousNodes", "updateResolvedNodesBatch", "compareAndSetNode", "updateNodeSet", "upsertEmbedding", "upsertEmbeddingBatch", "upsertFulltext", "upsertFulltextBatch", "vectorSearch", "vectorStrategy", "verifyContributions"]; type HistoryStoreBackendMember = (typeof HISTORY_STORE_BACKEND_KEYS)[number]; type UnsafeHistoryStoreBackendMember = "clearGraph" | "commitSchemaVersionWithPreflight" | "instantiateGraphTemplate" | "executeDdl" | "executeRaw" | "executeStatement" | "ensureIdentityTables" | "identityTableDdl" | "rebuildContribution" | "recordedTableDdl" | "repairContributions" | "registerGraphTemplate" | "schemaWriteTransaction" | "transaction" | "trustedImport"; type UnclassifiedHistoryStoreBackendMember = Exclude; type HistoryStoreBackend = UnclassifiedHistoryStoreBackendMember extends never ? Readonly> : never; /** * Data-cleanup phase for `store.removeKinds()`. * * The removeKinds verb commits the new schema atomically (millisecond * budget); the data deletion happens here, scoped per-deployment via * the `typegraph_kind_removals` status table. Splitting the verbs * mirrors how `materializeIndexes` complements `evolve`: schema * commits are atomic and fast, while data work is bounded by row count and * deferrable. Candidates run sequentially because they share one graph lock. */ type MaterializeRemovalsOptions = Readonly<{ /** Restrict to specific kind names. */ kinds?: readonly string[]; /** Halt on first failure. Default: false (best-effort). */ stopOnError?: boolean; }>; /** * Outcome of one queued kind removal, discriminated on `status`. * * A union rather than one shape with optional fields, so each outcome carries * exactly its own payload: `"failed"` always has an `error`, `"skipped"` * always has a `reason`, and `"removed"` has neither. The flat form permitted * impossible states — a `"skipped"` with no reason, or a `"removed"` carrying * `reason: "kind-is-live"` — and narrowing on `status` still left both fields * optional at the use site. */ type MaterializeRemovalsEntry = Readonly<{ kind: string; entity: KindEntity; status: "removed"; }> | Readonly<{ kind: string; entity: KindEntity; status: "failed"; error: Error; }> | Readonly<{ kind: string; entity: KindEntity; status: "skipped"; /** * `"kind-is-live"`: the active schema declares this kind again — it was * dropped, then re-added — so deleting its rows would destroy live * data. The queue row stays pending, so a later removal of the same * kind still reclaims it. * * Reported rather than silent: an empty `results` array would otherwise * be indistinguishable from "nothing was pending", leaving the queue at * a non-zero depth with nothing explaining why. */ reason: "kind-is-live"; }>; /** * Outcome of reclaiming one per-`(graphId, kind, field)` vector table that * was orphaned when its embedding field was dropped from a *surviving* * kind's schema. Distinct from {@link MaterializeRemovalsEntry} (whole * kinds) because the unit is a single embedding field, not a kind. */ type ReclaimedVectorFieldEntry = Readonly<{ kind: string; fieldPath: string; status: "reclaimed" | "failed"; error?: Error; }>; type MaterializeRemovalsResult = Readonly<{ results: readonly MaterializeRemovalsEntry[]; /** * Embedding fields removed from a *surviving* kind whose per-field vector * table this pass dropped (or confirmed already absent). Empty when the * backend has no vector strategy or no embedding field has ever been * dropped. Re-derived from immutable schema history each call, so it lists * the same removed fields on repeat passes — the underlying drop is * idempotent (`DROP ... IF EXISTS`). See {@link reclaimRemovedVectorFieldTables}. */ reclaimedVectorFields: readonly ReclaimedVectorFieldEntry[]; }>; declare const STORE_RUNTIME: unique symbol; /** * @internal Operations used by Store-owned views. The port is absent from the * public Store contract and non-enumerable at runtime. JavaScript reflection * can still discover symbol properties, so this is an unsupported internal * surface rather than a security boundary. */ type StoreRuntime = Readonly<{ backend: GraphBackend; /** Constructs a plan-owned resulting-schema view for outside-transaction merge planning. */ evolutionPlanningTarget?: (plan: EvolutionPlan) => Store; /** * @internal Whether TypeGraph itself performs recorded-time capture for * this store — recorded relations, a TypeGraph clock, the write-fence/ * schema-lock machinery capture needs. This is `Store`'s private * `#captureEnabled`, distinct from the public `historyEnabled` getter * (which answers "was `history: true` requested," true under * engine-native ownership too, where the engine tracks history on its * own and none of the TypeGraph-relations machinery below runs). A reader * of a recorded relation's own columns — `recordedRelationsLineage`, the * trusted-import bypass refusal, a revision-anchor lineage delta, the * capture-only merge-transaction isolation choice — consults this member, * never the public getter, so it cannot be fooled by an engine-native * store into reading a recorded relation the engine never populates. * * Optional at this boundary for the same contravariant-reach reason as * `uniqueSidecarBatch` below: the one real producer (`store.ts`'s * constructor) always populates it, and a consumer asserts it with * {@link storeCaptureEnabled}. */ captureEnabled?: boolean; /** * @internal The `uniqueSidecarBatch` bundle's verdict, resolved once at * store construction against `backend` (ruling B8 spec item 2) and exposed * here so a Store-owned view (provenance's fact close/reopen) can build a * {@link file://./claims/node-claims.ts NodeClaimContext} without re-minting * a second verdict for the same backend — the same reason `backend` itself * is exposed here rather than reconstructed. * * Optional at this boundary — a `StoreRuntime`-shaped value is a * contravariant (externally-authorable) position, so a new REQUIRED member * here would be a breaking change (`scripts/api-surface-compat.ts`). * Required after resolution instead, the same pattern * `CompileQueryOptions.recursiveTraversal` uses: the one real producer * (`store.ts`'s constructor) always populates it, and the one real * consumer (`provenance/index.ts`) asserts it with `requireDefined`. */ uniqueSidecarBatch?: BundleVerdictOf | undefined; /** * @internal The backend this Store's queries actually execute through for * `target` — the Store's own backend when `target` is omitted. * * It is the SAME private construction the query path uses rather than a * reconstruction of it, because the object it returns is the one a lost * derivation corrupts: the hooked query backend is a transient local inside * query construction that is stored nowhere, so no other handle on it exists * and a regression there is invisible to every assertion about * {@link StoreRuntime.backend}. * * NOTE for anything asserting on it: with no query hook configured this * returns its argument unchanged, so a hookless Store answers with the very * object it was handed and a comparison against that object is a tautology. */ queryBackend: (target?: GraphBackend | TransactionBackend) => GraphBackend; sealedQuery: (coordinate: ReadCoordinate) => InitialQueryBuilder; recordedNodeGetById: (kind: string, id: NodeId, coordinate: ReadCoordinate) => Promise | undefined>; recordedNodeGetByIds: (kind: string, ids: readonly NodeId[], coordinate: ReadCoordinate) => Promise | undefined)[]>; recordedNodeScan: (kind: string, coordinate: ReadCoordinate, options?: RecordedScanOptions) => Promise>>; recordedEdgeGetById: (kind: string, id: EdgeId, coordinate: ReadCoordinate) => Promise | undefined>; recordedEdgeGetByIds: (kind: string, ids: readonly EdgeId[], coordinate: ReadCoordinate) => Promise | undefined)[]>; recordedEdgeScan: (kind: string, coordinate: ReadCoordinate, options?: RecordedScanOptions) => Promise>>; subgraphAtCoordinate: , const NK extends NodeKinds = NodeKinds, const P extends SubgraphProject | undefined = undefined>(rootId: NodeId>, options: InternalSubgraphOptions) => Promise>; algorithmsAtCoordinate: (coordinate: ReadCoordinate) => InternalGraphAlgorithms; identityAtCoordinate: (coordinate: ReadCoordinate) => IdentityReadFacade; rebuildIdentityClosure: () => Promise; validateIdentity: () => Promise; /** * Validates one final resolved node write set, then clears the affected * nodes' claim rows so its upserts may take their approved keys in any order, * and re-takes the complete claim set once the writes have landed. The caller * must supply a transaction-bound backend. * * The clear is by OWNER, so it takes every claim the affected nodes hold — * uniqueness and `disjointWith` alike — and the rebuild therefore goes through * the same claim writer an ordinary create uses rather than a uniqueness-only * insert. See `store/claims/resolved-node-claims.ts`. */ applyResolvedNodeUniqueness: (target: TransactionBackend, writes: Readonly<{ upserts: readonly Readonly<{ kind: string; id: string; props: Readonly>; }>[]; releases: readonly Readonly<{ kind: string; id: string; }>[]; }>, apply: () => Promise) => Promise; /** * @internal Reads the graph's identity assertions in transfer shape, honoring * this store's SQL binding. Used by interchange export, base-version * fingerprinting, and merge staging/diff. */ readCurrentIdentityAssertions: (mode: "state" | "archival", options?: Readonly<{ nodeKinds?: readonly string[]; includeDeleted?: boolean; }>) => Promise; b: Readonly<{ kind: string; id: string; }>; validFrom: string; validTo?: string | undefined; endedBy?: Readonly<{ kind: string; id: string; }> | undefined; }>[]>; /** * Live nodes (registry kinds only) sharing any of the given bare ids — * the cross-kind peer set same-id folding would join. Used by graph-merge's * plan-time contradiction simulation to seed its node universe. */ liveNodesSharingIds: (ids: readonly string[], target?: GraphBackend | TransactionBackend) => Promise[]>; /** * Every stored assertion row (ended rows included) for the given assertion * ids — the rows the import coordinator's id-conflict check compares * against. Used by graph-merge to validate the one-id-one-truth invariant * at plan time and inside the commit transaction. */ identityAssertionRowsByIds: (ids: readonly string[], target?: GraphBackend | TransactionBackend) => Promise; b: Readonly<{ kind: string; id: string; }>; validFrom: string; validTo?: string | undefined; endedBy?: Readonly<{ kind: string; id: string; }> | undefined; }>>>; /** * The CURRENT structural identity class (materialized closure: folds plus * asserted links) of each reference, keyed by `refKey` — the * `JSON.stringify([kind, id])` serialization exported from * `identity/service`, which callers must use to probe the returned map. A * missing node coalesces to its singleton. Used by graph-merge's fold-peer * window guard to detect class-transitive drift in the plan→commit window. */ structuralIdentityClasses: (references: readonly Readonly<{ kind: string; id: string; }>[], target?: GraphBackend | TransactionBackend) => Promise[]>>; identityAssertionsAtTarget: (target: GraphBackend | TransactionBackend, mode?: "state" | "archival") => Promise; b: Readonly<{ kind: string; id: string; }>; validFrom: string; validTo?: string | undefined; endedBy?: Readonly<{ kind: string; id: string; }> | undefined; }>[]>; readIdentityAssertionPageAtTarget: (target: GraphBackend | TransactionBackend, mode: "state" | "archival", options: Readonly<{ nodeKinds?: readonly string[]; includeDeleted?: boolean; after?: string; limit: number; }>) => Promise; b: Readonly<{ kind: string; id: string; }>; validFrom: string; validTo?: string | undefined; endedBy?: Readonly<{ kind: string; id: string; }> | undefined; }>[]; nextAfter?: string; done: boolean; }>>; lockIdentityImportTarget: (target: Readonly>) => Promise; foldImportedIdentityNodes: (target: Readonly>, references: readonly Readonly<{ kind: string; id: string; }>[]) => Promise; importIdentityAssertionsAtTarget: (target: Readonly>, assertions: readonly Readonly<{ id: string; relation: "same" | "different"; a: Readonly<{ kind: string; id: string; }>; b: Readonly<{ kind: string; id: string; }>; validFrom: string; validTo?: string | undefined; endedBy?: Readonly<{ kind: string; id: string; }> | undefined; }>[], mode: "state" | "archival") => Promise>; applyIdentityMergeAtTarget: (target: GraphBackend | TransactionBackend, retractions: readonly Readonly<{ id: string; relation: "same" | "different"; a: Readonly<{ kind: string; id: string; }>; b: Readonly<{ kind: string; id: string; }>; validFrom: string; validTo?: string | undefined; endedBy?: Readonly<{ kind: string; id: string; }> | undefined; }>[], assertions: readonly Readonly<{ id: string; relation: "same" | "different"; a: Readonly<{ kind: string; id: string; }>; b: Readonly<{ kind: string; id: string; }>; validFrom: string; validTo?: string | undefined; endedBy?: Readonly<{ kind: string; id: string; }> | undefined; }>[]) => Promise>; /** * Proves the identity classes of `seeds` carry no contradiction in the state * the caller's transaction has just written — the post-write half of * graph-merge's identity correctness, scoped to the classes the merge * touched. A refusal aborts the caller's transaction; identity-disabled * graphs resolve immediately. */ assertIdentityClassesConsistentAtTarget: (target: GraphBackend | TransactionBackend, seeds: readonly Readonly<{ kind: string; id: string; }>[]) => Promise; }>; type RebuildFulltextOptions = Readonly<{ /** Page size. Must be a positive integer. Default: 500. */ pageSize?: number; /** * Maximum number of skipped node IDs to include in the `skippedIds` * array. Default: 10,000. Set higher to collect the full list when * investigating systemic corruption; set lower when `processed` is * all you care about. The `skipped` total is always accurate. */ maxSkippedIds?: number; }>; type RebuildFulltextResult = Readonly<{ /** Node kinds that were rebuilt (those with at least one searchable field). */ kinds: readonly string[]; /** Total nodes scanned. */ processed: number; /** Fulltext upsert operations executed. */ upserted: number; /** Fulltext delete operations executed (soft-deleted or all-empty nodes). */ cleared: number; /** * Nodes skipped due to corrupt or non-object `props` (not counted in * upserted/cleared). */ skipped: number; /** * IDs of skipped nodes, capped at 10,000 entries so pathological * corruption doesn't turn rebuild into an OOM. See `skippedTruncated` * to tell whether the cap was hit. Empty when `skipped === 0`. */ skippedIds: readonly string[]; /** True when `skipped > skippedIds.length` (the cap was reached). */ skippedTruncated: boolean; }>; /** * A fulltext search hit. `N` defaults to the generic `Node`; the * `store.search.fulltext(kind)` facade narrows it to the concrete * typed node for that kind so callers get `hit.node.title` without a cast. */ type FulltextSearchHit = Readonly<{ node: N; /** Backend-native relevance score; higher is better. */ score: number; /** 1-based rank within the result set. */ rank: number; /** Highlighted snippet (only present when `includeSnippets: true`). */ snippet?: string; }>; type VectorSearchHit = Readonly<{ node: N; score: number; rank: number; }>; /** * A hybrid search hit. Both sub-results (`vector`, `fulltext`) carry the * same narrowed node type as the top-level `node` for ergonomic access. */ type HybridSearchHit = Readonly<{ node: N; /** Fused RRF score (higher is better). */ score: number; /** 1-based rank in the fused list. */ rank: number; /** Sub-result from the vector half, if it ranked this node. */ vector?: VectorSearchHit; /** Sub-result from the fulltext half, if it ranked this node. */ fulltext?: FulltextSearchHit; }>; /** * Scope options shared by every facade search leg. * * `where` and `includeSubClasses` compile into the search statement's * candidate set (a subquery produced by the store's own query compiler), so * filtering happens INSIDE the engine's top-k — never by post-filtering a * ranked list. `offset` is rank-relative pagination: the engine fetches * `limit + offset` ranked candidates and discards the leading page. */ type SearchScopeOptions = Readonly<{ /** * Predicate over the node's properties, compiled by the shared query * compiler into the candidate subquery. Requires a query-capable store. * The facade instantiates `N` to the searched kind's node type, so the * accessor is fully typed at the call site; the base instantiation * exposes the system accessors (`id`, `kind`) for standalone option * values. */ where?: (accessor: NodeAccessor) => Predicate; /** Rows to skip after ranking (rank-relative pagination). */ offset?: number; /** * Expand the searched kind to include its `subClassOf` descendants. * Vector legs search each declaring kind's storage and merge by score; * kinds that don't declare the embedding field are skipped (mirroring * the query builder). Requires a query-capable store. */ includeSubClasses?: boolean; }>; type FulltextSearchOptions = SearchScopeOptions & Readonly<{ /** The user-supplied query string. */ query: string; /** Max results. Required. */ limit: number; /** Query parser mode. Default: "websearch". */ mode?: FulltextQueryMode; /** * Language override for query parsing. Default: the kind's * declared language (the same config rows were indexed with), * which keeps the parsed tsquery a plan-time constant on * PostgreSQL so the GIN index can serve the match. */ language?: string; /** Minimum relevance score to include in results. */ minScore?: number; /** Return a highlighted snippet alongside each hit. */ includeSnippets?: boolean; }>; type HybridVectorOptions = Readonly<{ /** Field path of the embedding column on the node kind. */ fieldPath: string; /** Query embedding to compare against. */ queryEmbedding: readonly number[]; /** Distance metric. Default: "cosine". */ metric?: VectorMetric; /** How many candidates to retrieve from the vector side. Default: 4 * limit. */ k?: number; /** Minimum similarity to include (units depend on metric). */ minScore?: number; /** * HNSW search frontier for this query (pgvector `hnsw.ef_search`). * The vector side over-fetches `k` (default `4 * limit`) candidates; * `efSearch` must be `>= k` for the index to surface that many * neighbors, and ~2–4× `k` is the high-recall target. PostgreSQL * requires an HNSW index and a transaction-capable driver, refusing the * option with a typed error otherwise. Engines with no frontier knob at * all (sqlite-vec, libSQL DiskANN) refuse it too, rather than searching * as if it had not been passed. See `VectorSearchOptions.efSearch`. */ efSearch?: number; }>; /** * Options for the standalone `store.search.vector` path. Mirrors the * vector half of `HybridSearchOptions` but flattens it because the * standalone path doesn't fuse against fulltext. */ type VectorSearchOptions = SearchScopeOptions & Readonly<{ /** Field path of the embedding column on the node kind. */ fieldPath: string; /** Query embedding to compare against. */ queryEmbedding: readonly number[]; /** Max results. Required. */ limit: number; /** Distance metric. Default: "cosine". */ metric?: VectorMetric; /** Minimum similarity to include (units depend on metric). */ minScore?: number; /** * HNSW search frontier for this query (pgvector `hnsw.ef_search`). * Sizes the dynamic candidate list the index scan maintains — higher * trades latency for recall. The floor for the index to surface * `limit` neighbors is `efSearch >= limit`; ~2–4× is the high-recall * target on million-scale corpora. Lets a latency-sensitive * interactive path and a recall-sensitive batch path share one * connection pool, tuning per query rather than per session. * * PostgreSQL HNSW only: applied transaction-locally via `SET LOCAL`. * Everywhere else the option is REFUSED with a typed error naming the * state, never silently ignored — a non-HNSW slot or a driver that * cannot hold a transaction (for example `drizzle-orm/neon-http`) on * PostgreSQL, and any SQLite backend, since neither sqlite-vec's vec0 * KNN nor libSQL's `vector_top_k` has a per-search frontier to set. * `backend.capabilities.vector.searchFrontierTuning` states which you * have. Must be a positive integer; pgvector caps it at 1000. */ efSearch?: number; }>; type HybridFulltextOptions = Readonly<{ query: string; /** How many candidates to retrieve from the fulltext side. Default: 4 * limit. */ k?: number; mode?: FulltextQueryMode; language?: string; minScore?: number; includeSnippets?: boolean; }>; type HybridSearchOptions = SearchScopeOptions & Readonly<{ vector: HybridVectorOptions; fulltext: HybridFulltextOptions; fusion?: HybridFusionOptions; /** Final number of fused results to return. Required. */ limit: number; }>; /** * StoreSearch — store.search facade. * * Groups fulltext, vector, hybrid, and maintenance operations under one * namespace so the top-level Store API stays focused on CRUD + graph * traversal. The methods delegate to their respective execution * modules; this class exists to shape the surface and to gate kind * names through the registry — the kind argument is `string` so * graph-extension kinds (added via `store.evolve()`) work without a * type cast, with a runtime guard rejecting misspellings at the call * site. */ /** * Resolves the hit's `node` type. Compile-time kinds keep their * narrowed `Node`; kinds outside `G` (added via graph extension through * `store.evolve()`, or string variables the type system can't see) * widen to the base `Node` so callers don't need a cast. * * This is the same shape as `getNodeCollection` — the dynamic form * works for any registered kind, and the type narrows when (and only * when) the literal is statically known. */ type ResolveNode = K extends NodeKinds ? G["nodes"][K] extends NodeRegistration ? Node : Node : Node; /** * The registered `NodeType` behind a kind literal — the accessor-level * companion of {@link ResolveNode}, used to type `where` predicates. * Falls back to the base `NodeType` for dynamic (string) kinds. */ type ResolveNodeType = K extends NodeKinds ? G["nodes"][K] extends NodeRegistration ? N : NodeType : NodeType; type StoreSearchContext = Readonly<{ graphId: string; backend: GraphBackend; registry: KindRegistry; createQuery?: () => QueryBuilder; /** * The threaded `batchPointRead` verdict, resolved once per facade. Optional * at this boundary for the same reason `search.ts`'s own * `StoreSearchContext` is: a contravariant position, so a new required * member would be a breaking change. The constructor resolves it when * legacy callers omit it; `store.ts`'s `search` getter threads its * already-resolved verdict through. */ batchPointRead?: BundleVerdictOf | undefined; }>; /** * Search-related operations exposed via `store.search`. * * @example * ```typescript * // Fulltext only * const hits = await store.search.fulltext("Document", { * query: "climate change", * limit: 10, * includeSnippets: true, * }); * * // Vector only — for extension kinds with embedding() modifiers, * // the auto-derived index serves this query. * const nearest = await store.search.vector("Document", { * fieldPath: "embedding", * queryEmbedding: vec, * limit: 10, * }); * * // Hybrid: vector + fulltext, fused with RRF * const ranked = await store.search.hybrid("Document", { * limit: 10, * vector: { fieldPath: "embedding", queryEmbedding: vec }, * fulltext: { query: "climate change" }, * }); * * // Rebuild after backfill / schema change * const stats = await store.search.rebuildFulltext(); * ``` * * Extension kinds added via `store.evolve(...)` work with all four * methods without a type cast — the kind argument is `string` and a * registry check rejects misspellings at the call site. */ declare class StoreSearch { #private; constructor(context: StoreSearchContext); /** * Runs a fulltext search against nodes of the given kind. * * Requires fields on the node schema declared with `searchable()`. * The search hits the backend's fulltext index (tsvector + GIN on * Postgres, FTS5 on SQLite — or whatever strategy the backend is * configured with) and resolves the matching node IDs back to typed * `Node` objects. */ fulltext(nodeKind: K, options: FulltextSearchOptions>): Promise>[]>; /** * Runs a vector similarity search against nodes of the given kind. * * Requires a field on the node schema declared with `embedding()`, * either at compile time or via a graph extension (the auto-derived * `VectorIndexDeclaration` flows through `materializeIndexes()` on * the same path either way). * * Pure vector — no fulltext leg, no fusion. For combined * vector+fulltext ranking, use `hybrid`. */ vector(nodeKind: K, options: VectorSearchOptions>): Promise>[]>; /** * Runs a vector + fulltext hybrid search and fuses the results with * Reciprocal Rank Fusion. * * RRF is rank-based, so it composes well across heterogeneous score * scales (cosine similarity vs ts_rank_cd vs FTS5 BM25). Default * over-fetch is 4× `limit` from each source — tune via `vector.k` / * `fulltext.k` for higher-recall corpora. */ hybrid(nodeKind: K, options: HybridSearchOptions>): Promise>[]>; /** * Rebuilds the fulltext index from existing node data. * * Use when: * - A node kind gained a `searchable()` field after data was already * written and existing rows were never indexed. * - The fulltext table was dropped / truncated. * - `language` was changed on a `searchable()` field. * * Iterates nodes with keyset pagination (stable under shared * timestamps and light concurrent writes), transacts per page, skips * kinds with no searchable fields, and cleans up stale rows for * soft-deleted nodes. Corrupt or non-object `props` are counted in * `skipped` with their node IDs surfaced via `skippedIds` so operators * can investigate. Concurrent hard-deletes between page fetches can * be missed by a single pass — run during a maintenance window for * full consistency. */ rebuildFulltext(nodeKind?: K, options?: RebuildFulltextOptions): Promise; } /** * The temporal coordinate a {@link StoreView} pins. A discriminated union on * `mode`: `asOf` is *required* for `"asOf"` and *rejected* (`never`) for every * other mode, so the type mirrors the runtime contract. `view({ mode: "asOf" })` * (missing timestamp) and `view({ mode: "current", asOf })` (pinning an instant * outside `"asOf"`) are both compile errors, not merely runtime * `ValidationError`s. */ type StoreViewCoordinate = Readonly<{ mode: "asOf"; asOf: string; }> | Readonly<{ mode: Exclude; asOf?: never; }>; /** * {@link Store.subgraph} options with the temporal axis removed — the * view's pinned coordinate supplies it. */ type StoreViewSubgraphOptions, NK extends NodeKinds, P extends SubgraphProject | undefined = undefined> = Omit, "temporalMode" | "asOf" | "recordedAsOf">; /** `reachable` options with the temporal axis removed (the pin supplies it). */ type StoreViewReachableOptions = Omit, keyof TemporalAlgorithmOptions>; /** `shortestPath` options with the temporal axis removed (the pin supplies it). */ type StoreViewShortestPathOptions = Omit, keyof TemporalAlgorithmOptions>; /** * `weightedShortestPath` options with the temporal axis removed (the pin * supplies it). */ type StoreViewWeightedShortestPathOptions = Omit, keyof TemporalAlgorithmOptions>; /** `canReach` options with the temporal axis removed (the pin supplies it). */ type StoreViewCanReachOptions = Omit, keyof TemporalAlgorithmOptions>; /** `neighbors` options with the temporal axis removed (the pin supplies it). */ type StoreViewNeighborsOptions = Omit, keyof TemporalAlgorithmOptions>; /** `degree` options with the temporal axis removed (the pin supplies it). */ type StoreViewDegreeOptions = Omit, keyof TemporalAlgorithmOptions>; /** WCC options with the temporal axis removed (the view's pin supplies it). */ type StoreViewWeaklyConnectedComponentsOptions = Omit, keyof TemporalAlgorithmOptions>; /** Label-propagation options pinned to the view's temporal coordinate. */ type StoreViewLabelPropagationOptions = Omit, keyof TemporalAlgorithmOptions>; /** PageRank options with the temporal axis removed (the view supplies it). */ type StoreViewPageRankOptions = Omit, keyof TemporalAlgorithmOptions>; /** Personalized PageRank options pinned to the view's temporal coordinate. */ type StoreViewPersonalizedPageRankOptions = Omit, keyof TemporalAlgorithmOptions>; /** Graph-algorithm facade sealed to a {@link StoreView}'s coordinate. */ type StoreViewGraphAlgorithms = Readonly<{ shortestPath: (from: NodeIdentifier, to: NodeIdentifier, options: StoreViewShortestPathOptions) => Promise; weightedShortestPath: (from: NodeIdentifier, to: NodeIdentifier, options: StoreViewWeightedShortestPathOptions) => Promise; reachable: (from: NodeIdentifier, options: StoreViewReachableOptions) => Promise; canReach: (from: NodeIdentifier, to: NodeIdentifier, options: StoreViewCanReachOptions) => Promise; neighbors: (node: NodeIdentifier, options: StoreViewNeighborsOptions) => Promise; degree: (node: NodeIdentifier, options?: StoreViewDegreeOptions) => Promise; labelPropagation: (options: StoreViewLabelPropagationOptions) => Promise; weaklyConnectedComponents: (options: StoreViewWeaklyConnectedComponentsOptions) => Promise; pageRank: (options: StoreViewPageRankOptions) => Promise; personalizedPageRank: (options: StoreViewPersonalizedPageRankOptions) => Promise; }>; /** * Shared base for the read-only views. Holds the pinned {@link ReadCoordinate} * and delegates the graph algorithms, `subgraph`, and `query` to the live store * with that coordinate flattened into each call. {@link StoreView} (valid-time) * and {@link RecordedStoreView} (recorded-time) extend it; only the surfaces * that genuinely differ — collections, search, and the coordinate-changing * helpers — live on the subclasses. */ declare abstract class CoordinatePinnedView { #private; protected readonly store: Store; protected readonly coordinate: ReadCoordinate; constructor(store: Store, coordinate: ReadCoordinate); /** The temporal mode this view reads in. */ get mode(): TemporalMode; /** The pinned valid-time `asOf` timestamp, or `undefined` for other modes. */ get asOf(): string | undefined; /** * A query builder pinned to this view's coordinate. The temporal axis is * sealed: calling `.temporal(...)` on the returned builder throws, so the * view's coordinate cannot be overridden on a per-query basis. */ query(): InitialQueryBuilder; protected internalAlgorithms(): InternalGraphAlgorithms; /** Graph algorithms pinned to this view's immutable temporal coordinate. */ get algorithms(): StoreViewGraphAlgorithms; /** Extracts a subgraph at this view's pinned coordinate. */ subgraph, const NK extends NodeKinds = NodeKinds, const P extends SubgraphProject | undefined = undefined>(rootId: NodeId>, options: StoreViewSubgraphOptions): Promise>; /** Shortest path between two nodes at this view's pinned coordinate. */ shortestPath(from: NodeIdentifier, to: NodeIdentifier, options: StoreViewShortestPathOptions): Promise; /** Minimum-total-weight path at this view's pinned coordinate. */ weightedShortestPath(from: NodeIdentifier, to: NodeIdentifier, options: StoreViewWeightedShortestPathOptions): Promise; /** Nodes reachable from `from` at this view's pinned coordinate. */ reachable(from: NodeIdentifier, options: StoreViewReachableOptions): Promise; /** Whether `to` is reachable from `from` at this view's pinned coordinate. */ canReach(from: NodeIdentifier, to: NodeIdentifier, options: StoreViewCanReachOptions): Promise; /** The k-hop neighborhood of `node` at this view's pinned coordinate. */ neighbors(node: NodeIdentifier, options: StoreViewNeighborsOptions): Promise; /** Counts active edges incident to `node` at this view's pinned coordinate. */ degree(node: NodeIdentifier, options?: StoreViewDegreeOptions): Promise; /** Deterministic label-propagation memberships at this view's coordinate. */ labelPropagation(options: StoreViewLabelPropagationOptions): Promise; /** Exact WCC memberships at this view's pinned coordinate. */ weaklyConnectedComponents(options: StoreViewWeaklyConnectedComponentsOptions): Promise; /** Global PageRank scores at this view's pinned coordinate. */ pageRank(options: StoreViewPageRankOptions): Promise; /** Personalized PageRank scores at this view's pinned coordinate. */ personalizedPageRank(options: StoreViewPersonalizedPageRankOptions): Promise; } /** The runtime half of {@link StoreView}; see that alias for the contract. */ declare class StoreViewImplementation extends CoordinatePinnedView { #private; constructor(store: Store, coordinate: StoreViewCoordinate | ReadCoordinate); /** Dynamic endpoint reads remain bound to this view's coordinate. */ getEdgeCollection>(kind: K): DynamicStoreViewEdgeCollection | undefined; getEdgeCollection(kind: string): DynamicStoreViewEdgeCollection | undefined; /** Adds a recorded-time pin, returning the narrow reconstructing view. */ asOfRecorded(recordedAsOf: RecordedInstant): RecordedStoreView; /** Read-only node collections pinned to this view's coordinate. */ get nodes(): StoreViewNodeCollections; /** Read-only edge collections pinned to this view's coordinate. */ get edges(): StoreViewEdgeCollections; /** Heterogeneous multi-kind edge read pinned to this view's coordinate. */ bulkFindEdgesFrom>(params: BulkFindEdgesFromParams, options?: Omit): Promise[]>; /** Inbound multi-kind edge read pinned to this view's coordinate. */ bulkFindEdgesTo>(params: BulkFindEdgesToParams, options?: Omit): Promise[]>; /** * Read-only search facade. On a `current` view the read methods * (`fulltext` / `vector` / `hybrid`) delegate to the live `store.search`, * while the mutating `rebuildFulltext` is refused — the view is read-only. * On any non-`current` pin every search method refuses: the fulltext / * vector index reflects current state only, so historical relevance is * out of scope. */ get search(): StoreSearch; } /** * A read-only `(mode, asOf)` lens over a {@link Store}. Construct one via * {@link Store.asOf} (valid-time) or {@link Store.view} (any public * mode), never directly. * * Carries `identity` — pinned identity reads — only when the graph declared * `identity: { ... }`. That conditional presence is why this is a type alias * over an implementation class plus {@link ViewIdentityAccess} rather than a * class declaration, which would put the member on every graph's view. * `instanceof StoreView` still works. * * @example * ```typescript * const past = store.asOf("2026-01-01T00:00:00.000Z"); * const alice = await past.nodes.Person.getById(aliceId); * const jobs = await past.edges.worksAt.findFrom(alice!); * const reach = await past.reachable(alice!, { edges: ["knows"] }); * ``` */ type StoreView = StoreViewImplementation & ViewIdentityAccess; declare const StoreView: new (store: Store, coordinate: StoreViewCoordinate | ReadCoordinate) => StoreView; /** * The runtime half of {@link RecordedStoreView}; see that alias for the * contract. */ declare class RecordedStoreViewImplementation extends CoordinatePinnedView { #private; constructor(store: Store, coordinate: ReadCoordinate); /** The recorded/system-time anchor this view reconstructs. */ get asOfRecorded(): RecordedInstant; /** Recorded-time node reconstructing-read collections. */ get nodes(): RecordedStoreViewNodeCollections; /** Recorded-time edge reconstructing-read collections. */ get edges(): RecordedStoreViewEdgeCollections; } /** * A narrow recorded-time read lens. It preserves the valid-time coordinate * carried by the source view and adds a recorded/system-time pin. Collection * reads expose point reconstruction and bounded scans; broad collection * predicates, endpoint reads, search, and further coordinate changes are absent * from the typed surface and refused by the runtime proxies for JS callers. * * Like {@link StoreView}, an alias over an implementation class so `identity` * is present only for identity-enabled graphs. */ type RecordedStoreView = RecordedStoreViewImplementation & ViewIdentityAccess; declare const RecordedStoreView: new (store: Store, coordinate: ReadCoordinate) => RecordedStoreView; /** * Main Store implementation for TypeGraph. * * The Store is the primary interface for interacting with a TypeGraph. * It coordinates: * - Node and edge CRUD operations * - Constraint validation * - Schema management * - Transaction handling */ /** * The Store provides typed access to a TypeGraph database. * * @example * ```typescript * const store = createStore(myGraph, backend); * * // Create nodes using collection API * const person = await store.nodes.Person.create({ * name: "Alice", * email: "alice@example.com", * }); * * const company = await store.nodes.Company.create({ * name: "Acme", * industry: "Technology", * }); * * // Create edges * await store.edges.worksAt.create( * { kind: "Person", id: person.id }, * { kind: "Company", id: company.id }, * { role: "Engineer" } * ); * * // Query with the fluent API * const results = await store.query() * .from("Person", "p") * .whereNode("p", (p) => p.name.eq("Alice")) * .select((ctx) => ctx.p) * .execute(); * ``` */ /** * Optional embedder for {@link Store.reembedVectorField}. Receives a batch of * the kind's nodes and returns a map of `nodeId → new embedding vector` (omit * an id to leave that node without an embedding). Called once per page; the * page size is `batchSize`. */ type ReembedFunction = (nodes: readonly Node[]) => Promise> | ReadonlyMap; /** Options for {@link Store.reembedVectorField}. */ type ReembedVectorFieldOptions = Readonly<{ /** * When supplied, drives a batched re-embed loop after recreating storage: * pages the kind's nodes, calls `embed(batch)`, and upserts the returned * vectors. When omitted, storage is recreated empty and the caller * re-embeds via normal `update()` / `upsertEmbedding` writes. */ embed?: ReembedFunction; /** Re-embed page size. Default 200. */ batchSize?: number; }>; /** Result of {@link Store.reembedVectorField}. */ type ReembedVectorFieldResult = Readonly<{ /** Whether the per-field storage was dropped and recreated. */ recreated: boolean; /** Number of nodes whose embedding was re-written (0 without `embed`). */ reembedded: number; }>; /** Options for {@link Store.rebuildContribution}. */ type RebuildContributionOptions = Readonly<{ /** * Page size for the content reconstruction pass. Default 500. * * Every page runs inside the rebuild's single transaction, so this * trades statement count against per-statement size rather than * bounding how long the transaction is held — that is set by the graph. */ pageSize?: number; }>; /** * The identity surface a store carries, present only when the graph declared * `identity: { ... }`. Conditional *presence* (not a `never`-typed property) is * the encoding used everywhere identity is exposed — `tx.identity` and the * read-only views included — so an identity-disabled graph simply has no * `identity` member to reach for. */ type StoreIdentityAccess = G["identity"] extends GraphIdentityConfig ? Readonly<{ identity: IdentityFacade; }> : Readonly>; /** The same conditional presence for the read-only pinned views. */ type ViewIdentityAccess = G["identity"] extends GraphIdentityConfig ? Readonly<{ identity: IdentityReadFacade; }> : Readonly>; type NodeCollectionLookup = (kind: K) => DynamicNodeCollection | undefined; interface RequiredNodeCollectionLookup { (token: T): RuntimeNodeCollection; (kind: K): DynamicNodeCollection; } interface EdgeCollectionLookup { >(kind: K): DynamicEdgeCollection | undefined; (kind: string): DynamicEdgeCollection | undefined; } interface RequiredEdgeCollectionLookup { (token: T): RuntimeEdgeCollection; >(kind: K): DynamicEdgeCollection; (kind: string): DynamicEdgeCollection; } type CheckedReadScopeBoundary = Readonly<{ query?: () => InitialQueryBuilder; }>; /** Read scope whose fluent-query executions all verify one schema version. */ type CheckedReadScope = CheckedReadScopeBoundary & Required, "query">>; type StoreCore = Readonly<{ [STORE_RUNTIME]: StoreRuntime; graph: G; graphId: string; capabilities: BackendCapabilities; registry: KindRegistry; historyEnabled: boolean; revisionTrackingEnabled: boolean; revisionSchema: SqlSchema; recordedReadBound: boolean; recordedTimeOwnership: RecordedTimeOwnership; workingCopyOptions: WorkingCopyOptions; nodes: GraphNodeCollections; edges: GraphEdgeCollections; algorithms: GraphAlgorithms; search: StoreSearch; runtimeNodeKind: (kind: K, definition: D) => RuntimeNodeKind>; runtimeEdgeKind: (kind: K, definition: D) => RuntimeEdgeKind>>; getNodeCollection: NodeCollectionLookup; getNodeCollectionOrThrow: RequiredNodeCollectionLookup; getEdgeCollection: EdgeCollectionLookup; getEdgeCollectionOrThrow: RequiredEdgeCollectionLookup; getNodePropsSchema: (kind: string) => z.ZodObject | undefined; getNodePropsSchemaOrThrow: (kind: string) => z.ZodObject; getEdgePropsSchema: (kind: string) => z.ZodObject | undefined; getEdgePropsSchemaOrThrow: (kind: string) => z.ZodObject; introspect: () => SchemaIntrospection; /** Describe the schema and current population with bounded aggregate statements. */ describe: () => Promise; /** Page declared-schema violations without treating undeclared fields as invalid. */ validateStore: (options: ValidateStoreOptions) => Promise; schemaChanges: () => Promise; requiresMigration: () => Promise; query: () => InitialQueryBuilder; withCheckedReads?: (expectedSchemaVersion: number | undefined, fn: (reads: CheckedReadScope) => Promise) => Promise; asOf: (asOf: string) => StoreView; asOfRecorded: (recordedAsOf: RecordedInstant) => RecordedStoreView; recordedNow: () => Promise; revisionNow: () => Promise; revisionOriginNow: () => Promise; view: (coordinate: StoreViewCoordinate) => StoreView; snapshot: () => StoreView; batch: , BatchableQuery, ...BatchableQuery[] ]>(...queries: Queries) => Promise>; batchOnce?: (build: (read: BatchReadBuilder) => Queries, options?: BatchOnceOptions) => Promise>; neighbors?: >(source: GraphNodeReference, options: NeighborReadOptions) => Promise[]>; countNeighbors?: >(source: GraphNodeReference, options: Omit, "limit" | "orderBy">) => Promise; bulkFindEdgesFrom: >(params: BulkFindEdgesFromParams, options?: EdgeBulkFindEndpointOptions) => Promise[]>; bulkFindEdgesTo: >(params: BulkFindEdgesToParams, options?: EdgeBulkFindEndpointOptions) => Promise[]>; bulkFindRuntimeEdgesFrom: (params: BulkFindRuntimeEdgesFromParams, options?: EdgeBulkFindEndpointOptions) => Promise[]>; subgraph: , const NK extends NodeKinds = NodeKinds, const P extends SubgraphProject | undefined = undefined>(rootId: NodeId>, options: SubgraphOptions) => Promise>; clear: () => Promise; refreshStatistics: () => Promise; materializeIndexes: (options?: MaterializeIndexesOptions) => Promise; materializeSystemIndexes: (options?: MaterializeSystemIndexesOptions) => Promise; reembedVectorField: (kind: string, fieldPath: string, options?: ReembedVectorFieldOptions) => Promise; verifyContributions: () => Promise; verifyConstraintFences: () => Promise; repairContributions: () => Promise; probeContributions: () => Promise; rebuildContribution: (scope: ContributionRebuildScope, options?: RebuildContributionOptions) => Promise; materializeRemovals: (options?: MaterializeRemovalsOptions) => Promise; close: () => Promise; }> & StoreIdentityAccess; /** * Options for {@link Store.transaction} and {@link Store.transactionWithReceipt}. * `retry` is TypeGraph's own option: it is read here and never forwarded to * the backend, which only ever sees the {@link TransactionOptions} fields. * * Without `retry`, the callback runs once; a transaction conflict the backend * reports (a serialization failure or deadlock) surfaces as * `TransactionConflictError` with `attempts: 1` instead of the raw driver * error. With `retry`, such a conflict re-runs the WHOLE callback from the * top — up to `attempts` times total — so the callback must tolerate being * invoked more than once: await all of its own work before returning, read * and write only values it creates fresh on each call (never something a * previous, rolled-back try left in memory), and perform no effect outside * its own transaction. Every hook the callback's operations fire carries the * 1-based attempt number that produced it; a rolled-back attempt's completed * operations report no `onOperationEnd` and no `onError` of their own — * only the attempt that actually commits (or the last one, once `attempts` * is exhausted) is reported. */ type StoreTransactionOptions = TransactionOptions & Readonly<{ /** * Opts the callback into replay on a transaction conflict. The total * number of tries, including the first. Exhausting it throws * `TransactionConflictError`. */ retry?: Readonly<{ attempts: number; }>; }>; type StoreTransactions = Readonly<{ transaction: (fn: (tx: TransactionContext) => Promise, options?: StoreTransactionOptions) => Promise; transactionWithReceipt: (fn: (tx: MeasurableTransactionContext) => Promise, options?: StoreTransactionOptions) => Promise>; }>; /** Builds cold reads that {@link Store.batchOnce} can compose in one statement. */ type BatchReadBuilder = Readonly<{ neighbors: >(source: GraphNodeReference, options: NeighborReadOptions) => CompiledOneStatementRead[]>; countNeighbors: >(source: GraphNodeReference, options: Omit, "limit" | "orderBy">) => CompiledOneStatementRead; subgraph: , const NK extends NodeKinds = NodeKinds, const P extends SubgraphProject | undefined = undefined>(rootId: NodeId>, options: SubgraphOptions) => CompiledOneStatementRead>; }>; type AddedStoreReadsBoundary = Readonly<{ withCheckedReads?: (expectedSchemaVersion: number | undefined, fn: (reads: CheckedReadScope) => Promise) => Promise; batchOnce?: (build: (read: BatchReadBuilder) => Queries, options?: BatchOnceOptions) => Promise>; neighbors?: >(source: GraphNodeReference, options: NeighborReadOptions) => Promise[]>; countNeighbors?: >(source: GraphNodeReference, options: Omit, "limit" | "orderBy">) => Promise; }>; type AddedStoreReadKey = keyof AddedStoreReadsBoundary; type AddedStoreReads = AddedStoreReadsBoundary & Required, AddedStoreReadKey>>; type ResolvedStoreCore = StoreCore & AddedStoreReads; /** Schema planning, reconciliation, and lifecycle operations shared by Stores. */ interface StoreEvolution> { /** * Prepares an immutable plan without acquiring a schema write fence. * The default source reads the active schema; cached planning reuses this Store's snapshot. * Apply the module-issued token through a compatible AdapterStore's withEvolvedTransaction(). */ readonly planEvolution: (extension: GraphExtension, options?: PlanEvolutionOptions) => Promise; /** * Reconciles this Store with committed schema metadata without writes or provisioning. * Call after outer commit. A cache matching minVersion skips SQL; otherwise a read * accepts that version or newer. Updates ref when supplied and returns the reconciled Store. */ readonly refreshSchema: = TStore>(options?: RefreshSchemaOptions) => Promise; /** Commits an extension in a TypeGraph-owned transaction and returns the evolved Store. */ readonly evolve: = TStore>(extension: GraphExtension, options?: Readonly<{ ref?: TStore extends TRefStore ? StoreRef : never; eager?: MaterializeIndexesOptions; }>) => Promise; /** Marks kinds deprecated for introspection without restricting reads or writes; returns the updated Store. */ readonly deprecateKinds: = TStore>(names: readonly string[], options?: Readonly<{ ref?: TStore extends TRefStore ? StoreRef : never; }>) => Promise; /** Clears kind deprecation markers and returns the updated Store. */ readonly undeprecateKinds: = TStore>(names: readonly string[], options?: Readonly<{ ref?: TStore extends TRefStore ? StoreRef : never; }>) => Promise; /** Removes runtime kinds from the schema, queues physical cleanup, and returns the updated Store. */ readonly removeKinds: = TStore>(names: readonly string[], options?: Readonly<{ ref?: TStore extends TRefStore ? StoreRef : never; eager?: MaterializeRemovalsOptions; }>) => Promise; } /** * The default TypeGraph Store contract. It contains the complete graph API and * graph-owned transactions while keeping adapter-native handles, backend * internals, and caller-owned transaction adoption out of the public surface. */ type Store = ResolvedStoreCore & StoreTransactions & StoreEvolution>; /** * An opaque, in-memory snapshot of a store's reconciled schema: the merged * compile-time + runtime-committed graph plus the committed schema version and * hash it reflects. Produced by {@link AdapterStore.reconciledSchema} after a * verified open, cached once per isolate/process, and handed to * {@link createAdapterStore} via `{ reconciled }` (or used implicitly by * {@link AdapterStore.withBackend}) to build request-scoped stores against * fresh connections with **zero** database round-trips. Reads and writes are * validated against the reconciled graph, so runtime-committed kinds seen at * the reconcile are honored without re-querying. * * Treat it as opaque and immutable — do not construct or mutate it. Its * `version` is the value to compare against {@link getCommittedSchemaVersion} * to detect a schema commit from another process and refresh the snapshot. */ type ReconciledSchema = Readonly<{ graph: G; version: number | undefined; hash: string | undefined; }>; /** Construction option carrying a cached {@link ReconciledSchema}. */ type ReconciledOption = Readonly<{ reconciled?: ReconciledSchema; }>; /** * The reconciliation surface shared by every adapter store flavor: read the * cached {@link ReconciledSchema} and rebind to a fresh connection with no * verify round-trip. `Self` is the receiver's own surface so `withBackend` * preserves the store flavor (live / history / recorded-read). * * Declared as an `interface` (not a `type`): the surface aliases intersect it * with `Self` bound to themselves, which a `type` alias rejects as a circular * self-reference (TS2456) but an interface resolves lazily — the same idiom as * {@link StoreEvolution}. */ interface AdapterStoreReconciliation { /** * An opaque snapshot of this store's reconciled schema. Cache it after a * verified open and pass it to {@link createAdapterStore} `{ reconciled }` * to build per-request stores with zero database round-trips. */ readonly reconciledSchema: ReconciledSchema; /** * Build an equivalent store bound to a fresh backend/connection, reusing * this store's already-reconciled schema — no verify round-trip. The * per-request primitive for serverless deployments that open a new * connection per request; the returned store validates writes against the * same reconciled graph as the receiver. */ readonly withBackend: (backend: AdapterBackend) => Self; } /** * A Store with explicit adapter interoperability. Adapter entrypoints return * this surface so native transaction handles remain precisely typed without * leaking into the default Store contract. */ type AdapterStore = ResolvedStoreCore & StoreEvolution> & AdapterStoreTransactions & AdapterStoreReconciliation> & Readonly<{ backend: GraphBackend; }>; type AdapterStoreTransactions = Readonly<{ transaction: (fn: (tx: AdapterTransactionContext) => Promise, options?: StoreTransactionOptions) => Promise; transactionWithReceipt: (fn: (tx: MeasurableAdapterTransactionContext) => Promise, options?: StoreTransactionOptions) => Promise>; withTransaction: (externalTransaction: TNativeTransaction) => AdapterTransactionContext; withEvolvedTransaction: (externalTransaction: TNativeTransaction, plan: EvolutionPlan, fn: (tx: MeasurableAdapterTransactionContext) => Promise, options?: EvolvedTransactionOptions) => Promise>; withRecordedTransaction: (externalTransaction: TNativeTransaction, fn: (tx: MeasurableAdapterTransactionContext) => Promise) => Promise>; }>; type AdapterHistoryTransactionContext = Omit, "sql" | "sqlAvailability"> & RecordedRevisionRequest & RecordedHeterogeneousNodeWriteBatch & Readonly<{ sqlAvailability: "history"; }>; /** * The {@link AdapterHistoryTransactionContext} handed to a `withRecordedTransaction` * callback, extended with {@link ScopedMeasure}. Its `measure` scopes to a child * {@link MeasurableAdapterHistoryTransactionContext} (so nested scopes keep the * history-safe absent-`sql` / backend typing). Assignable to * {@link MeasurableTransactionContext} (and hence to {@link TransactionContext}), * so a projector helper typed against either still accepts it. */ type MeasurableAdapterHistoryTransactionContext = AdapterHistoryTransactionContext & Readonly<{ measure: ScopedMeasure>; }>; type AdapterRecordedReadStore = ResolvedStoreCore & StoreEvolution> & AdapterStoreTransactions & AdapterStoreReconciliation> & Readonly<{ backend: GraphBackend; recordedReadBound: true; }>; type RecordedReadStore = ResolvedStoreCore & StoreTransactions & StoreEvolution> & Readonly<{ recordedReadBound: true; }>; type HistoryStore = ResolvedStoreCore & StoreEvolution> & Readonly<{ transaction: (fn: (tx: HistoryTransactionContext) => Promise, options?: StoreTransactionOptions) => Promise; transactionWithReceipt: (fn: (tx: MeasurableHistoryTransactionContext) => Promise, options?: StoreTransactionOptions) => Promise>; historyEnabled: true; recordedReadBound: true; }>; type AdapterHistoryStore = ResolvedStoreCore & StoreEvolution> & AdapterHistoryStoreTransactions & AdapterStoreReconciliation> & Readonly<{ backend: HistoryStoreBackend; historyEnabled: true; recordedReadBound: true; }>; type AdapterHistoryStoreTransactions = Readonly<{ transaction: (fn: (tx: AdapterHistoryTransactionContext) => Promise, options?: StoreTransactionOptions) => Promise; transactionWithReceipt: (fn: (tx: MeasurableAdapterHistoryTransactionContext) => Promise, options?: StoreTransactionOptions) => Promise>; withEvolvedTransaction: (externalTransaction: TNativeTransaction, plan: EvolutionPlan, fn: (tx: MeasurableAdapterHistoryTransactionContext) => Promise, options?: EvolvedTransactionOptions) => Promise>; withRecordedTransaction: (externalTransaction: TNativeTransaction, fn: (tx: MeasurableAdapterHistoryTransactionContext) => Promise) => Promise>; }>; /** * Creates a new Store instance. * * @param graph - The graph definition * @param backend - The database backend * @param options - Optional store configuration including observability hooks * @returns A new Store instance * * @example * ```typescript * // Basic usage * const store = createStore(graph, backend); * * // With observability hooks * const store = createStore(graph, backend, { * hooks: { * onOperationStart: (ctx) => { * console.log(`Starting ${ctx.operation} on ${ctx.entity}:${ctx.kind}`); * }, * onOperationEnd: (ctx, result) => { * console.log(`Completed in ${result.durationMs}ms`); * }, * onError: (ctx, error) => { * console.error(`Operation ${ctx.operationId} failed:`, error); * }, * }, * }); * ``` */ declare function createStore(graph: G, backend: GraphBackend, options: HistoryStoreOptions): HistoryStore; declare function createStore(graph: G, backend: GraphBackend, options: RecordedReadStoreOptions): RecordedReadStore; declare function createStore(graph: G, backend: GraphBackend, options?: UnboundLiveStoreOptions): Store; declare function createStore(graph: G, backend: GraphBackend, options: LiveStoreOptions | undefined): Store | RecordedReadStore; declare function createStore(graph: G, backend: GraphBackend, options: StoreOptions | undefined): Store | HistoryStore | RecordedReadStore; declare function createAdapterStore(graph: G, backend: AdapterBackend, options: HistoryStoreOptions & ReconciledOption): AdapterHistoryStore; declare function createAdapterStore(graph: G, backend: AdapterBackend, options: RecordedReadStoreOptions & ReconciledOption): AdapterRecordedReadStore; declare function createAdapterStore(graph: G, backend: AdapterBackend, options?: UnboundLiveStoreOptions & ReconciledOption): AdapterStore; declare function createAdapterStore(graph: G, backend: AdapterBackend, options: (LiveStoreOptions & ReconciledOption) | undefined): AdapterStore | AdapterRecordedReadStore; declare function createAdapterStore(graph: G, backend: AdapterBackend, options: (StoreOptions & ReconciledOption) | undefined): AdapterStore | AdapterHistoryStore | AdapterRecordedReadStore; /** * Creates a store and ensures the schema is initialized/migrated. * * This is the recommended way to create a store in production. * It automatically: * - Creates base tables on a fresh database (if the backend supports bootstrapTables) * - Initializes the schema on first run (version 1) * - Auto-migrates safe changes (additive changes) * - Throws MigrationError for breaking changes * - Fences managed writes against concurrent schema-version commits; a stale Store * fails before writing instead of landing rows against a replaced schema * (official transactional backends only; unsupported custom backends fail * closed on their first managed write) * * @param graph - The graph definition * @param backend - The database backend * @param options - Store and schema options * @returns A tuple of [store, validationResult] * * @example * ```typescript * const [store, result] = await createStoreWithSchema(graph, backend); * * if (result.status === "initialized") { * console.log("Schema initialized at version", result.version); * } else if (result.status === "migrated") { * console.log(`Migrated from v${result.fromVersion} to v${result.toVersion}`); * } else if (result.status === "pending") { * console.log(`Safe changes pending at version ${result.version}`); * } * ``` */ declare function createStoreWithSchema(graph: G, backend: GraphBackend, options: HistoryStoreOptions & SchemaManagerOptions): Promise<[HistoryStore, SchemaValidationResult]>; declare function createStoreWithSchema(graph: G, backend: GraphBackend, options: RecordedReadStoreOptions & SchemaManagerOptions): Promise<[RecordedReadStore, SchemaValidationResult]>; declare function createStoreWithSchema(graph: G, backend: GraphBackend, options?: UnboundLiveStoreOptions & SchemaManagerOptions): Promise<[Store, SchemaValidationResult]>; declare function createStoreWithSchema(graph: G, backend: GraphBackend, options: (LiveStoreOptions & SchemaManagerOptions) | undefined): Promise<[Store | RecordedReadStore, SchemaValidationResult]>; declare function createStoreWithSchema(graph: G, backend: GraphBackend, options: (StoreOptions & SchemaManagerOptions) | undefined): Promise<[ Store | HistoryStore | RecordedReadStore, SchemaValidationResult ]>; declare function createAdapterStoreWithSchema(graph: G, backend: AdapterBackend, options: HistoryStoreOptions & SchemaManagerOptions): Promise<[ AdapterHistoryStore, SchemaValidationResult ]>; declare function createAdapterStoreWithSchema(graph: G, backend: AdapterBackend, options: RecordedReadStoreOptions & SchemaManagerOptions): Promise<[ AdapterRecordedReadStore, SchemaValidationResult ]>; declare function createAdapterStoreWithSchema(graph: G, backend: AdapterBackend, options?: UnboundLiveStoreOptions & SchemaManagerOptions): Promise<[AdapterStore, SchemaValidationResult]>; declare function createAdapterStoreWithSchema(graph: G, backend: AdapterBackend, options: (LiveStoreOptions & SchemaManagerOptions) | undefined): Promise<[ (AdapterStore | AdapterRecordedReadStore), SchemaValidationResult ]>; declare function createAdapterStoreWithSchema(graph: G, backend: AdapterBackend, options: (StoreOptions & SchemaManagerOptions) | undefined): Promise<[ (AdapterStore | AdapterHistoryStore | AdapterRecordedReadStore), SchemaValidationResult ]>; /** * Creates a Store after **verifying** that the database is at the same * schema version as the code graph — without running any DDL, bootstrap, * or marker writes. The runtime counterpart of `createStoreWithSchema` * for the deployment model in "Database roles & least privilege": * * - **`createStoreWithSchema(graph, backend)`** runs DDL (bootstrap, * safe auto-migrations, durable contribution materialization). Run it * once at startup under a privileged role that holds `CREATE` / DDL. * - **`createVerifiedStore(graph, backend)`** is the zero-DDL runtime * attach with a verification gate. Throws `MigrationError` when the * persisted schema is behind the code graph (any pending change, safe * or breaking), `ConfigurationError` when no schema has been * initialized, or `StoreNotInitializedError` when the schema is * current but the runtime-contribution markers are missing/stale. * The runtime can use a least-privilege, DML-only database role. * Managed writes are fenced against concurrent schema-version commits just like * `createStoreWithSchema`; a stale verified Store fails before writing. * Runtime backends must support transactions and the schema-write fence; * unsupported custom or HTTP backends attach for reads but fail closed on a * managed write. * - **`createStore(graph, backend)`** is the same zero-DDL attach * *without* the verification gate — fastest, but schema drift goes * undetected until a hot-path operation trips. * * Folds any persisted graph-extension document into the supplied graph * before building the Store, just like `createStoreWithSchema`. * * @param graph - The graph definition * @param backend - The database backend * @param options - Optional store configuration * @returns A tuple of [store, validationResult] — `result.status` is * always `"unchanged"` on success * * @example * ```typescript * // Runtime — least-privilege, DML-only role. Zero DDL. * const [store, result] = await createVerifiedStore(graph, backend); * // result.status === "unchanged" — the privileged migrator is current. * ``` * * @throws ConfigurationError if no schema has been initialized. * @throws MigrationError if the persisted schema is behind the code graph. * @throws StoreNotInitializedError if runtime-contribution markers are * missing/stale/failed for this graph on this connection. */ declare function createVerifiedStore(graph: G, backend: GraphBackend, options: HistoryStoreOptions): Promise<[HistoryStore, SchemaValidationResult]>; declare function createVerifiedStore(graph: G, backend: GraphBackend, options: RecordedReadStoreOptions): Promise<[RecordedReadStore, SchemaValidationResult]>; declare function createVerifiedStore(graph: G, backend: GraphBackend, options?: UnboundLiveStoreOptions): Promise<[Store, SchemaValidationResult]>; declare function createVerifiedStore(graph: G, backend: GraphBackend, options: LiveStoreOptions | undefined): Promise<[Store | RecordedReadStore, SchemaValidationResult]>; declare function createVerifiedStore(graph: G, backend: GraphBackend, options: StoreOptions | undefined): Promise<[ Store | HistoryStore | RecordedReadStore, SchemaValidationResult ]>; declare function createVerifiedAdapterStore(graph: G, backend: AdapterBackend, options: HistoryStoreOptions): Promise<[ AdapterHistoryStore, SchemaValidationResult ]>; declare function createVerifiedAdapterStore(graph: G, backend: AdapterBackend, options: RecordedReadStoreOptions): Promise<[ AdapterRecordedReadStore, SchemaValidationResult ]>; declare function createVerifiedAdapterStore(graph: G, backend: AdapterBackend, options?: UnboundLiveStoreOptions): Promise<[AdapterStore, SchemaValidationResult]>; declare function createVerifiedAdapterStore(graph: G, backend: AdapterBackend, options: LiveStoreOptions | undefined): Promise<[ (AdapterStore | AdapterRecordedReadStore), SchemaValidationResult ]>; declare function createVerifiedAdapterStore(graph: G, backend: AdapterBackend, options: StoreOptions | undefined): Promise<[ (AdapterStore | AdapterHistoryStore | AdapterRecordedReadStore), SchemaValidationResult ]>; export { type AdapterHistoryTransactionContext as $, loadActiveSchemaWithBootstrap as A, type BatchReadBuilder as B, type CompiledOneStatementRead as C, loadAndMergeGraphExtensionDocument as D, type EvolutionPlan as E, migrateSchema as F, parseSerializedSchema as G, type HistoryStoreOptions as H, type IdentityAssertion as I, requiresMigration as J, KindRegistry as K, type LiveStoreOptions as L, type MigrateSchemaOptions as M, rollbackSchema as N, type AliasMap as O, type EmptyAliasMap as P, type EdgeAliasMap as Q, type RecordedReadStoreOptions as R, type StoreOptions as S, type EmptyEdgeAliasMap as T, type UnboundLiveStoreOptions as U, type RecursiveAliasMap as V, type QueryCoordinateState as W, QueryBuilder as X, type EmptyRecursiveAliasMap as Y, TraversalBuilder as Z, type AdapterHistoryStore as _, type SchemaManagerOptions as a, type ExecutableOneStatementRead as a$, type AdapterRecordedReadStore as a0, type AdapterStore as a1, type AdapterTransactionContext as a2, type AggregateResult as a3, type AlgorithmCyclePolicy as a4, type AnyEdge as a5, type AnyNode as a6, type BaseStoreOptions as a7, type BaseTraversalOptions as a8, type BatchOnceOptions as a9, type DynamicNode as aA, type DynamicNodeAccessor as aB, type DynamicNodeCollection as aC, type DynamicNodeKind as aD, type DynamicNodeReference as aE, type DynamicNodeType as aF, type DynamicSelectableEdge as aG, type DynamicSelectableNode as aH, type DynamicStoreViewEdgeCollection as aI, type Edge as aJ, type EdgeAccessor as aK, type EdgeBatchReads as aL, type EdgeBulkFindEndpointOptions as aM, type EdgeBulkFindOptions as aN, type EdgeCollection as aO, type EdgeCollectionLookup as aP, type EdgeFindByEndpointsOptions as aQ, type EdgeGetOrCreateByEndpointsOptions as aR, type EdgeGetOrCreateByEndpointsResult as aS, type EdgeIntrospection as aT, type EdgeReadWindow as aU, type EdgeTemporalReads as aV, type EdgeWrites as aW, type EmbeddableOneStatementRead as aX, type EvolvedTransactionOptions as aY, type EvolvedTransactionOutcome as aZ, ExecutableAggregateQuery as a_, type BatchResults as aa, type BatchableQuery as ab, type BulkEdgeSourceGroup as ac, type BulkFindEdgesFromParams as ad, type BulkFindEdgesFromResult as ae, type BulkFindEdgesToParams as af, type BulkFindEdgesToResult as ag, type BulkFindRuntimeEdgesFromParams as ah, type BulkFindRuntimeEdgesFromResult as ai, type BulkOperationHookContext as aj, type CheckedReadScope as ak, type ClaimOwner as al, type ClaimTarget as am, type CommonPropertyKeys as an, type CompareAndSetAbsent as ao, type CompareAndSetExpected as ap, type ConstraintFenceViolation as aq, type ConstraintNames as ar, type CreateEdgeInput as as, type CreateNodeInput as at, type DatabaseProjection as au, type DegreeOptions as av, type DynamicEdgeAccessor as aw, type DynamicEdgeCollection as ax, type DynamicEdgeType as ay, type DynamicFieldBuilder as az, type Store as b, type OneStatementBatchResults as b$, ExecutableProjectionQuery as b0, ExecutableQuery as b1, ExecutableRelationQuery as b2, type ExpressionAliasContext as b3, type ExpressionValue as b4, type FieldAccessor as b5, type FulltextSearchHit as b6, type FulltextSearchOptions as b7, type GetOrCreateAction as b8, type GraphAlgorithms as b9, type MeasurableAdapterHistoryTransactionContext as bA, type MeasurableAdapterTransactionContext as bB, type MeasurableHistoryTransactionContext as bC, type MeasurableTransactionContext as bD, type NeighborNodeOrderField as bE, type NeighborOrder as bF, type NeighborOrderField as bG, type NeighborReadOptions as bH, type NeighborResult as bI, type NeighborsOptions as bJ, type NoRecordedCoordinate as bK, type Node as bL, type NodeAccessor as bM, type NodeAlias as bN, type NodeBulkFindByIndexOptions as bO, type NodeCandidateQuery as bP, type NodeCandidateSelection as bQ, type NodeCollection as bR, type NodeCollectionLookup as bS, type NodeCurrentReads as bT, type NodeGetOrCreateByConstraintOptions as bU, type NodeGetOrCreateByConstraintResult as bV, type NodeIdentifier as bW, type NodePropsFor as bX, type NodeRef as bY, type NodeTemporalReads as bZ, type NodeWrites as b_, type GraphEdgeCollections as ba, type GraphEdgeForKinds as bb, type GraphNodeCollections as bc, type GraphNodeReference as bd, type HeterogeneousNodeUpsertInput as be, type HeterogeneousNodeUpsertResult as bf, type HistoryStoreBackend as bg, type HistoryTransactionContext as bh, type HookContext as bi, type HybridFulltextOptions as bj, type HybridSearchHit as bk, type HybridSearchOptions as bl, type HybridVectorOptions as bm, type IdentityAssertionWriteFacade as bn, type IdentityNodeReference as bo, type IdentityTraversalOption as bp, type IdentityValidityWindow as bq, type IfExistsMode as br, type InitialQueryBuilder as bs, type KindIntrospection as bt, type KindPopulationStatistics as bu, type LabelPropagationMembership as bv, type LabelPropagationOptions as bw, type MaterializeRemovalsEntry as bx, type MaterializeRemovalsOptions as by, type MaterializeRemovalsResult as bz, type HistoryStore as c, type SelectContext as c$, type OneStatementBatchableQuery as c0, type OntologyIntrospection as c1, type OperationHookContext as c2, type PageRankOptions as c3, type PageRankScore as c4, type PaginateOptions as c5, type PaginatedResult as c6, type PathNode as c7, type PersonalizedPageRankOptions as c8, type PersonalizedPageRankSeed as c9, RecordedStoreView as cA, type RecordedStoreViewEdgeCollection as cB, type RecordedStoreViewEdgeCollections as cC, type RecordedStoreViewNodeCollection as cD, type RecordedStoreViewNodeCollections as cE, type RecursiveTraversalOptions as cF, type ReembedFunction as cG, type ReembedVectorFieldOptions as cH, type ReembedVectorFieldResult as cI, type RefreshSchemaOptions as cJ, type RelationColumnContext as cK, type RelationProjection as cL, type RelationProjectionResult as cM, type RequiredEdgeCollectionLookup as cN, type RequiredNodeCollectionLookup as cO, type RuntimeBulkEdgeSourceGroup as cP, type RuntimeEdgeCollection as cQ, type RuntimeEdgeFor as cR, type RuntimeEdgeKind as cS, type RuntimeEdgeTypeFor as cT, type RuntimeNodeCollection as cU, type RuntimeNodeKind as cV, type RuntimeNodeReferenceFor as cW, type RuntimeNodeTypeFor as cX, type SchemaIntrospection as cY, type ScopedMeasure as cZ, type SearchScopeOptions as c_, type PlanEvolutionOptions as ca, type Predicate as cb, type PreparedBindings as cc, type PreparedParameterDeclaration as cd, PreparedQuery as ce, type ProjectionResult as cf, type PropertyPopulationStatistics as cg, type PropsAccessor as ch, type QualifiedRecursivePath as ci, type QualifiedRecursivePathEdge as cj, type QualifiedRecursivePathElement as ck, type QualifiedRecursivePathNode as cl, type QualifiedRecursivePathOption as cm, type QueryExpressionContext as cn, type QueryHookContext as co, type QueryOptions as cp, type ReachableNode as cq, type ReachableOptions as cr, type RebuildContributionOptions as cs, type RebuildFulltextOptions as ct, type RebuildFulltextResult as cu, type ReclaimedVectorFieldEntry as cv, type RecordedHeterogeneousNodeWriteBatch as cw, type RecordedRevisionRequest as cx, type RecordedScanOptions as cy, type RecordedScanPage as cz, type RecordedReadStore as d, type WeaklyConnectedComponentsOptions as d$, type SelectableEdge as d0, type SelectableNode as d1, type ShortestPathOptions as d2, type ShortestPathResult as d3, type SqlAvailability as d4, StoreAnalysisCursorStaleError as d5, type StoreAnalysisCursorStaleErrorDetails as d6, type StoreAnalysisSchemaCoordinate as d7, type StoreDescription as d8, type StoreEvolution as d9, type StoreViewWeightedShortestPathOptions as dA, type StreamOptions as dB, type SubgraphEdgeResult as dC, type SubgraphNodeResult as dD, type SubgraphOptions as dE, type SubgraphResult as dF, type SubsetEdge as dG, type SubsetNode as dH, type TemporalAlgorithmOptions as dI, type TopPerPartitionOptions as dJ, type TopPerPartitionOrder as dK, type TransactionContext as dL, type TransactionOutcome as dM, type TransactionReceipt as dN, type TraversalDirection as dO, type TypedEdgeCollection as dP, type TypedRecordedStoreViewEdgeCollection as dQ, type TypedStoreViewEdgeCollection as dR, UnionableQuery as dS, type UniqueIntrospection as dT, type UpdateEdgeInput as dU, type UpdateNodeInput as dV, type ValidateStoreOptions as dW, type ValidityEndMutation as dX, type VectorSearchHit as dY, type VectorSearchOptions as dZ, type WeaklyConnectedComponentMembership as d_, type StoreHooks as da, type StorePopulationStatistics as db, type StoreProjection as dc, type StoreRef as dd, StoreSearch as de, type StoreTransactionOptions as df, type StoreValidationFailure as dg, type StoreValidationPage as dh, StoreView as di, type StoreViewCanReachOptions as dj, type StoreViewCoordinate as dk, type StoreViewDegreeOptions as dl, type StoreViewEdgeCollection as dm, type StoreViewEdgeCollections as dn, type StoreViewGraphAlgorithms as dp, type StoreViewLabelPropagationOptions as dq, type StoreViewNeighborsOptions as dr, type StoreViewNodeCollection as ds, type StoreViewNodeCollections as dt, type StoreViewPageRankOptions as du, type StoreViewPersonalizedPageRankOptions as dv, type StoreViewReachableOptions as dw, type StoreViewShortestPathOptions as dx, type StoreViewSubgraphOptions as dy, type StoreViewWeaklyConnectedComponentsOptions as dz, type IdentityAssertionId as e, type WeightedShortestPathOptions as e0, type WeightedShortestPathResult as e1, type WorkingCopyOptions as e2, asIdentityAssertionId as e3, compareAndSetAbsent as e4, createAdapterStore as e5, createAdapterStoreWithSchema as e6, createQueryBuilder as e7, createStore as e8, createStoreWithSchema as e9, createVerifiedAdapterStore as ea, createVerifiedStore as eb, defineSubgraphProject as ec, exists as ed, fieldRef as ee, inSubquery as ef, isParameterRef as eg, notExists as eh, notInSubquery as ei, param as ej, EDGE_TEMPORAL_READ_NAMES as ek, IDENTITY_READ_NAMES as el, type IdentityAssertionResult as f, type IdentityFacade as g, type IdentityNode as h, type IdentityNodeRefInput as i, type IdentityPair as j, type IdentityReadFacade as k, type IdentityRelation as l, type IdentityWriteSummary as m, type ReconciledSchema as n, type EvolutionRequirement as o, type EvolutionRequirements as p, type MigrationHookContext as q, type SchemaValidationResult as r, applyDeprecatedKinds as s, assertSchemaCurrent as t, ensureSchema as u, getActiveSchema as v, getCommittedSchemaVersion as w, getSchemaChanges as x, initializeSchema as y, isSchemaInitialized as z };