@nicia-ai/typegraph 0.19.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) {
@@ -3539,6 +3649,8 @@ async function executeSubgraph(params) {
3539
3649
  excludeRoot: options.excludeRoot ?? false,
3540
3650
  direction: options.direction ?? "out",
3541
3651
  cyclePolicy: options.cyclePolicy ?? "prevent",
3652
+ temporalMode: options.temporalMode ?? params.graph.defaults.temporalMode,
3653
+ asOf: options.asOf,
3542
3654
  dialect: params.dialect,
3543
3655
  schema: params.schema ?? DEFAULT_SQL_SCHEMA,
3544
3656
  backend: params.backend
@@ -3556,7 +3668,19 @@ async function executeSubgraph(params) {
3556
3668
  (kind, field2) => schemaIntrospector.getEdgeFieldTypeInfo(kind, field2),
3557
3669
  "edge"
3558
3670
  );
3559
- 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
+ });
3560
3684
  const includedIdsCte = buildIncludedIdsCte(ctx);
3561
3685
  const [nodeRows, edgeRows] = await Promise.all([
3562
3686
  fetchSubgraphNodes(ctx, reachableCte, includedIdsCte, nodeProjectionPlan),
@@ -3654,79 +3778,6 @@ function getIncludedNodeKinds(graph, includeKinds) {
3654
3778
  function dedupeStrings(values) {
3655
3779
  return [...new Set(values)];
3656
3780
  }
3657
- function buildReachableCte(ctx) {
3658
- const shouldTrackPath = ctx.cyclePolicy === "prevent";
3659
- const edgeKindFilter = compileKindFilter(drizzleOrm.sql.raw("e.kind"), ctx.edgeKinds);
3660
- const initialPath = shouldTrackPath ? ctx.dialect.initializePath(drizzleOrm.sql.raw("n.id")) : void 0;
3661
- const pathExtension = shouldTrackPath ? ctx.dialect.extendPath(drizzleOrm.sql.raw("r.path"), drizzleOrm.sql.raw("n.id")) : void 0;
3662
- const cycleCheck = shouldTrackPath ? ctx.dialect.cycleCheck(drizzleOrm.sql.raw("n.id"), drizzleOrm.sql.raw("r.path")) : void 0;
3663
- const baseColumns = [drizzleOrm.sql`n.id`, drizzleOrm.sql`n.kind`, drizzleOrm.sql`0 AS depth`];
3664
- if (initialPath !== void 0) {
3665
- baseColumns.push(drizzleOrm.sql`${initialPath} AS path`);
3666
- }
3667
- 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`;
3668
- const recursiveColumns = [
3669
- drizzleOrm.sql`n.id`,
3670
- drizzleOrm.sql`n.kind`,
3671
- drizzleOrm.sql`r.depth + 1 AS depth`
3672
- ];
3673
- if (pathExtension !== void 0) {
3674
- recursiveColumns.push(drizzleOrm.sql`${pathExtension} AS path`);
3675
- }
3676
- const recursiveWhereClauses = [
3677
- drizzleOrm.sql`e.graph_id = ${ctx.graphId}`,
3678
- edgeKindFilter,
3679
- drizzleOrm.sql`e.deleted_at IS NULL`,
3680
- drizzleOrm.sql`n.deleted_at IS NULL`,
3681
- drizzleOrm.sql`r.depth < ${ctx.maxDepth}`
3682
- ];
3683
- if (cycleCheck !== void 0) {
3684
- recursiveWhereClauses.push(cycleCheck);
3685
- }
3686
- const forceWorktableOuterJoinOrder = ctx.dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
3687
- const recursiveCase = ctx.direction === "both" ? compileBidirectionalBranch({
3688
- recursiveColumns,
3689
- whereClauses: recursiveWhereClauses,
3690
- forceWorktableOuterJoinOrder,
3691
- schema: ctx.schema
3692
- }) : compileRecursiveBranch({
3693
- recursiveColumns,
3694
- whereClauses: recursiveWhereClauses,
3695
- joinField: "from_id",
3696
- targetField: "to_id",
3697
- targetKindField: "to_kind",
3698
- forceWorktableOuterJoinOrder,
3699
- schema: ctx.schema
3700
- });
3701
- return drizzleOrm.sql`WITH RECURSIVE reachable AS (${baseCase} UNION ALL ${recursiveCase})`;
3702
- }
3703
- function compileRecursiveBranch(params) {
3704
- const columns = [...params.recursiveColumns];
3705
- const selectClause = drizzleOrm.sql`SELECT ${drizzleOrm.sql.join(columns, drizzleOrm.sql`, `)}`;
3706
- 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)}`;
3707
- if (params.forceWorktableOuterJoinOrder) {
3708
- const allWhere = [
3709
- ...params.whereClauses,
3710
- drizzleOrm.sql`e.${drizzleOrm.sql.raw(params.joinField)} = r.id`
3711
- ];
3712
- return drizzleOrm.sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${drizzleOrm.sql.join(allWhere, drizzleOrm.sql` AND `)}`;
3713
- }
3714
- const where = [...params.whereClauses];
3715
- 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 `)}`;
3716
- }
3717
- function compileBidirectionalBranch(params) {
3718
- const columns = [...params.recursiveColumns];
3719
- const selectClause = drizzleOrm.sql`SELECT ${drizzleOrm.sql.join(columns, drizzleOrm.sql`, `)}`;
3720
- 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))`;
3721
- if (params.forceWorktableOuterJoinOrder) {
3722
- const allWhere = [
3723
- ...params.whereClauses,
3724
- drizzleOrm.sql`(e.from_id = r.id OR e.to_id = r.id)`
3725
- ];
3726
- return drizzleOrm.sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${drizzleOrm.sql.join(allWhere, drizzleOrm.sql` AND `)}`;
3727
- }
3728
- 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 `)}`;
3729
- }
3730
3781
  function buildIncludedIdsCte(ctx) {
3731
3782
  const filters = [];
3732
3783
  if (ctx.includeKinds !== void 0 && ctx.includeKinds.length > 0) {
@@ -3758,6 +3809,12 @@ async function fetchSubgraphNodes(ctx, reachableCte, includedIdsCte, projectionP
3758
3809
  }
3759
3810
  async function fetchSubgraphEdges(ctx, reachableCte, includedIdsCte, projectionPlan) {
3760
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
+ });
3761
3818
  const columns = [
3762
3819
  drizzleOrm.sql`e.id`,
3763
3820
  drizzleOrm.sql`e.kind`,
@@ -3775,7 +3832,7 @@ async function fetchSubgraphEdges(ctx, reachableCte, includedIdsCte, projectionP
3775
3832
  ]),
3776
3833
  ...buildProjectedPropertyColumns("e", projectionPlan, ctx.dialect)
3777
3834
  ];
3778
- 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)`;
3779
3836
  return ctx.backend.execute(query);
3780
3837
  }
3781
3838
  function buildMetadataColumns(alias, plan, columns) {
@@ -8920,6 +8977,246 @@ function extractEdgeTypes(graph) {
8920
8977
  return result;
8921
8978
  }
8922
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
+
8923
9220
  // src/store/collections/edge-collection.ts
8924
9221
  function narrowEdge(edge) {
8925
9222
  return edge;
@@ -11687,6 +11984,7 @@ var Store = class {
11687
11984
  #defaultTraversalExpansion;
11688
11985
  #nodeCollections;
11689
11986
  #edgeCollections;
11987
+ #algorithms;
11690
11988
  constructor(graph, backend, options) {
11691
11989
  this.#graph = graph;
11692
11990
  this.#backend = backend;
@@ -11768,6 +12066,38 @@ var Store = class {
11768
12066
  }
11769
12067
  return this.#edgeCollections;
11770
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
+ }
11771
12101
  // === Dynamic Collection Access ===
11772
12102
  /**
11773
12103
  * Returns the node collection for the given kind, or undefined if the kind
@@ -12046,6 +12376,7 @@ var Store = class {
12046
12376
  await (this.#backend.capabilities.transactions ? this.#backend.transaction(async (tx) => doClear(tx)) : doClear(this.#backend));
12047
12377
  this.#nodeCollections = void 0;
12048
12378
  this.#edgeCollections = void 0;
12379
+ this.#algorithms = void 0;
12049
12380
  }
12050
12381
  // === Lifecycle ===
12051
12382
  /**