@mastra/libsql 0.13.8-alpha.0 → 0.13.8-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,548 +0,0 @@
1
- import type { InValue } from '@libsql/client';
2
- import { parseFieldKey } from '@mastra/core/utils';
3
- import type {
4
- BasicOperator,
5
- NumericOperator,
6
- ArrayOperator,
7
- ElementOperator,
8
- LogicalOperator,
9
- } from '@mastra/core/vector/filter';
10
- import type { LibSQLVectorFilter } from './filter';
11
-
12
- type OperatorType =
13
- | BasicOperator
14
- | NumericOperator
15
- | ArrayOperator
16
- | ElementOperator
17
- | LogicalOperator
18
- | '$contains'
19
- | '$size';
20
-
21
- type FilterOperator = {
22
- sql: string;
23
- needsValue: boolean;
24
- transformValue?: () => any;
25
- };
26
-
27
- type OperatorFn = (key: string, value?: any) => FilterOperator;
28
-
29
- // Helper functions to create operators
30
- const createBasicOperator = (symbol: string) => {
31
- return (key: string, value: any): FilterOperator => {
32
- const jsonPath = getJsonPath(key);
33
- return {
34
- sql: `CASE
35
- WHEN ? IS NULL THEN json_extract(metadata, ${jsonPath}) IS ${symbol === '=' ? '' : 'NOT'} NULL
36
- ELSE json_extract(metadata, ${jsonPath}) ${symbol} ?
37
- END`,
38
- needsValue: true,
39
- transformValue: () => {
40
- // Return the values directly, not in an object
41
- return [value, value];
42
- },
43
- };
44
- };
45
- };
46
- const createNumericOperator = (symbol: string) => {
47
- return (key: string): FilterOperator => {
48
- const jsonPath = getJsonPath(key);
49
- return {
50
- sql: `CAST(json_extract(metadata, ${jsonPath}) AS NUMERIC) ${symbol} ?`,
51
- needsValue: true,
52
- };
53
- };
54
- };
55
-
56
- const validateJsonArray = (key: string) => {
57
- const jsonPath = getJsonPath(key);
58
- return `json_valid(json_extract(metadata, ${jsonPath}))
59
- AND json_type(json_extract(metadata, ${jsonPath})) = 'array'`;
60
- };
61
-
62
- const pattern = /json_extract\(metadata, '\$\.(?:"[^"]*"(?:\."[^"]*")*|[^']+)'\)/g;
63
-
64
- function buildElemMatchConditions(value: any) {
65
- const conditions = Object.entries(value).map(([field, fieldValue]) => {
66
- if (field.startsWith('$')) {
67
- // Direct operators on array elements ($in, $gt, etc)
68
- const { sql, values } = buildCondition('elem.value', { [field]: fieldValue }, '');
69
- // Replace the metadata path with elem.value
70
- const elemSql = sql.replace(pattern, 'elem.value');
71
- return { sql: elemSql, values };
72
- } else if (typeof fieldValue === 'object' && !Array.isArray(fieldValue)) {
73
- // Nested field with operators (count: { $gt: 20 })
74
- const { sql, values } = buildCondition(field, fieldValue, '');
75
- // Replace the field path with elem.value path
76
- const jsonPath = parseJsonPathKey(field);
77
- const elemSql = sql.replace(pattern, `json_extract(elem.value, '$.${jsonPath}')`);
78
- return { sql: elemSql, values };
79
- } else {
80
- const jsonPath = parseJsonPathKey(field);
81
- // Simple field equality (warehouse: 'A')
82
- return {
83
- sql: `json_extract(elem.value, '$.${jsonPath}') = ?`,
84
- values: [fieldValue],
85
- };
86
- }
87
- });
88
-
89
- return conditions;
90
- }
91
-
92
- // Define all filter operators
93
- const FILTER_OPERATORS: Record<OperatorType, OperatorFn> = {
94
- $eq: createBasicOperator('='),
95
- $ne: createBasicOperator('!='),
96
- $gt: createNumericOperator('>'),
97
- $gte: createNumericOperator('>='),
98
- $lt: createNumericOperator('<'),
99
- $lte: createNumericOperator('<='),
100
-
101
- // Array Operators
102
- $in: (key: string, value: any) => {
103
- const jsonPath = getJsonPath(key);
104
- const arr = Array.isArray(value) ? value : [value];
105
- if (arr.length === 0) {
106
- return { sql: '1 = 0', needsValue: true, transformValue: () => [] };
107
- }
108
- const paramPlaceholders = arr.map(() => '?').join(',');
109
- return {
110
- sql: `(
111
- CASE
112
- WHEN ${validateJsonArray(key)} THEN
113
- EXISTS (
114
- SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem
115
- WHERE elem.value IN (SELECT value FROM json_each(?))
116
- )
117
- ELSE json_extract(metadata, ${jsonPath}) IN (${paramPlaceholders})
118
- END
119
- )`,
120
- needsValue: true,
121
- transformValue: () => [JSON.stringify(arr), ...arr],
122
- };
123
- },
124
-
125
- $nin: (key: string, value: any) => {
126
- const jsonPath = getJsonPath(key);
127
- const arr = Array.isArray(value) ? value : [value];
128
- if (arr.length === 0) {
129
- return { sql: '1 = 1', needsValue: true, transformValue: () => [] };
130
- }
131
- const paramPlaceholders = arr.map(() => '?').join(',');
132
- return {
133
- sql: `(
134
- CASE
135
- WHEN ${validateJsonArray(key)} THEN
136
- NOT EXISTS (
137
- SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem
138
- WHERE elem.value IN (SELECT value FROM json_each(?))
139
- )
140
- ELSE json_extract(metadata, ${jsonPath}) NOT IN (${paramPlaceholders})
141
- END
142
- )`,
143
- needsValue: true,
144
- transformValue: () => [JSON.stringify(arr), ...arr],
145
- };
146
- },
147
- $all: (key: string, value: any) => {
148
- const jsonPath = getJsonPath(key);
149
- let sql: string;
150
- const arrayValue = Array.isArray(value) ? value : [value];
151
-
152
- if (arrayValue.length === 0) {
153
- // If the array is empty, always return false (no matches)
154
- sql = '1 = 0';
155
- } else {
156
- sql = `(
157
- CASE
158
- WHEN ${validateJsonArray(key)} THEN
159
- NOT EXISTS (
160
- SELECT value
161
- FROM json_each(?)
162
- WHERE value NOT IN (
163
- SELECT value
164
- FROM json_each(json_extract(metadata, ${jsonPath}))
165
- )
166
- )
167
- ELSE FALSE
168
- END
169
- )`;
170
- }
171
-
172
- return {
173
- sql,
174
- needsValue: true,
175
- transformValue: () => {
176
- if (arrayValue.length === 0) {
177
- return [];
178
- }
179
- return [JSON.stringify(arrayValue)];
180
- },
181
- };
182
- },
183
- $elemMatch: (key: string, value: any) => {
184
- const jsonPath = getJsonPath(key);
185
- if (typeof value !== 'object' || Array.isArray(value)) {
186
- throw new Error('$elemMatch requires an object with conditions');
187
- }
188
-
189
- // For nested object conditions
190
- const conditions = buildElemMatchConditions(value);
191
-
192
- return {
193
- sql: `(
194
- CASE
195
- WHEN ${validateJsonArray(key)} THEN
196
- EXISTS (
197
- SELECT 1
198
- FROM json_each(json_extract(metadata, ${jsonPath})) as elem
199
- WHERE ${conditions.map(c => c.sql).join(' AND ')}
200
- )
201
- ELSE FALSE
202
- END
203
- )`,
204
- needsValue: true,
205
- transformValue: () => conditions.flatMap(c => c.values),
206
- };
207
- },
208
-
209
- // Element Operators
210
- $exists: (key: string) => {
211
- const jsonPath = getJsonPath(key);
212
- return {
213
- sql: `json_extract(metadata, ${jsonPath}) IS NOT NULL`,
214
- needsValue: false,
215
- };
216
- },
217
-
218
- // Logical Operators
219
- $and: (key: string) => ({
220
- sql: `(${key})`,
221
- needsValue: false,
222
- }),
223
- $or: (key: string) => ({
224
- sql: `(${key})`,
225
- needsValue: false,
226
- }),
227
- $not: key => ({ sql: `NOT (${key})`, needsValue: false }),
228
- $nor: (key: string) => ({
229
- sql: `NOT (${key})`,
230
- needsValue: false,
231
- }),
232
- $size: (key: string, paramIndex: number) => {
233
- const jsonPath = getJsonPath(key);
234
- return {
235
- sql: `(
236
- CASE
237
- WHEN json_type(json_extract(metadata, ${jsonPath})) = 'array' THEN
238
- json_array_length(json_extract(metadata, ${jsonPath})) = $${paramIndex}
239
- ELSE FALSE
240
- END
241
- )`,
242
- needsValue: true,
243
- };
244
- },
245
- // /**
246
- // * Regex Operators
247
- // * Supports case insensitive and multiline
248
- // */
249
- // $regex: (key: string): FilterOperator => ({
250
- // sql: `json_extract(metadata, '$."${toJsonPathKey(key)}"') = ?`,
251
- // needsValue: true,
252
- // transformValue: (value: any) => {
253
- // const pattern = typeof value === 'object' ? value.$regex : value;
254
- // const options = typeof value === 'object' ? value.$options || '' : '';
255
- // let sql = `json_extract(metadata, '$."${toJsonPathKey(key)}"')`;
256
-
257
- // // Handle multiline
258
- // // if (options.includes('m')) {
259
- // // sql = `REPLACE(${sql}, CHAR(10), '\n')`;
260
- // // }
261
-
262
- // // let finalPattern = pattern;
263
- // // if (options) {
264
- // // finalPattern = `(\\?${options})${pattern}`;
265
- // // }
266
-
267
- // // // Handle case insensitivity
268
- // // if (options.includes('i')) {
269
- // // sql = `LOWER(${sql}) REGEXP LOWER(?)`;
270
- // // } else {
271
- // // sql = `${sql} REGEXP ?`;
272
- // // }
273
-
274
- // if (options.includes('m')) {
275
- // sql = `EXISTS (
276
- // SELECT 1
277
- // FROM json_each(
278
- // json_array(
279
- // ${sql},
280
- // REPLACE(${sql}, CHAR(10), CHAR(13))
281
- // )
282
- // ) as lines
283
- // WHERE lines.value REGEXP ?
284
- // )`;
285
- // } else {
286
- // sql = `${sql} REGEXP ?`;
287
- // }
288
-
289
- // // Handle case insensitivity
290
- // if (options.includes('i')) {
291
- // sql = sql.replace('REGEXP ?', 'REGEXP LOWER(?)');
292
- // sql = sql.replace('value REGEXP', 'LOWER(value) REGEXP');
293
- // }
294
-
295
- // // Handle extended - allows whitespace and comments in pattern
296
- // if (options.includes('x')) {
297
- // // Remove whitespace and comments from pattern
298
- // const cleanPattern = pattern.replace(/\s+|#.*$/gm, '');
299
- // return {
300
- // sql,
301
- // values: [cleanPattern],
302
- // };
303
- // }
304
-
305
- // return {
306
- // sql,
307
- // values: [pattern],
308
- // };
309
- // },
310
- // }),
311
- $contains: (key: string, value: any) => {
312
- const jsonPathKey = parseJsonPathKey(key);
313
- let sql;
314
- if (Array.isArray(value)) {
315
- sql = `(
316
- SELECT ${validateJsonArray(jsonPathKey)}
317
- AND EXISTS (
318
- SELECT 1
319
- FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as m
320
- WHERE m.value IN (SELECT value FROM json_each(?))
321
- )
322
- )`;
323
- } else if (typeof value === 'string') {
324
- sql = `lower(json_extract(metadata, '$."${jsonPathKey}"')) LIKE '%' || lower(?) || '%' ESCAPE '\\'`;
325
- } else {
326
- sql = `json_extract(metadata, '$."${jsonPathKey}"') = ?`;
327
- }
328
- return {
329
- sql,
330
- needsValue: true,
331
- transformValue: () => {
332
- if (Array.isArray(value)) {
333
- return [JSON.stringify(value)];
334
- }
335
- if (typeof value === 'object' && value !== null) {
336
- return [JSON.stringify(value)];
337
- }
338
- if (typeof value === 'string') {
339
- return [escapeLikePattern(value)];
340
- }
341
- return [value];
342
- },
343
- };
344
- },
345
- /**
346
- * $objectContains: True JSON containment for advanced use (deep sub-object match).
347
- * Usage: { field: { $objectContains: { ...subobject } } }
348
- */
349
- // $objectContains: (key: string) => ({
350
- // sql: '', // Will be overridden by transformValue
351
- // needsValue: true,
352
- // transformValue: (value: any) => ({
353
- // sql: `json_type(json_extract(metadata, '$."${toJsonPathKey(key)}"')) = 'object'
354
- // AND json_patch(json_extract(metadata, '$."${toJsonPathKey(key)}"'), ?) = json_extract(metadata, '$."${toJsonPathKey(key)}"')`,
355
- // values: [JSON.stringify(value)],
356
- // }),
357
- // }),
358
- };
359
-
360
- interface FilterResult {
361
- sql: string;
362
- values: InValue[];
363
- }
364
-
365
- function isFilterResult(obj: any): obj is FilterResult {
366
- return obj && typeof obj === 'object' && typeof obj.sql === 'string' && Array.isArray(obj.values);
367
- }
368
-
369
- const parseJsonPathKey = (key: string) => {
370
- const parsedKey = parseFieldKey(key);
371
- // Only add quotes around path segments if they contain dots
372
- if (parsedKey.includes('.')) {
373
- return parsedKey
374
- .split('.')
375
- .map(segment => `"${segment}"`)
376
- .join('.');
377
- }
378
- return parsedKey;
379
- };
380
-
381
- // Helper to generate the correct JSON path format for LibSQL
382
- const getJsonPath = (key: string) => {
383
- const jsonPathKey = parseJsonPathKey(key);
384
- // Always use quotes for consistency
385
- return `'$.${jsonPathKey}'`;
386
- };
387
-
388
- function escapeLikePattern(str: string): string {
389
- return str.replace(/([%_\\])/g, '\\$1');
390
- }
391
-
392
- export function buildFilterQuery(filter: LibSQLVectorFilter): FilterResult {
393
- if (!filter) {
394
- return { sql: '', values: [] };
395
- }
396
-
397
- const values: InValue[] = [];
398
- const conditions = Object.entries(filter)
399
- .map(([key, value]) => {
400
- const condition = buildCondition(key, value, '');
401
- values.push(...condition.values);
402
- return condition.sql;
403
- })
404
- .join(' AND ');
405
-
406
- return {
407
- sql: conditions ? `WHERE ${conditions}` : '',
408
- values,
409
- };
410
- }
411
-
412
- function buildCondition(key: string, value: any, parentPath: string): FilterResult {
413
- // Handle logical operators ($and/$or)
414
- if (['$and', '$or', '$not', '$nor'].includes(key)) {
415
- return handleLogicalOperator(key as '$and' | '$or' | '$not' | '$nor', value, parentPath);
416
- }
417
-
418
- // If condition is not a FilterCondition object, assume it's an equality check
419
- if (!value || typeof value !== 'object') {
420
- const jsonPath = getJsonPath(key);
421
- return {
422
- sql: `json_extract(metadata, ${jsonPath}) = ?`,
423
- values: [value],
424
- };
425
- }
426
-
427
- //TODO: Add regex support
428
- // if ('$regex' in value) {
429
- // return handleRegexOperator(key, value);
430
- // }
431
-
432
- // Handle operator conditions
433
- return handleOperator(key, value);
434
- }
435
-
436
- // function handleRegexOperator(key: string, value: any): FilterResult {
437
- // const operatorFn = FILTER_OPERATORS['$regex']!;
438
- // const operatorResult = operatorFn(key, value);
439
- // const transformed = operatorResult.transformValue ? operatorResult.transformValue(value) : value;
440
-
441
- // return {
442
- // sql: transformed.sql,
443
- // values: transformed.values,
444
- // };
445
- // }
446
-
447
- function handleLogicalOperator(
448
- key: '$and' | '$or' | '$not' | '$nor',
449
- value: LibSQLVectorFilter[] | LibSQLVectorFilter,
450
- parentPath: string,
451
- ): FilterResult {
452
- // Handle empty conditions
453
- if (!value || (Array.isArray(value) && value.length === 0)) {
454
- switch (key) {
455
- case '$and':
456
- case '$nor':
457
- return { sql: 'true', values: [] };
458
- case '$or':
459
- return { sql: 'false', values: [] };
460
- case '$not':
461
- throw new Error('$not operator cannot be empty');
462
- default:
463
- return { sql: 'true', values: [] };
464
- }
465
- }
466
-
467
- if (key === '$not') {
468
- // For top-level $not
469
- const entries = Object.entries(value);
470
- const conditions = entries.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue, key));
471
- return {
472
- sql: `NOT (${conditions.map(c => c.sql).join(' AND ')})`,
473
- values: conditions.flatMap(c => c.values),
474
- };
475
- }
476
-
477
- const values: InValue[] = [];
478
- const joinOperator = key === '$or' || key === '$nor' ? 'OR' : 'AND';
479
- const conditions = Array.isArray(value)
480
- ? value.map(f => {
481
- const entries = !!f ? Object.entries(f) : [];
482
- return entries.map(([k, v]) => buildCondition(k, v, key));
483
- })
484
- : [buildCondition(key, value, parentPath)];
485
-
486
- const joined = conditions
487
- .flat()
488
- .map(c => {
489
- values.push(...c.values);
490
- return c.sql;
491
- })
492
- .join(` ${joinOperator} `);
493
-
494
- return {
495
- sql: key === '$nor' ? `NOT (${joined})` : `(${joined})`,
496
- values,
497
- };
498
- }
499
-
500
- function handleOperator(key: string, value: any): FilterResult {
501
- if (typeof value === 'object' && !Array.isArray(value)) {
502
- const entries = Object.entries(value);
503
- const results = entries.map(([operator, operatorValue]) =>
504
- operator === '$not'
505
- ? {
506
- sql: `NOT (${Object.entries(operatorValue as Record<string, any>)
507
- .map(([op, val]) => processOperator(key, op as OperatorType, val).sql)
508
- .join(' AND ')})`,
509
- values: Object.entries(operatorValue as Record<string, any>).flatMap(
510
- ([op, val]) => processOperator(key, op as OperatorType, val).values,
511
- ),
512
- }
513
- : processOperator(key, operator as OperatorType, operatorValue),
514
- );
515
-
516
- return {
517
- sql: `(${results.map(r => r.sql).join(' AND ')})`,
518
- values: results.flatMap(r => r.values),
519
- };
520
- }
521
-
522
- // Handle single operator
523
- const [[operator, operatorValue] = []] = Object.entries(value);
524
- return processOperator(key, operator as OperatorType, operatorValue);
525
- }
526
-
527
- const processOperator = (key: string, operator: OperatorType, operatorValue: any): FilterResult => {
528
- if (!operator.startsWith('$') || !FILTER_OPERATORS[operator]) {
529
- throw new Error(`Invalid operator: ${operator}`);
530
- }
531
- const operatorFn = FILTER_OPERATORS[operator]!;
532
- const operatorResult = operatorFn(key, operatorValue);
533
-
534
- if (!operatorResult.needsValue) {
535
- return { sql: operatorResult.sql, values: [] };
536
- }
537
-
538
- const transformed = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue;
539
-
540
- if (isFilterResult(transformed)) {
541
- return transformed;
542
- }
543
-
544
- return {
545
- sql: operatorResult.sql,
546
- values: Array.isArray(transformed) ? transformed : [transformed],
547
- };
548
- };
@@ -1,9 +0,0 @@
1
- {
2
- "extends": ["./tsconfig.json", "../../tsconfig.build.json"],
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src/**/*"],
8
- "exclude": ["node_modules", "**/*.test.ts", "src/**/*.mock.ts"]
9
- }
package/tsconfig.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.node.json",
3
- "include": ["src/**/*", "tsup.config.ts"],
4
- "exclude": ["node_modules", "**/*.test.ts"]
5
- }
package/tsup.config.ts DELETED
@@ -1,17 +0,0 @@
1
- import { generateTypes } from '@internal/types-builder';
2
- import { defineConfig } from 'tsup';
3
-
4
- export default defineConfig({
5
- entry: ['src/index.ts'],
6
- format: ['esm', 'cjs'],
7
- clean: true,
8
- dts: false,
9
- splitting: true,
10
- treeshake: {
11
- preset: 'smallest',
12
- },
13
- sourcemap: true,
14
- onSuccess: async () => {
15
- await generateTypes(process.cwd());
16
- },
17
- });
package/vitest.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- environment: 'node',
6
- include: ['src/**/*.test.ts'],
7
- coverage: {
8
- reporter: ['text', 'json', 'html'],
9
- },
10
- },
11
- });