duron 0.3.0-beta.12 → 0.3.0-beta.13

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.
@@ -183,19 +183,28 @@ export declare class PostgresBaseAdapter<Database extends DrizzleDatabase, Conne
183
183
  protected _insertSpans(spans: InsertSpanOptions[]): Promise<number>;
184
184
  /**
185
185
  * Internal method to get spans for a job or step.
186
+ * For step queries, uses a recursive CTE to find all descendant spans.
186
187
  */
187
188
  protected _getSpans(options: GetSpansOptions): Promise<GetSpansResult>;
189
+ /**
190
+ * Get spans for a step using a recursive CTE to traverse the span hierarchy.
191
+ * This returns the step's span and all its descendant spans (children, grandchildren, etc.)
192
+ */
193
+ protected _getStepSpansRecursive(stepId: string, sortField: string, sortOrder: string, _filters?: GetSpansOptions['filters']): Promise<GetSpansResult>;
188
194
  /**
189
195
  * Internal method to delete all spans for a job.
190
196
  */
191
197
  protected _deleteSpans(options: DeleteSpansOptions): Promise<number>;
192
198
  /**
193
- * Build WHERE clause for spans queries.
194
- * When querying by jobId or stepId, we find all spans that share the same trace_id
195
- * as spans with that job/step. This includes spans from external libraries that
199
+ * Build WHERE clause for spans queries (used for job queries only).
200
+ * When querying by jobId, we find all spans that share the same trace_id
201
+ * as spans with that job. This includes spans from external libraries that
196
202
  * don't have the duron.job.id attribute but are part of the same trace.
203
+ *
204
+ * Note: Step queries are handled separately by _getStepSpansRecursive using
205
+ * a recursive CTE to traverse the span hierarchy.
197
206
  */
198
- protected _buildSpansWhereClause(jobId?: string, stepId?: string, filters?: GetSpansOptions['filters']): import("drizzle-orm").SQL<unknown> | undefined;
207
+ protected _buildSpansWhereClause(jobId?: string, _stepId?: string, filters?: GetSpansOptions['filters']): import("drizzle-orm").SQL<unknown> | undefined;
199
208
  /**
200
209
  * Send a PostgreSQL notification.
201
210
  *
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/adapters/postgres/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/adapters/postgres/base.ts"],"names":[],"mappings":"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"}
@@ -1139,19 +1139,26 @@ export class PostgresBaseAdapter extends Adapter {
1139
1139
  }
1140
1140
  /**
1141
1141
  * Internal method to get spans for a job or step.
1142
+ * For step queries, uses a recursive CTE to find all descendant spans.
1142
1143
  */
