@mastra/cloudflare-d1 1.0.1 → 1.0.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,31 @@
1
1
  # @mastra/cloudflare-d1
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Added resilient column handling to insert and update operations. Unknown columns in records are now silently dropped instead of causing SQL errors, ensuring forward compatibility when newer domain packages add fields that haven't been migrated yet. ([#14021](https://github.com/mastra-ai/mastra/pull/14021))
8
+
9
+ For example, calling `db.insert({ tableName, record: { id: '1', title: 'Hello', futureField: 'value' } })` will silently ignore `futureField` if it doesn't exist in the database table, rather than throwing. The same applies to `update` — unknown fields in the data payload are dropped before building the SQL statement.
10
+
11
+ - Fixed slow semantic recall in the libsql, Cloudflare D1, and ClickHouse storage adapters. Recall performance no longer degrades as threads grow larger. (Fixes #11702) ([#14022](https://github.com/mastra-ai/mastra/pull/14022))
12
+
13
+ - Updated dependencies [[`4f71b43`](https://github.com/mastra-ai/mastra/commit/4f71b436a4a6b8839842d8da47b57b84509af56c), [`a070277`](https://github.com/mastra-ai/mastra/commit/a07027766ce195ba74d0783116d894cbab25d44c), [`b628b91`](https://github.com/mastra-ai/mastra/commit/b628b9128b372c0f54214d902b07279f03443900), [`332c014`](https://github.com/mastra-ai/mastra/commit/332c014e076b81edf7fe45b58205882726415e90), [`6b63153`](https://github.com/mastra-ai/mastra/commit/6b63153878ea841c0f4ce632ba66bb33e57e9c1b), [`4246e34`](https://github.com/mastra-ai/mastra/commit/4246e34cec9c26636d0965942268e6d07c346671), [`b8837ee`](https://github.com/mastra-ai/mastra/commit/b8837ee77e2e84197609762bfabd8b3da326d30c), [`866cc2c`](https://github.com/mastra-ai/mastra/commit/866cc2cb1f0e3b314afab5194f69477fada745d1), [`5d950f7`](https://github.com/mastra-ai/mastra/commit/5d950f7bf426a215a1808f0abef7de5c8336ba1c), [`28c85b1`](https://github.com/mastra-ai/mastra/commit/28c85b184fc32b40f7f160483c982da6d388ecbd), [`e9a08fb`](https://github.com/mastra-ai/mastra/commit/e9a08fbef1ada7e50e961e2f54f55e8c10b4a45c), [`1d0a8a8`](https://github.com/mastra-ai/mastra/commit/1d0a8a8acf33203d5744fc429b090ad8598aa8ed), [`631ffd8`](https://github.com/mastra-ai/mastra/commit/631ffd82fed108648b448b28e6a90e38c5f53bf5), [`6bcbf8a`](https://github.com/mastra-ai/mastra/commit/6bcbf8a6774d5a53b21d61db8a45ce2593ca1616), [`aae2295`](https://github.com/mastra-ai/mastra/commit/aae2295838a2d329ad6640829e87934790ffe5b8), [`aa61f29`](https://github.com/mastra-ai/mastra/commit/aa61f29ff8095ce46a4ae16e46c4d8c79b2b685b), [`7ff3714`](https://github.com/mastra-ai/mastra/commit/7ff37148515439bb3be009a60e02c3e363299760), [`18c3a90`](https://github.com/mastra-ai/mastra/commit/18c3a90c9e48cf69500e308affeb8eba5860b2af), [`41d79a1`](https://github.com/mastra-ai/mastra/commit/41d79a14bd8cb6de1e2565fd0a04786bae2f211b), [`f35487b`](https://github.com/mastra-ai/mastra/commit/f35487bb2d46c636e22aa71d90025613ae38235a), [`6dc2192`](https://github.com/mastra-ai/mastra/commit/6dc21921aef0f0efab15cd0805fa3d18f277a76f), [`eeb3a3f`](https://github.com/mastra-ai/mastra/commit/eeb3a3f43aca10cf49479eed2a84b7d9ecea02ba), [`e673376`](https://github.com/mastra-ai/mastra/commit/e6733763ad1321aa7e5ae15096b9c2104f93b1f3), [`05f8d90`](https://github.com/mastra-ai/mastra/commit/05f8d9009290ce6aa03428b3add635268615db85), [`b2204c9`](https://github.com/mastra-ai/mastra/commit/b2204c98a42848bbfb6f0440f005dc2b6354f1cd), [`a1bf1e3`](https://github.com/mastra-ai/mastra/commit/a1bf1e385ed4c0ef6f11b56c5887442970d127f2), [`b6f647a`](https://github.com/mastra-ai/mastra/commit/b6f647ae2388e091f366581595feb957e37d5b40), [`0c57b8b`](https://github.com/mastra-ai/mastra/commit/0c57b8b0a69a97b5a4ae3f79be6c610f29f3cf7b), [`b081f27`](https://github.com/mastra-ai/mastra/commit/b081f272cf411716e1d6bd72ceac4bcee2657b19), [`4b8da97`](https://github.com/mastra-ai/mastra/commit/4b8da97a5ce306e97869df6c39535d9069e563db), [`0c09eac`](https://github.com/mastra-ai/mastra/commit/0c09eacb1926f64cfdc9ae5c6d63385cf8c9f72c), [`6b9b93d`](https://github.com/mastra-ai/mastra/commit/6b9b93d6f459d1ba6e36f163abf62a085ddb3d64), [`31b6067`](https://github.com/mastra-ai/mastra/commit/31b6067d0cc3ab10e1b29c36147f3b5266bc714a), [`797ac42`](https://github.com/mastra-ai/mastra/commit/797ac4276de231ad2d694d9aeca75980f6cd0419), [`0bc289e`](https://github.com/mastra-ai/mastra/commit/0bc289e2d476bf46c5b91c21969e8d0c6864691c), [`9b75a06`](https://github.com/mastra-ai/mastra/commit/9b75a06e53ebb0b950ba7c1e83a0142047185f46), [`4c3a1b1`](https://github.com/mastra-ai/mastra/commit/4c3a1b122ea083e003d71092f30f3b31680b01c0), [`256df35`](https://github.com/mastra-ai/mastra/commit/256df3571d62beb3ad4971faa432927cc140e603), [`85cc3b3`](https://github.com/mastra-ai/mastra/commit/85cc3b3b6f32ae4b083c26498f50d5b250ba944b), [`97ea28c`](https://github.com/mastra-ai/mastra/commit/97ea28c746e9e4147d56047bbb1c4a92417a3fec), [`d567299`](https://github.com/mastra-ai/mastra/commit/d567299cf81e02bd9d5221d4bc05967d6c224161), [`716ffe6`](https://github.com/mastra-ai/mastra/commit/716ffe68bed81f7c2690bc8581b9e140f7bf1c3d), [`8296332`](https://github.com/mastra-ai/mastra/commit/8296332de21c16e3dfc3d0b2d615720a6dc88f2f), [`4df2116`](https://github.com/mastra-ai/mastra/commit/4df211619dd922c047d396ca41cd7027c8c4c8e7), [`2219c1a`](https://github.com/mastra-ai/mastra/commit/2219c1acbd21da116da877f0036ffb985a9dd5a3), [`17c4145`](https://github.com/mastra-ai/mastra/commit/17c4145166099354545582335b5252bdfdfd908b)]:
14
+ - @mastra/core@1.11.0
15
+
16
+ ## 1.0.2-alpha.0
17
+
18
+ ### Patch Changes
19
+
20
+ - Added resilient column handling to insert and update operations. Unknown columns in records are now silently dropped instead of causing SQL errors, ensuring forward compatibility when newer domain packages add fields that haven't been migrated yet. ([#14021](https://github.com/mastra-ai/mastra/pull/14021))
21
+
22
+ For example, calling `db.insert({ tableName, record: { id: '1', title: 'Hello', futureField: 'value' } })` will silently ignore `futureField` if it doesn't exist in the database table, rather than throwing. The same applies to `update` — unknown fields in the data payload are dropped before building the SQL statement.
23
+
24
+ - Fixed slow semantic recall in the libsql, Cloudflare D1, and ClickHouse storage adapters. Recall performance no longer degrades as threads grow larger. (Fixes #11702) ([#14022](https://github.com/mastra-ai/mastra/pull/14022))
25
+
26
+ - Updated dependencies [[`4f71b43`](https://github.com/mastra-ai/mastra/commit/4f71b436a4a6b8839842d8da47b57b84509af56c), [`a070277`](https://github.com/mastra-ai/mastra/commit/a07027766ce195ba74d0783116d894cbab25d44c), [`b628b91`](https://github.com/mastra-ai/mastra/commit/b628b9128b372c0f54214d902b07279f03443900), [`332c014`](https://github.com/mastra-ai/mastra/commit/332c014e076b81edf7fe45b58205882726415e90), [`6b63153`](https://github.com/mastra-ai/mastra/commit/6b63153878ea841c0f4ce632ba66bb33e57e9c1b), [`4246e34`](https://github.com/mastra-ai/mastra/commit/4246e34cec9c26636d0965942268e6d07c346671), [`b8837ee`](https://github.com/mastra-ai/mastra/commit/b8837ee77e2e84197609762bfabd8b3da326d30c), [`5d950f7`](https://github.com/mastra-ai/mastra/commit/5d950f7bf426a215a1808f0abef7de5c8336ba1c), [`28c85b1`](https://github.com/mastra-ai/mastra/commit/28c85b184fc32b40f7f160483c982da6d388ecbd), [`e9a08fb`](https://github.com/mastra-ai/mastra/commit/e9a08fbef1ada7e50e961e2f54f55e8c10b4a45c), [`631ffd8`](https://github.com/mastra-ai/mastra/commit/631ffd82fed108648b448b28e6a90e38c5f53bf5), [`aae2295`](https://github.com/mastra-ai/mastra/commit/aae2295838a2d329ad6640829e87934790ffe5b8), [`aa61f29`](https://github.com/mastra-ai/mastra/commit/aa61f29ff8095ce46a4ae16e46c4d8c79b2b685b), [`7ff3714`](https://github.com/mastra-ai/mastra/commit/7ff37148515439bb3be009a60e02c3e363299760), [`41d79a1`](https://github.com/mastra-ai/mastra/commit/41d79a14bd8cb6de1e2565fd0a04786bae2f211b), [`e673376`](https://github.com/mastra-ai/mastra/commit/e6733763ad1321aa7e5ae15096b9c2104f93b1f3), [`b2204c9`](https://github.com/mastra-ai/mastra/commit/b2204c98a42848bbfb6f0440f005dc2b6354f1cd), [`a1bf1e3`](https://github.com/mastra-ai/mastra/commit/a1bf1e385ed4c0ef6f11b56c5887442970d127f2), [`b6f647a`](https://github.com/mastra-ai/mastra/commit/b6f647ae2388e091f366581595feb957e37d5b40), [`0c57b8b`](https://github.com/mastra-ai/mastra/commit/0c57b8b0a69a97b5a4ae3f79be6c610f29f3cf7b), [`b081f27`](https://github.com/mastra-ai/mastra/commit/b081f272cf411716e1d6bd72ceac4bcee2657b19), [`0c09eac`](https://github.com/mastra-ai/mastra/commit/0c09eacb1926f64cfdc9ae5c6d63385cf8c9f72c), [`6b9b93d`](https://github.com/mastra-ai/mastra/commit/6b9b93d6f459d1ba6e36f163abf62a085ddb3d64), [`31b6067`](https://github.com/mastra-ai/mastra/commit/31b6067d0cc3ab10e1b29c36147f3b5266bc714a), [`797ac42`](https://github.com/mastra-ai/mastra/commit/797ac4276de231ad2d694d9aeca75980f6cd0419), [`0bc289e`](https://github.com/mastra-ai/mastra/commit/0bc289e2d476bf46c5b91c21969e8d0c6864691c), [`9b75a06`](https://github.com/mastra-ai/mastra/commit/9b75a06e53ebb0b950ba7c1e83a0142047185f46), [`4c3a1b1`](https://github.com/mastra-ai/mastra/commit/4c3a1b122ea083e003d71092f30f3b31680b01c0), [`85cc3b3`](https://github.com/mastra-ai/mastra/commit/85cc3b3b6f32ae4b083c26498f50d5b250ba944b), [`97ea28c`](https://github.com/mastra-ai/mastra/commit/97ea28c746e9e4147d56047bbb1c4a92417a3fec), [`d567299`](https://github.com/mastra-ai/mastra/commit/d567299cf81e02bd9d5221d4bc05967d6c224161), [`716ffe6`](https://github.com/mastra-ai/mastra/commit/716ffe68bed81f7c2690bc8581b9e140f7bf1c3d), [`8296332`](https://github.com/mastra-ai/mastra/commit/8296332de21c16e3dfc3d0b2d615720a6dc88f2f), [`4df2116`](https://github.com/mastra-ai/mastra/commit/4df211619dd922c047d396ca41cd7027c8c4c8e7), [`2219c1a`](https://github.com/mastra-ai/mastra/commit/2219c1acbd21da116da877f0036ffb985a9dd5a3), [`17c4145`](https://github.com/mastra-ai/mastra/commit/17c4145166099354545582335b5252bdfdfd908b)]:
27
+ - @mastra/core@1.11.0-alpha.0
28
+
3
29
  ## 1.0.1
4
30
 
5
31
  ### Patch Changes
@@ -3,7 +3,7 @@ name: mastra-cloudflare-d1
3
3
  description: Documentation for @mastra/cloudflare-d1. Use when working with @mastra/cloudflare-d1 APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/cloudflare-d1"
6
- version: "1.0.1"
6
+ version: "1.0.2"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -16,7 +16,7 @@ Read the individual reference documents for detailed explanations and code examp
16
16
 
17
17
  ### Reference
18
18
 
19
- - [Reference: Cloudflare D1 Storage](references/reference-storage-cloudflare-d1.md) - Documentation for the Cloudflare D1 SQL storage implementation in Mastra.
19
+ - [Reference: Cloudflare D1 storage](references/reference-storage-cloudflare-d1.md) - Documentation for the Cloudflare D1 SQL storage implementation in Mastra.
20
20
 
21
21
 
22
22
  Read [assets/SOURCE_MAP.json](assets/SOURCE_MAP.json) for source code references.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.1",
2
+ "version": "1.0.2",
3
3
  "package": "@mastra/cloudflare-d1",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -1,8 +1,8 @@
1
- # Cloudflare D1 Storage
1
+ # Cloudflare D1 storage
2
2
 
3
3
  The Cloudflare D1 storage implementation provides a serverless SQL database solution using Cloudflare D1, supporting relational operations and transactional consistency.
4
4
 
5
- > **Observability Not Supported:** Cloudflare D1 storage **does not support the observability domain**. Traces from the `DefaultExporter` cannot be persisted to D1, and Mastra Studio's observability features won't work with D1 as your only storage provider. To enable observability, use [composite storage](https://mastra.ai/reference/storage/composite) to route observability data to a supported provider like ClickHouse or PostgreSQL.
5
+ > **Observability Not Supported:** Cloudflare D1 storage **doesn't support the observability domain**. Traces from the `DefaultExporter` can't be persisted to D1, and Mastra Studio's observability features won't work with D1 as your only storage provider. To enable observability, use [composite storage](https://mastra.ai/reference/storage/composite) to route observability data to a supported provider like ClickHouse or PostgreSQL.
6
6
 
7
7
  > **Row Size Limit:** Cloudflare D1 enforces a **1 MiB maximum row size**. This limit can be exceeded when storing messages with base64-encoded attachments such as images. See [Handling large attachments](https://mastra.ai/docs/memory/storage) for workarounds including uploading attachments to external storage.
8
8
 
@@ -85,7 +85,7 @@ export default {
85
85
  > - The **property name** (`D1Database`) must match the `binding` name in your `wrangler.toml`
86
86
  > - The **type** (`: D1Database`) is from `@cloudflare/workers-types` for TypeScript type checking
87
87
  >
88
- > At runtime, Cloudflare Workers provides the actual D1 database instance via `env.D1Database`. You cannot use `D1Database` directly outside of the worker's context.
88
+ > At runtime, Cloudflare Workers provides the actual D1 database instance via `env.D1Database`. You can't use `D1Database` directly outside of the worker's context.
89
89
 
90
90
  ### Using with REST API
91
91
 
@@ -129,17 +129,17 @@ Or in `wrangler.jsonc`:
129
129
 
130
130
  ## Parameters
131
131
 
132
- **binding?:** (`D1Database`): Cloudflare D1 Workers binding (for Workers runtime)
132
+ **binding** (`D1Database`): Cloudflare D1 Workers binding (for Workers runtime)
133
133
 
134
- **accountId?:** (`string`): Cloudflare Account ID (for REST API)
134
+ **accountId** (`string`): Cloudflare Account ID (for REST API)
135
135
 
136
- **databaseId?:** (`string`): Cloudflare D1 Database ID (for REST API)
136
+ **databaseId** (`string`): Cloudflare D1 Database ID (for REST API)
137
137
 
138
- **apiToken?:** (`string`): Cloudflare API Token (for REST API)
138
+ **apiToken** (`string`): Cloudflare API Token (for REST API)
139
139
 
140
- **tablePrefix?:** (`string`): Optional prefix for all table names (useful for environment isolation)
140
+ **tablePrefix** (`string`): Optional prefix for all table names (useful for environment isolation)
141
141
 
142
- ## Additional Notes
142
+ ## Additional notes
143
143
 
144
144
  ### Schema Management
145
145
 
@@ -207,7 +207,7 @@ export default {
207
207
  }
208
208
  ```
209
209
 
210
- > **Warning:** If `init()` is not called, tables won't be created and storage operations will fail silently or throw errors.
210
+ > **Warning:** If `init()` isn't called, tables won't be created and storage operations will fail silently or throw errors.
211
211
 
212
212
  ### Transactions & Consistency
213
213
 
package/dist/index.cjs CHANGED
@@ -291,6 +291,8 @@ var D1DB = class extends base.MastraBase {
291
291
  client;
292
292
  binding;
293
293
  tablePrefix;
294
+ /** Cache of actual table columns: tableName -> Promise<Set<columnName>> (stores in-flight promise to coalesce concurrent calls) */
295
+ tableColumnsCache = /* @__PURE__ */ new Map();
294
296
  constructor(config) {
295
297
  super({
296
298
  component: "STORAGE",
@@ -399,6 +401,38 @@ var D1DB = class extends base.MastraBase {
399
401
  throw new Error("Neither binding nor client is configured");
400
402
  }
401
403
  }
404
+ /**
405
+ * Gets the set of column names that actually exist in the database table.
406
+ * Results are cached; the cache is invalidated when alterTable() adds new columns.
407
+ */
408
+ async getKnownColumnNames(tableName) {
409
+ const cached = this.tableColumnsCache.get(tableName);
410
+ if (cached) return cached;
411
+ const promise = this.getTableColumns(tableName).then((columns) => {
412
+ const names = new Set(columns.map((c) => c.name));
413
+ if (names.size === 0) {
414
+ this.tableColumnsCache.delete(tableName);
415
+ }
416
+ return names;
417
+ });
418
+ this.tableColumnsCache.set(tableName, promise);
419
+ return promise;
420
+ }
421
+ /**
422
+ * Filters a record to only include columns that exist in the actual database table.
423
+ * Unknown columns are silently dropped to ensure forward compatibility.
424
+ */
425
+ async filterRecordToKnownColumns(tableName, record) {
426
+ const knownColumns = await this.getKnownColumnNames(tableName);
427
+ if (knownColumns.size === 0) return record;
428
+ const filtered = {};
429
+ for (const [key, value] of Object.entries(record)) {
430
+ if (knownColumns.has(key)) {
431
+ filtered[key] = value;
432
+ }
433
+ }
434
+ return filtered;
435
+ }
402
436
  async getTableColumns(tableName) {
403
437
  try {
404
438
  const sql = `PRAGMA table_info(${tableName})`;
@@ -465,6 +499,7 @@ var D1DB = class extends base.MastraBase {
465
499
  const { sql, params } = query.build();
466
500
  await this.executeQuery({ sql, params });
467
501
  this.logger.debug(`Created table ${fullTableName}`);
502
+ this.tableColumnsCache.delete(fullTableName);
468
503
  } catch (error$1) {
469
504
  throw new error.MastraError(
470
505
  {
@@ -497,8 +532,8 @@ var D1DB = class extends base.MastraBase {
497
532
  }
498
533
  }
499
534
  async dropTable({ tableName }) {
535
+ const fullTableName = this.getTableName(tableName);
500
536
  try {
501
- const fullTableName = this.getTableName(tableName);
502
537
  const sql = `DROP TABLE IF EXISTS ${fullTableName}`;
503
538
  await this.executeQuery({ sql });
504
539
  this.logger.debug(`Dropped table ${fullTableName}`);
@@ -512,11 +547,13 @@ var D1DB = class extends base.MastraBase {
512
547
  },
513
548
  error$1
514
549
  );
550
+ } finally {
551
+ this.tableColumnsCache.delete(fullTableName);
515
552
  }
516
553
  }
517
554
  async alterTable(args) {
555
+ const fullTableName = this.getTableName(args.tableName);
518
556
  try {
519
- const fullTableName = this.getTableName(args.tableName);
520
557
  const existingColumns = await this.getTableColumns(fullTableName);
521
558
  const existingColumnNames = new Set(existingColumns.map((col) => col.name));
522
559
  for (const [columnName, column] of Object.entries(args.schema)) {
@@ -538,14 +575,18 @@ var D1DB = class extends base.MastraBase {
538
575
  },
539
576
  error$1
540
577
  );
578
+ } finally {
579
+ this.tableColumnsCache.delete(fullTableName);
541
580
  }
542
581
  }
543
582
  async insert({ tableName, record }) {
544
583
  try {
545
584
  const fullTableName = this.getTableName(tableName);
546
585
  const processedRecord = await this.processRecord(record);
547
- const columns = Object.keys(processedRecord);
548
- const values = Object.values(processedRecord);
586
+ const filteredRecord = await this.filterRecordToKnownColumns(fullTableName, processedRecord);
587
+ const columns = Object.keys(filteredRecord);
588
+ if (columns.length === 0) return;
589
+ const values = Object.values(filteredRecord);
549
590
  const query = createSqlBuilder().insert(fullTableName, columns, values);
550
591
  const { sql, params } = query.build();
551
592
  await this.executeQuery({ sql, params });
@@ -566,8 +607,12 @@ var D1DB = class extends base.MastraBase {
566
607
  if (records.length === 0) return;
567
608
  const fullTableName = this.getTableName(tableName);
568
609
  const processedRecords = await Promise.all(records.map((record) => this.processRecord(record)));
569
- const columns = Object.keys(processedRecords[0] || {});
570
- for (const record of processedRecords) {
610
+ const filteredRecords = await Promise.all(
611
+ processedRecords.map((r) => this.filterRecordToKnownColumns(fullTableName, r))
612
+ );
613
+ for (const record of filteredRecords) {
614
+ const columns = Object.keys(record);
615
+ if (columns.length === 0) continue;
571
616
  const values = Object.values(record);
572
617
  const query = createSqlBuilder().insert(fullTableName, columns, values);
573
618
  const { sql, params } = query.build();
@@ -643,12 +688,14 @@ var D1DB = class extends base.MastraBase {
643
688
  const batch = records.slice(i, i + batchSize);
644
689
  const recordsToInsert = batch;
645
690
  if (recordsToInsert.length > 0) {
646
- const firstRecord = recordsToInsert[0];
647
- const columns = Object.keys(firstRecord || {});
648
- for (const record of recordsToInsert) {
691
+ const filteredRecords = await Promise.all(
692
+ recordsToInsert.map((r) => this.filterRecordToKnownColumns(fullTableName, r || {}))
693
+ );
694
+ for (const record of filteredRecords) {
695
+ const columns = Object.keys(record);
696
+ if (columns.length === 0) continue;
649
697
  const values = columns.map((col) => {
650
- if (!record) return null;
651
- const value = typeof col === "string" ? record[col] : null;
698
+ const value = record[col];
652
699
  return this.serializeValue(value);
653
700
  });
654
701
  const recordToUpsert = columns.reduce(
@@ -1134,56 +1181,79 @@ var MemoryStorageD1 = class extends storage.MemoryStorage {
1134
1181
  );
1135
1182
  }
1136
1183
  }
1184
+ _sortMessages(messages, field, direction) {
1185
+ return messages.sort((a, b) => {
1186
+ const isDateField = field === "createdAt" || field === "updatedAt";
1187
+ const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
1188
+ const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
1189
+ if (aValue === bValue) {
1190
+ return a.id.localeCompare(b.id);
1191
+ }
1192
+ if (typeof aValue === "number" && typeof bValue === "number") {
1193
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
1194
+ }
1195
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1196
+ });
1197
+ }
1137
1198
  async _getIncludedMessages(include) {
1138
1199
  if (!include || include.length === 0) return null;
1200
+ const tableName = this.#db.getTableName(storage.TABLE_MESSAGES);
1201
+ const targetIds = include.map((inc) => inc.id).filter(Boolean);
1202
+ if (targetIds.length === 0) return null;
1203
+ const idPlaceholders = targetIds.map(() => "?").join(", ");
1204
+ const targetResult = await this.#db.executeQuery({
1205
+ sql: `SELECT id, thread_id, createdAt FROM ${tableName} WHERE id IN (${idPlaceholders})`,
1206
+ params: targetIds
1207
+ });
1208
+ if (!Array.isArray(targetResult) || targetResult.length === 0) return null;
1209
+ const targetMap = new Map(
1210
+ targetResult.map((r) => [r.id, { threadId: r.thread_id, createdAt: r.createdAt }])
1211
+ );
1139
1212
  const unionQueries = [];
1140
1213
  const params = [];
1141
- let paramIdx = 1;
1142
- const tableName = this.#db.getTableName(storage.TABLE_MESSAGES);
1143
1214
  for (const inc of include) {
1144
1215
  const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1145
- unionQueries.push(`
1146
- SELECT * FROM (
1147
- WITH target_thread AS (
1148
- SELECT thread_id FROM ${tableName} WHERE id = ?
1149
- ),
1150
- ordered_messages AS (
1151
- SELECT
1152
- *,
1153
- ROW_NUMBER() OVER (ORDER BY createdAt ASC) AS row_num
1154
- FROM ${tableName}
1155
- WHERE thread_id = (SELECT thread_id FROM target_thread)
1156
- )
1157
- SELECT
1158
- m.id,
1159
- m.content,
1160
- m.role,
1161
- m.type,
1162
- m.createdAt,
1163
- m.thread_id AS threadId,
1164
- m.resourceId
1165
- FROM ordered_messages m
1166
- WHERE m.id = ?
1167
- OR EXISTS (
1168
- SELECT 1 FROM ordered_messages target
1169
- WHERE target.id = ?
1170
- AND (
1171
- (m.row_num <= target.row_num + ? AND m.row_num > target.row_num)
1172
- OR
1173
- (m.row_num >= target.row_num - ? AND m.row_num < target.row_num)
1174
- )
1175
- )
1176
- ) AS query_${paramIdx}
1177
- `);
1178
- params.push(id, id, id, withNextMessages, withPreviousMessages);
1179
- paramIdx++;
1180
- }
1181
- const finalQuery = unionQueries.join(" UNION ALL ") + " ORDER BY createdAt ASC";
1216
+ const target = targetMap.get(id);
1217
+ if (!target) continue;
1218
+ unionQueries.push(`SELECT * FROM (
1219
+ SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId
1220
+ FROM ${tableName}
1221
+ WHERE thread_id = ?
1222
+ AND createdAt <= ?
1223
+ ORDER BY createdAt DESC, id DESC
1224
+ LIMIT ?
1225
+ )`);
1226
+ params.push(target.threadId, target.createdAt, withPreviousMessages + 1);
1227
+ if (withNextMessages > 0) {
1228
+ unionQueries.push(`SELECT * FROM (
1229
+ SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId
1230
+ FROM ${tableName}
1231
+ WHERE thread_id = ?
1232
+ AND createdAt > ?
1233
+ ORDER BY createdAt ASC, id ASC
1234
+ LIMIT ?
1235
+ )`);
1236
+ params.push(target.threadId, target.createdAt, withNextMessages);
1237
+ }
1238
+ }
1239
+ if (unionQueries.length === 0) return null;
1240
+ let finalQuery;
1241
+ if (unionQueries.length === 1) {
1242
+ finalQuery = unionQueries[0];
1243
+ } else {
1244
+ finalQuery = `${unionQueries.join(" UNION ALL ")} ORDER BY createdAt ASC, id ASC`;
1245
+ }
1182
1246
  const messages = await this.#db.executeQuery({ sql: finalQuery, params });
1183
1247
  if (!Array.isArray(messages)) {
1184
1248
  return [];
1185
1249
  }
1186
- const processedMessages = messages.map((message) => {
1250
+ const seen = /* @__PURE__ */ new Set();
1251
+ const processedMessages = messages.filter((message) => {
1252
+ const id = message.id;
1253
+ if (seen.has(id)) return false;
1254
+ seen.add(id);
1255
+ return true;
1256
+ }).map((message) => {
1187
1257
  const processedMsg = {};
1188
1258
  for (const [key, value] of Object.entries(message)) {
1189
1259
  if (key === `type` && value === `v2`) continue;
@@ -1283,6 +1353,23 @@ var MemoryStorageD1 = class extends storage.MemoryStorage {
1283
1353
  queryParams.push(endDate);
1284
1354
  }
1285
1355
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
1356
+ if (perPage === 0 && (!include || include.length === 0)) {
1357
+ return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
1358
+ }
1359
+ if (perPage === 0 && include && include.length > 0) {
1360
+ const includeResult = await this._getIncludedMessages(include);
1361
+ if (!Array.isArray(includeResult) || includeResult.length === 0) {
1362
+ return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
1363
+ }
1364
+ const list2 = new agent.MessageList().add(includeResult, "memory");
1365
+ return {
1366
+ messages: this._sortMessages(list2.get.all.db(), field, direction),
1367
+ total: 0,
1368
+ page,
1369
+ perPage: perPageForResponse,
1370
+ hasMore: false
1371
+ };
1372
+ }
1286
1373
  query += ` ORDER BY "${field}" ${direction}`;
1287
1374
  if (perPage !== Number.MAX_SAFE_INTEGER) {
1288
1375
  query += ` LIMIT ? OFFSET ?`;
@@ -1342,19 +1429,7 @@ var MemoryStorageD1 = class extends storage.MemoryStorage {
1342
1429
  }
1343
1430
  }
1344
1431
  const list = new agent.MessageList().add(paginatedMessages, "memory");
1345
- let finalMessages = list.get.all.db();
1346
- finalMessages = finalMessages.sort((a, b) => {
1347
- const isDateField = field === "createdAt" || field === "updatedAt";
1348
- const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
1349
- const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
1350
- if (aValue === bValue) {
1351
- return a.id.localeCompare(b.id);
1352
- }
1353
- if (typeof aValue === "number" && typeof bValue === "number") {
1354
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
1355
- }
1356
- return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1357
- });
1432
+ const finalMessages = this._sortMessages(list.get.all.db(), field, direction);
1358
1433
  const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
1359
1434
  const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
1360
1435
  const hasMore = perPageInput === false ? false : allThreadMessagesReturned ? false : offset + paginatedCount < total;