@mastra/clickhouse 1.0.0-beta.1 → 1.0.0-beta.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @mastra/clickhouse
2
2
 
3
+ ## 1.0.0-beta.2
4
+
5
+ ### Patch Changes
6
+
7
+ - feat(storage): support querying messages from multiple threads ([#10663](https://github.com/mastra-ai/mastra/pull/10663))
8
+ - Fixed TypeScript errors where `threadId: string | string[]` was being passed to places expecting `Scalar` type
9
+ - Added proper multi-thread support for `listMessages` across all adapters when `threadId` is an array
10
+ - Updated `_getIncludedMessages` to look up message threadId by ID (since message IDs are globally unique)
11
+ - **upstash**: Added `msg-idx:{messageId}` index for O(1) message lookups (backwards compatible with fallback to scan for old messages, with automatic backfill)
12
+
13
+ - fix: ensure score responses match saved payloads for Mastra Stores. ([#10557](https://github.com/mastra-ai/mastra/pull/10557))
14
+
15
+ - Unify transformScoreRow functions across storage adapters ([#10648](https://github.com/mastra-ai/mastra/pull/10648))
16
+
17
+ Added a unified `transformScoreRow` function in `@mastra/core/storage` that provides schema-driven row transformation for score data. This eliminates code duplication across 10 storage adapters while maintaining store-specific behavior through configurable options:
18
+ - `preferredTimestampFields`: Preferred source fields for timestamps (PostgreSQL, Cloudflare D1)
19
+ - `convertTimestamps`: Convert timestamp strings to Date objects (MSSQL, MongoDB, ClickHouse)
20
+ - `nullValuePattern`: Skip values matching pattern (ClickHouse's `'_null_'`)
21
+ - `fieldMappings`: Map source column names to schema fields (LibSQL's `additionalLLMContext`)
22
+
23
+ Each store adapter now uses the unified function with appropriate options, reducing ~200 lines of duplicate transformation logic while ensuring consistent behavior across all storage backends.
24
+
25
+ - Updated dependencies [[`ac0d2f4`](https://github.com/mastra-ai/mastra/commit/ac0d2f4ff8831f72c1c66c2be809706d17f65789), [`1a0d3fc`](https://github.com/mastra-ai/mastra/commit/1a0d3fc811482c9c376cdf79ee615c23bae9b2d6), [`85a628b`](https://github.com/mastra-ai/mastra/commit/85a628b1224a8f64cd82ea7f033774bf22df7a7e), [`c237233`](https://github.com/mastra-ai/mastra/commit/c23723399ccedf7f5744b3f40997b79246bfbe64), [`15f9e21`](https://github.com/mastra-ai/mastra/commit/15f9e216177201ea6e3f6d0bfb063fcc0953444f), [`ff94dea`](https://github.com/mastra-ai/mastra/commit/ff94dea935f4e34545c63bcb6c29804732698809), [`5b2ff46`](https://github.com/mastra-ai/mastra/commit/5b2ff4651df70c146523a7fca773f8eb0a2272f8), [`db41688`](https://github.com/mastra-ai/mastra/commit/db4168806d007417e2e60b4f68656dca4e5f40c9), [`5ca599d`](https://github.com/mastra-ai/mastra/commit/5ca599d0bb59a1595f19f58473fcd67cc71cef58), [`bff1145`](https://github.com/mastra-ai/mastra/commit/bff114556b3cbadad9b2768488708f8ad0e91475), [`5c8ca24`](https://github.com/mastra-ai/mastra/commit/5c8ca247094e0cc2cdbd7137822fb47241f86e77), [`e191844`](https://github.com/mastra-ai/mastra/commit/e1918444ca3f80e82feef1dad506cd4ec6e2875f), [`22553f1`](https://github.com/mastra-ai/mastra/commit/22553f11c63ee5e966a9c034a349822249584691), [`7237163`](https://github.com/mastra-ai/mastra/commit/72371635dbf96a87df4b073cc48fc655afbdce3d), [`2500740`](https://github.com/mastra-ai/mastra/commit/2500740ea23da067d6e50ec71c625ab3ce275e64), [`873ecbb`](https://github.com/mastra-ai/mastra/commit/873ecbb517586aa17d2f1e99283755b3ebb2863f), [`4f9bbe5`](https://github.com/mastra-ai/mastra/commit/4f9bbe5968f42c86f4930b8193de3c3c17e5bd36), [`02e51fe`](https://github.com/mastra-ai/mastra/commit/02e51feddb3d4155cfbcc42624fd0d0970d032c0), [`8f3fa3a`](https://github.com/mastra-ai/mastra/commit/8f3fa3a652bb77da092f913ec51ae46e3a7e27dc), [`cd29ad2`](https://github.com/mastra-ai/mastra/commit/cd29ad23a255534e8191f249593849ed29160886), [`bdf4d8c`](https://github.com/mastra-ai/mastra/commit/bdf4d8cdc656d8a2c21d81834bfa3bfa70f56c16), [`854e3da`](https://github.com/mastra-ai/mastra/commit/854e3dad5daac17a91a20986399d3a51f54bf68b), [`ce18d38`](https://github.com/mastra-ai/mastra/commit/ce18d38678c65870350d123955014a8432075fd9), [`cccf9c8`](https://github.com/mastra-ai/mastra/commit/cccf9c8b2d2dfc1a5e63919395b83d78c89682a0), [`61a5705`](https://github.com/mastra-ai/mastra/commit/61a570551278b6743e64243b3ce7d73de915ca8a), [`db70a48`](https://github.com/mastra-ai/mastra/commit/db70a48aeeeeb8e5f92007e8ede52c364ce15287), [`f0fdc14`](https://github.com/mastra-ai/mastra/commit/f0fdc14ee233d619266b3d2bbdeea7d25cfc6d13), [`db18bc9`](https://github.com/mastra-ai/mastra/commit/db18bc9c3825e2c1a0ad9a183cc9935f6691bfa1), [`9b37b56`](https://github.com/mastra-ai/mastra/commit/9b37b565e1f2a76c24f728945cc740c2b09be9da), [`41a23c3`](https://github.com/mastra-ai/mastra/commit/41a23c32f9877d71810f37e24930515df2ff7a0f), [`5d171ad`](https://github.com/mastra-ai/mastra/commit/5d171ad9ef340387276b77c2bb3e83e83332d729), [`f03ae60`](https://github.com/mastra-ai/mastra/commit/f03ae60500fe350c9d828621006cdafe1975fdd8), [`d1e74a0`](https://github.com/mastra-ai/mastra/commit/d1e74a0a293866dece31022047f5dbab65a304d0), [`39e7869`](https://github.com/mastra-ai/mastra/commit/39e7869bc7d0ee391077ce291474d8a84eedccff), [`5761926`](https://github.com/mastra-ai/mastra/commit/57619260c4a2cdd598763abbacd90de594c6bc76), [`c900fdd`](https://github.com/mastra-ai/mastra/commit/c900fdd504c41348efdffb205cfe80d48c38fa33), [`604a79f`](https://github.com/mastra-ai/mastra/commit/604a79fecf276e26a54a3fe01bb94e65315d2e0e), [`887f0b4`](https://github.com/mastra-ai/mastra/commit/887f0b4746cdbd7cb7d6b17ac9f82aeb58037ea5), [`2562143`](https://github.com/mastra-ai/mastra/commit/256214336b4faa78646c9c1776612393790d8784), [`ef11a61`](https://github.com/mastra-ai/mastra/commit/ef11a61920fa0ed08a5b7ceedd192875af119749)]:
26
+ - @mastra/core@1.0.0-beta.6
27
+
3
28
  ## 1.0.0-beta.1
4
29
 
5
30
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -127,6 +127,8 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
127
127
  }
128
128
  async listMessages(args) {
129
129
  const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
130
+ const rawThreadIds = Array.isArray(threadId) ? threadId : [threadId];
131
+ const threadIds = rawThreadIds.filter((id) => id !== void 0 && id !== null).map((id) => (typeof id === "string" ? id : String(id)).trim()).filter((id) => id.length > 0);
130
132
  if (page < 0) {
131
133
  throw new error.MastraError(
132
134
  {
@@ -138,20 +140,21 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
138
140
  new Error("page must be >= 0")
139
141
  );
140
142
  }
141
- if (!threadId.trim()) {
143
+ if (threadIds.length === 0) {
142
144
  throw new error.MastraError(
143
145
  {
144
146
  id: "STORAGE_CLICKHOUSE_LIST_MESSAGES_INVALID_THREAD_ID",
145
147
  domain: error.ErrorDomain.STORAGE,
146
148
  category: error.ErrorCategory.THIRD_PARTY,
147
- details: { threadId }
149
+ details: { threadId: Array.isArray(threadId) ? JSON.stringify(threadId) : String(threadId) }
148
150
  },
149
- new Error("threadId must be a non-empty string")
151
+ new Error("threadId must be a non-empty string or array of non-empty strings")
150
152
  );
151
153
  }
152
154
  const perPageForQuery = storage.normalizePerPage(perPageInput, 40);
153
155
  const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPageForQuery);
154
156
  try {
157
+ const threadCondition = threadIds.length === 1 ? `thread_id = {threadId0:String}` : `thread_id IN (${threadIds.map((_, i) => `{threadId${i}:String}`).join(", ")})`;
155
158
  let dataQuery = `
156
159
  SELECT
157
160
  id,
@@ -162,9 +165,12 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
162
165
  thread_id AS "threadId",
163
166
  resourceId
164
167
  FROM ${storage.TABLE_MESSAGES}
165
- WHERE thread_id = {threadId:String}
168
+ WHERE ${threadCondition}
166
169
  `;
167
- const dataParams = { threadId };
170
+ const dataParams = {};
171
+ threadIds.forEach((tid, i) => {
172
+ dataParams[`threadId${i}`] = tid;
173
+ });
168
174
  if (resourceId) {
169
175
  dataQuery += ` AND resourceId = {resourceId:String}`;
170
176
  dataParams.resourceId = resourceId;
@@ -199,8 +205,11 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
199
205
  const rows = await result.json();
200
206
  const paginatedMessages = transformRows(rows.data);
201
207
  const paginatedCount = paginatedMessages.length;
202
- let countQuery = `SELECT count() as total FROM ${storage.TABLE_MESSAGES} WHERE thread_id = {threadId:String}`;
203
- const countParams = { threadId };
208
+ let countQuery = `SELECT count() as total FROM ${storage.TABLE_MESSAGES} WHERE ${threadCondition}`;
209
+ const countParams = {};
210
+ threadIds.forEach((tid, i) => {
211
+ countParams[`threadId${i}`] = tid;
212
+ });
204
213
  if (resourceId) {
205
214
  countQuery += ` AND resourceId = {resourceId:String}`;
206
215
  countParams.resourceId = resourceId;
@@ -239,12 +248,25 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
239
248
  const messageIds = new Set(paginatedMessages.map((m) => m.id));
240
249
  let includeMessages = [];
241
250
  if (include && include.length > 0) {
251
+ const includesNeedingThread = include.filter((inc) => !inc.threadId);
252
+ const threadByMessageId = /* @__PURE__ */ new Map();
253
+ if (includesNeedingThread.length > 0) {
254
+ const { messages: includeLookup } = await this.listMessagesById({
255
+ messageIds: includesNeedingThread.map((inc) => inc.id)
256
+ });
257
+ for (const msg of includeLookup) {
258
+ if (msg.threadId) {
259
+ threadByMessageId.set(msg.id, msg.threadId);
260
+ }
261
+ }
262
+ }
242
263
  const unionQueries = [];
243
264
  const params = [];
244
265
  let paramIdx = 1;
245
266
  for (const inc of include) {
246
267
  const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
247
- const searchId = inc.threadId || threadId;
268
+ const searchThreadId = inc.threadId ?? threadByMessageId.get(id);
269
+ if (!searchThreadId) continue;
248
270
  unionQueries.push(`
249
271
  SELECT * FROM (
250
272
  WITH numbered_messages AS (
@@ -266,31 +288,33 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
266
288
  ) AS query_${paramIdx}
267
289
  `);
268
290
  params.push(
269
- { [`var_thread_id_${paramIdx}`]: searchId },
291
+ { [`var_thread_id_${paramIdx}`]: searchThreadId },
270
292
  { [`var_include_id_${paramIdx}`]: id },
271
293
  { [`var_withPreviousMessages_${paramIdx}`]: withPreviousMessages },
272
294
  { [`var_withNextMessages_${paramIdx}`]: withNextMessages }
273
295
  );
274
296
  paramIdx++;
275
297
  }
276
- const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
277
- const mergedParams = params.reduce((acc, paramObj) => ({ ...acc, ...paramObj }), {});
278
- const includeResult = await this.client.query({
279
- query: finalQuery,
280
- query_params: mergedParams,
281
- clickhouse_settings: {
282
- date_time_input_format: "best_effort",
283
- date_time_output_format: "iso",
284
- use_client_time_zone: 1,
285
- output_format_json_quote_64bit_integers: 0
286
- }
287
- });
288
- const includeRows = await includeResult.json();
289
- includeMessages = transformRows(includeRows.data);
290
- for (const includeMsg of includeMessages) {
291
- if (!messageIds.has(includeMsg.id)) {
292
- paginatedMessages.push(includeMsg);
293
- messageIds.add(includeMsg.id);
298
+ if (unionQueries.length > 0) {
299
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
300
+ const mergedParams = params.reduce((acc, paramObj) => ({ ...acc, ...paramObj }), {});
301
+ const includeResult = await this.client.query({
302
+ query: finalQuery,
303
+ query_params: mergedParams,
304
+ clickhouse_settings: {
305
+ date_time_input_format: "best_effort",
306
+ date_time_output_format: "iso",
307
+ use_client_time_zone: 1,
308
+ output_format_json_quote_64bit_integers: 0
309
+ }
310
+ });
311
+ const includeRows = await includeResult.json();
312
+ includeMessages = transformRows(includeRows.data);
313
+ for (const includeMsg of includeMessages) {
314
+ if (!messageIds.has(includeMsg.id)) {
315
+ paginatedMessages.push(includeMsg);
316
+ messageIds.add(includeMsg.id);
317
+ }
294
318
  }
295
319
  }
296
320
  }
@@ -308,7 +332,10 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
308
332
  }
309
333
  return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
310
334
  });
311
- const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
335
+ const threadIdSet = new Set(threadIds);
336
+ const returnedThreadMessageIds = new Set(
337
+ finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
338
+ );
312
339
  const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
313
340
  const hasMore = perPageForResponse === false ? false : allThreadMessagesReturned ? false : offset + paginatedCount < total;
314
341
  return {
@@ -325,7 +352,7 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
325
352
  domain: error.ErrorDomain.STORAGE,
326
353
  category: error.ErrorCategory.THIRD_PARTY,
327
354
  details: {
328
- threadId,
355
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
329
356
  resourceId: resourceId ?? ""
330
357
  }
331
358
  },
@@ -1451,30 +1478,15 @@ var ScoresStorageClickhouse = class extends storage.ScoresStorage {
1451
1478
  this.client = client;
1452
1479
  this.operations = operations;
1453
1480
  }
1481
+ /**
1482
+ * ClickHouse-specific score row transformation.
1483
+ * Converts timestamps to Date objects and filters out '_null_' values.
1484
+ */
1454
1485
  transformScoreRow(row) {
1455
- const scorer = storage.safelyParseJSON(row.scorer);
1456
- const preprocessStepResult = storage.safelyParseJSON(row.preprocessStepResult);
1457
- const analyzeStepResult = storage.safelyParseJSON(row.analyzeStepResult);
1458
- const metadata = storage.safelyParseJSON(row.metadata);
1459
- const input = storage.safelyParseJSON(row.input);
1460
- const output = storage.safelyParseJSON(row.output);
1461
- const additionalContext = storage.safelyParseJSON(row.additionalContext);
1462
- const requestContext = storage.safelyParseJSON(row.requestContext);
1463
- const entity = storage.safelyParseJSON(row.entity);
1464
- return {
1465
- ...row,
1466
- scorer,
1467
- preprocessStepResult,
1468
- analyzeStepResult,
1469
- metadata,
1470
- input,
1471
- output,
1472
- additionalContext,
1473
- requestContext,
1474
- entity,
1475
- createdAt: new Date(row.createdAt),
1476
- updatedAt: new Date(row.updatedAt)
1477
- };
1486
+ return storage.transformScoreRow(row, {
1487
+ convertTimestamps: true,
1488
+ nullValuePattern: "_null_"
1489
+ });
1478
1490
  }
1479
1491
  async getScoreById({ id }) {
1480
1492
  try {
@@ -1523,9 +1535,15 @@ var ScoresStorageClickhouse = class extends storage.ScoresStorage {
1523
1535
  );
1524
1536
  }
1525
1537
  try {
1526
- const record = {
1527
- ...parsedScore
1528
- };
1538
+ const record = {};
1539
+ for (const key of Object.keys(storage.SCORERS_SCHEMA)) {
1540
+ const value = parsedScore[key];
1541
+ if (key === "createdAt" || key === "updatedAt") {
1542
+ record[key] = (/* @__PURE__ */ new Date()).toISOString();
1543
+ continue;
1544
+ }
1545
+ record[key] = value === void 0 || value === null ? "_null_" : value;
1546
+ }
1529
1547
  await this.client.insert({
1530
1548
  table: storage.TABLE_SCORERS,
1531
1549
  values: [record],