1143
1144
  async _getSpans(options) {
1144
1145
  const spansTable = this.tables.spansTable;
1145
1146
  const filters = options.filters ?? {};
1146
- // Build WHERE clause
1147
- const where = this._buildSpansWhereClause(options.jobId, options.stepId, filters);
1148
1147
  // Build sort
1149
1148
  const sortInput = options.sort ?? { field: 'startTimeUnixNano', order: 'asc' };
1150
1149
  const sortFieldMap = {
1151
- name: spansTable.name,
1152
- startTimeUnixNano: spansTable.start_time_unix_nano,
1153
- endTimeUnixNano: spansTable.end_time_unix_nano,
1150
+ name: 'name',
1151
+ startTimeUnixNano: 'start_time_unix_nano',
1152
+ endTimeUnixNano: 'end_time_unix_nano',
1154
1153
  };
1154
+ const sortField = sortFieldMap[sortInput.field];
1155
+ const sortOrder = sortInput.order === 'asc' ? 'ASC' : 'DESC';
1156
+ // For step queries, use a recursive CTE to get descendant spans
1157
+ if (options.stepId) {
1158
+ return this._getStepSpansRecursive(options.stepId, sortField, sortOrder, filters);
1159
+ }
1160
+ // Build WHERE clause for job queries
1161
+ const where = this._buildSpansWhereClause(options.jobId, undefined, filters);
1155
1162
  // Get total count
1156
1163
  const total = await this.db.$count(spansTable, where);
1157
1164
  if (!total) {
@@ -1160,8 +1167,10 @@ export class PostgresBaseAdapter extends Adapter {
1160
1167
  total: 0,
1161
1168
  };
1162
1169
  }
1163
- const sortField = sortFieldMap[sortInput.field];
1164
- const orderByClause = sortInput.order === 'asc' ? asc(sortField) : desc(sortField);
1170
+ const sortFieldColumn = sortFieldMap[sortInput.field];
1171
+ const orderByClause = sortInput.order === 'asc'
1172
+ ? asc(spansTable[sortFieldColumn])
1173
+ : desc(spansTable[sortFieldColumn]);
1165
1174
  const rows = await this.db
1166
1175
  .select({
1167
1176
  id: spansTable.id,
@@ -1196,6 +1205,61 @@ export class PostgresBaseAdapter extends Adapter {
1196
1205
  total,
1197
1206
  };
1198
1207
  }
1208
+ /**
1209
+ * Get spans for a step using a recursive CTE to traverse the span hierarchy.
1210
+ * This returns the step's span and all its descendant spans (children, grandchildren, etc.)
1211
+ */
1212
+ async _getStepSpansRecursive(stepId, sortField, sortOrder, _filters) {
1213
+ const schemaName = this.schema;
1214
+ // Use a recursive CTE to find all descendant spans
1215
+ // 1. Base case: find the span with step_id = stepId
1216
+ // 2. Recursive case: find all spans where parent_span_id = span_id of a span we've already found
1217
+ const query = sql `
1218
+ WITH RECURSIVE span_tree AS (
1219
+ -- Base case: the span(s) for the step
1220
+ SELECT * FROM ${sql.identifier(schemaName)}.spans WHERE step_id = ${stepId}::uuid
1221
+ UNION ALL
1222
+ -- Recursive case: children of spans we've found
1223
+ SELECT s.* FROM ${sql.identifier(schemaName)}.spans s
1224
+ INNER JOIN span_tree st ON s.parent_span_id = st.span_id
1225
+ )
1226
+ SELECT
1227
+ id,
1228
+ trace_id as "traceId",
1229
+ span_id as "spanId",
1230
+ parent_span_id as "parentSpanId",
1231
+ job_id as "jobId",
1232
+ step_id as "stepId",
1233
+ name,
1234
+ kind,
1235
+ start_time_unix_nano as "startTimeUnixNano",
1236
+ end_time_unix_nano as "endTimeUnixNano",
1237
+ status_code as "statusCode",
1238
+ status_message as "statusMessage",
1239
+ attributes,
1240
+ events
1241
+ FROM span_tree
1242
+ ORDER BY ${sql.identifier(sortField)} ${sql.raw(sortOrder)}
1243
+ `;
1244
+ // Raw SQL returns numeric types as strings, so we type them as such
1245
+ const rows = (await this.db.execute(query));
1246
+ // Convert types: raw SQL returns numeric types as strings
1247
+ const spans = rows.map((row) => ({
1248
+ ...row,
1249
+ // Convert id to number (bigserial comes as string from raw SQL)
1250
+ id: typeof row.id === 'string' ? Number.parseInt(row.id, 10) : row.id,
1251
+ // Convert kind and statusCode to proper types
1252
+ kind: (typeof row.kind === 'string' ? Number.parseInt(row.kind, 10) : row.kind),
1253
+ statusCode: (typeof row.statusCode === 'string' ? Number.parseInt(row.statusCode, 10) : row.statusCode),
1254
+ // Convert BigInt to string for JSON serialization
1255
+ startTimeUnixNano: row.startTimeUnixNano?.toString() ?? null,
1256
+ endTimeUnixNano: row.endTimeUnixNano?.toString() ?? null,
1257
+ }));
1258
+ return {
1259
+ spans,
1260
+ total: spans.length,
1261
+ };
1262
+ }
1199
1263
  /**
1200
1264
  * Internal method to delete all spans for a job.
1201
1265
  */
@@ -1207,12 +1271,15 @@ export class PostgresBaseAdapter extends Adapter {
1207
1271
  return result.length;
1208
1272
  }
1209
1273
  /**
1210
- * Build WHERE clause for spans queries.
1211
- * When querying by jobId or stepId, we find all spans that share the same trace_id
1212
- * as spans with that job/step. This includes spans from external libraries that
1274
+ * Build WHERE clause for spans queries (used for job queries only).
1275
+ * When querying by jobId, we find all spans that share the same trace_id
1276
+ * as spans with that job. This includes spans from external libraries that
1213
1277
  * don't have the duron.job.id attribute but are part of the same trace.
1278
+ *
1279
+ * Note: Step queries are handled separately by _getStepSpansRecursive using
1280
+ * a recursive CTE to traverse the span hierarchy.
1214
1281
  */
1215
- _buildSpansWhereClause(jobId, stepId, filters) {
1282
+ _buildSpansWhereClause(jobId, _stepId, filters) {
1216
1283
  const spansTable = this.tables.spansTable;
1217
1284
  // Build condition for finding spans by trace_id (includes external spans)
1218
1285
  let traceCondition;
@@ -1221,10 +1288,6 @@ export class PostgresBaseAdapter extends Adapter {
1221
1288
  // This includes external spans (like from AI SDK) that don't have duron.job.id
1222
1289
  traceCondition = inArray(spansTable.trace_id, this.db.select({ traceId: spansTable.trace_id }).from(spansTable).where(eq(spansTable.job_id, jobId)));
1223
1290
  }
1224
- else if (stepId) {
1225
- // Find all spans that share a trace_id with any span that has this step_id
1226
- traceCondition = inArray(spansTable.trace_id, this.db.select({ traceId: spansTable.trace_id }).from(spansTable).where(eq(spansTable.step_id, stepId)));
1227
- }
1228
1291
  return and(traceCondition, filters?.name
1229
1292
  ? Array.isArray(filters.name)
1230
1293
  ? or(...filters.name.map((n) => ilike(spansTable.name, `%${n}%`)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duron",
3
- "version": "0.3.0-beta.12",
3
+ "version": "0.3.0-beta.13",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -1418,22 +1418,30 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
1418
1418
 
1419
1419
  /**
1420
1420
  * Internal method to get spans for a job or step.
1421
+ * For step queries, uses a recursive CTE to find all descendant spans.
1421
1422
  */
1422
1423
  protected async _getSpans(options: GetSpansOptions): Promise<GetSpansResult> {
1423
1424
  const spansTable = this.tables.spansTable
1424
1425
  const filters = options.filters ?? {}
1425
1426
 
1426
- // Build WHERE clause
1427
- const where = this._buildSpansWhereClause(options.jobId, options.stepId, filters)
1428
-
1429
1427
  // Build sort
1430
1428
  const sortInput = options.sort ?? { field: 'startTimeUnixNano', order: 'asc' }
1431
- const sortFieldMap: Record<SpanSort['field'], any> = {
1432
- name: spansTable.name,
1433
- startTimeUnixNano: spansTable.start_time_unix_nano,
1434
- endTimeUnixNano: spansTable.end_time_unix_nano,
1429
+ const sortFieldMap: Record<SpanSort['field'], string> = {
1430
+ name: 'name',
1431
+ startTimeUnixNano: 'start_time_unix_nano',
1432
+ endTimeUnixNano: 'end_time_unix_nano',
1433
+ }
1434
+ const sortField = sortFieldMap[sortInput.field]
1435
+ const sortOrder = sortInput.order === 'asc' ? 'ASC' : 'DESC'
1436
+
1437
+ // For step queries, use a recursive CTE to get descendant spans
1438
+ if (options.stepId) {
1439
+ return this._getStepSpansRecursive(options.stepId, sortField, sortOrder, filters)
1435
1440
  }
1436
1441
 
1442
+ // Build WHERE clause for job queries
1443
+ const where = this._buildSpansWhereClause(options.jobId, undefined, filters)
1444
+
1437
1445
  // Get total count
1438
1446
  const total = await this.db.$count(spansTable, where)
1439
1447
  if (!total) {
@@ -1443,8 +1451,11 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
1443
1451
  }
1444
1452
  }
1445
1453
 
1446
- const sortField = sortFieldMap[sortInput.field]
1447
- const orderByClause = sortInput.order === 'asc' ? asc(sortField) : desc(sortField)
1454
+ const sortFieldColumn = sortFieldMap[sortInput.field]
1455
+ const orderByClause =
1456
+ sortInput.order === 'asc'
1457
+ ? asc(spansTable[sortFieldColumn as keyof typeof spansTable] as any)
1458
+ : desc(spansTable[sortFieldColumn as keyof typeof spansTable] as any)
1448
1459
 
1449
1460
  const rows = await this.db
1450
1461
  .select({
@@ -1483,6 +1494,89 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
1483
1494
  }
1484
1495
  }
1485
1496
 
1497
+ /**
1498
+ * Get spans for a step using a recursive CTE to traverse the span hierarchy.
1499
+ * This returns the step's span and all its descendant spans (children, grandchildren, etc.)
1500
+ */
1501
+ protected async _getStepSpansRecursive(
1502
+ stepId: string,
1503
+ sortField: string,
1504
+ sortOrder: string,
1505
+ _filters?: GetSpansOptions['filters'],
1506
+ ): Promise<GetSpansResult> {
1507
+ const schemaName = this.schema
1508
+
1509
+ // Use a recursive CTE to find all descendant spans
1510
+ // 1. Base case: find the span with step_id = stepId
1511
+ // 2. Recursive case: find all spans where parent_span_id = span_id of a span we've already found
1512
+ const query = sql`
1513
+ WITH RECURSIVE span_tree AS (
1514
+ -- Base case: the span(s) for the step
1515
+ SELECT * FROM ${sql.identifier(schemaName)}.spans WHERE step_id = ${stepId}::uuid
1516
+ UNION ALL
1517
+ -- Recursive case: children of spans we've found
1518
+ SELECT s.* FROM ${sql.identifier(schemaName)}.spans s
1519
+ INNER JOIN span_tree st ON s.parent_span_id = st.span_id
1520
+ )
1521
+ SELECT
1522
+ id,
1523
+ trace_id as "traceId",
1524
+ span_id as "spanId",
1525
+ parent_span_id as "parentSpanId",
1526
+ job_id as "jobId",
1527
+ step_id as "stepId",
1528
+ name,
1529
+ kind,
1530
+ start_time_unix_nano as "startTimeUnixNano",
1531
+ end_time_unix_nano as "endTimeUnixNano",
1532
+ status_code as "statusCode",
1533
+ status_message as "statusMessage",
1534
+ attributes,
1535
+ events
1536
+ FROM span_tree
1537
+ ORDER BY ${sql.identifier(sortField)} ${sql.raw(sortOrder)}
1538
+ `
1539
+
1540
+ // Raw SQL returns numeric types as strings, so we type them as such
1541
+ const rows = (await this.db.execute(query)) as unknown as Array<{
1542
+ id: string | number
1543
+ traceId: string
1544
+ spanId: string
1545
+ parentSpanId: string | null
1546
+ jobId: string | null
1547
+ stepId: string | null
1548
+ name: string
1549
+ kind: string | number
1550
+ startTimeUnixNano: string | bigint | null
1551
+ endTimeUnixNano: string | bigint | null
1552
+ statusCode: string | number
1553
+ statusMessage: string | null
1554
+ attributes: Record<string, any>
1555
+ events: Array<{ name: string; timeUnixNano: string; attributes?: Record<string, any> }>
1556
+ }>
1557
+
1558
+ // Convert types: raw SQL returns numeric types as strings
1559
+ const spans = rows.map((row) => ({
1560
+ ...row,
1561
+ // Convert id to number (bigserial comes as string from raw SQL)
1562
+ id: typeof row.id === 'string' ? Number.parseInt(row.id, 10) : row.id,
1563
+ // Convert kind and statusCode to proper types
1564
+ kind: (typeof row.kind === 'string' ? Number.parseInt(row.kind, 10) : row.kind) as 0 | 1 | 2 | 3 | 4,
1565
+ statusCode: (typeof row.statusCode === 'string' ? Number.parseInt(row.statusCode, 10) : row.statusCode) as
1566
+ | 0
1567
+ | 1
1568
+ | 2,
1569
+ // Convert BigInt to string for JSON serialization
1570
+ startTimeUnixNano: row.startTimeUnixNano?.toString() ?? null,
1571
+ endTimeUnixNano: row.endTimeUnixNano?.toString() ?? null,
1572
+ }))
1573
+
1574
+ return {
1575
+ spans,
1576
+ total: spans.length,
1577
+ }
1578
+ }
1579
+
1486
1580
  /**
1487
1581
  * Internal method to delete all spans for a job.
1488
1582
  */
@@ -1496,12 +1590,15 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
1496
1590
  }
1497
1591
 
1498
1592
  /**
1499
- * Build WHERE clause for spans queries.
1500
- * When querying by jobId or stepId, we find all spans that share the same trace_id
1501
- * as spans with that job/step. This includes spans from external libraries that
1593
+ * Build WHERE clause for spans queries (used for job queries only).
1594
+ * When querying by jobId, we find all spans that share the same trace_id
1595
+ * as spans with that job. This includes spans from external libraries that
1502
1596
  * don't have the duron.job.id attribute but are part of the same trace.
1597
+ *
1598
+ * Note: Step queries are handled separately by _getStepSpansRecursive using
1599
+ * a recursive CTE to traverse the span hierarchy.
1503
1600
  */
1504
- protected _buildSpansWhereClause(jobId?: string, stepId?: string, filters?: GetSpansOptions['filters']) {
1601
+ protected _buildSpansWhereClause(jobId?: string, _stepId?: string, filters?: GetSpansOptions['filters']) {
1505
1602
  const spansTable = this.tables.spansTable
1506
1603
 
1507
1604
  // Build condition for finding spans by trace_id (includes external spans)
@@ -1514,12 +1611,6 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
1514
1611
  spansTable.trace_id,
1515
1612
  this.db.select({ traceId: spansTable.trace_id }).from(spansTable).where(eq(spansTable.job_id, jobId)),
1516
1613
  )
1517
- } else if (stepId) {
1518
- // Find all spans that share a trace_id with any span that has this step_id
1519
- traceCondition = inArray(
1520
- spansTable.trace_id,
1521
- this.db.select({ traceId: spansTable.trace_id }).from(spansTable).where(eq(spansTable.step_id, stepId)),
1522
- )
1523
1614
  }
1524
1615
 
1525
1616
  return and(