@luxdb/sdk 1.4.2 → 1.4.3

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 CHANGED
@@ -35,8 +35,20 @@ if (error) throw error;
35
35
  Queries and mutations return a Supabase-style result object:
36
36
 
37
37
  ```ts
38
+ interface User {
39
+ id: number;
40
+ email: string;
41
+ age: number;
42
+ }
43
+
44
+ interface Message {
45
+ id: string;
46
+ body: string;
47
+ embedding: number[];
48
+ }
49
+
38
50
  const { data: users, error } = await lux
39
- .table<{ id: number; email: string; age: number }>("users")
51
+ .table<User[]>("users")
40
52
  .select()
41
53
  .gt("age", 25)
42
54
  .order("age", { ascending: false })
@@ -46,6 +58,28 @@ if (error) throw error;
46
58
  console.log(users);
47
59
  ```
48
60
 
61
+ `table<T>()` accepts either a row type or an array type. `table<User>("users")`
62
+ and `table<User[]>("users")` both infer `User` rows; the array form is useful
63
+ when you want the generic to read like the returned data.
64
+
65
+ For computed projections, pass the projection shape to `select<T>()`:
66
+
67
+ ```ts
68
+ import type { LuxAggregateRow, LuxNearRow } from "@luxdb/sdk";
69
+
70
+ type TeamStats = { team_id: number } & LuxAggregateRow<"member_count" | "avg_age">;
71
+
72
+ const { data: teamStats } = await lux
73
+ .table<User>("members")
74
+ .select<TeamStats>("team_id,COUNT(*) AS member_count,AVG(age) AS avg_age")
75
+ .group("team_id");
76
+
77
+ const { data: matches } = await lux
78
+ .table<Message>("messages")
79
+ .select<LuxNearRow<Message>>("id,body,_similarity")
80
+ .near("embedding", queryEmbedding, { k: 10, threshold: 0.8 });
81
+ ```
82
+
49
83
  ```ts
50
84
  const { data: inserted, error: insertError } = await lux
51
85
  .table("messages")
@@ -62,6 +96,31 @@ const { data: deleted, error: deleteError } = await lux
62
96
  .eq("id", inserted?.id);
63
97
  ```
64
98
 
99
+ ## Live tables
100
+
101
+ Browser clients can subscribe to table queries over Lux Live. The SDK opens a WebSocket to the project live endpoint, and Lux core sends a snapshot followed by insert/update/delete events for rows matching the query.
102
+
103
+ ```ts
104
+ const sub = lux
105
+ .table<{ id: string; channel_id: string; body: string }>("messages")
106
+ .eq("channel_id", "general")
107
+ .live()
108
+ .on("snapshot", (event) => {
109
+ console.log(event.rows);
110
+ })
111
+ .on("insert", (event) => {
112
+ console.log(event.new);
113
+ })
114
+ .on("update", (event) => {
115
+ console.log(event.old, event.new);
116
+ })
117
+ .on("delete", (event) => {
118
+ console.log(event.old);
119
+ });
120
+
121
+ await sub.unsubscribe();
122
+ ```
123
+
65
124
  ## OAuth
66
125
 
67
126
  ```ts
@@ -82,6 +141,18 @@ if (error) throw error;
82
141
  console.log(data.user);
83
142
  ```
84
143
 
144
+ Auth types are exported for app code and system table reads:
145
+
146
+ ```ts
147
+ import type { LuxUser, LuxAuthTables } from "@luxdb/sdk";
148
+
149
+ type AuthUserRow = LuxAuthTables["auth.users"];
150
+
151
+ function renderUser(user: LuxUser, row: AuthUserRow) {
152
+ return row.email ?? user.email;
153
+ }
154
+ ```
155
+
85
156
  ## Server client
86
157
 
87
158
  Use a secret key only from trusted server code.
@@ -129,4 +200,6 @@ const value = await lux.get("hello");
129
200
  - `lux_pub_...` keys are safe for browser app calls.
