@nicia-ai/typegraph 0.21.0 → 0.22.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.
Files changed (43) hide show
  1. package/dist/backend/postgres/index.cjs +33 -33
  2. package/dist/backend/postgres/index.js +4 -4
  3. package/dist/backend/sqlite/index.cjs +14 -14
  4. package/dist/backend/sqlite/index.d.cts +11 -0
  5. package/dist/backend/sqlite/index.d.ts +11 -0
  6. package/dist/backend/sqlite/index.js +4 -4
  7. package/dist/backend/sqlite/libsql.cjs +7 -7
  8. package/dist/backend/sqlite/libsql.js +4 -4
  9. package/dist/backend/sqlite/local.cjs +26 -9
  10. package/dist/backend/sqlite/local.cjs.map +1 -1
  11. package/dist/backend/sqlite/local.js +21 -5
  12. package/dist/backend/sqlite/local.js.map +1 -1
  13. package/dist/{chunk-FQCQM3KG.cjs → chunk-3JK66E3I.cjs} +4 -2
  14. package/dist/chunk-3JK66E3I.cjs.map +1 -0
  15. package/dist/{chunk-SMMOAVJ5.js → chunk-3PPEHLGK.js} +3 -3
  16. package/dist/{chunk-SMMOAVJ5.js.map → chunk-3PPEHLGK.js.map} +1 -1
  17. package/dist/{chunk-UQVBVBVG.js → chunk-5ZMMHUWR.js} +53 -7
  18. package/dist/chunk-5ZMMHUWR.js.map +1 -0
  19. package/dist/{chunk-4XNE2JG3.js → chunk-753F33CV.js} +4 -2
  20. package/dist/chunk-753F33CV.js.map +1 -0
  21. package/dist/{chunk-CQL6FOTT.cjs → chunk-GUYI6VAS.cjs} +43 -24
  22. package/dist/chunk-GUYI6VAS.cjs.map +1 -0
  23. package/dist/{chunk-NRNJ52AA.js → chunk-NSLTDXVH.js} +26 -7
  24. package/dist/chunk-NSLTDXVH.js.map +1 -0
  25. package/dist/{chunk-2X7P7AD5.cjs → chunk-PEE4EWXZ.cjs} +22 -22
  26. package/dist/{chunk-2X7P7AD5.cjs.map → chunk-PEE4EWXZ.cjs.map} +1 -1
  27. package/dist/{chunk-Q73OWTJY.cjs → chunk-SCXOGKLD.cjs} +112 -66
  28. package/dist/chunk-SCXOGKLD.cjs.map +1 -0
  29. package/dist/index.cjs +135 -54
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +45 -1
  32. package/dist/index.d.ts +45 -1
  33. package/dist/index.js +115 -36
  34. package/dist/index.js.map +1 -1
  35. package/dist/indexes/index.cjs +16 -16
  36. package/dist/indexes/index.js +2 -2
  37. package/package.json +1 -1
  38. package/dist/chunk-4XNE2JG3.js.map +0 -1
  39. package/dist/chunk-CQL6FOTT.cjs.map +0 -1
  40. package/dist/chunk-FQCQM3KG.cjs.map +0 -1
  41. package/dist/chunk-NRNJ52AA.js.map +0 -1
  42. package/dist/chunk-Q73OWTJY.cjs.map +0 -1
  43. package/dist/chunk-UQVBVBVG.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -480,6 +480,50 @@ declare function count(alias: string, field?: string): AggregateExpr;
480
480
  * @param field - Optional field to count distinct values of
481
481
  */
482
482
  declare function countDistinct(alias: string, field?: string): AggregateExpr;
