@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 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: VSearchOptions): Promise<VSearchResult[]>;
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 = (await this.call('VGET', key));
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 = (await this.call('VSEARCH', ...args));
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 = (await this.call('TSGET', key));
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 = (await this.call('TSRANGE', ...args));
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 = (await this.call('TSMRANGE', ...args));
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 = (await this.call('TSINFO', key));
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.0",
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": "npm run build"
10
+ "prepublishOnly": "bun run build"
11
11
  },
12
12
  "dependencies": {
13
13
  "ioredis": "^5.0.0"