@atscript/moost-db 0.1.36 → 0.1.38

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.cjs CHANGED
@@ -26,7 +26,7 @@ const __atscript_typescript_utils = __toESM(require("@atscript/typescript/utils"
26
26
  const __moostjs_event_http = __toESM(require("@moostjs/event-http"));
27
27
  const moost = __toESM(require("moost"));
28
28
  const __uniqu_url = __toESM(require("@uniqu/url"));
29
- const __atscript_utils_db = __toESM(require("@atscript/utils-db"));
29
+ const __atscript_db = __toESM(require("@atscript/db"));
30
30
 
31
31
  //#region packages/moost-db/src/decorators.ts
32
32
  const READABLE_DEF = "__atscript_db_readable_def";
@@ -44,7 +44,7 @@ function transformValidationError(error, reply) {
44
44
  statusCode: 400,
45
45
  errors: error.errors
46
46
  }));
47
- else if (error instanceof __atscript_utils_db.DbError) {
47
+ else if (error instanceof __atscript_db.DbError) {
48
48
  const statusCode = dbErrorCodeToStatus[error.code] ?? 400;
49
49
  reply(new __moostjs_event_http.HttpError(statusCode, {
50
50
  message: error.message,
@@ -140,7 +140,7 @@ _define_property$1(SelectControlDto, "__is_atscript_annotated_type", true);
140
140
  _define_property$1(SelectControlDto, "type", {});
141
141
  _define_property$1(SelectControlDto, "metadata", new Map());
142
142
  _define_property$1(SelectControlDto, "id", "SelectControlDto");
143
- (0, __atscript_typescript_utils.defineAnnotatedType)("object", QueryControlsDto).prop("$skip", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$limit", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$count", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("boolean").tags("boolean").optional().$type).prop("$sort", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript_utils.defineAnnotatedType)("union").item((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SelectControlDto).$type).item((0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").$type).$type).optional().$type).prop("$search", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$index", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$with", (0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithRelationDto).$type).optional().$type);
143
+ (0, __atscript_typescript_utils.defineAnnotatedType)("object", QueryControlsDto).prop("$skip", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$limit", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$count", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("boolean").tags("boolean").optional().$type).prop("$sort", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript_utils.defineAnnotatedType)("union").item((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SelectControlDto).$type).item((0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").$type).$type).optional().$type).prop("$search", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$index", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$vector", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$threshold", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$with", (0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithRelationDto).$type).optional().$type);
144
144
  (0, __atscript_typescript_utils.defineAnnotatedType)("object", PagesControlsDto).prop("$page", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").annotate("expect.pattern", {
145
145
  pattern: "^\\d+$",
146
146
  flags: "u",
@@ -149,7 +149,7 @@ _define_property$1(SelectControlDto, "id", "SelectControlDto");
149
149
  pattern: "^\\d+$",
150
150
  flags: "u",
151
151
  message: "Expected positive number"
152
- }, true).optional().$type).prop("$sort", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript_utils.defineAnnotatedType)("union").item((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SelectControlDto).$type).item((0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").$type).$type).optional().$type).prop("$search", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$index", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$with", (0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithRelationDto).$type).optional().$type);
152
+ }, true).optional().$type).prop("$sort", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript_utils.defineAnnotatedType)("union").item((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SelectControlDto).$type).item((0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").$type).$type).optional().$type).prop("$search", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$index", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$vector", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$threshold", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$with", (0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithRelationDto).$type).optional().$type);
153
153
  (0, __atscript_typescript_utils.defineAnnotatedType)("object", GetOneControlsDto).prop("$select", (0, __atscript_typescript_utils.defineAnnotatedType)("union").item((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SelectControlDto).$type).item((0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").$type).$type).optional().$type).prop("$with", (0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithRelationDto).$type).optional().$type);
154
154
  (0, __atscript_typescript_utils.defineAnnotatedType)("object", WithRelationDto).prop("name", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").$type).prop("filter", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithFilterDto).optional().$type).prop("controls", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithRelationControlsDto).optional().$type).prop("insights", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithFilterDto).optional().$type);
155
155
  (0, __atscript_typescript_utils.defineAnnotatedType)("object", WithRelationControlsDto).prop("$skip", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$limit", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$sort", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript_utils.defineAnnotatedType)("union").item((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SelectControlDto).$type).item((0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").$type).$type).optional().$type).prop("$with", (0, __atscript_typescript_utils.defineAnnotatedType)("array").of((0, __atscript_typescript_utils.defineAnnotatedType)().refTo(WithRelationDto).$type).optional().$type);
@@ -205,7 +205,10 @@ var AsDbReadableController = class {
205
205
  return undefined;
206
206
  }
207
207
  validateInsights(insights) {
208
- for (const key of insights.keys()) if (!this.readable.flatMap.has(key)) return `Unknown field "${key}"`;
208
+ for (const [key] of insights) {
209
+ if (key === "*") continue;
210
+ if (!this.readable.flatMap.has(key)) return `Unknown field "${key}"`;
211
+ }
209
212
  return undefined;
210
213
  }
211
214
  validateParsed(parsed, type) {
@@ -230,6 +233,13 @@ var AsDbReadableController = class {
230
233
  return undefined;
231
234
  }
232
235
  /**
236
+ * Compute an embedding vector from a search term.
237
+ * Override in subclass to integrate with your embedding provider (OpenAI, etc.).
238
+ * Called when `$vector` is present in query controls.
239
+ */ computeEmbedding(_search, _fieldName) {
240
+ throw new __moostjs_event_http.HttpError(501, "Vector search requires computeEmbedding() to be implemented");
241
+ }
242
+ /**
233
243
  * Transform filter before querying. Override to add tenant filtering, etc.
234
244
  */ transformFilter(filter) {
235
245
  return filter;
@@ -284,9 +294,23 @@ var AsDbReadableController = class {
284
294
  * **GET /query** — returns an array of records or a count.
285
295
  */ async query(url) {
286
296
  const parsed = this.parseQueryString(url);
297
+ const controls = parsed.controls;
298
+ const groupBy = controls.$groupBy;
299
+ if (groupBy?.length) {
300
+ if (controls.$with?.length) return new __moostjs_event_http.HttpError(400, "Cannot combine $with and $groupBy in the same query");
301
+ if (parsed.insights) {
302
+ const insightsError = this.validateInsights(parsed.insights);
303
+ if (insightsError) return new __moostjs_event_http.HttpError(400, insightsError);
304
+ }
305
+ const filter$1 = this.transformFilter(parsed.filter);
306
+ return this.readable.aggregate({
307
+ filter: filter$1,
308
+ controls,
309
+ insights: parsed.insights
310
+ });
311
+ }
287
312
  const error = this.validateParsed(parsed, "query");
288
313
  if (error) return error;
289
- const controls = parsed.controls;
290
314
  const filter = this.transformFilter(parsed.filter);
291
315
  const select = this.transformProjection(controls.$select);
292
316
  if (controls.$count) return this.readable.count({
@@ -298,22 +322,24 @@ var AsDbReadableController = class {
298
322
  });
299
323
  const searchTerm = controls.$search;
300
324
  const indexName = controls.$index;
301
- if (searchTerm && this.readable.isSearchable()) return this.readable.search(searchTerm, {
325
+ const vectorField = controls.$vector;
326
+ const threshold = controls.$threshold ? Number(controls.$threshold) : undefined;
327
+ const queryObj = {
302
328
  filter,
303
329
  controls: {
304
330
  ...controls,
305
331
  $select: select,
306
- $limit: controls.$limit || 1e3
332
+ $limit: controls.$limit || 1e3,
333
+ $threshold: threshold
307
334
  }
308
- }, indexName);
309
- return this.readable.findMany({
310
- filter,
311
- controls: {
312
- ...controls,
313
- $select: select,
314
- $limit: controls.$limit || 1e3
315
- }
316
- });
335
+ };
336
+ if (vectorField !== undefined && searchTerm) {
337
+ const vector = await this.computeEmbedding(searchTerm, vectorField || undefined);
338
+ if (vectorField) return this.readable.vectorSearch(vectorField, vector, queryObj);
339
+ return this.readable.vectorSearch(vector, queryObj);
340
+ }
341
+ if (searchTerm && this.readable.isSearchable()) return this.readable.search(searchTerm, queryObj, indexName);
342
+ return this.readable.findMany(queryObj);
317
343
  }
318
344
  /**
319
345
  * **GET /pages** — returns paginated records with metadata.
@@ -329,17 +355,24 @@ var AsDbReadableController = class {
329
355
  const select = this.transformProjection(controls.$select);
330
356
  const searchTerm = controls.$search;
331
357
  const indexName = controls.$index;
358
+ const vectorField = controls.$vector;
359
+ const threshold = controls.$threshold ? Number(controls.$threshold) : undefined;
332
360
  const query = {
333
361
  filter,
334
362
  controls: {
335
363
  ...controls,
336
364
  $select: select,
337
365
  $skip: skip,
338
- $limit: size
366
+ $limit: size,
367
+ $threshold: threshold
339
368
  }
340
369
  };
341
370
  let result;
342
- if (searchTerm && this.readable.isSearchable()) result = await this.readable.searchWithCount(searchTerm, query, indexName);
371
+ if (vectorField !== undefined && searchTerm) {
372
+ const vector = await this.computeEmbedding(searchTerm, vectorField || undefined);
373
+ if (vectorField) result = await this.readable.vectorSearchWithCount(vectorField, vector, query);
374
+ else result = await this.readable.vectorSearchWithCount(vector, query);
375
+ } else if (searchTerm && this.readable.isSearchable()) result = await this.readable.searchWithCount(searchTerm, query, indexName);
343
376
  else result = await this.readable.findManyWithCount(query);
344
377
  return {
345
378
  data: result.data,
@@ -382,6 +415,7 @@ else result = await this.readable.findManyWithCount(query);
382
415
  */ meta() {
383
416
  return {
384
417
  searchable: this.readable.isSearchable(),
418
+ vectorSearchable: this.readable.isVectorSearchable(),
385
419
  searchIndexes: this._searchIndexes,
386
420
  type: this._serializedType
387
421
  };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import * as _atscript_typescript_serialize from '@atscript/typescript/serialize';
2
- import * as _atscript_utils_db from '@atscript/utils-db';
3
- import { AtscriptDbReadable, Uniquery, FilterExpr, UniqueryControls, AtscriptDbTable } from '@atscript/utils-db';
4
- import * as _uniqu_url from '@uniqu/url';
1
+ import * as _atscript_typescript_utils from '@atscript/typescript/utils';
5
2
  import { TAtscriptAnnotatedType, TAtscriptDataType, Validator } from '@atscript/typescript/utils';
3
+ import * as _atscript_db from '@atscript/db';
4
+ import { AtscriptDbReadable, Uniquery, FilterExpr, UniqueryControls, AtscriptDbTable } from '@atscript/db';
5
+ import * as _uniqu_url from '@uniqu/url';
6
6
  import { HttpError } from '@moostjs/event-http';
7
7
  import * as moost from 'moost';
8
8
  import { TConsoleBase, Moost } from 'moost';
@@ -37,6 +37,12 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
37
37
  protected validateControls(controls: Record<string, unknown>, type: 'query' | 'pages' | 'getOne'): string | undefined;
38
38
  protected validateInsights(insights: Map<string, unknown>): string | undefined;
39
39
  protected validateParsed(parsed: Uniquery, type: 'query' | 'pages' | 'getOne'): HttpError | undefined;
40
+ /**
41
+ * Compute an embedding vector from a search term.
42
+ * Override in subclass to integrate with your embedding provider (OpenAI, etc.).
43
+ * Called when `$vector` is present in query controls.
44
+ */
45
+ protected computeEmbedding(_search: string, _fieldName?: string): Promise<number[]>;
40
46
  /**
41
47
  * Transform filter before querying. Override to add tenant filtering, etc.
42
48
  */
@@ -80,8 +86,9 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
80
86
  */
81
87
  meta(): {
82
88
  searchable: boolean;
83
- searchIndexes: _atscript_utils_db.TSearchIndexInfo[];
84
- type: _atscript_typescript_serialize.TSerializedAnnotatedType;
89
+ vectorSearchable: boolean;
90
+ searchIndexes: _atscript_db.TSearchIndexInfo[];
91
+ type: _atscript_typescript_utils.TSerializedAnnotatedType;
85
92
  };
86
93
  }
87
94
 
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { ValidatorError, defineAnnotatedType, serializeAnnotatedType, throwFeatu
2
2
  import { Body, Delete, Get, HttpError, Patch, Post, Put, Query, Url } from "@moostjs/event-http";
3
3
  import { ApplyDecorators, Controller, Inherit, Inject, Intercept, Moost, Param, Provide, TInterceptorPriority, defineInterceptor } from "moost";
4
4
  import { parseUrl } from "@uniqu/url";
5
- import { DbError } from "@atscript/utils-db";
5
+ import { DbError } from "@atscript/db";
6
6
 
7
7
  //#region packages/moost-db/src/decorators.ts
8
8
  const READABLE_DEF = "__atscript_db_readable_def";
@@ -116,7 +116,7 @@ _define_property$1(SelectControlDto, "__is_atscript_annotated_type", true);
116
116
  _define_property$1(SelectControlDto, "type", {});
117
117
  _define_property$1(SelectControlDto, "metadata", new Map());
118
118
  _define_property$1(SelectControlDto, "id", "SelectControlDto");
119
- defineAnnotatedType("object", QueryControlsDto).prop("$skip", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$limit", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$count", defineAnnotatedType().designType("boolean").tags("boolean").optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType("union").item(defineAnnotatedType().refTo(SelectControlDto).$type).item(defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).$type).optional().$type).prop("$search", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$index", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$with", defineAnnotatedType("array").of(defineAnnotatedType().refTo(WithRelationDto).$type).optional().$type);
119
+ defineAnnotatedType("object", QueryControlsDto).prop("$skip", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$limit", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$count", defineAnnotatedType().designType("boolean").tags("boolean").optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType("union").item(defineAnnotatedType().refTo(SelectControlDto).$type).item(defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).$type).optional().$type).prop("$search", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$index", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$vector", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$threshold", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$with", defineAnnotatedType("array").of(defineAnnotatedType().refTo(WithRelationDto).$type).optional().$type);
120
120
  defineAnnotatedType("object", PagesControlsDto).prop("$page", defineAnnotatedType().designType("string").tags("string").annotate("expect.pattern", {
121
121
  pattern: "^\\d+$",
122
122
  flags: "u",
@@ -125,7 +125,7 @@ defineAnnotatedType("object", PagesControlsDto).prop("$page", defineAnnotatedTyp
125
125
  pattern: "^\\d+$",
126
126
  flags: "u",
127
127
  message: "Expected positive number"
128
- }, true).optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType("union").item(defineAnnotatedType().refTo(SelectControlDto).$type).item(defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).$type).optional().$type).prop("$search", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$index", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$with", defineAnnotatedType("array").of(defineAnnotatedType().refTo(WithRelationDto).$type).optional().$type);
128
+ }, true).optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType("union").item(defineAnnotatedType().refTo(SelectControlDto).$type).item(defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).$type).optional().$type).prop("$search", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$index", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$vector", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$threshold", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$with", defineAnnotatedType("array").of(defineAnnotatedType().refTo(WithRelationDto).$type).optional().$type);
129
129
  defineAnnotatedType("object", GetOneControlsDto).prop("$select", defineAnnotatedType("union").item(defineAnnotatedType().refTo(SelectControlDto).$type).item(defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).$type).optional().$type).prop("$with", defineAnnotatedType("array").of(defineAnnotatedType().refTo(WithRelationDto).$type).optional().$type);
130
130
  defineAnnotatedType("object", WithRelationDto).prop("name", defineAnnotatedType().designType("string").tags("string").$type).prop("filter", defineAnnotatedType().refTo(WithFilterDto).optional().$type).prop("controls", defineAnnotatedType().refTo(WithRelationControlsDto).optional().$type).prop("insights", defineAnnotatedType().refTo(WithFilterDto).optional().$type);
131
131
  defineAnnotatedType("object", WithRelationControlsDto).prop("$skip", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$limit", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType("union").item(defineAnnotatedType().refTo(SelectControlDto).$type).item(defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).$type).optional().$type).prop("$with", defineAnnotatedType("array").of(defineAnnotatedType().refTo(WithRelationDto).$type).optional().$type);
@@ -181,7 +181,10 @@ var AsDbReadableController = class {
181
181
  return undefined;
182
182
  }
183
183
  validateInsights(insights) {
184
- for (const key of insights.keys()) if (!this.readable.flatMap.has(key)) return `Unknown field "${key}"`;
184
+ for (const [key] of insights) {
185
+ if (key === "*") continue;
186
+ if (!this.readable.flatMap.has(key)) return `Unknown field "${key}"`;
187
+ }
185
188
  return undefined;
186
189
  }
187
190
  validateParsed(parsed, type) {
@@ -206,6 +209,13 @@ var AsDbReadableController = class {
206
209
  return undefined;
207
210
  }
208
211
  /**
212
+ * Compute an embedding vector from a search term.
213
+ * Override in subclass to integrate with your embedding provider (OpenAI, etc.).
214
+ * Called when `$vector` is present in query controls.
215
+ */ computeEmbedding(_search, _fieldName) {
216
+ throw new HttpError(501, "Vector search requires computeEmbedding() to be implemented");
217
+ }
218
+ /**
209
219
  * Transform filter before querying. Override to add tenant filtering, etc.
210
220
  */ transformFilter(filter) {
211
221
  return filter;
@@ -260,9 +270,23 @@ var AsDbReadableController = class {
260
270
  * **GET /query** — returns an array of records or a count.
261
271
  */ async query(url) {
262
272
  const parsed = this.parseQueryString(url);
273
+ const controls = parsed.controls;
274
+ const groupBy = controls.$groupBy;
275
+ if (groupBy?.length) {
276
+ if (controls.$with?.length) return new HttpError(400, "Cannot combine $with and $groupBy in the same query");
277
+ if (parsed.insights) {
278
+ const insightsError = this.validateInsights(parsed.insights);
279
+ if (insightsError) return new HttpError(400, insightsError);
280
+ }
281
+ const filter$1 = this.transformFilter(parsed.filter);
282
+ return this.readable.aggregate({
283
+ filter: filter$1,
284
+ controls,
285
+ insights: parsed.insights
286
+ });
287
+ }
263
288
  const error = this.validateParsed(parsed, "query");
264
289
  if (error) return error;
265
- const controls = parsed.controls;
266
290
  const filter = this.transformFilter(parsed.filter);
267
291
  const select = this.transformProjection(controls.$select);
268
292
  if (controls.$count) return this.readable.count({
@@ -274,22 +298,24 @@ var AsDbReadableController = class {
274
298
  });
275
299
  const searchTerm = controls.$search;
276
300
  const indexName = controls.$index;
277
- if (searchTerm && this.readable.isSearchable()) return this.readable.search(searchTerm, {
301
+ const vectorField = controls.$vector;
302
+ const threshold = controls.$threshold ? Number(controls.$threshold) : undefined;
303
+ const queryObj = {
278
304
  filter,
279
305
  controls: {
280
306
  ...controls,
281
307
  $select: select,
282
- $limit: controls.$limit || 1e3
308
+ $limit: controls.$limit || 1e3,
309
+ $threshold: threshold
283
310
  }
284
- }, indexName);
285
- return this.readable.findMany({
286
- filter,
287
- controls: {
288
- ...controls,
289
- $select: select,
290
- $limit: controls.$limit || 1e3
291
- }
292
- });
311
+ };
312
+ if (vectorField !== undefined && searchTerm) {
313
+ const vector = await this.computeEmbedding(searchTerm, vectorField || undefined);
314
+ if (vectorField) return this.readable.vectorSearch(vectorField, vector, queryObj);
315
+ return this.readable.vectorSearch(vector, queryObj);
316
+ }
317
+ if (searchTerm && this.readable.isSearchable()) return this.readable.search(searchTerm, queryObj, indexName);
318
+ return this.readable.findMany(queryObj);
293
319
  }
294
320
  /**
295
321
  * **GET /pages** — returns paginated records with metadata.
@@ -305,17 +331,24 @@ var AsDbReadableController = class {
305
331
  const select = this.transformProjection(controls.$select);
306
332
  const searchTerm = controls.$search;
307
333
  const indexName = controls.$index;
334
+ const vectorField = controls.$vector;
335
+ const threshold = controls.$threshold ? Number(controls.$threshold) : undefined;
308
336
  const query = {
309
337
  filter,
310
338
  controls: {
311
339
  ...controls,
312
340
  $select: select,
313
341
  $skip: skip,
314
- $limit: size
342
+ $limit: size,
343
+ $threshold: threshold
315
344
  }
316
345
  };
317
346
  let result;
318
- if (searchTerm && this.readable.isSearchable()) result = await this.readable.searchWithCount(searchTerm, query, indexName);
347
+ if (vectorField !== undefined && searchTerm) {
348
+ const vector = await this.computeEmbedding(searchTerm, vectorField || undefined);
349
+ if (vectorField) result = await this.readable.vectorSearchWithCount(vectorField, vector, query);
350
+ else result = await this.readable.vectorSearchWithCount(vector, query);
351
+ } else if (searchTerm && this.readable.isSearchable()) result = await this.readable.searchWithCount(searchTerm, query, indexName);
319
352
  else result = await this.readable.findManyWithCount(query);
320
353
  return {
321
354
  data: result.data,
@@ -358,6 +391,7 @@ else result = await this.readable.findManyWithCount(query);
358
391
  */ meta() {
359
392
  return {
360
393
  searchable: this.readable.isSearchable(),
394
+ vectorSearchable: this.readable.isVectorSearchable(),
361
395
  searchIndexes: this._searchIndexes,
362
396
  type: this._serializedType
363
397
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/moost-db",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "Generic database controller for Moost with Atscript.",
5
5
  "keywords": [
6
6
  "annotations",
@@ -35,20 +35,20 @@
35
35
  "./package.json": "./package.json"
36
36
  },
37
37
  "dependencies": {
38
- "@uniqu/url": "^0.0.6"
38
+ "@uniqu/url": "^0.1.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@moostjs/event-http": "^0.6.2",
42
42
  "moost": "^0.6.2",
43
43
  "vitest": "3.2.4",
44
- "@atscript/core": "^0.1.36"
44
+ "@atscript/core": "^0.1.38"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@moostjs/event-http": "^0.6.2",
48
48
  "moost": "^0.6.2",
49
- "@uniqu/core": "^0.0.6",
50
- "@atscript/utils-db": "^0.1.36",
51
- "@atscript/typescript": "^0.1.36"
49
+ "@uniqu/core": "^0.1.2",
50
+ "@atscript/db": "^0.1.38",
51
+ "@atscript/typescript": "^0.1.38"
52
52
  },
53
53
  "scripts": {
54
54
  "pub": "pnpm publish --access public",