@mastra/libsql 0.10.2-alpha.0 → 0.10.2-alpha.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.
@@ -1,23 +1,23 @@
1
1
 
2
- > @mastra/libsql@0.10.2-alpha.0 build /home/runner/work/mastra/mastra/stores/libsql
2
+ > @mastra/libsql@0.10.2-alpha.2 build /home/runner/work/mastra/mastra/stores/libsql
3
3
  > tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.0
8
8
  TSC Build start
9
- TSC ⚡️ Build success in 9535ms
9
+ TSC ⚡️ Build success in 9592ms
10
10
  DTS Build start
11
11
  CLI Target: es2022
12
12
  Analysis will use the bundled TypeScript version 5.8.3
13
13
  Writing package typings: /home/runner/work/mastra/mastra/stores/libsql/dist/_tsup-dts-rollup.d.ts
14
14
  Analysis will use the bundled TypeScript version 5.8.3
15
15
  Writing package typings: /home/runner/work/mastra/mastra/stores/libsql/dist/_tsup-dts-rollup.d.cts
16
- DTS ⚡️ Build success in 9079ms
16
+ DTS ⚡️ Build success in 10548ms
17
17
  CLI Cleaning output folder
18
18
  ESM Build start
19
19
  CJS Build start
20
- ESM dist/index.js 55.48 KB
21
- ESM ⚡️ Build success in 980ms
22
- CJS dist/index.cjs 55.76 KB
23
- CJS ⚡️ Build success in 980ms
20
+ ESM dist/index.js 58.54 KB
21
+ ESM ⚡️ Build success in 1646ms
22
+ CJS dist/index.cjs 58.83 KB
23
+ CJS ⚡️ Build success in 1647ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @mastra/libsql
2
2
 
3
+ ## 0.10.2-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 48eddb9: update filter logic in Memory class to support semantic recall search scope
8
+ - Updated dependencies [48eddb9]
9
+ - @mastra/core@0.10.4-alpha.2
10
+
11
+ ## 0.10.2-alpha.1
12
+
13
+ ### Patch Changes
14
+
15
+ - dffb67b: updated stores to add alter table and change tests
16
+ - Updated dependencies [f6fd25f]
17
+ - Updated dependencies [dffb67b]
18
+ - Updated dependencies [f1309d3]
19
+ - Updated dependencies [f7f8293]
20
+ - @mastra/core@0.10.4-alpha.1
21
+
3
22
  ## 0.10.2-alpha.0
4
23
 
5
24
  ### Patch Changes
