@nicia-ai/typegraph 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3449,6 +3449,116 @@ function decodeByValueType(value, valueType) {
3449
3449
  }
3450
3450
  }
3451
3451
  }
3452
+ function buildReachableCte(options) {
3453
+ const trackPath = options.cyclePolicy === "prevent" || options.includePath;
3454
+ const edgeKindFilter = compileKindFilter(
3455
+ drizzleOrm.sql.raw("e.kind"),
3456
+ options.edgeKinds
3457
+ );
3458
+ const currentTimestamp = options.dialect.currentTimestamp();
3459
+ const nodeTemporalFilter = compileTemporalFilter({
3460
+ mode: options.temporalMode,
3461
+ asOf: options.asOf,
3462
+ tableAlias: "n",
3463
+ currentTimestamp
3464
+ });
3465
+ const edgeTemporalFilter = compileTemporalFilter({
3466
+ mode: options.temporalMode,
3467
+ asOf: options.asOf,
3468
+ tableAlias: "e",
3469
+ currentTimestamp
3470
+ });
3471
+ const initialPath = trackPath ? options.dialect.initializePath(drizzleOrm.sql.raw("n.id")) : void 0;
3472
+ const pathExtension = trackPath ? options.dialect.extendPath(drizzleOrm.sql.raw("r.path"), drizzleOrm.sql.raw("n.id")) : void 0;
3473
+ const cycleCheck = options.cyclePolicy === "prevent" ? options.dialect.cycleCheck(drizzleOrm.sql.raw("n.id"), drizzleOrm.sql.raw("r.path")) : void 0;
3474
+ const baseColumns = [drizzleOrm.sql`n.id`, drizzleOrm.sql`n.kind`, drizzleOrm.sql`0 AS depth`];
3475
+ if (initialPath !== void 0) {
3476
+ baseColumns.push(drizzleOrm.sql`${initialPath} AS path`);
3477
+ }
3478
+ const baseCase = drizzleOrm.sql`SELECT ${drizzleOrm.sql.join(baseColumns, drizzleOrm.sql`, `)} FROM ${options.schema.nodesTable} n WHERE n.graph_id = ${options.graphId} AND n.id = ${options.sourceId} AND ${nodeTemporalFilter}`;
3479
+ const recursiveColumns = [
3480
+ drizzleOrm.sql`n.id`,
3481
+ drizzleOrm.sql`n.kind`,
3482
+ drizzleOrm.sql`r.depth + 1 AS depth`
3483
+ ];
3484
+ if (pathExtension !== void 0) {
3485
+ recursiveColumns.push(drizzleOrm.sql`${pathExtension} AS path`);
3486
+ }
3487
+ const recursiveWhere = [
3488
+ drizzleOrm.sql`e.graph_id = ${options.graphId}`,
3489
+ edgeKindFilter,
3490
+ edgeTemporalFilter,
3491
+ nodeTemporalFilter,
3492
+ drizzleOrm.sql`r.depth < ${options.maxHops}`
3493
+ ];
3494
+ if (cycleCheck !== void 0) recursiveWhere.push(cycleCheck);
3495
+ const forceWorktableOuterJoinOrder = options.dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
3496
+ const recursiveCase = compileRecursiveBranch({
3497
+ recursiveColumns,
3498
+ whereClauses: recursiveWhere,
3499
+ direction: options.direction,
3500
+ forceWorktableOuterJoinOrder,
3501
+ schema: options.schema
3502
+ });
3503
+ return drizzleOrm.sql`WITH RECURSIVE reachable AS (${baseCase} UNION ALL ${recursiveCase})`;
3504
+ }
3505
+ function compileRecursiveBranch(options) {
3506
+ const selectClause = drizzleOrm.sql`SELECT ${drizzleOrm.sql.join([...options.recursiveColumns], drizzleOrm.sql`, `)}`;
3507
+ switch (options.direction) {
3508
+ case "out": {
3509
+ return buildDirectionalBranch({
3510
+ selectClause,
3511
+ whereClauses: options.whereClauses,
3512
+ joinField: "from_id",
3513
+ targetField: "to_id",
3514
+ targetKindField: "to_kind",
3515
+ forceWorktableOuterJoinOrder: options.forceWorktableOuterJoinOrder,
3516
+ schema: options.schema
3517
+ });
3518
+ }
3519
+ case "in": {
3520
+ return buildDirectionalBranch({
3521
+ selectClause,
3522
+ whereClauses: options.whereClauses,
3523
+ joinField: "to_id",
3524
+ targetField: "from_id",
3525
+ targetKindField: "from_kind",
3526
+ forceWorktableOuterJoinOrder: options.forceWorktableOuterJoinOrder,
3527
+ schema: options.schema
3528
+ });
3529
+ }
3530
+ case "both": {
3531
+ return buildBidirectionalBranch({
3532
+ selectClause,
3533
+ whereClauses: options.whereClauses,
3534
+ forceWorktableOuterJoinOrder: options.forceWorktableOuterJoinOrder,
3535
+ schema: options.schema
3536
+ });
3537
+ }
3538
+ }
3539
+ }
3540
+ function buildDirectionalBranch(options) {
3541
+ const nodeJoin = drizzleOrm.sql`JOIN ${options.schema.nodesTable} n ON n.graph_id = e.graph_id AND n.id = e.${drizzleOrm.sql.raw(options.targetField)} AND n.kind = e.${drizzleOrm.sql.raw(options.targetKindField)}`;
3542
+ if (options.forceWorktableOuterJoinOrder) {
3543
+ const allWhere = [
3544
+ ...options.whereClauses,
3545
+ drizzleOrm.sql`e.${drizzleOrm.sql.raw(options.joinField)} = r.id`
3546
+ ];
3547
+ return drizzleOrm.sql`${options.selectClause} FROM reachable r CROSS JOIN ${options.schema.edgesTable} e ${nodeJoin} WHERE ${drizzleOrm.sql.join(allWhere, drizzleOrm.sql` AND `)}`;
3548
+ }
3549
+ return drizzleOrm.sql`${options.selectClause} FROM reachable r JOIN ${options.schema.edgesTable} e ON e.${drizzleOrm.sql.raw(options.joinField)} = r.id ${nodeJoin} WHERE ${drizzleOrm.sql.join([...options.whereClauses], drizzleOrm.sql` AND `)}`;
3550
+ }
3551
+ function buildBidirectionalBranch(options) {
3552
+ const nodeJoin = drizzleOrm.sql`JOIN ${options.schema.nodesTable} n ON n.graph_id = e.graph_id AND ((e.to_id = r.id AND n.id = e.from_id AND n.kind = e.from_kind) OR (e.from_id = r.id AND n.id = e.to_id AND n.kind = e.to_kind))`;
3553
+ if (options.forceWorktableOuterJoinOrder) {
3554
+ const allWhere = [
3555
+ ...options.whereClauses,
3556
+ drizzleOrm.sql`(e.from_id = r.id OR e.to_id = r.id)`
3557
+ ];
3558
+ return drizzleOrm.sql`${options.selectClause} FROM reachable r CROSS JOIN ${options.schema.edgesTable} e ${nodeJoin} WHERE ${drizzleOrm.sql.join(allWhere, drizzleOrm.sql` AND `)}`;
3559
+ }
3560
+ return drizzleOrm.sql`${options.selectClause} FROM reachable r JOIN ${options.schema.edgesTable} e ON (e.from_id = r.id OR e.to_id = r.id) ${nodeJoin} WHERE ${drizzleOrm.sql.join([...options.whereClauses], drizzleOrm.sql` AND `)}`;
3561
+ }
3452
3562
 
