@mastra/clickhouse 1.23.0-alpha.0 → 1.23.0-alpha.1
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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +210 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +211 -1
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +3 -2
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/span-query.d.ts +45 -0
- package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -0
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +8 -2
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/docs/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: mastra-clickhouse
|
|
|
3
3
|
description: Documentation for @mastra/clickhouse. Use when working with @mastra/clickhouse APIs, configuration, or implementation.
|
|
4
4
|
metadata:
|
|
5
5
|
package: "@mastra/clickhouse"
|
|
6
|
-
version: "1.23.0-alpha.
|
|
6
|
+
version: "1.23.0-alpha.1"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## When to use
|
package/dist/index.cjs
CHANGED
|
@@ -7443,6 +7443,201 @@ async function queryThreads(client, plan, timeoutMs) {
|
|
|
7443
7443
|
}) : null }
|
|
7444
7444
|
});
|
|
7445
7445
|
}
|
|
7446
|
+
/** Compile a span-row filter with exactly the same rules as trace span predicates. */
|
|
7447
|
+
function compileSpanQueryPredicate(predicate) {
|
|
7448
|
+
const parameters = new ParameterBuilder();
|
|
7449
|
+
return {
|
|
7450
|
+
sql: compileScalarPredicate(predicate, SPAN_FIELDS, parameters),
|
|
7451
|
+
params: parameters.params
|
|
7452
|
+
};
|
|
7453
|
+
}
|
|
7454
|
+
//#endregion
|
|
7455
|
+
//#region src/storage/domains/observability/v-next/span-query.ts
|
|
7456
|
+
const identityFields = [
|
|
7457
|
+
"organizationId",
|
|
7458
|
+
"resourceId",
|
|
7459
|
+
"traceId",
|
|
7460
|
+
"spanId"
|
|
7461
|
+
];
|
|
7462
|
+
const identities = identityFields.join(", ");
|
|
7463
|
+
const identitySetColumns = "isNull(organizationId), ifNull(organizationId, ''), isNull(resourceId), ifNull(resourceId, ''), traceId, spanId";
|
|
7464
|
+
const identitySetKey = `tuple(${identitySetColumns})`;
|
|
7465
|
+
const displayFields = [
|
|
7466
|
+
"parentSpanId",
|
|
7467
|
+
"name",
|
|
7468
|
+
"spanType",
|
|
7469
|
+
"entityType",
|
|
7470
|
+
"entityId",
|
|
7471
|
+
"entityName",
|
|
7472
|
+
"startedAt",
|
|
7473
|
+
"endedAt",
|
|
7474
|
+
"model",
|
|
7475
|
+
"provider",
|
|
7476
|
+
"durationMs",
|
|
7477
|
+
"status"
|
|
7478
|
+
];
|
|
7479
|
+
var Parameters = class {
|
|
7480
|
+
values = {};
|
|
7481
|
+
add(value, timestamp = false) {
|
|
7482
|
+
const name = `span_${Object.keys(this.values).length}`;
|
|
7483
|
+
this.values[name] = timestamp ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
|
|
7484
|
+
return `{${name}:${timestamp ? "DateTime64(3, 'UTC')" : typeof value === "number" ? "UInt64" : "String"}}`;
|
|
7485
|
+
}
|
|
7486
|
+
};
|
|
7487
|
+
function identityValues(row) {
|
|
7488
|
+
return [
|
|
7489
|
+
row.organizationId === null ? 1 : 0,
|
|
7490
|
+
row.organizationId ?? "",
|
|
7491
|
+
row.resourceId === null ? 1 : 0,
|
|
7492
|
+
row.resourceId ?? "",
|
|
7493
|
+
row.traceId,
|
|
7494
|
+
row.spanId
|
|
7495
|
+
];
|
|
7496
|
+
}
|
|
7497
|
+
function afterCursor(plan, parameters) {
|
|
7498
|
+
if (!plan.cursor) return "1";
|
|
7499
|
+
const cursor = plan.cursor;
|
|
7500
|
+
const timestamp = parameters.add(cursor.sortValue, true);
|
|
7501
|
+
const field = `s.${plan.orderBy.field}`;
|
|
7502
|
+
const terms = [`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${timestamp}`];
|
|
7503
|
+
const equal = [`${field} = ${timestamp}`];
|
|
7504
|
+
for (const key of identityFields) {
|
|
7505
|
+
const value = cursor[key];
|
|
7506
|
+
if (value === null) equal.push(`isNull(s.${key})`);
|
|
7507
|
+
else {
|
|
7508
|
+
const parameter = parameters.add(value);
|
|
7509
|
+
terms.push(`(${equal.join(" AND ")} AND (s.${key} > ${parameter} OR isNull(s.${key})))`);
|
|
7510
|
+
equal.push(`s.${key} = ${parameter}`);
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7513
|
+
return `(${terms.join(" OR ")})`;
|
|
7514
|
+
}
|
|
7515
|
+
function predicateFields(predicate, fields = /* @__PURE__ */ new Set()) {
|
|
7516
|
+
if (!predicate) return fields;
|
|
7517
|
+
if (predicate.type === "boolean") for (const arg of predicate.args) predicateFields(arg, fields);
|
|
7518
|
+
else if (predicate.type === "not") predicateFields(predicate.arg, fields);
|
|
7519
|
+
else fields.add(predicate.field);
|
|
7520
|
+
return fields;
|
|
7521
|
+
}
|
|
7522
|
+
function fieldExpression(field) {
|
|
7523
|
+
if (field === "model" || field === "provider") return `if(JSONType(e.attributes, '${field}') = 'String', JSONExtractString(e.attributes, '${field}'), NULL)`;
|
|
7524
|
+
if (field === "durationMs") return "dateDiff('millisecond', e.startedAt, e.endedAt)";
|
|
7525
|
+
if (field === "status") return "if(isNull(e.error), 'success', 'error')";
|
|
7526
|
+
if (field === "error") return "if(isNull(e.error), NULL, 'error')";
|
|
7527
|
+
return `e.${field}`;
|
|
7528
|
+
}
|
|
7529
|
+
function compileClickHouseSpanQuery(plan) {
|
|
7530
|
+
const parameters = new Parameters();
|
|
7531
|
+
const from = parameters.add(plan.timeRange.from, true);
|
|
7532
|
+
const to = parameters.add(plan.timeRange.to, true);
|
|
7533
|
+
const scope = [];
|
|
7534
|
+
if (plan.scope) {
|
|
7535
|
+
scope.push(`organizationId = ${parameters.add(plan.scope.organizationId)}`);
|
|
7536
|
+
if (plan.scope.resourceId !== void 0) scope.push(`resourceId = ${parameters.add(plan.scope.resourceId)}`);
|
|
7537
|
+
}
|
|
7538
|
+
const predicate = plan.where ? compileSpanQueryPredicate(plan.where) : {
|
|
7539
|
+
sql: "1",
|
|
7540
|
+
params: {}
|
|
7541
|
+
};
|
|
7542
|
+
const selectionFields = [.../* @__PURE__ */ new Set([
|
|
7543
|
+
"startedAt",
|
|
7544
|
+
"endedAt",
|
|
7545
|
+
...predicateFields(plan.where)
|
|
7546
|
+
])].filter((field) => !new Set(identityFields).has(field));
|
|
7547
|
+
const expressions = selectionFields.map(fieldExpression);
|
|
7548
|
+
const after = afterCursor(plan, parameters);
|
|
7549
|
+
return {
|
|
7550
|
+
query: `WITH candidate_ids AS (
|
|
7551
|
+
SELECT ${identitySetKey} AS identity FROM ${TABLE_SPAN_EVENTS}
|
|
7552
|
+
PREWHERE endedAt >= ${from}
|
|
7553
|
+
WHERE startedAt >= ${from} AND startedAt < ${to} ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
|
|
7554
|
+
), candidates AS (
|
|
7555
|
+
SELECT ${identities}, ${selectionFields.map((field, index) => `${expressions[index]} AS ${field}`).join(", ")}
|
|
7556
|
+
FROM ${TABLE_SPAN_EVENTS} e
|
|
7557
|
+
PREWHERE e.endedAt >= ${from}
|
|
7558
|
+
WHERE ${identitySetKey} IN (SELECT identity FROM candidate_ids) ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
|
|
7559
|
+
ORDER BY ${identities}, e.endedAt DESC
|
|
7560
|
+
LIMIT 1 BY ${identities}
|
|
7561
|
+
)
|
|
7562
|
+
SELECT ${[
|
|
7563
|
+
...identityFields,
|
|
7564
|
+
"startedAt",
|
|
7565
|
+
"endedAt"
|
|
7566
|
+
].map((field) => `s.${field}`).join(", ")}
|
|
7567
|
+
FROM candidates s WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate.sql}) AND ${after}
|
|
7568
|
+
ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
|
|
7569
|
+
LIMIT ${parameters.add(plan.limit + 1)}`,
|
|
7570
|
+
query_params: {
|
|
7571
|
+
...parameters.values,
|
|
7572
|
+
...predicate.params
|
|
7573
|
+
}
|
|
7574
|
+
};
|
|
7575
|
+
}
|
|
7576
|
+
function iso(value) {
|
|
7577
|
+
return new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
|
|
7578
|
+
}
|
|
7579
|
+
async function querySpans(client, plan, timeoutMs) {
|
|
7580
|
+
const deadline = performance.now() + _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
7581
|
+
const run = async (compiled) => {
|
|
7582
|
+
const remaining = Math.floor(deadline - performance.now());
|
|
7583
|
+
if (remaining <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
|
|
7584
|
+
return await runWithClickHouseTraceQueryTimeout(client, {
|
|
7585
|
+
timeoutMs: remaining,
|
|
7586
|
+
memoryLimitBytes: 512 * 1024 * 1024
|
|
7587
|
+
}, compiled);
|
|
7588
|
+
};
|
|
7589
|
+
const selected = (await run(compileClickHouseSpanQuery(plan))).map((row) => ({
|
|
7590
|
+
...row,
|
|
7591
|
+
startedAt: iso(row.startedAt),
|
|
7592
|
+
endedAt: iso(row.endedAt)
|
|
7593
|
+
}));
|
|
7594
|
+
const page = selected.slice(0, plan.limit);
|
|
7595
|
+
if (!page.length) return {
|
|
7596
|
+
spans: [],
|
|
7597
|
+
page: { next: null }
|
|
7598
|
+
};
|
|
7599
|
+
const payloadWhere = `tuple(${identitySetColumns}, endedAt) IN {page:Array(Tuple(UInt8, String, UInt8, String, String, String, DateTime64(3, 'UTC')))}`;
|
|
7600
|
+
const payloads = await run({
|
|
7601
|
+
query: `SELECT ${identities}, ${displayFields.map((field, index) => `data.${index + 1} AS ${field}`).join(", ")},
|
|
7602
|
+
data.${displayFields.length + 1} AS inputPreview, data.${displayFields.length + 2} AS outputPreview
|
|
7603
|
+
FROM (SELECT ${identities}, any(tuple(${displayFields.map(fieldExpression).join(", ")}, substringUTF8(input, 1, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}), substringUTF8(output, 1, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}))) AS data
|
|
7604
|
+
FROM ${TABLE_SPAN_EVENTS} e WHERE ${payloadWhere} GROUP BY ${identities})`,
|
|
7605
|
+
query_params: { page: page.map((row) => new _clickhouse_client.TupleParam([...identityValues(row), row.endedAt.replace("T", " ").replace(/Z$/, "")])) }
|
|
7606
|
+
});
|
|
7607
|
+
const metricWhere = `${identitySetKey} IN {page:Array(Tuple(UInt8, String, UInt8, String, String, String))}`;
|
|
7608
|
+
const metrics = await run({
|
|
7609
|
+
query: `SELECT ${identities}, data.1 AS name, data.2 AS estimatedCost,
|
|
7610
|
+
data.3 AS costUnit, data.4 AS allocation, data.5 AS costError
|
|
7611
|
+
FROM (SELECT ${identities}, metricId, argMax(tuple(name, estimatedCost, costUnit,
|
|
7612
|
+
JSONExtractString(costMetadata, 'allocation'), JSONHas(costMetadata, 'error')), timestamp) AS data
|
|
7613
|
+
FROM ${TABLE_METRIC_EVENTS} WHERE (${metricWhere}) AND name LIKE 'mastra_model_%'
|
|
7614
|
+
GROUP BY ${identities}, metricId)
|
|
7615
|
+
LIMIT ${_mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS + 1}`,
|
|
7616
|
+
query_params: { page: page.map((row) => new _clickhouse_client.TupleParam(identityValues(row))) }
|
|
7617
|
+
});
|
|
7618
|
+
if (metrics.length > _mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS) throw new _mastra_core_storage.TraceQueryResourceLimitError();
|
|
7619
|
+
const byIdentity = new Map(payloads.map((row) => [_mastra_core_storage.spanQueryIdentityKey(row), row]));
|
|
7620
|
+
const hydrated = page.flatMap((identity) => {
|
|
7621
|
+
const row = byIdentity.get(_mastra_core_storage.spanQueryIdentityKey(identity));
|
|
7622
|
+
if (!row) return [];
|
|
7623
|
+
const { inputPreview: _input, outputPreview: _output, ...metadata } = row;
|
|
7624
|
+
return [{
|
|
7625
|
+
...metadata,
|
|
7626
|
+
...identity,
|
|
7627
|
+
durationMs: Number(row.durationMs)
|
|
7628
|
+
}];
|
|
7629
|
+
});
|
|
7630
|
+
const result = _mastra_core_storage.buildSpanQueryResponse(plan, hydrated, payloads, metrics);
|
|
7631
|
+
const last = page.at(-1);
|
|
7632
|
+
result.page.next = selected.length > plan.limit ? _mastra_core_storage.encodeSpanQueryCursor(plan, {
|
|
7633
|
+
organizationId: last.organizationId,
|
|
7634
|
+
resourceId: last.resourceId,
|
|
7635
|
+
traceId: last.traceId,
|
|
7636
|
+
spanId: last.spanId,
|
|
7637
|
+
sortValue: last[plan.orderBy.field]
|
|
7638
|
+
}) : null;
|
|
7639
|
+
return result;
|
|
7640
|
+
}
|
|
7446
7641
|
//#endregion
|
|
7447
7642
|
//#region src/storage/domains/observability/v-next/trace-aggregate.ts
|
|
7448
7643
|
/** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
|
|
@@ -8434,6 +8629,7 @@ function buildBranchCursor(row) {
|
|
|
8434
8629
|
}
|
|
8435
8630
|
//#endregion
|
|
8436
8631
|
//#region src/storage/domains/observability/v-next/index.ts
|
|
8632
|
+
const spanQueryFeatures = typeof _mastra_core_storage.planSpanQuery === "function" ? ["span-query"] : [];
|
|
8437
8633
|
function buildSignalMigrationRequiredMessage(args) {
|
|
8438
8634
|
const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
|
|
8439
8635
|
return `
|
|
@@ -8865,6 +9061,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
8865
9061
|
"tag-discovery",
|
|
8866
9062
|
"metric-discovery",
|
|
8867
9063
|
"trace-query",
|
|
9064
|
+
...spanQueryFeatures,
|
|
8868
9065
|
"trace-query-root-duration",
|
|
8869
9066
|
"trace-query-discovery",
|
|
8870
9067
|
"thread-query",
|
|
@@ -8884,6 +9081,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
8884
9081
|
"metric-discovery",
|
|
8885
9082
|
"delta-polling",
|
|
8886
9083
|
"trace-query",
|
|
9084
|
+
...spanQueryFeatures,
|
|
8887
9085
|
"trace-query-root-duration",
|
|
8888
9086
|
"trace-query-discovery",
|
|
8889
9087
|
"thread-query",
|
|
@@ -9005,6 +9203,18 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
9005
9203
|
}, error);
|
|
9006
9204
|
}
|
|
9007
9205
|
}
|
|
9206
|
+
async querySpans(plan) {
|
|
9207
|
+
try {
|
|
9208
|
+
return await querySpans(this.#client, plan, this.#traceQueryTimeoutMs);
|
|
9209
|
+
} catch (error) {
|
|
9210
|
+
if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryCursorError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
|
|
9211
|
+
throw new _mastra_core_error.MastraError({
|
|
9212
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "QUERY_SPANS", "FAILED"),
|
|
9213
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
9214
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY
|
|
9215
|
+
}, error);
|
|
9216
|
+
}
|
|
9217
|
+
}
|
|
9008
9218
|
async queryTraces(plan) {
|
|
9009
9219
|
try {
|
|
9010
9220
|
return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs, this.#deltaCursorStrategy);
|