@@ -84,11 +84,26 @@ declare class LibSQLStore extends MastraStorage {
84
84
  private readonly maxRetries;
85
85
  private readonly initialBackoffMs;
86
86
  constructor(config: LibSQLConfig);
87
+ get supports(): {
88
+ selectByIncludeResourceScope: boolean;
89
+ };
87
90
  private getCreateTableSQL;
88
91
  createTable({ tableName, schema, }: {
89
92
  tableName: TABLE_NAMES;
90
93
  schema: Record<string, StorageColumn>;
91
94
  }): Promise<void>;
95
+ protected getSqlType(type: StorageColumn['type']): string;
96
+ /**
97
+ * Alters table schema to add columns if they don't exist
98
+ * @param tableName Name of the table
99
+ * @param schema Schema of the table
100
+ * @param ifNotExists Array of column names to add if they don't exist
101
+ */
102
+ alterTable({ tableName, schema, ifNotExists, }: {
103
+ tableName: TABLE_NAMES;
104
+ schema: Record<string, StorageColumn>;
105
+ ifNotExists: string[];
106
+ }): Promise<void>;
92
107
  clearTable({ tableName }: {
93
108
  tableName: TABLE_NAMES;
94
109
  }): Promise<void>;
@@ -84,11 +84,26 @@ declare class LibSQLStore extends MastraStorage {
84
84
  private readonly maxRetries;
85
85
  private readonly initialBackoffMs;
86
86
  constructor(config: LibSQLConfig);
87
+ get supports(): {
88
+ selectByIncludeResourceScope: boolean;
89
+ };
87
90
  private getCreateTableSQL;
88
91
  createTable({ tableName, schema, }: {
89
92
  tableName: TABLE_NAMES;
90
93
  schema: Record<string, StorageColumn>;
91
94
  }): Promise<void>;
95
+ protected getSqlType(type: StorageColumn['type']): string;
96
+ /**
97
+ * Alters table schema to add columns if they don't exist
98
+ * @param tableName Name of the table
99
+ * @param schema Schema of the table
100
+ * @param ifNotExists Array of column names to add if they don't exist
101
+ */
102
+ alterTable({ tableName, schema, ifNotExists, }: {
103
+ tableName: TABLE_NAMES;
104
+ schema: Record<string, StorageColumn>;
105
+ ifNotExists: string[];
106
+ }): Promise<void>;
92
107
  clearTable({ tableName }: {
93
108
  tableName: TABLE_NAMES;
94
109
  }): Promise<void>;
package/dist/index.cjs CHANGED
@@ -818,6 +818,11 @@ var LibSQLStore = class extends storage.MastraStorage {
818
818
  this.client.execute("PRAGMA busy_timeout = 5000;").then(() => this.logger.debug("LibSQLStore: PRAGMA busy_timeout=5000 set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA busy_timeout.", err));
819
819
  }
820
820
  }
821
+ get supports() {
822
+ return {
823
+ selectByIncludeResourceScope: true
824
+ };
825
+ }
821
826
  getCreateTableSQL(tableName, schema) {
822
827
  const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
823
828
  const columns = Object.entries(schema).map(([name, col]) => {
@@ -851,6 +856,52 @@ var LibSQLStore = class extends storage.MastraStorage {
851
856
  throw error;
852
857
  }
853
858
  }
859
+ getSqlType(type) {
860
+ switch (type) {
861
+ case "bigint":
862
+ return "INTEGER";
863
+ // SQLite uses INTEGER for all integer sizes
864
+ case "jsonb":
865
+ return "TEXT";
866
+ // Store JSON as TEXT in SQLite
867
+ default:
868
+ return super.getSqlType(type);
869
+ }
870
+ }
871
+ /**
872
+ * Alters table schema to add columns if they don't exist
873
+ * @param tableName Name of the table
874
+ * @param schema Schema of the table
875
+ * @param ifNotExists Array of column names to add if they don't exist
876
+ */
877
+ async alterTable({
878
+ tableName,
879
+ schema,
880
+ ifNotExists
881
+ }) {
882
+ const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
883
+ try {
884
+ const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
885
+ const result = await this.client.execute(pragmaQuery);
886
+ const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
887
+ for (const columnName of ifNotExists) {
888
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
889
+ const columnDef = schema[columnName];
890
+ const sqlType = this.getSqlType(columnDef.type);
891
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
892
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
893
+ const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
894
+ await this.client.execute(alterSql);
895
+ this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
896
+ }
897
+ }
898
+ } catch (error) {
899
+ this.logger?.error?.(
900
+ `Error altering table ${tableName}: ${error instanceof Error ? error.message : String(error)}`
901
+ );
902
+ throw new Error(`Failed to alter table ${tableName}: ${error}`);
903
+ }
904
+ }
854
905
  async clearTable({ tableName }) {
855
906
  const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
856
907
  try {
@@ -1076,6 +1127,10 @@ var LibSQLStore = class extends storage.MastraStorage {
1076
1127
  return updatedThread;
1077
1128
  }
1078
1129
  async deleteThread({ threadId }) {
1130
+ await this.client.execute({
1131
+ sql: `DELETE FROM ${storage.TABLE_MESSAGES} WHERE thread_id = ?`,
1132
+ args: [threadId]
1133
+ });
1079
1134
  await this.client.execute({
1080
1135
  sql: `DELETE FROM ${storage.TABLE_THREADS} WHERE id = ?`,
1081
1136
  args: [threadId]
@@ -1092,40 +1147,61 @@ var LibSQLStore = class extends storage.MastraStorage {
1092
1147
  content,
1093
1148
  role: row.role,
1094
1149
  createdAt: new Date(row.createdAt),
1095
- threadId: row.thread_id
1150
+ threadId: row.thread_id,
1151
+ resourceId: row.resourceId
1096
1152
  };
1097
1153
  if (row.type && row.type !== `v2`) result.type = row.type;
1098
1154
  return result;
1099
1155
  }
1100
- async _getIncludedMessages(threadId, selectBy) {
1156
+ async _getIncludedMessages({
1157
+ threadId,
1158
+ selectBy
1159
+ }) {
1101
1160
  const include = selectBy?.include;
1102
1161
  if (!include) return null;
1103
- const includeIds = include.map((i) => i.id);
1104
- const maxPrev = Math.max(...include.map((i) => i.withPreviousMessages || 0));
1105
- const maxNext = Math.max(...include.map((i) => i.withNextMessages || 0));
1106
- const includeResult = await this.client.execute({
1107
- sql: `
1108
- WITH numbered_messages AS (
1109
- SELECT
1110
- id, content, role, type, "createdAt", thread_id,
1111
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1112
- FROM "${storage.TABLE_MESSAGES}"
1113
- WHERE thread_id = ?
1114
- ),
1115
- target_positions AS (
1116
- SELECT row_num as target_pos
1117
- FROM numbered_messages
1118
- WHERE id IN (${includeIds.map(() => "?").join(", ")})
1119
- )
1120
- SELECT DISTINCT m.*
1121
- FROM numbered_messages m
1122
- CROSS JOIN target_positions t
1123
- WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1124
- ORDER BY m."createdAt" ASC
1125
- `,
1126
- args: [threadId, ...includeIds, maxPrev, maxNext]
1127
- });
1128
- return includeResult.rows?.map((row) => this.parseRow(row));
1162
+ const unionQueries = [];
1163
+ const params = [];
1164
+ for (const inc of include) {
1165
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1166
+ const searchId = inc.threadId || threadId;
1167
+ unionQueries.push(
1168
+ `
1169
+ SELECT * FROM (
1170
+ WITH numbered_messages AS (
1171
+ SELECT
1172
+ id, content, role, type, "createdAt", thread_id, "resourceId",
1173
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1174
+ FROM "${storage.TABLE_MESSAGES}"
1175
+ WHERE thread_id = ?
1176
+ ),
1177
+ target_positions AS (
1178
+ SELECT row_num as target_pos
1179
+ FROM numbered_messages
1180
+ WHERE id = ?
1181
+ )
1182
+ SELECT DISTINCT m.*
1183
+ FROM numbered_messages m
1184
+ CROSS JOIN target_positions t
1185
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1186
+ )
1187
+ `
1188
+ // Keep ASC for final sorting after fetching context
1189
+ );
1190
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
1191
+ }
1192
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1193
+ const includedResult = await this.client.execute({ sql: finalQuery, args: params });
1194
+ const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
1195
+ const dedupedRows = Object.values(
1196
+ includedRows.reduce(
1197
+ (acc, row) => {
1198
+ acc[row.id] = row;
1199
+ return acc;
1200
+ },
1201
+ {}
1202
+ )
1203
+ );
1204
+ return dedupedRows;
1129
1205
  }
1130
1206
  async getMessages({
1131
1207
  threadId,
@@ -1136,19 +1212,27 @@ var LibSQLStore = class extends storage.MastraStorage {
1136
1212
  const messages = [];
1137
1213
  const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
1138
1214
  if (selectBy?.include?.length) {
1139
- const includeMessages = await this._getIncludedMessages(threadId, selectBy);
1215
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1140
1216
  if (includeMessages) {
1141
1217
  messages.push(...includeMessages);
1142
1218
  }
1143
1219
  }
1144
1220
  const excludeIds = messages.map((m) => m.id);
1145
1221
  const remainingSql = `
1146
- SELECT id, content, role, type, "createdAt", thread_id
1147
- FROM "${storage.TABLE_MESSAGES}"
1148
- WHERE thread_id = ?
1149
- ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1150
- ORDER BY "createdAt" DESC LIMIT ?
1151
- `;
1222
+ SELECT
1223
+ id,
1224
+ content,
1225
+ role,
1226
+ type,
1227
+ "createdAt",
1228
+ thread_id,
1229
+ "resourceId"
1230
+ FROM "${storage.TABLE_MESSAGES}"
1231
+ WHERE thread_id = ?
1232
+ ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1233
+ ORDER BY "createdAt" DESC
1234
+ LIMIT ?
1235
+ `;
1152
1236
  const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1153
1237
  const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
1154
1238
  if (remainingResult.rows) {
@@ -1170,7 +1254,7 @@ var LibSQLStore = class extends storage.MastraStorage {
1170
1254
  const toDate = dateRange?.end;
1171
1255
  const messages = [];
1172
1256
  if (selectBy?.include?.length) {
1173
- const includeMessages = await this._getIncludedMessages(threadId, selectBy);
1257
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1174
1258
  if (includeMessages) {
1175
1259
  messages.push(...includeMessages);
1176
1260
  }
@@ -1232,16 +1316,27 @@ var LibSQLStore = class extends storage.MastraStorage {
1232
1316
  }
1233
1317
  const batchStatements = messages.map((message) => {
1234
1318
  const time = message.createdAt || /* @__PURE__ */ new Date();
1319
+ if (!message.threadId) {
1320
+ throw new Error(
1321
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1322
+ );
1323
+ }
1324
+ if (!message.resourceId) {
1325
+ throw new Error(
1326
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1327
+ );
1328
+ }
1235
1329
  return {
1236
- sql: `INSERT INTO ${storage.TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt)
1237
- VALUES (?, ?, ?, ?, ?, ?)`,
1330
+ sql: `INSERT INTO ${storage.TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt, resourceId)
1331
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1238
1332
  args: [
1239
1333
  message.id,
1240
- threadId,
1334
+ message.threadId,
1241
1335
  typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1242
1336
  message.role,
1243
1337
  message.type || "v2",
1244
- time instanceof Date ? time.toISOString() : time
1338
+ time instanceof Date ? time.toISOString() : time,
1339
+ message.resourceId
1245
1340
  ]
1246
1341
  };
1247
1342
  });
package/dist/index.js CHANGED
@@ -816,6 +816,11 @@ var LibSQLStore = class extends MastraStorage {
816
816
  this.client.execute("PRAGMA busy_timeout = 5000;").then(() => this.logger.debug("LibSQLStore: PRAGMA busy_timeout=5000 set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA busy_timeout.", err));
817
817
  }
818
818
  }
819
+ get supports() {
820
+ return {
821
+ selectByIncludeResourceScope: true
822
+ };
823
+ }
819
824
  getCreateTableSQL(tableName, schema) {
820
825
  const parsedTableName = parseSqlIdentifier(tableName, "table name");
821
826
  const columns = Object.entries(schema).map(([name, col]) => {
@@ -849,6 +854,52 @@ var LibSQLStore = class extends MastraStorage {
849
854
  throw error;
850
855
  }
851
856
  }
857
+ getSqlType(type) {
858
+ switch (type) {
859
+ case "bigint":
860
+ return "INTEGER";
861
+ // SQLite uses INTEGER for all integer sizes
862
+ case "jsonb":
863
+ return "TEXT";
864
+ // Store JSON as TEXT in SQLite
865
+ default:
866
+ return super.getSqlType(type);
867
+ }
868
+ }
869
+ /**
870
+ * Alters table schema to add columns if they don't exist
871
+ * @param tableName Name of the table
872
+ * @param schema Schema of the table
873
+ * @param ifNotExists Array of column names to add if they don't exist
874
+ */
875
+ async alterTable({
876
+ tableName,
877
+ schema,
878
+ ifNotExists
879
+ }) {
880
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
881
+ try {
882
+ const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
883
+ const result = await this.client.execute(pragmaQuery);
884
+ const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
885
+ for (const columnName of ifNotExists) {
886
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
887
+ const columnDef = schema[columnName];
888
+ const sqlType = this.getSqlType(columnDef.type);
889
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
890
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
891
+ const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
892
+ await this.client.execute(alterSql);
893
+ this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
894
+ }
895
+ }
896
+ } catch (error) {
897
+ this.logger?.error?.(
898
+ `Error altering table ${tableName}: ${error instanceof Error ? error.message : String(error)}`
899
+ );
900
+ throw new Error(`Failed to alter table ${tableName}: ${error}`);
901
+ }
902
+ }
852
903
  async clearTable({ tableName }) {
853
904
  const parsedTableName = parseSqlIdentifier(tableName, "table name");
854
905
  try {
@@ -1074,6 +1125,10 @@ var LibSQLStore = class extends MastraStorage {
1074
1125
  return updatedThread;
1075
1126
  }
1076
1127
  async deleteThread({ threadId }) {
1128
+ await this.client.execute({
1129
+ sql: `DELETE FROM ${TABLE_MESSAGES} WHERE thread_id = ?`,
1130
+ args: [threadId]
1131
+ });
1077
1132
  await this.client.execute({
1078
1133
  sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
1079
1134
  args: [threadId]
@@ -1090,40 +1145,61 @@ var LibSQLStore = class extends MastraStorage {
1090
1145
  content,
1091
1146
  role: row.role,
1092
1147
  createdAt: new Date(row.createdAt),
1093
- threadId: row.thread_id
1148
+ threadId: row.thread_id,
1149
+ resourceId: row.resourceId
1094
1150
  };
1095
1151
  if (row.type && row.type !== `v2`) result.type = row.type;
1096
1152
  return result;
1097
1153
  }
1098
- async _getIncludedMessages(threadId, selectBy) {
1154
+ async _getIncludedMessages({
1155
+ threadId,
1156
+ selectBy
1157
+ }) {
1099
1158
  const include = selectBy?.include;
1100
1159
  if (!include) return null;
1101
- const includeIds = include.map((i) => i.id);
1102
- const maxPrev = Math.max(...include.map((i) => i.withPreviousMessages || 0));
1103
- const maxNext = Math.max(...include.map((i) => i.withNextMessages || 0));
1104
- const includeResult = await this.client.execute({
1105
- sql: `
1106
- WITH numbered_messages AS (
1107
- SELECT
1108
- id, content, role, type, "createdAt", thread_id,
1109
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1110
- FROM "${TABLE_MESSAGES}"
1111
- WHERE thread_id = ?
1112
- ),
1113
- target_positions AS (
1114
- SELECT row_num as target_pos
1115
- FROM numbered_messages
1116
- WHERE id IN (${includeIds.map(() => "?").join(", ")})
1117
- )
1118
- SELECT DISTINCT m.*
1119
- FROM numbered_messages m
1120
- CROSS JOIN target_positions t
1121
- WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1122
- ORDER BY m."createdAt" ASC
1123
- `,
1124
- args: [threadId, ...includeIds, maxPrev, maxNext]
1125
- });
1126
- return includeResult.rows?.map((row) => this.parseRow(row));
1160
+ const unionQueries = [];
1161
+ const params = [];
1162
+ for (const inc of include) {
1163
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1164
+ const searchId = inc.threadId || threadId;
1165
+ unionQueries.push(
1166
+ `
1167
+ SELECT * FROM (
1168
+ WITH numbered_messages AS (
1169
+ SELECT
1170
+ id, content, role, type, "createdAt", thread_id, "resourceId",
1171
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1172
+ FROM "${TABLE_MESSAGES}"
1173
+ WHERE thread_id = ?
1174
+ ),
1175
+ target_positions AS (
1176
+ SELECT row_num as target_pos
1177
+ FROM numbered_messages
1178
+ WHERE id = ?
1179
+ )
1180
+ SELECT DISTINCT m.*
1181
+ FROM numbered_messages m
1182
+ CROSS JOIN target_positions t
1183
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1184
+ )
1185
+ `
1186
+ // Keep ASC for final sorting after fetching context
1187
+ );
1188
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
1189
+ }
1190
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1191
+ const includedResult = await this.client.execute({ sql: finalQuery, args: params });
1192
+ const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
1193
+ const dedupedRows = Object.values(
1194
+ includedRows.reduce(
1195
+ (acc, row) => {
1196
+ acc[row.id] = row;
1197
+ return acc;
1198
+ },
1199
+ {}
1200
+ )
1201
+ );
1202
+ return dedupedRows;
1127
1203
  }
1128
1204
  async getMessages({
1129
1205
  threadId,
@@ -1134,19 +1210,27 @@ var LibSQLStore = class extends MastraStorage {
1134
1210
  const messages = [];
1135
1211
  const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
1136
1212
  if (selectBy?.include?.length) {
1137
- const includeMessages = await this._getIncludedMessages(threadId, selectBy);
1213
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1138
1214
  if (includeMessages) {
1139
1215
  messages.push(...includeMessages);
1140
1216
  }
1141
1217
  }
1142
1218
  const excludeIds = messages.map((m) => m.id);
1143
1219
  const remainingSql = `
1144
- SELECT id, content, role, type, "createdAt", thread_id
1145
- FROM "${TABLE_MESSAGES}"
1146
- WHERE thread_id = ?
1147
- ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1148
- ORDER BY "createdAt" DESC LIMIT ?
1149
- `;
1220
+ SELECT
1221
+ id,
1222
+ content,
1223
+ role,
1224
+ type,
1225
+ "createdAt",
1226
+ thread_id,
1227
+ "resourceId"
1228
+ FROM "${TABLE_MESSAGES}"
1229
+ WHERE thread_id = ?
1230
+ ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1231
+ ORDER BY "createdAt" DESC
1232
+ LIMIT ?
1233
+ `;
1150
1234
  const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1151
1235
  const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
1152
1236
  if (remainingResult.rows) {
@@ -1168,7 +1252,7 @@ var LibSQLStore = class extends MastraStorage {
1168
1252
  const toDate = dateRange?.end;
1169
1253
  const messages = [];
1170
1254
  if (selectBy?.include?.length) {
1171
- const includeMessages = await this._getIncludedMessages(threadId, selectBy);
1255
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1172
1256
  if (includeMessages) {
1173
1257
  messages.push(...includeMessages);
1174
1258
  }
@@ -1230,16 +1314,27 @@ var LibSQLStore = class extends MastraStorage {
1230
1314
  }
1231
1315
  const batchStatements = messages.map((message) => {
1232
1316
  const time = message.createdAt || /* @__PURE__ */ new Date();
1317
+ if (!message.threadId) {
1318
+ throw new Error(
1319
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1320
+ );
1321
+ }
1322
+ if (!message.resourceId) {
1323
+ throw new Error(
1324
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1325
+ );
1326
+ }
1233
1327
  return {
1234
- sql: `INSERT INTO ${TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt)
1235
- VALUES (?, ?, ?, ?, ?, ?)`,
1328
+ sql: `INSERT INTO ${TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt, resourceId)
1329
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1236
1330
  args: [
1237
1331
  message.id,
1238
- threadId,
1332
+ message.threadId,
1239
1333
  typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1240
1334
  message.role,
1241
1335
  message.type || "v2",
1242
- time instanceof Date ? time.toISOString() : time
1336
+ time instanceof Date ? time.toISOString() : time,
1337
+ message.resourceId
1243
1338
  ]
1244
1339
  };
1245
1340
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/libsql",
3
- "version": "0.10.2-alpha.0",
3
+ "version": "0.10.2-alpha.2",
4
4
  "description": "Libsql provider for Mastra - includes both vector and db storage capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,9 +28,9 @@
28
28
  "eslint": "^9.28.0",
29
29
  "tsup": "^8.5.0",
30
30
  "typescript": "^5.8.3",
31
- "vitest": "^3.1.2",
31
+ "vitest": "^3.2.2",
32
32
  "@internal/lint": "0.0.10",
33
- "@mastra/core": "0.10.4-alpha.0",
33
+ "@mastra/core": "0.10.4-alpha.2",
34
34
  "@internal/storage-test-utils": "0.0.6"
35
35
  },
36
36
  "peerDependencies": {
@@ -3,8 +3,9 @@ import {
3
3
  createSampleEval,
4
4
  createSampleTraceForDB,
5
5
  createSampleThread,
6
- createSampleMessage,
7
6
  createTestSuite,
7
+ createSampleMessageV1,
8
+ resetRole,
8
9
  } from '@internal/storage-test-utils';
9
10
  import type { MastraMessageV1, StorageThreadType } from '@mastra/core';
10
11
  import { Mastra } from '@mastra/core/mastra';
@@ -243,16 +244,14 @@ describe('LibSQLStore Pagination Features', () => {
243
244
 
244
245
  describe('getMessages with pagination', () => {
245
246
  it('should return paginated messages with total count', async () => {
247
+ resetRole();
246
248
  const threadData = createSampleThread();
247
249
  threadData.resourceId = 'resource-msg-pagination';
248
250
  const thread = await store.saveThread({ thread: threadData as StorageThreadType });
249
251
 
250
252
  const messageRecords: MastraMessageV1[] = [];
251
253
  for (let i = 0; i < 15; i++) {
252
- messageRecords.push({
253
- ...createSampleMessage(thread.id),
254
- content: [{ type: 'text', text: `Message ${i + 1}` }],
255
- } as MastraMessageV1);
254
+ messageRecords.push(createSampleMessageV1({ threadId: thread.id, content: `Message ${i + 1}` }));
256
255
  }
257
256
  await store.saveMessages({ messages: messageRecords });
258
257
 
@@ -300,17 +299,21 @@ describe('LibSQLStore Pagination Features', () => {
300
299
 
301
300
  // Ensure timestamps are distinct for reliable sorting by creating them with a slight delay for testing clarity
302
301
  const messagesToSave: MastraMessageV1[] = [];
303
- messagesToSave.push(createSampleMessage(thread.id, dayBeforeYesterday));
302
+ messagesToSave.push(
303
+ createSampleMessageV1({ threadId: thread.id, content: 'Message 1', createdAt: dayBeforeYesterday }),
304
+ );
304
305
  await new Promise(r => setTimeout(r, 5));
305
- messagesToSave.push(createSampleMessage(thread.id, dayBeforeYesterday));
306
+ messagesToSave.push(
307
+ createSampleMessageV1({ threadId: thread.id, content: 'Message 2', createdAt: dayBeforeYesterday }),
308
+ );
306
309
  await new Promise(r => setTimeout(r, 5));
307
- messagesToSave.push(createSampleMessage(thread.id, yesterday));
310
+ messagesToSave.push(createSampleMessageV1({ threadId: thread.id, content: 'Message 3', createdAt: yesterday }));
308
311
  await new Promise(r => setTimeout(r, 5));
309
- messagesToSave.push(createSampleMessage(thread.id, yesterday));
312
+ messagesToSave.push(createSampleMessageV1({ threadId: thread.id, content: 'Message 4', createdAt: yesterday }));
310
313
  await new Promise(r => setTimeout(r, 5));
311
- messagesToSave.push(createSampleMessage(thread.id, now));
314
+ messagesToSave.push(createSampleMessageV1({ threadId: thread.id, content: 'Message 5', createdAt: now }));
312
315
  await new Promise(r => setTimeout(r, 5));
313
- messagesToSave.push(createSampleMessage(thread.id, now));
316
+ messagesToSave.push(createSampleMessageV1({ threadId: thread.id, content: 'Message 6', createdAt: now }));
314
317
 
315
318
  await store.saveMessages({ messages: messagesToSave, format: 'v1' });
316
319
  // Total 6 messages: 2 now, 2 yesterday, 2 dayBeforeYesterday (oldest to newest)
@@ -81,6 +81,14 @@ export class LibSQLStore extends MastraStorage {
81
81
  }
82
82
  }
83
83
 
84
+ public get supports(): {
85
+ selectByIncludeResourceScope: boolean;
86
+ } {
87
+ return {
88
+ selectByIncludeResourceScope: true,
89
+ };
90
+ }
91
+
84
92
  private getCreateTableSQL(tableName: TABLE_NAMES, schema: Record<string, StorageColumn>): string {
85
93
  const parsedTableName = parseSqlIdentifier(tableName, 'table name');
86
94
  const columns = Object.entries(schema).map(([name, col]) => {
@@ -125,6 +133,63 @@ export class LibSQLStore extends MastraStorage {
125
133
  }
126
134
  }
127
135
 
136
+ protected getSqlType(type: StorageColumn['type']): string {
137
+ switch (type) {
138
+ case 'bigint':
139
+ return 'INTEGER'; // SQLite uses INTEGER for all integer sizes
140
+ case 'jsonb':
141
+ return 'TEXT'; // Store JSON as TEXT in SQLite
142
+ default:
143
+ return super.getSqlType(type);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Alters table schema to add columns if they don't exist
149
+ * @param tableName Name of the table
150
+ * @param schema Schema of the table
151
+ * @param ifNotExists Array of column names to add if they don't exist
152
+ */
153
+ async alterTable({
154
+ tableName,
155
+ schema,
156
+ ifNotExists,
157
+ }: {
158
+ tableName: TABLE_NAMES;
159
+ schema: Record<string, StorageColumn>;
160
+ ifNotExists: string[];
161
+ }): Promise<void> {
162
+ const parsedTableName = parseSqlIdentifier(tableName, 'table name');
163
+
164
+ try {
165
+ // 1. Get existing columns using PRAGMA
166
+ const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
167
+ const result = await this.client.execute(pragmaQuery);
168
+ const existingColumnNames = new Set(result.rows.map((row: any) => row.name.toLowerCase()));
169
+
170
+ // 2. Add missing columns
171
+ for (const columnName of ifNotExists) {
172
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
173
+ const columnDef = schema[columnName];
174
+ const sqlType = this.getSqlType(columnDef.type); // ensure this exists or implement
175
+ const nullable = columnDef.nullable === false ? 'NOT NULL' : '';
176
+ // In SQLite, you must provide a DEFAULT if adding a NOT NULL column to a non-empty table
177
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : '';
178
+ const alterSql =
179
+ `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
180
+
181
+ await this.client.execute(alterSql);
182
+ this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
183
+ }
184
+ }
185
+ } catch (error) {
186
+ this.logger?.error?.(
187
+ `Error altering table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
188
+ );
189
+ throw new Error(`Failed to alter table ${tableName}: ${error}`);
190
+ }
191
+ }
192
+
128
193
  async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
129
194
  const parsedTableName = parseSqlIdentifier(tableName, 'table name');
130
195
  try {
@@ -414,11 +479,16 @@ export class LibSQLStore extends MastraStorage {
414
479
  }
415
480
 
416
481
  async deleteThread({ threadId }: { threadId: string }): Promise<void> {
482
+ // Delete messages for this thread (manual step)
483
+ await this.client.execute({
484
+ sql: `DELETE FROM ${TABLE_MESSAGES} WHERE thread_id = ?`,
485
+ args: [threadId],
486
+ });
417
487
  await this.client.execute({
418
488
  sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
419
489
  args: [threadId],
420
490
  });
421
- // Messages will be automatically deleted due to CASCADE constraint
491
+ // TODO: Need to check if CASCADE is enabled so that messages will be automatically deleted due to CASCADE constraint
422
492
  }
423
493
 
424
494
  private parseRow(row: any): MastraMessageV2 {
@@ -434,42 +504,66 @@ export class LibSQLStore extends MastraStorage {
434
504
  role: row.role,
435
505
  createdAt: new Date(row.createdAt as string),
436
506
  threadId: row.thread_id,
507
+ resourceId: row.resourceId,
437
508
  } as MastraMessageV2;
438
509
  if (row.type && row.type !== `v2`) result.type = row.type;
439
510
  return result;
440
511
  }
441
512
 
442
- private async _getIncludedMessages(threadId: string, selectBy: StorageGetMessagesArg['selectBy']) {
513
+ private async _getIncludedMessages({
514
+ threadId,
515
+ selectBy,
516
+ }: {
517
+ threadId: string;
518
+ selectBy: StorageGetMessagesArg['selectBy'];
519
+ }) {
443
520
  const include = selectBy?.include;
444
521
  if (!include) return null;
445
522
 
446
- const includeIds = include.map(i => i.id);
447
- const maxPrev = Math.max(...include.map(i => i.withPreviousMessages || 0));
448
- const maxNext = Math.max(...include.map(i => i.withNextMessages || 0));
449
-
450
- const includeResult = await this.client.execute({
451
- sql: `
452
- WITH numbered_messages AS (
453
- SELECT
454
- id, content, role, type, "createdAt", thread_id,
455
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
456
- FROM "${TABLE_MESSAGES}"
457
- WHERE thread_id = ?
458
- ),
459
- target_positions AS (
460
- SELECT row_num as target_pos
461
- FROM numbered_messages
462
- WHERE id IN (${includeIds.map(() => '?').join(', ')})
463
- )
464
- SELECT DISTINCT m.*
465
- FROM numbered_messages m
466
- CROSS JOIN target_positions t
467
- WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
468
- ORDER BY m."createdAt" ASC
469
- `,
470
- args: [threadId, ...includeIds, maxPrev, maxNext],
471
- });
472
- return includeResult.rows?.map((row: any) => this.parseRow(row));
523
+ const unionQueries: string[] = [];
524
+ const params: any[] = [];
525
+
526
+ for (const inc of include) {
527
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
528
+ // if threadId is provided, use it, otherwise use threadId from args
529
+ const searchId = inc.threadId || threadId;
530
+ unionQueries.push(
531
+ `
532
+ SELECT * FROM (
533
+ WITH numbered_messages AS (
534
+ SELECT
535
+ id, content, role, type, "createdAt", thread_id, "resourceId",
536
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
537
+ FROM "${TABLE_MESSAGES}"
538
+ WHERE thread_id = ?
539
+ ),
540
+ target_positions AS (
541
+ SELECT row_num as target_pos
542
+ FROM numbered_messages
543
+ WHERE id = ?
544
+ )
545
+ SELECT DISTINCT m.*
546
+ FROM numbered_messages m
547
+ CROSS JOIN target_positions t
548
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
549
+ )
550
+ `, // Keep ASC for final sorting after fetching context
551
+ );
552
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
553
+ }
554
+ const finalQuery = unionQueries.join(' UNION ALL ') + ' ORDER BY "createdAt" ASC';
555
+ const includedResult = await this.client.execute({ sql: finalQuery, args: params });
556
+ const includedRows = includedResult.rows?.map(row => this.parseRow(row));
557
+ const dedupedRows = Object.values(
558
+ includedRows.reduce(
559
+ (acc, row) => {
560
+ acc[row.id] = row;
561
+ return acc;
562
+ },
563
+ {} as Record<string, MastraMessageV2>,
564
+ ),
565
+ );
566
+ return dedupedRows;
473
567
  }
474
568
 
475
569
  /**
@@ -489,7 +583,7 @@ export class LibSQLStore extends MastraStorage {
489
583
  const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
490
584
 
491
585
  if (selectBy?.include?.length) {
492
- const includeMessages = await this._getIncludedMessages(threadId, selectBy);
586
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
493
587
  if (includeMessages) {
494
588
  messages.push(...includeMessages);
495
589
  }
@@ -497,12 +591,20 @@ export class LibSQLStore extends MastraStorage {
497
591
 
498
592
  const excludeIds = messages.map(m => m.id);
499
593
  const remainingSql = `
500
- SELECT id, content, role, type, "createdAt", thread_id
501
- FROM "${TABLE_MESSAGES}"
502
- WHERE thread_id = ?
503
- ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => '?').join(', ')})` : ''}
504
- ORDER BY "createdAt" DESC LIMIT ?
505
- `;
594
+ SELECT
595
+ id,
596
+ content,
597
+ role,
598
+ type,
599
+ "createdAt",
600
+ thread_id,
601
+ "resourceId"
602
+ FROM "${TABLE_MESSAGES}"
603
+ WHERE thread_id = ?
604
+ ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => '?').join(', ')})` : ''}
605
+ ORDER BY "createdAt" DESC
606
+ LIMIT ?
607
+ `;
506
608
  const remainingArgs = [threadId, ...(excludeIds.length ? excludeIds : []), limit];
507
609
  const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
508
610
  if (remainingResult.rows) {
@@ -531,7 +633,7 @@ export class LibSQLStore extends MastraStorage {
531
633
  const messages: MastraMessageV2[] = [];
532
634
 
533
635
  if (selectBy?.include?.length) {
534
- const includeMessages = await this._getIncludedMessages(threadId, selectBy);
636
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
535
637
  if (includeMessages) {
536
638
  messages.push(...includeMessages);
537
639
  }
@@ -613,16 +715,27 @@ export class LibSQLStore extends MastraStorage {
613
715
  // Prepare batch statements for all messages
614
716
  const batchStatements = messages.map(message => {
615
717
  const time = message.createdAt || new Date();
718
+ if (!message.threadId) {
719
+ throw new Error(
720
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`,
721
+ );
722
+ }
723
+ if (!message.resourceId) {
724
+ throw new Error(
725
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`,
726
+ );
727
+ }
616
728
  return {
617
- sql: `INSERT INTO ${TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt)
618
- VALUES (?, ?, ?, ?, ?, ?)`,
729
+ sql: `INSERT INTO ${TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt, resourceId)
730
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
619
731
  args: [
620
732
  message.id,
621
- threadId,
733
+ message.threadId!,
622
734
  typeof message.content === 'object' ? JSON.stringify(message.content) : message.content,
623
735
  message.role,
624
736
  message.type || 'v2',
625
737
  time instanceof Date ? time.toISOString() : time,
738
+ message.resourceId,
626
739
  ],
627
740
  };
628
741
  });