@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/project.js
CHANGED
|
@@ -2,9 +2,13 @@ import { LuxAuthClient } from './auth.js';
|
|
|
2
2
|
import { err, ok, toLuxError } from './utils.js';
|
|
3
3
|
export class LuxProjectClient {
|
|
4
4
|
constructor(options) {
|
|
5
|
+
this.liveSocket = null;
|
|
6
|
+
this.liveSubscriptions = new Map();
|
|
7
|
+
this.livePending = [];
|
|
5
8
|
this.url = options.url.replace(/\/+$/, '');
|
|
6
9
|
this.key = options.key;
|
|
7
10
|
this.fetchImpl = resolveFetch(options.fetch);
|
|
11
|
+
this.WebSocketImpl = options.websocket;
|
|
8
12
|
this.auth = new LuxAuthClient({
|
|
9
13
|
...options.auth,
|
|
10
14
|
httpUrl: this.url,
|
|
@@ -79,6 +83,96 @@ export class LuxProjectClient {
|
|
|
79
83
|
return err('LUX_PROJECT_REQUEST_ERROR', 'Lux request failed', toLuxError(error));
|
|
80
84
|
}
|
|
81
85
|
}
|
|
86
|
+
async _subscribeLive(spec, handler, error) {
|
|
87
|
+
const id = `sub_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`;
|
|
88
|
+
const record = { id, spec, handler, error };
|
|
89
|
+
this.liveSubscriptions.set(id, record);
|
|
90
|
+
await this.ensureLiveSocket();
|
|
91
|
+
this.sendLive({
|
|
92
|
+
type: 'live.subscribe',
|
|
93
|
+
id,
|
|
94
|
+
spec,
|
|
95
|
+
});
|
|
96
|
+
return () => {
|
|
97
|
+
this.liveSubscriptions.delete(id);
|
|
98
|
+
this.sendLive({ type: 'live.unsubscribe', id });
|
|
99
|
+
if (this.liveSubscriptions.size === 0) {
|
|
100
|
+
this.liveSocket?.close();
|
|
101
|
+
this.liveSocket = null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async ensureLiveSocket() {
|
|
106
|
+
const WebSocketImpl = resolveWebSocket(this.WebSocketImpl);
|
|
107
|
+
this.WebSocketImpl = WebSocketImpl;
|
|
108
|
+
if (this.liveSocket &&
|
|
109
|
+
(this.liveSocket.readyState === WebSocketImpl.OPEN ||
|
|
110
|
+
this.liveSocket.readyState === WebSocketImpl.CONNECTING)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const accessToken = await this.auth.getAccessToken();
|
|
114
|
+
const url = new URL(`${this.url}/live`);
|
|
115
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
116
|
+
url.searchParams.set('apikey', this.key);
|
|
117
|
+
if (accessToken)
|
|
118
|
+
url.searchParams.set('access_token', accessToken);
|
|
119
|
+
const socket = new WebSocketImpl(url.toString());
|
|
120
|
+
this.liveSocket = socket;
|
|
121
|
+
socket.onopen = () => {
|
|
122
|
+
for (const message of this.livePending.splice(0))
|
|
123
|
+
socket.send(message);
|
|
124
|
+
};
|
|
125
|
+
socket.onmessage = (event) => {
|
|
126
|
+
let message;
|
|
127
|
+
try {
|
|
128
|
+
message = JSON.parse(String(event.data));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const subscription = typeof message.id === 'string' ? this.liveSubscriptions.get(message.id) : null;
|
|
134
|
+
if (message.type === 'live.event' && subscription) {
|
|
135
|
+
subscription.handler(message.event);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (message.type === 'live.error') {
|
|
139
|
+
const target = subscription ? [subscription] : [...this.liveSubscriptions.values()];
|
|
140
|
+
for (const sub of target) {
|
|
141
|
+
sub.error(message.error || { code: 'LIVE_ERROR', message: 'Live subscription failed' });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
socket.onerror = () => {
|
|
146
|
+
for (const subscription of this.liveSubscriptions.values()) {
|
|
147
|
+
subscription.error({ code: 'LIVE_SOCKET_ERROR', message: 'Live socket failed' });
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
socket.onclose = () => {
|
|
151
|
+
if (this.liveSocket === socket)
|
|
152
|
+
this.liveSocket = null;
|
|
153
|
+
if (this.liveSubscriptions.size > 0) {
|
|
154
|
+
for (const subscription of this.liveSubscriptions.values()) {
|
|
155
|
+
this.livePending.push(JSON.stringify({
|
|
156
|
+
type: 'live.subscribe',
|
|
157
|
+
id: subscription.id,
|
|
158
|
+
spec: subscription.spec,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
void this.ensureLiveSocket();
|
|
163
|
+
}, 1000);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
sendLive(message) {
|
|
168
|
+
const payload = JSON.stringify(message);
|
|
169
|
+
const WebSocketImpl = this.WebSocketImpl;
|
|
170
|
+
if (WebSocketImpl && this.liveSocket?.readyState === WebSocketImpl.OPEN) {
|
|
171
|
+
this.liveSocket.send(payload);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.livePending.push(payload);
|
|
175
|
+
}
|
|
82
176
|
}
|
|
83
177
|
export class LuxProjectTable {
|
|
84
178
|
constructor(client, name) {
|
|
@@ -88,6 +182,33 @@ export class LuxProjectTable {
|
|
|
88
182
|
select(columns = '*') {
|
|
89
183
|
return new LuxProjectSelectBuilder(this.client, this.name, columns);
|
|
90
184
|
}
|
|
185
|
+
eq(column, value) {
|
|
186
|
+
return this.select().eq(column, value);
|
|
187
|
+
}
|
|
188
|
+
neq(column, value) {
|
|
189
|
+
return this.select().neq(column, value);
|
|
190
|
+
}
|
|
191
|
+
gt(column, value) {
|
|
192
|
+
return this.select().gt(column, value);
|
|
193
|
+
}
|
|
194
|
+
gte(column, value) {
|
|
195
|
+
return this.select().gte(column, value);
|
|
196
|
+
}
|
|
197
|
+
lt(column, value) {
|
|
198
|
+
return this.select().lt(column, value);
|
|
199
|
+
}
|
|
200
|
+
lte(column, value) {
|
|
201
|
+
return this.select().lte(column, value);
|
|
202
|
+
}
|
|
203
|
+
near(column, vector, options = {}) {
|
|
204
|
+
return this.select().near(column, vector, options);
|
|
205
|
+
}
|
|
206
|
+
is(column, value) {
|
|
207
|
+
return this.select().is(column, value);
|
|
208
|
+
}
|
|
209
|
+
live() {
|
|
210
|
+
return this.select().live();
|
|
211
|
+
}
|
|
91
212
|
insert(rowOrRows) {
|
|
92
213
|
return new LuxProjectInsertBuilder(this.client, this.name, rowOrRows);
|
|
93
214
|
}
|
|
@@ -121,6 +242,9 @@ class LuxProjectFilterBuilder extends LuxProjectThenable {
|
|
|
121
242
|
this.client = client;
|
|
122
243
|
this.tableName = tableName;
|
|
123
244
|
this.filters = [];
|
|
245
|
+
this.joins = [];
|
|
246
|
+
this.groupColumns = [];
|
|
247
|
+
this.havingFilters = [];
|
|
124
248
|
}
|
|
125
249
|
eq(column, value) {
|
|
126
250
|
return this.addFilter(column, 'eq', value);
|
|
@@ -143,6 +267,39 @@ class LuxProjectFilterBuilder extends LuxProjectThenable {
|
|
|
143
267
|
is(column, value) {
|
|
144
268
|
return this.addFilter(column, 'is', value);
|
|
145
269
|
}
|
|
270
|
+
in(column, values) {
|
|
271
|
+
return this.addFilter(column, 'in', values);
|
|
272
|
+
}
|
|
273
|
+
notIn(column, values) {
|
|
274
|
+
return this.addFilter(column, 'notIn', values);
|
|
275
|
+
}
|
|
276
|
+
isValid(column) {
|
|
277
|
+
return this.addFilter(column, 'isValid', '');
|
|
278
|
+
}
|
|
279
|
+
isNotValid(column) {
|
|
280
|
+
return this.addFilter(column, 'isNotValid', '');
|
|
281
|
+
}
|
|
282
|
+
contains(column, value) {
|
|
283
|
+
return this.addFilter(column, 'contains', value);
|
|
284
|
+
}
|
|
285
|
+
join(table, alias, onLeft, onRight) {
|
|
286
|
+
this.joins.push({ type: 'inner', table, alias, onLeft, onRight });
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
leftJoin(table, alias, onLeft, onRight) {
|
|
290
|
+
this.joins.push({ type: 'left', table, alias, onLeft, onRight });
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
group(columns) {
|
|
294
|
+
this.groupColumns = Array.isArray(columns)
|
|
295
|
+
? columns
|
|
296
|
+
: columns.split(',').map((column) => column.trim()).filter(Boolean);
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
having(column, operator, value) {
|
|
300
|
+
this.havingFilters.push({ column, operator, value });
|
|
301
|
+
return this;
|
|
302
|
+
}
|
|
146
303
|
addFilter(column, operator, value) {
|
|
147
304
|
this.filters.push({ column, operator, value });
|
|
148
305
|
return this;
|
|
@@ -151,6 +308,22 @@ class LuxProjectFilterBuilder extends LuxProjectThenable {
|
|
|
151
308
|
const params = new URLSearchParams();
|
|
152
309
|
if (this.filters.length)
|
|
153
310
|
params.set('where', filtersToWhere(this.filters));
|
|
311
|
+
for (const join of this.joins) {
|
|
312
|
+
const kind = join.type === 'left' ? ':left' : '';
|
|
313
|
+
params.append('join', `${join.table}:${join.alias}${kind}:on(${join.onLeft}=${join.onRight})`);
|
|
314
|
+
}
|
|
315
|
+
if (this.groupColumns.length)
|
|
316
|
+
params.set('group', this.groupColumns.join(','));
|
|
317
|
+
if (this.havingFilters.length)
|
|
318
|
+
params.set('having', havingToWhere(this.havingFilters));
|
|
319
|
+
if (this.nearQuery) {
|
|
320
|
+
params.set('near_field', this.nearQuery.field);
|
|
321
|
+
params.set('near_vector', `[${this.nearQuery.vector.join(',')}]`);
|
|
322
|
+
params.set('near_k', String(this.nearQuery.k));
|
|
323
|
+
if (this.nearQuery.threshold != null) {
|
|
324
|
+
params.set('near_threshold', String(this.nearQuery.threshold));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
154
327
|
if (this.orderBy) {
|
|
155
328
|
params.set('order', `${this.orderBy.column} ${this.orderBy.ascending ? 'ASC' : 'DESC'}`);
|
|
156
329
|
}
|
|
@@ -171,6 +344,15 @@ export class LuxProjectSelectBuilder extends LuxProjectFilterBuilder {
|
|
|
171
344
|
this.orderBy = { column, ascending: options.ascending ?? true };
|
|
172
345
|
return this;
|
|
173
346
|
}
|
|
347
|
+
near(column, vector, options = {}) {
|
|
348
|
+
this.nearQuery = {
|
|
349
|
+
field: column,
|
|
350
|
+
vector,
|
|
351
|
+
k: options.k ?? 10,
|
|
352
|
+
threshold: options.threshold,
|
|
353
|
+
};
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
174
356
|
limit(count) {
|
|
175
357
|
this.limitCount = count;
|
|
176
358
|
return this;
|
|
@@ -203,6 +385,199 @@ export class LuxProjectSelectBuilder extends LuxProjectFilterBuilder {
|
|
|
203
385
|
}
|
|
204
386
|
return ok(rows[0]);
|
|
205
387
|
}
|
|
388
|
+
async live() {
|
|
389
|
+
const live = new LuxProjectLiveSubscription(this.client, this.tableName, this.columns, this.filters, this.nearQuery, this.orderBy, this.limitCount, this.offsetCount);
|
|
390
|
+
const error = await live.start();
|
|
391
|
+
if (error) {
|
|
392
|
+
await live.unsubscribe();
|
|
393
|
+
return { live: null, error };
|
|
394
|
+
}
|
|
395
|
+
return { live, error: null };
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
export class LuxProjectLiveSubscription {
|
|
399
|
+
constructor(client, table, columns, filters, nearQuery, orderBy, limitCount, offsetCount) {
|
|
400
|
+
this.client = client;
|
|
401
|
+
this.table = table;
|
|
402
|
+
this.columns = columns;
|
|
403
|
+
this.filters = filters;
|
|
404
|
+
this.nearQuery = nearQuery;
|
|
405
|
+
this.orderBy = orderBy;
|
|
406
|
+
this.limitCount = limitCount;
|
|
407
|
+
this.offsetCount = offsetCount;
|
|
408
|
+
this.handlers = {
|
|
409
|
+
snapshot: [],
|
|
410
|
+
insert: [],
|
|
411
|
+
update: [],
|
|
412
|
+
delete: [],
|
|
413
|
+
error: [],
|
|
414
|
+
change: [],
|
|
415
|
+
};
|
|
416
|
+
this.unsubscribeFn = null;
|
|
417
|
+
// Async-iterator plumbing: events buffer in `queue` until a `for await`
|
|
418
|
+
// consumer pulls them; pending `next()` calls park in `waiters`.
|
|
419
|
+
this.queue = [];
|
|
420
|
+
this.waiters = [];
|
|
421
|
+
this.closed = false;
|
|
422
|
+
}
|
|
423
|
+
on(type, handler) {
|
|
424
|
+
this.handlers[type].push(handler);
|
|
425
|
+
return this;
|
|
426
|
+
}
|
|
427
|
+
async unsubscribe() {
|
|
428
|
+
this.unsubscribeFn?.();
|
|
429
|
+
this.unsubscribeFn = null;
|
|
430
|
+
this.close();
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Open the subscription and wait for the server to confirm it. Resolves
|
|
434
|
+
* `null` once the initial snapshot arrives, or a `LuxError` if the
|
|
435
|
+
* subscription is rejected (e.g. a grant `FORBIDDEN`) or the socket fails.
|
|
436
|
+
* Subsequent errors after a successful start surface via `on('error')` and
|
|
437
|
+
* end the async iterator.
|
|
438
|
+
*/
|
|
439
|
+
async start() {
|
|
440
|
+
let settled = false;
|
|
441
|
+
let settle;
|
|
442
|
+
const ready = new Promise((resolve) => {
|
|
443
|
+
settle = (error) => {
|
|
444
|
+
if (settled)
|
|
445
|
+
return;
|
|
446
|
+
settled = true;
|
|
447
|
+
resolve(error);
|
|
448
|
+
};
|
|
449
|
+
});
|
|
450
|
+
// Safety net: a server that never answers shouldn't hang the caller.
|
|
451
|
+
const timeout = setTimeout(() => {
|
|
452
|
+
settle({ code: 'LIVE_TIMEOUT', message: 'Timed out establishing live subscription' });
|
|
453
|
+
}, 15000);
|
|
454
|
+
this.unsubscribeFn = await this.client._subscribeLive(this.spec(), (event) => {
|
|
455
|
+
const kind = event?.kind;
|
|
456
|
+
this.handleEvent(event);
|
|
457
|
+
if (kind === 'snapshot')
|
|
458
|
+
settle(null);
|
|
459
|
+
}, (error) => {
|
|
460
|
+
const luxError = {
|
|
461
|
+
code: error.code ?? 'LIVE_ERROR',
|
|
462
|
+
message: error.message ?? 'Live subscription failed',
|
|
463
|
+
};
|
|
464
|
+
if (settled) {
|
|
465
|
+
// Post-start failure: notify handlers and end the stream.
|
|
466
|
+
this.emit({ type: 'error', table: this.table, new: null, old: null, error });
|
|
467
|
+
this.close();
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
settle(luxError);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
const result = await ready;
|
|
474
|
+
clearTimeout(timeout);
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
[Symbol.asyncIterator]() {
|
|
478
|
+
return {
|
|
479
|
+
next: () => {
|
|
480
|
+
const buffered = this.queue.shift();
|
|
481
|
+
if (buffered)
|
|
482
|
+
return Promise.resolve({ value: buffered, done: false });
|
|
483
|
+
if (this.closed)
|
|
484
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
485
|
+
return new Promise((resolve) => this.waiters.push(resolve));
|
|
486
|
+
},
|
|
487
|
+
return: () => {
|
|
488
|
+
void this.unsubscribe();
|
|
489
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
close() {
|
|
494
|
+
if (this.closed)
|
|
495
|
+
return;
|
|
496
|
+
this.closed = true;
|
|
497
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
498
|
+
waiter({ value: undefined, done: true });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
spec() {
|
|
502
|
+
const spec = {
|
|
503
|
+
kind: 'table',
|
|
504
|
+
table: this.table,
|
|
505
|
+
select: this.columns,
|
|
506
|
+
};
|
|
507
|
+
if (this.filters.length) {
|
|
508
|
+
spec.where = this.filters.map((filter) => ({
|
|
509
|
+
field: filter.column,
|
|
510
|
+
op: filterOperatorToWhere(filter.operator),
|
|
511
|
+
value: filter.value,
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
if (this.nearQuery) {
|
|
515
|
+
spec.near = {
|
|
516
|
+
field: this.nearQuery.field,
|
|
517
|
+
vector: this.nearQuery.vector,
|
|
518
|
+
k: this.nearQuery.k,
|
|
519
|
+
threshold: this.nearQuery.threshold,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
if (this.orderBy) {
|
|
523
|
+
spec.orderBy = {
|
|
524
|
+
field: this.orderBy.column,
|
|
525
|
+
dir: this.orderBy.ascending ? 'asc' : 'desc',
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
if (this.limitCount != null)
|
|
529
|
+
spec.limit = this.limitCount;
|
|
530
|
+
if (this.offsetCount != null)
|
|
531
|
+
spec.offset = this.offsetCount;
|
|
532
|
+
return spec;
|
|
533
|
+
}
|
|
534
|
+
handleEvent(raw) {
|
|
535
|
+
if (!raw || typeof raw !== 'object')
|
|
536
|
+
return;
|
|
537
|
+
const event = raw;
|
|
538
|
+
if (event.kind === 'snapshot') {
|
|
539
|
+
this.emit({
|
|
540
|
+
type: 'snapshot',
|
|
541
|
+
table: this.table,
|
|
542
|
+
new: null,
|
|
543
|
+
old: null,
|
|
544
|
+
rows: Array.isArray(event.rows) ? event.rows : [],
|
|
545
|
+
raw,
|
|
546
|
+
});
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (event.kind === 'insert' || event.kind === 'update' || event.kind === 'delete') {
|
|
550
|
+
this.emit({
|
|
551
|
+
type: event.kind,
|
|
552
|
+
table: this.table,
|
|
553
|
+
pk: event.pk == null ? undefined : String(event.pk),
|
|
554
|
+
new: event.row ?? null,
|
|
555
|
+
old: event.previous ?? null,
|
|
556
|
+
changed: Array.isArray(event.changed) ? event.changed : undefined,
|
|
557
|
+
raw,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
emit(event) {
|
|
562
|
+
for (const handler of this.handlers[event.type])
|
|
563
|
+
handler(event);
|
|
564
|
+
if (event.type !== 'snapshot' && event.type !== 'error') {
|
|
565
|
+
for (const handler of this.handlers.change)
|
|
566
|
+
handler(event);
|
|
567
|
+
}
|
|
568
|
+
// Feed `for await` consumers the data events (errors end the stream via close()).
|
|
569
|
+
if (event.type !== 'error')
|
|
570
|
+
this.pushIterator(event);
|
|
571
|
+
}
|
|
572
|
+
pushIterator(event) {
|
|
573
|
+
if (this.closed)
|
|
574
|
+
return;
|
|
575
|
+
const waiter = this.waiters.shift();
|
|
576
|
+
if (waiter)
|
|
577
|
+
waiter({ value: event, done: false });
|
|
578
|
+
else
|
|
579
|
+
this.queue.push(event);
|
|
580
|
+
}
|
|
206
581
|
}
|
|
207
582
|
export class LuxProjectInsertBuilder extends LuxProjectThenable {
|
|
208
583
|
constructor(client, tableName, rowOrRows) {
|
|
@@ -258,6 +633,19 @@ function normalizeWhere(where) {
|
|
|
258
633
|
return where.trim().replace(/\s*(>=|<=|!=|=|>|<)\s*/g, ' $1 ');
|
|
259
634
|
}
|
|
260
635
|
function filtersToWhere(filters) {
|
|
636
|
+
return filters.map((filter) => {
|
|
637
|
+
const op = filterOperatorToWhere(filter.operator);
|
|
638
|
+
if (filter.operator === 'in' || filter.operator === 'notIn') {
|
|
639
|
+
const values = Array.isArray(filter.value) ? filter.value : [filter.value];
|
|
640
|
+
return normalizeWhere(`${filter.column} ${op} ( ${values.map(formatWhereValue).join(' ')} )`);
|
|
641
|
+
}
|
|
642
|
+
if (filter.operator === 'isValid' || filter.operator === 'isNotValid') {
|
|
643
|
+
return normalizeWhere(`${filter.column} ${op}`);
|
|
644
|
+
}
|
|
645
|
+
return normalizeWhere(`${filter.column} ${op} ${formatWhereValue(filter.value)}`);
|
|
646
|
+
}).join(' AND ');
|
|
647
|
+
}
|
|
648
|
+
function havingToWhere(filters) {
|
|
261
649
|
return filters.map((filter) => {
|
|
262
650
|
const op = filterOperatorToWhere(filter.operator);
|
|
263
651
|
return normalizeWhere(`${filter.column} ${op} ${formatWhereValue(filter.value)}`);
|
|
@@ -278,6 +666,16 @@ function filterOperatorToWhere(operator) {
|
|
|
278
666
|
return '<';
|
|
279
667
|
case 'lte':
|
|
280
668
|
return '<=';
|
|
669
|
+
case 'in':
|
|
670
|
+
return 'IN';
|
|
671
|
+
case 'notIn':
|
|
672
|
+
return 'NOT IN';
|
|
673
|
+
case 'isValid':
|
|
674
|
+
return 'IS VALID';
|
|
675
|
+
case 'isNotValid':
|
|
676
|
+
return 'IS NOT VALID';
|
|
677
|
+
case 'contains':
|
|
678
|
+
return 'CONTAINS';
|
|
281
679
|
}
|
|
282
680
|
}
|
|
283
681
|
function formatWhereValue(value) {
|
|
@@ -301,3 +699,10 @@ function resolveFetch(fetchImpl) {
|
|
|
301
699
|
}
|
|
302
700
|
return candidate;
|
|
303
701
|
}
|
|
702
|
+
function resolveWebSocket(websocketImpl) {
|
|
703
|
+
const candidate = websocketImpl ?? globalThis.WebSocket;
|
|
704
|
+
if (!candidate) {
|
|
705
|
+
throw new Error('Lux project live subscriptions require a WebSocket implementation');
|
|
706
|
+
}
|
|
707
|
+
return candidate;
|
|
708
|
+
}
|