130
201
  - `lux_sec_...` keys are server-only.
131
202
  - User sessions issue JWT access tokens.
203
+ - Browser live subscriptions use the project publishable key plus the signed-in user's JWT.
204
+ - Table `select()` accepts Lux's constrained projection grammar, not arbitrary SQL.
132
205
  - Direct `lux://` or `rediss://` database access uses the database password and is for trusted infrastructure.
@@ -1,15 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LuxProjectMutationBuilder = exports.LuxProjectInsertBuilder = exports.LuxProjectSelectBuilder = exports.LuxProjectTable = exports.LuxProjectClient = void 0;
3
+ exports.LuxProjectMutationBuilder = exports.LuxProjectInsertBuilder = exports.LuxProjectLiveSubscription = exports.LuxProjectSelectBuilder = exports.LuxProjectTable = exports.LuxProjectClient = void 0;
4
4
  exports.createProjectClient = createProjectClient;
5
5
  exports.createClient = createClient;
6
6
  const auth_1 = require("./auth");
7
7
  const utils_1 = require("./utils");
8
8
  class LuxProjectClient {
9
9
  constructor(options) {
10
+ this.liveSocket = null;
11
+ this.liveSubscriptions = new Map();
12
+ this.livePending = [];
10
13
  this.url = options.url.replace(/\/+$/, '');
11
14
  this.key = options.key;
12
15
  this.fetchImpl = resolveFetch(options.fetch);
16
+ this.WebSocketImpl = options.websocket;
13
17
  this.auth = new auth_1.LuxAuthClient({
14
18
  ...options.auth,
15
19
  httpUrl: this.url,
@@ -84,6 +88,96 @@ class LuxProjectClient {
84
88
  return (0, utils_1.err)('LUX_PROJECT_REQUEST_ERROR', 'Lux request failed', (0, utils_1.toLuxError)(error));
85
89
  }
86
90
  }
91
+ async _subscribeLive(spec, handler, error) {
92
+ const id = `sub_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`;
93
+ const record = { id, spec, handler, error };
94
+ this.liveSubscriptions.set(id, record);
95
+ await this.ensureLiveSocket();
96
+ this.sendLive({
97
+ type: 'live.subscribe',
98
+ id,
99
+ spec,
100
+ });
101
+ return () => {
102
+ this.liveSubscriptions.delete(id);
103
+ this.sendLive({ type: 'live.unsubscribe', id });
104
+ if (this.liveSubscriptions.size === 0) {
105
+ this.liveSocket?.close();
106
+ this.liveSocket = null;
107
+ }
108
+ };
109
+ }
110
+ async ensureLiveSocket() {
111
+ const WebSocketImpl = resolveWebSocket(this.WebSocketImpl);
112
+ this.WebSocketImpl = WebSocketImpl;
113
+ if (this.liveSocket &&
114
+ (this.liveSocket.readyState === WebSocketImpl.OPEN ||
115
+ this.liveSocket.readyState === WebSocketImpl.CONNECTING)) {
116
+ return;
117
+ }
118
+ const accessToken = await this.auth.getAccessToken();
119
+ const url = new URL(`${this.url}/live`);
120
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
121
+ url.searchParams.set('apikey', this.key);
122
+ if (accessToken)
123
+ url.searchParams.set('access_token', accessToken);
124
+ const socket = new WebSocketImpl(url.toString());
125
+ this.liveSocket = socket;
126
+ socket.onopen = () => {
127
+ for (const message of this.livePending.splice(0))
128
+ socket.send(message);
129
+ };
130
+ socket.onmessage = (event) => {
131
+ let message;
132
+ try {
133
+ message = JSON.parse(String(event.data));
134
+ }
135
+ catch {
136
+ return;
137
+ }
138
+ const subscription = typeof message.id === 'string' ? this.liveSubscriptions.get(message.id) : null;
139
+ if (message.type === 'live.event' && subscription) {
140
+ subscription.handler(message.event);
141
+ return;
142
+ }
143
+ if (message.type === 'live.error') {
144
+ const target = subscription ? [subscription] : [...this.liveSubscriptions.values()];
145
+ for (const sub of target) {
146
+ sub.error(message.error || { code: 'LIVE_ERROR', message: 'Live subscription failed' });
147
+ }
148
+ }
149
+ };
150
+ socket.onerror = () => {
151
+ for (const subscription of this.liveSubscriptions.values()) {
152
+ subscription.error({ code: 'LIVE_SOCKET_ERROR', message: 'Live socket failed' });
153
+ }
154
+ };
155
+ socket.onclose = () => {
156
+ if (this.liveSocket === socket)
157
+ this.liveSocket = null;
158
+ if (this.liveSubscriptions.size > 0) {
159
+ for (const subscription of this.liveSubscriptions.values()) {
160
+ this.livePending.push(JSON.stringify({
161
+ type: 'live.subscribe',
162
+ id: subscription.id,
163
+ spec: subscription.spec,
164
+ }));
165
+ }
166
+ setTimeout(() => {
167
+ void this.ensureLiveSocket();
168
+ }, 1000);
169
+ }
170
+ };
171
+ }
172
+ sendLive(message) {
173
+ const payload = JSON.stringify(message);
174
+ const WebSocketImpl = this.WebSocketImpl;
175
+ if (WebSocketImpl && this.liveSocket?.readyState === WebSocketImpl.OPEN) {
176
+ this.liveSocket.send(payload);
177
+ return;
178
+ }
179
+ this.livePending.push(payload);
180
+ }
87
181
  }
88
182
  exports.LuxProjectClient = LuxProjectClient;
89
183
  class LuxProjectTable {
@@ -94,6 +188,33 @@ class LuxProjectTable {
94
188
  select(columns = '*') {
95
189
  return new LuxProjectSelectBuilder(this.client, this.name, columns);
96
190
  }
191
+ eq(column, value) {
192
+ return this.select().eq(column, value);
193
+ }
194
+ neq(column, value) {
195
+ return this.select().neq(column, value);
196
+ }
197
+ gt(column, value) {
198
+ return this.select().gt(column, value);
199
+ }
200
+ gte(column, value) {
201
+ return this.select().gte(column, value);
202
+ }
203
+ lt(column, value) {
204
+ return this.select().lt(column, value);
205
+ }
206
+ lte(column, value) {
207
+ return this.select().lte(column, value);
208
+ }
209
+ near(column, vector, options = {}) {
210
+ return this.select().near(column, vector, options);
211
+ }
212
+ is(column, value) {
213
+ return this.select().is(column, value);
214
+ }
215
+ live() {
216
+ return this.select().live();
217
+ }
97
218
  insert(rowOrRows) {
98
219
  return new LuxProjectInsertBuilder(this.client, this.name, rowOrRows);
99
220
  }
@@ -128,6 +249,9 @@ class LuxProjectFilterBuilder extends LuxProjectThenable {
128
249
  this.client = client;
129
250
  this.tableName = tableName;
130
251
  this.filters = [];
252
+ this.joins = [];
253
+ this.groupColumns = [];
254
+ this.havingFilters = [];
131
255
  }
132
256
  eq(column, value) {
133
257
  return this.addFilter(column, 'eq', value);
@@ -150,6 +274,24 @@ class LuxProjectFilterBuilder extends LuxProjectThenable {
150
274
  is(column, value) {
151
275
  return this.addFilter(column, 'is', value);
152
276
  }
277
+ join(table, alias, onLeft, onRight) {
278
+ this.joins.push({ type: 'inner', table, alias, onLeft, onRight });
279
+ return this;
280
+ }
281
+ leftJoin(table, alias, onLeft, onRight) {
282
+ this.joins.push({ type: 'left', table, alias, onLeft, onRight });
283
+ return this;
284
+ }
285
+ group(columns) {
286
+ this.groupColumns = Array.isArray(columns)
287
+ ? columns
288
+ : columns.split(',').map((column) => column.trim()).filter(Boolean);
289
+ return this;
290
+ }
291
+ having(column, operator, value) {
292
+ this.havingFilters.push({ column, operator, value });
293
+ return this;
294
+ }
153
295
  addFilter(column, operator, value) {
154
296
  this.filters.push({ column, operator, value });
155
297
  return this;
@@ -158,6 +300,22 @@ class LuxProjectFilterBuilder extends LuxProjectThenable {
158
300
  const params = new URLSearchParams();
159
301
  if (this.filters.length)
160
302
  params.set('where', filtersToWhere(this.filters));
303
+ for (const join of this.joins) {
304
+ const kind = join.type === 'left' ? ':left' : '';
305
+ params.append('join', `${join.table}:${join.alias}${kind}:on(${join.onLeft}=${join.onRight})`);
306
+ }
307
+ if (this.groupColumns.length)
308
+ params.set('group', this.groupColumns.join(','));
309
+ if (this.havingFilters.length)
310
+ params.set('having', havingToWhere(this.havingFilters));
311
+ if (this.nearQuery) {
312
+ params.set('near_field', this.nearQuery.field);
313
+ params.set('near_vector', `[${this.nearQuery.vector.join(',')}]`);
314
+ params.set('near_k', String(this.nearQuery.k));
315
+ if (this.nearQuery.threshold != null) {
316
+ params.set('near_threshold', String(this.nearQuery.threshold));
317
+ }
318
+ }
161
319
  if (this.orderBy) {
162
320
  params.set('order', `${this.orderBy.column} ${this.orderBy.ascending ? 'ASC' : 'DESC'}`);
163
321
  }
@@ -178,6 +336,15 @@ class LuxProjectSelectBuilder extends LuxProjectFilterBuilder {
178
336
  this.orderBy = { column, ascending: options.ascending ?? true };
179
337
  return this;
180
338
  }
339
+ near(column, vector, options = {}) {
340
+ this.nearQuery = {
341
+ field: column,
342
+ vector,
343
+ k: options.k ?? 10,
344
+ threshold: options.threshold,
345
+ };
346
+ return this;
347
+ }
181
348
  limit(count) {
182
349
  this.limitCount = count;
183
350
  return this;
@@ -210,8 +377,119 @@ class LuxProjectSelectBuilder extends LuxProjectFilterBuilder {
210
377
  }
211
378
  return (0, utils_1.ok)(rows[0]);
212
379
  }
380
+ live() {
381
+ return new LuxProjectLiveSubscription(this.client, this.tableName, this.columns, this.filters, this.nearQuery, this.orderBy, this.limitCount, this.offsetCount);
382
+ }
213
383
  }
214
384
  exports.LuxProjectSelectBuilder = LuxProjectSelectBuilder;
385
+ class LuxProjectLiveSubscription {
386
+ constructor(client, table, columns, filters, nearQuery, orderBy, limitCount, offsetCount) {
387
+ this.client = client;
388
+ this.table = table;
389
+ this.columns = columns;
390
+ this.filters = filters;
391
+ this.nearQuery = nearQuery;
392
+ this.orderBy = orderBy;
393
+ this.limitCount = limitCount;
394
+ this.offsetCount = offsetCount;
395
+ this.handlers = {
396
+ snapshot: [],
397
+ insert: [],
398
+ update: [],
399
+ delete: [],
400
+ error: [],
401
+ change: [],
402
+ };
403
+ this.unsubscribeFn = null;
404
+ void this.start();
405
+ }
406
+ on(type, handler) {
407
+ this.handlers[type].push(handler);
408
+ return this;
409
+ }
410
+ async unsubscribe() {
411
+ this.unsubscribeFn?.();
412
+ this.unsubscribeFn = null;
413
+ }
414
+ async start() {
415
+ this.unsubscribeFn = await this.client._subscribeLive(this.spec(), (event) => this.handleEvent(event), (error) => this.emit({
416
+ type: 'error',
417
+ table: this.table,
418
+ new: null,
419
+ old: null,
420
+ error,
421
+ }));
422
+ }
423
+ spec() {
424
+ const spec = {
425
+ kind: 'table',
426
+ table: this.table,
427
+ select: this.columns,
428
+ };
429
+ if (this.filters.length) {
430
+ spec.where = this.filters.map((filter) => ({
431
+ field: filter.column,
432
+ op: filterOperatorToWhere(filter.operator),
433
+ value: filter.value,
434
+ }));
435
+ }
436
+ if (this.nearQuery) {
437
+ spec.near = {
438
+ field: this.nearQuery.field,
439
+ vector: this.nearQuery.vector,
440
+ k: this.nearQuery.k,
441
+ threshold: this.nearQuery.threshold,
442
+ };
443
+ }
444
+ if (this.orderBy) {
445
+ spec.orderBy = {
446
+ field: this.orderBy.column,
447
+ dir: this.orderBy.ascending ? 'asc' : 'desc',
448
+ };
449
+ }
450
+ if (this.limitCount != null)
451
+ spec.limit = this.limitCount;
452
+ if (this.offsetCount != null)
453
+ spec.offset = this.offsetCount;
454
+ return spec;
455
+ }
456
+ handleEvent(raw) {
457
+ if (!raw || typeof raw !== 'object')
458
+ return;
459
+ const event = raw;
460
+ if (event.kind === 'snapshot') {
461
+ this.emit({
462
+ type: 'snapshot',
463
+ table: this.table,
464
+ new: null,
465
+ old: null,
466
+ rows: Array.isArray(event.rows) ? event.rows : [],
467
+ raw,
468
+ });
469
+ return;
470
+ }
471
+ if (event.kind === 'insert' || event.kind === 'update' || event.kind === 'delete') {
472
+ this.emit({
473
+ type: event.kind,
474
+ table: this.table,
475
+ pk: event.pk == null ? undefined : String(event.pk),
476
+ new: event.row ?? null,
477
+ old: event.previous ?? null,
478
+ changed: Array.isArray(event.changed) ? event.changed : undefined,
479
+ raw,
480
+ });
481
+ }
482
+ }
483
+ emit(event) {
484
+ for (const handler of this.handlers[event.type])
485
+ handler(event);
486
+ if (event.type !== 'snapshot' && event.type !== 'error') {
487
+ for (const handler of this.handlers.change)
488
+ handler(event);
489
+ }
490
+ }
491
+ }
492
+ exports.LuxProjectLiveSubscription = LuxProjectLiveSubscription;
215
493
  class LuxProjectInsertBuilder extends LuxProjectThenable {
216
494
  constructor(client, tableName, rowOrRows) {
217
495
  super();
@@ -273,6 +551,12 @@ function filtersToWhere(filters) {
273
551
  return normalizeWhere(`${filter.column} ${op} ${formatWhereValue(filter.value)}`);
274
552
  }).join(' AND ');
275
553
  }
554
+ function havingToWhere(filters) {
555
+ return filters.map((filter) => {
556
+ const op = filterOperatorToWhere(filter.operator);
557
+ return normalizeWhere(`${filter.column} ${op} ${formatWhereValue(filter.value)}`);
558
+ }).join(' AND ');
559
+ }
276
560
  function filterOperatorToWhere(operator) {
277
561
  switch (operator) {
278
562
  case 'eq':
@@ -311,3 +595,10 @@ function resolveFetch(fetchImpl) {
311
595
  }
312
596
  return candidate;
313
597
  }
598
+ function resolveWebSocket(websocketImpl) {
599
+ const candidate = websocketImpl ?? globalThis.WebSocket;
600
+ if (!candidate) {
601
+ throw new Error('Lux project live subscriptions require a WebSocket implementation');
602
+ }
603
+ return candidate;
604
+ }
package/dist/cjs/table.js CHANGED
@@ -59,9 +59,10 @@ class TableSubscription {
59
59
  try {
60
60
  const initial = await this.fetchMatches();
61
61
  for (const row of initial) {
62
- if (row.id == null)
62
+ const id = row.id;
63
+ if (id == null)
63
64
  continue;
64
- this.knownRows.set(String(row.id), row);
65
+ this.knownRows.set(String(id), row);
65
66
  }
66
67
  const pattern = `_t:${this.table}:row:*`;
67
68
  this.unsubscribeFn = await this.client._subscribePattern(pattern, (raw) => {
@@ -111,7 +112,9 @@ class TableSubscription {
111
112
  if (!previous || !next)
112
113
  return;
113
114
  this.knownRows.set(pk, next);
114
- const changed = Object.keys(next).filter((key) => previous[key] !== next[key]);
115
+ const previousRow = previous;
116
+ const nextRow = next;
117
+ const changed = Object.keys(nextRow).filter((key) => previousRow[key] !== nextRow[key]);
115
118
  this.emitChange({
116
119
  type: 'update',
117
120
  table: this.table,
@@ -132,6 +135,8 @@ exports.TableSubscription = TableSubscription;
132
135
  class TableQueryBuilder {
133
136
  constructor(client, name, options) {
134
137
  this.conditions = [];
138
+ this.groupFields = [];
139
+ this.havingConditions = [];
135
140
  this.selectClause = '*';
136
141
  this.expectSingle = false;
137
142
  this.client = client;
@@ -157,7 +162,7 @@ class TableQueryBuilder {
157
162
  const args = [this.selectClause, 'FROM', this.name];
158
163
  const allConditions = extra ? [...this.conditions, ...extra] : this.conditions;
159
164
  if (this.joinClause) {
160
- args.push('JOIN', this.joinClause.table, this.joinClause.alias, 'ON', this.joinClause.onLeft, '=', this.joinClause.onRight);
165
+ args.push(...(this.joinClause.type === 'LEFT' ? ['LEFT', 'JOIN'] : ['JOIN']), this.joinClause.table, this.joinClause.alias, 'ON', this.joinClause.onLeft, '=', this.joinClause.onRight);
161
166
  }
162
167
  if (allConditions.length) {
163
168
  args.push('WHERE');
@@ -169,6 +174,25 @@ class TableQueryBuilder {
169
174
  }
170
175
  }
171
176
  }
177
+ if (this.groupFields.length) {
178
+ args.push('GROUP', 'BY', ...this.groupFields);
179
+ }
180
+ if (this.havingConditions.length) {
181
+ args.push('HAVING');
182
+ for (let i = 0; i < this.havingConditions.length; i++) {
183
+ const cond = this.havingConditions[i];
184
+ args.push(cond.field, cond.op, String(cond.value));
185
+ if (i < this.havingConditions.length - 1) {
186
+ args.push('AND');
187
+ }
188
+ }
189
+ }
190
+ if (this.similarityClause) {
191
+ args.push('NEAR', this.similarityClause.field, `[${this.similarityClause.vector.join(',')}]`, 'K', String(this.similarityClause.k));
192
+ if (this.similarityClause.threshold != null) {
193
+ args.push('THRESHOLD', String(this.similarityClause.threshold));
194
+ }
195
+ }
172
196
  if (this.orderField) {
173
197
  args.push('ORDER', 'BY', this.orderField, this.orderDir || 'ASC');
174
198
  }
@@ -230,68 +254,41 @@ class TableQueryBuilder {
230
254
  return this;
231
255
  }
232
256
  join(table, alias, onLeft, onRight) {
233
- this.joinClause = { table, alias, onLeft, onRight };
257
+ this.joinClause = { type: 'INNER', table, alias, onLeft, onRight };
258
+ return this;
259
+ }
260
+ leftJoin(table, alias, onLeft, onRight) {
261
+ this.joinClause = { type: 'LEFT', table, alias, onLeft, onRight };
262
+ return this;
263
+ }
264
+ group(fields) {
265
+ this.groupFields = Array.isArray(fields)
266
+ ? fields
267
+ : fields.split(',').map((field) => field.trim()).filter(Boolean);
268
+ return this;
269
+ }
270
+ groupBy(fields) {
271
+ return this.group(fields);
272
+ }
273
+ having(field, op, value) {
274
+ this.havingConditions.push({ field, op, value });
234
275
  return this;
235
276
  }
236
- similar(field, vector, options) {
277
+ near(field, vector, options = {}) {
237
278
  this.similarityClause = {
238
279
  field,
239
280
  vector,
240
- k: options.k,
241
- filter: options.filter,
281
+ k: options.k ?? 10,
282
+ threshold: options.threshold,
242
283
  };
243
284
  return this;
244
285
  }
245
- parseSimilarityPk(result, field) {
246
- const metadata = result.metadata;
247
- if (metadata && typeof metadata === 'object') {
248
- for (const key of ['id', 'pk', 'row_id']) {
249
- const value = metadata[key];
250
- if (value != null)
251
- return String(value);
252
- }
253
- }
254
- const expectedPrefix = `${this.name}:${field}:`;
255
- if (result.key.startsWith(expectedPrefix)) {
256
- return result.key.slice(expectedPrefix.length);
257
- }
258
- const segments = result.key.split(':');
259
- if (segments.length > 0) {
260
- return segments[segments.length - 1] || null;
261
- }
262
- return null;
286
+ similar(field, vector, options = {}) {
287
+ return this.near(field, vector, options);
263
288
  }
264
289
  async run() {
265
290
  try {
266
- let rows = [];
267
- if (this.similarityClause) {
268
- if (this.joinClause) {
269
- return (0, utils_1.err)('SIMILAR_JOIN_UNSUPPORTED', 'similar(...) cannot be combined with join(...) yet');
270
- }
271
- const similarResults = await this.client.vsearch(this.similarityClause.vector, {
272
- k: this.similarityClause.k,
273
- filter: this.similarityClause.filter,
274
- meta: true,
275
- });
276
- for (const match of similarResults) {
277
- const pk = this.parseSimilarityPk(match, this.similarityClause.field);
278
- if (!pk)
279
- continue;
280
- const args = this.buildSelectArgs([{ field: 'id', op: '=', value: pk }]);
281
- const one = await this.client._tselect(args);
282
- if (one.length === 0)
283
- continue;
284
- rows.push({ ...one[0], _similarity: match.similarity });
285
- }
286
- if (this.offsetCount != null || this.limitCount != null) {
287
- const start = this.offsetCount ?? 0;
288
- const end = this.limitCount != null ? start + this.limitCount : undefined;
289
- rows = rows.slice(start, end);
290
- }
291
- }
292
- else {
293
- rows = await this.client._tselect(this.buildSelectArgs());
294
- }
291
+ const rows = await this.client._tselect(this.buildSelectArgs());
295
292
  const validated = rows.map((row) => this.validateRow(row));
296
293
  if (this.expectSingle) {
297
294
  if (validated.length === 0) {
@@ -384,12 +381,6 @@ class TableQueryBuilder {
384
381
  }
385
382
  }
386
383
  subscribe() {
387
- if (this.similarityClause) {
388
- return new TableSubscription(this.client, this.name, (extra) => this.buildSelectArgs(extra), {
389
- code: 'SIMILAR_SUBSCRIBE_UNSUPPORTED',
390
- message: 'subscribe() is not supported on similar(...) queries yet',
391
- });
392
- }
393
384
  return new TableSubscription(this.client, this.name, (extra) => this.buildSelectArgs(extra));
394
385
  }
395
386
  }