@luxdb/sdk 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +67 -10
- package/dist/index.js +142 -20
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import Redis, { RedisOptions } from 'ioredis';
|
|
1
|
+
import Redis, { type RedisOptions } from 'ioredis';
|
|
2
2
|
export interface VSearchResult {
|
|
3
3
|
key: string;
|
|
4
4
|
similarity: number;
|
|
5
5
|
metadata?: Record<string, unknown>;
|
|
6
6
|
}
|
|
7
|
-
export interface VSearchOptions {
|
|
8
|
-
k: number;
|
|
9
|
-
filter?: {
|
|
10
|
-
key: string;
|
|
11
|
-
value: string;
|
|
12
|
-
};
|
|
13
|
-
meta?: boolean;
|
|
14
|
-
}
|
|
15
7
|
export interface TSSample {
|
|
16
8
|
timestamp: number;
|
|
17
9
|
value: number;
|
|
@@ -36,8 +28,66 @@ export interface KSubEvent {
|
|
|
36
28
|
key: string;
|
|
37
29
|
operation: string;
|
|
38
30
|
}
|
|
31
|
+
export interface TableRow {
|
|
32
|
+
id: number;
|
|
33
|
+
[field: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
declare class TableQueryBuilder {
|
|
36
|
+
private client;
|
|
37
|
+
private name;
|
|
38
|
+
private conditions;
|
|
39
|
+
private orderField?;
|
|
40
|
+
private orderDir?;
|
|
41
|
+
private limitCount?;
|
|
42
|
+
private joinTable?;
|
|
43
|
+
constructor(client: Lux, name: string);
|
|
44
|
+
where(field: string, op: '=' | '!=' | '>' | '<' | '>=' | '<=', value: string | number | boolean): this;
|
|
45
|
+
orderBy(field: string, dir?: 'asc' | 'desc'): this;
|
|
46
|
+
limit(n: number): this;
|
|
47
|
+
join(table: string): this;
|
|
48
|
+
run(): Promise<TableRow[]>;
|
|
49
|
+
insert(data: Record<string, unknown>): Promise<number>;
|
|
50
|
+
update(id: number, data: Record<string, unknown>): Promise<string>;
|
|
51
|
+
delete(...ids: number[]): Promise<number>;
|
|
52
|
+
}
|
|
53
|
+
declare class VectorNamespace {
|
|
54
|
+
private client;
|
|
55
|
+
constructor(client: Lux);
|
|
56
|
+
set(key: string, vector: number[], metadata?: Record<string, unknown>): Promise<string>;
|
|
57
|
+
get(key: string): Promise<{
|
|
58
|
+
dims: number;
|
|
59
|
+
vector: number[];
|
|
60
|
+
metadata?: Record<string, unknown>;
|
|
61
|
+
} | null>;
|
|
62
|
+
search(query: number[], options: {
|
|
63
|
+
topK: number;
|
|
64
|
+
filter?: {
|
|
65
|
+
key: string;
|
|
66
|
+
value: string;
|
|
67
|
+
};
|
|
68
|
+
meta?: boolean;
|
|
69
|
+
}): Promise<VSearchResult[]>;
|
|
70
|
+
count(): Promise<number>;
|
|
71
|
+
}
|
|
72
|
+
declare class TimeSeriesNamespace {
|
|
73
|
+
private client;
|
|
74
|
+
constructor(client: Lux);
|
|
75
|
+
add(key: string, value: number, options?: {
|
|
76
|
+
timestamp?: number | '*';
|
|
77
|
+
retention?: number;
|
|
78
|
+
labels?: Record<string, string>;
|
|
79
|
+
}): Promise<number>;
|
|
80
|
+
get(key: string): Promise<TSSample | null>;
|
|
81
|
+
range(key: string, from: number | '-', to: number | '+', options?: TSRangeOptions): Promise<TSSample[]>;
|
|
82
|
+
mrange(from: number | '-', to: number | '+', filter: string, options?: TSRangeOptions): Promise<TSMRangeResult[]>;
|
|
83
|
+
info(key: string): Promise<Record<string, unknown>>;
|
|
84
|
+
}
|
|
39
85
|
export declare class Lux extends Redis {
|
|
86
|
+
vectors: VectorNamespace;
|
|
87
|
+
timeseries: TimeSeriesNamespace;
|
|
40
88
|
constructor(options?: RedisOptions | string);
|
|
89
|
+
table(name: string): TableQueryBuilder;
|
|
90
|
+
_tquery(args: string[]): Promise<TableRow[]>;
|
|
41
91
|
vset(key: string, vector: number[], options?: {
|
|
42
92
|
metadata?: Record<string, unknown>;
|
|
43
93
|
ex?: number;
|
|
@@ -48,7 +98,14 @@ export declare class Lux extends Redis {
|
|
|
48
98
|
vector: number[];
|
|
49
99
|
metadata?: Record<string, unknown>;
|
|
50
100
|
} | null>;
|
|
51
|
-
vsearch(query: number[], options:
|
|
101
|
+
vsearch(query: number[], options: {
|
|
102
|
+
k: number;
|
|
103
|
+
filter?: {
|
|
104
|
+
key: string;
|
|
105
|
+
value: string;
|
|
106
|
+
};
|
|
107
|
+
meta?: boolean;
|
|
108
|
+
}): Promise<VSearchResult[]>;
|
|
52
109
|
vcard(): Promise<number>;
|
|
53
110
|
tsadd(key: string, timestamp: number | '*', value: number, options?: TSAddOptions): Promise<number>;
|
|
54
111
|
tsmadd(...entries: [string, number | '*', number][]): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -5,10 +5,140 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Lux = void 0;
|
|
7
7
|
const ioredis_1 = __importDefault(require("ioredis"));
|
|
8
|
+
class TableQueryBuilder {
|
|
9
|
+
constructor(client, name) {
|
|
10
|
+
this.conditions = [];
|
|
11
|
+
this.client = client;
|
|
12
|
+
this.name = name;
|
|
13
|
+
}
|
|
14
|
+
where(field, op, value) {
|
|
15
|
+
const v = typeof value === 'string' ? `'${value}'` : String(value);
|
|
16
|
+
this.conditions.push(`${field} ${op} ${v}`);
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
orderBy(field, dir = 'asc') {
|
|
20
|
+
this.orderField = field;
|
|
21
|
+
this.orderDir = dir.toUpperCase();
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
limit(n) {
|
|
25
|
+
this.limitCount = n;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
join(table) {
|
|
29
|
+
this.joinTable = table;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
async run() {
|
|
33
|
+
const args = [this.name];
|
|
34
|
+
if (this.conditions.length) {
|
|
35
|
+
args.push('WHERE', this.conditions.join(' AND '));
|
|
36
|
+
}
|
|
37
|
+
if (this.orderField) {
|
|
38
|
+
args.push('ORDER', 'BY', this.orderField, this.orderDir || 'ASC');
|
|
39
|
+
}
|
|
40
|
+
if (this.limitCount != null) {
|
|
41
|
+
args.push('LIMIT', String(this.limitCount));
|
|
42
|
+
}
|
|
43
|
+
if (this.joinTable) {
|
|
44
|
+
args.push('JOIN', this.joinTable);
|
|
45
|
+
}
|
|
46
|
+
return this.client._tquery(args);
|
|
47
|
+
}
|
|
48
|
+
async insert(data) {
|
|
49
|
+
const args = [this.name];
|
|
50
|
+
for (const [k, v] of Object.entries(data)) {
|
|
51
|
+
args.push(k, String(v));
|
|
52
|
+
}
|
|
53
|
+
const result = await this.client.call('TINSERT', ...args);
|
|
54
|
+
return parseInt(result, 10) || 0;
|
|
55
|
+
}
|
|
56
|
+
async update(id, data) {
|
|
57
|
+
const args = [this.name, id];
|
|
58
|
+
for (const [k, v] of Object.entries(data)) {
|
|
59
|
+
args.push(k, String(v));
|
|
60
|
+
}
|
|
61
|
+
return this.client.call('TUPDATE', ...args);
|
|
62
|
+
}
|
|
63
|
+
async delete(...ids) {
|
|
64
|
+
return this.client.call('TDEL', this.name, ...ids);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
class VectorNamespace {
|
|
68
|
+
constructor(client) {
|
|
69
|
+
this.client = client;
|
|
70
|
+
}
|
|
71
|
+
async set(key, vector, metadata) {
|
|
72
|
+
const args = [key, vector.length, ...vector];
|
|
73
|
+
if (metadata) {
|
|
74
|
+
args.push('META', JSON.stringify(metadata));
|
|
75
|
+
}
|
|
76
|
+
return this.client.call('VSET', ...args);
|
|
77
|
+
}
|
|
78
|
+
async get(key) {
|
|
79
|
+
return this.client.vget(key);
|
|
80
|
+
}
|
|
81
|
+
async search(query, options) {
|
|
82
|
+
return this.client.vsearch(query, { k: options.topK, filter: options.filter, meta: options.meta ?? true });
|
|
83
|
+
}
|
|
84
|
+
async count() {
|
|
85
|
+
return this.client.vcard();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
class TimeSeriesNamespace {
|
|
89
|
+
constructor(client) {
|
|
90
|
+
this.client = client;
|
|
91
|
+
}
|
|
92
|
+
async add(key, value, options) {
|
|
93
|
+
return this.client.tsadd(key, options?.timestamp ?? '*', value, { retention: options?.retention, labels: options?.labels });
|
|
94
|
+
}
|
|
95
|
+
async get(key) {
|
|
96
|
+
return this.client.tsget(key);
|
|
97
|
+
}
|
|
98
|
+
async range(key, from, to, options) {
|
|
99
|
+
return this.client.tsrange(key, from, to, options);
|
|
100
|
+
}
|
|
101
|
+
async mrange(from, to, filter, options) {
|
|
102
|
+
return this.client.tsmrange(from, to, filter, options);
|
|
103
|
+
}
|
|
104
|
+
async info(key) {
|
|
105
|
+
return this.client.tsinfo(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
8
108
|
class Lux extends ioredis_1.default {
|
|
9
109
|
constructor(options) {
|
|
110
|
+
if (typeof options === 'string') {
|
|
111
|
+
if (options.startsWith('rediss://') || options.startsWith('luxs://')) {
|
|
112
|
+
throw new Error('TLS is not yet supported');
|
|
113
|
+
}
|
|
114
|
+
options = options.replace(/^lux:\/\//, 'redis://');
|
|
115
|
+
}
|
|
10
116
|
super(options);
|
|
117
|
+
this.vectors = new VectorNamespace(this);
|
|
118
|
+
this.timeseries = new TimeSeriesNamespace(this);
|
|
11
119
|
}
|
|
120
|
+
table(name) {
|
|
121
|
+
return new TableQueryBuilder(this, name);
|
|
122
|
+
}
|
|
123
|
+
async _tquery(args) {
|
|
124
|
+
const result = await this.call('TQUERY', ...args);
|
|
125
|
+
if (!result || !Array.isArray(result))
|
|
126
|
+
return [];
|
|
127
|
+
const rows = [];
|
|
128
|
+
for (const item of result) {
|
|
129
|
+
if (Array.isArray(item) && item.length >= 1) {
|
|
130
|
+
const row = { id: parseInt(item[0], 10) };
|
|
131
|
+
for (let i = 1; i < item.length - 1; i += 2) {
|
|
132
|
+
const key = String(item[i]);
|
|
133
|
+
const val = item[i + 1];
|
|
134
|
+
row[key] = val;
|
|
135
|
+
}
|
|
136
|
+
rows.push(row);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return rows;
|
|
140
|
+
}
|
|
141
|
+
// Vector methods (keep for backward compat)
|
|
12
142
|
async vset(key, vector, options) {
|
|
13
143
|
const args = [key, vector.length, ...vector];
|
|
14
144
|
if (options?.metadata) {
|
|
@@ -23,7 +153,7 @@ class Lux extends ioredis_1.default {
|
|
|
23
153
|
return this.call('VSET', ...args);
|
|
24
154
|
}
|
|
25
155
|
async vget(key) {
|
|
26
|
-
const result =
|
|
156
|
+
const result = await this.call('VGET', key);
|
|
27
157
|
if (!result || !Array.isArray(result))
|
|
28
158
|
return null;
|
|
29
159
|
const dims = parseInt(result[0], 10);
|
|
@@ -49,16 +179,13 @@ class Lux extends ioredis_1.default {
|
|
|
49
179
|
if (options.meta) {
|
|
50
180
|
args.push('META');
|
|
51
181
|
}
|
|
52
|
-
const result =
|
|
182
|
+
const result = await this.call('VSEARCH', ...args);
|
|
53
183
|
if (!result || !Array.isArray(result))
|
|
54
184
|
return [];
|
|
55
185
|
const results = [];
|
|
56
186
|
for (const item of result) {
|
|
57
187
|
if (Array.isArray(item)) {
|
|
58
|
-
const entry = {
|
|
59
|
-
key: item[0],
|
|
60
|
-
similarity: parseFloat(item[1]),
|
|
61
|
-
};
|
|
188
|
+
const entry = { key: item[0], similarity: parseFloat(item[1]) };
|
|
62
189
|
if (options.meta && item[2]) {
|
|
63
190
|
try {
|
|
64
191
|
entry.metadata = JSON.parse(item[2]);
|
|
@@ -75,6 +202,7 @@ class Lux extends ioredis_1.default {
|
|
|
75
202
|
async vcard() {
|
|
76
203
|
return this.call('VCARD');
|
|
77
204
|
}
|
|
205
|
+
// Time series methods (keep for backward compat)
|
|
78
206
|
async tsadd(key, timestamp, value, options) {
|
|
79
207
|
const args = [key, timestamp === '*' ? '*' : timestamp, value];
|
|
80
208
|
if (options?.retention != null) {
|
|
@@ -96,7 +224,7 @@ class Lux extends ioredis_1.default {
|
|
|
96
224
|
return this.call('TSMADD', ...args);
|
|
97
225
|
}
|
|
98
226
|
async tsget(key) {
|
|
99
|
-
const result =
|
|
227
|
+
const result = await this.call('TSGET', key);
|
|
100
228
|
if (!result || !Array.isArray(result) || result.length < 2)
|
|
101
229
|
return null;
|
|
102
230
|
return { timestamp: parseInt(result[0], 10), value: parseFloat(result[1]) };
|
|
@@ -106,13 +234,10 @@ class Lux extends ioredis_1.default {
|
|
|
106
234
|
if (options?.aggregation) {
|
|
107
235
|
args.push('AGGREGATION', options.aggregation.type, options.aggregation.bucketSize);
|
|
108
236
|
}
|
|
109
|
-
const result =
|
|
237
|
+
const result = await this.call('TSRANGE', ...args);
|
|
110
238
|
if (!result || !Array.isArray(result))
|
|
111
239
|
return [];
|
|
112
|
-
return result.map((pair) => ({
|
|
113
|
-
timestamp: parseInt(pair[0], 10),
|
|
114
|
-
value: parseFloat(pair[1]),
|
|
115
|
-
}));
|
|
240
|
+
return result.map((pair) => ({ timestamp: parseInt(pair[0], 10), value: parseFloat(pair[1]) }));
|
|
116
241
|
}
|
|
117
242
|
async tsmrange(from, to, filter, options) {
|
|
118
243
|
const args = [from === '-' ? '-' : from, to === '+' ? '+' : to];
|
|
@@ -120,16 +245,15 @@ class Lux extends ioredis_1.default {
|
|
|
120
245
|
args.push('AGGREGATION', options.aggregation.type, options.aggregation.bucketSize);
|
|
121
246
|
}
|
|
122
247
|
args.push('FILTER', filter);
|
|
123
|
-
const result =
|
|
248
|
+
const result = await this.call('TSMRANGE', ...args);
|
|
124
249
|
if (!result || !Array.isArray(result))
|
|
125
250
|
return [];
|
|
126
251
|
return result.map((series) => {
|
|
127
252
|
const labels = {};
|
|
128
253
|
if (Array.isArray(series[1])) {
|
|
129
254
|
for (const pair of series[1]) {
|
|
130
|
-
if (Array.isArray(pair) && pair.length >= 2)
|
|
255
|
+
if (Array.isArray(pair) && pair.length >= 2)
|
|
131
256
|
labels[pair[0]] = pair[1];
|
|
132
|
-
}
|
|
133
257
|
}
|
|
134
258
|
}
|
|
135
259
|
const samples = Array.isArray(series[2])
|
|
@@ -139,7 +263,7 @@ class Lux extends ioredis_1.default {
|
|
|
139
263
|
});
|
|
140
264
|
}
|
|
141
265
|
async tsinfo(key) {
|
|
142
|
-
const result =
|
|
266
|
+
const result = await this.call('TSINFO', key);
|
|
143
267
|
if (!result || !Array.isArray(result))
|
|
144
268
|
return {};
|
|
145
269
|
const info = {};
|
|
@@ -160,10 +284,10 @@ class Lux extends ioredis_1.default {
|
|
|
160
284
|
}
|
|
161
285
|
return info;
|
|
162
286
|
}
|
|
287
|
+
// Realtime key subscriptions
|
|
163
288
|
ksub(patterns, handler) {
|
|
164
289
|
const sub = this.duplicate();
|
|
165
290
|
sub.on('error', () => { });
|
|
166
|
-
const origReturnReply = sub.returnReply?.bind(sub);
|
|
167
291
|
const dataHandler = sub._dataHandler || sub.dataHandler;
|
|
168
292
|
if (dataHandler && dataHandler.returnReply) {
|
|
169
293
|
const origReturn = dataHandler.returnReply.bind(dataHandler);
|
|
@@ -191,9 +315,7 @@ class Lux extends ioredis_1.default {
|
|
|
191
315
|
sub.call('KSUB', ...patterns);
|
|
192
316
|
return {
|
|
193
317
|
connection: sub,
|
|
194
|
-
unsubscribe() {
|
|
195
|
-
sub.disconnect();
|
|
196
|
-
},
|
|
318
|
+
unsubscribe() { sub.disconnect(); },
|
|
197
319
|
};
|
|
198
320
|
}
|
|
199
321
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luxdb/sdk",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Lux SDK - ioredis extended with vector search, time series, and realtime key subscriptions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": ["dist"],
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
|
-
"prepublishOnly": "
|
|
10
|
+
"prepublishOnly": "bun run build"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"ioredis": "^5.0.0"
|