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