import { z } from 'zod'; /** * Which side of the bipartite graph a kind lives on. Used everywhere a * function takes "the entity that owns a kind" — error metadata, * extension classifiers, removal queues, schema diffs. */ type KindEntity = "node" | "edge"; /** * Which physical surface an index targets. Vector indexes don't own a * kind directly (they back a per-`(kind, field)` typed embedding table * owned by the active `VectorStrategy`), but they participate in the same * materialization and diff pipelines, so they share this discriminator. * * `"system"` marks TypeGraph's own base-relation indexes * (`SYSTEM_INDEX_DECLARATIONS`) — graph-independent, but materialized and * status-tracked through the same pipeline; their status rows carry the * relation key (e.g. `"recordedNodes"`) in the `kind` column. */ type IndexEntity = "node" | "edge" | "vector" | "system"; /** * Field-level null-check operations that round-trip through persisted * documents (unique-constraint where clauses, index where clauses, * graph-extension where clauses, IsNullPredicate). Consolidated so a * single rename or extension touches one site. */ type NullCheckOp = "isNull" | "isNotNull"; /** Brand key for NodeType */ declare const NODE_TYPE_BRAND: "__nodeType"; /** Brand key for EdgeType */ declare const EDGE_TYPE_BRAND: "__edgeType"; /** Brand symbol for NodeId */ declare const __nodeId: unique symbol; /** Brand symbol for EdgeId */ declare const __edgeId: unique symbol; /** A primitive JSON value, excluding arrays and objects. */ type JsonScalar = null | string | number | boolean; /** * Any JSON-serializable value. * * Mirrors the JSON wire format: primitives, arrays, and string-keyed objects. * `null` is included because the JSON spec requires it for "no value here"; * this is the one place in the public API that uses `null` over `undefined`. */ type JsonValue = JsonScalar | readonly JsonValue[] | Readonly<{ [key: string]: JsonValue; }>; /** * Consumer-owned per-kind annotations. * * Stable labels attached to a node or edge kind at definition time — UI hints, * audit policy, provenance pointers, tooling annotations. * * **Consumer-owned, fully.** TypeGraph never reads, validates, or interprets * values in this field. Consumers own the entire namespace; there are no * reserved keys or extension prefixes. Future library-owned per-kind state, * if needed, will use a separate sibling field rather than carving out keys * here. * * **Annotations participate in schema hashing.** Any change to an annotation * value (or adding/removing an annotation key) bumps the canonical schema * hash and is reported as a `safe`-severity diff by `getSchemaChanges`. If * you don't want versioning for a piece of state, do not put it here. * * Values must be JSON-serializable — `bigint`, `function`, `symbol`, `undefined`, * `Date`, and other class instances are rejected at definition time so they can * never silently break schema hashing or storage round-trips. */ type KindAnnotations = Readonly>; /** * Consumer-owned annotations for a graph as a whole. * * This is the schema-level counterpart to {@link KindAnnotations}: display * names, descriptions, capability declarations, and other JSON metadata that * describes the materialization itself rather than one node or edge kind. * Values participate in canonical schema hashing and annotation-only changes * are safe schema changes. */ type GraphAnnotations = Readonly>; /** * A node type definition. * * Created via `defineNode()`. Represents a type of node in the graph * with an associated Zod schema for properties. */ type NodeType = z.ZodObject> = Readonly<{ [NODE_TYPE_BRAND]: true; kind: K; schema: S; description: string | undefined; annotations: KindAnnotations | undefined; }>; /** * Branded node ID type. * * Prevents mixing IDs from different node types at compile time. */ type NodeId = string & Readonly<{ [__nodeId]: N; }>; /** * Brands a non-empty string as a {@link NodeId}. * * Use this when a persisted node id has round-tripped through untyped storage * or an external boundary and must be passed back to read/update/delete * surfaces such as `getById`, `getByIds`, `update`, or `delete`. * Write surfaces that mint or claim ids, such as `create({ id })` and * `upsertById`, intentionally accept plain strings. * * @throws {ValidationError} when `value` is empty. */ declare function asNodeId(value: string): NodeId; /** * Infer the props type from a NodeType. */ type NodeProps = z.infer; /** * Target mapping from source kind name to allowed target node types. */ type EdgeTargetMap = Readonly>; /** * Valid edge target definitions: either a Cartesian array of target nodes, * or a source-to-target map for source-dependent endpoints. */ type EdgeTargets = readonly NodeType[] | EdgeTargetMap; /** * An edge type definition. * * Created via `defineEdge()`. Represents a type of edge in the graph * with an optional Zod schema for properties. * * Optionally includes `from` and `to` arrays or mappings that define the allowed * source and target node types (domain and range constraints). */ type EdgeType = z.ZodObject, From extends readonly NodeType[] | undefined = undefined, To extends EdgeTargets | undefined = undefined> = Readonly<{ [EDGE_TYPE_BRAND]: true; kind: K; schema: S; description: string | undefined; annotations: KindAnnotations | undefined; from: From; to: To; }>; /** * Base edge type for use in constraints - accepts any from/to configuration. */ type AnyEdgeType = EdgeType, readonly NodeType[] | undefined, EdgeTargets | undefined>; /** * An edge type that has both from and to constraints defined. * Can be used directly in defineGraph without an EdgeRegistration wrapper. */ type EdgeTypeWithEndpoints = EdgeType, readonly NodeType[], EdgeTargets>; /** * Branded edge ID type. * * Prevents mixing IDs from different edge types at compile time. */ type EdgeId = string & Readonly<{ [__edgeId]: E; }>; /** * Brands a non-empty string as an {@link EdgeId}. * * Use this when a persisted edge id has round-tripped through untyped storage * or an external boundary and must be passed back to read/update/delete * surfaces such as `getById`, `getByIds`, `update`, or `delete`. * Write surfaces that mint ids intentionally accept plain strings. * * @throws {ValidationError} when `value` is empty. */ declare function asEdgeId(value: string): EdgeId; /** * Infer the props type from an EdgeType. */ type EdgeProps = z.infer; /** * The graph-local fields that identify an edge for durable matching. */ type EdgeMatchIdentity = Readonly<{ name: string; fields: readonly (keyof z.infer & string)[]; }>; /** * Delete behaviors for nodes. */ type DeleteBehavior = "restrict" | "cascade" | "disconnect"; /** * Edge cardinality constraints. */ type Cardinality = "many" | "one" | "unique" | "oneActive"; /** * Endpoint existence modes for edge validation. */ type EndpointExistence = "notDeleted" | "currentlyValid" | "ever"; /** * Temporal query modes. */ type TemporalMode = "current" | "asOf" | "includeEnded" | "includeTombstones"; /** * Uniqueness constraint scope. */ type UniquenessScope = "kind" | "kindWithSubClasses"; /** * Collation for uniqueness constraints. */ type Collation = "binary" | "caseInsensitive"; /** * Uniqueness constraint definition. */ type UniqueConstraint = z.ZodObject> = Readonly<{ name: string; fields: readonly (keyof z.infer & string)[]; where?: (props: UniqueConstraintPredicateBuilder) => UniqueConstraintPredicate; scope: UniquenessScope; collation: Collation; }>; /** * Predicate builder for uniqueness constraint where clause. * Uses -? to make all fields required in the builder, even if optional in the schema. */ type UniqueConstraintPredicateBuilder> = Readonly<{ [K in keyof z.infer]-?: UniqueConstraintField; }>; /** * Field operations for uniqueness constraint predicates. */ type UniqueConstraintField = Readonly<{ isNull: () => UniqueConstraintPredicate; isNotNull: () => UniqueConstraintPredicate; }>; /** * A uniqueness constraint predicate (internal representation). */ type UniqueConstraintPredicate = Readonly<{ __type: "unique_predicate"; field: string; op: NullCheckOp; }>; /** * Node registration in a graph definition. */ type NodeRegistration = Readonly<{ type: N; unique?: readonly UniqueConstraint[]; onDelete?: DeleteBehavior; }>; /** * Edge registration in a graph definition. */ type EdgeRegistration = Readonly<{ type: E; from: readonly FromTypes[]; to: ToDef; cardinality?: Cardinality; endpointExistence?: EndpointExistence; matchIdentity?: EdgeMatchIdentity; }>; /** * Base edge registration type for use in constraints - accepts any endpoint configuration. */ type AnyEdgeRegistration = EdgeRegistration; /** * Default settings for a graph. */ type GraphDefaults = Readonly<{ onNodeDelete?: DeleteBehavior; temporalMode?: TemporalMode; }>; /** * Checks if a value is a NodeType. */ declare function isNodeType(value: unknown): value is NodeType; /** * Checks if a value is an EdgeType. */ declare function isEdgeType(value: unknown): value is AnyEdgeType; /** * Checks if a value is an EdgeType with both from and to constraints defined. * Such edges can be used directly in defineGraph without an EdgeRegistration wrapper. */ declare function isEdgeTypeWithEndpoints(value: unknown): value is EdgeTypeWithEndpoints; type DatabaseJsonValue = boolean | number | string | null | readonly DatabaseJsonValue[] | Readonly<{ [key: string]: DatabaseJsonValue; }>; type DatabaseLiteral = DatabaseJsonValue | Date | undefined; type ArithmeticOperator = "add" | "divide" | "multiply" | "subtract"; type ExpressionComparisonOperator = "eq" | "gt" | "gte" | "lt" | "lte" | "neq"; type AggregateOperator = "avg" | "count" | "countDistinct" | "max" | "min" | "sum"; type CollectOrder = Readonly<{ expression: DatabaseExpression; direction?: "asc" | "desc"; nulls?: "first" | "last"; }>; /** Options for ordered collection aggregation. */ type CollectOptions = Readonly<{ filter?: DatabaseExpression; orderBy: readonly [CollectOrder, ...CollectOrder[]]; }>; type CollectExpressionNode = Readonly<{ kind: "collect"; operand: DatabaseExpression | CollectRecordOperand; filter?: DatabaseExpression; orderBy: readonly CollectOrder[]; }>; /** Flat named scalar expressions collected as one JSON object per admitted row. */ type CollectRecordOperand = Readonly<{ kind: "record"; fields: Readonly>; }>; type FieldExpressionNode = Readonly<{ kind: "field"; field: FieldRef; }>; type LiteralExpressionNode = Readonly<{ kind: "literal"; value: DatabaseLiteral; }>; type ParameterExpressionNode = Readonly<{ kind: "parameter"; name: string; }>; type ArithmeticExpressionNode = Readonly<{ kind: "arithmetic"; operator: ArithmeticOperator; left: DatabaseExpression; right: DatabaseExpression; }>; type ComparisonExpressionNode = Readonly<{ kind: "comparison"; operator: ExpressionComparisonOperator; left: DatabaseExpression; right: DatabaseExpression; }>; type ArrayContainsExpressionNode = Readonly<{ kind: "array_contains"; array: DatabaseExpression; element: DatabaseExpression; }>; type BooleanExpressionNode = Readonly<{ kind: "boolean"; operator: "and" | "or"; operands: readonly DatabaseExpression[]; }>; type NotExpressionNode = Readonly<{ kind: "not"; operand: DatabaseExpression; }>; type NullCheckExpressionNode = Readonly<{ kind: "null_check"; operator: "isNull" | "isNotNull"; operand: DatabaseExpression; }>; type AggregateExpressionNode = Readonly<{ kind: "aggregate"; operator: AggregateOperator; operand?: DatabaseExpression | undefined; }>; type CoalesceExpressionNode = Readonly<{ kind: "coalesce"; operands: readonly DatabaseExpression[]; }>; type ConditionalExpressionNode = Readonly<{ kind: "conditional"; condition: DatabaseExpression; then: DatabaseExpression; otherwise: DatabaseExpression; }>; type NumericConversionExpressionNode = Readonly<{ kind: "numeric_conversion"; operand: DatabaseExpression; }>; type OuterReferenceExpressionNode = Readonly<{ kind: "outer_reference"; expression: DatabaseExpression; outerScopeIdentity: symbol; }>; type ExistsSubqueryExpressionNode = Readonly<{ kind: "exists_subquery"; subquery: QueryAst; }>; type ScalarSubqueryExpressionNode = Readonly<{ kind: "scalar_subquery"; subquery: QueryAst; }>; type DatabaseExpressionNode = AggregateExpressionNode | ArithmeticExpressionNode | ArrayContainsExpressionNode | BooleanExpressionNode | CoalesceExpressionNode | CollectExpressionNode | ComparisonExpressionNode | ConditionalExpressionNode | ExistsSubqueryExpressionNode | FieldExpressionNode | LiteralExpressionNode | NotExpressionNode | NullCheckExpressionNode | NumericConversionExpressionNode | OuterReferenceExpressionNode | ParameterExpressionNode | ScalarSubqueryExpressionNode; /** A portable database expression carrying its decoded type and query scope. */ type DatabaseExpression = Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: T; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; type NonNull = Exclude; type LiteralResult = T extends null ? undefined : T; type ParameterValue = Value extends "boolean" ? boolean : Value extends "date" ? Date : Value extends "number" ? number : Value extends "string" ? string : Value extends "array" ? readonly DatabaseJsonValue[] : Value extends "object" ? Readonly> : unknown; type Comparable = boolean | Date | number | string; type OrderedComparable = Date | number | string; type NullIfEitherUndefined = undefined extends Left | Right ? Value | undefined : Value; type NumericExpression = DatabaseExpression; type ComparableExpression = DatabaseExpression; declare function literal(value: T): DatabaseExpression, never>; declare function parameter(name: string, valueType: Value): DatabaseExpression, never>; type ArrayExpressionElement = ArrayValue extends readonly (infer Element)[] ? Element : never; declare function arrayContains(array: DatabaseExpression, element: DatabaseExpression | undefined, Scope>): DatabaseExpression; declare function not(operand: DatabaseExpression): DatabaseExpression; declare function isNull(operand: DatabaseExpression): DatabaseExpression; declare function isNotNull(operand: DatabaseExpression): DatabaseExpression; declare function count(operand?: DatabaseExpression): DatabaseExpression; declare function countDistinct(operand: DatabaseExpression): DatabaseExpression; /** Explicitly named scalar expressions accepted by record collection aggregation. */ type CollectRecordFields = Readonly>>; /** Readonly decoded record inferred from a collection field map. */ type CollectedRecord> = Readonly<{ [Name in keyof Fields]: Fields[Name] extends (DatabaseExpression) ? Value : never; }>; /** Collects scalar values in explicit order, optionally filtering admitted rows. */ declare function collect(operand: DatabaseExpression, options: CollectOptions): DatabaseExpression; /** Collects explicitly projected scalar fields into ordered readonly records. */ declare function collect>(operand: Fields & CollectRecordFields, options: CollectOptions): DatabaseExpression[], Scope>; declare function coalesce(first: DatabaseExpression, fallback: DatabaseExpression, Scope>, ...rest: readonly DatabaseExpression | undefined, Scope>[]): DatabaseExpression; declare function when | undefined, Scope extends string>(condition: DatabaseExpression, then: DatabaseExpression, otherwise: DatabaseExpression): DatabaseExpression>, Scope>; declare function toNumber(operand: DatabaseExpression): NumericExpression; declare const expr: { readonly add: (left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly and: (...operands: readonly DatabaseExpression[]) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: boolean | undefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly arrayContains: typeof arrayContains; readonly avg: (operand: NumericExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: number | undefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly coalesce: typeof coalesce; readonly collect: typeof collect; readonly count: typeof count; readonly countDistinct: typeof countDistinct; readonly divide: (left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: number | undefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly eq: | undefined, Scope extends string>(left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly gt: | undefined, Scope extends string>(left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly gte: | undefined, Scope extends string>(left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly isNotNull: typeof isNotNull; readonly isNull: typeof isNull; readonly literal: typeof literal; readonly lt: | undefined, Scope extends string>(left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly lte: | undefined, Scope extends string>(left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly max: (operand: ComparableExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: T | undefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly min: (operand: ComparableExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: T | undefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly multiply: (left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly neq: | undefined, Scope extends string>(left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly not: typeof not; readonly or: (...operands: readonly DatabaseExpression[]) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: boolean | undefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly param: typeof parameter; readonly subtract: (left: DatabaseExpression, right: DatabaseExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: NullIfEitherUndefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly sum: (operand: NumericExpression) => Readonly<{ __type: "database_expression"; node: DatabaseExpressionNode; valueType: ValueType; /** @internal Element typing for array operands; separate from result decoding. */ arrayElementType?: ValueType; /** Element type carried by collection-valued expressions; records also carry field codecs. */ elementValueType?: ValueType; /** Scalar codecs for fields of each collected record. */ elementFields?: Readonly>; nullable: boolean; scopeIdentity: symbol; /** @internal Carries the public result type without runtime data. */ __value?: number | undefined; /** @internal Carries the public query scope without exposing SQL aliases. */ __scope?: Scope; }>; readonly toNumber: typeof toNumber; readonly when: typeof when; }; declare const MAX_JSON_POINTER_DEPTH: 5; type JsonPointer = string & { readonly __jsonPointer: unique symbol; }; type JsonPointerSegment = string | number; type JsonPointerSegments = readonly JsonPointerSegment[]; type Depth = 0 | 1 | 2 | 3 | 4 | 5; interface DepthDecrementMap { 0: 0; 1: 0; 2: 1; 3: 2; 4: 3; 5: 4; } type Decrement = DepthDecrementMap[Current]; type NonNegativeIntegerString = Exclude<`${bigint}`, `-${string}`>; type ObjectPointerKey = Exclude, NonNegativeIntegerString>; type EncodeTilde = S extends `${infer Head}~${infer Tail}` ? `${EncodeTilde}~0${EncodeTilde}` : S; type EncodeSlash = S extends `${infer Head}/${infer Tail}` ? `${EncodeSlash}~1${EncodeSlash}` : S; type EncodePointerSegment = EncodeSlash>; type DecodePointerSegment = S extends `${infer Head}~1${infer Tail}` ? `${DecodePointerSegment}/${DecodePointerSegment}` : S extends `${infer Head}~0${infer Tail}` ? `${DecodePointerSegment}~${DecodePointerSegment}` : S; type PointerForArray = `/${NonNegativeIntegerString}` | (Current extends 1 ? never : `/${NonNegativeIntegerString}${JsonPointerFor>}`); type PointerForObject = { [K in ObjectPointerKey]: `/${EncodePointerSegment}` | (Current extends 1 ? never : `/${EncodePointerSegment}${JsonPointerFor>}`); }[ObjectPointerKey]; type JsonPointerFor = "" | (Current extends 0 ? "" : T extends readonly (infer U)[] ? PointerForArray : never) | (Current extends 0 ? "" : T extends Record ? PointerForObject : never); type PointerSegmentsForArray = readonly [number] | (Current extends 1 ? readonly [number] : readonly [number, ...JsonPointerSegmentsFor>]); type PointerSegmentsForObject = { [K in ObjectPointerKey]: readonly [K] | (Current extends 1 ? readonly [K] : readonly [K, ...JsonPointerSegmentsFor>]); }[ObjectPointerKey]; type JsonPointerSegmentsFor = readonly [] | (Current extends 0 ? readonly [] : T extends readonly (infer U)[] ? PointerSegmentsForArray : never) | (Current extends 0 ? readonly [] : T extends Record ? PointerSegmentsForObject : never); type JsonPointerInput = JsonPointerFor | JsonPointerSegmentsFor | JsonPointer; type ResolveJsonPointer = Pointer extends "" ? T : Pointer extends `/${infer Head}/${infer Tail}` ? ResolveJsonPointer>, `/${Tail}`> : Pointer extends `/${infer Head}` ? ResolvePointerSegment> : unknown; type ResolvePointerSegment = T extends readonly (infer U)[] ? Segment extends NonNegativeIntegerString ? U : unknown : T extends Record ? Segment extends keyof T ? Segment extends NonNegativeIntegerString ? unknown : T[Segment] : unknown : unknown; type ResolveJsonPointerSegments = Segments extends readonly [] ? T : Segments extends readonly [infer Head, ...infer Tail] ? ResolveJsonPointerSegments>, Extract> : unknown; type SegmentToString = Segment extends number ? `${Segment}` : Segment extends string ? Segment : string; declare function jsonPointer(segments: JsonPointerSegments): JsonPointer; declare function normalizeJsonPointer(input: JsonPointerInput): JsonPointer; declare function parseJsonPointer(pointer: JsonPointer): readonly string[]; declare function joinJsonPointers(base: JsonPointer | undefined, relative: JsonPointer): JsonPointer; /** * Query AST types. * * Defines the abstract syntax tree for TypeGraph queries. * This portable representation can be compiled to SQL (today) * or other query languages (Cypher, SPARQL) in the future. */ /** * A field reference in a predicate. */ type FieldRef = Readonly<{ __type: "field_ref"; alias: Alias; path: Path; jsonPointer?: JsonPointer | undefined; valueType?: ValueType | undefined; elementType?: ValueType | undefined; /** Whether this field can be absent in the current query row. */ nullable?: boolean | undefined; /** @internal Carries the public value type without affecting the AST. */ readonly __value?: { bivarianceHack(value: Value): void; }["bivarianceHack"]; /** @internal Carries the public property path without affecting the AST. */ readonly __propertyPath?: PropertyPath | undefined; }>; /** * A literal value in a predicate. */ type LiteralValue = Readonly<{ __type: "literal"; value: string | number | boolean; valueType?: ValueType | undefined; }>; /** * A parameter reference for prepared queries. * * Used in place of a literal value to create parameterized queries * that can be executed multiple times with different bindings. */ type ParameterRef = Readonly<{ __type: "parameter"; name: string; valueType?: ValueType | undefined; }>; /** * Supported value types for predicates. */ type ValueType = "string" | "number" | "boolean" | "date" | "array" | "object" | "embedding" | "unknown"; /** * Comparison operators. */ type ComparisonOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "notIn"; /** * String operators. */ type StringOp = "contains" | "startsWith" | "endsWith" | "like" | "ilike"; /** * A comparison predicate. */ type ComparisonPredicate = Readonly<{ __type: "comparison"; op: ComparisonOp; left: FieldRef; right: FieldRef | LiteralValue | LiteralValue[] | ParameterRef; }>; /** A lexicographic comparison between equally sized scalar field/value tuples. */ type TupleComparisonPredicate = Readonly<{ __type: "tuple_comparison"; op: "gt" | "lt"; fields: readonly [FieldRef, ...FieldRef[]]; values: readonly [LiteralValue, ...LiteralValue[]]; }>; /** * A string predicate. */ type StringPredicate = Readonly<{ __type: "string_op"; op: StringOp; field: FieldRef; pattern: string | ParameterRef; }>; /** * A null check predicate. */ type NullPredicate = Readonly<{ __type: "null_check"; op: NullCheckOp; field: FieldRef; }>; /** * A between predicate. */ type BetweenPredicate = Readonly<{ __type: "between"; field: FieldRef; lower: LiteralValue | ParameterRef; upper: LiteralValue | ParameterRef; }>; /** * Array operators. */ type ArrayOp = "contains" | "containsAll" | "containsAny" | "isEmpty" | "isNotEmpty" | "lengthEq" | "lengthGt" | "lengthGte" | "lengthLt" | "lengthLte"; /** * An array predicate. */ type ArrayPredicate = Readonly<{ __type: "array_op"; op: ArrayOp; field: FieldRef; values?: readonly LiteralValue[]; length?: number; }>; /** * Object operators. */ type ObjectOp = "hasKey" | "hasPath" | "pathEquals" | "pathContains" | "pathIsNull" | "pathIsNotNull"; /** * An object/JSON predicate. */ type ObjectPredicate = Readonly<{ __type: "object_op"; op: ObjectOp; field: FieldRef; pointer: JsonPointer; value?: LiteralValue; valueType?: ValueType; elementType?: ValueType; }>; /** * Logical AND predicate. */ type AndPredicate = Readonly<{ __type: "and"; predicates: readonly PredicateExpression[]; }>; /** * Logical OR predicate. */ type OrPredicate = Readonly<{ __type: "or"; predicates: readonly PredicateExpression[]; }>; /** * Logical NOT predicate. */ type NotPredicate = Readonly<{ __type: "not"; predicate: PredicateExpression; }>; /** A typed Boolean database expression used as a SQL predicate. */ type DatabaseExpressionPredicate = Readonly<{ __type: "database_expression_predicate"; expression: DatabaseExpression; }>; /** * An aggregate comparison predicate (for HAVING clauses). */ type AggregateComparisonPredicate = Readonly<{ __type: "aggregate_comparison"; op: ComparisonOp; aggregate: AggregateExpr; value: LiteralValue; }>; /** * An EXISTS subquery predicate. * Tests whether the subquery returns any rows. */ type ExistsSubquery = Readonly<{ __type: "exists"; subquery: QueryAst; negated: boolean; }>; /** * An IN subquery predicate. * Tests whether a field value is in the subquery results. */ type InSubquery = Readonly<{ __type: "in_subquery"; field: FieldRef; subquery: QueryAst; negated: boolean; }>; /** * Vector similarity metric types. */ type VectorMetricType = "cosine" | "l2" | "inner_product"; /** * A vector similarity predicate. * Finds nodes with embeddings similar to the query embedding. * * This predicate affects query execution by: * - Joining with the embeddings table * - Adding ORDER BY distance (ascending) * - Applying LIMIT (top k results) * - Optionally filtering by minimum score */ type VectorSimilarityPredicate = Readonly<{ __type: "vector_similarity"; /** The embedding field reference */ field: FieldRef; /** The query embedding to compare against */ queryEmbedding: readonly number[]; /** * Similarity metric. Omitted when the caller didn't pass one — the compiler * then uses the field's DECLARED `embedding()` metric (resolved per kind), * falling back to "cosine" only when no declaration is available. */ metric?: VectorMetricType; /** Maximum number of results to return */ limit: number; /** Optional minimum similarity score (0-1 for cosine) */ minScore?: number; /** * Retrieve each kind's candidates via the engine's native ANN structure * instead of an exact distance scan (see `SimilarToOptions.approximate`). */ approximate?: boolean; }>; /** * A fulltext MATCH predicate. * * Finds nodes whose combined searchable content matches the query. Affects * query execution by joining with the fulltext table, adding an ORDER BY * on relevance rank (descending), and applying a LIMIT (top k). */ type FulltextMatchPredicate = Readonly<{ __type: "fulltext_match"; /** * Reserved for forward compatibility with per-field fulltext dispatch. * Today only `field.alias` is consumed: the MATCH always targets the * combined `content` column, so `field.path` is a synthetic * `["$fulltext"]` marker rather than a real props path. Future * strategies (per-field indexes, `setweight()`-style boosts) can use * the full `FieldRef` without breaking the AST shape. */ field: FieldRef; /** The user-supplied query string. */ query: string; /** Parse mode for the query string. Default: "websearch". */ mode: FulltextQueryMode; /** Language override for query parsing. */ language?: string; /** Maximum number of results to return. */ limit: number; /** Minimum relevance to include (backend-native units). */ minScore?: number; }>; /** * Fusion options for hybrid (vector + fulltext) queries. * * Shared between: * - Query-builder path: `QueryBuilder.fuseWith()` stores this on `QueryAst`. * - Store path: `store.search.hybrid(kind, { fusion })` accepts the same shape. * * Only applied when the query contains both a vector and a fulltext * predicate. Defaults (when omitted): RRF with k=60 and equal weights. */ type HybridFusionOptions = Readonly<{ /** RRF is the only currently supported fusion method. */ method?: "rrf"; /** RRF constant. The classic value is 60. */ k?: number; /** Per-source weights. Default: { vector: 1, fulltext: 1 }. */ weights?: Readonly<{ vector?: number; fulltext?: number; }>; }>; /** * All predicate expression types. */ type PredicateExpression = ComparisonPredicate | TupleComparisonPredicate | StringPredicate | NullPredicate | BetweenPredicate | ArrayPredicate | ObjectPredicate | AndPredicate | OrPredicate | NotPredicate | AggregateComparisonPredicate | ExistsSubquery | InSubquery | VectorSimilarityPredicate | FulltextMatchPredicate | DatabaseExpressionPredicate; /** * The starting point of a query (the FROM clause). */ type QueryStart = Readonly<{ alias: string; kinds: readonly string[]; includeSubClasses: boolean; }>; /** * Direction of edge traversal. */ type TraversalDirection = "out" | "in"; /** * Traversal ontology expansion behavior. * * - `"none"` — follow only the exact edge kind specified * - `"implying"` — also follow edge kinds that imply the specified kind (subClassOf) * - `"inverse"` — also follow the ontological inverse edge kind (inverseOf) * - `"all"` — follow both implying and inverse expansions */ type TraversalExpansion = "none" | "implying" | "inverse" | "all"; /** * Cycle handling policy for recursive traversals. */ type RecursiveCyclePolicy = "prevent" | "allow"; /** * Variable-length traversal specification for recursive graph traversals. */ type VariableLengthSpec = Readonly<{ /** Minimum number of hops before including results (default: 1) */ minDepth: number; /** Maximum number of hops (-1 = unlimited, default: -1) */ maxDepth: number; /** * Cycle handling mode. * * - "prevent": Track visited nodes per path and reject revisits * - "allow": Skip cycle checks (faster, may revisit nodes) */ cyclePolicy: RecursiveCyclePolicy; /** Optional column alias for projected traversal path array */ pathAlias?: string; /** Qualified paths include alternating node and edge references. */ pathFormat?: "qualified"; /** Optional column alias for projected traversal depth */ depthAlias?: string; /** Stop expanding a matching node, optionally omitting that node from results. */ stopExpansion?: Readonly<{ expression: PredicateExpression; emitStopNode: boolean; }>; }>; /** * A traversal step in the query. */ type Traversal = Readonly<{ edgeAlias: string; edgeKinds: readonly string[]; /** * Edge kinds traversed in the opposite direction. * * Populated when query options request inverse/symmetric expansion. */ inverseEdgeKinds?: readonly string[]; direction: TraversalDirection; nodeAlias: string; nodeKinds: readonly string[]; joinFromAlias: string; joinEdgeField: "from_id" | "to_id"; /** If true, use LEFT JOIN instead of INNER JOIN (optional match) */ optional: boolean; /** Expand this hop through coordinate-visible Operational Identity members. */ includeIdentityMembers?: boolean; /** Variable-length traversal configuration (for recursive CTEs) */ variableLength?: VariableLengthSpec; }>; /** * Supported aggregate functions. */ type AggregateFunction = "count" | "countDistinct" | "sum" | "avg" | "min" | "max"; /** * An aggregate expression. */ type AggregateExpr = Readonly<{ __type: "aggregate"; function: Function; field: Field; }>; /** * A GROUP BY specification. */ type GroupBySpec = Readonly<{ fields: readonly (DatabaseExpression | FieldRef)[]; }>; /** * A projected field in the SELECT clause. * Can be either a direct field reference or an aggregate expression. */ type ProjectedField = Readonly<{ outputName: string; source: AggregateExpr | DatabaseExpression | FieldRef; /** Override the CTE alias for this field (used for edge fields in node CTEs) */ cteAlias?: string; }>; /** * The projection (SELECT) clause. */ type Projection = Readonly<{ fields: readonly ProjectedField[]; }>; /** * A selectively projected field for optimized queries. * * Used when the select callback only accesses specific fields, * allowing the compiler to generate optimized SQL that fetches * only those fields instead of the full props blob. */ type SelectiveField = Readonly<{ /** The alias (node or edge) this field belongs to */ alias: string; /** The field name (e.g., "email", "name", "id") */ field: string; /** The output column name in the result (e.g., "p_email") */ outputName: string; /** True if this is a system field (id, kind, etc.), false for props */ isSystemField: boolean; /** * Optional value type for props fields. * * When present, the compiler can use type-aware JSON extraction * (e.g. numeric/date casts) to better match predicate compilation * and enable expression index coverage. */ valueType?: ValueType | undefined; }>; /** * Null ordering preference. */ type NullOrdering = "first" | "last"; /** * Sort direction. */ type SortDirection = "asc" | "desc"; /** * An ordering specification. */ type OrderSpec = Readonly<{ field: DatabaseExpression | FieldRef; direction: SortDirection; nulls?: NullOrdering; }>; /** * An ordering specification for aggregate queries. * * Unlike {@link OrderSpec}, this references a projected SELECT-list output * column by name rather than a `FieldRef` into a source table/CTE. Aggregate * query projections always assign an output alias to every grouped field * and aggregate expression (via `.aggregate({ outputName: ... })`), so * ordering by that alias works uniformly for both — and both SQLite and * PostgreSQL allow `ORDER BY` to reference a SELECT-list alias directly, so * no re-derivation of the underlying expression (and no dialect seam) is * needed. */ type AggregateOrderSpec = Readonly<{ outputName: string; direction: SortDirection; nulls?: NullOrdering; }>; /** * A predicate applied to a specific node or edge alias. */ type NodePredicate = Readonly<{ targetAlias: string; /** Whether this predicate targets a node or edge. Defaults to "node". */ targetType?: KindEntity; expression: PredicateExpression; }>; /** * Temporal query options. */ type TemporalOptions = Readonly<{ mode: TemporalMode; asOf?: string; }>; /** * The complete query AST. */ type QueryAst = Readonly<{ /** Runtime identity for validating expression and outer-reference scope. */ expressionScope?: symbol; /** The graph ID this query is for (used for subqueries) */ graphId?: string; start: QueryStart; traversals: readonly Traversal[]; predicates: readonly NodePredicate[]; /** Filters completed match rows, after expansion/candidate generation and before grouping/ranges. */ resultPredicate?: PredicateExpression; projection: Projection; temporalMode: TemporalOptions; /** Recorded/system-time timestamp for recorded-pinned reads. */ recordedAsOf?: string; orderBy?: readonly OrderSpec[]; limit?: number; offset?: number; /** GROUP BY specification for aggregate queries */ groupBy?: GroupBySpec; /** HAVING clause - predicates applied after GROUP BY */ having?: PredicateExpression; /** * ORDER BY specification for aggregate queries, referencing projected * output aliases (grouped fields or aggregate expressions) instead of * source-table field refs. See {@link AggregateOrderSpec}. */ aggregateOrderBy?: readonly AggregateOrderSpec[]; /** * Selective fields for optimized queries. * When present, the compiler generates SQL that only fetches these specific * fields instead of the full props blob, enabling covered index usage. */ selectiveFields?: readonly SelectiveField[]; /** Fusion options for hybrid queries. Ignored unless both predicates present. */ fusion?: HybridFusionOptions; }>; /** * Set operation types for combining queries. */ type SetOperationType = "union" | "unionAll" | "intersect" | "except"; /** * A set operation combining two queries. */ type SetOperation = Readonly<{ __type: "set_operation"; operator: SetOperationType; left: ComposableQuery; right: ComposableQuery; orderBy?: readonly OrderSpec[]; limit?: number; offset?: number; }>; /** * A composable query - either a base query or a set operation. */ type ComposableQuery = QueryAst | SetOperation; /** * Unified table-contribution contract (#129). * * Every table TypeGraph owns — whether modeled as a Drizzle table or * emitted as strategy-owned raw DDL — is described by a single * {@link TableContribution} shape. This is the one place that answers * "what tables does this backend/strategy own?", replacing the * previously split surfaces (Drizzle named exports, tables-factory * recursion, strategy raw DDL, per-table `ensureXTable` methods). * * Lives in the neutral `backend/` layer (sibling of `backend/types.ts`, * which `query/dialect` already depends on) and is deliberately * Drizzle-free, so declaring contributions does not pull the concrete * Drizzle backend runtime into the query/dialect layer. * * ## Identity vs. signature (prerequisite for #135) * * #135 (durable fulltext/contribution materialization) needs to make * "not materialized" vs. "materialized but stale" a decidable, durable * fact instead of an in-memory per-backend latch. That requires two * conceptually separate things, and #129's job is only to make both * *derivable* from the contract: * * - **Materialization identity** — `owner` + `logicalName` + * resolved physical `tableName`. Keying on `logicalName` alone is * insufficient: custom per-deployment table names must be * distinguishable, otherwise two deployments with different physical * names would collide on one durable marker. (#135 additionally * scopes this by `graphId` at persistence time.) * - **Drift signature** — a hash of the strategy identity/version, * the resolved table name(s), and the normalized `createDdl`. #129 * guarantees `createDdl` is deterministic for a given resolved * configuration so the hash #135 computes is meaningful. * * The signature is intentionally **not** eagerly carried on the * contribution: hashing is async (Web Crypto, see `utils/hash`) and * #135 already owns signature persistence the same way * `materializeIndexes` does for declared indexes. */ /** * `logicalName` of the strategy-owned fulltext slot. Used as a logic * discriminant (latch routing, runtime-ensure) across the strategies * and both backends — a shared constant so a strategy declaring a * different name fails loudly at the call site instead of silently * skipping the latched fulltext path. */ declare const FULLTEXT_CONTRIBUTION_NAME = "fulltext"; /** `owner` of core/base schema tables (not strategy-owned). */ declare const BASE_CONTRIBUTION_OWNER = "base"; /** Ownership scope for a strategy-owned physical contribution. */ type ContributionScope = "deployment" | "graph"; /** Durable-marker key used for deployment-scoped physical contributions. */ declare const DEPLOYMENT_CONTRIBUTION_GRAPH_ID = "__typegraph_deployment__"; /** * A single table TypeGraph owns. */ type TableContribution = Readonly<{ /** * Whether physical storage is shared by the deployment or owned by one * graph. Omitted by older custom strategies, which retain graph scope. */ scope?: ContributionScope; /** * Stable, graph- and deployment-independent identity for the logical * slot this contribution fills (e.g. `"fulltext"`). NOT the physical * table name. Stable across table-name overrides and across strategy * swaps of the *same* logical slot, so #135's durable marker can * survive both. */ logicalName: string; /** * Identifies the producer of this contribution (e.g. a strategy id * like `"tsvector"` / `"fts5"`, or `"base"` for core schema tables). * Part of the #135 materialization identity and an input to the * drift signature — a strategy swap on the same `logicalName` is a * legitimate, detectable drift, not a silent reuse. */ owner: string; /** * Resolved physical table name after any per-deployment name * override. Part of the #135 materialization identity (custom names * must be distinguishable) and used by diagnostics / the focused * bootstrap ensure. */ tableName: string; /** * Idempotent (`CREATE ... IF NOT EXISTS`) statements that * materialize this contribution's table **and its supporting * indexes**. Running the full list is how the runtime ensure * self-heals partial states (table present, index missing) — it is * not a probe-and-skip. Deterministic for a given resolved * configuration: the canonical normalized input to #135's drift * signature. */ createDdl: readonly string[]; /** * Idempotent (`DROP ... IF EXISTS`) statements that tear this * contribution's storage down, ordered so that running the list * leaves nothing behind. Declaring it is what makes a contribution * *rebuildable*: the destructive * `store.rebuildContribution()` path drops through these statements * before re-running {@link createDdl}, which is the only repair for a * table provisioned at a shape the current `createDdl` no longer * produces. * * Optional because it is a capability, not an invariant. A * contribution whose content cannot be reconstructed from data * TypeGraph already stores has no business advertising a rebuild — * dropping it would destroy the only copy — and a third-party * strategy that predates this field keeps compiling and is reported * as not rebuildable rather than silently rebuilt through a * synthesized `DROP`. Backends surface the resulting gap as * `capabilities.contributions.rebuild`. * * The rebuild visits a strategy's `runtimeEnsure` contributions, the * same set the boot ensure provisions — so a companion table a strategy * declares outside that set is neither dropped nor recreated, and must * not be something the recreated storage depends on. */ dropDdl?: readonly string[]; /** * When `true`, the post-schema-load focused ensure * (`loadActiveSchemaWithBootstrap`) materializes this contribution * on every successful schema load. * * **Invariant: only strategy-declared contributions may set this.** * Core/base tables are always `false` — they are created by * drizzle-kit / `bootstrapTables`. This is what lets the boot path * derive runtime contributions straight from the strategy * (`fulltextStrategy.ownedTables`) without walking — and generating * DDL for — every base table. */ runtimeEnsure: boolean; }>; /** * A table a {@link FulltextStrategy} declares it owns. An alias, not a * distinct shape: a strategy's declaration is already authoritative * (no resolution step). The name documents the producer role. */ type StrategyTableContribution = TableContribution; /** * Fulltext Strategy — pluggable DDL + SQL generation for a dialect's * fulltext stack. A strategy owns every SQL statement that touches its * backing table: DDL, upsert (single + batch), delete (single + batch), * MATCH condition, rank expression, and snippet expression. * * A dialect picks one as its default; `createPostgresBackend` / * `createSqliteBackend` accept an override for alternate Postgres stacks * (pg_trgm, ParadeDB/pg_search, pgroonga) to swap the entire fulltext * pipeline without forking TypeGraph. */ /** * Every `FulltextQueryMode` both shipped strategies accept. */ declare const ALL_FULLTEXT_MODES: readonly FulltextQueryMode[]; /** * Derives the `FulltextCapabilities` backends advertise from the active * strategy, so the two never drift. */ declare function buildFulltextCapabilities(strategy: FulltextStrategy): FulltextCapabilities; /** * A pluggable fulltext implementation. Each strategy is expected to be * self-contained: given a table name, a query string, and a parse mode, * it emits every SQL statement the compiler and backend need — DDL, * reads, and writes. No out-of-band conventions across layers. */ type FulltextStrategy = Readonly<{ /** Human-readable identifier used in error messages and telemetry. */ name: string; /** * Parse modes this strategy can translate. Callers validate against * this list before emitting SQL; a mode outside the set means the * strategy rejects the query at compile time. */ supportedModes: readonly FulltextQueryMode[]; /** * Whether the strategy can emit a per-row highlighted snippet. When * false, `snippetExpression` returns a literal `NULL` so callers can * leave the `snippet` column in place without a branch. */ supportsSnippets: boolean; /** * Whether the strategy supports prefix queries (`foo*`). Used to * populate `BackendCapabilities.fulltext.prefixQueries`. A strategy * may support prefix queries via dedicated syntax without advertising * "raw" mode (and vice-versa). */ supportsPrefix: boolean; /** * Whether a per-query `language` override is honored. Postgres' tsvector * accepts any installed regconfig at query time; SQLite FTS5's tokenizer * is fixed at table-create time, so the override is silently ignored. * Callers may surface a warning when the user passes `language` to a * strategy that doesn't honor it. */ supportsLanguageOverride: boolean; /** * Languages / tokenizer names understood by the strategy. Advisory — a * runtime backend like Postgres may accept other installed regconfigs. * Used to populate `BackendCapabilities.fulltext.languages`. */ languages: readonly string[]; /** * Emits the WHERE-side MATCH expression. * * @example * tsvector: `"typegraph_node_fulltext"."tsv" @@ websearch_to_tsquery('english', 'cats')` * fts5: `"typegraph_node_fulltext" MATCH '"cats"'` */ matchCondition: (this: void, tableName: string, query: string, mode: FulltextQueryMode, language?: string) => SqlFragment; /** * Emits the relevance expression. Higher values = more relevant. The * compiler orders by this expression DESC, and both builder and * backend-direct paths use the same form so their top-k results agree. */ rankExpression: (this: void, tableName: string, query: string, mode: FulltextQueryMode, language?: string) => SqlFragment; /** * Emits a per-row highlighted snippet expression, or `NULL` when * `supportsSnippets` is false. Snippet markup is `…` in * both shipped strategies so consumers can apply one stylesheet. */ snippetExpression: (this: void, tableName: string, query: string, mode: FulltextQueryMode, language?: string) => SqlFragment; /** * The tables this strategy owns, as Drizzle-free, already * authoritative `TableContribution`s (`logicalName`, `owner`, * resolved `tableName`, idempotent `createDdl` for the table **and * its supporting indexes**, `runtimeEnsure`, `scope`). A strategy never * constructs a Drizzle table itself; drizzle-kit visibility, when * applicable, is the schema barrel's responsibility (the default * Postgres strategy's `schema/postgres.ts` exports a matching * `tables.fulltext` — a non-default strategy must export its own). * * Replaces the former `generateDdl(tableName)`: DDL is now one field * of a contribution rather than the strategy's whole storage * surface. (Public API change — see #129.) * * Declare `dropDdl` on each contribution to opt the strategy into the * destructive `store.rebuildContribution("fulltext")` path — the only * repair for storage provisioned at a shape `createDdl` no longer * produces. Fulltext content is reconstructed from the node rows, so a * rebuild loses nothing; a strategy that omits `dropDdl` is reported * as not rebuildable instead of being dropped through a synthesized * statement. */ ownedTables: (this: void, primaryTableName: string) => readonly StrategyTableContribution[]; /** * Emits the statements that upsert a single fulltext row. Returns one * or more statements — some backends (SQLite FTS5) cannot emulate * upsert in a single statement and need DELETE + INSERT. */ buildUpsert: (this: void, tableName: string, params: UpsertFulltextParams, timestamp: string) => readonly SqlFragment[]; /** * Emits one atomic projection write sourced from an `inserted_node` CTE. * Strategies that need more than one statement for a sync (SQLite FTS5 is * the shipped example) omit this capability and use the ordinary sidecar * path. The source alias is trusted backend-owned SQL, never caller input. */ buildSyncFromInsertedNode?: (this: void, tableName: string, sourceAlias: string, projection: Extract, timestamp: string) => SqlFragment; /** * Emits the statements that upsert many fulltext rows at once. Input * rows are expected to be de-duplicated last-write-wins by the * strategy if the underlying SQL statement cannot tolerate duplicate * conflict keys. Returns `[]` when `params.rows` is empty. */ buildBatchUpsert: (this: void, tableName: string, params: UpsertFulltextBatchParams, timestamp: string) => readonly SqlFragment[]; /** * Emits the statements that delete a single fulltext row. Normally a * single PK-scoped DELETE, but strategies that maintain auxiliary * indexes (delete-triggered external stores, ParadeDB-style secondary * structures) may need more. */ buildDelete: (this: void, tableName: string, params: DeleteFulltextParams) => readonly SqlFragment[]; /** * Emits the statements that delete many fulltext rows at once. Returns * `[]` when `params.nodeIds` is empty. */ buildBatchDelete: (this: void, tableName: string, params: DeleteFulltextBatchParams) => readonly SqlFragment[]; }>; declare const tsvectorStrategy: FulltextStrategy; declare const fts5Strategy: FulltextStrategy; /** * SQL Dialect Abstraction Layer * * Provides a unified interface for dialect-specific SQL generation. * Implementing a new dialect (MySQL, SQL Server, etc.) requires * implementing this interface. */ /** * Supported SQL dialects. */ type SqlDialect = "sqlite" | "postgres"; /** * Strategy for compiling standard (non-recursive, non-set-op) queries. */ type DialectStandardQueryStrategy = "cte_project"; /** * Strategy for compiling recursive queries. */ type DialectRecursiveQueryStrategy = "recursive_cte"; /** * Strategy for handling vector predicates. */ type DialectVectorPredicateStrategy = "native" | "unsupported"; /** * Strategy for computing subgraph reachable-node membership. * * `"materialized-ids"` fetches the traversal closure's ids in one extra * round trip and filters the node/edge fetches against that materialized * array. `"inline-cte"` embeds the recursive traversal CTE directly in each * fetch statement instead. This is a control-flow and prepared-plan choice, * not SQL text a token could express identically on both engines. */ type DialectSubgraphMembershipStrategy = "materialized-ids" | "inline-cte"; /** * Capability and strategy profile for a SQL dialect. */ type DialectCapabilities = Readonly<{ /** * Standard query compilation strategy. */ standardQueryStrategy: DialectStandardQueryStrategy; /** * Recursive query compilation strategy. */ recursiveQueryStrategy: DialectRecursiveQueryStrategy; /** * Whether intermediate traversal CTEs should be materialized. */ materializeIntermediateTraversalCtes: boolean; /** * When true, emit an explicit `NOT MATERIALIZED` hint on non-materialized * CTEs so the planner can inline them and see their row statistics. PostgreSQL * otherwise defaults to materializing any CTE referenced more than once, * which makes its planner opaque to the inner statistics. SQLite ignores * CTE materialization hints entirely. */ emitNotMaterializedHint: boolean; /** * Whether a traversal must pin its frontier ahead of the edge table with * `CROSS JOIN`, because the engine reads FROM order as a join-order directive * rather than costing the orderings itself. * * Set for engines whose planner has no useful statistics for the driving * relation and can therefore choose to enumerate candidate edges instead — * once per frontier row. Two traversal shapes need the pin: a recursive term, * whose driving relation is the worktable, and an identity-expanded step, * which reaches the edge through `COALESCE` over an outer join and so exposes * no direct frontier-to-edge equality. Every other traversal joins on the * frontier's own columns and is left to the planner. */ forceRecursiveWorktableOuterJoinOrder: boolean; /** * Strategy for vector predicate support. */ vectorPredicateStrategy: DialectVectorPredicateStrategy; /** * Metrics supported by vector predicates for this dialect — a FALLBACK only. * The active `VectorStrategy.capabilities.metrics` is the real authority and * is what the vector pass validates against on the normal compile path; this * dialect list is consulted ONLY by the strategy-less plan-lowering path * (recursive / set-operation queries, which don't plumb a strategy). It must * therefore stay a superset of every strategy that runs on this dialect, or a * metric a strategy supports could be wrongly rejected when a query happens to * lower through that path. (The bundled dialects mirror their strategies.) */ vectorMetrics: readonly VectorMetric[]; /** * Whether the dialect supports fulltext MATCH predicates. */ supportsFulltext: boolean; /** * Subgraph reachable-node membership strategy — see * {@link DialectSubgraphMembershipStrategy}. */ subgraphMembershipStrategy: DialectSubgraphMembershipStrategy; }>; /** * Shape of a list-valued `IN` parameter, resolved at compile time. */ type InListParameterOptions = Readonly<{ /** Whether the predicate is `notIn` rather than `in`. */ negated: boolean; /** * The value type the left operand was compiled for. `undefined` when the * schema declares nothing usable, in which case the comparison is textual. */ elementType: ValueType | undefined; }>; /** * Adapter interface for SQL dialect differences. * * Each method generates a dialect-specific `SqlFragment` for a common operation. * All methods return database-independent `SqlFragment` values that can be composed * together and rendered by a backend adapter. */ interface DialectAdapter { /** Converts strict finite decimal text to a number, returning SQL NULL otherwise. */ readonly safeNumericConversion: (this: void, expression: SqlFragment) => SqlFragment; /** Token for an unlimited result bound when OFFSET requires a LIMIT. */ unboundedLimit(): SqlFragment; /** * The dialect name this adapter handles. */ readonly name: SqlDialect; /** * Dialect capabilities and strategy selection used by query compilers. */ readonly capabilities: DialectCapabilities; /** * Applies the dialect's binary/code-point text collation to an expression. * SQLite's default BINARY collation already has this order; PostgreSQL must * force `COLLATE "C"` so database locale cannot change deterministic graph * labels or query tie-breaks. */ readonly binaryText: (this: void, expression: SqlFragment) => SqlFragment; /** * Builds the dialect's planner-statistics refresh for a temporary table. * Returns undefined when the engine plans temporary tables well enough * without an explicit refresh. */ readonly analyzeTemporaryTable: (this: void, table: SqlFragment) => SqlFragment | undefined; /** * Builds the dialect's transaction-scoped working-memory override for * sort/hash-heavy iterative rounds. PostgreSQL emits the parameterized * `SET LOCAL work_mem` form (`set_config(..., is_local => true)`), which * reverts automatically when the transaction ends and never touches the * session or server setting. Returns undefined when the engine has no * per-operation memory budget to raise (SQLite). Callers must run the * statement inside a transaction and validate the value's shape first. */ readonly setTransactionWorkingMemory: (this: void, workingMemory: string) => SqlFragment | undefined; /** * Aggregates an ordered derived-table row set into one JSON array value. * * `columns` are the public columns to retain; `orderColumn` is an internal * ordinal and must not appear in the JSON objects. This is the token-level * dialect seam used by one-statement batches: PostgreSQL can convert a whole * record to JSON, while SQLite must spell every `json_object` key. */ readonly orderedRowsJsonArray: (this: void, rowAlias: string, columns: readonly string[], orderColumn: string) => SqlFragment; /** * Aggregates scalar values into an ordered JSON array. * * A filter admits only rows for which SQL evaluates it to TRUE; FALSE and * NULL are excluded. Admitted NULL values remain array elements, and an * empty input produces an empty array. */ readonly orderedScalarJsonArray: (this: void, options: Readonly<{ value: SqlFragment; valueType: ValueType; orderBy: readonly SqlFragment[]; filter: SqlFragment | undefined; }>) => SqlFragment; /** Aggregates flat named scalar values into ordered JSON object rows. */ readonly orderedRecordJsonArray: (this: void, options: Readonly<{ fields: readonly Readonly<{ name: string; value: SqlFragment; valueType: ValueType; }>[]; orderBy: readonly SqlFragment[]; filter: SqlFragment | undefined; }>) => SqlFragment; /** * Converts a JSON pointer to dialect-specific path syntax. * * @example * SQLite: "$.name" or "$[0].value" * PostgreSQL: ARRAY['name'] or ARRAY['0', 'value'] */ readonly compilePath: (this: void, pointer: JsonPointer) => SqlFragment; /** * Extracts a JSON value at a path (returns JSON type). * * @example * SQLite: json_extract(column, '$.path') * PostgreSQL: column #> ARRAY['path'] */ readonly jsonExtract: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Extracts a JSON value at a path as text. * * @example * SQLite: json_extract(column, '$.path') * PostgreSQL: column #>> ARRAY['path'] */ readonly jsonExtractText: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Extracts a JSON value at a path and casts to numeric. * * @example * SQLite: json_extract(column, '$.path') * PostgreSQL: (column #>> ARRAY['path'])::numeric */ readonly jsonExtractNumber: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Extracts a JSON value at a path as an IEEE 754 double. * * Unlike {@link jsonExtractNumber} — whose PostgreSQL form casts to * `numeric` and therefore computes in exact decimal — this member * guarantees binary double arithmetic on every dialect, so accumulated * results (e.g. graph traversal weights) are backend-identical. * * @example * SQLite: json_extract(column, '$.path') * PostgreSQL: (column #>> ARRAY['path'])::double precision */ readonly jsonExtractDouble: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Extracts a JSON value at a path and casts to boolean. * * @example * SQLite: json_extract(column, '$.path') * PostgreSQL: (column #>> ARRAY['path'])::boolean */ readonly jsonExtractBoolean: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Extracts a JSON value at a path and casts to timestamp. * * @example * SQLite: json_extract(column, '$.path') * PostgreSQL: (column #>> ARRAY['path'])::timestamptz */ readonly jsonExtractDate: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Returns the length of a JSON array. * * @example * SQLite: json_array_length(column) * PostgreSQL: jsonb_array_length(column) */ readonly jsonArrayLength: (this: void, column: SqlFragment) => SqlFragment; /** * Checks if a JSON array contains a specific value. * * @example * SQLite: EXISTS (SELECT 1 FROM json_each(column) WHERE value = ?) * PostgreSQL: column @> '[value]'::jsonb */ readonly jsonArrayContains: (this: void, column: SqlFragment, value: unknown) => SqlFragment; /** * Checks whether a JSON array contains a value supplied by another SQL * expression. Unlike {@link jsonArrayContains}, the value is never encoded * as a JSON literal, so field references and correlated outer references * retain their row-by-row meaning. */ readonly jsonArrayContainsExpression?: (this: void, column: SqlFragment, value: SqlFragment, valueType: ValueType) => SqlFragment; /** Emits a row-value comparison for compatible lexicographic cursor keys. */ readonly rowValueComparison?: (this: void, operator: ">" | "<", left: readonly SqlFragment[], right: readonly SqlFragment[]) => SqlFragment; /** * Checks if a JSON array contains all specified values. * * @example * SQLite: Multiple EXISTS subqueries ANDed * PostgreSQL: column @> '[values]'::jsonb */ readonly jsonArrayContainsAll: (this: void, column: SqlFragment, values: readonly unknown[]) => SqlFragment; /** * Checks if a JSON array contains any of the specified values. * * @example * SQLite: Multiple EXISTS subqueries ORed * PostgreSQL: Multiple @> checks ORed */ readonly jsonArrayContainsAny: (this: void, column: SqlFragment, values: readonly unknown[]) => SqlFragment; /** * Checks if a JSON object has a key at a path. * * @example * SQLite: json_type(column, '$.path') IS NOT NULL * PostgreSQL: column #> ARRAY['path'] IS NOT NULL */ readonly jsonHasPath: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Checks if a JSON value at a path is absent or the JSON `null` literal — * i.e. carries no usable value. * * The check is type-based (`json_type` / `jsonb_typeof`): a JSON *string* * `"null"` is a string, not null. Never evaluates to SQL NULL — a missing * path yields TRUE — so the predicate composes safely under NOT/OR. * * @example * SQLite: COALESCE(json_type(column, '$.path') = 'null', 1) * PostgreSQL: COALESCE(jsonb_typeof(column #> path) = 'null', TRUE) */ readonly jsonPathIsNull: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Checks if a JSON value at a path is present and not the JSON `null` * literal. Exact negation of {@link jsonPathIsNull}: type-based, and * never SQL NULL — a missing path yields FALSE. */ readonly jsonPathIsNotNull: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Compares one JSON scalar by exact JSON type and value. Unlike text * extraction this preserves the distinction between strings, numbers, * booleans, and the JSON `null` literal without introducing a separate * per-dialect structural-comparison strategy. */ readonly jsonScalarPathEquals: (this: void, column: SqlFragment, pointer: JsonPointer, value: JsonScalar) => SqlFragment; /** * Checks if the JSON value at a path exists and is a JSON number. * * Never evaluates to SQL NULL: a missing path yields FALSE, so the * predicate can be negated safely inside audit-style WHERE clauses. * * @example * SQLite: json_type(column, '$.path') IN ('integer', 'real') * PostgreSQL: jsonb_typeof(column #> path) = 'number' */ readonly jsonPathIsNumber: (this: void, column: SqlFragment, pointer: JsonPointer) => SqlFragment; /** * Replaces top-level JSON object properties while preserving explicit JSON * null values. This is the token-level dialect seam used by set-based node * mutation; callers validate the patch against the node schema before SQL * compilation. */ readonly jsonSetProperties: (this: void, column: SqlFragment, patch: Readonly>, unsetProperties?: readonly string[]) => SqlFragment; /** * Null-safe equality: `TRUE` when both sides are equal OR both are NULL. * Unlike `=`, a NULL on either side does not yield NULL/unknown. * * Used by batched declared-index lookup so a NULL probe value matches a * NULL stored index-field value. Plain `=` would silently never match * NULLs, diverging from the lookup's documented null semantics. * * @example * SQLite: left IS right * PostgreSQL: left IS NOT DISTINCT FROM right */ readonly nullSafeEquals: (this: void, left: SqlFragment, right: SqlFragment) => SqlFragment; /** * Tests scalar membership in a literal list. Dialects may choose a packed * representation to keep the statement's bound-parameter count constant. * * @example * SQLite: left IN (SELECT value FROM json_each(?)) * PostgreSQL: left IN ($1, $2, ...) */ readonly inList: (this: void, left: SqlFragment, values: readonly unknown[], negated: boolean) => SqlFragment; /** * Tests scalar membership in a list supplied as a SINGLE bound parameter — * the `field.in(param("ids"))` form. * * The emitted SQL must not depend on how many elements the list holds, so a * prepared statement compiles once and serves every arity from the same * cached template. Both bundled dialects therefore unpack a JSON-encoded * array (produced by {@link DialectAdapter.packListValue}) into a one-column * relation instead of emitting one placeholder per element. * * `elementType` is the value type the left operand was compiled for, so a * dialect that extracts JSON as text can cast the unpacked elements to the * same type; `undefined` means text. * * @example * SQLite: left IN (SELECT value FROM json_each(?)) * PostgreSQL: left IN (SELECT e.value::numeric FROM jsonb_array_elements_text($1::jsonb) AS e(value)) */ readonly inListParameter: (this: void, left: SqlFragment, packedValues: SqlFragment, options: InListParameterOptions) => SqlFragment; /** * Packs the scalar list bound to an `in()`/`notIn()` parameter into the * single driver value {@link DialectAdapter.inListParameter} unpacks. * Elements arrive as plain JavaScript scalars and must be bound with the * same rules a literal of that type would be (SQLite booleans as 0/1, dates * as ISO text), so the parameterized and literal forms match row for row. * * Every element is guaranteed to match the compiled element type, and to be * a finite number when that type is numeric: `PreparedQuery` validates the * binding before it reaches this method. Implementations therefore do not * need to normalize or reject anything — and must not paper over a mismatch, * since doing so is what would let one dialect quietly disagree with * another. */ readonly packListValue: (this: void, values: readonly unknown[]) => unknown; /** * Case-insensitive LIKE comparison. * * @example * SQLite: LOWER(column) LIKE LOWER(pattern) * PostgreSQL: column ILIKE pattern */ readonly ilike: (this: void, column: SqlFragment, pattern: SqlFragment | string) => SqlFragment; /** * Wraps a single operand of a compound SELECT (UNION/INTERSECT/EXCEPT) so it * is a valid compound member for this dialect. The inner `SqlFragment` is a complete * leaf SELECT (which may carry its own WITH clause) or an already-combined * nested compound. * * @example * SQLite: SELECT * FROM (inner) // parenthesized operands are forbidden, * // but a WITH may live in a FROM-subquery * PostgreSQL: (inner) */ readonly wrapSetOperationOperand: (this: void, inner: SqlFragment) => SqlFragment; /** * Creates the initial path value for cycle detection. * * @example * SQLite: '|' || id || '|' * PostgreSQL: ARRAY[id] */ readonly initializePath: (this: void, nodeId: SqlFragment) => SqlFragment; /** Creates a JSON array of text scalar expressions, preserving delimiters verbatim. */ readonly textJsonArray: (this: void, values: readonly SqlFragment[]) => SqlFragment; /** Appends text scalar expressions to a JSON array. */ readonly appendTextJsonArray: (this: void, array: SqlFragment, values: readonly SqlFragment[]) => SqlFragment; /** * Extends a path with a new node ID. * * @example * SQLite: path || id || '|' * PostgreSQL: path || id */ readonly extendPath: (this: void, currentPath: SqlFragment, nodeId: SqlFragment) => SqlFragment; /** * Checks if a node ID is NOT already in the path (for cycle prevention). * Returns a condition that is TRUE if there is no cycle. * * @example * SQLite: INSTR(path, '|' || id || '|') = 0 * PostgreSQL: id != ALL(path) */ readonly cycleCheck: (this: void, nodeId: SqlFragment, path: SqlFragment) => SqlFragment; /** * Converts a value for SQL binding. * SQLite doesn't support booleans directly, so they must be converted to 0/1. * * @example * SQLite: true → 1, false → 0 * PostgreSQL: true → true (unchanged) */ readonly bindValue: (this: void, value: unknown) => unknown; /** * Returns a boolean literal for use in static SQL contexts (DDL, etc). * * @example * SQLite: sql.raw("1") or sql.raw("0") * PostgreSQL: sql.raw("TRUE") or sql.raw("FALSE") */ readonly booleanLiteral: (this: void, value: boolean) => SqlFragment; /** * Returns a boolean literal as a raw string for DDL generation. * * @example * SQLite: "1" or "0" * PostgreSQL: "TRUE" or "FALSE" */ readonly booleanLiteralString: (this: void, value: boolean) => string; /** * Quotes an identifier (table name, column name, alias) with proper escaping. * * @example * SQLite: "name" → "\"name\"", "foo\"bar" → "\"foo\"\"bar\"" * PostgreSQL: "name" → "\"name\"", "foo\"bar" → "\"foo\"\"bar\"" */ readonly quoteIdentifier: (this: void, name: string) => string; /** * Whether this dialect supports vector similarity predicates * (`field.similarTo(...)`) at compile time. The vector predicate pass * checks this before reaching for the backend's {@link VectorStrategy}; * the strategy owns the actual distance SQL (`distanceExpression`), so * no per-dialect distance/format method lives on the dialect anymore. */ readonly supportsVectors: boolean; /** * Pluggable fulltext implementation for this dialect. `undefined` when * the dialect does not support fulltext. The query compiler checks * `capabilities.supportsFulltext` before reaching for this and throws * if a fulltext predicate runs against a dialect without a strategy. */ readonly fulltext: FulltextStrategy | undefined; } /** Nominal identity shared by every package entrypoint. */ declare const SQL_FRAGMENT_BRAND: unique symbol; declare const SQL_PLACEHOLDER_BRAND: unique symbol; type SqlTextChunk = Readonly<{ kind: "text"; value: string; }>; type SqlParameterChunk = Readonly<{ kind: "parameter"; value: unknown; }>; type SqlIdentifierChunk = Readonly<{ kind: "identifier"; value: string; }>; type SqlPlaceholderChunk = Readonly<{ kind: "placeholder"; value: Placeholder; }>; /** One immutable unit in a database-independent SQL fragment. */ type SqlChunk = SqlTextChunk | SqlParameterChunk | SqlIdentifierChunk | SqlPlaceholderChunk; /** * An immutable, database-independent SQL expression. * * Fragments contain syntax and bound values as separate nodes. A database * adapter renders the same fragment with its native placeholder convention at * the final execution boundary. */ type SqlFragment = Readonly<{ [SQL_FRAGMENT_BRAND]: true; append: (fragment: SqlFragment) => SqlFragment; chunks: readonly SqlChunk[]; }>; /** A named value resolved when a prepared statement is executed. */ declare class Placeholder { readonly [SQL_PLACEHOLDER_BRAND]: true; readonly name: string; constructor(name: string); } /** Returns whether a value is a TypeGraph-owned SQL fragment. */ declare function isSqlFragment(value: unknown): value is SqlFragment; /** Returns whether a value is a TypeGraph named SQL placeholder. */ declare function isSqlPlaceholder(value: unknown): value is Placeholder; /** TypeGraph's database-independent SQL template and composition helpers. */ type SqlTag = Readonly<{ empty: () => SqlFragment; identifier: (value: string) => SqlFragment; join: (fragments: readonly SqlFragment[], separator?: SqlFragment) => SqlFragment; placeholder: (name: string) => SqlFragment; raw: (value: string) => SqlFragment; }> & ((strings: TemplateStringsArray, ...values: readonly unknown[]) => SqlFragment); /** TypeGraph's database-independent SQL template and composition helpers. */ declare const sql: SqlTag; /** A rendered statement and its ordered bound parameters. */ type RenderedSql = Readonly<{ sql: string; params: readonly unknown[]; }>; /** * Renders a fragment for a database adapter. * * Named placeholders remain in `params` when `bindings` is omitted, allowing * a prepared template to resolve them later. Passing `bindings` resolves every * placeholder eagerly. */ declare function renderSql(fragment: SqlFragment, dialect: SqlDialect, bindings?: Readonly>): RenderedSql; /** Renders a fragment using SQLite's `?` placeholders. */ declare function renderSqlite(fragment: SqlFragment, bindings?: Readonly>): RenderedSql; /** Renders a fragment using PostgreSQL's numbered placeholders. */ declare function renderPostgres(fragment: SqlFragment, bindings?: Readonly>): RenderedSql; /** * Renders a fragment with safe scalar literals instead of bound parameters. * * This is intentionally limited to DDL paths whose drivers cannot bind values. * Application queries should always use {@link renderSql}. */ declare function renderSqlInline(fragment: SqlFragment, dialect: SqlDialect): string; /** * The connection a `source()`/`revisionNow()` read runs on — the same * `Pick` shape * {@link LineageSession} (`./lineage.ts`) reuses for the identical reason: a * root backend and a `transaction()` handle both satisfy it, and * `TransactionBackend`'s two members are themselves `Pick` * projections of the same signatures. */ type RecordedTimeSession = Pick; /** * An engine-minted recorded-time revision: an opaque, engine-assigned * identifier paired with the ISO-8601 wall time the engine recorded * alongside it. Never parsed, ordered, or compared as a number — only ever * carried between `revisionNow` and `source`, and (through the engine * instant form built from it) compared by `recordedAt`. */ type EngineRecordedRevision = Readonly<{ /** Opaque engine revision identifier — URL-safe, non-empty, never parsed. */ revision: string; /** ISO-8601 wall time the engine recorded this revision at. */ recordedAt: string; }>; /** * The backend's engine-native recorded-time surface. Optional: a backend * that omits it is read under TypeGraph's own recorded-relations ownership * (see {@link resolveRecordedTimeOwnership} in `./recorded-time-ownership`). * * `source` names the table expression `table`'s rows are read from AS OF * `revision` — the engine's own temporal-table syntax, with the interval * already folded in, so it satisfies a recorded read binding's `source` * member (`RecordedReadSource`, `src/query/compiler/schema.ts`) with a * `predicate` that always returns `undefined`: the engine's `source` already * scopes every row to exactly one revision. * * `revisionNow` reads `session`'s own recorded-time revision — the * connection the CALLER's decision is bound to, not one this member opens * for itself, exactly as {@link LineageMembers}'s two members require * (`./lineage.ts`'s own doc comment states the full rationale: a commit-time * caller that already holds an open transaction passes that handle so the * read observes the transaction's own snapshot). What "own revision" means * depends on which session it is called with — the two call sites TypeGraph * makes never confuse them: * * - **On a root backend** (`store.recordedNow()`, `store.revisionNow()`): * the engine's current COMMITTED revision. * - **On an open `transaction()` handle** (both `TransactionReceipt.recorded` * sites, called before that transaction's own COMMIT): the revision at * which THIS transaction's writes will become visible once it commits — * the engine's pending/next revision for that session, not the last one * committed before it opened. TypeGraph stamps this uncommitted value * straight into the receipt it returns to the caller after the * transaction succeeds, trusting it to describe exactly the state that * commit produced. * * An engine that cannot name its own pending revision from inside an open * transaction — only its last-committed one — cannot supply `recordedTime`: * `TransactionReceipt.recorded` would then either lag one commit behind the * write it is supposed to describe, or require a second round trip after * COMMIT that reopens the race `recordedTime` exists to close. * * It is called at most once per transaction — the position TypeGraph's own * `flush()` occupies for a capture-owned store — never once per graph. */ type EngineRecordedTimeMembers = Readonly<{ /** * The table expression `table`'s recorded rows read from AS OF `revision`. * * `table` is never called with `"identityAssertions"` today: a recorded * identity read (`Store.identityAtCoordinate` and the query compiler's * historical identity traversal) is refused outright under engine-native * ownership before any read compiles * (`refuseEngineNativeRecordedIdentityRead`), and the recorded read schema * this member feeds (`recordedReadSqlSchema`) only ever sources `"nodes"` * and `"edges"`. An implementation still must handle the case — the union * is shared with the TypeGraph-relation-backed source, which every * revision does support — until a later engine-native identity-read seam * routes those reads through here instead of refusing them. */ source: (this: void, table: RecordedSourceTable, revision: EngineRecordedRevision) => SqlFragment; /** * `session`'s own recorded-time revision: the current committed one on a * root backend, or the pending revision an open transaction's writes will * land at once it commits. See this type's own doc comment for the full * contract. */ revisionNow: (this: void, session: RecordedTimeSession) => Promise; }>; declare const RECORDED_INSTANT_BRAND: unique symbol; /** * A versioned recorded-time anchor, in one of two forms sharing one grammar: * `::`. * * - `r1:0000000000000001:YYYY-MM-DDTHH:mm:ss.sssZ` — TypeGraph's own form. The * fixed-width logical revision is strictly monotonic per graph and makes * every commit addressable even when many commits share one wall-clock * millisecond. * - `e1::YYYY-MM-DDTHH:mm:ss.sssZ` — engine-native form, * minted by a backend that supplies `GraphBackend.recordedTime`. The * revision is an opaque token the engine itself assigns, never a TypeGraph * counter; ordering between two engine-native anchors compares the * timestamp only (see {@link compareRecordedInstants}). * * Either form's timestamp is a non-decreasing physical wall-time high-water * mark used by diagonal valid-time reads: same-millisecond commits repeat it, * and backward clock corrections hold it at the previous value until wall * time catches up. The string round-trips through plain string checkpoint * columns without a custom serializer. */ type RecordedInstant = string & { readonly [RECORDED_INSTANT_BRAND]: "RecordedInstant"; }; /** * The parsed halves of a TypeGraph-owned (`r1:`) recorded instant. Not * exported on its own — callers narrow {@link RecordedInstantParts} by * `kind` rather than naming either variant directly. */ type TypeGraphRecordedInstantParts = Readonly<{ kind: "typegraph"; revision: number; recordedAt: string; }>; /** * The parsed halves of an engine-native (`e1:`) recorded instant. Not * exported on its own — see {@link TypeGraphRecordedInstantParts}. */ type EngineRecordedInstantParts = Readonly<{ kind: "engine"; revision: string; recordedAt: string; }>; /** * The parsed halves of a {@link RecordedInstant}, discriminated by which * ownership form minted it. {@link parseRecordedInstant} is the one owner of * this grammar for both forms — a caller that needs the TypeGraph-only * numeric revision (or the engine-only opaque one) narrows on `kind` rather * than re-deriving the parse. */ type RecordedInstantParts = TypeGraphRecordedInstantParts | EngineRecordedInstantParts; /** * Returns the canonical UTC wall-time component of a recorded anchor. Works * for both ownership forms — every {@link RecordedInstantParts} variant * carries `recordedAt`. * * The value is non-decreasing per graph, but it is not the commit-order key; * use the complete {@link RecordedInstant} when ordering or replaying commits. */ declare function recordedInstantWallTime(instant: RecordedInstant): string; /** * Returns the strict per-graph logical revision carried by a TypeGraph-owned * (`r1:`) anchor. TypeGraph's numeric revision is meaningless for an * engine-native (`e1:`) anchor, so this refuses one rather than returning a * number parsed from an opaque token. * * @throws {ValidationError} when `instant` is an engine-native anchor. */ declare function recordedInstantRevision(instant: RecordedInstant): number; /** * Compares two recorded anchors of the SAME ownership form. * * For TypeGraph-owned (`r1:`) anchors, comparison is by logical revision: * revisions are local to a graph, so only compare anchors produced by the * same graph, and the physical wall-time component deliberately does not * participate. For engine-native (`e1:`) anchors there is no shared numeric * counter to compare — the engine's revision token is opaque — so comparison * falls back to the canonical ISO timestamp, which is lexicographically * ordered. * * Caveat for engine-native anchors: the timestamp has millisecond * resolution, so two distinct engine revisions minted within the same * millisecond compare equal here even though they are not the same commit. * TypeGraph-owned anchors have no such gap — their logical revision is a * strict per-commit counter — so this caveat is specific to the * timestamp-only fallback, not a general property of recorded-instant * comparison. * * @throws {ValidationError} when the two anchors were minted by different * ownership forms — there is no shared axis to compare them on. */ declare function compareRecordedInstants(left: RecordedInstant, right: RecordedInstant): -1 | 0 | 1; /** * Brands a canonical versioned anchor string as a {@link RecordedInstant}. * * The escape hatch for an instant that round-trips through untyped storage: * captured from {@link Store.recordedNow}, persisted as a plain string, read * back, and replayed into `asOfRecorded`. Validates the canonical form eagerly — * the same check `asOfRecorded` applies — so a malformed value fails here, at the * brand site, rather than deeper in a read. Does *not* assert the instant is a * real captured commit; it only guarantees the value is well-formed enough to * compare correctly against the recorded relations. * * @throws {ValidationError} when `value` is not a canonical versioned anchor. */ declare function asRecordedInstant(value: string): RecordedInstant; /** * The single opaque temporal coordinate every pinned read is resolved * against. It carries the valid-time axis plus an optional recorded/system-time * axis, so every surface can inject one coordinate object instead of threading * each temporal dimension separately. */ type ReadCoordinate = Readonly<{ /** * The valid-time axis: a resolved temporal mode and (only for `"asOf"`) the * instant it is pinned to. */ valid: Readonly<{ mode: TemporalMode; /** Defined only when `mode` is `"asOf"`. */ asOf?: string; }>; /** The recorded/system-time axis. Defined only for recorded-pinned views. */ recorded?: Readonly<{ asOf: RecordedInstant; }>; }>; /** * SQL Schema Configuration for Query Compilation * * Provides table and column identifiers that the query compiler uses. * This allows the compiler to work with custom table names instead of * hard-coded defaults. */ /** * Table names for TypeGraph SQL schema. * * Carries every customizable physical-table name the backend exposes, * including the secondary tables (`uniques`, `edgeClaims`, `fences`) that * the query compiler itself doesn't reference but `materializeRemovals` and * other cleanup paths (or, for `fences`, `resolveFenceStatements`'s * `row`-mechanism derivation) need to address by name. Backends without a * `uniques` table (custom embeddings-only stores) leave it as the * default — the cleanup path is a no-op for kinds with no unique * rows. */ type SqlTableNames = Readonly<{ /** Active schema version relation; absent on backends without checked reads. */ schemaVersions?: string | undefined; /** Nodes table name (default: "typegraph_nodes") */ nodes: string; /** Edges table name (default: "typegraph_edges") */ edges: string; /** Recorded node relation table name (default: "typegraph_recorded_nodes") */ recordedNodes?: string | undefined; /** Recorded edge relation table name (default: "typegraph_recorded_edges") */ recordedEdges?: string | undefined; /** Recorded-time commit clock table name (default: "typegraph_recorded_clock") */ recordedClock?: string | undefined; /** Durable per-graph revision-origin table name (default: "typegraph_revision_origins") */ revisionOrigins?: string | undefined; /** Identity assertion ledger (default: "typegraph_identity_assertions") */ identityAssertions?: string | undefined; /** Recorded identity assertion relation */ recordedIdentityAssertions?: string | undefined; /** Derived current identity closure */ identityClosure?: string | undefined; /** Derived separation relation over identity classes */ identitySeparation?: string | undefined; /** Node fulltext table name (default: "typegraph_node_fulltext") */ fulltext: string; /** Node uniques table name (default: "typegraph_node_uniques") */ uniques: string; /** Edge cardinality claim table name (default: "typegraph_edge_claims") */ edgeClaims?: string | undefined; /** * Write-fence rows table name (default: "typegraph_fences") — the * never-dropped relation a `row`-mechanism write fence acquires a keyed * exclusion against. Part of the base schema on every backend, whether or * not any target ever declares `writeFence.mechanism: "row"`. */ fences?: string | undefined; }>; type ResolvedSqlTableNames = Readonly<{ /** Active schema version relation; absent on backends without checked reads. */ schemaVersions?: string; /** Nodes table name */ nodes: string; /** Edges table name */ edges: string; /** Recorded node relation table name */ recordedNodes: string; /** Recorded edge relation table name */ recordedEdges: string; /** Recorded-time commit clock table name */ recordedClock: string; /** Durable per-graph revision-origin table name */ revisionOrigins: string; identityAssertions: string; recordedIdentityAssertions: string; identityClosure: string; identitySeparation: string; /** Node fulltext table name */ fulltext: string; /** Node uniques table name */ uniques: string; /** Edge cardinality claim table name */ edgeClaims: string; /** Write-fence rows table name */ fences: string; }>; type SqlSchemaFields = Readonly<{ /** Table names */ tables: ResolvedSqlTableNames; /** Get a `SqlFragment` reference to the nodes table. */ nodesTable: SqlFragment; /** Get a `SqlFragment` reference to the edges table. */ edgesTable: SqlFragment; /** Get a `SqlFragment` reference to the recorded node relation. */ recordedNodesTable: SqlFragment; /** Get a `SqlFragment` reference to the recorded edge relation. */ recordedEdgesTable: SqlFragment; /** Get a `SqlFragment` reference to the recorded-time commit clock. */ recordedClockTable: SqlFragment; /** Get a `SqlFragment` reference to the durable per-graph revision origins. */ revisionOriginsTable: SqlFragment; /** Get a `SqlFragment` reference to the identity assertion ledger. */ identityAssertionsTable: SqlFragment; /** Get a `SqlFragment` reference to the recorded identity assertion relation. */ recordedIdentityAssertionsTable: SqlFragment; /** Get a `SqlFragment` reference to the derived identity closure. */ identityClosureTable: SqlFragment; /** Get a `SqlFragment` reference to the derived identity separation relation. */ identitySeparationTable: SqlFragment; /** Get a `SqlFragment` reference to the fulltext table. */ fulltextTable: SqlFragment; }>; /** * SQL schema configuration for query compilation. * Contains table identifiers and utility methods for generating `SqlFragment` references. * * Branded and frozen by {@link createSqlSchema}; callers should not construct * schema-shaped objects by hand. */ declare abstract class SqlSchema implements SqlSchemaFields { private readonly typeGraphSqlSchemaBrand; abstract readonly tables: ResolvedSqlTableNames; abstract readonly nodesTable: SqlFragment; abstract readonly edgesTable: SqlFragment; abstract readonly recordedNodesTable: SqlFragment; abstract readonly recordedEdgesTable: SqlFragment; abstract readonly recordedClockTable: SqlFragment; abstract readonly revisionOriginsTable: SqlFragment; abstract readonly identityAssertionsTable: SqlFragment; abstract readonly recordedIdentityAssertionsTable: SqlFragment; abstract readonly identityClosureTable: SqlFragment; abstract readonly identitySeparationTable: SqlFragment; abstract readonly fulltextTable: SqlFragment; } /** * Creates a SqlSchema configuration from table names. * * Table names are validated to ensure they are valid SQL identifiers. * This prevents SQL injection and ensures compatibility across databases. * * @param names - Optional custom table names (defaults to standard names) * @returns SqlSchema configuration for query compilation * @throws Error if any table name is invalid * * @example * ```typescript * // Use default table names * const schema = createSqlSchema(); * * // Use custom table names * const schema = createSqlSchema({ * nodes: "myapp_nodes", * edges: "myapp_edges", * fulltext: "myapp_fulltext", * }); * ``` */ declare function createSqlSchema(names?: Partial): SqlSchema; /** * The recorded/system-time relation a read coordinate may reconstruct from. * * TypeGraph's built-in capture relation is bound by `createStore(..., { * history: true })`. Hosts can also bind externally populated row-compatible * recorded relations through `recordedRelation({ schema })`. Keeping both as * explicit values separates the read contract from the write-capture mechanism * so future external/TMS-owned recorded relations can feed the same query * machinery without changing StoreView or ReadCoordinate. */ declare const EXTERNAL_RECORDED_READ_SOURCE: unique symbol; /** * The relation a {@link RecordedReadSource} sources rows from: the two entity * tables the query compiler swaps to for a recorded read, and the identity * assertion ledger the identity reconstruction path consults. */ type RecordedSourceTable = "nodes" | "edges" | "identityAssertions"; /** * The one seam every recorded read consults instead of spelling the relation * swap and the recorded-time interval itself. * * `source` names the relation (or table expression) holding `table`'s * recorded rows for `revision`. TypeGraph's own capture and external * bindings both return the matching recorded relation regardless of * `revision` — the relation carries every revision, and `predicate` narrows * it afterward. A binding whose `source` already scopes its rows to exactly * one revision (an engine-native temporal table expression, say) returns * `undefined` from `predicate` instead of re-spelling a redundant filter. * * `carriesInterval` names the other fact every binding-shape-aware reader * needs: whether `source`'s rows carry the `recorded_from`/`recorded_to` * columns a TypeGraph-relation-backed source's every revision has. A reader * that needs to order or filter on that interval (`recorded-read-service.ts`'s * point-read and scan `ORDER BY`) consults this instead of re-deriving the * fact from `binding.kind` itself, so a fourth binding kind cannot leave one * site still assuming a column the new binding's source does not have. */ type RecordedReadSource = Readonly<{ source: (table: RecordedSourceTable, revision: RecordedInstantParts) => SqlFragment; predicate: (prefix: SqlFragment, revision: RecordedInstantParts) => SqlFragment | undefined; carriesInterval: boolean; }>; type ExternalRecordedReadSource = Readonly<{ kind: "external"; schema: SqlSchema; [EXTERNAL_RECORDED_READ_SOURCE]: true; }> & RecordedReadSource; declare const TYPEGRAPH_RECORDED_READ_SOURCE: unique symbol; type TypeGraphRecordedReadSource = Readonly<{ kind: "typegraph-capture"; schema: SqlSchema; [TYPEGRAPH_RECORDED_READ_SOURCE]: true; }> & RecordedReadSource; declare const ENGINE_RECORDED_READ_SOURCE: unique symbol; /** * The recorded read binding for a backend that owns recorded time itself * (`GraphBackend.recordedTime`, `backend/capabilities/recorded-time.ts`). * `source` defers to `recordedTime.source`, converting the parsed * engine-native revision into the `EngineRecordedRevision` shape that member * expects; `predicate` always returns `undefined` — the engine's own source * expression already scopes every row to exactly one revision, so there is no * separate interval to layer on top the way the TypeGraph-relation source * needs one. */ type EngineRecordedReadSource = Readonly<{ kind: "engine-native"; schema: SqlSchema; [ENGINE_RECORDED_READ_SOURCE]: true; }> & RecordedReadSource; type RecordedReadBinding = ExternalRecordedReadSource | TypeGraphRecordedReadSource | EngineRecordedReadSource; type RecordedRelationOptions = Readonly<{ schema: SqlSchema; }>; declare function recordedRelation(options: RecordedRelationOptions): ExternalRecordedReadSource; /** * Default SqlSchema using standard TypeGraph table names. */ declare const DEFAULT_SQL_SCHEMA: SqlSchema; /** * The compiler's resolved view of one declared embedding field — the * `(dimensions, metric, indexType)` a {@link VectorStrategy} needs to * name and scan the field's typed per-`(kind, field)` storage. Sourced * from the registered node schema's `embedding()` declaration when the * store builds its compile options. */ type VectorSlotDescriptor = Readonly<{ dimensions: number; metric: VectorMetric; indexType: VectorIndexType; }>; /** * Map of declared embedding slots keyed by {@link vectorSlotKey} - * `"\0"` (NUL-separated). Carries every `(concrete kind, * fieldPath)` that declares an embedding field, so the compiler's * `field.similarTo(...)` CTE can UNION ALL the per-field tables for the * kinds in an alias that actually declare the field (only * `includeSubClasses` yields more than one). */ type VectorSlotMap = ReadonlyMap; /** * Vector Strategy — pluggable storage + SQL generation for a backend's * vector stack. The sibling of {@link FulltextStrategy}: a strategy owns * every statement that touches its embedding storage — the per-field DDL, * upsert, delete, similarity search, and (optional) ANN index lifecycle — * plus the capability advertisement and the distance/score expressions the * query compiler splices into its relevance CTE. * * ## Why a strategy, and why per-(kind, field) storage * * The spike behind #157 established that "real" ANN on every engine we * support converges on a * **typed, fixed-dimension structure per `(nodeKind, fieldPath)`**: * * - pgvector: a `vector(N)` column + HNSW/IVFFlat; * - libSQL native: an `F32_BLOB(N)` column + `libsql_vector_idx` + * `vector_top_k` (a plain `BLOB` or dimensionless `F32_BLOB` is rejected * by the index — the dimension must live in the column type); * - sqlite-vec: a `vec0(embedding float[N])` virtual table. * * The legacy single shared `typegraph_node_embeddings` table (one generic * column holding mixed-dimension vectors) can only ever be brute-forced; * pgvector was the sole engine that hid this, because its index supplies the * dimension via a `::vector(N)` cast expression. So storage is the * strategy's to own, slot by slot, rather than a fixed global table. * * Brute force remains a legitimate, capability-advertised mode (libSQL's * `vector_distance_cos` over a column with no index, sqlite-vec's * `vec_distance_cosine`): a strategy whose `capabilities.indexTypes` is * `["none"]` simply never emits an ANN index and `buildSearch` always scans. */ /** * `logicalName` prefix of a strategy-owned vector slot. Each * `(nodeKind, fieldPath)` pair fills one logical vector slot; the full * logical name is `${VECTOR_CONTRIBUTION_PREFIX}:${nodeKind}.${fieldPath}`, * stable across table-name overrides and strategy swaps so #135's durable * materialization marker survives both. */ declare const VECTOR_CONTRIBUTION_PREFIX = "vector"; /** * The resolved identity of one embedding field's storage in a graph — * everything a strategy needs to DDL, address, and index it. * * Graph-scoped: each `(graphId, nodeKind, fieldPath)` gets its own physical * table. TypeGraph supports many graphs per physical database, and the same * `(kind, field)` can carry different embedding dimensions across graphs — a * shared per-`(kind, field)` table would collide on the fixed column type * (`vector(N)` / `F32_BLOB(N)`). Graph-scoping also makes libSQL's * table-global `vector_top_k` per-graph-exact (no cross-graph recall bleed). */ type VectorSlot = Readonly<{ /** Graph the embedding belongs to — scopes the physical table. */ graphId: string; /** Node kind owning the embedding field (e.g. `"Document"`). */ nodeKind: string; /** Dot-path of the embedding field within the node props (e.g. `"embedding"`). */ fieldPath: string; /** Fixed vector dimension `N` for this field — carried into the column type. */ dimensions: number; /** Distance metric the field's index is built for. */ metric: VectorMetric; /** * Index type to materialize. `"none"` means brute-force only (no ANN * index emitted); the strategy still stores and searches the field. */ indexType: VectorIndexType; /** * Optional ANN index tuning carried into the index DDL (pgvector * `m`/`ef_construction`/`lists`). Present on the create-index / re-embed * paths (resolved from the field's `embedding()` declaration); omitted on * the write/search ensure paths, where the strategy falls back to defaults. */ indexParams?: Readonly<{ m?: number; efConstruction?: number; lists?: number; }>; }>; /** * Derives the `VectorCapabilities` a backend advertises from its active * strategy, so the two never drift (mirrors `buildFulltextCapabilities`). * The strategy is the single source of truth; there are no per-call-site * `SQLITE_VECTOR_*` constants. */ declare function buildVectorCapabilities(strategy: VectorStrategy): VectorCapabilities; /** * A pluggable vector implementation. Each strategy is self-contained: * given a {@link VectorSlot}, it emits every statement the backend and * compiler need — per-field storage DDL, writes, similarity search, ANN * index lifecycle — and advertises exactly the metrics and index types it * can honor. Adding the Nth backend is one of these objects; no core edits. */ type VectorStrategy = Readonly<{ /** Human-readable identifier used in error messages and telemetry. */ name: string; /** * The metrics, index types, and dimension ceiling this strategy honors — * advertised verbatim as `backend.capabilities.vector`. Asymmetry across * engines is legitimate and explicit here (pgvector has `inner_product`; * libSQL/sqlite-vec do not), never a silent runtime failure. */ capabilities: VectorCapabilities; /** * Deterministic physical table (or virtual-table) name backing a field in * a graph. The compiler references this to scan the right per-field storage, * and the backend uses it to route upserts/deletes. Must be a stable, * collision-safe SQL identifier derived from `(graphId, nodeKind, fieldPath)`. */ tableName: (this: void, graphId: string, nodeKind: string, fieldPath: string) => string; /** * The per-field storage this strategy owns for `slot`, as Drizzle-free * `StrategyTableContribution`s (resolved `tableName`, deterministic * idempotent `createDdl` for the table **and** its ANN index when the * slot's `indexType` warrants one, `runtimeEnsure`). Rides the #129/#135 * table-contribution + durable-materialization machinery exactly as the * FTS5 / tsvector virtual tables do — these are materialized per graph by * `materializeIndexes()`, not by global `bootstrapTables`. */ ownedTables: (this: void, slot: VectorSlot) => readonly StrategyTableContribution[]; /** * Emits the statement(s) that upsert a single embedding into the slot's * storage. Multiple statements are allowed for engines that cannot upsert * a vector in one statement (e.g. a `vec0` virtual table → DELETE+INSERT). */ buildUpsert: (this: void, slot: VectorSlot, params: UpsertEmbeddingParams, timestamp: string) => readonly SqlFragment[]; /** * Emits one atomic upsert sourced from a node `RETURNING` CTE. * * The source alias is backend-owned SQL (normally `inserted_node`), never * caller input. The strategy must take graph/node identity from the source * row rather than from the embedding parameters, so a node insert and its * sidecar cannot disagree about identity. Strategies whose storage needs * more than one statement omit this capability and use {@link buildUpsert} * on the ordinary sidecar path. */ buildUpsertFromInsertedNode?: (this: void, slot: VectorSlot, sourceAlias: string, embedding: readonly number[], timestamp: string) => SqlFragment; /** * Emits the statement(s) that upsert MANY embeddings into the slot's * storage in multi-row form. Optional — the backend falls back to one * {@link buildUpsert} per row when unset. The backend guarantees the * rows carry distinct `nodeId`s and fit the connection's bound-parameter * budget (it chunks before calling). */ buildUpsertBatch?: (this: void, slot: VectorSlot, params: UpsertEmbeddingBatchParams, timestamp: string) => readonly SqlFragment[]; /** Emits the statement(s) that delete a single embedding from the slot. */ buildDelete: (this: void, slot: VectorSlot, params: DeleteEmbeddingParams) => readonly SqlFragment[]; /** Emits one set-based delete for many node ids in a vector slot. */ buildDeleteBatch: (this: void, slot: VectorSlot, params: Omit & Readonly<{ nodeIds: readonly string[]; }>) => readonly SqlFragment[]; /** * Raw DDL statement(s) that drop the slot's entire physical storage * (table + ANN index, and any engine-managed shadow tables). Used by the * destructive `store.reembedVectorField()` path to recreate a field's * storage at a new dimension. Returned as raw strings (like * `ownedTables(...).createDdl`) for `backend.executeDdl`; must be idempotent * (`IF EXISTS`). */ buildDropStorage: (this: void, slot: VectorSlot) => readonly string[]; /** * Emits the similarity-search query for the `backend.vectorSearch` path, * returning rows shaped `{ node_id, score }` ordered best-first. The * strategy picks brute-force vs ANN based on whether `slot.indexType` * materialized an index — the caller never branches. `score` follows the * shared convention (cosine → similarity `1 - distance`; l2 / * inner_product → raw distance), so `coerceVectorScore` / fusion stay * dialect-neutral. * * `candidates`, when provided, is a subquery yielding the node ids * eligible to appear in results (the backend passes its live-node-ids * subquery so top-k is computed over live rows in SQL — see * `liveNodeIdsSubquery`). Strategies whose ANN form cannot take the * filter directly must over-fetch and post-filter, documenting the * recall bound. A custom strategy that ignores the argument keeps the * pre-pushdown behavior: tombstoned ids are dropped after top-k during * hydration, so results can shrink below `limit` under index drift. */ buildSearch: (this: void, slot: VectorSlot, params: VectorSearchParams, candidates?: SqlFragment) => SqlFragment; /** * True when {@link buildSearch} returns EXACT rankings — a brute-force * engine form, not an approximate index (sqlite-vec's vec0 KNN scans * every row in C). The query compiler then routes the NON-approximate * `.similarTo()` branch through `buildSearch` too: same results as the * SQL distance scan, at engine speed (measured 489ms -> 113ms at 50k * on the SQLite lane). Leave false/absent when the engine form is or * can be approximate (pgvector planner rewrites, libSQL DiskANN): * exactness of the default path is a semantic guarantee. */ searchIsExact?: boolean; /** * The distance expression over the slot's embedding column, used by the * **query compiler** to splice vector relevance into its CTE. This is the * one genuinely engine-specific fragment (`vec_distance_cosine` vs * `<=>` vs `vector_distance_cos`); the surrounding score / minScore / * ORDER BY math is shared (see {@link vectorScoreExpression} etc.). * * `embeddingColumn` is the already-qualified column SQL; `queryEmbedding` * is formatted by the strategy into its engine's literal form. */ distanceExpression: (this: void, embeddingColumn: SqlFragment, queryEmbedding: readonly number[], metric: VectorMetric) => SqlFragment; /** * Emits the ANN index creation statement for a slot, or `undefined` when * indexing is inline (vec0) or unsupported (brute-force-only strategies). * Invoked through `backend.createVectorIndex` during `materializeIndexes`. */ buildCreateIndex?: (this: void, slot: VectorSlot, options?: Readonly<{ concurrent?: boolean; }>) => SqlFragment | undefined; /** Emits the ANN index drop statement, or `undefined` when not applicable. */ buildDropIndex?: (this: void, slot: VectorSlot) => SqlFragment | undefined; }>; /** * Converts a distance expression into a score expression (higher = better). * Cosine distance is mapped to similarity (`1 - d`); l2 and inner_product * are returned as-is (lower distance already ranks better, ordered ASC). */ declare function vectorScoreExpression(distanceExpression: SqlFragment, metric: VectorMetric): SqlFragment; /** * Builds the `minScore` WHERE condition against a distance expression, * translating the score threshold back into the metric's distance space. */ declare function vectorMinScoreCondition(distanceExpression: SqlFragment, metric: VectorMetric, minScore: number): SqlFragment; /** * Validates that every value in an embedding is a finite number. Shared by * strategies so a NaN/Infinity is reported with the offending index before * it reaches engine-specific literal formatting (which would mask it). */ declare function assertFiniteEmbedding(embedding: readonly number[], name: string): void; /** * Validates a vector-search `limit` is a positive integer. Enforced by the * backend's `vectorSearch` (defense in depth — the store boundary also * checks) so a direct backend call with `limit: 0` fails loudly instead of * silently scanning nothing. */ declare function assertVectorSearchLimit(limit: number): void; /** * Validates a `minScore` floor against its (resolved) metric: it must be finite, * and for cosine it must lie in [-1, 1] (a cosine score is the `1 - distance` * similarity). Shared by the store facade, the compiler's relevance CTE * ({@link vectorMinScoreCondition}), and the backend search path so every entry * point rejects the same out-of-range floor instead of silently returning none. */ declare function assertVectorMinScore(minScore: number, metric: VectorMetric, label?: string): void; /** * Deterministic 8-char hash for collision-safe truncation of over-long * identifiers. Self-contained so the strategy layer has no backend-runtime * dependency. */ declare function shortHash(input: string): string; /** * Deterministic per-`(graphId, nodeKind, fieldPath)` physical name. Shared by * every strategy's `tableName`, by index naming, and by the compiler's * per-field table resolution so all three agree on which physical object backs * a field in a graph. Truncated with a hash suffix past the 63-char ceiling; * the hash covers all three parts, so truncation stays collision-safe even * when long graph ids dominate the prefix. * * @example `vectorPhysicalName("tg_vec", "g1", "Document", "embedding")` * → `"tg_vec_g1_document_embedding_"` */ declare function vectorPhysicalName(prefix: string, graphId: string, nodeKind: string, fieldPath: string): string; /** * Double-quote a SQL identifier, escaping embedded quotes. Dialect-neutral * (both SQLite and Postgres use `"..."`), so the three vector strategies share * one implementation for quoting their per-field table / index names. */ declare function quoteIdentifier(name: string): string; declare const SqlIntentBrand: unique symbol; type SqlIntent = "rows" | "statement" | "temporary-statement"; type IntentSql = SqlFragment & Readonly<{ [SqlIntentBrand]: I; }>; type CompiledRowsSql = IntentSql<"rows">; type CompiledSelectSql = CompiledRowsSql; type CompiledStatementSql = IntentSql<"statement">; type CompiledTemporaryStatementSql = IntentSql<"temporary-statement">; declare const ALL_META_EDGE_NAMES: readonly ["subClassOf", "broader", "narrower", "relatedTo", "equivalentTo", "sameAs", "differentFrom", "disjointWith", "partOf", "hasPart", "inverseOf", "implies"]; type MetaEdgeName = (typeof ALL_META_EDGE_NAMES)[number]; /** * Pure-value document format for graph extensions. * * A `GraphExtension` is a plain JSON-serializable description of * additional node kinds, edge kinds, and ontology relations that should * be merged into a graph at runtime. The document is the canonical * artifact: every restart re-compiles it back to the same Zod-bearing * `NodeType` / `EdgeType` / `OntologyRelation` shapes. * * The supported property-type subset is intentionally narrower than full * JSON Schema — only what compiles cleanly to Zod and what real * agent-induced schemas use in practice. Anything outside this set fails * loudly at `defineGraphExtension(...)`. */ /** * Per-property modifier: tag the field as fulltext-searchable. * * Compiles to a `searchable({ language })`-wrapped Zod string when applied * to a `string` property. Rejected on any other property type at validation * time. */ type ExtensionSearchableModifier = Readonly<{ language?: string; }>; /** * Per-property modifier: declare a vector embedding with the given * dimensionality. Compiles to `embedding(dimensions)` and is only valid on * `array` properties whose item type is `number`. Rejected elsewhere. */ type ExtensionEmbeddingModifier = Readonly<{ dimensions: number; }>; /** * Modifiers shared by every graph-extension property type. * * `optional: true` flips the field from required-with-graph-extension-validation * to `.optional()` and removes it from the parent object's `required` * list. `searchable` and `embedding` only apply to specific underlying * types — see the per-modifier docs. */ type ExtensionPropertyModifiers = Readonly<{ optional?: boolean; searchable?: ExtensionSearchableModifier; embedding?: ExtensionEmbeddingModifier; description?: string; }>; /** * String property. Compiles to `z.string()` plus the requested refinements. * `format` accepts the two formats motivated by induced schemas in * practice — covers ISO datetimes, URIs, email addresses, UUIDs, and * date-only strings. Each format routes to the corresponding Zod * factory (`z.iso.datetime()`, `z.url()`, `z.email()`, `z.uuid()`, * `z.iso.date()`); other JSON-Schema formats are deliberately not * supported in v1. */ type ExtensionStringProperty = Readonly<{ type: "string"; minLength?: number; maxLength?: number; pattern?: string; format?: "datetime" | "uri" | "email" | "uuid" | "date"; }> & ExtensionPropertyModifiers; /** * Number property. `int: true` requires whole numbers; `min` / `max` are * inclusive bounds. Compiles to `z.number().int()?.min(...)?.max(...)`. */ type ExtensionNumberProperty = Readonly<{ type: "number"; min?: number; max?: number; int?: boolean; }> & ExtensionPropertyModifiers; /** * Boolean property. Compiles to `z.boolean()`. */ type ExtensionBooleanProperty = Readonly<{ type: "boolean"; }> & ExtensionPropertyModifiers; /** * Closed-set string enum. Compiles to `z.enum([...values])`. Must contain * at least one value; duplicate values are rejected at validation time. */ type ExtensionEnumProperty = Readonly<{ type: "enum"; values: readonly string[]; }> & ExtensionPropertyModifiers; /** * Array property. `items` is any of the scalar property types or an * object property — nested arrays are forbidden in v1. Compiles to * `z.array()`. The `embedding` modifier turns this into a * vector embedding instead of a generic array. */ type ExtensionArrayProperty = Readonly<{ type: "array"; items: ExtensionArrayItemType; }> & ExtensionPropertyModifiers; /** * Element types allowed inside an array — every leaf property type plus * single-level object. Nesting an array inside an array is rejected at * validation time so the v1 surface stays a flat tree. */ type ExtensionArrayItemType = ExtensionStringProperty | ExtensionNumberProperty | ExtensionBooleanProperty | ExtensionEnumProperty | ExtensionObjectProperty; /** * Object property. `properties` is a single nesting level; deeper objects * are rejected at validation time so the v1 surface is auditable at a * glance. */ type ExtensionObjectProperty = Readonly<{ type: "object"; properties: Readonly>; }> & ExtensionPropertyModifiers; /** * Property types allowed inside an `object`'s `properties` — leaf scalars * and arrays only. Nested objects are blocked here to enforce the * single-nesting-level rule. */ type ExtensionObjectFieldProperty = ExtensionStringProperty | ExtensionNumberProperty | ExtensionBooleanProperty | ExtensionEnumProperty | ExtensionArrayProperty; /** * Top-level property descriptor for a node or edge field. * * The discriminated union covers every type in the v1 subset; arbitrary * `unknown` inputs are validated against this set at * `defineGraphExtension(...)` time. */ type ExtensionPropertyType = ExtensionStringProperty | ExtensionNumberProperty | ExtensionBooleanProperty | ExtensionEnumProperty | ExtensionArrayProperty | ExtensionObjectProperty; type ExtensionDefinedOutput

= P extends ExtensionStringProperty ? string : P extends ExtensionNumberProperty ? number : P extends ExtensionBooleanProperty ? boolean : P extends ExtensionEnumProperty ? P["values"][number] : P extends ExtensionArrayProperty ? readonly ExtensionPropertyOutput[] : P extends ExtensionObjectProperty ? ExtensionObjectOutput : never; /** TypeScript value represented by one graph-extension property descriptor. */ type ExtensionPropertyOutput

= P extends { optional: true; } ? ExtensionDefinedOutput

| undefined : ExtensionDefinedOutput

; /** TypeScript object represented by a graph-extension property map. */ type ExtensionObjectOutput

>> = Readonly<{ [K in keyof P as P[K] extends { optional: true; } ? never : K]: ExtensionPropertyOutput; } & { [K in keyof P as P[K] extends { optional: true; } ? K : never]?: ExtensionPropertyOutput; }>; type ExtensionPropertySchema

= P extends { optional: true; } ? z.ZodOptional>> : z.ZodType>; /** Phantom Zod object type used by sound runtime-kind evidence. */ type ExtensionObjectSchema

>> = z.ZodObject<{ [K in keyof P]: ExtensionPropertySchema; }>; /** Property map represented by an edge definition, including the empty default. */ type ExtensionEdgeProperties = D extends (Readonly<{ properties: infer P extends Readonly>; }>) ? P : Readonly>; /** * Document-side `where` clause for a unique constraint. * * Mirrors `serializeWherePredicate`'s capability — the only operations * round-trippable through the persisted form are `isNull` and `isNotNull`. * Anything richer (equality, `in`, etc.) is rejected at validation time. */ type ExtensionUniqueWhere = Readonly<{ field: string; op: NullCheckOp; }>; /** * Unique constraint declaration. `fields` must reference declared * properties on the kind. Defaults match the existing `UniqueConstraint`: * `scope: "kind"`, `collation: "binary"`. */ type ExtensionUniqueConstraint = Readonly<{ name: string; fields: readonly string[]; scope?: "kind" | "kindWithSubClasses"; collation?: "binary" | "caseInsensitive"; where?: ExtensionUniqueWhere; }>; /** * Graph-extension declaration of a node kind. * * Property names follow the same reserved-key rules as compile-time * `defineNode` (`id`, `kind`, `meta`, and the `$`-prefix accessor * namespace are forbidden). Annotation values must be JSON. */ type ExtensionNodeDef = Readonly<{ description?: string; annotations?: KindAnnotations; properties: Readonly>; unique?: readonly ExtensionUniqueConstraint[]; }>; /** * Graph-extension declaration of an edge kind. * * `from` / `to` reference node kind names — either kinds declared in this * same document or compile-time kinds the document is being merged into. * Endpoints that resolve to nothing within the document are flagged as * soft references at validation time but not rejected; the final * cross-graph check happens when the document is merged into a host * `GraphDef`. */ type ExtensionEdgeDef = Readonly<{ description?: string; annotations?: KindAnnotations; from: readonly string[]; to: readonly string[] | Readonly>; properties?: Readonly>; }>; /** * Document-level analogue of compile-time `defineNodeIndex` / * `defineEdgeIndex`. Mirrors `serializeWherePredicate`'s persistence- * round-trippable subset: only `isNull` / `isNotNull` predicates are * supported in v1, matching `ExtensionUniqueWhere`. */ type ExtensionIndexWhere = Readonly<{ field: string; op: NullCheckOp; }>; /** * Graph-extension-declared node index. `kind` references either a kind * declared in this same document or a compile-time host kind resolved * at merge time. `fields` and `coveringFields` are top-level property * names — JSON-pointer paths are not supported in v1, matching the * `ExtensionUniqueConstraint` v1 surface. */ type ExtensionNodeIndex = Readonly<{ entity: "node"; kind: string; name?: string; fields: readonly string[]; coveringFields?: readonly string[]; unique?: boolean; scope?: "graphAndKind" | "graph" | "none"; where?: ExtensionIndexWhere; }>; /** * Graph-extension-declared edge index. `direction` mirrors * `EdgeIndexDirection`; `kind` references a graph-extension or compile-time * edge kind. */ type ExtensionEdgeIndex = Readonly<{ entity: "edge"; kind: string; name?: string; direction?: "out" | "in" | "none"; fields: readonly string[]; coveringFields?: readonly string[]; unique?: boolean; scope?: "graphAndKind" | "graph" | "none"; where?: ExtensionIndexWhere; }>; type ExtensionIndex = ExtensionNodeIndex | ExtensionEdgeIndex; /** * Graph-extension ontology relation. `metaEdge` is one of the built-in * meta-edge names (subClassOf, broader, disjointWith, etc.). * * `from` / `to` are either node-kind names declared in this document or * external IRI strings. The pure-value document does not distinguish the * two — the compiler treats endpoints that match a declared kind as * `NodeType` references and falls back to passing the raw string for * IRIs (matching the existing `OntologyRelation` shape, where * `from`/`to` are `NodeType | EdgeType | string`). */ type ExtensionOntologyRelation = Readonly<{ metaEdge: MetaEdgeName; from: string; to: string; }>; /** * Stable default major used when a stored document omits `version`. * * Pinned to `1` permanently. Pre-versioning documents (those persisted * before the field existed) and documents that explicitly omit * `version` are interpreted as `1` regardless of which major the * library currently supports. Splitting this from * `CURRENT_GRAPH_EXTENSION_VERSION` is load-bearing for future major * bumps: when v2 ships, `CURRENT` becomes `2` but `LEGACY` stays at * `1`, so a v1-era stored document still parses as v1 (and the * version-mismatch path can route it through a migration) rather than * being silently misinterpreted as v2. */ declare const LEGACY_GRAPH_EXTENSION_VERSION: 1; /** * Current major version of the `GraphExtension` format. * * Documents with a higher major version than this constant are * rejected with `GRAPH_EXTENSION_VERSION_UNSUPPORTED` — there is no * automatic downgrade path. Minor / additive changes ride forward- * compat via `.loose()` on every nested object schema. */ declare const CURRENT_GRAPH_EXTENSION_VERSION: 1; /** * Type of the `GraphExtension.version` field. Stays `number` * rather than `typeof CURRENT_GRAPH_EXTENSION_VERSION` because the * field can carry any major across the library version range a * stored document might have been written by — the * document-vs-supported check happens in the validator, not at the * type level. Pinning to the current literal would prevent a v1 * v1 library from typing a v2 document at all, which is the wrong * relationship: we WANT a v1 library to receive v2 documents and * report `GRAPH_EXTENSION_VERSION_UNSUPPORTED` cleanly. */ type GraphExtensionVersion = number; /** * The canonical pure-value graph-extension document. * * Frozen at construction. Round-trips losslessly through `JSON.stringify` * / `JSON.parse`; the `GraphExtension → CompiledExtension` direction * is provided by `compileGraphExtension(...)`. There is no * `Zod → GraphExtension` direction because graph-extension kinds always * originate as documents. * * `version` is the major-version tag for the document format. The * compiler accepts documents whose version is equal to the current * supported major (today: 1) or absent (the canonical persisted form * omits `version` when it equals the legacy default — see * `serializer.ts` — so the round-trip default is "absent means legacy * major"). Higher majors surface as * `GRAPH_EXTENSION_VERSION_UNSUPPORTED` so a newer-version extension * committed by a future writer can't be silently misread by an older * runtime. See `graph-extensions.md` for the format-versioning policy. */ type GraphExtension = Readonly<{ version?: GraphExtensionVersion; /** Graph-scoped annotations shallow-merged into the host graph by key. */ annotations?: GraphAnnotations; nodes?: Readonly>; edges?: Readonly>; ontology?: readonly ExtensionOntologyRelation[]; /** * Graph-extension-declared relational indexes. Each entry references a * node or edge kind by name — either declared in this document or * a compile-time host kind that the document is being merged into. * Vector indexes auto-derive from `embedding()` modifiers on * graph-extension kinds; this slot is for explicit relational indexes * (analogue of compile-time `defineGraph({ indexes: [...] })`). */ indexes?: readonly ExtensionIndex[]; }>; /** * Top-level v1 slots a `GraphExtension` document may carry. Single * source of truth for both the strict-authoring validator (typo rejection * via `defineGraphExtension`) and the persistence Zod schema (which * leaves the field set loose for forward compatibility but uses this * list to drive the Zod object shape). */ declare const GRAPH_EXTENSION_TOP_LEVEL_KEYS: readonly ["version", "annotations", "nodes", "edges", "ontology", "indexes"]; type GraphExtensionTopLevelKey = (typeof GRAPH_EXTENSION_TOP_LEVEL_KEYS)[number]; type IndexScope = /** * Prefix index keys with `(graph_id, kind)` (nodes) or `(graph_id, kind)` (edges). * * This matches TypeGraph queries which always filter on `graph_id` and `kind` * (often as `IN (...)` due to ontology expansion). */ "graphAndKind" /** * Prefix index keys with `graph_id` only. */ | "graph" /** * Do not prefix index keys with TypeGraph system columns. */ | "none"; /** * Index access method for relational (node/edge) index declarations. * * - `"btree"` — the default ordered index over the compiled extraction * expressions; serves equality/range predicates and `orderBy`. * - `"gin"` — a PostgreSQL expression GIN over the field's jsonb value * (`jsonb_path_ops`); serves the array containment predicates * (`contains` / `containsAll` / `containsAny` on array fields), which a * btree can never serve. Skipped on SQLite. * - `"trigram"` — a PostgreSQL expression GIN over the field's text value * (`gin_trgm_ops`, requires the `pg_trgm` extension); serves substring * and case-insensitive matches (`contains` / `startsWith` / `endsWith` / * `like` / `ilike` on string fields). Skipped on SQLite, whose substring * search story is FTS5 fulltext. * * `"gin"` and `"trigram"` take exactly one field and reject `unique`, * `coveringFields`, and `where`; `scope` (and edge `direction`) prefix * columns do not apply — the query's `graph_id` / `kind` equality filters * are applied as residual conditions over the index's candidate rows. */ type RelationalIndexMethod = "btree" | "gin" | "trigram"; type IndexWhereOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "notIn"; type IndexWhereExpression = Readonly<{ __type: "index_where_and"; predicates: readonly IndexWhereExpression[]; }> | Readonly<{ __type: "index_where_or"; predicates: readonly IndexWhereExpression[]; }> | Readonly<{ __type: "index_where_not"; predicate: IndexWhereExpression; }> | Readonly<{ __type: "index_where_comparison"; left: IndexWhereOperand; op: IndexWhereOp; right: IndexWhereLiteral | readonly IndexWhereLiteral[]; }> | Readonly<{ __type: "index_where_null_check"; operand: IndexWhereOperand; op: NullCheckOp; }>; type IndexWhereOperand = Readonly<{ __type: "index_operand_system"; column: SystemColumnName; valueType: ValueType | undefined; }> | Readonly<{ __type: "index_operand_prop"; field: string; valueType: ValueType | undefined; }>; type IndexWhereLiteral = Readonly<{ __type: "index_where_literal"; value: string | number | boolean; valueType: ValueType; }>; type IndexWhereComparableValue = NonNullable extends string ? string : NonNullable extends number ? number : NonNullable extends boolean ? boolean : NonNullable extends Date ? Date | string : never; type IndexWhereFieldBuilder = Readonly<{ eq: (value: IndexWhereComparableValue) => IndexWhereExpression; neq: (value: IndexWhereComparableValue) => IndexWhereExpression; gt: (value: IndexWhereComparableValue) => IndexWhereExpression; gte: (value: IndexWhereComparableValue) => IndexWhereExpression; lt: (value: IndexWhereComparableValue) => IndexWhereExpression; lte: (value: IndexWhereComparableValue) => IndexWhereExpression; in: (values: readonly IndexWhereComparableValue[]) => IndexWhereExpression; notIn: (values: readonly IndexWhereComparableValue[]) => IndexWhereExpression; isNull: () => IndexWhereExpression; isNotNull: () => IndexWhereExpression; }>; type NodeIndexWhereBuilder = Readonly<{ graphId: IndexWhereFieldBuilder; kind: IndexWhereFieldBuilder; id: IndexWhereFieldBuilder; deletedAt: IndexWhereFieldBuilder; validFrom: IndexWhereFieldBuilder; validTo: IndexWhereFieldBuilder; createdAt: IndexWhereFieldBuilder; updatedAt: IndexWhereFieldBuilder; version: IndexWhereFieldBuilder; } & { [K in keyof z.infer]-?: IndexWhereFieldBuilder[K]>; }>; type EdgeIndexWhereBuilder = Readonly<{ graphId: IndexWhereFieldBuilder; kind: IndexWhereFieldBuilder; id: IndexWhereFieldBuilder; fromKind: IndexWhereFieldBuilder; fromId: IndexWhereFieldBuilder; toKind: IndexWhereFieldBuilder; toId: IndexWhereFieldBuilder; deletedAt: IndexWhereFieldBuilder; validFrom: IndexWhereFieldBuilder; validTo: IndexWhereFieldBuilder; createdAt: IndexWhereFieldBuilder; updatedAt: IndexWhereFieldBuilder; } & { [K in keyof z.infer]-?: IndexWhereFieldBuilder[K]>; }>; type IndexWhereInput = IndexWhereExpression | ((where: Builder) => IndexWhereExpression); type NonEmptyJsonPointerFor = Exclude, "">; type NonEmptyJsonPointerSegmentsFor = Exclude, readonly []>; type IndexFieldInput = (keyof T & string) | NonEmptyJsonPointerFor | NonEmptyJsonPointerSegmentsFor | JsonPointer; /** Node system columns accepted by {@link NodeIndexKeyInput}. */ declare const NODE_SYSTEM_COLUMN_NAMES: readonly ["graph_id", "kind", "id", "deleted_at", "valid_from", "valid_to", "created_at", "updated_at", "version"]; type NodeSystemColumnName = (typeof NODE_SYSTEM_COLUMN_NAMES)[number]; type NodeIndexKeyInput = Readonly<{ field: IndexFieldInput; system?: never; direction: "asc" | "desc"; }> | Readonly<{ field?: never; system: NodeSystemColumnName; direction: "asc" | "desc"; }>; type NodeIndexConfig = Readonly<{ coveringFields?: readonly IndexFieldInput>[] | undefined; name?: string | undefined; scope?: IndexScope | undefined; where?: IndexWhereInput> | undefined; }> & (Readonly<{ /** Ordered B-tree keys. */ keys: readonly [ NodeIndexKeyInput>, ...NodeIndexKeyInput>[] ]; fields?: never; keySystemColumns?: never; unique?: false | undefined; method?: "btree" | undefined; }> | Readonly<{ keys?: never; /** * Prop-based key fields. May be empty (or omitted) only if * `coveringFields` or `keySystemColumns` supplies at least one key * column instead. */ fields?: readonly IndexFieldInput>[] | undefined; /** * System columns to include after the scope prefix and before * `fields`/`coveringFields`. Node indexes reject edge-only endpoint * columns and columns already implied by `scope`. */ keySystemColumns?: readonly SystemColumnName[] | undefined; unique?: boolean | undefined; /** Index access method. Default: `"btree"`. */ method?: RelationalIndexMethod | undefined; }>); type EdgeIndexDirection = "out" | "in" | "none"; type EdgeIndexConfig = Readonly<{ fields: readonly [ IndexFieldInput>, ...IndexFieldInput>[] ]; coveringFields?: readonly IndexFieldInput>[] | undefined; unique?: boolean | undefined; name?: string | undefined; scope?: IndexScope | undefined; /** * Optional direction hint to prefix edge indexes with the join key that * TypeGraph traversal queries use (`from_id` for out, `to_id` for in). */ direction?: EdgeIndexDirection | undefined; where?: IndexWhereInput> | undefined; /** Index access method. Default: `"btree"`. See {@link RelationalIndexMethod}. */ method?: RelationalIndexMethod | undefined; }>; /** * Where an index declaration originated. * * - `compile-time`: declared via `defineNodeIndex` / `defineEdgeIndex` and * threaded through `defineGraph({ indexes })`. This is the default and is * omitted from the canonical schema document so legacy graphs hash * byte-identically. * - `runtime`: produced by a graph extension. Always emitted explicitly * so the loader can re-route the declaration through the extension * compiler on restart. */ type IndexOrigin = "compile-time" | "runtime"; /** * Common shape shared by node and edge index declarations. * * `IndexDeclaration` is the canonical, JSON-serializable representation of * an index that flows through `GraphDef.indexes` and * `SerializedSchema.indexes`. It carries everything the DDL compiler and * the Drizzle schema factories need to generate index SQL — the same * value can come from a typed builder (`defineNodeIndex` / * `defineEdgeIndex`) or be reconstructed from a graph extension on * restart. */ type IndexDeclarationBase = Readonly<{ /** Unique index name (used in DDL and as the diffing identity key). */ name: string; /** * Where this declaration originated. * * `undefined` is the canonical representation of `"compile-time"` — * the default origin is omitted from the serialized form so legacy * graphs (no `indexes` slice) hash byte-identically with new graphs * that declare only compile-time indexes. */ origin?: IndexOrigin; fields: readonly JsonPointer[]; fieldValueTypes: readonly (ValueType | undefined)[]; coveringFields: readonly JsonPointer[]; coveringFieldValueTypes: readonly (ValueType | undefined)[]; unique: boolean; scope: IndexScope; where: IndexWhereExpression | undefined; /** * Index access method. Absent means `"btree"` — canonicalized by * absence (like `origin`) so serialized declarations and * materialization signatures from before this field existed stay * byte-identical. */ method?: Exclude; }>; type NodeIndexDeclaration = IndexDeclarationBase & Readonly<{ entity: "node"; kind: string; /** * System columns included in the index key (see * {@link NodeIndexConfig.keySystemColumns}). Absent means none — * canonicalized by absence like `origin`/`method` so declarations * that don't use this stay byte-identical to before it existed. */ keySystemColumns?: readonly SystemColumnName[]; /** Ordered B-tree keys; absent for legacy field-key declarations. */ keys?: readonly (Readonly<{ type: "field"; pointer: JsonPointer; valueType: ValueType | undefined; direction: "asc" | "desc"; }> | Readonly<{ type: "system"; column: "graph_id" | "kind" | "id" | "deleted_at" | "valid_from" | "valid_to" | "created_at" | "updated_at" | "version"; direction: "asc" | "desc"; }>)[]; }>; type NodeIndexKey = NonNullable[number]; type EdgeIndexDeclaration = IndexDeclarationBase & Readonly<{ entity: "edge"; kind: string; direction: EdgeIndexDirection; }>; /** * Distance metric for vector similarity. Mirrors `EmbeddingMetric` from * `core/embedding.ts` (re-exported here as part of the index surface). */ type VectorIndexMetric = "cosine" | "l2" | "inner_product"; /** * Vector index implementation. `none` is a declarative opt-out: the * declaration carries shape metadata for tooling but `materializeIndexes` * skips the DDL. */ type VectorIndexImplementation = "hnsw" | "ivfflat" | "none"; /** * Vector-index parameters. Concrete defaults are applied at the * `embedding(...)` brand boundary; this carries them onto the * declaration so the materializer / signature / drift detection have * everything they need without re-resolving from the brand. */ type VectorIndexParams = Readonly<{ /** HNSW: max connections per layer. */ m: number; /** HNSW: build-time search depth. */ efConstruction: number; /** IVFFlat: number of inverted lists. `undefined` when not IVFFlat. */ lists: number | undefined; }>; /** * Vector index declaration. Auto-derived from `embedding()` brands at * `defineGraph()` time and explicitly buildable via `defineVectorIndex`. * * Identity key is `(kind, fieldPath)` — v1 allows at most one vector * index per (kind, field) pair. The `name` field is generated * deterministically from this tuple plus the metric so consumers don't * accidentally collide vector index names with relational indexes. * * `unique` / `scope` / `where` from the relational base are NOT * supported on vector — pgvector / sqlite-vec don't implement them. */ type VectorIndexDeclaration = Readonly<{ entity: "vector"; /** Index name (also the physical identity key in the materialization status table). */ name: string; origin?: IndexOrigin; /** Node kind the embedding lives on. */ kind: string; /** JSON-pointer-style field path for the embedding inside the node's props. */ fieldPath: string; /** Embedding dimensionality. */ dimensions: number; /** Distance metric. */ metric: VectorIndexMetric; /** Index implementation. */ indexType: VectorIndexImplementation; /** Concrete index parameters. */ indexParams: VectorIndexParams; }>; /** * Relational subset of `IndexDeclaration` — the variants that emit * `CREATE INDEX` DDL via `generateIndexDDL`. Used to narrow input * types in the relational DDL / serializer / migration code paths * that don't apply to vector indexes (which use a different * materialization primitive on the backend). */ type RelationalIndexDeclaration = NodeIndexDeclaration | EdgeIndexDeclaration; /** * A serializable index declaration that flows through `GraphDef.indexes` * and `SerializedSchema.indexes`. * * Discriminated by `entity`. Everything is JSON round-trippable so a * declaration produced by `defineNodeIndex` and a declaration * reconstructed from a stored schema document compile to byte-identical * SQL. */ type IndexDeclaration = RelationalIndexDeclaration | VectorIndexDeclaration; type SystemColumnName = "graph_id" | "kind" | "id" | "from_kind" | "from_id" | "to_kind" | "to_id" | "deleted_at" | "valid_from" | "valid_to" | "created_at" | "updated_at" | "version"; /** * Schema migration utilities. * * Provides diff detection between schema versions to identify * what has changed and what migrations might be needed. */ /** * Types of changes that can occur in a schema. */ type ChangeType = "added" | "removed" | "modified" | "renamed"; /** * Severity of a change for migration purposes. */ type ChangeSeverity = "safe" | "warning" | "breaking"; /** * A change to a node definition. */ type NodeChange = Readonly<{ type: ChangeType; kind: string; severity: ChangeSeverity; details: string; before?: SerializedNodeDef | undefined; after?: SerializedNodeDef | undefined; }>; /** * A change to an edge definition. */ type EdgeChange = Readonly<{ type: ChangeType; kind: string; severity: ChangeSeverity; details: string; before?: SerializedEdgeDef | undefined; after?: SerializedEdgeDef | undefined; }>; /** * A change to the ontology. */ type OntologyChange = Readonly<{ type: ChangeType; entity: "metaEdge" | "relation"; name: string; severity: ChangeSeverity; details: string; }>; /** A durable graph-level Operational Identity capability change. */ type IdentityChange = Readonly<{ type: ChangeType; severity: ChangeSeverity; details: string; }>; /** A safe metadata-only change to graph-scoped annotations. */ type GraphAnnotationsChange = Readonly<{ type: ChangeType; severity: "safe"; details: string; }>; /** * A change to an index declaration. * * Index changes are always `safe`-severity: index DDL is materialized * separately and never blocks schema-version commits or migrations. * Adding, removing, or modifying an index never invalidates existing * data — it only changes which physical indexes the deployment will * materialize on its next pass. */ type IndexChange = Readonly<{ type: ChangeType; /** Index name (the diffing identity key). */ name: string; /** * Whether this index is on a node, edge, or vector field. Vector * index changes flow through the same diff classification as * relational ones. */ entity: IndexEntity; severity: ChangeSeverity; details: string; before?: IndexDeclaration | undefined; after?: IndexDeclaration | undefined; }>; /** * A change to the persisted graph-extension document. * * Graph-extension document changes are committed only through the * graph-extension lifecycle verbs (`evolve` and `removeKinds`), so the * extension-slice change itself is `safe`-severity. The detailed * per-kind effect is captured in the corresponding node/edge/ontology * changes the merged document produced. */ type ExtensionChange = Readonly<{ type: ChangeType; severity: ChangeSeverity; details: string; }>; /** * Change to the soft-deprecated kind set. `safe`-severity by * construction — deprecation is a metadata signal that doesn't gate * reads, writes, or queries. The `added` and `removed` arrays carry * the per-name deltas so consumers can render granular diffs. */ type DeprecatedKindsChange = Readonly<{ added: readonly string[]; removed: readonly string[]; severity: ChangeSeverity; details: string; }>; /** * A complete diff between two schema versions. */ type SchemaDiff = Readonly<{ /** Version of the schema being compared from. */ fromVersion: number; /** * Version of the schema being compared to. For `getSchemaChanges()`, this * is the version a commit would produce and equals `fromVersion` when * `hasChanges` is false. Direct `computeSchemaDiff()` calls preserve the * supplied after-schema version. */ toVersion: number; /** Changes to node definitions */ nodes: readonly NodeChange[]; /** Changes to edge definitions */ edges: readonly EdgeChange[]; /** Changes to ontology */ ontology: readonly OntologyChange[]; /** Change to the graph-level identity capability, if any. */ identity?: IdentityChange; /** Change to consumer-owned graph-scoped annotations, if any. */ annotations?: GraphAnnotationsChange; /** Changes to index declarations */ indexes: readonly IndexChange[]; /** * Change to the graph-extension document, if any. `undefined` when * the slice is unchanged on both sides (the common case). */ extension?: ExtensionChange; /** * Change to the soft-deprecated kind set, if any. `undefined` when * the set is unchanged on both sides. */ deprecatedKinds?: DeprecatedKindsChange; /** Whether any breaking changes exist */ hasBreakingChanges: boolean; /** Whether the change is backwards compatible (no breaking changes) */ isBackwardsCompatible: boolean; /** Whether any changes exist at all */ hasChanges: boolean; /** Summary of changes */ summary: string; }>; /** * Computes the diff between two schema versions. * * @param before - The previous schema version * @param after - The new schema version * @returns A diff describing all changes */ declare function computeSchemaDiff(before: SerializedSchema, after: SerializedSchema): SchemaDiff; /** * Checks if a schema change is backwards compatible. * * A change is backwards compatible if: * - No nodes or edges were removed * - No required properties were added * - No existing properties were removed */ declare function isBackwardsCompatible(diff: SchemaDiff): boolean; /** * How a proposed graph relates to the committed schema. * * - `identical` — a semantic no-op; committing it changes nothing. * - `additive` — changes exist and are all backwards compatible. * - `incompatible` — at least one breaking change; needs a deliberate * migration decision. */ type SchemaChangeClassification = "identical" | "additive" | "incompatible"; /** * Classifies a schema diff into the three outcomes a caller actually branches * on. Pure — no I/O, no DDL. Pair with `getSchemaChanges(backend, graph)` (or * `store.schemaChanges()`) to pre-flight a proposal *before* touching a * privileged, migration-gated path. */ declare function classifySchemaChanges(diff: SchemaDiff): SchemaChangeClassification; /** * Gets a list of actions needed for migration. */ declare function getMigrationActions(diff: SchemaDiff): readonly string[]; /** * A physical index's presence and, on engines that can leave a build * half-finished, whether the leftover is usable. * * `invalid` is PostgreSQL's `pg_index.indisvalid = false` — an interrupted * `CREATE INDEX CONCURRENTLY` leaves a same-named index behind that a later * `IF NOT EXISTS` build would otherwise accept silently. SQLite has no such * state: `invalid` is always `false` there, and `exists` alone is the whole * answer. */ type IndexState = Readonly<{ /** The physical SQL index name that was probed. */ name: string; /** Whether an index with this name exists in the engine catalog. */ exists: boolean; /** PostgreSQL's invalid-build leftover flag; always `false` on SQLite. */ invalid: boolean; }>; /** * A physical column's declared type, reduced to the family the two callers * that classify a recorded-time column actually distinguish: an integer * counter, text (SQLite's affinity for a wall-clock column stored as an * ISO-8601 string), PostgreSQL's exact `timestamp with time zone`, or * anything else. Kept this coarse on purpose — no caller needs a finer * classification, and each dialect's own probe decides which of these a * given declared type maps to, using that engine's own type rules (exact * match on PostgreSQL, affinity on SQLite). * * PostgreSQL's own normalizer never reports `"text"` — a declared `text` * column on that dialect falls through to `"other"`, since nothing * PostgreSQL-side stores a recorded-time column that way. That asymmetry is * why a revision or wall-time comparison must compare against * {@link REVISION_COLUMN_KINDS} / {@link WALL_TIME_COLUMN_KINDS} rather than * a single literal: the two dialects agree on the revision kind but not on * the wall-time one. */ type NormalizedColumnKind = "integer" | "text" | "timestamp-with-time-zone" | "other"; /** * One physical column's name, normalized type family, and raw declared * type — trimmed and lower-cased, but otherwise exactly what the engine's * own catalog reports (`"bigint"`, `"timestamp with time zone"`, a SQLite * declared type such as `"integer"` or `"text"`, and so on). `kind` is what * a comparison should classify against; `declaredType` is what a * diagnostic should show a human, since `kind` discards the declared * spelling entirely. */ type CatalogColumn = Readonly<{ name: string; kind: NormalizedColumnKind; declaredType: string; }>; /** One physical table's catalog presence. See {@link BackendCatalogProbes.tablesExist}. */ type TableState = Readonly<{ /** The physical SQL table name that was probed. */ name: string; /** Whether a table with this name exists in the engine catalog. */ exists: boolean; }>; /** * The three facts index materialization used to keep in its own * dialect-keyed record: whether this engine can build an index * concurrently (without blocking readers/writers), whether a concurrent * build can be interrupted into a usable-but-invalid leftover, and whether * it offers the GIN index family fulltext/trigram indexing needs. */ type CatalogIndexBehavior = Readonly<{ concurrentBuilds: boolean; hasInvalidIndexState: boolean; supportsGinFamily: boolean; }>; /** * Physical-schema introspection a store path consults directly: table and * index presence, PostgreSQL's invalid-index leftover state and its * self-heal, normalized column types, and the per-dialect index-build * facts above. * * Optional: a custom backend that omits it loses the store paths that * consult it directly — index materialization (`store.materializeIndexes()` * refuses only once its empty-candidate short circuit and the status-table * ensure step have already run; `store.materializeSystemIndexes()`, which * has no candidate short circuit, refuses only once that same status-table * ensure step has run), the recorded-time schema check, and the * recorded-time migration's column read. */ type BackendCatalogProbes = Readonly<{ /** * Whether a table with this physical name exists in the engine catalog — * on PostgreSQL, anything an unqualified `DELETE`/`ANALYZE` against the * name could hit (ordinary and partitioned tables, views, materialized * views, foreign tables), matching the dialect operation strategy's own * DDL-target probe. A caller that means specifically "is this a TABLE", * as opposed to any relation an unqualified statement could resolve to, * wants {@link BackendCatalogProbes.tablesExist} instead — its narrower * predicate excludes views, materialized views, and foreign tables. */ tableExists: (this: void, name: string) => Promise; /** * The catalog state of each named physical TABLE, one entry per input * name (an absent name reports `exists: false`), resolved in one round * trip. On PostgreSQL this is ordinary and partitioned tables only * (`relkind IN ('r', 'p')`) — narrower than {@link tableExists}, which * also matches views, materialized views, and foreign tables. A caller * gating bulk, table-scoped work (a preload ahead of table DDL, say) * wants this member so it gets one round trip and the strict predicate, * rather than looping {@link tableExists} or accepting its wider match. */ tablesExist: (this: void, names: readonly string[]) => Promise; /** * The catalog state of each named physical index, one entry per input * name (an absent index reports `exists: false`), resolved in one * round trip. */ indexStates: (this: void, names: readonly string[]) => Promise; /** * Drops the named index if — and only if — it is a PostgreSQL INVALID * leftover from an interrupted `CREATE INDEX CONCURRENTLY`. A no-op on * an engine whose `indexBehavior.hasInvalidIndexState` is `false`, and * a no-op for a valid or absent index on every engine. * * A ROOT-BACKEND operation on an engine with an invalid-index state: * PostgreSQL refuses `DROP INDEX CONCURRENTLY` inside a transaction * block, so there the `catalog` a `transaction()` handle exposes throws a * typed `ConfigurationError` from this member instead of attempting the * DDL. An engine whose `indexBehavior.hasInvalidIndexState` is `false` * (SQLite) has no invalid leftover to ever drop and stays a no-op in * both scopes — every other member here stays a plain read on that same * transaction-scoped bag regardless of engine. */ dropInvalidIndex: (this: void, name: string) => Promise; /** Every column's name and normalized type family for one physical table. */ columnTypes: (this: void, table: string) => Promise; /** This engine's index-build facts. See {@link CatalogIndexBehavior}. */ indexBehavior: CatalogIndexBehavior; }>; /** Brand key for MetaEdge */ declare const META_EDGE_BRAND: "__metaEdge"; /** * How a meta-edge affects queries and validation. */ type InferenceType = "subsumption" | "hierarchy" | "substitution" | "constraint" | "composition" | "association" | "none"; /** * Properties of a meta-edge. */ type MetaEdgeProperties = Readonly<{ transitive: boolean; symmetric: boolean; reflexive: boolean; inverse: string | undefined; inference: InferenceType; description: string | undefined; }>; /** * A meta-edge definition. * * Meta-edges represent type-level relationships (between kinds), * not instance-level relationships (between nodes). */ type MetaEdge = Readonly<{ [META_EDGE_BRAND]: true; name: K; properties: MetaEdgeProperties; }>; /** * A relation in the ontology (instance of meta-edge between types). * * @example * ```typescript * // Podcast subClassOf Media * subClassOf(Podcast, Media) * * // Person equivalentTo schema:Person * equivalentTo(Person, "https://schema.org/Person") * ``` */ type OntologyRelation = Readonly<{ metaEdge: MetaEdge; from: NodeType | AnyEdgeType | string; to: NodeType | AnyEdgeType | string; }>; /** * Checks if a value is a MetaEdge. */ declare function isMetaEdge(value: unknown): value is MetaEdge; /** Brand key for GraphDef */ declare const GRAPH_DEF_BRAND: "__graphDef"; /** * An edge entry in the graph definition. * Can be: * - EdgeType directly (constrained or unconstrained) * - EdgeRegistration object (always works, can override/narrow defaults) */ type EdgeEntry = EdgeRegistration | AnyEdgeType; /** * Normalized edge map type - all entries become EdgeRegistration. * For bare EdgeTypes, constrained endpoints are extracted from the type; * unconstrained edges fall back to all node types in the graph. */ type NormalizedEdges, TEdges extends Record> = { [K in keyof TEdges]: TEdges[K] extends EdgeRegistration ? TEdges[K] : TEdges[K] extends AnyEdgeType ? EdgeRegistration) ? N : TNodes[keyof TNodes]["type"], TEdges[K]["to"] extends EdgeTargets ? TEdges[K]["to"] : readonly TNodes[keyof TNodes]["type"][]> : never; }; /** Durable graph-level configuration for the TypeGraph Identity Profile. */ type GraphIdentityConfig = Readonly<{ /** Whether equal ids in different kinds implicitly join one identity class. */ sameIdAcrossKinds: "fold" | "ignore"; }>; /** * Configuration for defineGraph. */ type GraphDefConfig, TEdges extends Record, TOntology extends readonly OntologyRelation[], TIdentity extends GraphIdentityConfig | undefined> = Readonly<{ /** Unique identifier for this graph */ id: string; /** Consumer-owned JSON metadata describing the graph as a whole. */ annotations?: GraphAnnotations; /** Node registrations */ nodes: TNodes; /** Edge registrations or EdgeTypes with built-in domain/range */ edges: TEdges; /** Ontology relations */ ontology?: TOntology; /** Graph-wide defaults */ defaults?: GraphDefaults; /** Enables the TypeGraph Identity Profile for this graph. */ identity?: TIdentity; /** * Index declarations attached to this graph. * * Accepts the outputs of `defineNodeIndex` / `defineEdgeIndex` (which * already return `IndexDeclaration` values) or pre-built declarations * reconstructed from a stored schema or graph extension. * * Validated at definition time: every index must reference a `kind` * that exists in `nodes` / `edges`, and index `name`s must be unique * within the graph. * * Index DDL is **not** generated by `defineGraph`. Indexes flow into * `SerializedSchema.indexes` so they can later be materialized via * `store.materializeIndexes()` — lifting the storage concern out of * the schema-version commit path. */ indexes?: readonly IndexDeclaration[]; }>; /** * A graph definition. * * This is a compile-time artifact that describes the structure of a graph. * Use `createStore()` to create a runtime store from this definition. */ type GraphDef = Record, TEdges extends Record = Record, TOntology extends readonly OntologyRelation[] = readonly OntologyRelation[], TIdentity extends GraphIdentityConfig | undefined = GraphIdentityConfig | undefined> = Readonly<{ [GRAPH_DEF_BRAND]: true; id: string; /** Consumer-owned JSON metadata describing the graph as a whole. */ annotations: GraphAnnotations | undefined; nodes: TNodes; edges: TEdges; ontology: TOntology; /** Durable graph-level opt-in to the TypeGraph Identity Profile. */ identity: TIdentity; defaults: Readonly<{ onNodeDelete: DeleteBehavior; temporalMode: TemporalMode; }>; /** * Index declarations attached to this graph. Preserves whatever the * caller passed to `defineGraph` (including an explicit empty array) * for introspection purposes. * * The serialized canonical form is order-canonicalized (sorted by * `name`) and treats `undefined` and `[]` as the same "no slice" form * — an empty array does not bump the schema hash, since indexes are * an unordered set keyed by name and `[]` carries no semantic meaning * that an absent slice doesn't. */ indexes: readonly IndexDeclaration[] | undefined; /** * Graph extension this graph was merged with, if any. Set by * `mergeGraphExtension`; never set by `defineGraph` directly. * Exists solely so re-serialization is stable — the rest of the * system reads the merged kinds through `nodes` / `edges` / * `ontology` and never inspects this field. Absent on graphs that * have never been extended; legacy graphs hash byte-identically. */ extension: GraphExtension | undefined; /** * Soft-deprecated kind names attached to this graph. Set by the * loader from the persisted schema and by `store.deprecateKinds(...)` * / `store.undeprecateKinds(...)`. A purely informational signal * surfaced for introspection — does not gate reads, writes, or * queries. Defaults to the empty set on freshly-defined graphs. */ deprecatedKinds: ReadonlySet; }>; /** * Extract node kind names from a GraphDef. */ type NodeKinds = keyof G["nodes"] & string; /** * Extract edge kind names from a GraphDef. */ type EdgeKinds = keyof G["edges"] & string; /** * Get a NodeType from a GraphDef by kind name. */ type GetNodeType> = G["nodes"][K]["type"]; /** * Get an EdgeType from a GraphDef by kind name. */ type GetEdgeType> = G["edges"][K]["type"]; /** * Get all NodeTypes from a GraphDef. */ type AllNodeTypes = { [K in NodeKinds]: G["nodes"][K]["type"]; }[NodeKinds]; /** * Get all EdgeTypes from a GraphDef. */ type AllEdgeTypes = { [K in EdgeKinds]: G["edges"][K]["type"]; }[EdgeKinds]; /** * Creates a graph definition. * * @example * ```typescript * const graph = defineGraph({ * id: "my_graph", * nodes: { * Person: { type: Person }, * Company: { type: Company }, * }, * edges: { * // Traditional EdgeRegistration syntax * worksAt: { * type: worksAt, * from: [Person], * to: [Company], * cardinality: "many", * }, * // Or use EdgeType directly if it has from/to defined * knows, // EdgeType with built-in domain/range * }, * ontology: [ * subClassOf(Company, Organization), * disjointWith(Person, Organization), * ], * defaults: { * onNodeDelete: "restrict", * temporalMode: "current", * }, * }); * ``` */ declare function defineGraph>, const TEdges extends Record, const TOntology extends readonly OntologyRelation[], const TIdentity extends GraphIdentityConfig | undefined = undefined>(config: GraphDefConfig): GraphDef, TOntology, TIdentity>; /** * Checks if a value is a GraphDef. */ declare function isGraphDef(value: unknown): value is GraphDef; /** * Gets all node kind names from a GraphDef. */ declare function getNodeKinds(graph: G): readonly (keyof G["nodes"] & string)[]; /** * Gets all edge kind names from a GraphDef. */ declare function getEdgeKinds(graph: G): readonly (keyof G["edges"] & string)[]; /** * Index materialization — runs declared index DDL against the live * database and tracks per-deployment status in * `typegraph_index_materializations`. * * Reads `IndexDeclaration[]` from `GraphDef.indexes`, generates DDL via * `generateIndexDDL`, executes via `backend.executeDdl` (Postgres path * uses `CREATE INDEX CONCURRENTLY`, which cannot run inside a * transaction — `executeDdl` runs at the top-level backend, never inside * `transaction(...)`), and upserts a status row per index. * * Caveats baked in to the algorithm: * * - SQL index names are physical, database-global identifiers. * Cross-graph collisions (two graphs declaring the same name with * different shapes) surface as a `failed` result with reason * "signature drift", because the existing recorded signature won't * match the new one. * - On Postgres, `CREATE INDEX CONCURRENTLY IF NOT EXISTS` does NOT * prove the existing physical index has the same shape as ours — * only that something with that name exists. Drift detection here * relies on TypeGraph's recorded signature, not on PG metadata. * - Failed `CONCURRENTLY` builds leave invalid indexes behind * (`pg_index.indisvalid = false`). Relational rebuilds self-heal: the * claim-holding materializer drops an invalid leftover with the * declaration's name before rebuilding (see the backend's * `catalog.dropInvalidIndex`, called from materializeWithClaim). * Vector per-field index leftovers remain operator-repair. * - Two materializers racing the SAME index name serialize through a * durable claim in the status table (see materializeWithClaim) — * concurrent same-name expression-index CIC builds deadlock on * Postgres (no safe-snapshot exemption). */ type MaterializeIndexesOptions = Readonly<{ /** Restrict to indexes whose `kind` is in this set. */ kinds?: readonly string[]; /** Stop on the first failure. Default: false (best-effort). */ stopOnError?: boolean; /** * Refresh planner statistics (ANALYZE) after at least one index was * created. A fresh index can be ignored by the planner until statistics * exist. Default: true. Applied on non-concurrent builders (SQLite) and * on concurrent builders that serialize same-index builds via the * cross-caller claim primitive (the bundled Postgres backend); a custom * concurrent backend without the primitive skips the refresh — see * refreshStatisticsAfterCreation — and should call * `store.refreshStatistics()` after materializing. */ refreshStatistics?: boolean; }>; /** * Per-index outcome from `materializeIndexes()`. * * Status values: * - `created`: DDL ran successfully and a new physical index now exists. * - `alreadyMaterialized`: status table shows a prior successful * materialization with the same signature; no DDL ran. * - `failed`: the DDL or status write failed; `error` carries the * captured exception. Best-effort mode continues to the next index; * `stopOnError: true` halts. * - `skipped`: the backend can't materialize this index variant in its * current configuration (e.g. vector indexes against SQLite without * sqlite-vec, or `indexType: "none"` declared on an embedding). The * declaration is recognized but intentionally not acted on. Status * table is NOT updated for skipped entries. */ type MaterializeIndexesEntry = Readonly<{ indexName: string; entity: IndexEntity; kind: string; status: "created" | "alreadyMaterialized" | "failed" | "skipped"; error?: Error; /** * Human-readable reason. Required for `skipped`; optional otherwise. */ reason?: string; }>; type MaterializeIndexesResult = Readonly<{ results: readonly MaterializeIndexesEntry[]; }>; type MaterializeSystemIndexesOptions = Readonly<{ /** Stop on the first failure. Default: false (best-effort). */ stopOnError?: boolean; /** Refresh planner statistics after a creation. Default: true. */ refreshStatistics?: boolean; }>; /** * TypeGraph Error Hierarchy * * All errors extend TypeGraphError with: * - `code`: Machine-readable error code for programmatic handling * - `category`: Classification for error handling strategies * - `suggestion`: Optional recovery guidance for users * - `details`: Structured context about the error * * @example * ```typescript * try { * await store.nodes.Person.create({ name: "" }); * } catch (error) { * if (isTypeGraphError(error)) { * console.error(error.toUserMessage()); * if (isUserRecoverable(error)) { * // Show to user for correction * } * } * } * ``` */ /** * Error category for programmatic handling. * * - `user`: Caused by invalid input or incorrect usage. Recoverable by fixing input. * - `constraint`: Business rule or schema constraint violation. Recoverable by changing data. * - `system`: Internal error or infrastructure issue. May require investigation or retry. */ type ErrorCategory = "user" | "constraint" | "system"; /** * Options for TypeGraphError constructor. */ type TypeGraphErrorOptions = Readonly<{ /** Structured context about the error */ details?: Record; /** Error category for handling strategies */ category: ErrorCategory; /** Recovery guidance for users */ suggestion?: string; /** Underlying cause of the error */ cause?: unknown; }>; /** * Base error class for all TypeGraph errors. * * Provides structured error information for both programmatic handling * and user-friendly messages. */ declare class TypeGraphError extends Error { /** Machine-readable error code (e.g., "VALIDATION_ERROR") */ readonly code: string; /** Error category for handling strategies */ readonly category: ErrorCategory; /** Structured context about the error */ readonly details: Readonly>; /** Recovery guidance for users */ readonly suggestion?: string; constructor(message: string, code: string, options: TypeGraphErrorOptions); /** * Returns a user-friendly error message with suggestion if available. */ toUserMessage(): string; /** * Returns a detailed string representation for logging. */ toLogString(): string; } /** * Validation issue from Zod or custom validation. */ type ValidationIssue = Readonly<{ /** Path to the invalid field (e.g., "address.city") */ path: string; /** Human-readable error message */ message: string; /** Zod error code if from Zod validation */ code?: string; /** * The identity assertion the issue is about, carried structurally so * consumers (e.g. interchange import error reporting) never have to parse * the human-readable message for it. */ assertionId?: string; }>; /** * The stable {@link ValidationIssue.code} every inverted-validity-window * refusal carries, so a caller can recognize that specific failure without * matching on the message. `ValidationError`'s own code is the family-wide * `VALIDATION_ERROR`, so the discriminator lives on the issue — mirroring the * identity-import window refusal (`IDENTITY_IMPORT_INVALID_WINDOW`). * * A write carrying it was refused because `validTo` precedes the row's * effective `validFrom`: the row would have stopped being true before it * started, observable at no `asOf` coordinate at all. Zero-width windows * (`validTo === validFrom`) are legal and never raise it. */ declare const INVERTED_VALIDITY_WINDOW_CODE = "INVERTED_VALIDITY_WINDOW"; /** * The stable {@link ValidationIssue.code} carried by a refusal to accept a * `validFrom` the write cannot apply: the target row is LIVE, and an in-place * update never rewrites a live row's lower bound — that bound is history. * `ValidationError`'s own code is the family-wide `VALIDATION_ERROR`, so the * discriminator lives on the issue — mirroring * {@link INVERTED_VALIDITY_WINDOW_CODE}. * * Only a bound naming a DIFFERENT instant than the row already stores raises it. * Restating the stored bound is legal and writes nothing new, and a resurrection * — which does rewrite `valid_from` — applies a stated bound rather than * refusing it. * * The message names both instants: the one stated and the one the row stores, so * the caller can restate the stored bound without a second read. */ declare const IMMUTABLE_VALIDITY_LOWER_BOUND_CODE = "IMMUTABLE_VALIDITY_LOWER_BOUND"; /** * Stable {@link ValidationIssue.code} for an edge-id ownership mismatch. * * Edge ids are graph-global. A collection-scoped write therefore has to prove * that the stored row has the collection's kind and, when the caller supplies * endpoints, the same immutable endpoints. This refusal prevents a collection * from mutating another kind's row and prevents an upsert from silently * ignoring an attempted repoint. */ declare const EDGE_IDENTITY_MISMATCH_CODE = "EDGE_IDENTITY_MISMATCH"; /** * The stable {@link ValidationIssue.code} every "this id is already taken" * create refusal carries, whichever way the create found out: its own existence * probe, or the engine refusing the INSERT. `ValidationError`'s own code is the * family-wide `VALIDATION_ERROR`, so the discriminator lives on the issue — * mirroring {@link INVERTED_VALIDITY_WINDOW_CODE}. * * `details.entityType` says whether a node or an edge was refused and * `details.kind` names its kind. `details.id` names the taken id, and is absent * only when the refused statement inserted MORE THAN ONE row: the engine reports * that the statement collided without saying which row did. */ declare const ENTITY_ALREADY_EXISTS_CODE = "ENTITY_ALREADY_EXISTS"; /** * Details for ValidationError. */ type ValidationErrorDetails = Readonly<{ /** Type of entity being validated */ entityType?: KindEntity; /** Kind/type name of the entity */ kind?: string; /** Operation being performed */ operation?: "create" | "update" | "delete" | "hardDelete"; /** Entity ID if updating */ id?: string; /** Individual validation issues */ issues: readonly ValidationIssue[]; }>; /** * Thrown when schema validation fails during node or edge operations. * * @example * ```typescript * try { * await store.nodes.Person.create({ email: "invalid" }); * } catch (error) { * if (error instanceof ValidationError) { * console.log(error.details.issues); * // [{ path: "email", message: "Invalid email" }] * } * } * ``` */ declare class ValidationError extends TypeGraphError { readonly details: ValidationErrorDetails; constructor(message: string, details: ValidationErrorDetails, options?: { cause?: unknown; suggestion?: string; }); } /** * Details for NodeNotFoundError. */ type NodeNotFoundErrorDetails = Readonly<{ kind: string; id: string; }>; /** * Thrown when a node is not found. * * @example * ```typescript * try { * await store.nodes.Person.get("nonexistent-id"); * } catch (error) { * if (error instanceof NodeNotFoundError) { * console.log(error.details.kind, error.details.id); * } * } * ``` */ declare class NodeNotFoundError extends TypeGraphError { readonly details: NodeNotFoundErrorDetails; constructor(kind: string, id: string, options?: { cause?: unknown; }); } /** * Details for EdgeNotFoundError. */ type EdgeNotFoundErrorDetails = Readonly<{ kind: string; id: string; }>; /** * Thrown when an edge is not found. */ declare class EdgeNotFoundError extends TypeGraphError { readonly details: EdgeNotFoundErrorDetails; constructor(kind: string, id: string, options?: { cause?: unknown; }); } /** * Details for KindNotFoundError. */ type KindNotFoundErrorDetails = Readonly<{ kindName: string; entity: KindEntity; graphId?: string; }>; /** * Thrown when an operation references a kind that isn't registered on * the graph — `getNodeCollectionOrThrow` against an unknown kind, a * `materializeIndexes({ kinds })` filter naming a missing kind, an * extension referencing an unresolved endpoint, search facade typos, * etc. Carries the offending `kindName` and `entity` plus the host * `graphId` so logs are unambiguous when multiple stores share a * process. */ declare class KindNotFoundError extends TypeGraphError { readonly details: KindNotFoundErrorDetails; readonly kindName: string; readonly entity: KindEntity; constructor(kindName: string, entity: KindEntity, options?: Readonly<{ graphId?: string; suggestion?: string; cause?: unknown; }>); } type RuntimeKindTokenFailure = "invalid" | "wrong-entity" | "wrong-kind" | "wrong-store" | "stale" | "schema-mismatch" | "unreconciled"; /** * Refuses runtime-kind evidence that was forged, misapplied, or licensed for a * different reconciled store schema. */ declare class RuntimeKindTokenError extends TypeGraphError { readonly reason: RuntimeKindTokenFailure; constructor(reason: RuntimeKindTokenFailure, entity: KindEntity, details?: Readonly>); } /** * Details for NodeConstraintNotFoundError. */ type NodeConstraintNotFoundErrorDetails = Readonly<{ constraintName: string; kind: string; }>; /** * Thrown when a uniqueness constraint name is not found on a node kind. */ declare class NodeConstraintNotFoundError extends TypeGraphError { readonly details: NodeConstraintNotFoundErrorDetails; constructor(constraintName: string, kind: string, options?: { cause?: unknown; }); } /** * Details for NodeIndexNotFoundError. */ type NodeIndexNotFoundErrorDetails = Readonly<{ indexName: string; kind: string; }>; /** * Thrown when a declared node index name is not found on a node kind. */ declare class NodeIndexNotFoundError extends TypeGraphError { readonly details: NodeIndexNotFoundErrorDetails; constructor(indexName: string, kind: string, options?: { cause?: unknown; }); } /** * Details for EndpointNotFoundError. */ type EndpointNotFoundErrorDetails = Readonly<{ edgeKind: string; endpoint: "from" | "to"; nodeKind: string; nodeId: string; }>; /** * Thrown when edge endpoint node does not exist or is deleted. */ declare class EndpointNotFoundError extends TypeGraphError { readonly details: EndpointNotFoundErrorDetails; constructor(details: EndpointNotFoundErrorDetails, options?: { cause?: unknown; }); } /** * Details for EndpointError. */ type EndpointErrorDetails = Readonly<{ edgeKind: string; endpoint: "from" | "to"; actualKind: string; expectedKinds: readonly string[]; }>; /** * Thrown when edge endpoint has wrong node type on "from" or "to". */ declare class EndpointError extends TypeGraphError { readonly details: EndpointErrorDetails; constructor(details: EndpointErrorDetails, options?: { cause?: unknown; }); } /** * Details for EndpointPairError. */ type EndpointPairErrorDetails = Readonly<{ edgeKind: string; endpoint: "pair"; fromKind: string; toKind: string; allowedPairs: readonly Readonly<{ from: string; to: string; }>[]; }>; /** * Thrown when an edge connects an invalid/undeclared endpoint pair. */ declare class EndpointPairError extends TypeGraphError { readonly details: EndpointPairErrorDetails; constructor(details: Readonly<{ edgeKind: string; fromKind: string; toKind: string; allowedPairs: readonly Readonly<{ from: string; to: string; }>[]; }>, options?: { cause?: unknown; }); } /** * Details for UniquenessError. */ type UniquenessErrorDetails = Readonly<{ constraintName: string; kind: string; existingId: string; newId: string; fields: readonly string[]; /** * The claim row's `node_kind` — the axis this violation was found at. * Optional so a third-party backend implementing the documented "throws * `UniquenessError`" contract keeps working unchanged; when a throw site * omits it, a caller loses only the disambiguation this field exists for, * never correctness of the refusal itself. * * `constraintName` alone cannot tell two disjoint pairs apart — every * `disjointWith` pair shares the single reserved `DISJOINT_CONSTRAINT_NAME` * — so `mapClaimRefusal` (`store/claims/node-claims.ts`) matches on this * field together with `constraintName` and the key, which is exactly the * claim row's primary key and therefore unambiguous. */ axis?: string; }>; /** * Thrown when uniqueness constraint is violated. */ declare class UniquenessError extends TypeGraphError { readonly details: UniquenessErrorDetails; constructor(details: UniquenessErrorDetails, options?: { cause?: unknown; }); } /** A direct edge write collided with its schema-declared match identity. */ declare class EdgeMatchIdentityConflictError extends TypeGraphError { constructor(details: Readonly<{ attempted: readonly Readonly<{ id: string; identityName: string; kind: string; }>[]; }>, options?: Readonly<{ cause?: unknown; }>); } /** * Details for CardinalityError. */ type CardinalityErrorDetails = Readonly<{ edgeKind: string; fromKind: string; fromId: string; cardinality: string; existingCount: number; }>; /** * Thrown when cardinality constraint is violated. */ declare class CardinalityError extends TypeGraphError { readonly details: CardinalityErrorDetails; constructor(details: CardinalityErrorDetails, options?: { cause?: unknown; }); } /** * Details for DisjointError. */ type DisjointErrorDetails = Readonly<{ nodeId: string; attemptedKind: string; conflictingKind: string; }>; /** * Thrown when disjointness constraint is violated. * * Disjoint types cannot share the same ID - a node cannot be both * a Person and an Organization if they are declared disjoint. */ declare class DisjointError extends TypeGraphError { readonly details: DisjointErrorDetails; constructor(details: DisjointErrorDetails, options?: { cause?: unknown; }); } type IdentityContradictionErrorDetails = Readonly<{ /** * The identity write that could not stand. `merge` is the post-write * affected-class assertion a graph merge runs inside its commit * transaction, which names the whole merge rather than a single assertion * because the contradiction is a property of the state the merge would * leave behind. */ operation: "assertSame" | "assertDifferent" | "fold" | "import" | "merge"; a: Readonly<{ kind: string; id: string; }>; b: Readonly<{ kind: string; id: string; }>; reason: "different-assertion" | "same-class" | "disjoint-kinds"; conflictingAssertionId?: string; conflictingKinds?: readonly [string, string]; }>; /** Thrown when an identity mutation would make the ledger contradictory. */ declare class IdentityContradictionError extends TypeGraphError { readonly details: IdentityContradictionErrorDetails; constructor(details: IdentityContradictionErrorDetails, options?: Readonly<{ cause?: unknown; }>); } type IdentityValidityWindowErrorDetails = Readonly<{ reason: "future-valid-from" | "future-valid-to" | "inverted" | "overlapping-open-window"; validFrom: string; validTo?: string; operationInstant: string; }>; /** Thrown when an identity assertion validity window cannot be applied. */ declare class IdentityValidityWindowError extends TypeGraphError { readonly details: IdentityValidityWindowErrorDetails; constructor(details: IdentityValidityWindowErrorDetails); } type IdentityEndpointValidityErrorDetails = Readonly<{ endpoint: Readonly<{ kind: string; id: string; }>; assertionWindow: Readonly<{ validFrom: string; validTo?: string; }>; endpointWindow: Readonly<{ validFrom?: string; validTo?: string; deletedAt?: string; }>; }>; /** Thrown when an identity endpoint does not exist throughout the assertion window. */ declare class IdentityEndpointValidityError extends TypeGraphError { readonly details: IdentityEndpointValidityErrorDetails; constructor(details: IdentityEndpointValidityErrorDetails); } type IdentitySeparationViolationErrorDetails = Readonly<{ graphId: string; /** * `"database"` when the CHECK constraint on the separation relation aborted * the write — the backstop firing as designed. `"writer"` when the write was * accepted, which means the relation was provisioned without its constraint * and only the writer's own re-check caught the contradiction. */ enforcedBy: "database" | "writer"; /** The single class key both endpoints collapsed onto. */ classKey: string; /** The `different` assertion whose endpoints landed in one class. */ assertionId: string; a: Readonly<{ kind: string; id: string; }>; b: Readonly<{ kind: string; id: string; }>; }>; /** * Thrown when the derived separation relation refuses a write that would place * both endpoints of a current `different` assertion in one identity class. * * This is the database-level backstop beneath the plan-time simulation and the * applier's validation. Reaching it means an earlier layer let a contradiction * through: the transaction is aborted rather than committed, but the layer that * should have refused it typed and early is worth investigating. */ declare class IdentitySeparationViolationError extends TypeGraphError { readonly details: IdentitySeparationViolationErrorDetails; constructor(details: IdentitySeparationViolationErrorDetails, options?: Readonly<{ cause?: unknown; }>); } /** * Details for RestrictedDeleteError. */ type RestrictedDeleteErrorDetails = Readonly<{ nodeKind: string; nodeId: string; edgeCount: number; edgeKinds: readonly string[]; }>; /** * Thrown when deletion is blocked due to existing edges (restrict behavior). */ declare class RestrictedDeleteError extends TypeGraphError { readonly details: RestrictedDeleteErrorDetails; constructor(details: RestrictedDeleteErrorDetails, options?: { cause?: unknown; }); } /** * Details for VersionConflictError. */ type VersionConflictErrorDetails = Readonly<{ kind: string; id: string; expectedVersion: number; actualVersion: number; }>; /** * Thrown when optimistic locking detects a concurrent modification. * * This occurs when two operations try to update the same entity simultaneously. * The operation with the stale version fails. */ declare class VersionConflictError extends TypeGraphError { readonly details: VersionConflictErrorDetails; constructor(details: VersionConflictErrorDetails, options?: { cause?: unknown; }); } /** * Details for TransactionConflictError. */ type TransactionConflictErrorDetails = Readonly<{ operation: string; attempts: number; }>; /** * Thrown when a transaction was aborted by a serialization failure or * deadlock on every attempt available to it. * * The failing driver error — the last attempt's — is preserved as `cause`. * PostgreSQL's own protocol for both conditions is to re-run the whole * transaction from the top, which is what exhausted the attempt budget here. */ declare class TransactionConflictError extends TypeGraphError { readonly details: TransactionConflictErrorDetails; constructor(details: TransactionConflictErrorDetails, options?: { cause?: unknown; }); } /** * Details for SchemaMismatchError. */ type SchemaMismatchErrorDetails = Readonly<{ graphId: string; expectedHash: string; actualHash: string; }>; /** * Thrown when the schema in code doesn't match the schema in the database. */ declare class SchemaMismatchError extends TypeGraphError { readonly details: SchemaMismatchErrorDetails; constructor(details: SchemaMismatchErrorDetails, options?: { cause?: unknown; }); } /** * Details for MigrationError. */ /** * Why a migration operation failed. A stable discriminant so callers can * branch on the outcome instead of matching the message text, which is free * to be reworded in any release. */ declare const MIGRATION_FAILURE_REASONS: readonly ["schema-behind", "breaking-change", "no-active-version", "version-not-found", "kind-removal", "edge-match-identity-rekey"]; type MigrationFailureReason = (typeof MIGRATION_FAILURE_REASONS)[number]; /** * Structured context for a {@link MigrationError}, discriminated on `reason`. * * Modelled as a union rather than one shape with optional fields so the * payload a reason promises is the payload it must carry: narrowing on * `reason === "kind-removal"` gives you a non-optional `droppedKinds`, and the * constructor cannot be handed a `kind-removal` without one. * * The common fields are repeated per member rather than factored into a shared * base alias, which would be a hoisted-unexported symbol on the public * entrypoint. */ type MigrationErrorDetails = Readonly<{ graphId: string; fromVersion: number; toVersion: number; /** Stable discriminant — branch on this, not the message. */ reason: "schema-behind" | "breaking-change"; /** * The structured diff behind the failure. Carries per-change `severity` * alongside `hasChanges` / `hasBreakingChanges`, so a caller can decide * "additive → proceed, incompatible → ask the user" without re-querying. */ diff?: SchemaDiff; }> | Readonly<{ graphId: string; fromVersion: number; toVersion: number; reason: "no-active-version" | "version-not-found"; }> | Readonly<{ graphId: string; fromVersion: number; toVersion: number; reason: "kind-removal"; /** * The dropped kinds that still held rows, and so caused the refusal. A * kind the commit drops but that is already empty is permitted and does * NOT appear here. Both lists are sorted; at least one is non-empty. */ droppedKinds: Readonly<{ nodes: readonly string[]; edges: readonly string[]; }>; }> | Readonly<{ graphId: string; fromVersion: number; toVersion: number; reason: "edge-match-identity-rekey"; edgeKinds: readonly string[]; }>; /** * Thrown when schema migration fails. */ declare class MigrationError extends TypeGraphError { readonly details: MigrationErrorDetails; constructor(message: string, details: MigrationErrorDetails, options?: { cause?: unknown; }); } type BaseSchemaMigrationErrorDetails = Readonly<{ installedVersion: number | undefined; requiredVersion: number; reason: "missing" | "stale" | "newer"; }>; /** * Thrown when the deployment-wide TypeGraph base relations have not been * adopted to the physical schema required by this library version. * * Unlike {@link MigrationError}, this is not about one graph's serialized * schema document. It is the zero-DDL runtime refusal that tells an operator * to run privileged store preparation before attaching a DML-only role. */ declare class BaseSchemaMigrationError extends TypeGraphError { readonly details: BaseSchemaMigrationErrorDetails; constructor(details: BaseSchemaMigrationErrorDetails, options?: Readonly<{ cause?: unknown; }>); } /** * Details for EagerMaterializationError. */ type EagerMaterializationErrorDetails = Readonly<{ graphId: string; failedIndexNames: readonly string[]; }>; /** * Thrown by `Store.evolve(extension, { eager })` when the schema * commit succeeded but the follow-on `materializeIndexes()` produced * one or more failed entries. * * Recovery: the schema commit is NOT rolled back. The new `Store` is * fully constructed and (when `options.ref` was supplied) `ref.current` * already points to it — the caller can read the new store via the ref * and decide how to handle the failed indexes (retry, skip, alert). * The full `MaterializeIndexesResult` is attached as `.materialization`. * * @example * ```ts * const ref = { current: store }; * try { * await store.evolve(extension, { ref, eager: {} }); * } catch (error) { * if (error instanceof EagerMaterializationError) { * // schema is committed; ref.current is the new store * log.warn( * { failed: error.failedIndexNames }, * "indexes did not materialize; will retry", * ); * await ref.current.materializeIndexes(); * } else { * throw error; * } * } * ``` */ declare class EagerMaterializationError extends TypeGraphError { readonly details: EagerMaterializationErrorDetails; /** * The full materialization result, including successful and failed * entries. Same shape as `Store.materializeIndexes()` returns. */ readonly materialization: MaterializeIndexesResult; constructor(materialization: MaterializeIndexesResult, graphId: string); /** * Names of just the indexes that failed — derived from * `materialization.results` so the two views can never disagree. */ get failedIndexNames(): readonly string[]; } /** * Details for StaleVersionError. `actual` is the active version the rejecting * read observed, and is `0` only when the graph genuinely has no active version * (an initial-commit race where another writer initialized first, or a graph * whose schema rows were removed out of band). A concurrent advance always * reports the newly active version, so `0` is a reliable signal of absence * rather than of contention. */ type StaleVersionErrorDetails = Readonly<{ graphId: string; expected: number; actual: number; }>; /** * Thrown by `commitSchemaVersion`, `setActiveVersion`, and the schema write * fence a schema-managed Store write takes, when the caller's view of the * active schema version is out of date — another writer has already advanced * it. * * Recovery: re-read the active version with `getActiveSchema(graphId)`, * recompute against the new baseline, and retry. This is a routine * concurrency signal, not a bug. */ declare class StaleVersionError extends TypeGraphError { readonly details: StaleVersionErrorDetails; constructor(details: StaleVersionErrorDetails, options?: { cause?: unknown; }); } /** * Details for SchemaContentConflictError. */ type SchemaContentConflictErrorDetails = Readonly<{ graphId: string; version: number; existingHash: string; incomingHash: string; }>; /** * Thrown by `commitSchemaVersion` when a row already exists at the * target version with a *different* schema hash — i.e. two writers * committed materially different schemas at the same version number. * * Distinct from `StaleVersionError`: this is not a refetch-and-retry * situation, it's a content disagreement that needs operator * intervention. Typically caused by inconsistent application * deployments writing schemas that hash differently. */ declare class SchemaContentConflictError extends TypeGraphError { readonly details: SchemaContentConflictErrorDetails; constructor(details: SchemaContentConflictErrorDetails, options?: { cause?: unknown; }); } /** * Thrown when graph configuration is invalid. * * This includes invalid schema definitions, ontology conflicts, * and other configuration issues detected at graph creation time. */ declare class ConfigurationError extends TypeGraphError { constructor(message: string, details?: Record, options?: { cause?: unknown; suggestion?: string; }); } /** The lock acquisition phase that exhausted the schema-fence wait budget. */ type SchemaFencePhase = "schema-advisory" | "schema-row" | "writer-slot"; /** Identifies the graph, lock phase, and requested wait budget for a timeout. */ type SchemaFenceTimeoutErrorDetails = Readonly<{ graphId: string; phase: SchemaFencePhase; waitBudgetMs: number; }>; /** The caller-owned transaction could not acquire its schema fence in time. */ declare class SchemaFenceTimeoutError extends TypeGraphError { /** The graph and lock acquisition that exceeded the caller's budget. */ readonly details: SchemaFenceTimeoutErrorDetails; constructor(graphId: string, phase: SchemaFencePhase, waitBudgetMs: number, cause?: unknown); } /** * Why a destructive contribution rebuild is refused. * * - `vector-source-unavailable` — the projection is vector storage. * TypeGraph stores caller-supplied embeddings and nothing else: the * vectors exist only in the table a rebuild would drop, so there is no * source to reconstruct them from. Dropping anyway would silently * destroy the embeddings and hand back storage that looks healthy and * returns nothing. `store.reembedVectorField(kind, fieldPath, { * embed })` is the sanctioned destructive path — it takes the callback * that can regenerate what the drop destroys. * - `no-drop-ddl` — the active strategy declares no `dropDdl` for the * contribution, so TypeGraph does not know how to tear its storage * down. Synthesizing a `DROP TABLE` from the resolved name would * guess at a teardown the strategy never sanctioned. * - `no-schema-fence` — the backend exposes no transactional schema * fence (`schemaWriteTransaction`), so the drop, recreate, refill, and * stamp could not be made atomic. Running them unfenced could leave * storage attested but empty, or a concurrent schema writer * interleaved with the drop. * - `shared-storage-in-use` — the contribution's recorded shape is stale, * so only recreating its storage repairs it, but that storage is one * table holding other graphs' rows as well. Their content is derived * from their own nodes through their own schemas, so this process cannot * put it back. A rebuild that dropped anyway would leave every other * graph's search silently empty; one that re-stamped this graph's marker * without the drop would bless a physical shape nothing verified. * - `fulltext-unavailable` — the backend declares no fulltext capability (`fulltext: false`, or no `capabilities.fulltext`), * so there is no fulltext contribution to rebuild at all. */ type ContributionRebuildRefusal = "vector-source-unavailable" | "no-drop-ddl" | "no-schema-fence" | "shared-storage-in-use" | "fulltext-unavailable"; /** * Thrown when `store.rebuildContribution()` cannot honor a rebuild. * * A refusal, never a partial attempt: nothing has been dropped when this * throws. The point is that the alternative — proceeding — destroys data * that cannot be reconstructed, so the operation declines instead of * quietly doing less than its name promises. */ declare class ContributionRebuildUnsupportedError extends TypeGraphError { readonly reason: ContributionRebuildRefusal; constructor(reason: ContributionRebuildRefusal, details?: Readonly>, options?: { cause?: unknown; }); } /** * Thrown when an iterative graph algorithm exhausts its caller-visible round * budget before reaching its convergence predicate. */ declare class GraphAlgorithmConvergenceError extends TypeGraphError { constructor(algorithm: string, maxIterations: number, options?: Readonly<{ oscillating?: boolean; }>); } /** Stable reasons a weighted traversal can reject an edge's weight value. */ type InvalidEdgeWeightReason = "missing" | "negative" | "non_numeric" | "out_of_range"; /** Details for InvalidEdgeWeightError. */ type InvalidEdgeWeightErrorDetails = Readonly<{ /** Id of the first offending edge, in binary-collation order. */ edgeId: string; /** Kind of the offending edge. */ edgeKind: string; /** The audited weight property. */ property: string; /** Why the weight was rejected. */ reason: InvalidEdgeWeightReason; /** The offending value, when one was present and renderable. */ value?: string; }>; /** * Thrown when a weighted graph algorithm finds an edge whose weight property * violates the weighted-traversal contract (missing without a default, * non-numeric, or negative). Raised before any traversal rounds run, so a * weighted call either observes a fully valid weight domain or fails fast. */ declare class InvalidEdgeWeightError extends TypeGraphError { readonly details: InvalidEdgeWeightErrorDetails; constructor(details: InvalidEdgeWeightErrorDetails); } /** Thrown when an operation requires a backend capability it does not expose. */ declare class UnsupportedBackendCapabilityError extends TypeGraphError { constructor(operation: string, capability: string, details?: Readonly>, suggestion?: string, messageSuffix?: string); } /** Stable reasons an intentionally trusted initial import can be rejected. */ type TrustedImportErrorReason = "backend_unsupported" | "database_not_empty" | "fulltext_unsupported" | "history_unsupported" | "identity_unsupported" | "invalid_stream" | "revision_tracking_unsupported" | "uniqueness_unsupported" | "vector_unsupported"; /** Thrown when the trusted initial-import contract is unavailable or violated. */ declare class TrustedImportError extends TypeGraphError { constructor(message: string, reason: TrustedImportErrorReason, details?: Readonly>, options?: Readonly<{ cause?: unknown; suggestion?: string; }>); } /** * Thrown to a graph export stream's consumer when the caller's `AbortSignal` * settled the stream instead of it reaching its end. * * Raised only after the export has given back everything it took, so receiving * it means the connection is already usable again. WHAT it took depends on the * backend: an export on one reporting `capabilities.execution.interactiveTransactions` holds a * repeatable-read snapshot (and, on a serialized connection, that connection's * stream lease), and both are settled before this is thrown; an export on a * backend without transactions holds neither, and its remaining reads are * simply abandoned — its already-delivered chunks were never one snapshot to * begin with. The message says which case applies. `cause` carries the signal's * own `reason` when the caller supplied one. */ declare class ExportStreamCancelledError extends TypeGraphError { constructor(message: string, details?: Readonly>, options?: Readonly<{ cause?: unknown; suggestion?: string; }>); } /** * Thrown when an export stream's consumer leaves a delivered chunk * unacknowledged for longer than its configured idle timeout. * * The timeout measures consumer idleness only: it starts when a chunk is * yielded and stops when the consumer asks for the next one. Receiving this * error means the timed-out export has settled its snapshot transaction and * released any serialized connection lease it held. `details.idleTimeoutMs` * carries the configured bound. */ declare class ExportStreamIdleTimeoutError extends TypeGraphError { constructor(graphId: string, idleTimeoutMs: number, transactional: boolean); } /** * The stable `details.code` values raised by the recorded-capture guards on a * history- or revision-tracked store. These are the sanctioned branch points * for a portable caller that must pick a transaction strategy without * substring-matching {@link ConfigurationError} messages. * * - `RECORDED_CAPTURE_REQUIRES_CALLBACK_TRANSACTION`: `store.withTransaction` * was called on a history-enabled store, which has no flush point before the * caller commits — use `store.withRecordedTransaction` instead. * - `RECORDED_CAPTURE_RAW_SQL_DISABLED`: a raw SQL escape (`tx.sql`, * `backend.executeStatement`/`executeDdl`) was reached on a history-enabled * store, where it would bypass recorded-time capture. * - `REVISION_TRACKING_RAW_SQL_DISABLED`: the same raw SQL escape on a * revision-tracked store, where it would bypass the revision anchor. * * @see isRecordedCaptureGuardError */ declare const RECORDED_CAPTURE_GUARD_CODES: readonly ["RECORDED_CAPTURE_REQUIRES_CALLBACK_TRANSACTION", "RECORDED_CAPTURE_RAW_SQL_DISABLED", "REVISION_TRACKING_RAW_SQL_DISABLED"]; /** A stable, branchable code raised by a recorded-capture guard. */ type RecordedCaptureGuardCode = (typeof RECORDED_CAPTURE_GUARD_CODES)[number]; /** * A {@link ConfigurationError} narrowed to carry a {@link RecordedCaptureGuardCode} * in `details.code` — the shape {@link isRecordedCaptureGuardError} guarantees. * `C` narrows `details.code` to a single code when the guard was called with a * specific one; it defaults to the full union. */ type RecordedCaptureGuardError = ConfigurationError & Readonly<{ details: Readonly<{ code: C; }>; }>; /** * Type guard for the recorded-capture guard errors, so a portable caller can * branch on the invariant a store enforced instead of substring-matching the * message. Pass `code` to narrow to a single guard; omit it to match any. * * Distinguishes "history capture forbids raw SQL here" from "this backend has * no transactions" (the latter carries no guard code — see * `TransactionContext.sqlAvailability` for the capability discriminant). * * Passing `code` also narrows `details.code` to that literal on the guarded * branch, so a caller can read the payload without re-checking. * * @example * ```typescript * // `withTransaction` is a compile error on a history-enabled store, so widen * // to the base Store surface to reach the runtime guard this branches on. * const store: Store = historyStore; * try { * store.withTransaction(externalTx); * } catch (error) { * if ( * isRecordedCaptureGuardError( * error, * "RECORDED_CAPTURE_REQUIRES_CALLBACK_TRANSACTION", * ) * ) { * // error.details.code is now the literal, not the union. * await historyStore.withRecordedTransaction(externalTx, run); * } else { * throw error; * } * } * ``` */ declare function isRecordedCaptureGuardError(error: unknown, code: C): error is RecordedCaptureGuardError; declare function isRecordedCaptureGuardError(error: unknown): error is RecordedCaptureGuardError; /** * Why a store's strategy-owned storage is not usable on the current * connection. Drives the {@link StoreNotInitializedError} message and * is surfaced in `details.reason` for programmatic handling. * * - `missing`: no durable materialization marker exists — the database * was never initialized for this graph's runtime contributions. * - `stale`: a marker exists but its recorded signature no longer * matches the resolved contribution DDL (strategy swap or DDL drift). * The hot path refuses rather than silently re-materializing. * - `failed`: the most recent boot-time materialization attempt * recorded an error. Boot may retry; the hot path refuses. */ type StoreNotInitializedReason = "missing" | "stale" | "failed"; /** * Details for StoreNotInitializedError. `graphId`/`reason` are always * present; callers may merge additional context (e.g. `logicalName`) via * `options.details`. */ type StoreNotInitializedErrorDetails = Readonly<{ graphId: string; reason: StoreNotInitializedReason; }> & Readonly>; /** * Thrown when a fulltext- or vector-dependent operation runs against a * connection whose strategy-owned storage has not been durably materialized. * * `createStore()` is a synchronous, zero-I/O attach: it never creates * tables, repairs DDL, or writes materialization markers. The durable * marker is written exclusively by the async boot path * (`createStoreWithSchema`). When a fulltext or embedding read/write — or * an adopted/business transaction — observes no valid marker, it refuses * loudly here instead of lazily emitting DDL on the hot path. */ declare class StoreNotInitializedError extends TypeGraphError { readonly details: StoreNotInitializedErrorDetails; constructor(graphId: string, reason: StoreNotInitializedReason, options?: { cause?: unknown; details?: Readonly> & Readonly<{ graphId?: never; reason?: never; }>; }); } /** Details for a runtime contribution whose durable marker outlived storage. */ type ContributionUnavailableErrorDetails = Readonly<{ graphId: string; logicalName: "fulltext"; physicalName: string; state: "physical-storage-missing"; }>; /** * Thrown when a gated fulltext operation discovers that materialized storage * disappeared after its durable marker recorded successful initialization. */ declare class ContributionUnavailableError extends TypeGraphError { readonly details: ContributionUnavailableErrorDetails; constructor(graphId: string, physicalName: string, options?: Readonly<{ cause?: unknown; }>); } /** * Details for DatabaseOperationError. */ type DatabaseOperationErrorDetails = Readonly<{ operation: string; entity: string; /** * `"duplicate_key"` means the engine refused the write because the entity's * identity is already taken — the shape a concurrent create of the same new id * produces on an engine that does not serialize the two writers. It is a * *classified* driver failure, so callers that own a better error for the * condition (the create paths, which report it as "already exists") translate * it rather than surfacing it. */ reason?: "no_row_returned" | "duplicate_key"; /** * The entities the failed statement tried to insert, carried structurally so a * translating caller never has to parse the driver's message for them. One * element for a single insert; for a batch, every row in the failing chunk — * the engine reports the collision without saying which row lost. */ attempted?: readonly Readonly<{ kind: string; id: string; }>[]; }>; /** * Thrown when a database operation fails unexpectedly. * * This indicates a system-level failure in the database backend, * not a user-recoverable error. */ declare class DatabaseOperationError extends TypeGraphError { readonly details: DatabaseOperationErrorDetails; constructor(message: string, details: DatabaseOperationErrorDetails, options?: { cause?: unknown; }); } /** * Details for EmbeddingDimensionChangedError. */ type EmbeddingDimensionChangedErrorDetails = Readonly<{ kind: string; fieldPath: string; declaredDimensions?: number; storedDimensions?: number; }>; /** * Thrown when an embedding field's declared dimension no longer matches the * dimension of its materialized per-field storage — i.e. a field's * `embedding(N)` was changed to `embedding(M)`. The stored vectors are invalid * under the new dimension and cannot be converted, only recomputed, so this is * a deliberate app-driven migration: call * `store.reembedVectorField(kind, fieldPath, ...)` to recreate the storage at * the new dimension and re-embed existing rows. */ declare class EmbeddingDimensionChangedError extends TypeGraphError { readonly details: EmbeddingDimensionChangedErrorDetails; constructor(message: string, details: EmbeddingDimensionChangedErrorDetails, options?: { cause?: unknown; }); } /** * Thrown when a query predicate cannot be compiled for the target database. */ declare class UnsupportedPredicateError extends TypeGraphError { constructor(message: string, details?: Readonly>, options?: { cause?: unknown; suggestion?: string; }); } /** * Thrown when a compiler invariant is violated. * * This indicates a bug in the query compiler — the compiler reached * a state that should be unreachable. These errors are not user-recoverable. */ declare class CompilerInvariantError extends TypeGraphError { constructor(message: string, details?: Readonly>, options?: { cause?: unknown; }); } /** * Thrown when an operation is attempted on a backend that has been disposed. * * This typically occurs during runtime teardown — for example, when a * Cloudflare Workers test runner resets Durable Object storage while * the TypeGraph backend still has queued operations. */ declare class BackendDisposedError extends TypeGraphError { constructor(options?: { cause?: unknown; }); } /** * Thrown when a statement is issued on a transaction-scoped backend after * its transaction boundary has already returned. * * The usual source is a callback that lets work escape it. `Promise.all` * rejects on its first rejection while its siblings keep running, so * * ```typescript * await store.transaction(async (tx) => { * await Promise.all([tx.nodes.Doc.create(a), tx.nodes.Doc.create(b)]); * }); * ``` * * leaves `b`'s remaining statements in flight when `a` fails. Those * statements have nowhere safe to go: the driver is about to emit `ROLLBACK` * on the same pinned connection and then hand it back to the pool, where a * late arrival would execute inside somebody else's transaction. TypeGraph * refuses them here instead. * * The error is normally invisible — `Promise.all` has already rejected with * the original failure, and discards this one. */ declare class TransactionClosedError extends TypeGraphError { constructor(options?: { cause?: unknown; }); } /** * Type guard for TypeGraphError. * * @example * ```typescript * try { * await store.nodes.Person.create({}); * } catch (error) { * if (isTypeGraphError(error)) { * console.log(error.code, error.category); * } * } * ``` */ declare function isTypeGraphError(error: unknown): error is TypeGraphError; /** * Check if error is recoverable by user action (user or constraint error). * * User-recoverable errors can typically be resolved by: * - Fixing invalid input data * - Using different IDs or values * - Deleting conflicting data first * * @example * ```typescript * if (isUserRecoverable(error)) { * showErrorToUser(error.toUserMessage()); * } else { * logAndAlertOps(error); * } * ``` */ declare function isUserRecoverable(error: unknown): boolean; /** * Check if error indicates a system/infrastructure issue. * * System errors typically require: * - Retry logic (for transient failures) * - Investigation (for persistent failures) * - Ops team notification */ declare function isSystemError(error: unknown): boolean; /** * Check if error is a constraint violation. */ declare function isConstraintError(error: unknown): boolean; /** * Extract suggestion from error if available. */ declare function getErrorSuggestion(error: unknown): string | undefined; /** The committed schema differs from the version expected by a checked read. */ type SchemaChangedErrorDetails = Readonly<{ graphId: string; expected: number | undefined; actual: number | undefined; }>; declare class SchemaChangedError extends TypeGraphError { readonly details: SchemaChangedErrorDetails; constructor(details: SchemaChangedErrorDetails); } /** * The `recursiveTraversal` capability: whether this engine can compute a * BOUNDED TRANSITIVE CLOSURE of a relation in one round trip — the traversal * primitive, not the SQL syntax. */ /** * Whether this engine can compute a BOUNDED TRANSITIVE CLOSURE of a relation * in one round trip — the traversal primitive, not the SQL syntax. A SQL * engine satisfies it with `WITH RECURSIVE`; a graph-native engine satisfies * it with a native expansion operator. What the capability promises is the * SEMANTICS: given a seed set, a step relation, and a hop bound, the engine * returns the reachable set (optionally with depth and path) without the * client issuing one statement per hop. * * Absent means SUPPORTED. Every engine TypeGraph ships supports it, and every * custom backend already has the six emission sites run against it * unconditionally: making absence mean `false` would refuse traversals that * work today. This mirrors `returning`, not `constraintClaims` — absence is * only allowed to mean "false" where absence is SAFE, and here it is not. A * backend that genuinely lacks the primitive must say so. */ type RecursiveTraversalCapability = Readonly<{ supported: boolean; /** * Why the engine lacks it — surfaced in every refusal's details so the * state is named rather than implied. Required when `supported: false` and * forbidden when `true`, so the union cannot carry a dangling reason. */ reason?: string; }>; /** * The brand. Declared but never exported, and never assigned at runtime — it * exists only so that an object literal outside this module is not assignable * to {@link RecursiveTraversalVerdict}. Compare `VectorSlot`'s construction * discipline: the type is public, the constructor is the seam. */ declare const RECURSIVE_TRAVERSAL_VERDICT: unique symbol; /** * The decision, branded so it can only originate from this module's * constructors. Round 1 made the verdict a required *field*, which forces a * token, not a verdict: any caller could satisfy it by writing * `{ supported: true }` inline, and nothing tied the value to the resolver. * The brand closes that at the type level. */ type RecursiveTraversalVerdict = Readonly<{ [RECURSIVE_TRAVERSAL_VERDICT]: true; } & ({ supported: true; } | { supported: false; reason: string; })>; /** THE one reader of `capabilities.recursiveTraversal`, and THE one constructor. */ declare function resolveRecursiveTraversal(capabilities: BackendCapabilities): RecursiveTraversalVerdict; /** * The ONE sanctioned way to obtain a verdict without a backend: the query * compiler's public entry point `compileQuery(ast, graphId, "postgres")` * takes no backend at all, so it has no capabilities to resolve from. The * reason string is required and is echoed in nothing — it exists so the call * site states why it is allowed to assume. */ declare function assumeRecursiveTraversalSupported(reason: string): RecursiveTraversalVerdict; /** THE one refusal builder. */ declare function recursiveTraversalUnsupportedError(verdict: Extract, operation: string): ConfigurationError; /** THE one assertion: refuses when the verdict says the engine cannot. */ declare function assertRecursiveTraversal(verdict: RecursiveTraversalVerdict, operation: string): asserts verdict is Extract; /** * JSON Schema type (subset used by Zod toJSONSchema). * * This is a simplified version - the actual JSON Schema has many more properties. */ type JsonSchema = Readonly<{ $schema?: string; type?: string | readonly string[]; properties?: Record; required?: readonly string[]; items?: JsonSchema; additionalProperties?: boolean | JsonSchema; enum?: readonly unknown[]; const?: unknown; anyOf?: readonly JsonSchema[]; oneOf?: readonly JsonSchema[]; allOf?: readonly JsonSchema[]; not?: JsonSchema; description?: string; default?: unknown; minimum?: number; maximum?: number; minLength?: number; maxLength?: number; pattern?: string; format?: string; [key: string]: unknown; }>; /** * Serialized representation of a meta-edge. */ type SerializedMetaEdge = Readonly<{ name: string; transitive: boolean; symmetric: boolean; reflexive: boolean; inverse: string | undefined; inference: InferenceType; description: string | undefined; }>; /** * Serialized representation of an ontology relation. */ type SerializedOntologyRelation = Readonly<{ metaEdge: string; from: string; to: string; }>; /** * Precomputed closures stored in the schema for fast runtime lookup. */ type SerializedClosures = Readonly<{ subClassAncestors: Record; subClassDescendants: Record; broaderClosure: Record; narrowerClosure: Record; equivalenceSets: Record; disjointPairs: readonly string[]; partOfClosure: Record; hasPartClosure: Record; iriToKind: Record; edgeInverses: Record; edgeImplicationsClosure: Record; edgeImplyingClosure: Record; }>; /** * Complete serialized ontology section. */ type SerializedOntology = Readonly<{ metaEdges: Record; relations: readonly SerializedOntologyRelation[]; closures: SerializedClosures; }>; /** * Serialized representation of a uniqueness constraint. */ type SerializedUniqueConstraint = Readonly<{ name: string; fields: readonly string[]; where: string | undefined; scope: UniquenessScope; collation: Collation; }>; /** * Serialized representation of a node kind. */ type SerializedNodeDef = Readonly<{ kind: string; properties: JsonSchema; uniqueConstraints: readonly SerializedUniqueConstraint[]; onDelete: DeleteBehavior; description: string | undefined; annotations?: KindAnnotations; }>; /** * Serialized representation of an edge kind. */ type SerializedEdgeDef = Readonly<{ kind: string; fromKinds: readonly string[]; toKinds: readonly string[]; targetKindsBySource?: Readonly>; properties: JsonSchema; cardinality: Cardinality; endpointExistence: EndpointExistence; matchIdentity?: Readonly<{ name: string; fields: readonly string[]; }>; description: string | undefined; annotations?: KindAnnotations; }>; /** * Complete serialized schema document. * * This is the format stored in the schema_doc column of * typegraph_schema_versions. The type is kept explicit rather than * inferred from the Zod schema so that downstream code sees the * precise literal union types (DeleteBehavior, TemporalMode, etc.) * instead of the broader `string` type that Zod's passthrough schema uses. */ type SerializedSchema = Readonly<{ graphId: string; /** Consumer-owned graph-scoped JSON metadata. */ annotations?: GraphAnnotations; version: number; generatedAt: string; nodes: Record; edges: Record; ontology: SerializedOntology; defaults: Readonly<{ onNodeDelete: DeleteBehavior; temporalMode: TemporalMode; }>; /** Durable opt-in to the TypeGraph Identity Profile. */ identity?: GraphIdentityConfig; /** * Index declarations attached to the graph. * * Omitted entirely when the graph never declared the slice — legacy * schemas hash byte-identically to before `indexes` existed. Each * entry carries an `origin` discriminator: `"compile-time"` is the * default and is omitted from the canonical form (see `serializer.ts`); * only `"runtime"` is emitted explicitly. */ indexes?: readonly IndexDeclaration[]; /** * Graph extension, when this schema was produced from a graph that * had been merged with one. The loader uses this value (and only * this value) to rebuild extension-kind Zod validators on restart — * the merged `nodes` / `edges` / `ontology` maps above carry the * JSON-Schema-shaped views for diff machinery and human-readable * reporting, but they cannot reconstruct Zod alone. * * Omitted entirely on graphs that have never been extended — legacy * schemas hash byte-identically. */ extension?: GraphExtension; /** * Soft-deprecated node and edge kind names. Set by * `store.deprecateKinds(...)`; cleared by `store.undeprecateKinds(...)`. * Surfaces in introspection but does not affect reads, writes, or * queries. Omitted entirely when empty so legacy schemas hash * byte-identically. */ deprecatedKinds?: readonly string[]; }>; /** * A schema hash for detecting changes. * * We hash the schema content (excluding version and generatedAt) * to detect if the schema has actually changed. */ type SchemaHash = string; /** The version and content hash of one stored schema snapshot. */ type SchemaIdentity = Readonly<{ version: number; hash: SchemaHash; }>; /** * The lock-statement spelling a backend supplies alongside its * `writeFence` declaration. * * `advisory` needs `advisoryLockExpression` and `isolationFactExpression` * (required by that mechanism's own construction-time check below); `row` * needs neither — TypeGraph spells its acquire statement itself from the * fences relation — but MAY supply `isolationFactExpression` so recorded * capture and match-key convergence can still read the session fact off the * same acquisition (absent, they fail closed on an unknown fact, as they do * today). `lockTables` is needed by either mechanism only when its * declaration's `drain` is `"table-lock"`. Every member therefore stays * optional in the type; {@link planFromWriteFenceDeclaration} is what * refuses construction when the RESOLVED mechanism/drain combination needed * a member this object does not supply. * * This is deliberately the ONLY spelling a backend author writes. * {@link resolveFenceStatements} derives the standalone-statement forms * (`acquireKeyed`, `acquireKeyedWithIsolation`, `isolationFact`) from these * expressions — a backend never spells both a statement and the expression * it wraps separately, so the fused embedding and the standalone statement * can never disagree about what they lock or read. * * `advisoryLockExpression`'s `key` accepts a `number` for the * database-scoped locks that key on a constant second argument (`0`) rather * than a hashed value — the two-argument `pg_advisory_xact_lock(int4, int4)` * overload takes that second argument as a plain integer, never as * `hashtext(...)` of one. */ type FenceSql = Readonly<{ /** A relation lock, e.g. `LOCK TABLE ... IN ... MODE`. */ lockTables?: (tables: readonly string[], mode: "share" | "share-row-exclusive" | "access-exclusive") => SqlFragment; /** * The bare lock expression, with no `SELECT` around it. A statement that * must take the lock INSIDE a larger query it composes itself embeds this * directly — PostgreSQL's fused schema + graph-write fence * (`postgres-schema-write-fence.ts`) is the one site that needs this: it * reaches the schema table, so it cannot be built from a standalone * statement. Every other lock site consumes * {@link resolveFenceStatements}'s derived `acquireKeyed`, which wraps * this in a standalone `SELECT`. Absent for a `row`-mechanism target, * which has no lock expression to embed. */ advisoryLockExpression?: (namespace: string, key: string | number) => SqlFragment; /** * The bare session isolation-level read, with no `SELECT`/alias around * it — embedded the same way `advisoryLockExpression` is, and wrapped by * {@link resolveFenceStatements}'s derived `isolationFact` / * `acquireKeyedWithIsolation` for every other site. */ isolationFactExpression?: () => SqlFragment; }>; /** * `FenceSql`'s author-supplied expressions plus the three * mechanism-neutral standalone-statement forms {@link resolveFenceStatements} * derives from them — what a `lock` or `row` plan's `sql` field actually * carries, and what every ordinary lock site consumes. A site never asks * which mechanism produced its `sql`; it calls `acquireKeyed`, * `acquireKeyedWithIsolation`, or `isolationFact` exactly the same way * either way. */ type FenceStatements = FenceSql & Readonly<{ /** * A keyed exclusion, scoped to the transaction: `pg_advisory_xact_lock` * under `advisory`, an `INSERT ... ON CONFLICT ... DO UPDATE ... * RETURNING` against the fences relation under `row`. */ acquireKeyed: (namespace: string, key: string | number) => SqlFragment; /** * The same acquisition plus the session's isolation-level fact, in ONE * statement — the "session facts come from the session that enforces * them" contract: the fact is read on the exact connection the * acquisition was just taken on. Under `row` with no * `isolationFactExpression` supplied, the acquisition still runs and * returns its generation; the isolation fact is simply absent from the * row, which the consumers already read as "unknown" and fail closed on. */ acquireKeyedWithIsolation: (namespace: string, key: string | number) => SqlFragment; /** * The bare session isolation-level read, with no acquisition. Yields no * row when the target supplies no `isolationFactExpression` — the same * "unknown fact" shape a real read produces for a value this fence * cannot classify. */ isolationFact: () => SqlFragment; }>; /** * How a backend excludes concurrent writers, and how far a caller that took * the lock can drain the resource it protects. * * `mechanism` is the exclusion primitive: `"advisory"` is a keyed * `pg_advisory_xact_lock`-style lock a caller takes explicitly (and needs * `fenceSql` to spell); `"row"` is a keyed exclusion spelled by TypeGraph * itself against a never-dropped relation of fence rows, for an engine with * no advisory-lock primitive; `"engine-serialized"` is the engine's own * single writer slot (SQLite); `"caller-serialized"` is a promise the * DEPLOYMENT makes rather than the engine or a lock — the backend's own * process serializes every write unit it issues (see the in-process queue * this mechanism requires) AND no other client writes to the same database * while this backend is open. * * `drain` is a separate fact, and applies ONLY to `mechanism: "advisory"` or * `"row"`: whether a caller that already took the keyed exclusion can * additionally take a relation-wide lock on the resource a table-lock site * protects. `"table-lock"` means yes (a `LOCK TABLE`-style statement is * available and appropriate); `"quiescent"` means the resource is already * exclusive for another reason (e.g. a `caller-serialized` in-process queue * layered alongside an advisory lock) so a table-lock site takes NO * statement rather than one it does not need; `"none"` means neither — a * table-lock site refuses, naming this drain. `"engine-serialized"` and * `"caller-serialized"` carry no `drain`: an engine's single writer slot and * an in-process serialization promise are each already a stronger exclusion * than any `drain` value could add, so there is nothing for the field to say * — declaring one alongside either mechanism is refused * (`WRITE_FENCE_DECLARATION_INVALID`, `validateWriteFenceDeclaration` below). * * `conflict` applies ONLY to `mechanism: "row"`: the engine fact for two * writers of one fence row. `"wait"` is a lock-based engine — the second * acquirer's statement blocks until the first commits, exactly like an * advisory lock. `"commit-time"` is an optimistic-concurrency engine — both * acquirers proceed and the loser's COMMIT fails, so correctness comes from * the unit owner retrying it, never from waiting; the retry can only run * inside an interactive transaction it replays, so `"commit-time"` requires * `capabilities.execution.interactiveTransactions: true` and is refused on a * backend that declares it `false` — the tier `commit-time` needs would * silently never derive otherwise (`WRITE_FENCE_DECLARATION_INVALID`, * `validateWriteFenceDeclaration` below). Declaring `conflict` on any other * mechanism is refused the same way an out-of-place `drain` is. */ type WriteFenceDeclaration = Readonly<{ mechanism: "advisory"; drain: "table-lock" | "quiescent" | "none"; }> | Readonly<{ mechanism: "row"; drain: "table-lock" | "quiescent" | "none"; conflict: "wait" | "commit-time"; }> | Readonly<{ mechanism: "engine-serialized"; }> | Readonly<{ mechanism: "caller-serialized"; }>; /** * The decision every lock site consumes, rather than a flag a caller would * have to re-derive. */ type WriteFencePlan = /** * Take the keyed advisory lock, spelled by `sql` — the target's OWN * declared spelling: a lock site never hand-writes the statement, it * resolves a plan and consumes `sql.(…)`. */ Readonly<{ kind: "lock"; drain: "table-lock" | "quiescent" | "none"; sql: FenceStatements; }> /** * Take the keyed exclusion against the fences relation, spelled by `sql` — * mechanism-neutral: a keyed site calls the exact same `sql.acquireKeyed`/ * `sql.acquireKeyedWithIsolation` a `lock` plan's site calls. `conflict` * is the one fact a `row` site (and the tier deriving `optimistic-retry`) * reads that a `lock` site never needs, because an advisory engine only * ever waits. */ | Readonly<{ kind: "row"; drain: "table-lock" | "quiescent" | "none"; conflict: "wait" | "commit-time"; sql: FenceStatements; }> /** No lock needed: the engine serializes writers. */ | Readonly<{ kind: "engine-serialized"; }> /** * No lock needed: the deployment itself promises no concurrent writer * exists — this backend's own process serializes every write unit it * issues, and no other client writes to the database while it is open. */ | Readonly<{ kind: "caller-serialized"; }> /** Neither. Every non-degradable fence refuses: `capabilities.writeFence` is absent. */ | Readonly<{ kind: "unfenced"; }>; /** * What `resolveWriteFencePlan` needs: the dialect (for the first-party * dialect-derivation arm and for the refusal message), the declared * capabilities, the lock-statement spelling a resolved `"advisory"` or * `"row"` mechanism requires, and — for `"row"` — the resolved table names * carrying the physical name of the fences relation * `resolveFenceStatements` spells its acquisition against. Structural on * purpose — see the module-private first-party mark below, which is * carried out-of-band rather than as a type member. `GraphBackend`'s own * `tableNames` field already satisfies this structurally, so every lock * site that calls `resolveWriteFencePlan(target)` with the backend itself — * narrowed to whatever `Pick` that site declares — reads * the SAME resolved fences table name a `row`-mechanism backend was built * with, with no separate field to keep in sync. */ type WriteFenceTarget = Readonly<{ dialect: SqlDialect; capabilities: BackendCapabilities; fenceSql?: FenceSql | undefined; /** * Read only when the resolved mechanism is `"row"` — specifically * `tableNames.fences`. Typed as the same optional-field `SqlTableNames` * `GraphBackend.tableNames` declares (rather than the fully resolved * `ResolvedSqlTableNames`) so every existing lock site that passes the * backend itself as this target — narrowed to whatever `Pick` that site declares — stays structurally assignable with no * changes; a target whose `tableNames.fences` is genuinely absent refuses * the first time a keyed site actually acquires the fence row instead * (`resolveFenceStatements`'s `requiredFencesTable`, called lazily so a * purely drain-side site that never acquires never refuses over a name it * never needed). */ tableNames?: SqlTableNames | undefined; }>; /** * THE one reader of `capabilities.writeFence`, and THE one constructor of a * {@link WriteFencePlan}. * * Resolution order: * * 1. **`writeFence` declared** — resolve it directly through * {@link planFromWriteFenceDeclaration}. * 2. **Undeclared, first-party factory** — derive from `dialect` * ({@link deriveFromDialect}), which is exactly what every lock site used * to compute inline. Both bundled factories declare `writeFence` * unconditionally, so nothing in-tree reaches this arm; it is reachable * only from tests that build a backend object bypassing the factories' * declared capabilities while still carrying the first-party mark. * 3. **Undeclared, anything else** — `unfenced`. Conservative: an * undeclared custom backend is by definition uncertified, and inferring * lock support from `dialect` alone is the unsound inference this * capability replaces (a PostgreSQL-wire backend reporting `dialect: * "postgres"` need not honor `pg_advisory_xact_lock`). */ declare function resolveWriteFencePlan(target: WriteFenceTarget): WriteFencePlan; /** * THE refusal for a fence that cannot degrade: `unfenced` always refuses, * and a `lock` or `row` plan whose `drain` cannot back the table lock an * operation requires refuses too — the declared-advisory-only (or * declared-row-only) posture (`drain: "none"`), which T15 exercises as its * own matrix row. * * `engine-serialized` and `caller-serialized` satisfy both `requires` * values without consulting `drain`: a writer slot and an in-process, * out-of-band serialization promise are each a stronger exclusion than * either lock shape, so there is nothing for either `requires` to add. * * @throws {ConfigurationError} under `unfenced`, and under * `(kind: "lock" | "row") && drain === "none"` when `requires === "drain"`. */ declare function requireWriteFence(plan: WriteFencePlan, operation: string, requires: "keyed" | "drain"): Extract; declare const ENGINE_REVISION_BRAND: unique symbol; /** * The connection a `revision()`/`changesSince()` read runs on — the * narrowest existing execution-target type a root backend and a * `transaction()` handle both satisfy. Reused rather than invented: it is * the same `Pick` shape the * engine assembly layer already threads as `rawSql`/`rawSqlMembers` * (`backend/drizzle/engine/profile.ts`, `.../operation-layer.ts`) — a * `GraphBackend` is assignable to it for the identical reason those two * members are: `TransactionBackend`'s `execute`/`executeRaw` are themselves * `Pick` projections, so the two types share the exact same * member signatures. */ type LineageSession = Pick; /** * An opaque token identifying the engine's current committed state of the * whole database — comparable only by equality, never parsed or ordered by * a caller. Two backends never share a comparable revision space; a * revision is only ever compared against another revision the SAME * `lineage` produced. */ type EngineRevision = string & Readonly<{ [ENGINE_REVISION_BRAND]: "EngineRevision"; }>; /** One node or edge row, identified the way every lineage delta names a row. */ type EntityKey = Readonly<{ kind: string; id: string; }>; /** * What changed in one graph since a given revision, or an admission that * the source cannot answer. * * `"keys"` lists every node and edge inserted, updated, deleted, or * resurrected after the anchored revision — deduplicated, and covering a * hard delete the same as any other change (a row's disappearance is still * a change a caller must not miss). `"unbounded"` means the source cannot * bound the change set for the given revision — the anchor is unknown, or * older than what the source retains — and the caller must fall back to a * full comparison rather than guess. */ type LineageDelta = Readonly<{ kind: "keys"; nodes: readonly EntityKey[]; edges: readonly EntityKey[]; }> | Readonly<{ kind: "unbounded"; }>; /** * The backend's lineage surface. Optional: a custom backend that omits it * loses only the callers that consult it directly, all of which already * fall back to a full scan when it is absent — see {@link requireLineage}. * * Both members take a {@link LineageSession} as their first argument: the * connection the CALLER'S decision is bound to, not a connection `lineage` * chooses for itself. A caller planning outside any transaction passes the * root backend it holds (`branch()`, `staging.ts`, `base-version.ts`'s * `lineageDeltaSinceAnchor` all do exactly this). A commit-time guard that * already holds an open transaction passes that transaction handle instead * — `graph-merge/merge.ts`'s `assertTargetUnchanged` is the concrete * caller this exists for: it reads `lineage` off the pinned transaction * handle and invokes both members WITH that same handle as the session, so * the read observes the transaction's own snapshot rather than whatever a * separately-held connection happens to see. An implementation MUST run its * read on the session it is given — one that opens its own connection, or * reads through a connection it closed over instead of the argument, is a * defect: it answers from a snapshot the caller never asked for, and inside * an open transaction it also risks colliding with whatever exclusion the * caller's own connection is holding (the bundled caller-serialized SQLite * backend's reentrancy guard refuses exactly this collision with a typed * `ConfigurationError` rather than hanging — see * `tests/graph-merge/base-version-engine-anchor.test.ts`'s * ignores-the-session case). A `session` is always either the backend that * declared this `lineage` or a `transaction()` handle it built, so an * implementation can freely call `session.execute`/`session.executeRaw` * without opening anything of its own. The one documented exception is the * origin half of the bundled recorded-relations `revision()` (see the file * doc above): a graph-identity row that must be ensured and minted off * `session` regardless of which one is passed, not a fact this transaction's * snapshot could answer differently anyway. */ type LineageMembers = Readonly<{ /** The engine's current committed revision of the whole database, read on `session`. */ revision: (this: void, session: LineageSession) => Promise; /** * What changed in `graphId` after `revision`, read on `session`, or * `{ kind: "unbounded" }` when the source cannot answer for that revision. */ changesSince: (this: void, session: LineageSession, revision: EngineRevision, graphId: string) => Promise; }>; /** * Backend interface types for TypeGraph storage. * * The backend abstracts database operations, allowing different * SQL implementations (SQLite, PostgreSQL) behind a common interface. */ /** * Supported vector similarity metrics. */ type VectorMetric = "cosine" | "l2" | "inner_product"; /** * Supported vector index types. */ type VectorIndexType = "hnsw" | "ivfflat" | "none"; /** * The mechanism a strategy uses to combine a row filter with an *approximate* * (ANN) vector search. * * Every approximate search TypeGraph issues carries at least one filter: the * liveness predicate that excludes soft-deleted and out-of-validity rows. Add * a `.where(...)` predicate and it narrows further. Engines differ in where * they apply it relative to the index traversal: * * - `"filter-pushdown"` — the filter constrains the ANN candidate set itself. * sqlite-vec's `vec0` KNN accepts primary-key `IN (SELECT …)` pushdown. * - `"iterative-scan"` — the engine re-enters the index, gathering more * candidates until `LIMIT` rows survive the filter *or* it hits its own scan * bound. pgvector >= 0.8 (`hnsw.iterative_scan` / `ivfflat.iterative_scan`, * applied automatically by the backend). * - `"post-filter"` — the engine retrieves a fixed multiple of the page from * the ANN index and applies the filter afterwards. libSQL's DiskANN * `vector_top_k` is a table function with no filter pushdown, so this is the * only shape available to it. * * This names what the strategy *asks the engine to do*. Whether the engine can * honor it — and whether honoring it is enough to fill a page — is * {@link FilteredApproximateSearch.guaranteesFullPage}. */ type FilteredApproximateSearchMode = "filter-pushdown" | "iterative-scan" | "post-filter"; /** * How a filtered approximate (ANN) vector search behaves, and whether it can * silently return fewer rows than `limit` while more matches exist. * * Read `guaranteesFullPage` — not `mode` — to decide whether a short page is * possible. Only `"filter-pushdown"` guarantees a full page: * * - **sqlite-vec** pushes the filter into the KNN. Exact. * - **pgvector** re-enters the index, but the iterative scan stops at * `hnsw.max_scan_tuples` / `ivfflat.max_probes`, and on **pgvector < 0.8** * there is no iterative scan at all — the backend detects that at runtime, * warns once, and the search stays `ef_search`-bounded, behaving like * `"post-filter"`. Neither case is visible in a statically-declared `mode`, * which is exactly why the guarantee is a separate field. * - **libSQL** over-fetches a fixed multiple of the page and filters * afterwards, so it under-fills once more than that headroom is filtered out. * * A store with heavy tombstone drift — routine in a temporal store — is what * turns a short page from a theoretical caveat into an observed one. Exact * (`approximate: false`) searches are unaffected on every engine: they scan, so * the filter reaches every row. */ type FilteredApproximateSearch = Readonly<{ mode: FilteredApproximateSearchMode; /** * `true` only when a filtered approximate search is guaranteed to return * `limit` rows whenever `limit` matching rows exist — no engine-side scan * bound, no version dependence, no over-fetch headroom to exhaust. */ guaranteesFullPage: boolean; }>; /** * Whether an engine exposes a PER-SEARCH knob for the ANN candidate frontier — * the parameter {@link VectorSearchParams.efSearch} maps onto. * * Declared, never inferred: `efSearch` is an accepted option, so a backend that * cannot apply it must refuse it rather than ignore it (AGENTS.md contract * discipline). This capability is what the shared refusal predicate reads, and * what a caller reads to know whether passing `efSearch` will be honored. * * - **pgvector**: `hnsw.ef_search`, applied per search with `SET LOCAL` inside * the search's own transaction — hence `requiresTransactionScope`, since a * session-wide `SET` would leak the override into concurrent searches. * - **sqlite-vec**: `tunable: false`. A vec0 KNN takes `k` and nothing else; * there is no frontier to widen. * - **libSQL DiskANN**: `tunable: false`. `vector_top_k(idx, q, k)` is a table * function with no per-query parameters; DiskANN's `search_l` is fixed at * index-creation time. */ type VectorSearchFrontierTuning = Readonly<{ tunable: true; /** Engine-native parameter `efSearch` maps to (`"hnsw.ef_search"`). */ parameter: string; /** * The one index type whose search honors the parameter. `efSearch` on a * slot of any other index type is refused, not ignored: an IVFFlat or * brute-force slot would silently discard it. */ indexType: VectorIndexType; /** * `true` when applying the parameter needs a transaction to scope it to * the one search. A backend without interactive transactions refuses * `efSearch` rather than leaking a session-wide setting. */ requiresTransactionScope: boolean; }> | Readonly<{ tunable: false; /** * Why this engine has no per-search frontier knob — surfaced in the * refusal's details so the state is named rather than implied. */ reason: string; }>; /** * Vector search capabilities. */ type VectorCapabilities = Readonly<{ /** Whether the backend supports vector operations */ supported: boolean; /** Supported similarity metrics */ metrics: readonly VectorMetric[]; /** Supported index types */ indexTypes: readonly VectorIndexType[]; /** Maximum dimensions supported */ maxDimensions: number; /** * How a filtered approximate search bounds recall — and whether it can * silently return a short page. See {@link FilteredApproximateSearch}. * * Required, so a new vector strategy cannot omit the declaration and inherit * an engine promise it does not keep. */ filteredApproximateSearch: FilteredApproximateSearch; /** * Whether a per-search ANN frontier override (`efSearch`) can be applied. * See {@link VectorSearchFrontierTuning}. * * Required, so a new vector strategy must state whether it honors the * option instead of inheriting silence — the exact defect this closes. */ searchFrontierTuning: VectorSearchFrontierTuning; }>; /** * Query modes for fulltext search. * * - "websearch": Google-style syntax (quoted phrases, +required, -excluded). * Postgres: `websearch_to_tsquery`. SQLite: translated to FTS5 MATCH. * - "phrase": treats the whole query as a phrase. * Postgres: `phraseto_tsquery`. SQLite: FTS5 `"..."` phrase. * - "plain": splits on whitespace and ANDs terms. * Postgres: `plainto_tsquery`. SQLite: default FTS5 AND. * - "raw": dialect-native syntax passed through unchanged. */ type FulltextQueryMode = "websearch" | "phrase" | "plain" | "raw"; /** * Fulltext search capabilities declared by a backend. */ type FulltextCapabilities = Readonly<{ /** Whether the backend supports fulltext operations */ supported: boolean; /** * Language / tokenizer names understood by this backend. * Postgres: installed regconfigs (english, simple, ...). * SQLite FTS5: tokenizer names (unicode61, porter, trigram). */ languages: readonly string[]; /** Whether phrase queries are supported. */ phraseQueries: boolean; /** Whether prefix (`foo*`) queries are supported. */ prefixQueries: boolean; /** Whether highlighting / snippets are supported. */ highlighting: boolean; }>; /** * Whole-graph analytics capabilities declared by a backend. * * `supported` means the backend can pin one transactional connection while * an algorithm creates, updates, and drops session-local temporary state. * `mathFunctions` records whether transcendental SQL functions needed by * deferred algorithms such as MCL/spectral are available; exact WCC does not * require them. */ type GraphAnalyticsCapabilities = Readonly<{ supported: boolean; mathFunctions: boolean; }>; /** * How much of the contribution health lifecycle a backend can serve. * * The three rungs escalate — probe (read-only) → repair * (non-destructive) → rebuild (destructive) — and each is declared * separately because a backend can genuinely stop at any of them. A * backend that provisions contributions but cannot probe its own catalog * is a real gap, and the honest answer to `store.probeContributions()` * there is a refusal, not `ready`: "assessed and healthy" and "never * looked" must never share a return value. */ type ContributionCapabilities = Readonly<{ /** * Whether strategy-owned contribution tables are provisioned and * attested by durable markers on this backend at all. `false` means * there is nothing to assess, and the probe reports no entries rather * than refusing. */ supported: boolean; /** * Whether the backend can cross its durable markers against the live * catalog — `store.probeContributions()` and * `store.verifyContributions()`. False with `supported: true` is the * declared gap: both refuse with `ConfigurationError`. */ probe: boolean; /** * Whether the backend can run the destructive drop → recreate → * repopulate → stamp rebuild (`store.rebuildContribution()`). Requires * both a strategy that declares `dropDdl` and a transactional schema * fence to run it under; false means `rebuildContribution` refuses. * * Describes the fulltext projection only. `rebuildContribution("vector")` * refuses on every backend regardless of this flag — embeddings exist * only in the storage a rebuild would drop, so there is nothing to * reconstruct them from — and `reembedVectorField` is the sanctioned * destructive path there. */ rebuild: boolean; }>; /** * Backend capabilities that vary by dialect. */ type BackendCapabilities = Readonly<{ /** How this backend executes work and establishes atomicity. */ execution: Readonly<{ /** Whether the backend can hold an interactive callback/session transaction. */ interactiveTransactions: boolean; /** * Where this exact backend object can execute a conformance-earned atomic * batch. `root` owns the transaction boundary; `session` is already bound * to the transaction that makes a closed statement sequence atomic. */ atomicBatch: "none" | "root" | "session"; /** * How this backend groups a multi-statement write into one unit: an * `"interactive"` callback/session transaction that can hold a fenced * conversation across several round trips; `"optimistic-retry"`, the * same interactive transaction on an engine whose write fence resolves * `mechanism: "row"` with `conflict: "commit-time"` — two acquirers of * one fence row both proceed and the loser's COMMIT fails, so every * store-owned unit of work must be prepared to replay from the top; a * `"batch"` atomic program with no such transaction; or `"none"` when it * offers neither and a managed write can only run its statements one at * a time with no atomicity across them. * * Derived, never hand-declared, on every bundled backend through * `deriveUnitOfWork` (`backend/capabilities/execution.ts`) — * `"optimistic-retry"` when `interactiveTransactions` is true AND the * caller supplied the resolved write-fence plan's `conflict` fact as * `"commit-time"`, else `"interactive"` when `interactiveTransactions` * is true, else `"batch"` when `atomicBatch` is not `"none"`, else * `"none"` — overwriting whatever a profile's own `declaredCapabilities` * set. `finalizeEngineCapabilities` is the one place that resolves this * fact fresh, from the root profile's own `row`-mechanism plan; every * later derivation boundary (`downgradeAtomicBatch`, * `scopeAtomicBatchToSession`) carries it forward instead of * re-resolving it, by reading whether its own source object was already * `"optimistic-retry"` — so the tier survives a derived or * session-scoped backend for as long as `interactiveTransactions` stays * `true`. Optional so a custom `GraphBackend` implementation, which * nothing derives this for, is not forced to declare it. * * `src/store/operations/write-transaction.ts`'s retry-routing helpers * are the store-owned `"optimistic-retry"` consumers: every * `runWritePlan`/`runHookedWritePlan`/ * `runAutocommitSingleStatementWritePlan` call, `runIdentityMutation`, * `rebuildIdentityClosureWithSchemaFence`, `rebuildContribution`, and the * index-materialization claim/record calls each wrap their write in * `runRetriedUnit` when this reads `"optimistic-retry"` (and, for the * transaction-opening ones, the write opens its own transaction rather * than joining an existing one). Two backend-owned transactions consult * the SAME tier directly, outside the store's write path entirely: * PostgreSQL's graph-template instantiation row branch and * `runSchemaWriteTransaction` (behind `commitSchemaVersion` and its * three siblings), both in `src/backend/drizzle/postgres.ts` — each * acquires the schema-commit fence row in a `db.transaction(...)` it * opens directly, so each wraps that whole transaction in * `runRetriedUnit` itself rather than routing through the store's * helpers. Two further readers key off the * `"batch"` value: the batch-tier write verdict * (`resolveBatchWriteVerdict` in * `backend/capabilities/batch-write-verdict.ts`) and the autocommit * single-statement eligibility gate (`canFuseSchemaFenceInFirstWrite`). * Absent is treated as anything but `"batch"`: `resolveBatchWriteVerdict` * answers `program` (its "not a batch-tier limitation" verdict) for it, * so a backend that declares neither an interactive transaction nor an * atomic batch still fails closed on a write that needs one — through * that write's own capability check, not through a batch-tier refusal it * never earned. */ unitOfWork?: "interactive" | "optimistic-retry" | "batch" | "none"; }>; /** Whether the backend supports SQL window functions such as ROW_NUMBER() */ windowFunctions: boolean; /** * Whether aggregate calls may contain their own `ORDER BY` clause, as used * by ordered scalar and record collection. Absent is `false`: custom and remote * backends must opt in only when their active engine accepts that syntax. */ orderedAggregates?: boolean; /** * Whether `updateNode` / `updateEdge` honor `clearValidTo: true` by storing * SQL NULL in `valid_to`. Absent is `false`: custom backends must opt in so * the store refuses every explicit clear-bearing call before lookup, * coalescing, or write instead of making support depend on row state. */ clearValidTo?: boolean; /** * Whether the backend's `execute()` supports `UPDATE … RETURNING`. Absent or * `true` means supported (every engine TypeGraph ships — SQLite ≥ 3.35, * PostgreSQL ≥ 8.2 — supports it). A custom backend whose engine cannot run * `RETURNING` must set this to `false` so recorded-time history capture * (`history: true`), which relies on `UPDATE … RETURNING` on its hot path, * is refused up front instead of failing mid-flush. */ returning?: boolean; /** * Maximum number of bound parameters the engine accepts in one statement. * SQLite defaults to 999 (raisable at compile time via * `SQLITE_MAX_VARIABLE_NUMBER`), while hosted SQLite runtimes may impose a * lower platform ceiling. PostgreSQL's wire protocol encodes a 65535-count, * but postgres.js accepts at most 65533 bound values, so the bundled * PostgreSQL capability advertises that lower shared-driver ceiling. * Recorded-time capture and recorded point reads size their multi-row * statements to this ceiling — the same budget the backend's own batched * inserts use — instead of a conservative dialect-blind constant. Custom * runtimes can override a detected or probed limit here, but hosted platform * hard ceilings may only be lowered. Absent means the recorded-time fallback * budget applies. */ maxBindParameters?: number; /** * Whether this backend supplies the claim relations that fence declared * constraints (`uniques`, re-keyed onto claim axes, plus * `typegraph_edge_claims`) AND implements the claim members. * * Absent means `false`: the backend predates the claim relations, keeps every * fence it has today, and is listed as such in the parity matrix. Never * INFERRED from member presence — a projection that forwarded `capabilities` * while dropping a method would otherwise yield a verdict read from a * different object than the write goes to. `claimSupport` * (`store/claims/backing.ts`) is the one reader, and it refuses a backend * whose declaration and surface disagree in either direction. */ readonly constraintClaims?: boolean; /** * Whether the command port can atomically apply the claim portion of * a node create. Absent means projection-only: method presence alone is not * evidence that a custom backend understands the newer plan field, so the * store retains the standalone claim fallback. */ readonly atomicNodeInsertClaims?: boolean; /** Whether edge writes atomically persist and arbitrate `matchIdentity`. */ readonly durableEdgeMatchIdentity?: boolean; /** Vector search capabilities (undefined if not configured) */ vector?: VectorCapabilities | undefined; /** Fulltext search capabilities (undefined if not configured) */ fulltext?: FulltextCapabilities | undefined; /** Whole-graph analytics capabilities (undefined when unavailable). */ graphAnalytics?: GraphAnalyticsCapabilities | undefined; /** * How far up the contribution health ladder this backend goes * (undefined on a backend with no contribution machinery at all, * equivalent to every member `false`). */ contributions?: ContributionCapabilities | undefined; /** * Whether this engine can compute a bounded transitive closure of a * relation in one round trip (a recursive CTE, or a graph-native * expansion operator). * * Absent means SUPPORTED. Every engine TypeGraph ships supports it, and * every custom backend on this release already has every recursion site * run against it unconditionally: making absence mean `false` would * refuse traversals that work today. See * {@link RecursiveTraversalCapability} for the full contract. */ recursiveTraversal?: RecursiveTraversalCapability | undefined; /** * How this backend excludes concurrent writers: a keyed advisory lock, a * single writer slot the engine serializes by construction, or a * deployment-level promise that this process serializes its own writes and * no other client writes to the database — see * {@link WriteFenceDeclaration} for the full `mechanism`/`drain` contract. * * Absent means `unfenced` for any backend the first-party factories did * not build (`resolveWriteFencePlan`, `src/backend/capabilities/write-fence.ts`): * an undeclared custom backend is refused at construction for Operational * Identity and for TypeGraph-owned recorded-clock allocation (`history` / * `revisionTracking`) rather than silently emitting locks the engine may * not honor. */ writeFence?: WriteFenceDeclaration | undefined; }>; type BackendExecutionCapabilities = BackendCapabilities["execution"]; /** * Capability overrides accepted by bundled backend factories. * * Root atomic-batch support is deliberately absent: bundled factories derive * it from the transport they actually discover and register. Callers may * override interactive transaction availability for an otherwise unknown * driver, but cannot advertise an executor the factory did not find. */ type BundledBackendCapabilityOverrides = Readonly, "execution"> & { execution?: Readonly<{ interactiveTransactions?: boolean; }>; }>; /** Keeps session-scoped analytics honest when a required SQL feature is absent. */ declare function normalizeGraphAnalyticsCapabilities(capabilities: BackendCapabilities): BackendCapabilities; /** * Returns whether the supplied backend or capability declaration can keep an * interactive transaction open across awaited statements. */ declare function supportsInteractiveTransactions(backendOrCapabilities: BackendCapabilities | Readonly<{ capabilities: BackendCapabilities; }>): boolean; /** Returns whether this exact root declares certified atomic batch support. */ declare function supportsRootAtomicBatch(backendOrCapabilities: BackendCapabilities | Readonly<{ capabilities: BackendCapabilities; }>): boolean; /** * A node/edge row's `props` column as the driver returned it: SQLite always * yields the JSON text; PostgreSQL drivers yield the jsonb value already * parsed. Keeping the driver's shape avoids a per-row * parse→stringify→re-parse round trip on the PostgreSQL read path — * consumers normalize through {@link rowPropsToObject} / * {@link rowPropsToJsonText} at the point of use. */ type RowProps = string | Readonly>; /** The row's props as an object, parsing only when the driver gave text. */ declare function rowPropsToObject(props: RowProps): Record; /** The row's props as JSON text, serializing only when the driver gave an object. */ declare function rowPropsToJsonText(props: RowProps): string; /** * A row from the typegraph_nodes table. */ type NodeRow = Readonly<{ graph_id: string; kind: string; id: string; props: RowProps; version: number; valid_from: string | undefined; valid_to: string | undefined; created_at: string; updated_at: string; deleted_at: string | undefined; }>; /** * A {@link NodeRow} proven live (not soft-deleted). Functions that must only * ever touch live rows — the live-row update pipeline, soft delete — take * this type so a caller holding a possibly-tombstoned row is forced through * {@link isLiveNodeRow} first, instead of silently running live-row side * effects (uniqueness reservations, embedding/fulltext sync) for a row that * stays invisible. */ type LiveNodeRow = NodeRow & Readonly<{ deleted_at: undefined; }>; /** * A {@link NodeRow} proven soft-deleted. A resurrect targets exactly this * shape; see {@link isTombstonedNodeRow}. */ type TombstonedNodeRow = NodeRow & Readonly<{ deleted_at: string; }>; declare function isLiveNodeRow(row: NodeRow): row is LiveNodeRow; declare function isTombstonedNodeRow(row: NodeRow): row is TombstonedNodeRow; /** * The read-only slice of a backend. Give this type to code that is * semantically a read — probes, gates, support-graph loads — so a read path * structurally CANNOT open a write transaction, mutate rows, or execute raw * SQL: those members do not exist on the type. Extend the pick as read paths * need more of the read surface. */ type GraphReadBackend = Pick; /** * A row from the typegraph_edges table. */ type EdgeRow = Readonly<{ graph_id: string; id: string; kind: string; from_kind: string; from_id: string; to_kind: string; to_id: string; props: RowProps; match_identity_name?: string; match_identity_key?: string; valid_from: string | undefined; valid_to: string | undefined; created_at: string; updated_at: string; deleted_at: string | undefined; }>; /** The schema-declared identity persisted with an edge row. */ type EdgeMatchIdentityStorage = Readonly<{ name: string; key: string; }>; /** * A row from the typegraph_node_uniques table. */ type UniqueRow = Readonly<{ graph_id: string; node_kind: string; constraint_name: string; key: string; node_id: string; concrete_kind: string; deleted_at: string | undefined; }>; /** * A row from the typegraph_schema_versions table. */ type SchemaVersionRow = Readonly<{ graph_id: string; version: number; schema_hash: string; schema_doc: string; created_at: string; is_active: boolean; }>; /** A durable, server-resident materialized schema template. */ type GraphTemplateRow = Readonly<{ template_id: string; schema_hash: string; schema_doc: string; created_at: string; }>; /** * Parameters for inserting a node. */ type InsertNodeParams = Readonly<{ graphId: string; kind: string; id: string; props: Readonly>; /** * Omitted (`undefined`): the insert stamps its own creation timestamp — * UNLESS a stated `validTo` at or before that instant would make the stored * window readable at no coordinate, in which case the row is stored with no * lower bound ("ended at T, start unknown"). See * `resolveStampedValidityLowerBound`, which every insert builder decides * through. Custom backend implementations can import that owner from * `@nicia-ai/typegraph/backend` rather than reimplementing the rule. * `null`: preserves an explicit open-left validity window (no lower * bound) — used by interchange import to round-trip a row that was * already NULL, instead of re-stamping it to the import's own timestamp. */ validFrom?: string | null; validTo?: string; }>; /** One validated plain caller-ID member of the heterogeneous node upsert CTE. */ type HeterogeneousNodeUpsertEntry = Readonly<{ kind: string; id: string; /** Complete create-parsed document used for inserts and resurrections. */ props: Readonly>; /** Caller patch used only when the target row is already live. */ updateProps: Readonly>; }>; /** Exact-session input for the PostgreSQL heterogeneous node upsert lowering. */ type HeterogeneousNodeUpsertParams = Readonly<{ entries: readonly HeterogeneousNodeUpsertEntry[]; schemaFence: SchemaWriteFenceParams; }>; /** * A backend 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 SQL `NULL`. */ type BackendValidityEndMutation = Readonly<{ validTo?: string; clearValidTo?: never; }> | Readonly<{ validTo?: never; clearValidTo: true; }>; /** Parameters for updating a node. */ type UpdateNodeParams = Readonly<{ graphId: string; kind: string; id: string; props: Readonly>; /** * Applied when resurrecting a tombstone, which RESETS the window. Omitted * means the resurrection instant — unless a stated `validTo` at or before it * would leave the row readable at no coordinate, in which case the * resurrection stores no lower bound. Same rule, same owner, as an insert: * `resolveStampedValidityLowerBound`. * * The store's own resurrection paths never omit it. Their window guard has to * judge the bound the write will STORE, so they resolve it through that owner * against the instant they sampled and pass the result — `null` included, * which is how "store no lower bound" is spelled here. Omission is for callers * with no such verdict to honor; it lets this builder decide against its own, * strictly later sample. */ validFrom?: string | null; /** * The effective `valid_from` this write ASSERTS the target row already * carries, stated only by a caller whose decision DEPENDED on it. * * `(graph_id, kind, id)` does not pin a row across time. A caller that * validated the document's window against the bound it probed — interchange * import's `onConflict: "update"` is the case — decided what to write from a * value a concurrent `hardDelete` + recreate can replace between the probe * and the write, so a predicate on identity alone lets that decision land on * a row it was never computed for: the document's stated `validFrom` is * ignored, or a `valid_to` is persisted below the new row's `valid_from`. * Stating the bound here puts it in the UPDATE's own `WHERE`, which is the * only placement the race cannot slip past. * * NULL-SAFE: `null` asserts the row has NO lower bound (`IS NULL`), a string * asserts equality, and omitting it asserts nothing. `null` and `undefined` * are therefore NOT interchangeable here — see `expectedValidFromPredicate`. * * A backend MUST apply the predicate when it is present, on the same terms as * {@link UpdateEdgeParams.kind}: silently ignoring it re-opens the window the * caller stated it to close. A write whose stated bound does not match affects * zero rows and surfaces as a `no_row_returned` `DatabaseOperationError`. */ expectedValidFrom?: string | null; incrementVersion?: boolean; /** If true, clears deleted_at (un-deletes the node). Used by upsert. */ clearDeleted?: boolean; }> & BackendValidityEndMutation; /** * Parameters for updating a set of live nodes selected by a compiled * TypeGraph query. * * The candidate query must expose the named node-id column from the same graph * and kind. The backend independently fences the rows it writes to * `graphId` and `kind`; constructing the matching candidate query remains the * caller's responsibility. * Property values replace top-level keys, including preserving an explicit * JSON `null` value. * * This is a storage primitive: callers that expose it as a graph mutation are * responsible for schema validation and for synchronizing uniqueness, * fulltext, and vector sidecars. The result contains after-images only; callers * that need previous property values must freeze the candidate set and retain * its before-images in the same transaction before invoking this method. */ type UpdateNodeSetParams = Readonly<{ operation: "updateWhere"; graphId: string; kind: string; patch: Readonly>; /** Top-level properties to remove (the storage form of an `undefined` patch value). */ unsetProperties?: readonly string[]; candidateIds: CompiledSelectSql; /** Projected SQL column holding the candidate node id (for example `n_id`). */ candidateIdColumn: string; }>; /** * A guarded set update whose exact current-value predicates are applied by the * same outer UPDATE that writes the row. Kept as a distinct backend port so an * older/custom `updateNodeSet` implementation can never silently ignore the * compare-and-set fence. */ type NodePropertyExpectation = Readonly<{ kind: "value"; value: JsonScalar; }> | Readonly<{ kind: "absent"; }>; type CompareAndSetNodeParams = Readonly<{ operation: "compareAndSet"; graphId: string; kind: string; patch: Readonly>; unsetProperties?: readonly string[]; candidateIds: CompiledSelectSql; candidateIdColumn: string; /** * Exact scalar-or-absence predicates re-checked by the outer UPDATE. This * discriminated map cannot be assigned to an ordinary set update. */ expected: Readonly>; }>; /** The after-images changed by {@link GraphBackend.updateNodeSet}. */ type UpdateNodeSetResult = Readonly<{ affectedCount: number; rows: readonly NodeRow[]; }>; /** One resolved, whole-row replacement in a guarded node batch. */ type ResolvedNodeUpdateBatchEntry = Readonly<{ graphId: string; kind: string; id: string; props: Readonly>; /** The preimage version the batch must still observe for every member. */ expectedVersion: number; }>; /** * Replaces several distinct live rows in one statement and returns their * after-images. The all-or-nothing version gate makes this safe to use before * rebuilding shared sidecars in a portable transaction. */ type ResolvedNodeUpdateBatchParams = Readonly<{ entries: readonly ResolvedNodeUpdateBatchEntry[]; }>; /** * Parameters for deleting a node (soft delete). */ type DeleteNodeParams = Readonly<{ graphId: string; kind: string; id: string; }>; /** * Parameters for inserting an edge. */ type InsertEdgeParams = Readonly<{ graphId: string; id: string; kind: string; fromKind: string; fromId: string; toKind: string; toId: string; props: Readonly>; /** * Durable schema-declared match identity. Both values are written with the * edge row and arbitrated by the database's edge-identity unique key. */ matchIdentity?: EdgeMatchIdentityStorage; /** * Omitted (`undefined`): the insert stamps its own creation timestamp — * UNLESS a stated `validTo` at or before that instant would make the stored * window readable at no coordinate, in which case the row is stored with no * lower bound ("ended at T, start unknown"). See * `resolveStampedValidityLowerBound`, which every insert builder decides * through. Custom backend implementations can import that owner from * `@nicia-ai/typegraph/backend` rather than reimplementing the rule. * `null`: preserves an explicit open-left validity window (no lower * bound) — used by interchange import to round-trip a row that was * already NULL, instead of re-stamping it to the import's own timestamp. */ validFrom?: string | null; validTo?: string; }>; /** * Write-only session exposed inside {@link GraphBackend.trustedImport}. * * The caller has accepted the trusted-import contract: properties, endpoint * references, uniqueness, and cardinality are not validated. Backends may * therefore use engine-native ingestion primitives that bypass the normal * store write pipeline. */ type TrustedImportSession = Readonly<{ insertNodes: (params: readonly InsertNodeParams[]) => Promise; insertEdges: (params: readonly InsertEdgeParams[]) => Promise; }>; /** * Parameters for updating an edge. */ type UpdateEdgeParams = Readonly<{ graphId: string; id: string; /** * The kind this write ASSERTS the target row already carries. * * Edge ids are graph-global while public collections are kind-scoped, so a * kind-scoped write has an expected kind and must not land on a row carrying * a different one. Stating it here puts the predicate in the write * statement's own `WHERE`, which is the only placement a concurrent * `hardDelete` + recreate cannot slip past: a read-then-write pair keyed on * `(graph_id, id)` alone re-resolves that id between the read and the write * under PostgreSQL READ COMMITTED. A write whose stated kind does not match * affects zero rows. * * A backend MUST apply the predicate when it is present — silently ignoring * it re-opens the window the caller stated it to close, and the ignoring * backend looks correct until it is raced. * `tests/edge-write-self-verification.test.ts` asserts the behavior against * the built-in backends. * * Omitted where the operation legitimately spans kinds: the node delete * cascade removes every connected edge whatever its kind, so it states none. * {@link DeleteEdgeParams} and {@link HardDeleteEdgeParams} carry the same * field with the same contract. */ kind?: string; /** * The ENDPOINTS this write asserts the target row already carries, stated * only by a write that actually checked them. * * Kind alone does not pin a row's identity. An edge's endpoints are immutable * for a given row, but the ID is not: a concurrent `hardDelete` + recreate * under the SAME kind with DIFFERENT endpoints satisfies a kind-only * predicate, so an upsert that resolved this id BY endpoints could otherwise * write to an edge pointing somewhere it never looked. Carrying them here * closes that, on the same terms and in the same `WHERE` as `kind`. * * All four move together or not at all — they are one assertion. A plain * `update` on a kind-scoped collection states none of them, because it * resolved the edge by id and kind and made no claim about where it points; * predicating on endpoints it never checked would refuse legitimate writes. * The same MUST-apply contract as `kind` binds a backend that receives them. */ fromKind?: string; fromId?: string; toKind?: string; toId?: string; props: Readonly>; /** * Applied when resurrecting a tombstone, where it asserts a COMPLETE window: * `validTo` is rewritten alongside it (omitted `validTo` reopens the window). * Omitting `validFrom` on a resurrection leaves the stored window in place. */ validFrom?: string | null; /** * The effective `valid_from` this write asserts the target row already * carries. Same three states, same NULL-safety, and the same MUST-apply * contract as {@link UpdateNodeParams.expectedValidFrom}; stated by the same * kind of caller, one whose verdict READ that bound. * * Separate from the immutable-identity components above because it is not * immutable: a resurrection rewrites `valid_from`. It is asserted for the * opposite reason — precisely because it can change, a decision computed from * it must be fenced against it having changed. */ expectedValidFrom?: string | null; /** * The `valid_to` state a constraint decision read. NULL-safe and MUST apply * when present, like `expectedValidFrom`; used to fence ended-to-open edges. */ expectedValidTo?: string | null; clearDeleted?: boolean; }> & BackendValidityEndMutation; /** * Parameters for deleting an edge (soft delete). */ type DeleteEdgeParams = Readonly<{ graphId: string; id: string; /** See {@link UpdateEdgeParams.kind}. */ kind?: string; }>; /** * Parameters for hard deleting a node (permanent removal). */ type HardDeleteNodeParams = Readonly<{ graphId: string; kind: string; id: string; }>; /** * Parameters for hard deleting an edge (permanent removal). */ type HardDeleteEdgeParams = Readonly<{ graphId: string; id: string; /** See {@link UpdateEdgeParams.kind}. */ kind?: string; }>; /** Parameters for a batched edge delete (soft or hard). */ type DeleteEdgesBatchParams = Readonly<{ graphId: string; ids: readonly string[]; /** See {@link UpdateEdgeParams.kind}. */ kind?: string; }>; /** * Parameters for inserting or updating an embedding. * * `dimensions`, `metric`, and `indexType` together resolve the slot's * typed per-`(nodeKind, fieldPath)` storage (the strategy needs the fixed * dimension for the column type, and the metric/index type to address the * right ANN structure). The store populates them from the schema's * `embedding()` declaration via `getEmbeddingDimensions()` / * `getEmbeddingIndex()`; the backend constructs a `VectorSlot` from them * and stays graph-agnostic. */ type UpsertEmbeddingParams = Readonly<{ graphId: string; nodeKind: string; nodeId: string; fieldPath: string; embedding: readonly number[]; dimensions: number; metric: VectorMetric; indexType: VectorIndexType; }>; /** * A projection written from the node row's `RETURNING` source by a fused * generated-id insert. Identity is deliberately absent: the backend derives * graph, kind, and id from the inserted-row CTE, so a sidecar cannot disagree * with the node row it accompanies. */ type NodeInsertProjection = Readonly<{ kind: "embedding"; fieldPath: string; embedding: readonly number[]; dimensions: number; metric: VectorMetric; indexType: VectorIndexType; }> | Readonly<{ kind: "fulltext"; action: "upsert"; content: string; language: string; }> | Readonly<{ kind: "fulltext"; action: "delete"; }>; /** * A uniqueness-relation claim carried by an atomic node-insert plan. * * Node identity is deliberately absent: the backend derives the owner pair * from the node insert parameters, so the claim cannot name a different row. * Placement is decided by the store's claim-site owner and preserved by the * SQL compiler as an explicit dependency around the node insert. */ type NodeInsertClaimVerdict = Readonly<{ kind: "uniqueness"; probeAxes: readonly string[]; fields: readonly string[]; }> | Readonly<{ kind: "disjointness"; conflictingKinds: readonly string[]; }>; type NodeInsertClaim = Readonly<{ axis: string; constraintName: string; key: string; placement: "pre-insert" | "post-insert"; /** * The complete read set for this claim. A canonical claim upsert only sees * the folded axis; the additional uniqueness axes preserve compatibility * with rows written before that fold. Disjoint claims name their live-node * fallback population instead, because old databases can have no claim row * for an existing disjoint overlap. */ verdict: NodeInsertClaimVerdict; }>; /** The two atomic node-insert shapes supported by a planned-write backend. */ type ManagedNodeCreateMode = Readonly<{ kind: "ordinary"; }> | Readonly<{ kind: "schema-fenced"; schemaFence: SchemaWriteFenceParams; }>; /** A complete managed node create, including its row and atomic write plan. */ type ManagedNodeCreatePlan = Readonly<{ entity: "node"; params: InsertNodeParams; /** Whether the node id was generated by the store rather than supplied. */ idGenerated: boolean; mode: ManagedNodeCreateMode; claims: readonly NodeInsertClaim[]; projections: readonly NodeInsertProjection[]; }>; /** A complete managed edge create, including its row and atomic write plan. */ type ManagedEdgeCreatePlan = Readonly<{ entity: "edge"; params: InsertEdgeParams; schemaFence?: SchemaWriteFenceParams; cardinalityClaim?: ClaimEdgeCardinalityParams; }>; /** A node create command accepted by the authoritative command port. */ type NodeCreateCommand = Readonly<{ kind: "node.create"; plan: ManagedNodeCreatePlan; }>; /** An edge create command accepted by the authoritative command port. */ type EdgeCreateCommand = Readonly<{ kind: "edge.create"; plan: ManagedEdgeCreatePlan; }>; /** The authoritative match-key read paired with a convergent edge create. */ type EdgeConvergenceMatch = Readonly<{ kind: "dynamic"; matchOn: readonly string[]; props: Record; }> | Readonly<{ kind: "durable"; identity: EdgeMatchIdentityStorage; }>; /** An edge create command that atomically returns an existing match or inserts. */ type EdgeConvergeCreateCommand = Readonly<{ kind: "edge.converge-create"; plan: ManagedEdgeCreatePlan; match: EdgeConvergenceMatch; }>; /** The semantic commands currently supported by the authoritative port. */ type GraphCommand = NodeCreateCommand | EdgeCreateCommand | EdgeConvergeCreateCommand; type NodeCreateCommandResult = Readonly<{ outcome: "created"; entity: "node"; row: NodeRow; }> | Readonly<{ outcome: "rejected"; entity: "node"; reason: "unknown"; }> | Readonly<{ outcome: "unsupported"; entity: "node"; dimensions: readonly [ "schemaFence" | "claims" | "projections", ...(readonly ("schemaFence" | "claims" | "projections")[]) ]; }>; type EdgeCreateCommandResult = Readonly<{ outcome: "created"; entity: "edge"; row: EdgeRow; }> | Readonly<{ outcome: "rejected"; entity: "edge"; reason: "unknown"; }> | Readonly<{ outcome: "unsupported"; entity: "edge"; dimensions: readonly [ "schemaFence" | "cardinalityClaim" | "endpointPredicate", ...(readonly ("schemaFence" | "cardinalityClaim" | "endpointPredicate")[]) ]; }>; type EdgeConvergeCreateCommandResult = Readonly<{ outcome: "created"; entity: "edge"; row: EdgeRow; }> | Readonly<{ outcome: "found"; entity: "edge"; row: EdgeRow; }> | Readonly<{ outcome: "rejected"; entity: "edge"; reason: "unknown"; }> | Readonly<{ outcome: "unsupported"; entity: "edge"; dimensions: readonly [ "convergence" | "endpointPredicate", ...(readonly ("convergence" | "endpointPredicate")[]) ]; }>; /** The session boundary on which an authoritative command executes. */ type GraphCommandSession = "root" | "transaction"; /** Effective transaction isolation observed on the command's physical session. */ type GraphCommandIsolation = "read_committed" | "repeatable_read" | "serializable" | "unknown"; declare const GRAPH_COMMAND_COORDINATION_BRAND: unique symbol; /** Compile-time evidence that graph-write coordination is already established. */ type GraphCommandCoordination = Readonly<{ [GRAPH_COMMAND_COORDINATION_BRAND]: true; }>; type GraphCommandExecutionContext = Readonly<{ session: "root"; coordination: "none"; }> | Readonly<{ session: "transaction"; coordination: "none" | GraphCommandCoordination; }>; /** The result union returned by the authoritative command port. */ type GraphCommandResult = NodeCreateCommandResult | EdgeCreateCommandResult | EdgeConvergeCreateCommandResult; /** The single extensible authority boundary for semantic backend commands. */ type GraphCommandPort = Readonly<{ /** The database session this port is structurally bound to. */ session: GraphCommandSession; /** * Execute an authoritative command. First-party Store paths pass an * explicit execution context through * `executeAuthoritativeGraphCommand`. */ execute: (this: void, command: GraphCommand, context: GraphCommandExecutionContext) => Promise; }>; /** * One row of a batched embedding upsert. */ type UpsertEmbeddingBatchRow = Readonly<{ nodeId: string; embedding: readonly number[]; }>; /** * Parameters for a batched embedding upsert. Homogeneous per vector slot: * one graph, one node kind, one field, many nodes. Duplicate `nodeId` * values within a batch are deduplicated last-write-wins by the backend * (a multi-row upsert cannot affect one row twice). */ type UpsertEmbeddingBatchParams = Readonly<{ graphId: string; nodeKind: string; fieldPath: string; dimensions: number; metric: VectorMetric; indexType: VectorIndexType; rows: readonly UpsertEmbeddingBatchRow[]; }>; /** * Parameters for deleting an embedding. * * `dimensions` / `metric` / `indexType` mirror {@link UpsertEmbeddingParams} * so the backend can resolve the slot's typed per-`(nodeKind, fieldPath)` * storage and idempotently ensure it exists before the DELETE. That * matters because a delete can run before any embedding was ever written * (e.g. a node hard-deleted having never carried one), and on Postgres a * DELETE against a missing relation inside a transaction aborts the whole * transaction. The store populates them from the schema's `embedding()` * declaration, exactly as for upserts. */ type DeleteEmbeddingParams = Readonly<{ graphId: string; nodeKind: string; nodeId: string; fieldPath: string; dimensions: number; metric: VectorMetric; indexType: VectorIndexType; }>; /** * Parameters for vector similarity search. */ type VectorSearchParams = Readonly<{ graphId: string; nodeKind: string; fieldPath: string; queryEmbedding: readonly number[]; metric: VectorMetric; /** * Fixed vector dimension `N` for the searched `(nodeKind, fieldPath)` * slot. The store populates it from the schema's `embedding()` * declaration via `getEmbeddingDimensions()`; the backend uses it to * construct the `VectorSlot` whose typed storage the strategy scans. */ dimensions: number; /** * Index type materialized for this slot. `"none"` means brute-force * only; otherwise the strategy may route through its ANN structure. * The store populates it from `getEmbeddingIndex()`. */ indexType: VectorIndexType; limit: number; minScore?: number; /** * Subquery of eligible node ids (single column, exposed as `node_id`). * When present, the search statement computes top-k over this candidate * set — the store passes its compiled current-read query here so * `where` predicates push down into the search SQL. Exact on engines * whose search form takes the filter inside retrieval (pgvector, * sqlite-vec, tsvector, FTS5); libSQL's DiskANN cannot pre-filter, so * it post-filters an over-fetched ANN set and recall against the * candidate set is bounded by that headroom. When absent, the backend * restricts to CURRENT nodes of the kind — non-tombstoned AND inside * their validity window — matching a `current` read. */ candidates?: SqlFragment; /** * Rows to skip AFTER ranking (pagination). The engine fetches * `limit + offset` ranked candidates and discards the first `offset`. */ offset?: 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 over-fetch to fill its * candidate set is `efSearch >= limit`; ~2–4× is the high-recall * target. Applied transaction-locally (`SET LOCAL`) on the Postgres * HNSW path only. * * APPLIED OR REFUSED, never ignored. Read * `capabilities.vector.searchFrontierTuning` to know which you get: * * - `tunable: false` (sqlite-vec, libSQL DiskANN — no per-search frontier * knob exists): `UnsupportedBackendCapabilityError`. * - a slot whose index type is not the tunable one (pgvector IVFFlat or * brute-force): `ConfigurationError`. * - `requiresTransactionScope` on a backend reporting * `execution.interactiveTransactions: false` (for example `drizzle-orm/neon-http`): * `UnsupportedBackendCapabilityError`, because `SET LOCAL` has no frame * to be local to. */ efSearch?: number; }>; /** * Result from a vector similarity search. */ type VectorSearchResult = Readonly<{ nodeId: string; /** * Cosine metric returns similarity score (higher is better). * L2 and inner_product return raw distance (lower is better). */ score: number; }>; /** * Parameters for a single-statement hybrid (vector + fulltext, RRF-fused) * search. The store facade resolves every default before calling — the * per-source candidate depths (`k`), the fusion constants, and the shared * `candidates` subquery — so the backend composes `SqlFragment` values without policy. */ type HybridSearchParams = Readonly<{ graphId: string; nodeKind: string; vector: Readonly<{ fieldPath: string; queryEmbedding: readonly number[]; metric: VectorMetric; dimensions: number; indexType: VectorIndexType; /** Candidate depth of the vector source (rows entering fusion). */ k: number; minScore?: number; efSearch?: number; }>; fulltext: Readonly<{ query: string; mode?: FulltextQueryMode; language?: string; /** Candidate depth of the fulltext source (rows entering fusion). */ k: number; minScore?: number; includeSnippets?: boolean; }>; fusion: Readonly<{ /** RRF rank constant (`1 / (k + rank)`). */ k: number; vectorWeight: number; fulltextWeight: number; }>; /** Fused rows to return after `offset`. */ limit: number; /** Fused rows to skip (rank-relative pagination). */ offset?: number; /** See {@link VectorSearchParams.candidates}; applies to both sources. */ candidates?: SqlFragment; }>; /** * One fused hybrid hit, hydrated in the same statement: the node row * rides along so the caller needs no follow-up id fetch. */ type HybridSearchRow = Readonly<{ node: NodeRow; fusedScore: number; vectorRank?: number; vectorScore?: number; fulltextRank?: number; fulltextScore?: number; snippet?: string; }>; /** * Parameters for creating a vector index. */ type CreateVectorIndexParams = Readonly<{ graphId: string; nodeKind: string; fieldPath: string; dimensions: number; metric: VectorMetric; indexType: VectorIndexType; /** Index-specific parameters */ indexParams?: Readonly<{ /** HNSW: max connections per layer */ m?: number; /** HNSW: construction search depth */ efConstruction?: number; /** IVFFlat: number of lists */ lists?: number; }>; /** * Build the index without taking an `AccessExclusiveLock` on live * tables (Postgres `CREATE INDEX CONCURRENTLY`). Mirrors the * `concurrent` flag the relational DDL path uses inside * `materializeIndexes()`. Cannot be set inside a transaction — * callers (`materializeIndexes()`) run at top level. Backends that * don't have a CONCURRENTLY equivalent (SQLite) ignore this flag. */ concurrent?: boolean; }>; /** * Parameters for dropping a vector index. */ type DropVectorIndexParams = Readonly<{ graphId: string; nodeKind: string; fieldPath: string; }>; /** * Parameters for inserting or updating a fulltext entry. * * One row per node. Callers concatenate the searchable fields into * `content` so a single MATCH query can find terms spread across fields. * * Note: `createFulltextIndex` / `dropFulltextIndex` were removed as * dead code in #PR_E. The fulltext table's canonical index (Postgres * GIN on `tsv`, SQLite FTS5 virtual table) is created with the table * itself by `bootstrapTables` per the active `FulltextStrategy`; * per-kind fulltext indexes are an "advanced strategy" surface that * doesn't fit the relational-style declaration model and is reserved * for future work. */ type UpsertFulltextParams = Readonly<{ graphId: string; nodeKind: string; nodeId: string; content: string; language: string; }>; /** * Parameters for deleting a single fulltext entry. */ type DeleteFulltextParams = Readonly<{ graphId: string; nodeKind: string; nodeId: string; }>; /** * A single row in a fulltext batch upsert. */ type FulltextBatchRow = Readonly<{ nodeId: string; content: string; language: string; }>; /** * Parameters for a batched fulltext upsert. * * Homogeneous: one graph, one node kind, many nodes. Duplicate `nodeId` * values within a single batch are deduplicated last-write-wins by the * builders before SQL generation — Postgres `ON CONFLICT` errors on * repeated conflict keys in one statement, and SQLite `DELETE + INSERT` * would create duplicate virtual-table rows otherwise. */ type UpsertFulltextBatchParams = Readonly<{ graphId: string; nodeKind: string; rows: readonly FulltextBatchRow[]; }>; /** * Parameters for a batched fulltext delete. */ type DeleteFulltextBatchParams = Readonly<{ graphId: string; nodeKind: string; nodeIds: readonly string[]; }>; /** * Parameters for fulltext search. */ type FulltextSearchParams = Readonly<{ graphId: string; nodeKind: string; /** The user-supplied query string. */ query: string; /** How to parse `query`. Default: "websearch". */ mode?: FulltextQueryMode; /** * Language for query parsing. The store facade resolves the kind's * declared language and passes it here, so the tsquery is a plan-time * constant (GIN-servable); when absent the backend falls back to the * per-row language column. * Postgres: passed as the regconfig to `to_tsquery` / `websearch_to_tsquery`. * SQLite: informational only (FTS5 tokenizer is fixed at table-create time). */ language?: string; /** Max rows to return. */ limit: number; /** Minimum rank to include (backend-dependent units). */ minScore?: number; /** Whether to return a highlighted snippet per match. */ includeSnippets?: boolean; /** * Subquery of eligible node ids (single column). When present, the * search statement computes top-k over this candidate set — the store * passes its compiled current-read query here so `where` predicates, * subclass expansion, and valid-time currency all push down into the * search SQL. Exact on engines whose search form takes the filter * inside retrieval (pgvector, sqlite-vec, tsvector, FTS5); libSQL's * DiskANN cannot pre-filter, so it post-filters an over-fetched ANN * set and recall against the candidate set is bounded by that * headroom. When absent, the backend restricts to CURRENT nodes of * the kind — non-tombstoned AND inside their validity window — * matching a `current` read. */ candidates?: SqlFragment; /** * Rows to skip AFTER ranking (pagination). The engine fetches * `limit + offset` ranked candidates and discards the first `offset`. */ offset?: number; }>; /** * Result from a fulltext search. * * Score semantics differ by backend; prefer `rank` (1-based) when fusing * with another source via RRF. */ type FulltextSearchResult = Readonly<{ nodeId: string; /** * Backend-native relevance score. * Postgres: `ts_rank_cd` (higher is better). * SQLite FTS5: negated `bm25()` (higher is better; FTS5 returns lower-is-better). */ score: number; /** 1-based rank within the result set, suitable for RRF. */ rank: number; /** Highlighted snippet of the content (if `includeSnippets` was set). */ snippet?: string; }>; /** * Parameters for creating a fulltext index. * * The canonical index (Postgres GIN on `tsv`, SQLite FTS5 virtual table) * is created when the fulltext table itself is created. This is reserved * for advanced per-kind specializations. */ /** * Per-deployment record for a single declared index. * * Identified by `indexName` because SQL index names are physical, * database-global identifiers — `graphId` is provenance, not identity. * `materializedAt` is null until the first successful CREATE INDEX * completes; `lastAttemptedAt` is always set, even on failure. */ /** * Parameters for atomically claiming an index build (see * `claimIndexMaterialization`). The declaration fields ride along so a * fresh claim row is honest while the build is in flight. */ type ClaimIndexMaterializationParams = Readonly<{ indexName: string; graphId: string; entity: IndexEntity; kind: string; signature: string; schemaVersion: number; /** Opaque claim identity; release is token-guarded. */ token: string; /** A claim older than this is stale and may be taken over. */ leaseMs: number; }>; /** Parameters for releasing a build claim (token-guarded). */ type ReleaseIndexMaterializationClaimParams = Readonly<{ indexName: string; token: string; }>; type IndexMaterializationRow = Readonly<{ indexName: string; graphId: string; entity: IndexEntity; kind: string; signature: string; schemaVersion: number; materializedAt: string | undefined; lastAttemptedAt: string; lastError: string | undefined; }>; /** * Parameters for upserting a materialization attempt. * * On success: pass `materializedAt` (ISO timestamp) and undefined `error`. * On failure: pass undefined `materializedAt` (preserve any existing * timestamp from a prior success) and the error message. */ type RecordIndexMaterializationParams = Readonly<{ indexName: string; graphId: string; entity: IndexEntity; kind: string; signature: string; schemaVersion: number; attemptedAt: string; /** ISO timestamp on success; undefined on failure (preserves existing). */ materializedAt: string | undefined; /** Error message on failure; undefined on success (clears existing). */ error: string | undefined; }>; /** * Durable identity of a strategy-owned table contribution (#129), * scoped to a graph. This is the primary key of * `typegraph_contribution_materializations`. * * `logicalName` is the stable slot ("fulltext"); `owner` is the * producing strategy ("tsvector" / "fts5"); `tableName` is the resolved * physical name (custom per-deployment names must be distinguishable). * `graphId` is part of identity here — unlike the index status table * where the physical index name is database-global, two graphs can each * own a logically-identical fulltext contribution. */ type ContributionMaterializationIdentity = Readonly<{ graphId: string; logicalName: string; owner: string; tableName: string; }>; /** * Per-deployment record that a strategy-owned contribution has been * durably materialized against this database. * * `signature` is intentionally NOT part of the identity: a row with the * same identity but a different signature means "materialized artifact * is stale/drifted" — a loud error on the hot path, never a silent * re-materialize. `materializedAt` is undefined until the first * successful materialization; `lastAttemptedAt` is always set. */ type ContributionMaterializationRow = Readonly<{ graphId: string; logicalName: string; owner: string; tableName: string; signature: string; materializedAt: string | undefined; lastAttemptedAt: string; lastError: string | undefined; }>; /** * Parameters for upserting a contribution-materialization attempt. * Same success/failure contract as * {@link RecordIndexMaterializationParams}: on failure pass undefined * `materializedAt` so a prior successful timestamp is preserved via * COALESCE, and the error message. */ type RecordContributionMaterializationParams = Readonly<{ graphId: string; logicalName: string; owner: string; tableName: string; signature: string; attemptedAt: string; /** ISO timestamp on success; undefined on failure (preserves existing). */ materializedAt: string | undefined; /** Error message on failure; undefined on success (clears existing). */ error: string | undefined; }>; /** * Why a contribution is not usable. Most members are a disagreement * between the durable marker and the physical catalog; one * (`failed-materialization`) is a state the two agree on and that is * broken anyway. A contribution that is genuinely healthy — or that was * simply never attempted — is not reported at all. * * - `orphaned-marker` — the marker records a successful materialization * but the physical table is gone (a partial restore, an out-of-band * `DROP`, a schema-scoped restore that missed the contribution * tables). The state {@link GraphBackend.verifyContributions} exists * to surface: nothing on the open path probes the catalog, so this * database opens clean and fails at the first dependent read or write. * - `missing-marker` — the physical table exists but no marker attests * it as initialized (no row, no recorded success, or a recorded * failure). Reads and writes are refused with * `StoreNotInitializedError` even though storage is present. These * three causes share one state because they share one repair; check * {@link ContributionDiagnostic.lastError} to tell them apart. * - `failed-materialization` — the marker records a *failed* attempt * and no table was produced. Marker and catalog agree here, so this * is not a disagreement — but it is where a contribution lands when * provisioning genuinely broke (the `fts5` module is absent, the role * lacks `CREATE`, the extension was never loaded), and calling it * healthy on the grounds that both sides agree would be the one * answer this method must never give. Distinguished from a * contribution that was never attempted, which has no marker row and * is correctly silent. {@link ContributionDiagnostic.lastError} * carries the reason it failed. * - `stale` — the table exists and the marker records a prior success * at a different signature (a strategy swap, or a declared embedding * dimension that has moved ahead of the provisioned table). * * **Do not re-frame this union as "how the marker disagrees with the * catalog."** It was described that way once, and the description was * load-bearing in the wrong direction: under it, a marker recording a * failed attempt with no table looks like agreement, therefore not a * disagreement, therefore correctly silent — and a contribution that * had genuinely broken was reported as healthy. The gap read as * principled rather than as an oversight precisely because the framing * endorsed it. "Why the contribution is unusable" is the question that * makes `failed-materialization` obviously belong, and any future * tidy-up that narrows the framing back will re-open the same hole. */ type ContributionDiagnosticState = "orphaned-marker" | "missing-marker" | "failed-materialization" | "stale"; /** * One contribution that is not usable, reported by * {@link GraphBackend.verifyContributions}. * * The identity fields come from the active contribution declaration and * match the durable-marker identity contract, so a caller can route an entry * to its repair without reconstructing any internal naming contract. A * `missing-marker` entry may have no marker row at all. * * **Route on {@link ContributionDiagnosticState}, not on whether the * entry is a vector slot.** The repair differs per state and the wrong * choice destroys data: `store.reembedVectorField` drops and recreates * storage, so applying it to a `missing-marker` — where the table is * intact and only the bookkeeping is wrong — discards every embedding * to fix a marker, and without an `embed` callback it leaves the field * empty. Prefer `store.repairContributions()` for `missing-marker` and * `failed-materialization`; it resolves the current strategy declarations * internally and keeps the normal signature-drift guard enabled. It reports * `stale` and `orphaned-marker` as `requires-rebuild`. See the per-state * repair table in the troubleshooting guide. */ type ContributionDiagnostic = Readonly<{ /** Producing strategy, e.g. `"fts5"` / `"tsvector"` / `"pgvector"`. */ owner: string; /** Stable logical slot, e.g. `"fulltext"`. Never the SQL name. */ logicalName: string; /** Resolved physical table name that was probed. */ physicalName: string; /** Node kind — vector slots only. */ kind?: string; /** Embedding field path — vector slots only. */ fieldPath?: string; state: ContributionDiagnosticState; /** * The error the marker recorded against its last attempt, when it * recorded one. Absent otherwise. * * This is the part of the picture the catalog cannot supply. `state` * says what to do — the states are chosen so that each maps to one * repair — while this says *why it broke*, which is a different * question and often a different investigation. A failed attempt with no * table surfaces as `failed-materialization`; when the table exists but * the marker does not attest a successful materialization, it surfaces as * `missing-marker`. This field distinguishes only failures whose marker * recorded a reason; a missing row has no `lastError`. Present on any state * whose marker row carries an error, not just those two states. */ lastError?: string; }>; /** * Outcome of one contribution considered by * {@link GraphBackend.repairContributions}. * * Repair targets are always resolved from the backend's current strategy * declarations. The diagnostic is returned for operator context only; callers * never pass diagnostics or physical DDL back into the repair API. */ type ContributionRepairEntry = Readonly<{ diagnostic: ContributionDiagnostic; status: "repaired"; }> | Readonly<{ diagnostic: ContributionDiagnostic; status: "requires-rebuild"; }> | Readonly<{ diagnostic: ContributionDiagnostic; status: "failed"; error: string; }>; /** Result of a contribution repair pass followed by a fresh verification. */ type ContributionRepairResult = Readonly<{ results: readonly ContributionRepairEntry[]; remaining: readonly ContributionDiagnostic[]; }>; /** * The search projection a probe entry summarizes. Deliberately the * logical *class* rather than one physical table: a caller asking "is * search ready?" is deciding whether to issue a fulltext or a vector * query, and a per-`(kind, field)` breakdown of pgvector tables answers * a question they did not ask. The physical detail an operator needs to * act is one `verifyContributions()` call away, and * {@link ContributionProbeEntry.detail} names the affected slots. */ type ContributionProbeContribution = "fulltext" | "vector"; /** * Readiness of one search projection. * * - `ready` — every contribution backing this projection is attested by * its durable marker and present in the catalog. Not a promise about * future coherence: a write that lands after the probe returns is * outside what any assessment can cover. * - `degraded` — at least one contribution is unusable. Dependent operations * may be refused with a typed contribution error, fail at the engine boundary * when compiled SQL directly references missing storage, or observe * incomplete storage. * {@link ContributionProbeEntry.detail} says which and why. * - `building` — **reserved.** No shipped path publishes it. Recording * an in-flight marker would need a fifth * {@link ContributionDiagnosticState} and would widen the hot-path * gate's verdict set; the destructive rebuild instead runs inside one * transaction, so a concurrent probe observes the state before or * after it and never a partial one. Reserved rather than removed so a * future streaming/async materializer can populate it without a * breaking change — treat it as "not `ready`" today. */ type ContributionProbeState = "ready" | "degraded" | "building"; /** * One search projection's readiness, from * {@link GraphBackend.probeContributions}. */ type ContributionProbeEntry = Readonly<{ contribution: ContributionProbeContribution; state: ContributionProbeState; /** * Operator-facing summary of why the projection is not `ready` — * e.g. `"fulltext table \"typegraph_node_fulltext\" is missing * (orphaned-marker)"`. Absent on a `ready` entry. * * Human-readable and not a stable format: route on `state`, and call * `store.verifyContributions()` for the structured per-table * diagnostics this string is derived from. */ detail?: string; }>; /** * Result of {@link GraphBackend.probeContributions}. * * A projection with no declared contributions is omitted rather than * reported `ready`, so an empty `entries` array means "nothing to * assess" — a graph with no `searchable()` or `embedding()` fields, or a * backend with no contribution support — never "assessed and healthy". */ type ContributionProbeResult = Readonly<{ /** * The durable graph revision the assessment was taken at, so a caller * can place this probe in the graph's committed history. * * Graph-global, like the clock it reads: it advances on every committed * capture from any writer, so an advance between two probes means * "something committed in between", never "the write this caller just * made landed". Confirm a specific write by observing the write itself. * * Absent unless the Store is revision-tracked (`revisionTracking: * true` or `history: true`) — the same condition under which * `store.revisionNow()` returns a value. This is the one honest shape: * a store with no durable revision clock has no revision to stamp, and * substituting a wall-clock timestamp or the schema version would be a * materially weaker guarantee wearing the name of a stronger one. The * schema version in particular does not advance on data writes, so it * could not order a probe against a caller's write at all. */ graphRevision?: string; entries: readonly ContributionProbeEntry[]; }>; /** * Which search projection {@link GraphBackend.rebuildContribution} * targets. Scoped explicitly at the call site rather than inferred from * a diagnostic: the operation destroys storage, so which storage is the * caller's decision to state, not TypeGraph's to guess. */ type ContributionRebuildScope = ContributionProbeContribution; /** * What a rebuild's `repopulate` callback reconstructed. Reported back so * the rebuild result can distinguish "recreated empty storage" from * "recreated and refilled", which is the difference between a finished * repair and half of one. */ type ContributionRepopulationStats = Readonly<{ /** Nodes scanned. */ processed: number; /** Content rows written. */ repopulated: number; /** Nodes whose stored `props` could not be read as an object. */ skipped: number; }>; /** Outcome of one destructive contribution rebuild. */ type ContributionRebuildResult = Readonly<{ /** Physical tables dropped and recreated, in the order rebuilt. */ rebuilt: readonly string[]; /** Nodes scanned while reconstructing content from stored rows. */ processed: number; /** Content rows written into the recreated storage. */ repopulated: number; /** * Nodes whose stored `props` could not be read as an object, so no * content row could be reconstructed for them. Non-zero means the * rebuilt index is missing those nodes; the IDs come back from * `store.search.rebuildFulltext()`, which reports them individually. */ skipped: number; }>; /** * One row of the per-deployment `typegraph_kind_removals` table: * a graph-extension kind that has been removed from the schema and whose * data may or may not have been cleaned up yet. */ type KindRemovalRow = Readonly<{ graphId: string; kindName: string; entity: KindEntity; schemaVersion: number; /** ISO timestamp when the data-cleanup pass succeeded; undefined while pending. */ removedAt: string | undefined; lastAttemptedAt: string; lastError: string | undefined; }>; /** * Upsert payload for a kind-removal status row. On removal-commit: * pass `removedAt: undefined` (the pending state). On successful * data-cleanup: pass `removedAt` set and `error: undefined`. On * cleanup failure: pass `removedAt: undefined` (preserves any prior * timestamp from a partial success on a different replica) and the * error message. */ type RecordKindRemovalParams = Readonly<{ graphId: string; kindName: string; entity: KindEntity; schemaVersion: number; attemptedAt: string; /** ISO timestamp on success; undefined while pending or on failure. */ removedAt: string | undefined; /** Error message on failure; undefined on success or while pending. */ error: string | undefined; }>; /** * Transaction options. */ type TransactionOptions = Readonly<{ /** Transaction isolation level (if supported) */ isolationLevel?: "read_uncommitted" | "read_committed" | "repeatable_read" | "serializable"; /** * Transaction access mode (if supported). `read_only` is intended for * multi-statement reads that need one snapshot and must not perform writes. */ accessMode?: "read_only" | "read_write"; }>; /** * The physical names of the four Operational Identity relations: the current * assertion ledger, its recorded-time twin, and the two DERIVED relations * (closure, separation) rebuilt from the ledger. * * Named once so the two ports that speak about them — * {@link GraphBackend.ensureIdentityTables} and * {@link GraphBackend.identityTableDdl} — cannot drift apart. */ type IdentityTableNames = Readonly<{ identityAssertions: string; recordedIdentityAssertions: string; identityClosure: string; identitySeparation: string; }>; /** * The physical names of the three recorded relations. Named once so the * ports that speak about them cannot drift apart — the {@link IdentityTableNames} * precedent. */ type RecordedTableNames = Readonly<{ recordedClock: string; recordedEdges: string; recordedNodes: string; }>; /** * One recorded relation's provisioning DDL, with the roles the caller needs * SEPARATED rather than positional. */ type RecordedRelationDdl = Readonly<{ /** The single `CREATE TABLE` statement. */ createTable: string; /** Its index/constraint statements, in application order. */ indexes: readonly string[]; /** * The name the ENGINE will have given this relation's PRIMARY KEY constraint * once `createTable` has executed, or `undefined` on an engine that does not * name PK constraints separately (SQLite). * * NOT "the name the DDL text declares": both bundled emitters push an unnamed * inline `PRIMARY KEY (…)` (`ddl.ts:114-118`, `:441-445`), so on PostgreSQL the * server derives `_pkey`. The point of the field is that the derivation * is an ENGINE convention, and the only honest source of it is whoever authored * the DDL for that engine — a migration that reconstructs it bakes one adapter's * convention in. * * The name is returned UNREDUCED. Reducing an over-long identifier is a * separate decision with a separate owner (`shortenedIdentifier`), and it * applies only to the RENAME's target. The source name is the *temporary* * table's PK name, and temp table names are hash-derived and short BY * CONSTRUCTION (`` `__tg_${role}_${shortHash(tableName)}` ``, an 8-char * hash, so `_pkey` is at most ~21 bytes for any configured table * name) — nowhere near the 63-byte ceiling. So the split is a structural * fact about the temp-name shape, not a claim about PostgreSQL's * truncation semantics: `shortenedIdentifier` is the identity on the * source for every reachable input, and its effect is observable only on * the target. * * A backend must apply this option consistently across name sets: return a * name for both the temporary and final relation, or `undefined` for both. * The migration refuses a mixed pair because silently dropping either name * would leave the swapped relation with a backend-inconsistent constraint. */ primaryKeyConstraintName?: string | undefined; }>; /** * The database-global extensions TypeGraph installs on a caller's behalf. * * The extent is closed on purpose: {@link GraphBackend.ensureExtension} * interpolates the name into DDL, so the allowlist — not the caller — is what * decides the identifier can be trusted. `pg_trgm` backs `method: "trigram"` * index materialization; `vector` backs pgvector storage. */ declare const DATABASE_EXTENSION_NAMES: readonly ["pg_trgm", "vector"]; /** A name {@link GraphBackend.ensureExtension} accepts. */ type DatabaseExtensionName = (typeof DATABASE_EXTENSION_NAMES)[number]; /** * Optional durable-identity batch write seam. Every input must carry * `matchIdentity`; identity conflicts are omitted from the returned rows. */ type DurableEdgeBatchMembers = Readonly<{ insertEdgesDurableBatchReturning?: (this: void, params: readonly InsertEdgeParams[]) => Promise; }>; /** * The GraphBackend interface abstracts database operations. * * Implementations should provide: * - SQLite backend via better-sqlite3 or libsql * - PostgreSQL backend via pg or postgres * * Every function is receiver-free (`this: void`). Implementations must close * over their state instead of reading `this`, which makes saved optional * capabilities and other detached port calls safe by construction. */ type GraphBackend = Readonly<{ /** The SQL dialect */ dialect: SqlDialect; /** Backend capabilities */ capabilities: BackendCapabilities; /** Table names used by this backend (for query schema auto-derivation) */ tableNames?: SqlTableNames | undefined; /** * Optional fulltext strategy override. When present, both the compiler * (for `$fulltext.matches()` in query builder) and backend-direct * search paths use this instead of the dialect's default strategy — * allowing a Postgres backend to ship pg_trgm, ParadeDB, pgroonga etc. * When absent, the dialect's default strategy is used. */ fulltextStrategy?: FulltextStrategy | undefined; /** * Optional vector strategy this backend is wired with. The query * compiler reads it (via `compileQuery` options) to emit per-`(kind, * field)` relevance scans for `field.similarTo(...)` predicates, and * the index-materialization / removal paths read its deterministic * `tableName(...)` to address the right physical per-field storage. * `undefined` when the backend has no vector support (e.g. a generic * SQLite backend without sqlite-vec). */ vectorStrategy?: VectorStrategy | undefined; /** * The lock-statement spelling this backend's `capabilities.writeFence` * declaration requires when `mechanism` is `"advisory"` — resolved through * {@link resolveWriteFencePlan} rather than read directly by a lock site. * Absent on a backend that serializes writers instead (SQLite's writer * slot needs no lock statement at all) or that declares no usable fence. */ fenceSql?: FenceSql | undefined; insertNode: (this: void, params: InsertNodeParams) => Promise; /** * Inserts a node only when its primary-key slot is empty. `undefined` means * the slot was already occupied; unlike `insertNode`, that outcome is not a * database error and leaves the surrounding transaction usable. * * First-party SQL backends expose this for the caller-supplied-id create * fast path. Custom backends may omit it and retain the probe-then-insert * path. */ insertNodeIfAbsent?: (this: void, params: InsertNodeParams) => Promise; /** * First-party transactional fast path: the INSERT takes the active-schema * shared fence in its source query. A missing row is deliberately ambiguous * (stale schema versus an occupied id) and must be diagnosed by the caller. */ insertNodeIfAbsentWithSchemaFence?: (this: void, params: InsertNodeParams, schemaFence: SchemaWriteFenceParams) => Promise; /** Like `insertNode`, but acquires the schema fence in that INSERT. */ insertNodeWithSchemaFence?: (this: void, params: InsertNodeParams, schemaFence: SchemaWriteFenceParams) => Promise; insertNodeNoReturn?: (this: void, params: InsertNodeParams) => Promise; insertNodesBatch?: (this: void, params: readonly InsertNodeParams[]) => Promise; insertNodesBatchReturning?: (this: void, params: readonly InsertNodeParams[]) => Promise; updateNode: (this: void, params: UpdateNodeParams) => Promise; /** One data-modifying CTE for plain caller-ID node upserts across kinds. */ upsertHeterogeneousNodes?: (this: void, params: HeterogeneousNodeUpsertParams) => Promise; updateNodeSet?: (this: void, params: UpdateNodeSetParams) => Promise; updateResolvedNodesBatch?: (this: void, params: ResolvedNodeUpdateBatchParams) => Promise; compareAndSetNode?: (this: void, params: CompareAndSetNodeParams) => Promise; deleteNode: (this: void, params: DeleteNodeParams) => Promise; hardDeleteNode: (this: void, params: HardDeleteNodeParams) => Promise; getNode: (this: void, graphId: string, kind: string, id: string) => Promise; getNodes?: (this: void, graphId: string, kind: string, ids: readonly string[]) => Promise; insertEdge: (this: void, params: InsertEdgeParams) => Promise; /** Executes semantic authoritative graph commands. */ commands: GraphCommandPort; insertEdgeNoReturn?: (this: void, params: InsertEdgeParams) => Promise; insertEdgesBatch?: (this: void, params: readonly InsertEdgeParams[]) => Promise; insertEdgesBatchReturning?: (this: void, params: readonly InsertEdgeParams[]) => Promise; updateEdge: (this: void, params: UpdateEdgeParams) => Promise; deleteEdge: (this: void, params: DeleteEdgeParams) => Promise; hardDeleteEdge: (this: void, params: HardDeleteEdgeParams) => Promise; /** * Batched {@link deleteEdge}: one soft-delete statement per bind-budget * chunk instead of one per edge. Optional — cascade deletes fall back to * the per-edge form when unset. Same per-row semantics, including an * optional asserted kind and idempotence on already-tombstoned rows. */ deleteEdgesBatch?: (this: void, params: DeleteEdgesBatchParams) => Promise; /** Batched {@link hardDeleteEdge}; see {@link deleteEdgesBatch}. */ hardDeleteEdgesBatch?: (this: void, params: DeleteEdgesBatchParams) => Promise; getEdge: (this: void, graphId: string, id: string) => Promise; getEdges?: (this: void, graphId: string, ids: readonly string[]) => Promise; countEdgesFrom: (this: void, params: CountEdgesFromParams) => Promise; edgeExistsBetween: (this: void, params: EdgeExistsBetweenParams) => Promise; findEdgesConnectedTo: (this: void, params: FindEdgesConnectedToParams) => Promise; findNodesByKind: (this: void, params: FindNodesByKindParams) => Promise; countNodesByKind: (this: void, params: CountNodesByKindParams) => Promise; findEdgesByKind: (this: void, params: FindEdgesByKindParams) => Promise; /** * Reads the edges of a SET of endpoints in one statement per bind-budget * chunk — see {@link FindEdgesByEndpointSetParams}. * * **Optional, and its absence is the capability signal.** `store.edges. * .bulkFindFrom` / `.bulkFindTo` check for this method before issuing any * read and refuse with a typed `ConfigurationError` when it is missing, * rather than degrading to a per-endpoint loop. A caller reaching for a bulk * endpoint read is asking for set-oriented statements; quietly giving them N * singleton statements is the surprise the method exists to prevent. * * Both bundled Drizzle backends implement it. A custom backend that does not * simply omits it and the bulk reads refuse; the singleton `findEdgesByKind` * path is unaffected. */ findEdgesByEndpointSet?: (this: void, params: FindEdgesByEndpointSetParams) => Promise; /** * Reads several edge kinds from a heterogeneous set of endpoints without * issuing one statement per licensed `(edge kind, endpoint kind)` pair. * * Optional for custom backends. Store-level heterogeneous bulk reads refuse * when it is absent rather than hiding a per-kind/per-endpoint fallback. */ findEdgesByHeterogeneousEndpointSet?: (this: void, params: FindEdgesByHeterogeneousEndpointSetParams) => Promise; countEdgesByKind: (this: void, params: CountEdgesByKindParams) => Promise; insertUnique: (this: void, params: InsertUniqueParams) => Promise; /** * Batched variant of `insertUnique`: one multi-row statement per chunk * with the same per-entry conflict semantics (throws `UniquenessError` * for the first entry whose key a different live node holds). Optional — * callers fall back to per-entry `insertUnique` when unset. */ insertUniqueBatch?: (this: void, entries: readonly InsertUniqueParams[]) => Promise; deleteUnique: (this: void, params: DeleteUniqueParams) => Promise; /** * Permanently removes every uniqueness sidecar owned by the specified * concrete nodes. Optional capability used by set-based updates before they * rebuild reservations from the returned node after-images. */ hardDeleteUniquesByNodeIds?: (this: void, params: HardDeleteUniquesByNodeIdsParams) => Promise; /** * Permanently removes every uniqueness claim owned by nodes of one concrete * kind, at whatever axis each claim sits on. THE definition of "the claims * this kind owns" — kind removal reaps through it so it can neither leak a * claim whose axis is a sibling kind nor delete a surviving sibling's claim. * Optional capability. */ hardDeleteUniquesByConcreteKind?: (this: void, params: HardDeleteUniquesByConcreteKindParams) => Promise; checkUnique: (this: void, params: CheckUniqueParams) => Promise; checkUniqueBatch?: (this: void, params: CheckUniqueBatchParams) => Promise; /** * Takes the claim on one edge cardinality axis, in two statements: a * decision-free create-or-lock that reports the committed holder, and — only * when that holder is a different edge — a conditional takeover that succeeds * exactly when the incumbent is no longer an edge the axis and key describe. * * The claim is the FENCE for a declared cardinality: `(kind, from)` and * `(kind, from, to)` are predicates the edges primary key `(graph_id, id)` * cannot enforce, so without this relation two concurrent writers can both * pass the probe and both commit. Read through the `constraintClaims` * capability, never through member presence — see `claimSupport` * (`store/claims/backing.ts`). */ claimEdgeCardinality?: (this: void, params: ClaimEdgeCardinalityParams) => Promise; /** * Strong single-claim variant which, while holding the claim row, also * refuses any claimless live edge that already matches the declared axis. * * Member presence is an explicit optimization contract: callers may omit * the separate entity-relation cardinality probe only when the exact write * target exposes this operation. Custom and legacy backends that implement * only `claimEdgeCardinality` retain the probe-first path unchanged. */ claimEdgeCardinalityGuarded?: (this: void, params: ClaimEdgeCardinalityParams) => Promise; /** * Batched variant of `claimEdgeCardinality`: one multi-row create-or-lock * statement, then a takeover statement only for the entries a different edge * holds. Outcomes are returned positionally, one per entry. * * Callers must not pass two entries with the same conflict target * (`axis`, `key`): a multi-row upsert cannot affect one row twice. */ claimEdgeCardinalityBatch?: (this: void, entries: readonly ClaimEdgeCardinalityParams[]) => Promise; /** * Housekeeping only: drops the claim rows named edges hold, so hard deletes, * kind removal and `clearGraph` do not grow the relation without bound. The * FENCE never depends on it having run — a claim whose holder is no longer * live (or, for `oneActive`, no longer active) is taken over in place. */ purgeEdgeClaims?: (this: void, params: PurgeEdgeClaimsParams) => Promise; /** * Read-only diagnostic: the rows that make a declared constraint currently * violated, for a caller (`store.verifyConstraintFences()`) that folds them * onto the claim axes they contend for. * * It reads the ENTITY relations for the two families whose pre-upgrade * violations no claim row records — a database written before the claim * relations existed holds no edge claims at all — and the `uniques` relation * for uniqueness, where a pre-upgrade duplicate is exactly two live rows * sitting at two different `node_kind`s. A scan of a claim relation's primary * key can find neither, which is why this is not one. * * Returns the contending ROWS, not the verdict: the axis a `uniques` row * belongs to is a fold over the graph's subclass component, and the key an * edge's claim sits on is `EDGE_CARDINALITY_SPECS`' — both of which live * above the backend, so a backend that decided either would be a second * spelling of a decision the fence already owns. * * Writes nothing: no DDL, no claim writes. Safe on a replica and under a * least-privilege role. Present only on backends that can run the audit; * `store.verifyConstraintFences()` refuses with a typed error when absent * rather than reporting an empty (and therefore reassuring) result. */ readConstraintFenceViolations?: (this: void, params: ReadConstraintFenceViolationsParams) => Promise; getActiveSchema: (this: void, graphId: string) => Promise; getSchemaVersion: (this: void, graphId: string, version: number) => Promise; /** * Atomically inserts a new schema version and activates it as a single * transactional unit, with optimistic compare-and-swap on the currently * active version. * * - If `expected.kind === "active"` and the actual active version * differs, throws `StaleVersionError` (caller should refetch and * retry). * - If a row already exists at `params.version` with the same * `schemaHash`, returns it idempotently — reactivating it if it was * left inactive by an earlier crashed commit. * - If a row already exists at `params.version` with a different * `schemaHash`, throws `SchemaContentConflictError`. * * Requires `capabilities.execution.interactiveTransactions === true`. On non-transactional * backends (e.g. Cloudflare D1, drizzle-orm/neon-http) this method * throws `ConfigurationError` rather than running with degraded * atomicity that would silently re-introduce the orphan-row crash * window the primitive exists to eliminate. */ commitSchemaVersion: (this: void, params: CommitSchemaVersionParams) => Promise; /** * Commit a schema version only if every requested kind is empty. The probes * and schema CAS run under one backend-owned write fence, preventing a * participating schema-managed Store write from landing between the final * count and commit. Raw Stores and direct backend writes are not fenced. */ commitSchemaVersionIfKindsEmpty?: (this: void, params: CommitSchemaVersionParams, probes: readonly SchemaKindEmptinessProbe[]) => Promise; /** * Acquire the transaction-scoped shared fence for a schema-managed graph * write, then verify that the active schema still matches the Store that is * issuing it. Official transactional backends provide this method; custom * backends may omit it, in which case schema-managed Store writes fail * closed rather than racing a schema change. This method must be called on a * transaction-scoped backend. PostgreSQL locks the active schema row, so a * concurrent change either becomes visible at read committed or raises the * database's native serialization failure at stronger isolation. * * On rejection the thrown `StaleVersionError` reports the active * version this transaction can observe after the conflict resolves, so * `details.actual` names the version that won rather than the absence the * blocked read momentarily saw. */ lockSchemaVersionForWrite?: (this: void, params: Readonly<{ graphId: string; expectedVersion: number; }>) => Promise; /** * PostgreSQL/PGlite transaction-scoped fast path that takes the active * schema `FOR SHARE` fence and the recorded graph advisory lock in one SQL * statement, in that order. The graph-lock CTE is reachable only through a * matching schema row, so a stale Store never acquires the later lock. * * Bundled PostgreSQL/PGlite transaction targets expose this member; their * root backends omit it so it cannot be used outside an already-open * transaction. SQLite/custom transaction backends omit it too. A zero-row * fence runs the same honest active-version diagnostic as * {@link GraphBackend.lockSchemaVersionForWrite} and throws its * `StaleVersionError`. */ lockSchemaVersionAndGraphWrite?: (this: void, params: SchemaWriteFenceParams) => Promise; /** * Internal schema-lifecycle seam for features whose data preflight must * commit atomically with the schema CAS. The callback runs in the same * write transaction after the schema write fence is acquired and before the * version write; it receives no schema-version write methods, so callers * cannot bypass the CAS. * * The preflight target is a {@link SchemaCommitPreflightBackend}: the * transaction backend plus the schema-write DDL primitive, because a * transition may have to CREATE the storage its own preflight then fills — * and creating it outside this transaction would publish it empty whenever * the commit is refused. * * @internal */ commitSchemaVersionWithPreflight?: (this: void, params: CommitSchemaVersionParams, preflight: (target: SchemaCommitPreflightBackend) => Promise) => Promise; /** * Atomically flips the active schema pointer to an existing version, * with optimistic compare-and-swap on the currently active version. * Used by `rollbackSchema` and any other "promote/demote existing * version" workflow. Throws `StaleVersionError` on CAS mismatch and * `MigrationError` if the target version row does not exist. * * Same transactional requirements as `commitSchemaVersion`. */ setActiveVersion: (this: void, params: SetActiveVersionParams) => Promise; /** Register an immutable materialized schema template. */ registerGraphTemplate?: (this: void, params: Readonly<{ templateId: string; schemaHash: string; schemaDoc: SerializedSchema; }>) => Promise; /** * Instantiate a v1 schema from a registered template in one database * statement. The caller supplies the hash of the graph-id-rebound document; * the large schema document remains server-side. */ instantiateGraphTemplate?: (this: void, params: Readonly<{ templateId: string; templateSchemaHash: string; graphId: string; schemaHash: string; }>) => Promise | Readonly<{ status: "refused"; }>>; /** * Run an administrative callback while holding the same per-graph lock as * schema commits. The callback receives the transaction-scoped backend, so * its reads, DML, and DDL are committed atomically before another schema * writer can proceed. * * This is intentionally absent from {@link TransactionBackend}: nesting a * schema-write lock from an already-open transaction can deadlock. It is an * optional backend capability so custom backends can decline operations * that require a schema fence rather than silently running them unsafely. */ schemaWriteTransaction?: (this: void, graphId: string, fn: (tx: TransactionBackend & Readonly<{ executeStatement: NonNullable; tableExists: (this: void, tableName: string) => Promise; executeSchemaDdl: (this: void, ddl: string) => Promise; deleteSchemaVectorSlotContribution: (this: void, slot: VectorSlot) => Promise; }>) => Promise) => Promise; upsertEmbedding?: (this: void, params: UpsertEmbeddingParams) => Promise; /** * Batched variant of `upsertEmbedding` for one vector slot. Optional — * callers fall back to per-row `upsertEmbedding` when unset. */ upsertEmbeddingBatch?: (this: void, params: UpsertEmbeddingBatchParams) => Promise; deleteEmbedding?: (this: void, params: DeleteEmbeddingParams) => Promise; deleteEmbeddingBatch?: (this: void, params: Omit & Readonly<{ nodeIds: readonly string[]; }>) => Promise; /** * KNN search over one `(nodeKind, fieldPath)` slot. Top-k is computed * over CURRENT nodes only — non-tombstoned AND inside their validity * window, matching a `current` read — so index drift (embedding rows * whose node was deleted or expired outside the store pipeline) can * neither surface nor crowd current rows out of the top-k. Custom * backends implementing this member must honor the same contract. Exact on pgvector >= 0.8 (HNSW via * `hnsw.iterative_scan = strict_order`; IVFFlat via * `ivfflat.iterative_scan = relaxed_order` plus a re-sort of the * bounded set; probe-bounded below 0.8) and sqlite-vec; * libSQL's DiskANN over-fetches 4x and post-filters (recall bounded by * that headroom). */ vectorSearch?: (this: void, params: VectorSearchParams) => Promise; createVectorIndex?: (this: void, params: CreateVectorIndexParams) => Promise; dropVectorIndex?: (this: void, params: DropVectorIndexParams) => Promise; /** * Single-statement hybrid search: both sources, RRF fusion, liveness * join, and node hydration composed into ONE statement — replacing the * facade's two search round trips plus id-hydration fetch. Optional; * the store falls back to the multi-statement path when unset (custom * backends, engines without window functions). Same liveness contract * as `vectorSearch` / `fulltextSearch`. */ hybridSearch?: (this: void, params: HybridSearchParams) => Promise; upsertFulltext?: (this: void, params: UpsertFulltextParams) => Promise; deleteFulltext?: (this: void, params: DeleteFulltextParams) => Promise; /** * Batched variant of `upsertFulltext`. Optional — callers fall back to * per-row `upsertFulltext` when unset. */ upsertFulltextBatch?: (this: void, params: UpsertFulltextBatchParams) => Promise; /** * Batched variant of `deleteFulltext`. Optional — callers fall back to * per-row `deleteFulltext` when unset. */ deleteFulltextBatch?: (this: void, params: DeleteFulltextBatchParams) => Promise; /** * Ranked fulltext search over one node kind. Like `vectorSearch`, top-k * is computed over CURRENT nodes only — non-tombstoned AND inside their * validity window (exact on both engines — plain WHERE, no top-k table * function involved). Custom backends implementing this member must * honor the same contract. */ fulltextSearch?: (this: void, params: FulltextSearchParams) => Promise; /** * Idempotently ensure ONLY the `typegraph_index_materializations` * table exists — separate from `bootstrapTables` so that * `materializeIndexes` doesn't pull in the full base-table DDL set * just to access the status table. * * Why focused: `bootstrapTables` issues 20+ `CREATE TABLE / CREATE * INDEX IF NOT EXISTS` statements covering every base table. Two * concurrent calls (e.g. two replicas of the same `schema_doc` both * starting up and calling `materializeIndexes`) race on * Postgres SHARE locks and DEADLOCK. Restricting the ensure-step to * the single status table eliminates the cross-table race entirely * — concurrent `CREATE TABLE IF NOT EXISTS` for one specific table * is well-behaved on Postgres. */ ensureIndexMaterializationsTable?: (this: void) => Promise; /** * Install the PostgreSQL `pg_trgm` extension under a database-global * concurrency fence. Relational trigram index materialization calls this * before emitting its index DDL; non-PostgreSQL backends omit it. * * @deprecated The `pg_trgm`-only spelling of {@link GraphBackend.ensureExtension}, * which says the same thing for every extension the library installs. It is * retained — and still consulted, after `ensureExtension` — so a backend * written against 0.47 keeps its fence; the bundled PostgreSQL backend * implements it by delegating. Implement `ensureExtension` in new backends. */ ensureTrigramExtension?: (this: void) => Promise; /** * Idempotently ensure ONLY the `typegraph_revision_origins` table exists. * * Revision-tracked stores use this per-graph, durable random origin together * with the recorded clock to prevent two independent stores with coincident * timestamps from sharing a merge base anchor. It is focused rather than * using `bootstrapTables` so existing deployments can adopt revision anchors * without replaying all base-table DDL during a merge read. */ ensureRevisionOriginsTable?: (this: void) => Promise; /** * Idempotently add the durable edge-match identity columns, pair constraint, * and unique index to the configured edge relation. * * Every edge write names these nullable columns, even when its graph does * not declare `matchIdentity`. Privileged schema preparation therefore calls * this focused hook on every open so pre-provisioned base tables adopt newer * physical storage without replaying the complete base-table DDL set. * Ordinary runtime store construction never performs DDL. * * @internal */ ensureEdgeMatchIdentityStorage?: (this: void) => Promise; /** * Idempotently ensure ONLY the three Operational Identity relations exist — * the current-assertions table, the recorded-time assertions table, and the * derived closure and separation tables — with their indexes and CHECK * constraints (CREATE TABLE / CREATE INDEX IF NOT EXISTS). * * First enablement of identity on an existing populated deployment attaches * via `createStore` / `createSqliteBackend` / `createPostgresBackend`, none * of which run DDL, so the enablement preflight would otherwise * SELECT/DELETE/INSERT tables that do not exist yet. The store calls this * before the enablement locks and closure rebuild. Focused rather than * `bootstrapTables` for the same concurrency rationale as * {@link ensureRevisionOriginsTable}. Stores call it before opening the * schema-commit transaction so DDL does not re-enter its per-graph lock. * Returns the logical names that were absent before this call. Callers set * `provisionMissing` only for safe first enablement; on an already-enabled * graph, missing ledger tables are deliberately left absent so a failed open * cannot make its next retry silently accept empty replacement storage. * * @internal */ ensureIdentityTables?: (this: void, tableNames: IdentityTableNames, options: Readonly<{ provisionMissing: boolean; }>) => Promise; /** * The idempotent CREATE statements for exactly the identity relations — * the same DDL {@link ensureIdentityTables} issues, handed back as data * instead of executed. * * Pure and synchronous: it executes nothing and probes no catalog, so it is * safe to call from inside an already-open transaction, where invoking a * top-level backend method would re-enter the backend's serialized statement * queue and deadlock. That is the whole point. Provisioning a missing DERIVED * relation (closure, separation) on an already-enabled graph has to CREATE it * and FILL it in one transaction, because a created-but-empty derived * relation is readable and answers every question with "nothing" — for the * separation relation, "nothing" means "not separated", which is exactly the * answer that lets a contradictory merge commit. * * A backend that omits this cannot offer that atomicity. Callers do not fall * back: when a fill is owed, the upgrade is REFUSED with the typed * `IDENTITY_UPGRADE_REQUIRES_ATOMIC_DDL` error naming this port — creating * and filling back-to-back would publish the readable-empty state the * paragraph above forbids. * * @internal */ identityTableDdl?: (this: void, tableNames: IdentityTableNames) => readonly string[]; /** * The provisioning DDL for the three recorded relations under the given * physical names, keyed by logical relation. Used only by the offline legacy * preview-schema migration, which builds temp tables, copies, and swaps — * so it is called TWICE per migration, once per name set, and the migration * (not the backend) composes the temp `createTable` with the final `indexes`. * * A backend that omits this cannot be migrated FROM the timestamp-only * preview schema — a schema only the bundled Drizzle backends ever created. * `migrateLegacyRecordedTime` REFUSES with `UnsupportedBackendCapabilityError` * naming this port rather than emitting DDL it cannot author. * * @internal */ recordedTableDdl?: (this: void, tableNames: RecordedTableNames) => Readonly>; /** * Look up a recorded materialization for a declared index by its * physical SQL index name. Returns `undefined` if no row exists. */ getIndexMaterialization?: (this: void, indexName: string) => Promise; /** * Bulk variant of `getIndexMaterialization`: load every recorded * materialization whose `indexName` (status key) is in `statusKeys`, * in a single round-trip. Returned rows are unordered — callers index * by `indexName`. Optional; consumers fall back to per-key * `getIndexMaterialization` when unset. */ getIndexMaterializations?: (this: void, statusKeys: readonly string[]) => Promise; /** * Upsert a materialization attempt — success or failure. Failure rows * preserve any prior `materializedAt` so the historical successful * timestamp survives across error windows. */ recordIndexMaterialization?: (this: void, params: RecordIndexMaterializationParams) => Promise; /** * Atomically claims the build of one index across every materializer * (process- and pool-safe: the status row's own atomicity is the mutex). * Returns true when this caller now holds the claim. Backends whose * index builds cannot deadlock across callers (SQLite: engine-level * write serialization, no CONCURRENTLY) omit it; `materializeIndexes` * then builds without a claim, exactly as before. */ claimIndexMaterialization?: (this: void, params: ClaimIndexMaterializationParams) => Promise; /** Releases a claim taken by `claimIndexMaterialization` (token-guarded). */ releaseIndexMaterializationClaim?: (this: void, params: ReleaseIndexMaterializationClaimParams) => Promise; /** * Physical-schema introspection: table/index presence, PostgreSQL's * invalid-index leftover state, and normalized column types. Present on * both bundled Drizzle backends; a custom backend that omits it loses the * store paths that consult it directly: index materialization * (`store.materializeIndexes()` refuses only once its empty-candidate * short circuit and the status-table ensure step have already run; * `store.materializeSystemIndexes()`, which has no candidate short * circuit, refuses only once that same status-table ensure step has * run), the recorded-time schema check, and the recorded-time * migration's column read. */ catalog?: BackendCatalogProbes | undefined; /** * The engine's whole-database revision and the per-graph change delta * since an earlier one. Present only when a backend's engine declares it * (`EngineProvisioning.lineage`) — absent by default on a custom backend * that supplies none, and absent on both bundled Drizzle profiles * regardless of `history`. A history-enabled store never populates this * member itself: it always resolves its lineage from its own recorded * relations instead (`resolveLineage` in * `store/recorded-capture/lineage.ts`), never from this member. Every * consumer falls back to a full comparison when this is absent — see * `requireLineage` in `backend/capabilities/lineage.ts`. */ lineage?: LineageMembers | undefined; /** * The engine's own recorded (system-time) read source and revision clock. * Present only when a backend's engine declares it * (`EngineProvisioning.recordedTime`) — absent by default on a custom * backend that supplies none, and absent on both bundled Drizzle profiles, * which always allocate TypeGraph's own recorded clock and relations * instead. Declaring this member is what makes a backend engine-native for * recorded time — see `resolveRecordedTimeOwnership` in * `backend/capabilities/recorded-time-ownership.ts`, the one reader of * that distinction. A backend that supplies `recordedTime` must also * supply `lineage`: engine-native history keeps no recorded relations of * its own to derive a change delta from, so `createSqlBackend` refuses a * profile that declares one without the other. */ recordedTime?: EngineRecordedTimeMembers | undefined; /** * Idempotently ensure ONLY the * `typegraph_contribution_materializations` table exists. Same * focused-bootstrap rationale as `ensureIndexMaterializationsTable`: * a single `CREATE TABLE IF NOT EXISTS` is concurrency-safe under * replica startup, where the full `bootstrapTables` set risks a * Postgres SHARE-lock deadlock. */ ensureContributionMaterializationsTable?: (this: void) => Promise; /** * Look up the durable materialization marker for one strategy-owned * contribution identity. Returns `undefined` when no row exists * ("never initialized"). */ getContributionMaterialization?: (this: void, identity: ContributionMaterializationIdentity) => Promise; /** * Upsert a contribution-materialization attempt — success or failure. * Failure rows preserve any prior `materializedAt` via COALESCE so a * later failed re-attempt doesn't erase the historical success. */ recordContributionMaterialization?: (this: void, params: RecordContributionMaterializationParams) => Promise; /** * Resolve (once per backend instance, cached) and assert the durable * materialization markers for every `runtimeEnsure` contribution this * backend's strategy declares, for `graphId`. Throws * `StoreNotInitializedError` when a marker is missing, stale * (signature drift), or recorded a failed last attempt. * * This is the single read-side gate the fulltext hot-path wrappers * and `store.transaction()` consult. It performs ZERO DDL and ZERO * marker writes — initialization is the exclusive job of the async * boot path (`createStoreWithSchema` → `ensureRuntimeContributions`). */ assertRuntimeContributionsInitialized?: (this: void, graphId: string) => Promise; /** * Bootstraps the per-deployment `typegraph_kind_removals` table so * `store.evolve()` can check pending removals and the removal verbs can * persist and materialize removal status. Mirrors the focused-bootstrap * rationale documented on `ensureIndexMaterializationsTable` — the full * `bootstrapTables` touches every base table and risks Postgres SHARE-lock * deadlock under concurrent replica startup. */ ensureKindRemovalsTable?: (this: void) => Promise; /** * List graph-extension kind removals whose data-cleanup pass has not yet * succeeded for this `graphId`. Returns rows with * `removedAt: undefined`. Order is unspecified; callers materialize * one-at-a-time and don't depend on it. */ getPendingKindRemovals?: (this: void, graphId: string) => Promise; /** * List ALL kind-removal rows for a `graphId` — pending and completed. * Used by `materializeRemovals()` reconciliation to detect rows that * are missing entirely (the `removeKinds()` crash window) versus * already completed. Without this distinction the reconciler would * have to upsert every expected historical removal on every call, * churning `last_attempted_at` on rows that long since succeeded. * Order is unspecified. */ getAllKindRemovals?: (this: void, graphId: string) => Promise; /** * Upsert a kind-removal status row. `removedAt: undefined` records * the pending state at schema-commit time; `removedAt: ` * marks the data cleanup successful. The COALESCE rule on `removedAt` * mirrors `recordIndexMaterialization` so a later failure doesn't * clobber the historical successful timestamp from another replica. */ recordKindRemoval?: (this: void, params: RecordKindRemovalParams) => Promise; /** * Bootstraps the per-deployment `typegraph_reconciliation_markers` * table so `materializeRemovals()` can persist reconciliation * progress. Same focused-bootstrap rationale as the other status * tables — full `bootstrapTables` risks Postgres SHARE-lock * deadlock under concurrent replica startup. */ ensureReconciliationMarkersTable?: (this: void) => Promise; /** * Materializes every contribution flagged `runtimeEnsure` — the * strategy-owned runtime tables (fulltext today) that drizzle-kit- * managed setups don't create. Called once after a successful schema * load. Deliberately scoped: base/drizzle-visible tables are * `runtimeEnsure: false`, so this does not regress startup into * broad DDL/probing across every table. * * The canonical durable-marker writer (#135): for each runtime * contribution it short-circuits when the marker already records a * matching signature, otherwise runs the idempotent `createDdl` and * records the marker (success or failure) keyed by `graphId`. */ ensureRuntimeContributions?: (this: void, graphId: string) => Promise; /** * Privileged materializer for one embedding `(kind, field)` slot's * `ownedTables` contribution(s): creates the per-field vector table and * records its durable marker, idempotently. The vector counterpart of * `ensureRuntimeContributions` — vectors are per-`(kind, field)` and * graph-derived, so the store enumerates slots (via * `resolveEmbeddingFields`) and uses the batch counterpart at boot under * the privileged role. Pass `{ force: true }` to overwrite the marker * at the current signature, bypassing the drift-guard — the sanctioned * path `store.reembedVectorField()` uses after recreating storage at a * new dimension. Pass `{ onDrift: "skip" }` to leave an * already-provisioned slot whose shape has since changed untouched * (warn + no-op) instead of refusing — the boot/evolve path, so a * declared dimension change never blocks startup before the operator * can run `reembedVectorField`. Present only on backends wired with a * vector strategy. */ ensureVectorSlotContribution?: (this: void, slot: VectorSlot, options?: Readonly<{ force?: boolean; onDrift?: "throw" | "skip"; }>) => Promise; /** * Batch form of `ensureVectorSlotContribution`, used by privileged boot to * resolve every slot's durable markers with one graph-scoped query. The * singular method remains available for re-embedding and compatibility. */ ensureVectorSlotContributions?: (this: void, slots: readonly VectorSlot[], options?: Readonly<{ force?: boolean; onDrift?: "throw" | "skip"; }>) => Promise; /** * SELECT-only gate for one embedding `(kind, field)` slot: asserts the * durable marker(s) for the slot's `ownedTables` contribution(s), * cached per backend instance. Throws `StoreNotInitializedError` when * the slot is missing, stale (signature drift), or recorded a failed * last attempt. The vector counterpart of * `assertRuntimeContributionsInitialized`, consulted by the verified * runtime attach. Performs ZERO DDL and ZERO writes. Present only on * backends wired with a vector strategy. */ assertVectorSlotInitialized?: (this: void, slot: VectorSlot) => Promise; /** * Batch form of `assertVectorSlotInitialized`, used by verified attach to * resolve every slot's durable markers with one graph-scoped query. */ assertVectorSlotsInitialized?: (this: void, slots: readonly VectorSlot[]) => Promise; /** * Forget one embedding `(kind, field)` slot's durable contribution * marker(s). Called after the slot's per-field table is dropped * (vector-field reclaim) so a later `ensureVectorSlotContribution` * re-creates the table instead of trusting an orphaned "initialized" * marker. Does NOT drop the table itself (the caller already did). * Present only on backends wired with a vector strategy. */ deleteVectorSlotContribution?: (this: void, slot: VectorSlot) => Promise; /** * Diagnostic: compare each contribution currently expected for `graphId` * against its durable marker and the physical catalog. * * Covers the strategy-owned `runtimeEnsure` contributions (fulltext) * plus the `ownedTables` contribution(s) of each supplied vector slot. * Returns one {@link ContributionDiagnostic} per detected unusable * contribution. A recorded failed materialization is unusable even when * marker and catalog agree that no table was produced. A contribution with * neither a marker nor a table is treated as never attempted and omitted; * marker rows outside the current declaration set are not audited. An empty * array therefore is not proof that storage was initialized. * * Deliberately NOT part of the open path. `ensureRuntimeContributions` * and `assertRuntimeContributionsInitialized` short-circuit on a * per-instance signature cache and then on the marker row alone, which * is the right default for a hot path but leaves a database whose * contribution tables were dropped out of band opening clean and * failing at the first dependent read or write. This method is the explicit, * operator-invoked catalog probe that fills that gap: it issues one uncached * existence query per distinct physical table and performs ZERO DDL * and ZERO writes, so it is safe under a least-privilege runtime role. * * Present only on backends that can probe their own catalog. */ verifyContributions?: (this: void, graphId: string, vectorSlots: readonly VectorSlot[]) => Promise; /** * Re-audit current contribution declarations and non-destructively repair * `missing-marker` and `failed-materialization` findings. The backend must * resolve every target itself; it must not accept caller-provided physical * identities or DDL. `stale` and `orphaned-marker` findings are returned as * `requires-rebuild` because repairing either can require destructive data * reconstruction. * * Present only on backends that can both probe their catalog and run the * strategy-owned contribution DDL. */ repairContributions?: (this: void, graphId: string, vectorSlots: readonly VectorSlot[]) => Promise; /** * Read-only readiness probe: the same marker-versus-catalog audit * `verifyContributions` performs, projected onto one entry per search * projection. Shares that method's detection logic exactly — a second * implementation would be free to disagree with the one the hot-path * gate actually consults, and a health check that disagrees with the * gate is worse than none. * * Writes nothing: no DDL, no marker writes, no effect on the * per-instance caches the hot path relies on. Safe on a read path, on * a replica, and under a least-privilege role. * * Present only on backends that can probe their catalog; a backend * that provisions contributions without this method declares the gap * as `capabilities.contributions.probe === false`. */ probeContributions?: (this: void, graphId: string, vectorSlots: readonly VectorSlot[]) => Promise; /** * Destructively rebuild one search projection for one graph: clear that * graph's rows from the projection's storage, ensure the storage matches * the current `createDdl`, reconstruct the graph's content, and stamp the * durable marker at the current signature. * * This is the repair `repairContributions` deliberately refuses to * perform, and it must never be reachable from it. A `stale` * contribution's table exists at the *old* physical shape, so the * idempotent `CREATE ... IF NOT EXISTS` in the ordinary ensure path * no-ops; re-stamping the marker there would leave it blessing a table * whose shape is wrong, which is exactly what the drift guard exists * to prevent. Only a drop makes the recreate meaningful, and destroying * content is a decision the caller states rather than one a flag named * `force` implies. * * The fulltext projection's storage is one physical table shared by every * graph in the database, while this call is fenced per graph. * Implementations must therefore scope the teardown to `graphId` and may * drop the storage only when doing so destroys no other graph's rows; * when the recorded shape is stale and the drop that would repair it is * not available, they refuse (`shared-storage-in-use`) rather than * re-stamp a shape nothing verified. * * Runs the whole sequence inside one transaction under the same * per-graph fence as a schema commit, so an interrupted rebuild leaves * the contribution exactly as it was rather than attested-but-empty. * * `repopulate` receives that transaction and reconstructs content from * rows TypeGraph already stores. The inversion exists because deciding * *what* content a node contributes needs the schema registry, which * is a Store-layer concern the backend must not reach into. * * @param scope which projection to rebuild. Implementations must * refuse `"vector"`: embeddings live only in the storage this would * drop, so there is nothing to reconstruct them from. */ rebuildContribution?: (this: void, graphId: string, scope: ContributionRebuildScope, repopulate: (target: TransactionBackend) => Promise) => Promise; /** * Bootstraps the fulltext storage table the active `FulltextStrategy` * owns. Same focused-bootstrap rationale as the other `ensure*Table` * methods: idempotent and concurrency-safe under replica startup. * * Superseded by `ensureRuntimeContributions()` (#129); retained as * a thin back-compat wrapper for backends/callers predating #129. Not * machine-`@deprecated` because the manager still calls it as the * pre-#129 fallback. #135 removed the remaining hot-path callers and * routed this through the durable-marker writer. */ ensureFulltextTable?: (this: void, graphId: string) => Promise; /** * Read the high-water mark schema version for which * `materializeRemovals` reconciliation has already verified history * for `graphId`. Returns `undefined` when no marker has been * recorded yet. Used to skip already-checked transitions in the * recovery walk. */ getReconciliationMarker?: (this: void, graphId: string) => Promise; /** * Persist the reconciliation high-water mark for `graphId`. Called * after `materializeRemovals` completes a clean walk; subsequent * calls walk only versions newer than this marker. Idempotent * upsert by `graphId`. */ setReconciliationMarker?: (this: void, graphId: string, version: number) => Promise; /** * Adopts deployment-wide base relations to the physical schema required by * this library version and stamps the singleton installation marker. * Privileged schema-management entry points call this; ordinary raw Store * construction never does. * * @internal */ adoptBaseSchema?: (this: void) => Promise; /** * SELECT-only sibling of {@link GraphBackend.adoptBaseSchema}. Throws a * typed base-schema migration error when privileged adoption has not run. * Least-privilege verified/template entry points call this before DML. * * @internal */ assertBaseSchemaCurrent?: (this: void) => Promise; /** * Hard-deletes all data for a graph (nodes, edges, uniques, embeddings, schema versions). * Intended for import-replacement workflows. No hooks, no per-row logic. */ clearGraph: (this: void, graphId: string) => Promise; /** * Creates the base TypeGraph tables if they don't already exist. * * Called automatically by `createStoreWithSchema()` when a fresh database * is detected. Users who manage DDL themselves via `createStore()` never * hit this path. */ bootstrapTables?: (this: void) => Promise; /** * Refreshes the backend's query-planner statistics. * * Call this once after a large initial import or bulk backfill. Without * up-to-date statistics, the planner can pick suboptimal execution plans * — on PostgreSQL this is the difference between a 0.5ms and a 5ms * forward traversal; on SQLite it's the difference between 0.9ms and * 23ms fulltext search. Autovacuum / background statistics collection * will catch up eventually, but calling this explicitly after a bulk * load gives you correct latencies immediately. * * Implementations: * - SQLite runs `ANALYZE`, which populates `sqlite_stat1` * - PostgreSQL runs `ANALYZE` on the TypeGraph-managed tables * * Safe to call at any time; costs a few tens of milliseconds on the * sizes this library is designed for. */ refreshStatistics: (this: void) => Promise; /** * Runs an intentionally trusted, all-or-nothing initial import. * * Optional because only backends that can pin one transaction and use a * native high-throughput write path expose it. Implementations must reject * a database whose TypeGraph node or edge tables already contain rows. They * may temporarily remove rebuildable secondary indexes, but must restore * them before committing. Any callback, insert, or rebuild failure rolls the * entire operation back. * * This is a top-level-only lifecycle operation and is deliberately absent * from {@link TransactionBackend}. When `schemaWrite` is supplied, the * import acquires and validates that Store version's managed-write fence * before checking emptiness or writing. Omitting it retains the raw, * unversioned import behavior. */ trustedImport?: (this: void, fn: (session: TrustedImportSession) => Promise, options?: Readonly<{ schemaWrite?: Readonly<{ graphId: string; expectedVersion: number; }>; }>) => Promise; execute: (this: void, query: CompiledRowsSql) => Promise; /** * Execute a non-row-returning SQL statement bound to this backend or * transaction. Optional because custom backends may only expose the public * row-returning query path; features that need statement execution must * capability-check and fail loudly. */ executeStatement?: (this: void, query: CompiledStatementSql) => Promise; /** * Execute an internally compiled statement against connection-local * temporary state. History wrappers preserve this path because it cannot * mutate graph or history tables; callers cannot construct its branded * input through the public API. */ executeTemporaryStatement?: (this: void, query: CompiledTemporaryStatementSql) => Promise; /** Execute pre-compiled SQL text with bound parameters. Available on sync SQLite and pg backends. */ executeRaw?: (this: void, sqlText: string, params: readonly unknown[]) => Promise; /** Compile a TypeGraph `SqlFragment` to `{ sql, params }` without executing. */ compileSql?: (this: void, query: SqlFragment) => Readonly<{ sql: string; params: readonly unknown[]; }>; /** * Execute a DDL statement that returns no rows (CREATE INDEX, * CREATE TABLE, ALTER TABLE, etc.). Separate from `executeRaw` * because some drivers (better-sqlite3) require `.run()` for DDL * and `.all()` for queries — the ambiguity can't be resolved by * inspecting the SQL string portably. * * Postgres path can use this for `CREATE INDEX CONCURRENTLY`, which * cannot run inside a transaction. Implementations must execute the * statement outside `transaction(...)`. */ executeDdl?: (this: void, ddl: string) => Promise; /** * Installs a database-global extension idempotently, tolerating the * concurrent-install race. * * `CREATE EXTENSION IF NOT EXISTS` is not a concurrency primitive on * PostgreSQL: the existence check cannot see another session's uncommitted * `pg_extension` row, so the loser of a race waits for the winner and is * then handed SQLSTATE 23505 instead of the harmless "already exists" * notice (#446). A backend implementing this member owns that retry, and * may additionally serialize same-extension installers behind a fence of its * own (the bundled PostgreSQL backend takes a transaction advisory lock * keyed on the extension, #475). * * This is the one member the library asks for an extension install; it * supersedes {@link GraphBackend.ensureTrigramExtension}, which said the same * thing for one extension. * * Like `executeDdl`, implementations MUST run the statement at the * top-level backend, never inside `transaction(...)`: the 23505 aborts the * enclosing transaction, so a retry issued inside one would only collect * `25P02` on the way out. That is why this member is absent from * {@link TransactionBackend}. */ ensureExtension?: (this: void, name: DatabaseExtensionName) => Promise; /** Runs TypeGraph operations inside a backend-owned transaction. */ transaction: (this: void, fn: (tx: TransactionBackend) => Promise, options?: TransactionOptions) => Promise; close: (this: void) => Promise; }> & DurableEdgeBatchMembers; /** Policy for provisioning physical schema inside a caller-owned transaction. */ type SchemaProvisioning = "dml-only" | "transactional"; /** * Adapter-native transaction interoperability layered on top of the portable * TypeGraph backend. Only adapter entrypoints expose this capability. */ type AdapterBackend = GraphBackend & Readonly<{ /** Whether caller-owned schema transactions may provision physical storage. */ schemaProvisioning: SchemaProvisioning; /** * Runs TypeGraph operations and exposes the exact adapter-native handle * bound to the same transaction. */ transactionWithNative: (this: void, fn: (tx: TransactionBackend, nativeTransaction: TNativeTransaction) => Promise, options?: TransactionOptions) => Promise; /** Adopts a caller-owned, already-open adapter transaction. */ adoptTransaction: (this: void, externalTransaction: TNativeTransaction) => TransactionBackend; /** * Adopt a caller-owned native transaction for a schema change. The adapter * proves the transaction is active and acquires its schema-write fence on * that literal session before returning the privileged CAS target. It * never opens, commits, retries, or rolls back the caller's transaction. * Optional because drivers without active-session evidence must refuse. */ adoptSchemaWriteTransaction?: (this: void, externalTransaction: TNativeTransaction, graphId: string, options: Readonly<{ waitBudgetMs: number; }>) => Promise; }>; /** * The schema-write facet available only after adoption earned its fence. * The caller owns the native transaction and remains responsible for its commit * or rollback. */ type AdoptedSchemaWriteTransaction = Readonly<{ backend: SchemaWriteTransactionBackend & Readonly<{ commitSchemaVersion: GraphBackend["commitSchemaVersion"]; ensureVectorSlotContributions?: (this: void, slots: readonly VectorSlot[], options?: Readonly<{ onDrift?: "throw" | "skip"; }>) => Promise; }>; activeSchema: SchemaVersionRow | undefined; }>; type BackendIdentity = Pick; type NodeEntityReadBackend = Pick; type NodeEntityWriteBackend = Pick; type EdgeEntityReadBackend = Pick; type EdgeEntityWriteBackend = Pick; type GraphEntityReadBackend = NodeEntityReadBackend & EdgeEntityReadBackend; type GraphEntityWriteBackend = NodeEntityWriteBackend & EdgeEntityWriteBackend; type UniqueConstraintBackend = Pick; type SchemaReadBackend = Pick; type SchemaCommitBackend = Pick; type SchemaWriteFenceBackend = Pick; type VectorOperationBackend = Pick; type FulltextOperationBackend = Pick; type IndexMaterializationBackend = Pick; /** The optional catalog-introspection surface. See {@link BackendCatalogProbes}. */ type CatalogBackend = Pick; /** The optional engine-lineage surface. See {@link LineageMembers}. */ type LineageBackend = Pick; /** The optional engine-native recorded-time surface. See {@link EngineRecordedTimeMembers}. */ type RecordedTimeBackend = Pick; type ContributionMaterializationBackend = Pick; type RemovalMaterializationBackend = Pick; type GraphLifecycleBackend = Pick; type QueryExecutionBackend = Pick; type RawQueryExecutionBackend = Pick; type SqlCompilationBackend = Pick; type RawStatementExecutionBackend = Pick; type BackendMaintenance = Pick; type BackendTransactions = Pick; type AdapterBackendTransactions = Pick, "transactionWithNative" | "adoptTransaction">; type BackendLifecycle = Pick; /** * Read-oriented transaction projection exposed by portable transaction * contexts. It preserves snapshot-aware graph reads and TypeGraph-compiled * row queries while excluding graph writes, arbitrary raw SQL, DDL, * transaction adoption, and lifecycle control. */ type TransactionReadBackend = Readonly; /** * Transaction backend — a backend scoped to a transaction. * * This is an explicit facet composition, not `Omit`. * New top-level-only backend methods therefore do not silently appear on * transaction-scoped backends. `commitSchemaVersion`, `setActiveVersion`, * `refreshStatistics`, `transaction`, `adoptTransaction`, and `close` stay * deliberately absent: their guarantees depend on the top-level backend or * lifecycle owner rather than an already-open transaction. */ type TransactionBackend = Readonly & SchemaReadBackend & SchemaWriteFenceBackend & VectorOperationBackend & FulltextOperationBackend & IndexMaterializationBackend & CatalogBackend & LineageBackend & RecordedTimeBackend & ContributionMaterializationBackend & RemovalMaterializationBackend & GraphLifecycleBackend & QueryExecutionBackend & RawQueryExecutionBackend & RawStatementExecutionBackend>; /** * Transaction backend exposed only while the backend's schema-write lock is * held. Unlike the ordinary top-level `executeDdl` port, this DDL primitive is * explicitly transaction-scoped and must use the callback's transaction. * * @internal */ type SchemaWriteTransactionBackend = TransactionBackend & Readonly<{ executeStatement: NonNullable; tableExists: (this: void, tableName: string) => Promise; executeSchemaDdl: (this: void, ddl: string) => Promise; deleteSchemaVectorSlotContribution: (this: void, slot: VectorSlot) => Promise; }>; /** * The target a schema-commit preflight runs against: a transaction backend * that MAY also carry the schema-write DDL primitive. * * Optional, deliberately. Both bundled backends pass the same * {@link SchemaWriteTransactionBackend} their `schemaWriteTransaction` exposes, * so DDL is available there; a custom backend may implement * {@link GraphBackend.commitSchemaVersionWithPreflight} over a transaction that * cannot run DDL. Declaring the port as present-or-absent lets a preflight that * needs it refuse with a typed error naming the capability, instead of calling * `undefined` — or, far worse, skipping the DDL and continuing. * * @internal */ type SchemaCommitPreflightBackend = TransactionBackend & Readonly<{ executeSchemaDdl?: (this: void, ddl: string) => Promise; }>; /** * Parameters for inserting a unique constraint entry. */ type InsertUniqueParams = Readonly<{ graphId: string; nodeKind: string; constraintName: string; key: string; nodeId: string; concreteKind: string; }>; /** * Parameters for releasing a unique constraint entry. * * Every release is scoped to the claim's OWNER — the pair * `(concreteKind, nodeId)`, because a node id is unique only within its kind. * The two shapes differ only in whether the claim AXIS participates: * * - **Lifecycle release** (`nodeKind` absent) — "give up every claim this node * holds for this constraint and key, whatever axis it sits on". Soft delete, * an update's key-change release and the resurrect diff use it, so a claim * written under an older axis is still released by newer code. * - **Compensating release** (`nodeKind` present) — "undo exactly the row I * just claimed, at the axis I claimed it on". A failed write's rollback uses * it, so it touches neither a row that predates the write nor one another * node holds. */ type DeleteUniqueParams = Readonly<{ graphId: string; /** * The claim axis (`uniques.node_kind`) the release is restricted to. Absent * releases the owner's claims at every axis — see the two shapes above. */ nodeKind?: string; constraintName: string; key: string; /** The claim owner's concrete kind (`uniques.concrete_kind`). */ concreteKind: string; /** The claim owner's node id (`uniques.node_id`). */ nodeId: string; }>; /** Parameters for permanently clearing uniqueness sidecars by node identity. */ type HardDeleteUniquesByNodeIdsParams = Readonly<{ graphId: string; concreteKind: string; nodeIds: readonly string[]; }>; /** Parameters for permanently clearing every uniqueness claim a kind owns. */ type HardDeleteUniquesByConcreteKindParams = Readonly<{ graphId: string; concreteKind: string; }>; /** * One edge cardinality claim, named by the components its axis, its key and its * holder-liveness predicate are all built from. * * The components are passed RAW rather than pre-rendered: `EDGE_CARDINALITY_SPECS` * (`store/claims/edge-claims.ts`) is the one table that decides which endpoints * the key covers and what a holder must still be, and both the TypeScript probe * and the SQL builder read it. A caller that rendered the axis and key itself * would be a second spelling of that decision. */ type ClaimEdgeCardinalityParams = Readonly<{ graphId: string; /** The declared cardinality; `many` declares nothing and never claims. */ cardinality: Exclude; edgeKind: string; /** The edge that will hold the axis if this claim lands. */ edgeId: string; fromKind: string; fromId: string; toKind: string; toId: string; }>; /** * What a claim statement decided. * * `refused` carries the incumbent so the caller can say which edge holds the * axis; the typed refusal itself is the store's, built from the same * `checkCardinality` / `checkUniqueEdge` owners the probe uses, so a caller * cannot tell which layer refused. */ type EdgeClaimOutcome = Readonly<{ status: "claimed"; }> | Readonly<{ status: "refused"; holderEdgeId: string; }>; /** Parameters for the housekeeping purge of claims held by named edges. */ type PurgeEdgeClaimsParams = Readonly<{ graphId: string; edgeIds: readonly string[]; }>; /** One edge kind's declared cardinality, as the fence audit reads it. */ type EdgeCardinalityDeclaration = Readonly<{ edgeKind: string; /** `many` declares nothing, so it is unrepresentable here. */ cardinality: Exclude; }>; /** * What a constraint-fence audit asks the database about: the declarations the * graph carries, one list per family. * * The declarations are passed rather than discovered because the backend holds * no schema — and because the `uniqueConstraintNames` restriction is * load-bearing rather than an optimization: the `uniques` relation also holds * disjointness claims, whose `node_kind` is a pair label rather than a kind and * for which no uniqueness axis can be computed. Those rows are covered by * `disjointKindPairs`, from the nodes relation. */ type ReadConstraintFenceViolationsParams = Readonly<{ graphId: string; /** Every unique constraint name the graph declares, in any scope. */ uniqueConstraintNames: readonly string[]; /** Every declared `disjointWith` pair, each read as one intersection. */ disjointKindPairs: readonly (readonly [string, string])[]; /** Every edge kind declaring a cardinality other than `many`. */ edgeCardinalities: readonly EdgeCardinalityDeclaration[]; }>; /** * One live `uniques` row that shares its `(constraint_name, key)` with at least * one other live row — a candidate, not a verdict: two rows contend only when * their `node_kind`s fold onto ONE claim axis, which the caller decides. */ type ContendedUniqueRow = Readonly<{ nodeKind: string; constraintName: string; key: string; concreteKind: string; nodeId: string; }>; /** * One live edge that shares its declared cardinality's population with at least * one other live edge. The endpoints are returned whole so the caller can name * the claim key through the one builder that renders it. */ type ContendedEdgeRow = Readonly<{ edgeKind: string; cardinality: Exclude; edgeId: string; fromKind: string; fromId: string; toKind: string; toId: string; }>; /** One node id live under BOTH kinds of a declared disjoint pair. */ type DisjointOverlapRow = Readonly<{ kinds: readonly [string, string]; nodeId: string; }>; /** Everything one constraint-fence audit read, per family. */ type ConstraintFenceViolationRows = Readonly<{ contendedUniqueRows: readonly ContendedUniqueRow[]; contendedEdgeRows: readonly ContendedEdgeRow[]; disjointOverlaps: readonly DisjointOverlapRow[]; }>; /** * Parameters for checking a unique constraint. */ type CheckUniqueParams = Readonly<{ graphId: string; nodeKind: string; constraintName: string; key: string; /** If true, also returns soft-deleted entries. Used by get-or-create operations. */ includeDeleted?: boolean; }>; /** * Parameters for batch-checking unique constraints. */ type CheckUniqueBatchParams = Readonly<{ graphId: string; nodeKind: string; constraintName: string; keys: readonly string[]; /** If true, also returns soft-deleted entries. Used by get-or-create operations. */ includeDeleted?: boolean; }>; /** * Parameters for inserting a schema version. * * Used internally by backend implementations. Public callers go through * `commitSchemaVersion`, which handles the insert + activate atomically * with CAS guarantees. */ type InsertSchemaParams = Readonly<{ graphId: string; version: number; schemaHash: string; schemaDoc: SerializedSchema; isActive: boolean; }>; /** * The caller's claim about the currently-active schema version, used as * the optimistic compare-and-swap guard for `commitSchemaVersion`. * * - `{ kind: "initial" }` — caller is committing the first-ever version * for this graph and asserts no active version exists yet. * - `{ kind: "active", version: N }` — caller observed version N as * active and is committing version N+1 against that baseline. The * commit fails with `StaleVersionError` if some other writer has * advanced or rolled back the pointer in the meantime. * * Tagged-union form (rather than a magic `version: 0` sentinel) because * the two cases have materially different semantics: the initial path * skips the "deactivate prior" UPDATE, and the no-active-row state is * a *valid* expected state, not an out-of-band signal. */ type CommitSchemaVersionExpected = Readonly<{ kind: "initial"; }> | Readonly<{ kind: "active"; version: number; }>; /** * Parameters for `commitSchemaVersion`. */ type CommitSchemaVersionParams = Readonly<{ graphId: string; /** CAS guard — see `CommitSchemaVersionExpected`. */ expected: CommitSchemaVersionExpected; /** The new version to insert and activate. */ version: number; schemaHash: string; schemaDoc: SerializedSchema; }>; type SchemaKindEmptinessProbe = Readonly<{ entity: "node" | "edge"; kind: string; /** Which rows make this schema transition unsafe. Must be chosen explicitly. */ rows: "nonDeleted" | "all"; }>; type PopulatedSchemaKind = SchemaKindEmptinessProbe & Readonly<{ count: number; }>; type CommitSchemaVersionIfKindsEmptyResult = Readonly<{ status: "committed"; row: SchemaVersionRow; }> | Readonly<{ status: "populated"; kinds: readonly PopulatedSchemaKind[]; }>; type LockSchemaVersionForWriteParams = Readonly<{ graphId: string; expectedVersion: number; }>; /** Parameters shared by the ordinary and fused schema-write fences. */ type SchemaWriteFenceParams = LockSchemaVersionForWriteParams; /** * Optional schema-managed write identity for a trusted import. Supplying it * acquires and validates the Store version's transaction-scoped schema fence; * omitting it leaves the import outside the versioned guarantee. */ type TrustedImportOptions = Readonly<{ schemaWrite?: Readonly<{ graphId: string; expectedVersion: number; }>; }>; /** * Parameters for `setActiveVersion`. * * Flips the active pointer from `expected` to `version` for an existing * row. CAS prevents overwriting a concurrent rollback or commit. */ type SetActiveVersionParams = Readonly<{ graphId: string; /** CAS guard. Same semantics as `commitSchemaVersion.expected`. */ expected: CommitSchemaVersionExpected; /** The version to mark active. Must already exist. */ version: number; }>; /** * Parameters for counting edges from a source node. */ type CountEdgesFromParams = Readonly<{ graphId: string; edgeKind: string; fromKind: string; fromId: string; /** If true, only count edges where valid_to IS NULL */ activeOnly?: boolean; }>; /** * Parameters for checking if an edge exists between two nodes. */ type EdgeExistsBetweenParams = Readonly<{ graphId: string; edgeKind: string; fromKind: string; fromId: string; toKind: string; toId: string; }>; /** * Parameters for finding edges connected to a node. */ type FindEdgesConnectedToParams = Readonly<{ graphId: string; nodeKind: string; nodeId: string; }>; /** * Parameters for finding nodes by kind. */ type FindNodesByKindParams = Readonly<{ graphId: string; kind: string; /** Max rows to return. */ limit?: number; /** Offset. Present for backward compat; rebuild uses `after` instead. */ offset?: number; /** If true, exclude deleted nodes. Default true. */ excludeDeleted?: boolean; /** Temporal mode for filtering by validity period. */ temporalMode?: TemporalMode; /** Timestamp for "current" and "asOf" temporal modes. */ asOf?: string; /** * Stable ordering for keyset pagination. Default: "created_at" (existing * behavior). Rebuild should use "id" for iteration that is stable under * concurrent writes and shared timestamps. */ orderBy?: "id" | "created_at"; /** * Keyset cursor. Returns rows strictly greater (by `orderBy`) than this * value. When `orderBy: "id"`, compared lexicographically. Mutually * exclusive with `offset` — callers pick one. */ after?: string; }>; /** * Parameters for counting nodes by kind. */ type CountNodesByKindParams = Readonly<{ graphId: string; kind: string; /** If true, exclude deleted nodes. Default true. */ excludeDeleted?: boolean; /** Temporal mode for filtering by validity period. */ temporalMode?: TemporalMode; /** Timestamp for "current" and "asOf" temporal modes. */ asOf?: string; }>; /** * Parameters for finding edges by kind. */ type FindEdgesByKindParams = Readonly<{ graphId: string; kind: string; fromKind?: string; fromId?: string; toKind?: string; toId?: string; limit?: number; offset?: number; /** If true, exclude deleted edges. Default true. */ excludeDeleted?: boolean; /** Temporal mode for filtering by validity period. */ temporalMode?: TemporalMode; /** Timestamp for "current" and "asOf" temporal modes. */ asOf?: string; /** * Stable ordering for keyset pagination. Default: "created_at" (existing * behavior). Use "id" for iteration that is stable under shared `created_at` * timestamps — the offset path orders by the NON-unique `created_at`, so a * full enumeration must page by the unique `id` to avoid skipping a row at a * page boundary. Mirrors {@link FindNodesByKindParams.orderBy}. */ orderBy?: "id" | "created_at"; /** * Keyset cursor. Returns rows strictly greater (by `orderBy`) than this value. * When `orderBy: "id"`, compared lexicographically. Mutually exclusive with * `offset` — callers pick one. Mirrors {@link FindNodesByKindParams.after}. */ after?: string; }>; /** * The endpoint side a {@link FindEdgesByEndpointSetParams} read fans out over. */ type EdgeEndpointSide = "from" | "to"; /** * Parameters for reading the edges of a SET of endpoints in one statement per * bind-budget chunk — the widened form of {@link FindEdgesByKindParams}'s * scalar `fromId` / `toId`. * * Deliberately a SEPARATE parameter type on a separate operation rather than * optional fields on `FindEdgesByKindParams`. A backend that did not implement * set membership would still type-check while ignoring the id list, and would * then return every edge of the kind — which the caller would rebucket into a * correct-looking answer at unbounded cost. Splitting the operation makes that * failure unreachable: support is detected by the method's presence, before any * read is issued. * * The shape also makes the previously-validated illegal states * unrepresentable. One `side` instead of two independent id lists means both * endpoints can never fan out at once; no scalar `fromId` / `toId` field means * a scalar and a set can never disagree; no `limit` / `offset` / `after` means * a global slice can never be requested across a read the backend splits into * bind-budget chunks. */ type FindEdgesByEndpointSetParams = Readonly<{ graphId: string; kind: string; /** Which endpoint column the id set constrains. */ side: EdgeEndpointSide; /** * Kind of the fanned-out endpoint. Required: it is the index prefix that * makes the set a seek rather than a scan, so a set is always scoped to one * endpoint kind and a heterogeneous page costs one read per distinct kind. */ endpointKind: string; /** * Endpoint NODE ids to match — deliberately not named `ids`, which in the * backend params vocabulary means edge ids (see * {@link DeleteEdgesBatchParams}); the write-surface assertion in * `recorded-capture/write-surface.ts` classifies params structurally and a * `{ graphId, ids }` read would be misread as a write. * * The backend deduplicates before splitting the list across bind-budget * chunks — a repeated id spanning two chunks would return its edges twice. * An empty list reads nothing and yields no rows. */ endpointIds: readonly string[]; /** * Maximum rows per distinct endpoint id, applied inside the statement (via * `ROW_NUMBER()` over the read's own ordering) rather than by the caller. * Only meaningful on a backend whose `capabilities.windowFunctions` is true; * callers must still cap client-side, so a backend that ignores this returns * a superset rather than a wrong answer. Unlike a global `limit`, a * per-endpoint cap composes with chunking: each endpoint's rows fall * entirely within one chunk. */ limitPerEndpoint?: number; /** If true, exclude deleted edges. Default true. */ excludeDeleted?: boolean; /** Temporal mode for filtering by validity period. */ temporalMode?: TemporalMode; /** Timestamp for "current" and "asOf" temporal modes. */ asOf?: string; }>; /** * Parameters for reading several edge kinds from several endpoint kinds. * * The endpoint pairs are joined as a relation, so round trips are independent * of the number of licensed edge-kind/endpoint-kind combinations. Large source * sets may still be split to respect the backend's bind-parameter budget. */ type FindEdgesByHeterogeneousEndpointSetParams = Readonly<{ graphId: string; side: EdgeEndpointSide; endpoints: readonly Readonly<{ kind: string; id: string; /** * Optional endpoint on the opposite side of the edge. When present, the * set read performs an exact directed-pair seek instead of materializing * every edge incident to `kind` / `id` and leaving the caller to filter. * This is particularly important for hub nodes. */ opposite?: Readonly<{ kind: string; id: string; }>; }>[]; edgeKinds: readonly string[]; limitPerEndpoint?: number; /** If true, exclude deleted edges. Default true. */ excludeDeleted?: boolean; /** Temporal mode for filtering by validity period. */ temporalMode?: TemporalMode; /** Timestamp for "current" and "asOf" temporal modes. */ asOf?: string; }>; /** * Parameters for counting edges by kind. */ type CountEdgesByKindParams = Readonly<{ graphId: string; kind: string; fromKind?: string; fromId?: string; toKind?: string; toId?: string; /** If true, exclude deleted edges. Default true. */ excludeDeleted?: boolean; /** Temporal mode for filtering by validity period. */ temporalMode?: TemporalMode; /** Timestamp for "current" and "asOf" temporal modes. */ asOf?: string; }>; /** * Conservative per-statement bound-parameter floor for SQLite. The * compiled-in `SQLITE_MAX_VARIABLE_NUMBER` defaulted to 999 before SQLite * 3.32.0; drivers whose real ceiling cannot be probed (async/remote * connections) keep this floor. Single source of truth for * {@link SQLITE_CAPABILITIES} and the SQLite backend's batch math fallback. */ declare const SQLITE_MAX_BIND_PARAMETERS = 999; /** * `SQLITE_MAX_VARIABLE_NUMBER` default since SQLite 3.32.0 (better-sqlite3 * also compiles it in explicitly). Used when a synchronous driver's probe * confirms a modern build. */ declare const MODERN_SQLITE_MAX_BIND_PARAMETERS = 32766; /** * Cloudflare D1's documented per-statement bound-parameter ceiling. Far * below the classic SQLite floor, so D1 detection must cap the budget or * batched writes fail at runtime. */ declare const D1_MAX_BIND_PARAMETERS = 100; /** * Cloudflare SQLite-backed Durable Objects' documented per-statement * bound-parameter ceiling. The resolved `do-sqlite` execution profile must * advertise this limit so every capability-driven batch path stays below it. */ declare const DURABLE_OBJECT_MAX_BIND_PARAMETERS = 100; /** * Safe bound-parameter ceiling shared by every bundled PostgreSQL driver. * The protocol count can represent 65535, but postgres.js rejects a statement * with 65534 bound values, so backend batch math uses the lower portable limit. */ declare const POSTGRES_MAX_BIND_PARAMETERS = 65533; /** * Default capabilities for SQLite. */ declare const SQLITE_CAPABILITIES: BackendCapabilities; /** * Default capabilities for PostgreSQL. */ declare const POSTGRES_CAPABILITIES: BackendCapabilities; export { type VectorIndexImplementation as $, type AdapterBackend as A, type BundledBackendCapabilityOverrides as B, type ContributionDiagnostic as C, type IndexWhereExpression as D, type EdgeConvergenceMatch as E, type FenceSql as F, type GraphIdentityConfig as G, type IndexWhereFieldBuilder as H, type IndexDeclaration as I, type JsonPointer as J, type KindEntity as K, type IndexWhereInput as L, NODE_SYSTEM_COLUMN_NAMES as M, type NodeInsertClaim as N, type NodeIndexConfig as O, type NodeIndexKey as P, type QueryAst as Q, type RecordedInstant as R, type SchemaProvisioning as S, type TransactionBackend as T, type NodeIndexKeyInput as U, type VectorStrategy as V, type WriteFencePlan as W, type NodeIndexWhereBuilder as X, type NodeSystemColumnName as Y, type SystemColumnName as Z, type VectorIndexDeclaration as _, type ContributionDiagnosticState as a, type CatalogColumn as a$, type VectorIndexMetric as a0, type VectorIndexParams as a1, type BackendCapabilities as a2, type VectorSlot as a3, type AnyEdgeType as a4, type NodeType as a5, type SerializedNodeDef as a6, type SerializedEdgeDef as a7, type SerializedMetaEdge as a8, type SerializedOntologyRelation as a9, type GraphCommandPort as aA, type GraphCommandCoordination as aB, type GraphCommandExecutionContext as aC, type NodeCreateCommand as aD, type NodeCreateCommandResult as aE, type EdgeCreateCommand as aF, type EdgeCreateCommandResult as aG, type EdgeConvergeCreateCommand as aH, type EdgeConvergeCreateCommandResult as aI, type GraphCommand as aJ, type GraphCommandResult as aK, type GraphCommandSession as aL, type GraphCommandIsolation as aM, TypeGraphError as aN, type FindEdgesByEndpointSetParams as aO, ALL_FULLTEXT_MODES as aP, ALL_META_EDGE_NAMES as aQ, type AdapterBackendTransactions as aR, BASE_CONTRIBUTION_OWNER as aS, type BackendCatalogProbes as aT, type BackendExecutionCapabilities as aU, type BackendIdentity as aV, type BackendLifecycle as aW, type BackendMaintenance as aX, type BackendTransactions as aY, type Cardinality as aZ, type CatalogBackend as a_, type SerializedClosures as aa, type SerializedSchema as ab, type SchemaHash as ac, type NullCheckOp as ad, type ValidationIssue as ae, ValidationError as af, type ChangeSeverity as ag, type ChangeType as ah, type DeprecatedKindsChange as ai, type EdgeChange as aj, type ExtensionChange as ak, type GraphAnnotationsChange as al, type IdentityChange as am, type IndexChange as an, type JsonSchema as ao, type NodeChange as ap, type OntologyChange as aq, type SchemaChangeClassification as ar, type SchemaDiff as as, type SchemaIdentity as at, type SerializedOntology as au, type SerializedUniqueConstraint as av, classifySchemaChanges as aw, computeSchemaDiff as ax, getMigrationActions as ay, isBackwardsCompatible as az, type ContributionRepairEntry as b, type EntityKey as b$, type CatalogIndexBehavior as b0, type CheckUniqueBatchParams as b1, type CheckUniqueParams as b2, type ClaimIndexMaterializationParams as b3, type Collation as b4, type CommitSchemaVersionExpected as b5, type CommitSchemaVersionIfKindsEmptyResult as b6, type CommitSchemaVersionParams as b7, type CompareAndSetNodeParams as b8, type CompiledRowsSql as b9, type DatabaseExtensionName as bA, type DeleteBehavior as bB, type DeleteEdgeParams as bC, type DeleteEdgesBatchParams as bD, type DeleteEmbeddingParams as bE, type DeleteFulltextBatchParams as bF, type DeleteFulltextParams as bG, type DeleteNodeParams as bH, type DeleteUniqueParams as bI, type DialectAdapter as bJ, type DialectCapabilities as bK, type DialectRecursiveQueryStrategy as bL, type DialectStandardQueryStrategy as bM, type DialectSubgraphMembershipStrategy as bN, type DialectVectorPredicateStrategy as bO, type DisjointOverlapRow as bP, type DropVectorIndexParams as bQ, type DurableEdgeBatchMembers as bR, type EdgeCardinalityDeclaration as bS, type EdgeClaimOutcome as bT, type EdgeEntityReadBackend as bU, type EdgeEntityWriteBackend as bV, type EdgeExistsBetweenParams as bW, type EndpointExistence as bX, type EngineRecordedRevision as bY, type EngineRecordedTimeMembers as bZ, type EngineRevision as b_, type CompiledSelectSql as ba, type CompiledStatementSql as bb, type CompiledTemporaryStatementSql as bc, type ConstraintFenceViolationRows as bd, type ContendedEdgeRow as be, type ContendedUniqueRow as bf, type ContributionCapabilities as bg, type ContributionMaterializationBackend as bh, type ContributionMaterializationIdentity as bi, type ContributionMaterializationRow as bj, type ContributionProbeContribution as bk, type ContributionProbeEntry as bl, type ContributionProbeResult as bm, type ContributionProbeState as bn, type ContributionRebuildResult as bo, type ContributionRebuildScope as bp, type ContributionRepopulationStats as bq, type ContributionScope as br, type CountEdgesByKindParams as bs, type CountEdgesFromParams as bt, type CountNodesByKindParams as bu, type CreateVectorIndexParams as bv, D1_MAX_BIND_PARAMETERS as bw, DATABASE_EXTENSION_NAMES as bx, DEPLOYMENT_CONTRIBUTION_GRAPH_ID as by, DURABLE_OBJECT_MAX_BIND_PARAMETERS as bz, type ContributionRepairResult as c, type JsonScalar as c$, type ExtensionArrayItemType as c0, type ExtensionArrayProperty as c1, type ExtensionBooleanProperty as c2, type ExtensionEdgeDef as c3, type ExtensionEdgeIndex as c4, type ExtensionEmbeddingModifier as c5, type ExtensionEnumProperty as c6, type ExtensionIndex as c7, type ExtensionIndexWhere as c8, type ExtensionNodeDef as c9, type GraphEntityWriteBackend as cA, type GraphExtension as cB, type GraphExtensionVersion as cC, type GraphLifecycleBackend as cD, type GraphReadBackend as cE, type HardDeleteEdgeParams as cF, type HardDeleteNodeParams as cG, type HardDeleteUniquesByConcreteKindParams as cH, type HardDeleteUniquesByNodeIdsParams as cI, type HeterogeneousNodeUpsertEntry as cJ, type HeterogeneousNodeUpsertParams as cK, type HybridSearchParams as cL, type HybridSearchRow as cM, type IdentityTableNames as cN, type InListParameterOptions as cO, type IndexDeclarationBase as cP, type IndexEntity as cQ, type IndexMaterializationBackend as cR, type IndexMaterializationRow as cS, type IndexState as cT, type IndexWhereLiteral as cU, type IndexWhereOp as cV, type IndexWhereOperand as cW, type InferenceType as cX, type InsertSchemaParams as cY, type InsertUniqueParams as cZ, type IntentSql as c_, type ExtensionNodeIndex as ca, type ExtensionNumberProperty as cb, type ExtensionObjectFieldProperty as cc, type ExtensionObjectProperty as cd, type ExtensionOntologyRelation as ce, type ExtensionPropertyModifiers as cf, type ExtensionPropertyType as cg, type ExtensionSearchableModifier as ch, type ExtensionStringProperty as ci, type ExtensionUniqueConstraint as cj, type ExtensionUniqueWhere as ck, FULLTEXT_CONTRIBUTION_NAME as cl, type FilteredApproximateSearch as cm, type FilteredApproximateSearchMode as cn, type FindEdgesByHeterogeneousEndpointSetParams as co, type FindEdgesByKindParams as cp, type FindEdgesConnectedToParams as cq, type FindNodesByKindParams as cr, type FulltextBatchRow as cs, type FulltextCapabilities as ct, type FulltextOperationBackend as cu, type FulltextQueryMode as cv, type FulltextSearchParams as cw, type FulltextSearchResult as cx, type GraphAnalyticsCapabilities as cy, type GraphEntityReadBackend as cz, type FulltextStrategy as d, type SqlTextChunk as d$, type JsonValue as d0, type KindAnnotations as d1, type KindRemovalRow as d2, type LineageBackend as d3, type LineageDelta as d4, type LineageMembers as d5, type LineageSession as d6, type LiveNodeRow as d7, type LockSchemaVersionForWriteParams as d8, MODERN_SQLITE_MAX_BIND_PARAMETERS as d9, type RecordedTimeSession as dA, type RecursiveTraversalCapability as dB, type RecursiveTraversalVerdict as dC, type RelationalIndexMethod as dD, type ReleaseIndexMaterializationClaimParams as dE, type RemovalMaterializationBackend as dF, type RenderedSql as dG, type ResolvedNodeUpdateBatchEntry as dH, type ResolvedNodeUpdateBatchParams as dI, type ResolvedSqlTableNames as dJ, type RowProps as dK, SQLITE_CAPABILITIES as dL, SQLITE_MAX_BIND_PARAMETERS as dM, type SchemaCommitBackend as dN, type SchemaCommitPreflightBackend as dO, type SchemaKindEmptinessProbe as dP, type SchemaReadBackend as dQ, type SchemaWriteFenceBackend as dR, type SetActiveVersionParams as dS, type SqlChunk as dT, type SqlCompilationBackend as dU, type SqlFragment as dV, type SqlIdentifierChunk as dW, type SqlIntent as dX, type SqlParameterChunk as dY, type SqlPlaceholderChunk as dZ, type SqlTag as d_, type ManagedEdgeCreatePlan as da, type ManagedNodeCreateMode as db, type ManagedNodeCreatePlan as dc, type MetaEdgeName as dd, type NodeEntityReadBackend as de, type NodeEntityWriteBackend as df, type NodeInsertClaimVerdict as dg, type NodePropertyExpectation as dh, type NormalizedColumnKind as di, POSTGRES_CAPABILITIES as dj, POSTGRES_MAX_BIND_PARAMETERS as dk, Placeholder as dl, type PopulatedSchemaKind as dm, type PurgeEdgeClaimsParams as dn, type QueryExecutionBackend as dp, type RawQueryExecutionBackend as dq, type RawStatementExecutionBackend as dr, type ReadConstraintFenceViolationsParams as ds, type RecordContributionMaterializationParams as dt, type RecordIndexMaterializationParams as du, type RecordKindRemovalParams as dv, type RecordedRelationDdl as dw, type RecordedSourceTable as dx, type RecordedTableNames as dy, type RecordedTimeBackend as dz, type GraphDef as e, type TraversalDirection as e$, type StrategyTableContribution as e0, type TableContribution as e1, type TableState as e2, type TemporalMode as e3, type TombstonedNodeRow as e4, type TransactionOptions as e5, type TransactionReadBackend as e6, type TrustedImportOptions as e7, type TrustedImportSession as e8, type UniqueConstraintBackend as e9, buildFulltextCapabilities as eA, buildVectorCapabilities as eB, fts5Strategy as eC, isLiveNodeRow as eD, isSqlFragment as eE, isSqlPlaceholder as eF, isTombstonedNodeRow as eG, normalizeGraphAnalyticsCapabilities as eH, quoteIdentifier as eI, recursiveTraversalUnsupportedError as eJ, renderPostgres as eK, renderSql as eL, renderSqlInline as eM, renderSqlite as eN, requireWriteFence as eO, resolveRecursiveTraversal as eP, resolveWriteFencePlan as eQ, rowPropsToJsonText as eR, rowPropsToObject as eS, shortHash as eT, sql as eU, supportsInteractiveTransactions as eV, supportsRootAtomicBatch as eW, tsvectorStrategy as eX, vectorMinScoreCondition as eY, vectorPhysicalName as eZ, vectorScoreExpression as e_, type UniqueRow as ea, type UniquenessScope as eb, type UpdateEdgeParams as ec, type UpdateNodeParams as ed, type UpdateNodeSetParams as ee, type UpdateNodeSetResult as ef, type UpsertEmbeddingBatchParams as eg, type UpsertEmbeddingBatchRow as eh, type UpsertEmbeddingParams as ei, type UpsertFulltextBatchParams as ej, type UpsertFulltextParams as ek, VECTOR_CONTRIBUTION_PREFIX as el, type ValueType as em, type VectorCapabilities as en, type VectorIndexType as eo, type VectorMetric as ep, type VectorOperationBackend as eq, type VectorSearchFrontierTuning as er, type VectorSearchParams as es, type VectorSearchResult as et, type WriteFenceDeclaration as eu, assertFiniteEmbedding as ev, assertRecursiveTraversal as ew, assertVectorMinScore as ex, assertVectorSearchLimit as ey, assumeRecursiveTraversalSupported as ez, type SchemaVersionRow as f, type HybridFusionOptions as f$, type AggregateExpr as f0, type FieldRef as f1, type ComparisonOp as f2, type AggregateComparisonPredicate as f3, type OntologyRelation as f4, type AllEdgeTypes as f5, type AllNodeTypes as f6, BackendDisposedError as f7, BaseSchemaMigrationError as f8, type BaseSchemaMigrationErrorDetails as f9, type EdgeMatchIdentity as fA, EdgeMatchIdentityConflictError as fB, EdgeNotFoundError as fC, type EdgeNotFoundErrorDetails as fD, type EdgeProps as fE, type EdgeRegistration as fF, type EdgeTargetMap as fG, type EdgeTargets as fH, type EdgeType as fI, type EdgeTypeWithEndpoints as fJ, EmbeddingDimensionChangedError as fK, type EmbeddingDimensionChangedErrorDetails as fL, EndpointError as fM, type EndpointErrorDetails as fN, EndpointNotFoundError as fO, type EndpointNotFoundErrorDetails as fP, EndpointPairError as fQ, type EndpointPairErrorDetails as fR, type ErrorCategory as fS, ExportStreamCancelledError as fT, ExportStreamIdleTimeoutError as fU, type ExternalRecordedReadSource as fV, type GetEdgeType as fW, type GetNodeType as fX, GraphAlgorithmConvergenceError as fY, type GraphAnnotations as fZ, type GraphDefaults as f_, CURRENT_GRAPH_EXTENSION_VERSION as fa, CardinalityError as fb, type CardinalityErrorDetails as fc, type CollectOptions as fd, type CollectOrder as fe, type CollectRecordFields as ff, type CollectRecordOperand as fg, type CollectedRecord as fh, CompilerInvariantError as fi, ConfigurationError as fj, type ContributionRebuildRefusal as fk, ContributionRebuildUnsupportedError as fl, ContributionUnavailableError as fm, type ContributionUnavailableErrorDetails as fn, DEFAULT_SQL_SCHEMA as fo, type DatabaseExpression as fp, DatabaseOperationError as fq, type DatabaseOperationErrorDetails as fr, DisjointError as fs, type DisjointErrorDetails as ft, EDGE_IDENTITY_MISMATCH_CODE as fu, ENTITY_ALREADY_EXISTS_CODE as fv, EagerMaterializationError as fw, type EagerMaterializationErrorDetails as fx, type EdgeId as fy, type EdgeKinds as fz, type GraphBackend as g, type SchemaMismatchErrorDetails as g$, IMMUTABLE_VALIDITY_LOWER_BOUND_CODE as g0, INVERTED_VALIDITY_WINDOW_CODE as g1, IdentityContradictionError as g2, type IdentityContradictionErrorDetails as g3, IdentityEndpointValidityError as g4, type IdentityEndpointValidityErrorDetails as g5, IdentitySeparationViolationError as g6, type IdentitySeparationViolationErrorDetails as g7, IdentityValidityWindowError as g8, type IdentityValidityWindowErrorDetails as g9, type NodeIndexNotFoundErrorDetails as gA, type NodeKinds as gB, NodeNotFoundError as gC, type NodeNotFoundErrorDetails as gD, type NodeProps as gE, type NodeRegistration as gF, type OrderSpec as gG, type ParameterRef as gH, RECORDED_CAPTURE_GUARD_CODES as gI, type RecordedCaptureGuardCode as gJ, type RecordedCaptureGuardError as gK, type RecordedReadSource as gL, type RecordedRelationOptions as gM, type ResolveJsonPointer as gN, type ResolveJsonPointerSegments as gO, RestrictedDeleteError as gP, type RestrictedDeleteErrorDetails as gQ, RuntimeKindTokenError as gR, type RuntimeKindTokenFailure as gS, SchemaChangedError as gT, type SchemaChangedErrorDetails as gU, SchemaContentConflictError as gV, type SchemaContentConflictErrorDetails as gW, type SchemaFencePhase as gX, SchemaFenceTimeoutError as gY, type SchemaFenceTimeoutErrorDetails as gZ, SchemaMismatchError as g_, InvalidEdgeWeightError as ga, type InvalidEdgeWeightErrorDetails as gb, type InvalidEdgeWeightReason as gc, type JsonPointerFor as gd, type JsonPointerInput as ge, type JsonPointerSegment as gf, type JsonPointerSegments as gg, type JsonPointerSegmentsFor as gh, KindNotFoundError as gi, type KindNotFoundErrorDetails as gj, LEGACY_GRAPH_EXTENSION_VERSION as gk, MAX_JSON_POINTER_DEPTH as gl, MIGRATION_FAILURE_REASONS as gm, type MaterializeIndexesEntry as gn, type MaterializeIndexesOptions as go, type MaterializeIndexesResult as gp, type MaterializeSystemIndexesOptions as gq, type MetaEdge as gr, type MetaEdgeProperties as gs, MigrationError as gt, type MigrationErrorDetails as gu, type MigrationFailureReason as gv, NodeConstraintNotFoundError as gw, type NodeConstraintNotFoundErrorDetails as gx, type NodeId as gy, NodeIndexNotFoundError as gz, type AdoptedSchemaWriteTransaction as h, type SetOperationType as h$, type SortDirection as h0, SqlSchema as h1, StaleVersionError as h2, type StaleVersionErrorDetails as h3, StoreNotInitializedError as h4, type StoreNotInitializedErrorDetails as h5, type StoreNotInitializedReason as h6, TransactionClosedError as h7, TransactionConflictError as h8, type TransactionConflictErrorDetails as h9, isMetaEdge as hA, isNodeType as hB, isRecordedCaptureGuardError as hC, isSystemError as hD, isTypeGraphError as hE, isUserRecoverable as hF, joinJsonPointers as hG, jsonPointer as hH, normalizeJsonPointer as hI, parseJsonPointer as hJ, recordedInstantRevision as hK, recordedInstantWallTime as hL, recordedRelation as hM, type WriteFenceTarget as hN, type SchemaWriteTransactionBackend as hO, type PredicateExpression as hP, type VectorMetricType as hQ, type VectorSlotMap as hR, type RecordedReadBinding as hS, type Traversal as hT, type NodePredicate as hU, type ProjectedField as hV, type AggregateOrderSpec as hW, type GroupBySpec as hX, type RecursiveCyclePolicy as hY, type SelectiveField as hZ, type ComposableQuery as h_, type TraversalExpansion as ha, TrustedImportError as hb, type TrustedImportErrorReason as hc, type TypeGraphErrorOptions as hd, type UniqueConstraint as he, UniquenessError as hf, type UniquenessErrorDetails as hg, UnsupportedBackendCapabilityError as hh, UnsupportedPredicateError as hi, type ValidationErrorDetails as hj, VersionConflictError as hk, type VersionConflictErrorDetails as hl, asEdgeId as hm, asNodeId as hn, asRecordedInstant as ho, compareRecordedInstants as hp, createSqlSchema as hq, defineGraph as hr, expr as hs, getEdgeKinds as ht, getErrorSuggestion as hu, getNodeKinds as hv, isConstraintError as hw, isEdgeType as hx, isEdgeTypeWithEndpoints as hy, isGraphDef as hz, type FenceStatements as i, type SetOperation as i0, type BackendValidityEndMutation as i1, type ReadCoordinate as i2, type ExtensionObjectSchema as i3, type ExtensionEdgeProperties as i4, type AnyEdgeRegistration as i5, type ExtensionObjectOutput as i6, type ExtensionPropertyOutput as i7, GRAPH_EXTENSION_TOP_LEVEL_KEYS as i8, type GraphExtensionTopLevelKey as i9, type InsertNodeParams as j, type NodeInsertProjection as k, type SchemaWriteFenceParams as l, type InsertEdgeParams as m, type EdgeRow as n, type ClaimEdgeCardinalityParams as o, type NodeRow as p, type SqlTableNames as q, type EdgeIndexDeclaration as r, type SqlDialect as s, type RelationalIndexDeclaration as t, type NodeIndexDeclaration as u, type EdgeIndexConfig as v, type EdgeIndexDirection as w, type EdgeIndexWhereBuilder as x, type IndexOrigin as y, type IndexScope as z };