483
+ /**
484
+ * Creates a COUNT aggregate expression over an edge alias.
485
+ *
486
+ * Counts edges directly, without joining back to the target node table.
487
+ * This has different semantics from `count(targetAlias)`:
488
+ *
489
+ * - `countEdges(edgeAlias)` — counts live edges, regardless of target-node
490
+ * temporal state. If a target node's `validTo` is in the past while its
491
+ * incident edges are still live, those edges are still counted.
492
+ * - `count(targetAlias)` — counts edges whose target node is currently
493
+ * valid under the query's temporal mode.
494
+ *
495
+ * For the common case ("how many follow relationships does this user
496
+ * have?") the edge-count semantics is what you want, and the compiler
497
+ * can skip the target-node join entirely — measurably faster on
498
+ * full-graph aggregates where the join dominates cost.
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * store
503
+ * .query()
504
+ * .from("User", "u")
505
+ * .optionalTraverse("follows", "e", { expand: "none" })
506
+ * .to("User", "target")
507
+ * .groupByNode("u")
508
+ * .aggregate({
509
+ * name: field("u", "name"),
510
+ * // counts edges — skips the target-node join
511
+ * followCount: countEdges("e"),
512
+ * })
513
+ * .execute();
514
+ * ```
515
+ */
516
+ declare function countEdges(edgeAlias: string): AggregateExpr;
517
+ /**
518
+ * Creates a COUNT DISTINCT aggregate expression over an edge alias.
519
+ * See {@link countEdges} for semantics.
520
+ *
521
+ * Distinct-on-edge-id is equivalent to `countEdges` when the query
522
+ * surface produces each edge exactly once, but stays meaningful under
523
+ * polymorphic expansions and ontology-driven edge fan-outs where the
524
+ * same edge can appear multiple times in the join output.
525
+ */
526
+ declare function countDistinctEdges(edgeAlias: string): AggregateExpr;
483
527
  /**
484
528
  * Creates a SUM aggregate expression.
485
529
  *
@@ -722,4 +766,4 @@ type IdConfig = Readonly<{
722
766
  edgeIdGenerator: IdGenerator;
723
767
  }>;
724
768
 
725
- export { AliasMap, type DefineEdgeOptions, type DefineNodeOptions, EdgeType, type ExternalRefSchema, type ExternalRefValue, FieldRef, type FlexibleQueryFragment, GraphDef, type IdConfig, type IdGenerator, InferenceType, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, MetaEdge, type MetaEdgeOptions, NodeType, OntologyRelation, QueryBuilder, type QueryFragment, type TraversalFragment, avg, broader, composeFragments, core, count, countDistinct, createExternalRef, createFragment, createQueryBuilder, defineEdge, defineNode, differentFrom, disjointWith, equivalentTo, externalRef, field, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inverseOf, isExternalRefSchema, limitFragment, max, metaEdge, min, narrower, offsetFragment, orderByFragment, partOf, relatedTo, sameAs, subClassOf, sum };
769
+ export { AliasMap, type DefineEdgeOptions, type DefineNodeOptions, EdgeType, type ExternalRefSchema, type ExternalRefValue, FieldRef, type FlexibleQueryFragment, GraphDef, type IdConfig, type IdGenerator, InferenceType, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, MetaEdge, type MetaEdgeOptions, NodeType, OntologyRelation, QueryBuilder, type QueryFragment, type TraversalFragment, avg, broader, composeFragments, core, count, countDistinct, countDistinctEdges, countEdges, createExternalRef, createFragment, createQueryBuilder, defineEdge, defineNode, differentFrom, disjointWith, equivalentTo, externalRef, field, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inverseOf, isExternalRefSchema, limitFragment, max, metaEdge, min, narrower, offsetFragment, orderByFragment, partOf, relatedTo, sameAs, subClassOf, sum };
package/dist/index.d.ts CHANGED
@@ -480,6 +480,50 @@ declare function count(alias: string, field?: string): AggregateExpr;
480
480
  * @param field - Optional field to count distinct values of
481
481
  */
482
482
  declare function countDistinct(alias: string, field?: string): AggregateExpr;