3453
3563
  // src/store/row-mappers.ts
3454
3564
  function nullToUndefined2(value) {
@@ -3526,9 +3636,6 @@ function defineSubgraphProject(_graph) {
3526
3636
  }
3527
3637
  async function executeSubgraph(params) {
3528
3638
  const { options } = params;
3529
- if (options.edges.length === 0) {
3530
- return { nodes: [], edges: [] };
3531
- }
3532
3639
  const maxDepth = Math.min(
3533
3640
  options.maxDepth ?? DEFAULT_SUBGRAPH_MAX_DEPTH,
3534
3641
  MAX_EXPLICIT_RECURSIVE_DEPTH
@@ -3542,6 +3649,8 @@ async function executeSubgraph(params) {
3542
3649
  excludeRoot: options.excludeRoot ?? false,
3543
3650
  direction: options.direction ?? "out",
3544
3651
  cyclePolicy: options.cyclePolicy ?? "prevent",
3652
+ temporalMode: options.temporalMode ?? params.graph.defaults.temporalMode,
3653
+ asOf: options.asOf,
3545
3654
  dialect: params.dialect,
3546
3655
  schema: params.schema ?? DEFAULT_SQL_SCHEMA,
3547
3656
  backend: params.backend
@@ -3559,21 +3668,47 @@ async function executeSubgraph(params) {
3559
3668
  (kind, field2) => schemaIntrospector.getEdgeFieldTypeInfo(kind, field2),
3560
3669
  "edge"
3561
3670
  );
3562
- const reachableCte = buildReachableCte(ctx);
3671
+ const reachableCte = buildReachableCte({
3672
+ graphId: ctx.graphId,
3673
+ sourceId: ctx.rootId,
3674
+ edgeKinds: ctx.edgeKinds,
3675
+ maxHops: ctx.maxDepth,
3676
+ direction: ctx.direction,
3677
+ cyclePolicy: ctx.cyclePolicy,
3678
+ includePath: false,
3679
+ temporalMode: ctx.temporalMode,
3680
+ ...ctx.asOf !== void 0 && { asOf: ctx.asOf },
3681
+ dialect: ctx.dialect,
3682
+ schema: ctx.schema
3683
+ });
3563
3684
  const includedIdsCte = buildIncludedIdsCte(ctx);
3564
3685
  const [nodeRows, edgeRows] = await Promise.all([
3565
3686
  fetchSubgraphNodes(ctx, reachableCte, includedIdsCte, nodeProjectionPlan),
3566
3687
  fetchSubgraphEdges(ctx, reachableCte, includedIdsCte, edgeProjectionPlan)
3567
3688
  ]);
3568
- const nodes = nodeRows.map(
3569
- (row) => mapSubgraphNodeRow(row, nodeProjectionPlan)
3570
- );
3571
- const edges = edgeRows.map(
3572
- (row) => mapSubgraphEdgeRow(row, edgeProjectionPlan)
3573
- );
3689
+ const nodesMap = /* @__PURE__ */ new Map();
3690
+ for (const row of nodeRows) {
3691
+ const node = mapSubgraphNodeRow(row, nodeProjectionPlan);
3692
+ nodesMap.set(node.id, node);
3693
+ }
3694
+ const adjacency = /* @__PURE__ */ new Map();
3695
+ const reverseAdjacency = /* @__PURE__ */ new Map();
3696
+ for (const row of edgeRows) {
3697
+ const edge = mapSubgraphEdgeRow(row, edgeProjectionPlan);
3698
+ insertAdjacencyEntry(adjacency, edge.fromId, edge.kind, edge);
3699
+ insertAdjacencyEntry(
3700
+ reverseAdjacency,
3701
+ edge.toId,
3702
+ edge.kind,
3703
+ edge
3704
+ );
3705
+ }
3706
+ const root = nodesMap.get(ctx.rootId);
3574
3707
  return {
3575
- nodes,
3576
- edges
3708
+ root,
3709
+ nodes: nodesMap,
3710
+ adjacency,
3711
+ reverseAdjacency
3577
3712
  };
3578
3713
  }
3579
3714
  var introspectorCache = /* @__PURE__ */ new WeakMap();
@@ -3643,79 +3778,6 @@ function getIncludedNodeKinds(graph, includeKinds) {
3643
3778
  function dedupeStrings(values) {
3644
3779
  return [...new Set(values)];
3645
3780
  }
3646
- function buildReachableCte(ctx) {
3647
- const shouldTrackPath = ctx.cyclePolicy === "prevent";
3648
- const edgeKindFilter = compileKindFilter(drizzleOrm.sql.raw("e.kind"), ctx.edgeKinds);
3649
- const initialPath = shouldTrackPath ? ctx.dialect.initializePath(drizzleOrm.sql.raw("n.id")) : void 0;
3650
- const pathExtension = shouldTrackPath ? ctx.dialect.extendPath(drizzleOrm.sql.raw("r.path"), drizzleOrm.sql.raw("n.id")) : void 0;
3651
- const cycleCheck = shouldTrackPath ? ctx.dialect.cycleCheck(drizzleOrm.sql.raw("n.id"), drizzleOrm.sql.raw("r.path")) : void 0;
3652
- const baseColumns = [drizzleOrm.sql`n.id`, drizzleOrm.sql`n.kind`, drizzleOrm.sql`0 AS depth`];
3653
- if (initialPath !== void 0) {
3654
- baseColumns.push(drizzleOrm.sql`${initialPath} AS path`);
3655
- }
3656
- const baseCase = drizzleOrm.sql`SELECT ${drizzleOrm.sql.join(baseColumns, drizzleOrm.sql`, `)} FROM ${ctx.schema.nodesTable} n WHERE n.graph_id = ${ctx.graphId} AND n.id = ${ctx.rootId} AND n.deleted_at IS NULL`;
3657
- const recursiveColumns = [
3658
- drizzleOrm.sql`n.id`,
3659
- drizzleOrm.sql`n.kind`,
3660
- drizzleOrm.sql`r.depth + 1 AS depth`
3661
- ];
3662
- if (pathExtension !== void 0) {
3663
- recursiveColumns.push(drizzleOrm.sql`${pathExtension} AS path`);
3664
- }
3665
- const recursiveWhereClauses = [
3666
- drizzleOrm.sql`e.graph_id = ${ctx.graphId}`,
3667
- edgeKindFilter,
3668
- drizzleOrm.sql`e.deleted_at IS NULL`,
3669
- drizzleOrm.sql`n.deleted_at IS NULL`,
3670
- drizzleOrm.sql`r.depth < ${ctx.maxDepth}`
3671
- ];
3672
- if (cycleCheck !== void 0) {
3673
- recursiveWhereClauses.push(cycleCheck);
3674
- }
3675
- const forceWorktableOuterJoinOrder = ctx.dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
3676
- const recursiveCase = ctx.direction === "both" ? compileBidirectionalBranch({
3677
- recursiveColumns,
3678
- whereClauses: recursiveWhereClauses,
3679
- forceWorktableOuterJoinOrder,
3680
- schema: ctx.schema
3681
- }) : compileRecursiveBranch({
3682
- recursiveColumns,
3683
- whereClauses: recursiveWhereClauses,
3684
- joinField: "from_id",
3685
- targetField: "to_id",
3686
- targetKindField: "to_kind",
3687
- forceWorktableOuterJoinOrder,
3688
- schema: ctx.schema
3689
- });
3690
- return drizzleOrm.sql`WITH RECURSIVE reachable AS (${baseCase} UNION ALL ${recursiveCase})`;
3691
- }
3692
- function compileRecursiveBranch(params) {
3693
- const columns = [...params.recursiveColumns];
3694
- const selectClause = drizzleOrm.sql`SELECT ${drizzleOrm.sql.join(columns, drizzleOrm.sql`, `)}`;
3695
- const nodeJoin = drizzleOrm.sql`JOIN ${params.schema.nodesTable} n ON n.graph_id = e.graph_id AND n.id = e.${drizzleOrm.sql.raw(params.targetField)} AND n.kind = e.${drizzleOrm.sql.raw(params.targetKindField)}`;
3696
- if (params.forceWorktableOuterJoinOrder) {
3697
- const allWhere = [
3698
- ...params.whereClauses,
3699
- drizzleOrm.sql`e.${drizzleOrm.sql.raw(params.joinField)} = r.id`
3700
- ];
3701
- return drizzleOrm.sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${drizzleOrm.sql.join(allWhere, drizzleOrm.sql` AND `)}`;
3702
- }
3703
- const where = [...params.whereClauses];
3704
- return drizzleOrm.sql`${selectClause} FROM reachable r JOIN ${params.schema.edgesTable} e ON e.${drizzleOrm.sql.raw(params.joinField)} = r.id ${nodeJoin} WHERE ${drizzleOrm.sql.join(where, drizzleOrm.sql` AND `)}`;
3705
- }
3706
- function compileBidirectionalBranch(params) {
3707
- const columns = [...params.recursiveColumns];
3708
- const selectClause = drizzleOrm.sql`SELECT ${drizzleOrm.sql.join(columns, drizzleOrm.sql`, `)}`;
3709
- const nodeJoin = drizzleOrm.sql`JOIN ${params.schema.nodesTable} n ON n.graph_id = e.graph_id AND ((e.to_id = r.id AND n.id = e.from_id AND n.kind = e.from_kind) OR (e.from_id = r.id AND n.id = e.to_id AND n.kind = e.to_kind))`;
3710
- if (params.forceWorktableOuterJoinOrder) {
3711
- const allWhere = [
3712
- ...params.whereClauses,
3713
- drizzleOrm.sql`(e.from_id = r.id OR e.to_id = r.id)`
3714
- ];
3715
- return drizzleOrm.sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${drizzleOrm.sql.join(allWhere, drizzleOrm.sql` AND `)}`;
3716
- }
3717
- return drizzleOrm.sql`${selectClause} FROM reachable r JOIN ${params.schema.edgesTable} e ON (e.from_id = r.id OR e.to_id = r.id) ${nodeJoin} WHERE ${drizzleOrm.sql.join([...params.whereClauses], drizzleOrm.sql` AND `)}`;
3718
- }
3719
3781
  function buildIncludedIdsCte(ctx) {
3720
3782
  const filters = [];
3721
3783
  if (ctx.includeKinds !== void 0 && ctx.includeKinds.length > 0) {
@@ -3747,6 +3809,12 @@ async function fetchSubgraphNodes(ctx, reachableCte, includedIdsCte, projectionP
3747
3809
  }
3748
3810
  async function fetchSubgraphEdges(ctx, reachableCte, includedIdsCte, projectionPlan) {
3749
3811
  const edgeKindFilter = compileKindFilter(drizzleOrm.sql.raw("e.kind"), ctx.edgeKinds);
3812
+ const edgeTemporalFilter = compileTemporalFilter({
3813
+ mode: ctx.temporalMode,
3814
+ asOf: ctx.asOf,
3815
+ tableAlias: "e",
3816
+ currentTimestamp: ctx.dialect.currentTimestamp()
3817
+ });
3750
3818
  const columns = [
3751
3819
  drizzleOrm.sql`e.id`,
3752
3820
  drizzleOrm.sql`e.kind`,
@@ -3764,7 +3832,7 @@ async function fetchSubgraphEdges(ctx, reachableCte, includedIdsCte, projectionP
3764
3832
  ]),
3765
3833
  ...buildProjectedPropertyColumns("e", projectionPlan, ctx.dialect)
3766
3834
  ];
3767
- const query = drizzleOrm.sql`${reachableCte}${includedIdsCte} SELECT ${drizzleOrm.sql.join(columns, drizzleOrm.sql`, `)} FROM ${ctx.schema.edgesTable} e WHERE e.graph_id = ${ctx.graphId} AND ${edgeKindFilter} AND e.deleted_at IS NULL AND e.from_id IN (SELECT id FROM included_ids) AND e.to_id IN (SELECT id FROM included_ids)`;
3835
+ const query = drizzleOrm.sql`${reachableCte}${includedIdsCte} SELECT ${drizzleOrm.sql.join(columns, drizzleOrm.sql`, `)} FROM ${ctx.schema.edgesTable} e WHERE e.graph_id = ${ctx.graphId} AND ${edgeKindFilter} AND ${edgeTemporalFilter} AND e.from_id IN (SELECT id FROM included_ids) AND e.to_id IN (SELECT id FROM included_ids)`;
3768
3836
  return ctx.backend.execute(query);
3769
3837
  }
3770
3838
  function buildMetadataColumns(alias, plan, columns) {
@@ -3813,6 +3881,19 @@ function buildProjectedPropertyColumns(alias, plan, dialect) {
3813
3881
  }
3814
3882
  return columns;
3815
3883
  }
3884
+ function insertAdjacencyEntry(index, nodeId, edgeKind, edge) {
3885
+ let kindMap = index.get(nodeId);
3886
+ if (kindMap === void 0) {
3887
+ kindMap = /* @__PURE__ */ new Map();
3888
+ index.set(nodeId, kindMap);
3889
+ }
3890
+ const edges = kindMap.get(edgeKind);
3891
+ if (edges === void 0) {
3892
+ kindMap.set(edgeKind, [edge]);
3893
+ } else {
3894
+ edges.push(edge);
3895
+ }
3896
+ }
3816
3897
  function applyProjectedFields(target, row, kindPlan) {
3817
3898
  for (const fieldPlan of kindPlan.propertyFields) {
3818
3899
  target[fieldPlan.field] = decodeSelectedValue(
@@ -8896,6 +8977,246 @@ function extractEdgeTypes(graph) {
8896
8977
  return result;
8897
8978
  }
8898
8979
 
8980
+ // src/store/algorithms/context.ts
8981
+ var DEFAULT_ALGORITHM_MAX_HOPS = 10;
8982
+ var DEFAULT_NEIGHBOR_DEPTH = 1;
8983
+ function resolveTemporalOptions(ctx, options) {
8984
+ return {
8985
+ temporalMode: options.temporalMode ?? ctx.defaultTemporalMode,
8986
+ ...options.asOf !== void 0 && { asOf: options.asOf }
8987
+ };
8988
+ }
8989
+ function resolveTemporalFilter(ctx, options, tableAlias) {
8990
+ const resolved = resolveTemporalOptions(ctx, options);
8991
+ const filterOptions = {
8992
+ mode: resolved.temporalMode,
8993
+ asOf: resolved.asOf,
8994
+ tableAlias,
8995
+ currentTimestamp: ctx.dialect.currentTimestamp()
8996
+ };
8997
+ return compileTemporalFilter(filterOptions);
8998
+ }
8999
+ function resolveMaxHops(rawMaxHops, fallback, optionName) {
9000
+ const value = rawMaxHops ?? fallback;
9001
+ if (!Number.isFinite(value) || !Number.isInteger(value)) {
9002
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
9003
+ `Graph algorithm ${optionName} must be a finite integer, got ${String(value)}.`,
9004
+ { option: optionName, value }
9005
+ );
9006
+ }
9007
+ if (value < 1) {
9008
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
9009
+ `Graph algorithm ${optionName} must be at least 1, got ${value}.`,
9010
+ { option: optionName, value }
9011
+ );
9012
+ }
9013
+ if (value > MAX_EXPLICIT_RECURSIVE_DEPTH) {
9014
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
9015
+ `Graph algorithm ${optionName} (${value}) exceeds the maximum of ${MAX_EXPLICIT_RECURSIVE_DEPTH}.`,
9016
+ { option: optionName, value, limit: MAX_EXPLICIT_RECURSIVE_DEPTH }
9017
+ );
9018
+ }
9019
+ return value;
9020
+ }
9021
+ function assertEdgeKinds(edges) {
9022
+ if (edges.length === 0) {
9023
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
9024
+ `Graph algorithms require at least one edge kind in 'edges'.`,
9025
+ { edges }
9026
+ );
9027
+ }
9028
+ }
9029
+
9030
+ // src/store/algorithms/degree.ts
9031
+ async function executeDegree(ctx, nodeId, options = {}) {
9032
+ const direction = options.direction ?? "both";
9033
+ const whereClauses = [
9034
+ drizzleOrm.sql`graph_id = ${ctx.graphId}`,
9035
+ resolveTemporalFilter(ctx, options),
9036
+ compileDirectionFilter(direction, nodeId)
9037
+ ];
9038
+ if (options.edges !== void 0) {
9039
+ whereClauses.push(compileKindFilter(drizzleOrm.sql.raw("kind"), options.edges));
9040
+ }
9041
+ const countExpr = direction === "both" ? drizzleOrm.sql`COUNT(DISTINCT id)` : drizzleOrm.sql`COUNT(*)`;
9042
+ const query = drizzleOrm.sql`SELECT ${countExpr} AS count FROM ${ctx.schema.edgesTable} WHERE ${drizzleOrm.sql.join(whereClauses, drizzleOrm.sql` AND `)}`;
9043
+ const rows = await ctx.backend.execute(query);
9044
+ return Number(rows[0]?.count ?? 0);
9045
+ }
9046
+ function compileDirectionFilter(direction, nodeId) {
9047
+ switch (direction) {
9048
+ case "out": {
9049
+ return drizzleOrm.sql`from_id = ${nodeId}`;
9050
+ }
9051
+ case "in": {
9052
+ return drizzleOrm.sql`to_id = ${nodeId}`;
9053
+ }
9054
+ case "both": {
9055
+ return drizzleOrm.sql`(from_id = ${nodeId} OR to_id = ${nodeId})`;
9056
+ }
9057
+ }
9058
+ }
9059
+ async function executeReachable(ctx, sourceId, options) {
9060
+ assertEdgeKinds(options.edges);
9061
+ const maxHops = resolveMaxHops(
9062
+ options.maxHops,
9063
+ DEFAULT_ALGORITHM_MAX_HOPS,
9064
+ "maxHops"
9065
+ );
9066
+ const cte = buildReachableCte({
9067
+ graphId: ctx.graphId,
9068
+ sourceId,
9069
+ edgeKinds: options.edges,
9070
+ maxHops,
9071
+ direction: options.direction ?? "out",
9072
+ cyclePolicy: options.cyclePolicy ?? "prevent",
9073
+ includePath: false,
9074
+ ...resolveTemporalOptions(ctx, options),
9075
+ dialect: ctx.dialect,
9076
+ schema: ctx.schema
9077
+ });
9078
+ const sourceFilter = options.excludeSource === true ? drizzleOrm.sql` WHERE id != ${sourceId}` : drizzleOrm.sql``;
9079
+ const query = drizzleOrm.sql`${cte} SELECT id, kind, MIN(depth) AS depth FROM reachable${sourceFilter} GROUP BY id, kind ORDER BY depth ASC, id ASC`;
9080
+ const rows = await ctx.backend.execute(query);
9081
+ return rows.map((row) => ({
9082
+ id: row.id,
9083
+ kind: row.kind,
9084
+ depth: Number(row.depth)
9085
+ }));
9086
+ }
9087
+ async function executeCanReach(ctx, sourceId, targetId, options) {
9088
+ assertEdgeKinds(options.edges);
9089
+ const maxHops = resolveMaxHops(
9090
+ options.maxHops,
9091
+ DEFAULT_ALGORITHM_MAX_HOPS,
9092
+ "maxHops"
9093
+ );
9094
+ const cte = buildReachableCte({
9095
+ graphId: ctx.graphId,
9096
+ sourceId,
9097
+ edgeKinds: options.edges,
9098
+ maxHops,
9099
+ direction: options.direction ?? "out",
9100
+ cyclePolicy: options.cyclePolicy ?? "prevent",
9101
+ includePath: false,
9102
+ ...resolveTemporalOptions(ctx, options),
9103
+ dialect: ctx.dialect,
9104
+ schema: ctx.schema
9105
+ });
9106
+ const query = drizzleOrm.sql`${cte} SELECT 1 AS hit FROM reachable WHERE id = ${targetId} LIMIT 1`;
9107
+ const rows = await ctx.backend.execute(query);
9108
+ return rows.length > 0;
9109
+ }
9110
+ async function executeNeighbors(ctx, sourceId, options) {
9111
+ const depth = resolveMaxHops(options.depth, DEFAULT_NEIGHBOR_DEPTH, "depth");
9112
+ return executeReachable(ctx, sourceId, {
9113
+ edges: options.edges,
9114
+ maxHops: depth,
9115
+ direction: options.direction ?? "out",
9116
+ cyclePolicy: options.cyclePolicy ?? "prevent",
9117
+ ...options.temporalMode !== void 0 && {
9118
+ temporalMode: options.temporalMode
9119
+ },
9120
+ ...options.asOf !== void 0 && { asOf: options.asOf },
9121
+ excludeSource: true
9122
+ });
9123
+ }
9124
+ async function executeShortestPath(ctx, sourceId, targetId, options) {
9125
+ assertEdgeKinds(options.edges);
9126
+ const maxHops = resolveMaxHops(
9127
+ options.maxHops,
9128
+ DEFAULT_ALGORITHM_MAX_HOPS,
9129
+ "maxHops"
9130
+ );
9131
+ if (sourceId === targetId) {
9132
+ const source = await fetchNodeKind(ctx, sourceId, options);
9133
+ if (source === void 0) return void 0;
9134
+ return { nodes: [source], depth: 0 };
9135
+ }
9136
+ const cte = buildReachableCte({
9137
+ graphId: ctx.graphId,
9138
+ sourceId,
9139
+ edgeKinds: options.edges,
9140
+ maxHops,
9141
+ direction: options.direction ?? "out",
9142
+ cyclePolicy: options.cyclePolicy ?? "prevent",
9143
+ includePath: true,
9144
+ ...resolveTemporalOptions(ctx, options),
9145
+ dialect: ctx.dialect,
9146
+ schema: ctx.schema
9147
+ });
9148
+ const pathContainsCheck = drizzleOrm.sql`NOT (${ctx.dialect.cycleCheck(drizzleOrm.sql.raw("r.id"), drizzleOrm.sql.raw("h.path"))})`;
9149
+ const query = drizzleOrm.sql`${cte}, hit AS (SELECT id, kind, depth, path FROM reachable WHERE id = ${targetId} ORDER BY depth ASC LIMIT 1) SELECT h.id AS hit_id, h.kind AS hit_kind, h.depth AS hit_depth, h.path AS hit_path, r.id AS node_id, r.kind AS node_kind FROM hit h LEFT JOIN reachable r ON ${pathContainsCheck}`;
9150
+ const rows = await ctx.backend.execute(query);
9151
+ const first = rows[0];
9152
+ if (first === void 0) return void 0;
9153
+ const pathIds = normalizePath(first.hit_path);
9154
+ const kindById = buildKindIndex(rows);
9155
+ const pathNodes = pathIds.map((id) => ({
9156
+ id,
9157
+ kind: kindById.get(id) ?? ""
9158
+ }));
9159
+ return { nodes: pathNodes, depth: Number(first.hit_depth) };
9160
+ }
9161
+ function buildKindIndex(rows) {
9162
+ const map = /* @__PURE__ */ new Map();
9163
+ for (const row of rows) {
9164
+ if (row.node_id !== void 0 && row.node_kind !== void 0) {
9165
+ map.set(row.node_id, row.node_kind);
9166
+ }
9167
+ }
9168
+ return map;
9169
+ }
9170
+ async function fetchNodeKind(ctx, nodeId, options) {
9171
+ const temporalFilter = resolveTemporalFilter(ctx, options);
9172
+ const query = drizzleOrm.sql`SELECT id, kind FROM ${ctx.schema.nodesTable} WHERE graph_id = ${ctx.graphId} AND id = ${nodeId} AND ${temporalFilter} LIMIT 1`;
9173
+ const rows = await ctx.backend.execute(query);
9174
+ const row = rows[0];
9175
+ if (row === void 0) return void 0;
9176
+ return { id: row.id, kind: row.kind };
9177
+ }
9178
+
9179
+ // src/store/algorithms/index.ts
9180
+ function resolveNodeId(value) {
9181
+ return typeof value === "string" ? value : value.id;
9182
+ }
9183
+ function createGraphAlgorithms(params) {
9184
+ const ctx = {
9185
+ graphId: params.graphId,
9186
+ backend: params.backend,
9187
+ dialect: chunkMME3H4ZF_cjs.getDialect(params.backend.dialect),
9188
+ schema: params.schema ?? (params.backend.tableNames ? createSqlSchema(params.backend.tableNames) : createSqlSchema()),
9189
+ defaultTemporalMode: params.defaultTemporalMode
9190
+ };
9191
+ return {
9192
+ shortestPath(from, to, options) {
9193
+ return executeShortestPath(
9194
+ ctx,
9195
+ resolveNodeId(from),
9196
+ resolveNodeId(to),
9197
+ options
9198
+ );
9199
+ },
9200
+ reachable(from, options) {
9201
+ return executeReachable(ctx, resolveNodeId(from), options);
9202
+ },
9203
+ canReach(from, to, options) {
9204
+ return executeCanReach(
9205
+ ctx,
9206
+ resolveNodeId(from),
9207
+ resolveNodeId(to),
9208
+ options
9209
+ );
9210
+ },
9211
+ neighbors(node, options) {
9212
+ return executeNeighbors(ctx, resolveNodeId(node), options);
9213
+ },
9214
+ degree(node, options) {
9215
+ return executeDegree(ctx, resolveNodeId(node), options ?? {});
9216
+ }
9217
+ };
9218
+ }
9219
+
8899
9220
  // src/store/collections/edge-collection.ts
8900
9221
  function narrowEdge(edge) {
8901
9222
  return edge;
@@ -11663,6 +11984,7 @@ var Store = class {
11663
11984
  #defaultTraversalExpansion;
11664
11985
  #nodeCollections;
11665
11986
  #edgeCollections;
11987
+ #algorithms;
11666
11988
  constructor(graph, backend, options) {
11667
11989
  this.#graph = graph;
11668
11990
  this.#backend = backend;
@@ -11744,6 +12066,38 @@ var Store = class {
11744
12066
  }
11745
12067
  return this.#edgeCollections;
11746
12068
  }
12069
+ // === Graph Algorithms ===
12070
+ /**
12071
+ * Tier 1 graph algorithms: shortest path, reachability, neighborhoods, and
12072
+ * degree centrality.
12073
+ *
12074
+ * Each call compiles to a single recursive-CTE query against the backend.
12075
+ * The facade is built lazily on first access and cached for the lifetime
12076
+ * of the store.
12077
+ *
12078
+ * @example
12079
+ * ```typescript
12080
+ * const path = await store.algorithms.shortestPath(alice, bob, {
12081
+ * edges: ["knows"],
12082
+ * });
12083
+ *
12084
+ * const friends = await store.algorithms.neighbors(alice, {
12085
+ * edges: ["knows"],
12086
+ * depth: 2,
12087
+ * });
12088
+ * ```
12089
+ */
12090
+ get algorithms() {
12091
+ if (this.#algorithms === void 0) {
12092
+ this.#algorithms = createGraphAlgorithms({
12093
+ graphId: this.graphId,
12094
+ backend: this.#backend,
12095
+ schema: this.#schema,
12096
+ defaultTemporalMode: this.#graph.defaults.temporalMode
12097
+ });
12098
+ }
12099
+ return this.#algorithms;
12100
+ }
11747
12101
  // === Dynamic Collection Access ===
11748
12102
  /**
11749
12103
  * Returns the node collection for the given kind, or undefined if the kind
@@ -11924,22 +12278,26 @@ var Store = class {
11924
12278
  * Extracts a typed subgraph by traversing from a root node.
11925
12279
  *
11926
12280
  * Performs a BFS traversal from `rootId` following the specified edge kinds,
11927
- * then returns all reachable nodes and the edges connecting them.
12281
+ * returning an indexed result with adjacency maps for immediate traversal.
11928
12282
  *
11929
12283
  * @example
11930
12284
  * ```typescript
11931
- * const result = await store.subgraph(run.id, {
12285
+ * const sg = await store.subgraph(run.id, {
11932
12286
  * edges: ["has_task", "runs_agent", "uses_skill"],
11933
12287
  * maxDepth: 4,
11934
- * includeKinds: ["Run", "Task", "Agent", "Skill"],
11935
12288
  * });
11936
12289
  *
11937
- * for (const node of result.nodes) {
11938
- * switch (node.kind) {
11939
- * case "Task": console.log(node.name); break;
11940
- * case "Agent": console.log(node.model); break;
11941
- * }
11942
- * }
12290
+ * // Root node (the traversal starting point)
12291
+ * console.log(sg.root?.kind);
12292
+ *
12293
+ * // Lookup by ID
12294
+ * const task = sg.nodes.get(taskId);
12295
+ *
12296
+ * // Forward adjacency: edges of a kind from a node
12297
+ * const taskEdges = sg.adjacency.get(run.id)?.get("has_task") ?? [];
12298
+ *
12299
+ * // Reverse adjacency: edges of a kind pointing to a node
12300
+ * const parentEdges = sg.reverseAdjacency.get(taskId)?.get("has_task") ?? [];
11943
12301
  * ```
11944
12302
  */
11945
12303
  async subgraph(rootId, options) {
@@ -12018,6 +12376,7 @@ var Store = class {
12018
12376
  await (this.#backend.capabilities.transactions ? this.#backend.transaction(async (tx) => doClear(tx)) : doClear(this.#backend));
12019
12377
  this.#nodeCollections = void 0;
12020
12378
  this.#edgeCollections = void 0;
12379
+ this.#algorithms = void 0;
12021
12380
  }
12022
12381
  // === Lifecycle ===
12023
12382
  /**