@luxdb/sdk 1.2.1 → 1.3.0
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 +11 -85
- package/dist/index.js +32 -110
- package/dist/namespaces.d.ts +56 -0
- package/dist/namespaces.js +46 -0
- package/dist/realtime.d.ts +16 -0
- package/dist/realtime.js +88 -0
- package/dist/table.d.ts +83 -0
- package/dist/table.js +384 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +17 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,93 +1,18 @@
|
|
|
1
1
|
import Redis, { type RedisOptions } from 'ioredis';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
export
|
|
8
|
-
timestamp: number;
|
|
9
|
-
value: number;
|
|
10
|
-
}
|
|
11
|
-
export interface TSAddOptions {
|
|
12
|
-
retention?: number;
|
|
13
|
-
labels?: Record<string, string>;
|
|
14
|
-
}
|
|
15
|
-
export interface TSRangeOptions {
|
|
16
|
-
aggregation?: {
|
|
17
|
-
type: 'avg' | 'sum' | 'min' | 'max' | 'count' | 'first' | 'last' | 'range' | 'std.p' | 'std.s' | 'var.p' | 'var.s';
|
|
18
|
-
bucketSize: number;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
export interface TSMRangeResult {
|
|
22
|
-
key: string;
|
|
23
|
-
labels: Record<string, string>;
|
|
24
|
-
samples: TSSample[];
|
|
25
|
-
}
|
|
26
|
-
export interface KSubEvent {
|
|
27
|
-
pattern: string;
|
|
28
|
-
key: string;
|
|
29
|
-
operation: string;
|
|
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
|
-
}
|
|
2
|
+
import { TimeSeriesNamespace, VectorNamespace } from './namespaces';
|
|
3
|
+
import { TableQueryBuilder, type TableQueryBuilderOptions } from './table';
|
|
4
|
+
import type { KSubEvent, TableRow, TSAddOptions, TSMRangeResult, TSRangeOptions, TSSample, VSearchResult } from './types';
|
|
5
|
+
export type { KSubEvent, LuxError, LuxResult, TableChangeEvent, TableChangeType, TableErrorEvent, TableRow, TableSchema, TSAddOptions, TSMRangeResult, TSRangeOptions, TSSample, VSearchResult, } from './types';
|
|
6
|
+
export { TableQueryBuilder, TableSubscription } from './table';
|
|
7
|
+
export type { TableQueryBuilderOptions } from './table';
|
|
85
8
|
export declare class Lux extends Redis {
|
|
86
9
|
vectors: VectorNamespace;
|
|
87
10
|
timeseries: TimeSeriesNamespace;
|
|
11
|
+
private realtimeManager?;
|
|
88
12
|
constructor(options?: RedisOptions | string);
|
|
89
|
-
table(name: string): TableQueryBuilder
|
|
90
|
-
|
|
13
|
+
table<T extends TableRow = TableRow>(name: string, options?: TableQueryBuilderOptions<T>): TableQueryBuilder<T>;
|
|
14
|
+
_subscribePattern(pattern: string, handler: (event: KSubEvent) => void): Promise<() => void>;
|
|
15
|
+
_tselect(args: string[]): Promise<TableRow[]>;
|
|
91
16
|
vset(key: string, vector: number[], options?: {
|
|
92
17
|
metadata?: Record<string, unknown>;
|
|
93
18
|
ex?: number;
|
|
@@ -118,4 +43,5 @@ export declare class Lux extends Redis {
|
|
|
118
43
|
connection: Redis;
|
|
119
44
|
};
|
|
120
45
|
}
|
|
46
|
+
export declare function createClient(options?: RedisOptions | string): Lux;
|
|
121
47
|
export default Lux;
|
package/dist/index.js
CHANGED
|
@@ -3,108 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Lux = void 0;
|
|
6
|
+
exports.Lux = exports.TableSubscription = exports.TableQueryBuilder = void 0;
|
|
7
|
+
exports.createClient = createClient;
|
|
7
8
|
const ioredis_1 = __importDefault(require("ioredis"));
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
}
|
|
9
|
+
const namespaces_1 = require("./namespaces");
|
|
10
|
+
const realtime_1 = require("./realtime");
|
|
11
|
+
const table_1 = require("./table");
|
|
12
|
+
var table_2 = require("./table");
|
|
13
|
+
Object.defineProperty(exports, "TableQueryBuilder", { enumerable: true, get: function () { return table_2.TableQueryBuilder; } });
|
|
14
|
+
Object.defineProperty(exports, "TableSubscription", { enumerable: true, get: function () { return table_2.TableSubscription; } });
|
|
108
15
|
class Lux extends ioredis_1.default {
|
|
109
16
|
constructor(options) {
|
|
110
17
|
if (typeof options === 'string') {
|
|
@@ -114,25 +21,37 @@ class Lux extends ioredis_1.default {
|
|
|
114
21
|
options = options.replace(/^lux:\/\//, 'redis://');
|
|
115
22
|
}
|
|
116
23
|
super(options);
|
|
117
|
-
this.vectors = new VectorNamespace(this);
|
|
118
|
-
this.timeseries = new TimeSeriesNamespace(this);
|
|
24
|
+
this.vectors = new namespaces_1.VectorNamespace(this);
|
|
25
|
+
this.timeseries = new namespaces_1.TimeSeriesNamespace(this);
|
|
119
26
|
}
|
|
120
|
-
table(name) {
|
|
121
|
-
return new TableQueryBuilder(this, name);
|
|
27
|
+
table(name, options) {
|
|
28
|
+
return new table_1.TableQueryBuilder(this, name, options);
|
|
29
|
+
}
|
|
30
|
+
async _subscribePattern(pattern, handler) {
|
|
31
|
+
if (!this.realtimeManager) {
|
|
32
|
+
this.realtimeManager = new realtime_1.LuxRealtimeManager(this);
|
|
33
|
+
}
|
|
34
|
+
return this.realtimeManager.subscribe(pattern, handler);
|
|
122
35
|
}
|
|
123
|
-
async
|
|
124
|
-
const result = await this.call('
|
|
36
|
+
async _tselect(args) {
|
|
37
|
+
const result = await this.call('TSELECT', ...args);
|
|
125
38
|
if (!result || !Array.isArray(result))
|
|
126
39
|
return [];
|
|
127
40
|
const rows = [];
|
|
128
41
|
for (const item of result) {
|
|
129
|
-
if (Array.isArray(item)
|
|
130
|
-
const row = {
|
|
131
|
-
for (let i =
|
|
42
|
+
if (Array.isArray(item)) {
|
|
43
|
+
const row = {};
|
|
44
|
+
for (let i = 0; i < item.length - 1; i += 2) {
|
|
132
45
|
const key = String(item[i]);
|
|
133
46
|
const val = item[i + 1];
|
|
134
47
|
row[key] = val;
|
|
135
48
|
}
|
|
49
|
+
if (row.id != null) {
|
|
50
|
+
const parsed = Number(row.id);
|
|
51
|
+
if (!Number.isNaN(parsed) && Number.isFinite(parsed)) {
|
|
52
|
+
row.id = parsed;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
136
55
|
rows.push(row);
|
|
137
56
|
}
|
|
138
57
|
}
|
|
@@ -320,4 +239,7 @@ class Lux extends ioredis_1.default {
|
|
|
320
239
|
}
|
|
321
240
|
}
|
|
322
241
|
exports.Lux = Lux;
|
|
242
|
+
function createClient(options) {
|
|
243
|
+
return new Lux(options);
|
|
244
|
+
}
|
|
323
245
|
exports.default = Lux;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { TSAddOptions, TSMRangeResult, TSRangeOptions, TSSample, VSearchResult } from './types';
|
|
2
|
+
interface NamespaceClient {
|
|
3
|
+
call(command: string, ...args: Array<string | number>): Promise<unknown>;
|
|
4
|
+
vget(key: string): Promise<{
|
|
5
|
+
dims: number;
|
|
6
|
+
vector: number[];
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
} | null>;
|
|
9
|
+
vsearch(query: number[], options: {
|
|
10
|
+
k: number;
|
|
11
|
+
filter?: {
|
|
12
|
+
key: string;
|
|
13
|
+
value: string;
|
|
14
|
+
};
|
|
15
|
+
meta?: boolean;
|
|
16
|
+
}): Promise<VSearchResult[]>;
|
|
17
|
+
vcard(): Promise<number>;
|
|
18
|
+
tsadd(key: string, timestamp: number | '*', value: number, options?: TSAddOptions): Promise<number>;
|
|
19
|
+
tsget(key: string): Promise<TSSample | null>;
|
|
20
|
+
tsrange(key: string, from: number | '-', to: number | '+', options?: TSRangeOptions): Promise<TSSample[]>;
|
|
21
|
+
tsmrange(from: number | '-', to: number | '+', filter: string, options?: TSRangeOptions): Promise<TSMRangeResult[]>;
|
|
22
|
+
tsinfo(key: string): Promise<Record<string, unknown>>;
|
|
23
|
+
}
|
|
24
|
+
export declare class VectorNamespace {
|
|
25
|
+
private client;
|
|
26
|
+
constructor(client: NamespaceClient);
|
|
27
|
+
set(key: string, vector: number[], metadata?: Record<string, unknown>): Promise<string>;
|
|
28
|
+
get(key: string): Promise<{
|
|
29
|
+
dims: number;
|
|
30
|
+
vector: number[];
|
|
31
|
+
metadata?: Record<string, unknown>;
|
|
32
|
+
} | null>;
|
|
33
|
+
search(query: number[], options: {
|
|
34
|
+
topK: number;
|
|
35
|
+
filter?: {
|
|
36
|
+
key: string;
|
|
37
|
+
value: string;
|
|
38
|
+
};
|
|
39
|
+
meta?: boolean;
|
|
40
|
+
}): Promise<VSearchResult[]>;
|
|
41
|
+
count(): Promise<number>;
|
|
42
|
+
}
|
|
43
|
+
export declare class TimeSeriesNamespace {
|
|
44
|
+
private client;
|
|
45
|
+
constructor(client: NamespaceClient);
|
|
46
|
+
add(key: string, value: number, options?: {
|
|
47
|
+
timestamp?: number | '*';
|
|
48
|
+
retention?: number;
|
|
49
|
+
labels?: Record<string, string>;
|
|
50
|
+
}): Promise<number>;
|
|
51
|
+
get(key: string): Promise<TSSample | null>;
|
|
52
|
+
range(key: string, from: number | '-', to: number | '+', options?: TSRangeOptions): Promise<TSSample[]>;
|
|
53
|
+
mrange(from: number | '-', to: number | '+', filter: string, options?: TSRangeOptions): Promise<TSMRangeResult[]>;
|
|
54
|
+
info(key: string): Promise<Record<string, unknown>>;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TimeSeriesNamespace = exports.VectorNamespace = void 0;
|
|
4
|
+
class VectorNamespace {
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
}
|
|
8
|
+
async set(key, vector, metadata) {
|
|
9
|
+
const args = [key, vector.length, ...vector];
|
|
10
|
+
if (metadata) {
|
|
11
|
+
args.push('META', JSON.stringify(metadata));
|
|
12
|
+
}
|
|
13
|
+
return this.client.call('VSET', ...args);
|
|
14
|
+
}
|
|
15
|
+
async get(key) {
|
|
16
|
+
return this.client.vget(key);
|
|
17
|
+
}
|
|
18
|
+
async search(query, options) {
|
|
19
|
+
return this.client.vsearch(query, { k: options.topK, filter: options.filter, meta: options.meta ?? true });
|
|
20
|
+
}
|
|
21
|
+
async count() {
|
|
22
|
+
return this.client.vcard();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.VectorNamespace = VectorNamespace;
|
|
26
|
+
class TimeSeriesNamespace {
|
|
27
|
+
constructor(client) {
|
|
28
|
+
this.client = client;
|
|
29
|
+
}
|
|
30
|
+
async add(key, value, options) {
|
|
31
|
+
return this.client.tsadd(key, options?.timestamp ?? '*', value, { retention: options?.retention, labels: options?.labels });
|
|
32
|
+
}
|
|
33
|
+
async get(key) {
|
|
34
|
+
return this.client.tsget(key);
|
|
35
|
+
}
|
|
36
|
+
async range(key, from, to, options) {
|
|
37
|
+
return this.client.tsrange(key, from, to, options);
|
|
38
|
+
}
|
|
39
|
+
async mrange(from, to, filter, options) {
|
|
40
|
+
return this.client.tsmrange(from, to, filter, options);
|
|
41
|
+
}
|
|
42
|
+
async info(key) {
|
|
43
|
+
return this.client.tsinfo(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.TimeSeriesNamespace = TimeSeriesNamespace;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type Redis from 'ioredis';
|
|
2
|
+
import type { KSubEvent } from './types';
|
|
3
|
+
interface RealtimeClient {
|
|
4
|
+
duplicate(): Redis;
|
|
5
|
+
}
|
|
6
|
+
export declare class LuxRealtimeManager {
|
|
7
|
+
private client;
|
|
8
|
+
private connection;
|
|
9
|
+
private initPromise;
|
|
10
|
+
private nextHandlerId;
|
|
11
|
+
private handlersByPattern;
|
|
12
|
+
constructor(client: RealtimeClient);
|
|
13
|
+
private ensureConnection;
|
|
14
|
+
subscribe(pattern: string, handler: (event: KSubEvent) => void): Promise<() => void>;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
package/dist/realtime.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LuxRealtimeManager = void 0;
|
|
4
|
+
class LuxRealtimeManager {
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.connection = null;
|
|
7
|
+
this.initPromise = null;
|
|
8
|
+
this.nextHandlerId = 1;
|
|
9
|
+
this.handlersByPattern = new Map();
|
|
10
|
+
this.client = client;
|
|
11
|
+
}
|
|
12
|
+
async ensureConnection() {
|
|
13
|
+
if (this.connection)
|
|
14
|
+
return;
|
|
15
|
+
if (this.initPromise)
|
|
16
|
+
return this.initPromise;
|
|
17
|
+
this.initPromise = (async () => {
|
|
18
|
+
const sub = this.client.duplicate();
|
|
19
|
+
sub.on('error', () => { });
|
|
20
|
+
const dispatch = (event) => {
|
|
21
|
+
const handlers = this.handlersByPattern.get(event.pattern);
|
|
22
|
+
if (!handlers)
|
|
23
|
+
return;
|
|
24
|
+
for (const handler of handlers.values()) {
|
|
25
|
+
handler(event);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const dataHandler = sub._dataHandler || sub.dataHandler;
|
|
29
|
+
if (dataHandler && dataHandler.returnReply) {
|
|
30
|
+
const origReturn = dataHandler.returnReply.bind(dataHandler);
|
|
31
|
+
dataHandler.returnReply = (reply) => {
|
|
32
|
+
if (Array.isArray(reply) && reply.length === 4 && reply[0] === 'kmessage') {
|
|
33
|
+
dispatch({ pattern: reply[1], key: reply[2], operation: reply[3] });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
return origReturn(reply);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const origEmit = sub.emit.bind(sub);
|
|
41
|
+
sub.emit = (event, ...args) => {
|
|
42
|
+
if (event === 'error' && args[0]?.message?.includes('Command queue state error')) {
|
|
43
|
+
const match = args[0].message.match(/Last reply: kmessage,([^,]+),([^,]+),(.+)/);
|
|
44
|
+
if (match) {
|
|
45
|
+
dispatch({ pattern: match[1], key: match[2], operation: match[3] });
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return origEmit(event, ...args);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
this.connection = sub;
|
|
53
|
+
})();
|
|
54
|
+
try {
|
|
55
|
+
await this.initPromise;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
this.initPromise = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async subscribe(pattern, handler) {
|
|
62
|
+
await this.ensureConnection();
|
|
63
|
+
const id = this.nextHandlerId++;
|
|
64
|
+
let handlers = this.handlersByPattern.get(pattern);
|
|
65
|
+
const firstForPattern = !handlers;
|
|
66
|
+
if (!handlers) {
|
|
67
|
+
handlers = new Map();
|
|
68
|
+
this.handlersByPattern.set(pattern, handlers);
|
|
69
|
+
}
|
|
70
|
+
handlers.set(id, handler);
|
|
71
|
+
if (firstForPattern && this.connection) {
|
|
72
|
+
await this.connection.call('KSUB', pattern);
|
|
73
|
+
}
|
|
74
|
+
return () => {
|
|
75
|
+
const map = this.handlersByPattern.get(pattern);
|
|
76
|
+
if (!map)
|
|
77
|
+
return;
|
|
78
|
+
map.delete(id);
|
|
79
|
+
if (map.size === 0) {
|
|
80
|
+
this.handlersByPattern.delete(pattern);
|
|
81
|
+
if (this.connection) {
|
|
82
|
+
this.connection.call('KUNSUB', pattern).catch(() => { });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.LuxRealtimeManager = LuxRealtimeManager;
|
package/dist/table.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { KSubEvent, LuxError, LuxResult, TableChangeEvent, TableErrorEvent, TableRow, TableSchema, VSearchResult } from './types';
|
|
2
|
+
type TableWhereOp = '=' | '!=' | '>' | '<' | '>=' | '<=';
|
|
3
|
+
type TableWhereValue = string | number | boolean;
|
|
4
|
+
interface TableWhereCondition {
|
|
5
|
+
field: string;
|
|
6
|
+
op: TableWhereOp;
|
|
7
|
+
value: TableWhereValue;
|
|
8
|
+
}
|
|
9
|
+
interface TableClient {
|
|
10
|
+
call(command: string, ...args: Array<string | number>): Promise<unknown>;
|
|
11
|
+
_tselect(args: string[]): Promise<TableRow[]>;
|
|
12
|
+
_subscribePattern(pattern: string, handler: (event: KSubEvent) => void): Promise<() => void>;
|
|
13
|
+
vsearch(query: number[], options: {
|
|
14
|
+
k: number;
|
|
15
|
+
filter?: {
|
|
16
|
+
key: string;
|
|
17
|
+
value: string;
|
|
18
|
+
};
|
|
19
|
+
meta?: boolean;
|
|
20
|
+
}): Promise<VSearchResult[]>;
|
|
21
|
+
}
|
|
22
|
+
export interface TableQueryBuilderOptions<T extends TableRow> {
|
|
23
|
+
schema?: TableSchema<T>;
|
|
24
|
+
}
|
|
25
|
+
export declare class TableSubscription<T extends TableRow> {
|
|
26
|
+
private client;
|
|
27
|
+
private table;
|
|
28
|
+
private selectArgsBuilder;
|
|
29
|
+
private handlers;
|
|
30
|
+
private knownRows;
|
|
31
|
+
private unsubscribeFn;
|
|
32
|
+
private initError;
|
|
33
|
+
constructor(client: TableClient, table: string, selectArgsBuilder: (extra?: TableWhereCondition[]) => string[], initError?: LuxError | null);
|
|
34
|
+
on(event: 'insert' | 'update' | 'delete' | 'change', handler: (event: TableChangeEvent<T>) => void): this;
|
|
35
|
+
on(event: 'error', handler: (event: TableErrorEvent) => void): this;
|
|
36
|
+
unsubscribe(): Promise<void>;
|
|
37
|
+
private emitError;
|
|
38
|
+
private emitChange;
|
|
39
|
+
private extractPkFromKey;
|
|
40
|
+
private fetchMatches;
|
|
41
|
+
private start;
|
|
42
|
+
private handleRawChange;
|
|
43
|
+
}
|
|
44
|
+
export declare class TableQueryBuilder<T extends TableRow = TableRow> {
|
|
45
|
+
private client;
|
|
46
|
+
private name;
|
|
47
|
+
private conditions;
|
|
48
|
+
private orderField?;
|
|
49
|
+
private orderDir?;
|
|
50
|
+
private limitCount?;
|
|
51
|
+
private offsetCount?;
|
|
52
|
+
private joinClause?;
|
|
53
|
+
private similarityClause?;
|
|
54
|
+
private selectClause;
|
|
55
|
+
private expectSingle;
|
|
56
|
+
private schema?;
|
|
57
|
+
constructor(client: TableClient, name: string, options?: TableQueryBuilderOptions<T>);
|
|
58
|
+
private validateRow;
|
|
59
|
+
private buildSelectArgs;
|
|
60
|
+
select(columns?: string): this;
|
|
61
|
+
single(): this;
|
|
62
|
+
where(field: string, op: TableWhereOp, value: TableWhereValue): this;
|
|
63
|
+
orderBy(field: string, dir?: 'asc' | 'desc'): this;
|
|
64
|
+
limit(n: number): this;
|
|
65
|
+
offset(n: number): this;
|
|
66
|
+
join(table: string, alias: string, onLeft: string, onRight: string): this;
|
|
67
|
+
similar(field: string, vector: number[], options: {
|
|
68
|
+
k: number;
|
|
69
|
+
filter?: {
|
|
70
|
+
key: string;
|
|
71
|
+
value: string;
|
|
72
|
+
};
|
|
73
|
+
}): this;
|
|
74
|
+
private parseSimilarityPk;
|
|
75
|
+
run(): Promise<LuxResult<T[] | T>>;
|
|
76
|
+
insert(data: Record<string, unknown>): Promise<LuxResult<number>>;
|
|
77
|
+
update(id: number | string, data: Record<string, unknown>): Promise<LuxResult<number>>;
|
|
78
|
+
updateWhere(data: Record<string, unknown>): Promise<LuxResult<number>>;
|
|
79
|
+
delete(...ids: Array<number | string>): Promise<LuxResult<number>>;
|
|
80
|
+
deleteWhere(): Promise<LuxResult<number>>;
|
|
81
|
+
subscribe(): TableSubscription<T>;
|
|
82
|
+
}
|
|
83
|
+
export {};
|
package/dist/table.js
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TableQueryBuilder = exports.TableSubscription = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
class TableSubscription {
|
|
6
|
+
constructor(client, table, selectArgsBuilder, initError = null) {
|
|
7
|
+
this.handlers = {
|
|
8
|
+
change: [],
|
|
9
|
+
insert: [],
|
|
10
|
+
update: [],
|
|
11
|
+
delete: [],
|
|
12
|
+
error: [],
|
|
13
|
+
};
|
|
14
|
+
this.knownRows = new Map();
|
|
15
|
+
this.unsubscribeFn = null;
|
|
16
|
+
this.client = client;
|
|
17
|
+
this.table = table;
|
|
18
|
+
this.selectArgsBuilder = selectArgsBuilder;
|
|
19
|
+
this.initError = initError;
|
|
20
|
+
void this.start();
|
|
21
|
+
}
|
|
22
|
+
on(event, handler) {
|
|
23
|
+
this.handlers[event].push(handler);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
async unsubscribe() {
|
|
27
|
+
if (this.unsubscribeFn) {
|
|
28
|
+
this.unsubscribeFn();
|
|
29
|
+
this.unsubscribeFn = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
emitError(error) {
|
|
33
|
+
for (const handler of this.handlers.error) {
|
|
34
|
+
handler({ type: 'error', table: this.table, error });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
emitChange(event) {
|
|
38
|
+
for (const handler of this.handlers.change)
|
|
39
|
+
handler(event);
|
|
40
|
+
for (const handler of this.handlers[event.type])
|
|
41
|
+
handler(event);
|
|
42
|
+
}
|
|
43
|
+
extractPkFromKey(key) {
|
|
44
|
+
const prefix = `_t:${this.table}:row:`;
|
|
45
|
+
if (!key.startsWith(prefix))
|
|
46
|
+
return null;
|
|
47
|
+
return key.slice(prefix.length);
|
|
48
|
+
}
|
|
49
|
+
async fetchMatches(extra) {
|
|
50
|
+
const args = this.selectArgsBuilder(extra);
|
|
51
|
+
const rows = await this.client._tselect(args);
|
|
52
|
+
return rows;
|
|
53
|
+
}
|
|
54
|
+
async start() {
|
|
55
|
+
if (this.initError) {
|
|
56
|
+
this.emitError(this.initError);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const initial = await this.fetchMatches();
|
|
61
|
+
for (const row of initial) {
|
|
62
|
+
if (row.id == null)
|
|
63
|
+
continue;
|
|
64
|
+
this.knownRows.set(String(row.id), row);
|
|
65
|
+
}
|
|
66
|
+
const pattern = `_t:${this.table}:row:*`;
|
|
67
|
+
this.unsubscribeFn = await this.client._subscribePattern(pattern, (raw) => {
|
|
68
|
+
void this.handleRawChange(raw);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
this.emitError((0, utils_1.toLuxError)(error, 'LUX_SUBSCRIBE_INIT_ERROR'));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async handleRawChange(raw) {
|
|
76
|
+
const pk = this.extractPkFromKey(raw.key);
|
|
77
|
+
if (!pk)
|
|
78
|
+
return;
|
|
79
|
+
try {
|
|
80
|
+
const previous = this.knownRows.get(pk) ?? null;
|
|
81
|
+
const rows = await this.fetchMatches([{ field: 'id', op: '=', value: pk }]);
|
|
82
|
+
const next = rows[0] ?? null;
|
|
83
|
+
if (!previous && !next)
|
|
84
|
+
return;
|
|
85
|
+
if (!previous && next) {
|
|
86
|
+
this.knownRows.set(pk, next);
|
|
87
|
+
this.emitChange({
|
|
88
|
+
type: 'insert',
|
|
89
|
+
table: this.table,
|
|
90
|
+
pk,
|
|
91
|
+
operation: raw.operation,
|
|
92
|
+
new: next,
|
|
93
|
+
old: null,
|
|
94
|
+
raw,
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (previous && !next) {
|
|
99
|
+
this.knownRows.delete(pk);
|
|
100
|
+
this.emitChange({
|
|
101
|
+
type: 'delete',
|
|
102
|
+
table: this.table,
|
|
103
|
+
pk,
|
|
104
|
+
operation: raw.operation,
|
|
105
|
+
new: null,
|
|
106
|
+
old: previous,
|
|
107
|
+
raw,
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!previous || !next)
|
|
112
|
+
return;
|
|
113
|
+
this.knownRows.set(pk, next);
|
|
114
|
+
const changed = Object.keys(next).filter((key) => previous[key] !== next[key]);
|
|
115
|
+
this.emitChange({
|
|
116
|
+
type: 'update',
|
|
117
|
+
table: this.table,
|
|
118
|
+
pk,
|
|
119
|
+
operation: raw.operation,
|
|
120
|
+
new: next,
|
|
121
|
+
old: previous,
|
|
122
|
+
changed,
|
|
123
|
+
raw,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
this.emitError((0, utils_1.toLuxError)(error, 'LUX_SUBSCRIBE_EVENT_ERROR'));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.TableSubscription = TableSubscription;
|
|
132
|
+
class TableQueryBuilder {
|
|
133
|
+
constructor(client, name, options) {
|
|
134
|
+
this.conditions = [];
|
|
135
|
+
this.selectClause = '*';
|
|
136
|
+
this.expectSingle = false;
|
|
137
|
+
this.client = client;
|
|
138
|
+
this.name = name;
|
|
139
|
+
this.schema = options?.schema;
|
|
140
|
+
}
|
|
141
|
+
validateRow(row) {
|
|
142
|
+
if (!this.schema)
|
|
143
|
+
return row;
|
|
144
|
+
if (this.schema.safeParse) {
|
|
145
|
+
const parsed = this.schema.safeParse(row);
|
|
146
|
+
if (!parsed.success) {
|
|
147
|
+
throw new Error('row failed schema validation');
|
|
148
|
+
}
|
|
149
|
+
return parsed.data;
|
|
150
|
+
}
|
|
151
|
+
if (this.schema.parse) {
|
|
152
|
+
return this.schema.parse(row);
|
|
153
|
+
}
|
|
154
|
+
return row;
|
|
155
|
+
}
|
|
156
|
+
buildSelectArgs(extra) {
|
|
157
|
+
const args = [this.selectClause, 'FROM', this.name];
|
|
158
|
+
const allConditions = extra ? [...this.conditions, ...extra] : this.conditions;
|
|
159
|
+
if (this.joinClause) {
|
|
160
|
+
args.push('JOIN', this.joinClause.table, this.joinClause.alias, 'ON', this.joinClause.onLeft, '=', this.joinClause.onRight);
|
|
161
|
+
}
|
|
162
|
+
if (allConditions.length) {
|
|
163
|
+
args.push('WHERE');
|
|
164
|
+
for (let i = 0; i < allConditions.length; i++) {
|
|
165
|
+
const cond = allConditions[i];
|
|
166
|
+
args.push(cond.field, cond.op, String(cond.value));
|
|
167
|
+
if (i < allConditions.length - 1) {
|
|
168
|
+
args.push('AND');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (this.orderField) {
|
|
173
|
+
args.push('ORDER', 'BY', this.orderField, this.orderDir || 'ASC');
|
|
174
|
+
}
|
|
175
|
+
if (this.limitCount != null) {
|
|
176
|
+
args.push('LIMIT', String(this.limitCount));
|
|
177
|
+
}
|
|
178
|
+
if (this.offsetCount != null) {
|
|
179
|
+
args.push('OFFSET', String(this.offsetCount));
|
|
180
|
+
}
|
|
181
|
+
return args;
|
|
182
|
+
}
|
|
183
|
+
select(columns = '*') {
|
|
184
|
+
this.selectClause = columns;
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
single() {
|
|
188
|
+
this.expectSingle = true;
|
|
189
|
+
if (this.limitCount == null) {
|
|
190
|
+
this.limitCount = 1;
|
|
191
|
+
}
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
where(field, op, value) {
|
|
195
|
+
this.conditions.push({ field, op, value });
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
orderBy(field, dir = 'asc') {
|
|
199
|
+
this.orderField = field;
|
|
200
|
+
this.orderDir = dir.toUpperCase();
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
limit(n) {
|
|
204
|
+
this.limitCount = n;
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
offset(n) {
|
|
208
|
+
this.offsetCount = n;
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
join(table, alias, onLeft, onRight) {
|
|
212
|
+
this.joinClause = { table, alias, onLeft, onRight };
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
similar(field, vector, options) {
|
|
216
|
+
this.similarityClause = {
|
|
217
|
+
field,
|
|
218
|
+
vector,
|
|
219
|
+
k: options.k,
|
|
220
|
+
filter: options.filter,
|
|
221
|
+
};
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
parseSimilarityPk(result, field) {
|
|
225
|
+
const metadata = result.metadata;
|
|
226
|
+
if (metadata && typeof metadata === 'object') {
|
|
227
|
+
for (const key of ['id', 'pk', 'row_id']) {
|
|
228
|
+
const value = metadata[key];
|
|
229
|
+
if (value != null)
|
|
230
|
+
return String(value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const expectedPrefix = `${this.name}:${field}:`;
|
|
234
|
+
if (result.key.startsWith(expectedPrefix)) {
|
|
235
|
+
return result.key.slice(expectedPrefix.length);
|
|
236
|
+
}
|
|
237
|
+
const segments = result.key.split(':');
|
|
238
|
+
if (segments.length > 0) {
|
|
239
|
+
return segments[segments.length - 1] || null;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
async run() {
|
|
244
|
+
try {
|
|
245
|
+
let rows = [];
|
|
246
|
+
if (this.similarityClause) {
|
|
247
|
+
if (this.joinClause) {
|
|
248
|
+
return (0, utils_1.err)('SIMILAR_JOIN_UNSUPPORTED', 'similar(...) cannot be combined with join(...) yet');
|
|
249
|
+
}
|
|
250
|
+
const similarResults = await this.client.vsearch(this.similarityClause.vector, {
|
|
251
|
+
k: this.similarityClause.k,
|
|
252
|
+
filter: this.similarityClause.filter,
|
|
253
|
+
meta: true,
|
|
254
|
+
});
|
|
255
|
+
for (const match of similarResults) {
|
|
256
|
+
const pk = this.parseSimilarityPk(match, this.similarityClause.field);
|
|
257
|
+
if (!pk)
|
|
258
|
+
continue;
|
|
259
|
+
const args = this.buildSelectArgs([{ field: 'id', op: '=', value: pk }]);
|
|
260
|
+
const one = await this.client._tselect(args);
|
|
261
|
+
if (one.length === 0)
|
|
262
|
+
continue;
|
|
263
|
+
rows.push({ ...one[0], _similarity: match.similarity });
|
|
264
|
+
}
|
|
265
|
+
if (this.offsetCount != null || this.limitCount != null) {
|
|
266
|
+
const start = this.offsetCount ?? 0;
|
|
267
|
+
const end = this.limitCount != null ? start + this.limitCount : undefined;
|
|
268
|
+
rows = rows.slice(start, end);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
rows = await this.client._tselect(this.buildSelectArgs());
|
|
273
|
+
}
|
|
274
|
+
const validated = rows.map((row) => this.validateRow(row));
|
|
275
|
+
if (this.expectSingle) {
|
|
276
|
+
if (validated.length === 0) {
|
|
277
|
+
return (0, utils_1.err)('NOT_FOUND', `No rows found in table '${this.name}'`);
|
|
278
|
+
}
|
|
279
|
+
return (0, utils_1.ok)(validated[0]);
|
|
280
|
+
}
|
|
281
|
+
return (0, utils_1.ok)(validated);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
return (0, utils_1.err)('TSELECT_ERROR', `Failed to query table '${this.name}'`, (0, utils_1.toLuxError)(error));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async insert(data) {
|
|
288
|
+
try {
|
|
289
|
+
if (this.schema) {
|
|
290
|
+
this.validateRow(data);
|
|
291
|
+
}
|
|
292
|
+
const args = [this.name];
|
|
293
|
+
for (const [k, v] of Object.entries(data)) {
|
|
294
|
+
args.push(k, String(v));
|
|
295
|
+
}
|
|
296
|
+
const result = await this.client.call('TINSERT', ...args);
|
|
297
|
+
return (0, utils_1.ok)(parseInt(result, 10) || 0);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
return (0, utils_1.err)('TINSERT_ERROR', `Failed to insert into '${this.name}'`, (0, utils_1.toLuxError)(error));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async update(id, data) {
|
|
304
|
+
try {
|
|
305
|
+
const args = [this.name, 'SET'];
|
|
306
|
+
for (const [k, v] of Object.entries(data)) {
|
|
307
|
+
args.push(k, String(v));
|
|
308
|
+
}
|
|
309
|
+
args.push('WHERE', 'id', '=', String(id));
|
|
310
|
+
const result = await this.client.call('TUPDATE', ...args);
|
|
311
|
+
return (0, utils_1.ok)(Number(result) || 0);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
return (0, utils_1.err)('TUPDATE_ERROR', `Failed to update '${this.name}'`, (0, utils_1.toLuxError)(error));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async updateWhere(data) {
|
|
318
|
+
if (this.conditions.length === 0) {
|
|
319
|
+
return (0, utils_1.err)('MISSING_WHERE', 'updateWhere requires at least one where() condition');
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const args = [this.name, 'SET'];
|
|
323
|
+
for (const [k, v] of Object.entries(data)) {
|
|
324
|
+
args.push(k, String(v));
|
|
325
|
+
}
|
|
326
|
+
args.push('WHERE');
|
|
327
|
+
for (let i = 0; i < this.conditions.length; i++) {
|
|
328
|
+
const cond = this.conditions[i];
|
|
329
|
+
args.push(cond.field, cond.op, String(cond.value));
|
|
330
|
+
if (i < this.conditions.length - 1) {
|
|
331
|
+
args.push('AND');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const result = await this.client.call('TUPDATE', ...args);
|
|
335
|
+
return (0, utils_1.ok)(Number(result) || 0);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
return (0, utils_1.err)('TUPDATE_ERROR', `Failed to update '${this.name}'`, (0, utils_1.toLuxError)(error));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async delete(...ids) {
|
|
342
|
+
try {
|
|
343
|
+
let deleted = 0;
|
|
344
|
+
for (const id of ids) {
|
|
345
|
+
const result = await this.client.call('TDELETE', 'FROM', this.name, 'WHERE', 'id', '=', String(id));
|
|
346
|
+
deleted += Number(result) || 0;
|
|
347
|
+
}
|
|
348
|
+
return (0, utils_1.ok)(deleted);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
return (0, utils_1.err)('TDELETE_ERROR', `Failed to delete from '${this.name}'`, (0, utils_1.toLuxError)(error));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async deleteWhere() {
|
|
355
|
+
if (this.conditions.length === 0) {
|
|
356
|
+
return (0, utils_1.err)('MISSING_WHERE', 'deleteWhere requires at least one where() condition');
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
const args = ['FROM', this.name, 'WHERE'];
|
|
360
|
+
for (let i = 0; i < this.conditions.length; i++) {
|
|
361
|
+
const cond = this.conditions[i];
|
|
362
|
+
args.push(cond.field, cond.op, String(cond.value));
|
|
363
|
+
if (i < this.conditions.length - 1) {
|
|
364
|
+
args.push('AND');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const result = await this.client.call('TDELETE', ...args);
|
|
368
|
+
return (0, utils_1.ok)(Number(result) || 0);
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
return (0, utils_1.err)('TDELETE_ERROR', `Failed to delete from '${this.name}'`, (0, utils_1.toLuxError)(error));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
subscribe() {
|
|
375
|
+
if (this.similarityClause) {
|
|
376
|
+
return new TableSubscription(this.client, this.name, (extra) => this.buildSelectArgs(extra), {
|
|
377
|
+
code: 'SIMILAR_SUBSCRIBE_UNSUPPORTED',
|
|
378
|
+
message: 'subscribe() is not supported on similar(...) queries yet',
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return new TableSubscription(this.client, this.name, (extra) => this.buildSelectArgs(extra));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
exports.TableQueryBuilder = TableQueryBuilder;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface VSearchResult {
|
|
2
|
+
key: string;
|
|
3
|
+
similarity: number;
|
|
4
|
+
metadata?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface TSSample {
|
|
7
|
+
timestamp: number;
|
|
8
|
+
value: number;
|
|
9
|
+
}
|
|
10
|
+
export interface TSAddOptions {
|
|
11
|
+
retention?: number;
|
|
12
|
+
labels?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export interface TSRangeOptions {
|
|
15
|
+
aggregation?: {
|
|
16
|
+
type: 'avg' | 'sum' | 'min' | 'max' | 'count' | 'first' | 'last' | 'range' | 'std.p' | 'std.s' | 'var.p' | 'var.s';
|
|
17
|
+
bucketSize: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface TSMRangeResult {
|
|
21
|
+
key: string;
|
|
22
|
+
labels: Record<string, string>;
|
|
23
|
+
samples: TSSample[];
|
|
24
|
+
}
|
|
25
|
+
export interface KSubEvent {
|
|
26
|
+
pattern: string;
|
|
27
|
+
key: string;
|
|
28
|
+
operation: string;
|
|
29
|
+
}
|
|
30
|
+
export interface TableRow {
|
|
31
|
+
id?: number | string;
|
|
32
|
+
[field: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
export interface LuxError {
|
|
35
|
+
code: string;
|
|
36
|
+
message: string;
|
|
37
|
+
details?: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface LuxResult<T> {
|
|
40
|
+
data: T | null;
|
|
41
|
+
error: LuxError | null;
|
|
42
|
+
}
|
|
43
|
+
type SchemaSafeParse<T> = {
|
|
44
|
+
success: true;
|
|
45
|
+
data: T;
|
|
46
|
+
} | {
|
|
47
|
+
success: false;
|
|
48
|
+
error: unknown;
|
|
49
|
+
};
|
|
50
|
+
export interface TableSchema<T> {
|
|
51
|
+
parse?: (input: unknown) => T;
|
|
52
|
+
safeParse?: (input: unknown) => SchemaSafeParse<T>;
|
|
53
|
+
}
|
|
54
|
+
export type TableChangeType = 'insert' | 'update' | 'delete' | 'change' | 'error';
|
|
55
|
+
export interface TableChangeEvent<T extends TableRow> {
|
|
56
|
+
type: Exclude<TableChangeType, 'error'>;
|
|
57
|
+
table: string;
|
|
58
|
+
pk: string;
|
|
59
|
+
operation: string;
|
|
60
|
+
new: T | null;
|
|
61
|
+
old: T | null;
|
|
62
|
+
changed?: string[];
|
|
63
|
+
raw?: KSubEvent;
|
|
64
|
+
}
|
|
65
|
+
export interface TableErrorEvent {
|
|
66
|
+
type: 'error';
|
|
67
|
+
table: string;
|
|
68
|
+
error: LuxError;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
package/dist/types.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LuxError, LuxResult } from './types';
|
|
2
|
+
export declare function ok<T>(data: T): LuxResult<T>;
|
|
3
|
+
export declare function err<T>(code: string, message: string, details?: unknown): LuxResult<T>;
|
|
4
|
+
export declare function toLuxError(error: unknown, fallbackCode?: string): LuxError;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ok = ok;
|
|
4
|
+
exports.err = err;
|
|
5
|
+
exports.toLuxError = toLuxError;
|
|
6
|
+
function ok(data) {
|
|
7
|
+
return { data, error: null };
|
|
8
|
+
}
|
|
9
|
+
function err(code, message, details) {
|
|
10
|
+
return { data: null, error: { code, message, details } };
|
|
11
|
+
}
|
|
12
|
+
function toLuxError(error, fallbackCode = 'LUX_SDK_ERROR') {
|
|
13
|
+
if (typeof error === 'object' && error && 'message' in error) {
|
|
14
|
+
return { code: fallbackCode, message: String(error.message), details: error };
|
|
15
|
+
}
|
|
16
|
+
return { code: fallbackCode, message: String(error) };
|
|
17
|
+
}
|