483
+ /**
484
+ * Creates a COUNT aggregate expression over an edge alias.
485
+ *
486
+ * Counts edges directly, without joining back to the target node table.
487
+ * This has different semantics from `count(targetAlias)`:
488
+ *
489
+ * - `countEdges(edgeAlias)` — counts live edges, regardless of target-node
490
+ * temporal state. If a target node's `validTo` is in the past while its
491
+ * incident edges are still live, those edges are still counted.
492
+ * - `count(targetAlias)` — counts edges whose target node is currently
493
+ * valid under the query's temporal mode.
494
+ *
495
+ * For the common case ("how many follow relationships does this user
496
+ * have?") the edge-count semantics is what you want, and the compiler
497
+ * can skip the target-node join entirely — measurably faster on
498
+ * full-graph aggregates where the join dominates cost.
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * store
503
+ * .query()
504
+ * .from("User", "u")
505
+ * .optionalTraverse("follows", "e", { expand: "none" })
506
+ * .to("User", "target")
507
+ * .groupByNode("u")
508
+ * .aggregate({
509
+ * name: field("u", "name"),
510
+ * // counts edges — skips the target-node join
511
+ * followCount: countEdges("e"),
512
+ * })
513
+ * .execute();
514
+ * ```
515
+ */
516
+ declare function countEdges(edgeAlias: string): AggregateExpr;
517
+ /**
518
+ * Creates a COUNT DISTINCT aggregate expression over an edge alias.
519
+ * See {@link countEdges} for semantics.
520
+ *
521
+ * Distinct-on-edge-id is equivalent to `countEdges` when the query
522
+ * surface produces each edge exactly once, but stays meaningful under
523
+ * polymorphic expansions and ontology-driven edge fan-outs where the
524
+ * same edge can appear multiple times in the join output.
525
+ */
526
+ declare function countDistinctEdges(edgeAlias: string): AggregateExpr;
483
527
  /**
484
528
  * Creates a SUM aggregate expression.
485
529
  *
@@ -722,4 +766,4 @@ type IdConfig = Readonly<{
722
766
  edgeIdGenerator: IdGenerator;
723
767
  }>;
724
768
 
725
- export { AliasMap, type DefineEdgeOptions, type DefineNodeOptions, EdgeType, type ExternalRefSchema, type ExternalRefValue, FieldRef, type FlexibleQueryFragment, GraphDef, type IdConfig, type IdGenerator, InferenceType, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, MetaEdge, type MetaEdgeOptions, NodeType, OntologyRelation, QueryBuilder, type QueryFragment, type TraversalFragment, avg, broader, composeFragments, core, count, countDistinct, createExternalRef, createFragment, createQueryBuilder, defineEdge, defineNode, differentFrom, disjointWith, equivalentTo, externalRef, field, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inverseOf, isExternalRefSchema, limitFragment, max, metaEdge, min, narrower, offsetFragment, orderByFragment, partOf, relatedTo, sameAs, subClassOf, sum };
769
+ export { AliasMap, type DefineEdgeOptions, type DefineNodeOptions, EdgeType, type ExternalRefSchema, type ExternalRefValue, FieldRef, type FlexibleQueryFragment, GraphDef, type IdConfig, type IdGenerator, InferenceType, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, MetaEdge, type MetaEdgeOptions, NodeType, OntologyRelation, QueryBuilder, type QueryFragment, type TraversalFragment, avg, broader, composeFragments, core, count, countDistinct, countDistinctEdges, countEdges, createExternalRef, createFragment, createQueryBuilder, defineEdge, defineNode, differentFrom, disjointWith, equivalentTo, externalRef, field, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inverseOf, isExternalRefSchema, limitFragment, max, metaEdge, min, narrower, offsetFragment, orderByFragment, partOf, relatedTo, sameAs, subClassOf, sum };
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@ import { NODE_TYPE_BRAND, EDGE_TYPE_BRAND, nowIso, validateOptionalIsoDate, getN
4
4
  export { defineGraph, getEdgeKinds, getNodeKinds, isEdgeType, isEdgeTypeWithEndpoints, isGraphDef, isNodeType } from './chunk-RS5OLGEW.js';
5
5
  import { ConfigurationError, UnsupportedPredicateError, ValidationError, CompilerInvariantError, KindNotFoundError, DatabaseOperationError, EdgeNotFoundError, NodeConstraintNotFoundError, RestrictedDeleteError, NodeNotFoundError, EndpointNotFoundError, UniquenessError, CardinalityError, EndpointError, DisjointError } from './chunk-WFMTAEEN.js';
6
6
  export { BackendDisposedError, CardinalityError, CompilerInvariantError, ConfigurationError, DatabaseOperationError, DisjointError, EdgeNotFoundError, EndpointError, EndpointNotFoundError, KindNotFoundError, MigrationError, NodeConstraintNotFoundError, NodeNotFoundError, RestrictedDeleteError, SchemaMismatchError, TypeGraphError, UniquenessError, UnsupportedPredicateError, ValidationError, VersionConflictError, getErrorSuggestion, isConstraintError, isSystemError, isTypeGraphError, isUserRecoverable } from './chunk-WFMTAEEN.js';
7
- import { MAX_PG_IDENTIFIER_LENGTH, DEFAULT_PAGINATION_LIMIT, getDialect, DEFAULT_STREAM_BATCH_SIZE, NODE_META_KEYS, EDGE_META_KEYS, SQLITE_PATH_DELIMITER, PG_ARRAY_START, PG_ARRAY_END, PG_PATH_ELEMENT_SEPARATOR, fnv1aBase36 } from './chunk-4XNE2JG3.js';
8
- export { fts5Strategy, tsvectorStrategy } from './chunk-4XNE2JG3.js';
7
+ import { MAX_PG_IDENTIFIER_LENGTH, DEFAULT_PAGINATION_LIMIT, getDialect, DEFAULT_STREAM_BATCH_SIZE, NODE_META_KEYS, EDGE_META_KEYS, SQLITE_PATH_DELIMITER, PG_ARRAY_START, PG_ARRAY_END, PG_PATH_ELEMENT_SEPARATOR, fnv1aBase36 } from './chunk-753F33CV.js';
8
+ export { fts5Strategy, tsvectorStrategy } from './chunk-753F33CV.js';
9
9
  import { jsonPointer, parseJsonPointer, createSchemaIntrospector, normalizeJsonPointer, joinJsonPointers, resolveFieldTypeInfoAtJsonPointer, getSearchableMetadata, DEFAULT_SEARCHABLE_LANGUAGE, getEmbeddingDimensions, isEmbeddingSchema } from './chunk-EYELZHKY.js';
10
10
  export { DEFAULT_SEARCHABLE_LANGUAGE, MAX_JSON_POINTER_DEPTH, embedding, getEmbeddingDimensions, getSearchableMetadata, isEmbeddingSchema, isSearchableSchema, joinJsonPointers, jsonPointer, normalizeJsonPointer, parseJsonPointer, searchable } from './chunk-EYELZHKY.js';
11
11
  import { z } from 'zod';
@@ -2107,6 +2107,7 @@ function buildStandardStartCte(input) {
2107
2107
  const { ast, ctx, graphId, predicateIndex, requiredColumnsByAlias } = input;
2108
2108
  const alias = ast.start.alias;
2109
2109
  const kinds = ast.start.kinds;
2110
+ const cteMaterialization = ctx.dialect.capabilities.emitNotMaterializedHint ? sql`NOT MATERIALIZED ` : sql``;
2110
2111
  const kindFilter = compileKindFilter(sql.raw("kind"), kinds);
2111
2112
  const temporalFilter = input.temporalFilterPass.forAlias();
2112
2113
  const cteContext = { ...ctx, cteColumnPrefix: "" };
@@ -2121,14 +2122,19 @@ function buildStandardStartCte(input) {
2121
2122
  ...predicateClauses
2122
2123
  ];
2123
2124
  const effectiveRequiredColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(alias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
2125
+ const limitClause = input.limitOffset === void 0 ? sql`` : input.limitOffset.offset === void 0 ? sql`
2126
+ LIMIT ${input.limitOffset.limit}
2127
+ ` : sql`
2128
+ LIMIT ${input.limitOffset.limit} OFFSET ${input.limitOffset.offset}
2129
+ `;
2124
2130
  return sql`
2125
- cte_${sql.raw(alias)} AS (
2131
+ cte_${sql.raw(alias)} AS ${cteMaterialization}(
2126
2132
  SELECT ${sql.join(
2127
2133
  compileNodeSelectColumns(void 0, alias, effectiveRequiredColumns),
2128
2134
  sql`, `
2129
2135
  )}
2130
2136
  FROM ${ctx.schema.nodesTable}
2131
- WHERE ${sql.join(whereClauses, sql` AND `)}
2137
+ WHERE ${sql.join(whereClauses, sql` AND `)}${limitClause}
2132
2138
  )
2133
2139
  `;
2134
2140
  }
@@ -2192,7 +2198,7 @@ function buildStandardTraversalCte(input) {
2192
2198
  ...compileEdgeSelectColumns("e", edgeAlias, requiredEdgeColumns),
2193
2199
  ...compileNodeSelectColumns("n", nodeAlias, requiredNodeColumns)
2194
2200
  ];
2195
- const cteMaterialization = materializeCte ? sql`MATERIALIZED ` : sql``;
2201
+ const cteMaterialization = materializeCte ? sql`MATERIALIZED ` : ctx.dialect.capabilities.emitNotMaterializedHint ? sql`NOT MATERIALIZED ` : sql``;
2196
2202
  function compileTraversalBranch(branch) {
2197
2203
  const whereClauses = [
2198
2204
  ...baseWhereClauses,
@@ -5282,8 +5288,10 @@ function resolveCountAggregateFastPath(ast) {
5282
5288
  if (groupField.alias !== ast.start.alias || !isIdFieldRef(groupField)) {
5283
5289
  return void 0;
5284
5290
  }
5285
- let requiresCount = false;
5286
- let requiresCountDistinct = false;
5291
+ let requiresNodeCount = false;
5292
+ let requiresNodeCountDistinct = false;
5293
+ let requiresEdgeCount = false;
5294
+ let requiresEdgeCountDistinct = false;
5287
5295
  for (const projectedField of ast.projection.fields) {
5288
5296
  const source = projectedField.source;
5289
5297
  if (!isAggregateExpr(source)) {
@@ -5292,26 +5300,35 @@ function resolveCountAggregateFastPath(ast) {
5292
5300
  }
5293
5301
  continue;
5294
5302
  }
5295
- if (source.field.alias !== traversal.nodeAlias || !isIdFieldRef(source.field)) {
5303
+ if (!isIdFieldRef(source.field)) {
5304
+ return void 0;
5305
+ }
5306
+ const isNodeTarget = source.field.alias === traversal.nodeAlias;
5307
+ const isEdgeTarget = source.field.alias === traversal.edgeAlias;
5308
+ if (!isNodeTarget && !isEdgeTarget) {
5296
5309
  return void 0;
5297
5310
  }
5298
5311
  if (source.function === "count") {
5299
- requiresCount = true;
5312
+ if (isNodeTarget) requiresNodeCount = true;
5313
+ else requiresEdgeCount = true;
5300
5314
  continue;
5301
5315
  }
5302
5316
  if (source.function === "countDistinct") {
5303
- requiresCountDistinct = true;
5317
+ if (isNodeTarget) requiresNodeCountDistinct = true;
5318
+ else requiresEdgeCountDistinct = true;
5304
5319
  continue;
5305
5320
  }
5306
5321
  return void 0;
5307
5322
  }
5308
- if (!requiresCount && !requiresCountDistinct) {
5323
+ if (!requiresNodeCount && !requiresNodeCountDistinct && !requiresEdgeCount && !requiresEdgeCountDistinct) {
5309
5324
  return void 0;
5310
5325
  }
5311
5326
  return {
5312
5327
  traversal,
5313
- requiresCount,
5314
- requiresCountDistinct
5328
+ requiresNodeCount,
5329
+ requiresNodeCountDistinct,
5330
+ requiresEdgeCount,
5331
+ requiresEdgeCountDistinct
5315
5332
  };
5316
5333
  }
5317
5334
  function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredColumnsByAlias, predicateIndex, temporalFilterPass) {
@@ -5319,15 +5336,23 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
5319
5336
  if (!plan) {
5320
5337
  return void 0;
5321
5338
  }
5322
- const { traversal, requiresCount, requiresCountDistinct } = plan;
5339
+ const {
5340
+ traversal,
5341
+ requiresNodeCount,
5342
+ requiresNodeCountDistinct,
5343
+ requiresEdgeCount,
5344
+ requiresEdgeCountDistinct
5345
+ } = plan;
5323
5346
  const { dialect } = ctx;
5324
5347
  const startAlias = ast.start.alias;
5325
5348
  const previousAlias = traversal.joinFromAlias;
5326
5349
  const previousAliasIdColumn = `${previousAlias}_id`;
5327
5350
  const previousAliasKindColumn = `${previousAlias}_kind`;
5328
5351
  const countCteAlias = `cte_${traversal.nodeAlias}_counts`;
5329
- const countColumn = `${traversal.nodeAlias}_count`;
5330
- const countDistinctColumn = `${traversal.nodeAlias}_count_distinct`;
5352
+ const nodeCountColumn = `${traversal.nodeAlias}_count`;
5353
+ const nodeCountDistinctColumn = `${traversal.nodeAlias}_count_distinct`;
5354
+ const edgeCountColumn = `${traversal.edgeAlias}_count`;
5355
+ const edgeCountDistinctColumn = `${traversal.edgeAlias}_count_distinct`;
5331
5356
  const previousNodeKinds = getNodeKindsForAlias(ast, traversal.joinFromAlias);
5332
5357
  const edgeKinds = [...new Set(traversal.edgeKinds)];
5333
5358
  const nodeKinds = traversal.nodeKinds;
@@ -5353,34 +5378,68 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
5353
5378
  const targetField = traversal.direction === "out" ? "to_id" : "from_id";
5354
5379
  const joinKindField = traversal.direction === "out" ? "from_kind" : "to_kind";
5355
5380
  const targetKindField = traversal.direction === "out" ? "to_kind" : "from_kind";
5381
+ const hasNodePredicates = nodePredicateClauses.length > 0;
5382
+ const requiresNodeJoin = requiresNodeCount || requiresNodeCountDistinct || hasNodePredicates;
5383
+ const useInnerJoin = hasNodePredicates;
5356
5384
  const whereClauses = [
5357
5385
  sql`e.graph_id = ${graphId}`,
5358
5386
  compileKindFilter(sql.raw("e.kind"), edgeKinds),
5359
5387
  compileKindFilter(sql.raw(`e.${joinKindField}`), previousNodeKinds),
5360
5388
  compileKindFilter(sql.raw(`e.${targetKindField}`), nodeKinds),
5361
- compileKindFilter(sql.raw("n.kind"), nodeKinds),
5362
5389
  edgeTemporalFilter,
5363
- nodeTemporalFilter,
5364
- ...nodePredicateClauses,
5365
- ...edgePredicateClauses
5390
+ ...edgePredicateClauses,
5391
+ // INNER JOIN mode: node-side filters in WHERE constrain every aggregate.
5392
+ ...requiresNodeJoin && useInnerJoin ? [
5393
+ compileKindFilter(sql.raw("n.kind"), nodeKinds),
5394
+ nodeTemporalFilter,
5395
+ ...nodePredicateClauses
5396
+ ] : []
5366
5397
  ];
5398
+ const nodeJoinOnClauses = requiresNodeJoin ? useInnerJoin ? (
5399
+ // INNER JOIN: only the key conditions; filters live in WHERE.
5400
+ [
5401
+ sql`n.graph_id = e.graph_id`,
5402
+ sql`n.id = e.${sql.raw(targetField)}`,
5403
+ sql`n.kind = e.${sql.raw(targetKindField)}`
5404
+ ]
5405
+ ) : [
5406
+ sql`n.graph_id = e.graph_id`,
5407
+ sql`n.id = e.${sql.raw(targetField)}`,
5408
+ sql`n.kind = e.${sql.raw(targetKindField)}`,
5409
+ compileKindFilter(sql.raw("n.kind"), nodeKinds),
5410
+ nodeTemporalFilter
5411
+ ] : [];
5367
5412
  const aggregateColumns = [];
5368
- if (requiresCount) {
5369
- aggregateColumns.push(sql`COUNT(n.id) AS ${sql.raw(countColumn)}`);
5413
+ if (requiresNodeCount) {
5414
+ aggregateColumns.push(sql`COUNT(n.id) AS ${sql.raw(nodeCountColumn)}`);
5415
+ }
5416
+ if (requiresNodeCountDistinct) {
5417
+ aggregateColumns.push(
5418
+ sql`COUNT(DISTINCT n.id) AS ${sql.raw(nodeCountDistinctColumn)}`
5419
+ );
5420
+ }
5421
+ if (requiresEdgeCount) {
5422
+ aggregateColumns.push(sql`COUNT(e.id) AS ${sql.raw(edgeCountColumn)}`);
5370
5423
  }
5371
- if (requiresCountDistinct) {
5424
+ if (requiresEdgeCountDistinct) {
5372
5425
  aggregateColumns.push(
5373
- sql`COUNT(DISTINCT n.id) AS ${sql.raw(countDistinctColumn)}`
5426
+ sql`COUNT(DISTINCT e.id) AS ${sql.raw(edgeCountDistinctColumn)}`
5374
5427
  );
5375
5428
  }
5429
+ const startCteLimit = traversal.optional && ast.orderBy === void 0 && ast.limit !== void 0 ? { limit: ast.limit, offset: ast.offset } : void 0;
5376
5430
  const startCte = buildStandardStartCte({
5377
5431
  ast,
5378
5432
  ctx,
5379
5433
  graphId,
5380
5434
  predicateIndex,
5381
5435
  requiredColumnsByAlias,
5382
- temporalFilterPass
5436
+ temporalFilterPass,
5437
+ ...startCteLimit === void 0 ? {} : { limitOffset: startCteLimit }
5383
5438
  });
5439
+ const targetNodeJoinKeyword = useInnerJoin ? "JOIN" : "LEFT JOIN";
5440
+ const targetNodeJoin = requiresNodeJoin ? sql`
5441
+ ${sql.raw(targetNodeJoinKeyword)} ${ctx.schema.nodesTable} n ON ${sql.join(nodeJoinOnClauses, sql` AND `)}
5442
+ ` : sql``;
5384
5443
  const countCte = sql`
5385
5444
  ${sql.raw(countCteAlias)} AS (
5386
5445
  SELECT
@@ -5389,16 +5448,20 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
5389
5448
  ${sql.join(aggregateColumns, sql`, `)}
5390
5449
  FROM cte_${sql.raw(previousAlias)}
5391
5450
  JOIN ${ctx.schema.edgesTable} e ON cte_${sql.raw(previousAlias)}.${sql.raw(previousAliasIdColumn)} = e.${sql.raw(joinField)}
5392
- AND cte_${sql.raw(previousAlias)}.${sql.raw(previousAliasKindColumn)} = e.${sql.raw(joinKindField)}
5393
- JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
5394
- AND n.id = e.${sql.raw(targetField)}
5395
- AND n.kind = e.${sql.raw(targetKindField)}
5451
+ AND cte_${sql.raw(previousAlias)}.${sql.raw(previousAliasKindColumn)} = e.${sql.raw(joinKindField)}${targetNodeJoin}
5396
5452
  WHERE ${sql.join(whereClauses, sql` AND `)}
5397
5453
  GROUP BY
5398
5454
  cte_${sql.raw(previousAlias)}.${sql.raw(previousAliasIdColumn)},
5399
5455
  cte_${sql.raw(previousAlias)}.${sql.raw(previousAliasKindColumn)}
5400
5456
  )
5401
5457
  `;
5458
+ function resolveCountColumn(source) {
5459
+ const isEdge = source.field.alias === traversal.edgeAlias;
5460
+ if (source.function === "countDistinct") {
5461
+ return isEdge ? edgeCountDistinctColumn : nodeCountDistinctColumn;
5462
+ }
5463
+ return isEdge ? edgeCountColumn : nodeCountColumn;
5464
+ }
5402
5465
  const projection = sql.join(
5403
5466
  ast.projection.fields.map((projectedField) => {
5404
5467
  const source = projectedField.source;
@@ -5411,7 +5474,7 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
5411
5474
  );
5412
5475
  return sql`${value} AS ${quoteIdentifier2(projectedField.outputName)}`;
5413
5476
  }
5414
- const projectedCountColumn = source.function === "countDistinct" ? countDistinctColumn : countColumn;
5477
+ const projectedCountColumn = resolveCountColumn(source);
5415
5478
  const countValue = sql`${sql.raw(countCteAlias)}.${sql.raw(projectedCountColumn)}`;
5416
5479
  const aggregateValue = traversal.optional ? sql`COALESCE(${countValue}, 0)` : countValue;
5417
5480
  return sql`${aggregateValue} AS ${quoteIdentifier2(projectedField.outputName)}`;
@@ -5426,19 +5489,29 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
5426
5489
  AND ${sql.raw(countCteAlias)}.${sql.raw(previousAliasKindColumn)} = cte_${sql.raw(previousAlias)}.${sql.raw(previousAliasKindColumn)}
5427
5490
  `;
5428
5491
  const orderBy = buildStandardOrderBy({ ast, dialect });
5429
- const limitOffset = buildLimitOffsetClause({
5430
- limit: ast.limit,
5431
- offset: ast.offset
5432
- });
5492
+ const emittedLogicalPlan = startCteLimit === void 0 ? logicalPlan : stripLimitOffsetFromPlan(logicalPlan);
5493
+ const limitOffset = startCteLimit === void 0 ? buildLimitOffsetClause({ limit: ast.limit, offset: ast.offset }) : void 0;
5433
5494
  return emitStandardQuerySql({
5434
5495
  ctes: [startCte, countCte],
5435
5496
  fromClause,
5436
5497
  ...orderBy === void 0 ? {} : { orderBy },
5437
5498
  ...limitOffset === void 0 ? {} : { limitOffset },
5438
- logicalPlan,
5499
+ logicalPlan: emittedLogicalPlan,
5439
5500
  projection
5440
5501
  });
5441
5502
  }
5503
+ function stripLimitOffsetFromPlan(plan) {
5504
+ return { ...plan, root: stripLimitOffsetFromPlanNode(plan.root) };
5505
+ }
5506
+ function stripLimitOffsetFromPlanNode(node) {
5507
+ if (node.op === "limit_offset") {
5508
+ return stripLimitOffsetFromPlanNode(node.input);
5509
+ }
5510
+ if ("input" in node) {
5511
+ return { ...node, input: stripLimitOffsetFromPlanNode(node.input) };
5512
+ }
5513
+ return node;
5514
+ }
5442
5515
  var STANDARD_QUERY_STRATEGY_HANDLERS = {
5443
5516
  cte_project: compileStandardQueryWithCteStrategy
5444
5517
  };
@@ -9363,6 +9436,12 @@ function countDistinct(alias, field2) {
9363
9436
  }
9364
9437
  };
9365
9438
  }
9439
+ function countEdges(edgeAlias) {
9440
+ return count(edgeAlias);
9441
+ }
9442
+ function countDistinctEdges(edgeAlias) {
9443
+ return countDistinct(edgeAlias);
9444
+ }
9366
9445
  function sum(alias, field2) {
9367
9446
  return {
9368
9447
  __type: "aggregate",
@@ -13676,6 +13755,6 @@ async function createStoreWithSchema(graph, backend, options) {
13676
13755
  return [store, result];
13677
13756
  }
13678
13757
 
13679
- export { DEFAULT_SQL_SCHEMA, ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, StoreSearch, UnionableQuery, avg, broader, composeFragments, core, count, countDistinct, createExternalRef, createFragment, createQueryBuilder, createSqlSchema, createStore, createStoreWithSchema, defineEdge, defineNode, defineSubgraphProject, differentFrom, disjointWith, equivalentTo, exists, externalRef, field, fieldRef, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inSubquery, inverseOf, isExternalRefSchema, isParameterRef, limitFragment, max, metaEdge, min, narrower, notExists, notInSubquery, offsetFragment, orderByFragment, param, partOf, relatedTo, sameAs, subClassOf, sum };
13758
+ export { DEFAULT_SQL_SCHEMA, ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, StoreSearch, UnionableQuery, avg, broader, composeFragments, core, count, countDistinct, countDistinctEdges, countEdges, createExternalRef, createFragment, createQueryBuilder, createSqlSchema, createStore, createStoreWithSchema, defineEdge, defineNode, defineSubgraphProject, differentFrom, disjointWith, equivalentTo, exists, externalRef, field, fieldRef, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inSubquery, inverseOf, isExternalRefSchema, isParameterRef, limitFragment, max, metaEdge, min, narrower, notExists, notInSubquery, offsetFragment, orderByFragment, param, partOf, relatedTo, sameAs, subClassOf, sum };
13680
13759
  //# sourceMappingURL=index.js.map
13681
13760
  //# sourceMappingURL=index.js.map