@luxdb/sdk 1.4.2 → 2.0.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/README.md +113 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/project.js +407 -1
- package/dist/cjs/table.js +131 -81
- package/dist/esm/index.js +1 -0
- package/dist/esm/project.js +405 -0
- package/dist/esm/table.js +131 -81
- package/dist/types/auth.d.ts +84 -0
- package/dist/types/index.d.ts +6 -5
- package/dist/types/project.d.ts +132 -14
- package/dist/types/table.d.ts +31 -21
- package/dist/types/types.d.ts +9 -1
- package/package.json +1 -1
package/dist/esm/table.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
import { err, ok, toLuxError } from './utils.js';
|
|
2
|
+
/** Serialize a field value: JSON objects/arrays round-trip as JSON text. */
|
|
3
|
+
function serializeFieldValue(v) {
|
|
4
|
+
if (v !== null && typeof v === 'object') {
|
|
5
|
+
return JSON.stringify(v);
|
|
6
|
+
}
|
|
7
|
+
return String(v);
|
|
8
|
+
}
|
|
9
|
+
/** Serialize one WHERE condition into RESP tokens. */
|
|
10
|
+
function serializeCondition(cond) {
|
|
11
|
+
if (cond.op === 'IN' || cond.op === 'NOT IN') {
|
|
12
|
+
const values = Array.isArray(cond.value) ? cond.value : [cond.value];
|
|
13
|
+
return [
|
|
14
|
+
cond.field,
|
|
15
|
+
...(cond.op === 'NOT IN' ? ['NOT', 'IN'] : ['IN']),
|
|
16
|
+
'(',
|
|
17
|
+
...values.map(String),
|
|
18
|
+
')',
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
if (cond.op === 'IS VALID')
|
|
22
|
+
return [cond.field, 'IS', 'VALID'];
|
|
23
|
+
if (cond.op === 'IS NOT VALID')
|
|
24
|
+
return [cond.field, 'IS', 'NOT', 'VALID'];
|
|
25
|
+
return [cond.field, cond.op, String(cond.value)];
|
|
26
|
+
}
|
|
27
|
+
/** Join serialized conditions with AND separators. */
|
|
28
|
+
function serializeConditions(conditions) {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (let i = 0; i < conditions.length; i++) {
|
|
31
|
+
out.push(...serializeCondition(conditions[i]));
|
|
32
|
+
if (i < conditions.length - 1)
|
|
33
|
+
out.push('AND');
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
2
37
|
export class TableSubscription {
|
|
3
38
|
constructor(client, table, selectArgsBuilder, initError = null) {
|
|
4
39
|
this.handlers = {
|
|
@@ -56,9 +91,10 @@ export class TableSubscription {
|
|
|
56
91
|
try {
|
|
57
92
|
const initial = await this.fetchMatches();
|
|
58
93
|
for (const row of initial) {
|
|
59
|
-
|
|
94
|
+
const id = row.id;
|
|
95
|
+
if (id == null)
|
|
60
96
|
continue;
|
|
61
|
-
this.knownRows.set(String(
|
|
97
|
+
this.knownRows.set(String(id), row);
|
|
62
98
|
}
|
|
63
99
|
const pattern = `_t:${this.table}:row:*`;
|
|
64
100
|
this.unsubscribeFn = await this.client._subscribePattern(pattern, (raw) => {
|
|
@@ -108,7 +144,9 @@ export class TableSubscription {
|
|
|
108
144
|
if (!previous || !next)
|
|
109
145
|
return;
|
|
110
146
|
this.knownRows.set(pk, next);
|
|
111
|
-
const
|
|
147
|
+
const previousRow = previous;
|
|
148
|
+
const nextRow = next;
|
|
149
|
+
const changed = Object.keys(nextRow).filter((key) => previousRow[key] !== nextRow[key]);
|
|
112
150
|
this.emitChange({
|
|
113
151
|
type: 'update',
|
|
114
152
|
table: this.table,
|
|
@@ -128,6 +166,8 @@ export class TableSubscription {
|
|
|
128
166
|
export class TableQueryBuilder {
|
|
129
167
|
constructor(client, name, options) {
|
|
130
168
|
this.conditions = [];
|
|
169
|
+
this.groupFields = [];
|
|
170
|
+
this.havingConditions = [];
|
|
131
171
|
this.selectClause = '*';
|
|
132
172
|
this.expectSingle = false;
|
|
133
173
|
this.client = client;
|
|
@@ -153,18 +193,30 @@ export class TableQueryBuilder {
|
|
|
153
193
|
const args = [this.selectClause, 'FROM', this.name];
|
|
154
194
|
const allConditions = extra ? [...this.conditions, ...extra] : this.conditions;
|
|
155
195
|
if (this.joinClause) {
|
|
156
|
-
args.push('JOIN', this.joinClause.table, this.joinClause.alias, 'ON', this.joinClause.onLeft, '=', this.joinClause.onRight);
|
|
196
|
+
args.push(...(this.joinClause.type === 'LEFT' ? ['LEFT', 'JOIN'] : ['JOIN']), this.joinClause.table, this.joinClause.alias, 'ON', this.joinClause.onLeft, '=', this.joinClause.onRight);
|
|
157
197
|
}
|
|
158
198
|
if (allConditions.length) {
|
|
159
|
-
args.push('WHERE');
|
|
160
|
-
|
|
161
|
-
|
|
199
|
+
args.push('WHERE', ...serializeConditions(allConditions));
|
|
200
|
+
}
|
|
201
|
+
if (this.groupFields.length) {
|
|
202
|
+
args.push('GROUP', 'BY', ...this.groupFields);
|
|
203
|
+
}
|
|
204
|
+
if (this.havingConditions.length) {
|
|
205
|
+
args.push('HAVING');
|
|
206
|
+
for (let i = 0; i < this.havingConditions.length; i++) {
|
|
207
|
+
const cond = this.havingConditions[i];
|
|
162
208
|
args.push(cond.field, cond.op, String(cond.value));
|
|
163
|
-
if (i <
|
|
209
|
+
if (i < this.havingConditions.length - 1) {
|
|
164
210
|
args.push('AND');
|
|
165
211
|
}
|
|
166
212
|
}
|
|
167
213
|
}
|
|
214
|
+
if (this.similarityClause) {
|
|
215
|
+
args.push('NEAR', this.similarityClause.field, `[${this.similarityClause.vector.join(',')}]`, 'K', String(this.similarityClause.k));
|
|
216
|
+
if (this.similarityClause.threshold != null) {
|
|
217
|
+
args.push('THRESHOLD', String(this.similarityClause.threshold));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
168
220
|
if (this.orderField) {
|
|
169
221
|
args.push('ORDER', 'BY', this.orderField, this.orderDir || 'ASC');
|
|
170
222
|
}
|
|
@@ -209,6 +261,29 @@ export class TableQueryBuilder {
|
|
|
209
261
|
lte(field, value) {
|
|
210
262
|
return this.where(field, '<=', value);
|
|
211
263
|
}
|
|
264
|
+
in(field, values) {
|
|
265
|
+
this.conditions.push({ field, op: 'IN', value: values });
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
notIn(field, values) {
|
|
269
|
+
this.conditions.push({ field, op: 'NOT IN', value: values });
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
/** Match rows where a JSON dot-path resolves to a present, non-null value. */
|
|
273
|
+
isValid(field) {
|
|
274
|
+
this.conditions.push({ field, op: 'IS VALID', value: '' });
|
|
275
|
+
return this;
|
|
276
|
+
}
|
|
277
|
+
/** Match rows where a JSON dot-path is absent or resolves to null. */
|
|
278
|
+
isNotValid(field) {
|
|
279
|
+
this.conditions.push({ field, op: 'IS NOT VALID', value: '' });
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
/** Match rows where an ARRAY column (or array-valued path) contains a value. */
|
|
283
|
+
contains(field, value) {
|
|
284
|
+
this.conditions.push({ field, op: 'CONTAINS', value });
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
212
287
|
orderBy(field, dir = 'asc') {
|
|
213
288
|
this.orderField = field;
|
|
214
289
|
this.orderDir = dir.toUpperCase();
|
|
@@ -226,68 +301,41 @@ export class TableQueryBuilder {
|
|
|
226
301
|
return this;
|
|
227
302
|
}
|
|
228
303
|
join(table, alias, onLeft, onRight) {
|
|
229
|
-
this.joinClause = { table, alias, onLeft, onRight };
|
|
304
|
+
this.joinClause = { type: 'INNER', table, alias, onLeft, onRight };
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
leftJoin(table, alias, onLeft, onRight) {
|
|
308
|
+
this.joinClause = { type: 'LEFT', table, alias, onLeft, onRight };
|
|
309
|
+
return this;
|
|
310
|
+
}
|
|
311
|
+
group(fields) {
|
|
312
|
+
this.groupFields = Array.isArray(fields)
|
|
313
|
+
? fields
|
|
314
|
+
: fields.split(',').map((field) => field.trim()).filter(Boolean);
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
groupBy(fields) {
|
|
318
|
+
return this.group(fields);
|
|
319
|
+
}
|
|
320
|
+
having(field, op, value) {
|
|
321
|
+
this.havingConditions.push({ field, op, value });
|
|
230
322
|
return this;
|
|
231
323
|
}
|
|
232
|
-
|
|
324
|
+
near(field, vector, options = {}) {
|
|
233
325
|
this.similarityClause = {
|
|
234
326
|
field,
|
|
235
327
|
vector,
|
|
236
|
-
k: options.k,
|
|
237
|
-
|
|
328
|
+
k: options.k ?? 10,
|
|
329
|
+
threshold: options.threshold,
|
|
238
330
|
};
|
|
239
331
|
return this;
|
|
240
332
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (metadata && typeof metadata === 'object') {
|
|
244
|
-
for (const key of ['id', 'pk', 'row_id']) {
|
|
245
|
-
const value = metadata[key];
|
|
246
|
-
if (value != null)
|
|
247
|
-
return String(value);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
const expectedPrefix = `${this.name}:${field}:`;
|
|
251
|
-
if (result.key.startsWith(expectedPrefix)) {
|
|
252
|
-
return result.key.slice(expectedPrefix.length);
|
|
253
|
-
}
|
|
254
|
-
const segments = result.key.split(':');
|
|
255
|
-
if (segments.length > 0) {
|
|
256
|
-
return segments[segments.length - 1] || null;
|
|
257
|
-
}
|
|
258
|
-
return null;
|
|
333
|
+
similar(field, vector, options = {}) {
|
|
334
|
+
return this.near(field, vector, options);
|
|
259
335
|
}
|
|
260
336
|
async run() {
|
|
261
337
|
try {
|
|
262
|
-
|
|
263
|
-
if (this.similarityClause) {
|
|
264
|
-
if (this.joinClause) {
|
|
265
|
-
return err('SIMILAR_JOIN_UNSUPPORTED', 'similar(...) cannot be combined with join(...) yet');
|
|
266
|
-
}
|
|
267
|
-
const similarResults = await this.client.vsearch(this.similarityClause.vector, {
|
|
268
|
-
k: this.similarityClause.k,
|
|
269
|
-
filter: this.similarityClause.filter,
|
|
270
|
-
meta: true,
|
|
271
|
-
});
|
|
272
|
-
for (const match of similarResults) {
|
|
273
|
-
const pk = this.parseSimilarityPk(match, this.similarityClause.field);
|
|
274
|
-
if (!pk)
|
|
275
|
-
continue;
|
|
276
|
-
const args = this.buildSelectArgs([{ field: 'id', op: '=', value: pk }]);
|
|
277
|
-
const one = await this.client._tselect(args);
|
|
278
|
-
if (one.length === 0)
|
|
279
|
-
continue;
|
|
280
|
-
rows.push({ ...one[0], _similarity: match.similarity });
|
|
281
|
-
}
|
|
282
|
-
if (this.offsetCount != null || this.limitCount != null) {
|
|
283
|
-
const start = this.offsetCount ?? 0;
|
|
284
|
-
const end = this.limitCount != null ? start + this.limitCount : undefined;
|
|
285
|
-
rows = rows.slice(start, end);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
rows = await this.client._tselect(this.buildSelectArgs());
|
|
290
|
-
}
|
|
338
|
+
const rows = await this.client._tselect(this.buildSelectArgs());
|
|
291
339
|
const validated = rows.map((row) => this.validateRow(row));
|
|
292
340
|
if (this.expectSingle) {
|
|
293
341
|
if (validated.length === 0) {
|
|
@@ -311,7 +359,7 @@ export class TableQueryBuilder {
|
|
|
311
359
|
}
|
|
312
360
|
const args = [this.name];
|
|
313
361
|
for (const [k, v] of Object.entries(data)) {
|
|
314
|
-
args.push(k,
|
|
362
|
+
args.push(k, serializeFieldValue(v));
|
|
315
363
|
}
|
|
316
364
|
const result = await this.client.call('TINSERT', ...args);
|
|
317
365
|
return ok(parseInt(result, 10) || 0);
|
|
@@ -320,6 +368,26 @@ export class TableQueryBuilder {
|
|
|
320
368
|
return err('TINSERT_ERROR', `Failed to insert into '${this.name}'`, toLuxError(error));
|
|
321
369
|
}
|
|
322
370
|
}
|
|
371
|
+
/** Declare a typed index on a JSON dot-path, e.g. ('meta.reactions.count', 'int'). */
|
|
372
|
+
async createIndex(path, type) {
|
|
373
|
+
try {
|
|
374
|
+
await this.client.call('TINDEX', this.name, path, type.toUpperCase());
|
|
375
|
+
return ok(true);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
return err('TINDEX_ERROR', `Failed to index '${path}'`, toLuxError(error));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** Drop a previously declared JSON path index. */
|
|
382
|
+
async dropIndex(path) {
|
|
383
|
+
try {
|
|
384
|
+
await this.client.call('TDROPINDEX', this.name, path);
|
|
385
|
+
return ok(true);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
return err('TDROPINDEX_ERROR', `Failed to drop index '${path}'`, toLuxError(error));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
323
391
|
async update(idOrData, data) {
|
|
324
392
|
try {
|
|
325
393
|
const hasExplicitId = data !== undefined;
|
|
@@ -329,20 +397,14 @@ export class TableQueryBuilder {
|
|
|
329
397
|
}
|
|
330
398
|
const args = [this.name, 'SET'];
|
|
331
399
|
for (const [k, v] of Object.entries(patch)) {
|
|
332
|
-
args.push(k,
|
|
400
|
+
args.push(k, serializeFieldValue(v));
|
|
333
401
|
}
|
|
334
402
|
args.push('WHERE');
|
|
335
403
|
if (hasExplicitId) {
|
|
336
404
|
args.push('id', '=', String(idOrData));
|
|
337
405
|
}
|
|
338
406
|
else {
|
|
339
|
-
|
|
340
|
-
const cond = this.conditions[i];
|
|
341
|
-
args.push(cond.field, cond.op, String(cond.value));
|
|
342
|
-
if (i < this.conditions.length - 1) {
|
|
343
|
-
args.push('AND');
|
|
344
|
-
}
|
|
345
|
-
}
|
|
407
|
+
args.push(...serializeConditions(this.conditions));
|
|
346
408
|
}
|
|
347
409
|
const result = await this.client.call('TUPDATE', ...args);
|
|
348
410
|
return ok(Number(result) || 0);
|
|
@@ -358,13 +420,7 @@ export class TableQueryBuilder {
|
|
|
358
420
|
return err('MISSING_WHERE', 'delete requires at least one filter');
|
|
359
421
|
}
|
|
360
422
|
const args = ['FROM', this.name, 'WHERE'];
|
|
361
|
-
|
|
362
|
-
const cond = this.conditions[i];
|
|
363
|
-
args.push(cond.field, cond.op, String(cond.value));
|
|
364
|
-
if (i < this.conditions.length - 1) {
|
|
365
|
-
args.push('AND');
|
|
366
|
-
}
|
|
367
|
-
}
|
|
423
|
+
args.push(...serializeConditions(this.conditions));
|
|
368
424
|
const result = await this.client.call('TDELETE', ...args);
|
|
369
425
|
return ok(Number(result) || 0);
|
|
370
426
|
}
|
|
@@ -380,12 +436,6 @@ export class TableQueryBuilder {
|
|
|
380
436
|
}
|
|
381
437
|
}
|
|
382
438
|
subscribe() {
|
|
383
|
-
if (this.similarityClause) {
|
|
384
|
-
return new TableSubscription(this.client, this.name, (extra) => this.buildSelectArgs(extra), {
|
|
385
|
-
code: 'SIMILAR_SUBSCRIBE_UNSUPPORTED',
|
|
386
|
-
message: 'subscribe() is not supported on similar(...) queries yet',
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
439
|
return new TableSubscription(this.client, this.name, (extra) => this.buildSelectArgs(extra));
|
|
390
440
|
}
|
|
391
441
|
}
|
package/dist/types/auth.d.ts
CHANGED
|
@@ -11,6 +11,90 @@ export interface LuxAuthUser {
|
|
|
11
11
|
user_metadata?: Record<string, unknown>;
|
|
12
12
|
app_metadata?: Record<string, unknown>;
|
|
13
13
|
}
|
|
14
|
+
export type LuxUser = LuxAuthUser;
|
|
15
|
+
export interface LuxAuthUserRow {
|
|
16
|
+
id: string;
|
|
17
|
+
email?: string;
|
|
18
|
+
phone?: string;
|
|
19
|
+
encrypted_password?: string;
|
|
20
|
+
email_confirmed_at?: number | null;
|
|
21
|
+
phone_confirmed_at?: number | null;
|
|
22
|
+
raw_user_meta_data?: string;
|
|
23
|
+
raw_app_meta_data?: string;
|
|
24
|
+
created_at?: number | null;
|
|
25
|
+
updated_at?: number | null;
|
|
26
|
+
last_sign_in_at?: number | null;
|
|
27
|
+
banned_until?: number | null;
|
|
28
|
+
deleted_at?: number | null;
|
|
29
|
+
}
|
|
30
|
+
export interface LuxAuthIdentityRow {
|
|
31
|
+
id: string;
|
|
32
|
+
user_id: string;
|
|
33
|
+
provider: string;
|
|
34
|
+
provider_id: string;
|
|
35
|
+
identity_data?: string;
|
|
36
|
+
created_at?: number | null;
|
|
37
|
+
updated_at?: number | null;
|
|
38
|
+
}
|
|
39
|
+
export interface LuxAuthSessionRow {
|
|
40
|
+
id: string;
|
|
41
|
+
user_id: string;
|
|
42
|
+
refresh_token_hash: string;
|
|
43
|
+
refresh_token_family?: string;
|
|
44
|
+
user_agent?: string;
|
|
45
|
+
ip?: string;
|
|
46
|
+
expires_at?: number | null;
|
|
47
|
+
revoked_at?: number | null;
|
|
48
|
+
created_at?: number | null;
|
|
49
|
+
updated_at?: number | null;
|
|
50
|
+
}
|
|
51
|
+
export interface LuxAuthKeyRow {
|
|
52
|
+
id: string;
|
|
53
|
+
name?: string;
|
|
54
|
+
kind: 'publishable' | 'secret' | string;
|
|
55
|
+
prefix: string;
|
|
56
|
+
key_hash: string;
|
|
57
|
+
scopes?: string;
|
|
58
|
+
created_at?: number | null;
|
|
59
|
+
revoked_at?: number | null;
|
|
60
|
+
last_used_at?: number | null;
|
|
61
|
+
}
|
|
62
|
+
export interface LuxAuthSigningKeyRow {
|
|
63
|
+
id: string;
|
|
64
|
+
kid: string;
|
|
65
|
+
algorithm: string;
|
|
66
|
+
public_jwk?: string;
|
|
67
|
+
private_key_encrypted?: string;
|
|
68
|
+
active: boolean;
|
|
69
|
+
created_at?: number | null;
|
|
70
|
+
rotated_at?: number | null;
|
|
71
|
+
}
|
|
72
|
+
export interface LuxAuthGrantRow {
|
|
73
|
+
id: string;
|
|
74
|
+
user_id: string;
|
|
75
|
+
capability: string;
|
|
76
|
+
created_at?: number | null;
|
|
77
|
+
revoked_at?: number | null;
|
|
78
|
+
}
|
|
79
|
+
export interface LuxAuthProviderRow {
|
|
80
|
+
provider: LuxOAuthProvider | string;
|
|
81
|
+
enabled: boolean;
|
|
82
|
+
client_id?: string;
|
|
83
|
+
client_secret?: string;
|
|
84
|
+
redirect_uri?: string;
|
|
85
|
+
scopes?: string;
|
|
86
|
+
created_at?: number | null;
|
|
87
|
+
updated_at?: number | null;
|
|
88
|
+
}
|
|
89
|
+
export interface LuxAuthTables {
|
|
90
|
+
'auth.users': LuxAuthUserRow;
|
|
91
|
+
'auth.identities': LuxAuthIdentityRow;
|
|
92
|
+
'auth.sessions': LuxAuthSessionRow;
|
|
93
|
+
'auth.keys': LuxAuthKeyRow;
|
|
94
|
+
'auth.signing_keys': LuxAuthSigningKeyRow;
|
|
95
|
+
'auth.grants': LuxAuthGrantRow;
|
|
96
|
+
'auth.providers': LuxAuthProviderRow;
|
|
97
|
+
}
|
|
14
98
|
export interface LuxAuthSession {
|
|
15
99
|
access_token: string;
|
|
16
100
|
token_type: 'bearer';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,15 +3,16 @@ import { LuxAuthClient, type LuxAuthOptions } from './auth';
|
|
|
3
3
|
import { createProjectClient, LuxProjectClient, type LuxProjectOptions } from './project';
|
|
4
4
|
import { TimeSeriesNamespace, VectorNamespace } from './namespaces';
|
|
5
5
|
import { TableQueryBuilder, type TableQueryBuilderOptions } from './table';
|
|
6
|
-
import type { KSubEvent, TableRow, TSAddOptions, TSMRangeResult, TSRangeOptions, TSSample, VSearchResult } from './types';
|
|
7
|
-
export type { LuxAuthKey, LuxAuthChangeEvent, LuxAuthOptions, LuxAuthSession, LuxAuthStateChangeCallback, LuxAuthStorage, LuxAuthSubscription, LuxAuthUser, LuxOAuthProvider, LuxOAuthUrl, LuxSignInWithOAuthOptions, LuxCreateApiKeyOptions, LuxSignInOptions, LuxSignUpOptions, } from './auth';
|
|
6
|
+
import type { KSubEvent, LuxTypedRow, TableRow, TSAddOptions, TSMRangeResult, TSRangeOptions, TSSample, VSearchResult } from './types';
|
|
7
|
+
export type { LuxAuthKey, LuxAuthGrantRow, LuxAuthIdentityRow, LuxAuthChangeEvent, LuxAuthOptions, LuxAuthKeyRow, LuxAuthProviderRow, LuxAuthSession, LuxAuthSessionRow, LuxAuthSigningKeyRow, LuxAuthStateChangeCallback, LuxAuthStorage, LuxAuthSubscription, LuxAuthTables, LuxAuthUserRow, LuxAuthUser, LuxUser, LuxOAuthProvider, LuxOAuthUrl, LuxSignInWithOAuthOptions, LuxCreateApiKeyOptions, LuxSignInOptions, LuxSignUpOptions, } from './auth';
|
|
8
8
|
export { createProjectClient, LuxProjectClient, };
|
|
9
|
+
export { LuxProjectLiveSubscription } from './project';
|
|
9
10
|
export { createBrowserClient } from './browser';
|
|
10
11
|
export type { LuxBrowserClientOptions } from './browser';
|
|
11
12
|
export { createServerClient } from './ssr';
|
|
12
13
|
export type { LuxCookieMethods, LuxCookieOptions, LuxServerClientOptions } from './ssr';
|
|
13
|
-
export type { LuxProjectOptions, LuxTableColumn, LuxVectorSearchOptions, } from './project';
|
|
14
|
-
export type { KSubEvent, LuxError, LuxResult, TableChangeEvent, TableChangeType, TableErrorEvent, TableRow, TableSchema, TSAddOptions, TSMRangeResult, TSRangeOptions, TSSample, VSearchResult, } from './types';
|
|
14
|
+
export type { LuxLiveResult, LuxProjectLiveEvent, LuxProjectLiveEventType, LuxProjectOptions, LuxTableColumn, LuxVectorSearchOptions, } from './project';
|
|
15
|
+
export type { KSubEvent, LuxAggregateRow, LuxAggregateValue, LuxError, LuxInferRow, LuxNearRow, LuxResult, LuxSimilarity, LuxTypedRow, TableChangeEvent, TableChangeType, TableErrorEvent, TableRow, TableSchema, TSAddOptions, TSMRangeResult, TSRangeOptions, TSSample, VSearchResult, } from './types';
|
|
15
16
|
export { TableQueryBuilder, TableSubscription } from './table';
|
|
16
17
|
export type { TableQueryBuilderOptions } from './table';
|
|
17
18
|
export type LuxClientOptions = RedisOptions & LuxAuthOptions;
|
|
@@ -23,7 +24,7 @@ export declare class Lux extends Redis {
|
|
|
23
24
|
authApi: LuxAuthClient;
|
|
24
25
|
private realtimeManager?;
|
|
25
26
|
constructor(options?: LuxClientOptions | RedisOptions | string);
|
|
26
|
-
table<T extends
|
|
27
|
+
table<T extends object | readonly object[] = TableRow>(name: string, options?: TableQueryBuilderOptions<LuxTypedRow<T>>): TableQueryBuilder<LuxTypedRow<T>>;
|
|
27
28
|
_subscribePattern(pattern: string, handler: (event: KSubEvent) => void): Promise<() => void>;
|
|
28
29
|
_tselect(args: string[]): Promise<TableRow[]>;
|
|
29
30
|
vset(key: string, vector: number[], options?: {
|
package/dist/types/project.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { LuxAuthClient, type LuxAuthOptions } from './auth';
|
|
2
|
-
import type { LuxResult } from './types';
|
|
2
|
+
import type { LuxError, LuxResult, LuxTypedRow } from './types';
|
|
3
3
|
export interface LuxProjectOptions {
|
|
4
4
|
url: string;
|
|
5
5
|
key: string;
|
|
6
6
|
fetch?: typeof fetch;
|
|
7
|
+
websocket?: typeof WebSocket;
|
|
7
8
|
auth?: Omit<LuxAuthOptions, 'httpUrl' | 'apiKey' | 'fetch'>;
|
|
8
9
|
}
|
|
9
10
|
export interface LuxTableColumn {
|
|
10
11
|
name: string;
|
|
11
|
-
type: 'STR' | 'INT' | 'FLOAT' | 'BOOL' | 'TIMESTAMP' | 'UUID'
|
|
12
|
+
type: 'STR' | 'INT' | 'FLOAT' | 'BOOL' | 'TIMESTAMP' | 'UUID' | `VECTOR(${number})`;
|
|
12
13
|
primaryKey?: boolean;
|
|
13
14
|
unique?: boolean;
|
|
14
15
|
notNull?: boolean;
|
|
@@ -21,24 +22,73 @@ export interface LuxVectorSearchOptions {
|
|
|
21
22
|
filter?: string;
|
|
22
23
|
filter_value?: string;
|
|
23
24
|
}
|
|
24
|
-
type QueryValue = string | number | boolean | null;
|
|
25
|
-
type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'is';
|
|
25
|
+
type QueryValue = string | number | boolean | number[] | null;
|
|
26
|
+
type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'is' | 'in' | 'notIn' | 'isValid' | 'isNotValid' | 'contains';
|
|
27
|
+
type ProjectRowInput<T extends object> = Partial<T> & Record<string, QueryValue>;
|
|
28
|
+
type ProjectSelectSingle<TResult> = TResult extends readonly (infer Row)[] ? Row : TResult;
|
|
26
29
|
interface QueryFilter {
|
|
27
30
|
column: string;
|
|
28
31
|
operator: FilterOperator;
|
|
29
|
-
value: QueryValue;
|
|
32
|
+
value: QueryValue | QueryValue[];
|
|
30
33
|
}
|
|
31
34
|
interface QueryOrder {
|
|
32
35
|
column: string;
|
|
33
36
|
ascending: boolean;
|
|
34
37
|
}
|
|
38
|
+
interface QueryJoin {
|
|
39
|
+
type: 'inner' | 'left';
|
|
40
|
+
table: string;
|
|
41
|
+
alias: string;
|
|
42
|
+
onLeft: string;
|
|
43
|
+
onRight: string;
|
|
44
|
+
}
|
|
45
|
+
interface QueryHaving {
|
|
46
|
+
column: string;
|
|
47
|
+
operator: FilterOperator;
|
|
48
|
+
value: QueryValue;
|
|
49
|
+
}
|
|
50
|
+
interface QueryNear {
|
|
51
|
+
field: string;
|
|
52
|
+
vector: number[];
|
|
53
|
+
k: number;
|
|
54
|
+
threshold?: number;
|
|
55
|
+
}
|
|
56
|
+
export type LuxProjectLiveEventType = 'snapshot' | 'insert' | 'update' | 'delete' | 'error';
|
|
57
|
+
export interface LuxProjectLiveEvent<T extends object = Record<string, unknown>> {
|
|
58
|
+
type: LuxProjectLiveEventType;
|
|
59
|
+
table: string;
|
|
60
|
+
pk?: string;
|
|
61
|
+
new: T | null;
|
|
62
|
+
old: T | null;
|
|
63
|
+
rows?: T[];
|
|
64
|
+
changed?: string[];
|
|
65
|
+
raw?: unknown;
|
|
66
|
+
error?: {
|
|
67
|
+
code?: string;
|
|
68
|
+
message?: string;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
type LiveEventHandler<T extends object> = (event: LuxProjectLiveEvent<T>) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Result of opening a live subscription, in the same `{ data, error }` spirit as
|
|
74
|
+
* the rest of the SDK: `live` is the established subscription (or `null` if the
|
|
75
|
+
* server rejected it), `error` carries the rejection (e.g. a grant `FORBIDDEN`).
|
|
76
|
+
*/
|
|
77
|
+
export interface LuxLiveResult<T extends object> {
|
|
78
|
+
live: LuxProjectLiveSubscription<T> | null;
|
|
79
|
+
error: LuxError | null;
|
|
80
|
+
}
|
|
35
81
|
export declare class LuxProjectClient {
|
|
36
82
|
readonly url: string;
|
|
37
83
|
readonly key: string;
|
|
38
84
|
readonly auth: LuxAuthClient;
|
|
39
85
|
private fetchImpl;
|
|
86
|
+
private WebSocketImpl?;
|
|
87
|
+
private liveSocket;
|
|
88
|
+
private liveSubscriptions;
|
|
89
|
+
private livePending;
|
|
40
90
|
constructor(options: LuxProjectOptions);
|
|
41
|
-
table<T extends
|
|
91
|
+
table<T extends object | readonly object[] = Record<string, unknown>>(name: string): LuxProjectTable<LuxTypedRow<T>>;
|
|
42
92
|
ping(): Promise<LuxResult<unknown>>;
|
|
43
93
|
createTable(name: string, columns: Array<string | LuxTableColumn>): Promise<LuxResult<unknown>>;
|
|
44
94
|
exec(command: string | string[]): Promise<LuxResult<unknown>>;
|
|
@@ -55,15 +105,33 @@ export declare class LuxProjectClient {
|
|
|
55
105
|
count?: number;
|
|
56
106
|
}): Promise<LuxResult<unknown>>;
|
|
57
107
|
request<T = unknown>(method: string, path: string, body?: unknown): Promise<LuxResult<T>>;
|
|
108
|
+
_subscribeLive(spec: Record<string, unknown>, handler: (event: unknown) => void, error: (error: {
|
|
109
|
+
code?: string;
|
|
110
|
+
message?: string;
|
|
111
|
+
}) => void): Promise<() => void>;
|
|
112
|
+
private ensureLiveSocket;
|
|
113
|
+
private sendLive;
|
|
58
114
|
}
|
|
59
|
-
export declare class LuxProjectTable<T extends
|
|
115
|
+
export declare class LuxProjectTable<T extends object> {
|
|
60
116
|
private client;
|
|
61
117
|
private name;
|
|
62
118
|
constructor(client: LuxProjectClient, name: string);
|
|
63
|
-
select(columns?: string): LuxProjectSelectBuilder<T,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
119
|
+
select<TResult extends object = T>(columns?: string): LuxProjectSelectBuilder<T, TResult[]>;
|
|
120
|
+
eq(column: string, value: QueryValue): LuxProjectSelectBuilder<T, T[]>;
|
|
121
|
+
neq(column: string, value: QueryValue): LuxProjectSelectBuilder<T, T[]>;
|
|
122
|
+
gt(column: string, value: QueryValue): LuxProjectSelectBuilder<T, T[]>;
|
|
123
|
+
gte(column: string, value: QueryValue): LuxProjectSelectBuilder<T, T[]>;
|
|
124
|
+
lt(column: string, value: QueryValue): LuxProjectSelectBuilder<T, T[]>;
|
|
125
|
+
lte(column: string, value: QueryValue): LuxProjectSelectBuilder<T, T[]>;
|
|
126
|
+
near(column: string, vector: number[], options?: {
|
|
127
|
+
k?: number;
|
|
128
|
+
threshold?: number;
|
|
129
|
+
}): LuxProjectSelectBuilder<T, T[]>;
|
|
130
|
+
is(column: string, value: QueryValue): LuxProjectSelectBuilder<T, T[]>;
|
|
131
|
+
live(): Promise<LuxLiveResult<T>>;
|
|
132
|
+
insert(row: ProjectRowInput<T>): LuxProjectInsertBuilder<unknown>;
|
|
133
|
+
insert(rows: Array<ProjectRowInput<T>>): LuxProjectInsertBuilder<unknown[]>;
|
|
134
|
+
update(patch: ProjectRowInput<T>): LuxProjectMutationBuilder<unknown>;
|
|
67
135
|
delete(): LuxProjectMutationBuilder<unknown>;
|
|
68
136
|
count(): Promise<LuxResult<number>>;
|
|
69
137
|
}
|
|
@@ -78,6 +146,10 @@ declare abstract class LuxProjectFilterBuilder<TResult, TSelf> extends LuxProjec
|
|
|
78
146
|
protected tableName: string;
|
|
79
147
|
protected filters: QueryFilter[];
|
|
80
148
|
protected orderBy?: QueryOrder;
|
|
149
|
+
protected joins: QueryJoin[];
|
|
150
|
+
protected groupColumns: string[];
|
|
151
|
+
protected havingFilters: QueryHaving[];
|
|
152
|
+
protected nearQuery?: QueryNear;
|
|
81
153
|
protected limitCount?: number;
|
|
82
154
|
protected offsetCount?: number;
|
|
83
155
|
protected constructor(client: LuxProjectClient, tableName: string);
|
|
@@ -88,20 +160,66 @@ declare abstract class LuxProjectFilterBuilder<TResult, TSelf> extends LuxProjec
|
|
|
88
160
|
lt(column: string, value: QueryValue): TSelf;
|
|
89
161
|
lte(column: string, value: QueryValue): TSelf;
|
|
90
162
|
is(column: string, value: QueryValue): TSelf;
|
|
91
|
-
|
|
163
|
+
in(column: string, values: QueryValue[]): TSelf;
|
|
164
|
+
notIn(column: string, values: QueryValue[]): TSelf;
|
|
165
|
+
isValid(column: string): TSelf;
|
|
166
|
+
isNotValid(column: string): TSelf;
|
|
167
|
+
contains(column: string, value: QueryValue): TSelf;
|
|
168
|
+
join(table: string, alias: string, onLeft: string, onRight: string): TSelf;
|
|
169
|
+
leftJoin(table: string, alias: string, onLeft: string, onRight: string): TSelf;
|
|
170
|
+
group(columns: string | string[]): TSelf;
|
|
171
|
+
having(column: string, operator: FilterOperator, value: QueryValue): TSelf;
|
|
172
|
+
protected addFilter(column: string, operator: FilterOperator, value: QueryValue | QueryValue[]): TSelf;
|
|
92
173
|
protected filteredQueryParams(): URLSearchParams;
|
|
93
174
|
}
|
|
94
|
-
export declare class LuxProjectSelectBuilder<T extends
|
|
175
|
+
export declare class LuxProjectSelectBuilder<T extends object, TResult> extends LuxProjectFilterBuilder<TResult, LuxProjectSelectBuilder<T, TResult>> {
|
|
95
176
|
private columns;
|
|
96
177
|
private expectSingle;
|
|
97
178
|
constructor(client: LuxProjectClient, tableName: string, columns: string);
|
|
98
179
|
order(column: string, options?: {
|
|
99
180
|
ascending?: boolean;
|
|
100
181
|
}): this;
|
|
182
|
+
near(column: string, vector: number[], options?: {
|
|
183
|
+
k?: number;
|
|
184
|
+
threshold?: number;
|
|
185
|
+
}): this;
|
|
101
186
|
limit(count: number): this;
|
|
102
187
|
range(from: number, to: number): this;
|
|
103
|
-
single(): LuxProjectSelectBuilder<T,
|
|
188
|
+
single(): LuxProjectSelectBuilder<T, ProjectSelectSingle<TResult>>;
|
|
104
189
|
execute(): Promise<LuxResult<TResult>>;
|
|
190
|
+
live(): Promise<LuxLiveResult<LuxTypedRow<TResult>>>;
|
|
191
|
+
}
|
|
192
|
+
export declare class LuxProjectLiveSubscription<T extends object> {
|
|
193
|
+
private client;
|
|
194
|
+
private table;
|
|
195
|
+
private columns;
|
|
196
|
+
private filters;
|
|
197
|
+
private nearQuery?;
|
|
198
|
+
private orderBy?;
|
|
199
|
+
private limitCount?;
|
|
200
|
+
private offsetCount?;
|
|
201
|
+
private handlers;
|
|
202
|
+
private unsubscribeFn;
|
|
203
|
+
private queue;
|
|
204
|
+
private waiters;
|
|
205
|
+
private closed;
|
|
206
|
+
constructor(client: LuxProjectClient, table: string, columns: string, filters: QueryFilter[], nearQuery?: QueryNear | undefined, orderBy?: QueryOrder | undefined, limitCount?: number | undefined, offsetCount?: number | undefined);
|
|
207
|
+
on(type: LuxProjectLiveEventType | 'change', handler: LiveEventHandler<T>): this;
|
|
208
|
+
unsubscribe(): Promise<void>;
|
|
209
|
+
/**
|
|
210
|
+
* Open the subscription and wait for the server to confirm it. Resolves
|
|
211
|
+
* `null` once the initial snapshot arrives, or a `LuxError` if the
|
|
212
|
+
* subscription is rejected (e.g. a grant `FORBIDDEN`) or the socket fails.
|
|
213
|
+
* Subsequent errors after a successful start surface via `on('error')` and
|
|
214
|
+
* end the async iterator.
|
|
215
|
+
*/
|
|
216
|
+
start(): Promise<LuxError | null>;
|
|
217
|
+
[Symbol.asyncIterator](): AsyncIterator<LuxProjectLiveEvent<T>>;
|
|
218
|
+
private close;
|
|
219
|
+
private spec;
|
|
220
|
+
private handleEvent;
|
|
221
|
+
private emit;
|
|
222
|
+
private pushIterator;
|
|
105
223
|
}
|
|
106
224
|
export declare class LuxProjectInsertBuilder<TResult> extends LuxProjectThenable<TResult> {
|
|
107
225
|
private client;
|