@atscript/moost-mongo 0.0.19 → 0.0.21

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
@@ -59,7 +59,7 @@ let SelectControlDto = class SelectControlDto$1 {};
59
59
  _define_property$1(SelectControlDto, "__is_atscript_annotated_type", true);
60
60
  _define_property$1(SelectControlDto, "type", {});
61
61
  _define_property$1(SelectControlDto, "metadata", new Map());
62
- (0, __atscript_typescript.defineAnnotatedType)("object", QueryControlsDto).prop("$skip", (0, __atscript_typescript.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.min", 0).annotate("expect.int", true).optional().$type).prop("$limit", (0, __atscript_typescript.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.min", 0).annotate("expect.int", true).optional().$type).prop("$count", (0, __atscript_typescript.defineAnnotatedType)().designType("boolean").tags("boolean").optional().$type).prop("$sort", (0, __atscript_typescript.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript.defineAnnotatedType)().refTo(SelectControlDto).optional().$type);
62
+ (0, __atscript_typescript.defineAnnotatedType)("object", QueryControlsDto).prop("$skip", (0, __atscript_typescript.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.min", 0).annotate("expect.int", true).optional().$type).prop("$limit", (0, __atscript_typescript.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.min", 0).annotate("expect.int", true).optional().$type).prop("$count", (0, __atscript_typescript.defineAnnotatedType)().designType("boolean").tags("boolean").optional().$type).prop("$sort", (0, __atscript_typescript.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript.defineAnnotatedType)().refTo(SelectControlDto).optional().$type).prop("$search", (0, __atscript_typescript.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$index", (0, __atscript_typescript.defineAnnotatedType)().designType("string").tags("string").optional().$type);
63
63
  (0, __atscript_typescript.defineAnnotatedType)("object", PagesControlsDto).prop("$page", (0, __atscript_typescript.defineAnnotatedType)().designType("string").tags("string").annotate("expect.pattern", {
64
64
  pattern: "^\\d+$",
65
65
  flags: "u",
@@ -68,7 +68,7 @@ _define_property$1(SelectControlDto, "metadata", new Map());
68
68
  pattern: "^\\d+$",
69
69
  flags: "u",
70
70
  message: "Expected positive number"
71
- }, true).optional().$type).prop("$sort", (0, __atscript_typescript.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript.defineAnnotatedType)().refTo(SelectControlDto).optional().$type);
71
+ }, true).optional().$type).prop("$sort", (0, __atscript_typescript.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript.defineAnnotatedType)().refTo(SelectControlDto).optional().$type).prop("$search", (0, __atscript_typescript.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$index", (0, __atscript_typescript.defineAnnotatedType)().designType("string").tags("string").optional().$type);
72
72
  (0, __atscript_typescript.defineAnnotatedType)("object", GetOneControlsDto).prop("$select", (0, __atscript_typescript.defineAnnotatedType)().refTo(SelectControlDto).optional().$type);
73
73
  (0, __atscript_typescript.defineAnnotatedType)("object", SortControlDto).propPattern(/./, (0, __atscript_typescript.defineAnnotatedType)("union").item((0, __atscript_typescript.defineAnnotatedType)().designType("number").value(1).$type).item((0, __atscript_typescript.defineAnnotatedType)().designType("number").value(-1).$type).$type);
74
74
  (0, __atscript_typescript.defineAnnotatedType)("object", SelectControlDto).propPattern(/./, (0, __atscript_typescript.defineAnnotatedType)("union").item((0, __atscript_typescript.defineAnnotatedType)().designType("number").value(1).$type).item((0, __atscript_typescript.defineAnnotatedType)().designType("number").value(0).$type).$type);
@@ -206,6 +206,14 @@ var AsMongoController = class {
206
206
  return projection;
207
207
  }
208
208
  /**
209
+ * Allows subclasses to transform or modify the filter before querying.
210
+ *
211
+ * @param filter - The original filter object.
212
+ * @returns The transformed filter object (may return `Promise`).
213
+ */ transformFilter(filter) {
214
+ return filter;
215
+ }
216
+ /**
209
217
  * Builds MongoDB `FindOptions` object out of URLQL controls.
210
218
  *
211
219
  * @param controls - Parsed `controls` object.
@@ -218,6 +226,25 @@ var AsMongoController = class {
218
226
  };
219
227
  }
220
228
  /**
229
+ * Prepares a MongoDB $search stage for text-based searching.
230
+ *
231
+ * @param searchTerm - The text string to search for. If not provided, no search is performed.
232
+ * @param indexName - The name of the Atlas Search index to use. If not provided, the default index is used.
233
+ * @returns A $search pipeline stage or an error string if the index is not found. Returns undefined if no searchTerm is provided.
234
+ */ prepareSearch(searchTerm, indexName) {
235
+ if (!searchTerm) return undefined;
236
+ const index = this.asCollection.getSearchIndex(indexName);
237
+ if (!index) return indexName ? `Search index "${indexName}" does not exist` : "No search index found";
238
+ if (!index.key) return `Invalid index definition: missing index key`;
239
+ return { $search: {
240
+ index: index.key,
241
+ text: {
242
+ query: searchTerm,
243
+ path: { wildcard: "*" }
244
+ }
245
+ } };
246
+ }
247
+ /**
221
248
  * **GET /query** – returns an array of documents or a count depending on
222
249
  * presence of `$count` control.
223
250
  *
@@ -228,7 +255,19 @@ var AsMongoController = class {
228
255
  const parsed = (0, urlql.parseUrlql)(query);
229
256
  const error = await this.validateUrlql(parsed, "query");
230
257
  if (error) return error;
231
- return parsed.controls.$count ? this.asCollection.collection.countDocuments(parsed.filter) : this.asCollection.collection.find(parsed.filter, this.prepareQueryOptions(parsed.controls)).toArray();
258
+ if (parsed.controls.$count) return this.asCollection.collection.countDocuments(parsed.filter);
259
+ const search = this.prepareSearch(parsed.controls.$search, parsed.controls.$index);
260
+ if (typeof search === "string") return new __moostjs_event_http.HttpError(400, search);
261
+ const { projection, sort, limit, skip } = this.prepareQueryOptions(parsed.controls);
262
+ const pipeline = [];
263
+ if (search) pipeline.push(search);
264
+ pipeline.push({ $match: this.transformFilter(parsed.filter) });
265
+ if (sort) pipeline.push({ $sort: sort });
266
+ if (skip) pipeline.push({ $skip: skip });
267
+ if (limit) pipeline.push({ $limit: limit });
268
+ else pipeline.push({ $limit: 1e3 });
269
+ if (projection) pipeline.push({ $project: projection });
270
+ return this.asCollection.collection.aggregate(pipeline).toArray();
232
271
  }
233
272
  /**
234
273
  * **GET /pages** – returns paginated documents plus basic pagination meta.
@@ -240,19 +279,24 @@ var AsMongoController = class {
240
279
  const parsed = (0, urlql.parseUrlql)(query);
241
280
  const error = await this.validateUrlql(parsed, "pages");
242
281
  if (error) return error;
282
+ const search = this.prepareSearch(parsed.controls.$search, parsed.controls.$index);
283
+ if (typeof search === "string") return new __moostjs_event_http.HttpError(400, search);
243
284
  const controls = parsed.controls;
244
285
  const page = Math.max(Number(controls.$page || 1), 1);
245
286
  const size = Math.max(Number(controls.$size || 10), 1);
246
287
  const skip = (page - 1) * size;
247
- const result = await this.asCollection.collection.aggregate([{ $match: parsed.filter }, { $facet: {
288
+ const pipeline = [];
289
+ if (search) pipeline.push(search);
290
+ pipeline.push({ $match: this.transformFilter(parsed.filter) }, { $facet: {
248
291
  documents: [
249
292
  controls.$sort ? { $sort: controls.$sort } : undefined,
250
293
  { $skip: skip },
251
294
  { $limit: size },
252
- controls.$select ? { $project: controls.$select } : undefined
295
+ controls.$select ? { $project: this.transformProjection(controls.$select) } : undefined
253
296
  ].filter(Boolean),
254
297
  meta: [{ $count: "count" }]
255
- } }]).toArray();
298
+ } });
299
+ const result = await this.asCollection.collection.aggregate(pipeline).toArray();
256
300
  const totalDocuments = result[0].meta[0].count;
257
301
  return {
258
302
  documents: result[0].documents,
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { TConsoleBase, Moost } from 'moost';
3
3
  import { UrlqlQuery } from 'urlql';
4
4
  import { AsMongo, AsCollection } from '@atscript/mongo';
5
5
  import { TAtscriptAnnotatedTypeConstructor, ValidatorError } from '@atscript/typescript';
6
- import { WithId, InsertOneResult, InsertManyResult, UpdateResult, DeleteResult, DeleteOptions, ObjectId, OptionalUnlessRequiredId, InsertOneOptions, BulkWriteOptions, WithoutId, ReplaceOptions, UpdateFilter, UpdateOptions } from 'mongodb';
6
+ import { Document, WithId, InsertOneResult, InsertManyResult, UpdateResult, DeleteResult, DeleteOptions, ObjectId, OptionalUnlessRequiredId, InsertOneOptions, BulkWriteOptions, WithoutId, ReplaceOptions, UpdateFilter, UpdateOptions } from 'mongodb';
7
7
 
8
8
  /**
9
9
  * Generic **Moost** controller that exposes a full REST‑style CRUD surface over a
@@ -106,6 +106,13 @@ declare class AsMongoController<T extends TAtscriptAnnotatedTypeConstructor> {
106
106
  * @returns Adjusted projection (may return `Promise`).
107
107
  */
108
108
  protected transformProjection(projection?: Record<string, 0 | 1>): Record<string, 1> | Record<string, 0> | undefined | Promise<Record<string, 1> | Record<string, 0> | undefined>;
109
+ /**
110
+ * Allows subclasses to transform or modify the filter before querying.
111
+ *
112
+ * @param filter - The original filter object.
113
+ * @returns The transformed filter object (may return `Promise`).
114
+ */
115
+ protected transformFilter(filter: Document): Document;
109
116
  /**
110
117
  * Builds MongoDB `FindOptions` object out of URLQL controls.
111
118
  *
@@ -117,6 +124,14 @@ declare class AsMongoController<T extends TAtscriptAnnotatedTypeConstructor> {
117
124
  limit: number | undefined;
118
125
  skip: number | undefined;
119
126
  };
127
+ /**
128
+ * Prepares a MongoDB $search stage for text-based searching.
129
+ *
130
+ * @param searchTerm - The text string to search for. If not provided, no search is performed.
131
+ * @param indexName - The name of the Atlas Search index to use. If not provided, the default index is used.
132
+ * @returns A $search pipeline stage or an error string if the index is not found. Returns undefined if no searchTerm is provided.
133
+ */
134
+ protected prepareSearch(searchTerm?: string, indexName?: string): string | undefined | Document;
120
135
  /**
121
136
  * **GET /query** – returns an array of documents or a count depending on
122
137
  * presence of `$count` control.
@@ -162,19 +177,19 @@ declare class AsMongoController<T extends TAtscriptAnnotatedTypeConstructor> {
162
177
  *
163
178
  * @param payload - Raw request body to be inserted.
164
179
  */
165
- insert(payload: any): Promise<HttpError | InsertOneResult | InsertManyResult>;
180
+ insert(payload: Parameters<AsCollection<T>['prepareInsert']>[0]): Promise<HttpError | InsertOneResult | InsertManyResult>;
166
181
  /**
167
182
  * **PUT /** – fully replaces a document matched by `_id`.
168
183
  *
169
184
  * @param payload - Object containing `_id` plus full replacement document.
170
185
  */
171
- replace(payload: any): Promise<HttpError | UpdateResult<InstanceType<T>>>;
186
+ replace(payload: Parameters<AsCollection<T>['prepareReplace']>[0]): Promise<HttpError | UpdateResult<InstanceType<T>>>;
172
187
  /**
173
188
  * **PATCH /** – updates one document using MongoDB update operators.
174
189
  *
175
190
  * @param payload - Update payload produced by `asCollection.prepareUpdate`.
176
191
  */
177
- update(payload: any): Promise<HttpError | UpdateResult>;
192
+ update(payload: Parameters<AsCollection<T>['prepareUpdate']>[0]): Promise<HttpError | UpdateResult>;
178
193
  /**
179
194
  * **DELETE /:id** – removes a single document by `_id`.
180
195
  *
package/dist/index.mjs CHANGED
@@ -35,7 +35,7 @@ let SelectControlDto = class SelectControlDto$1 {};
35
35
  _define_property$1(SelectControlDto, "__is_atscript_annotated_type", true);
36
36
  _define_property$1(SelectControlDto, "type", {});
37
37
  _define_property$1(SelectControlDto, "metadata", new Map());
38
- defineAnnotatedType("object", QueryControlsDto).prop("$skip", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.min", 0).annotate("expect.int", true).optional().$type).prop("$limit", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.min", 0).annotate("expect.int", true).optional().$type).prop("$count", defineAnnotatedType().designType("boolean").tags("boolean").optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType().refTo(SelectControlDto).optional().$type);
38
+ defineAnnotatedType("object", QueryControlsDto).prop("$skip", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.min", 0).annotate("expect.int", true).optional().$type).prop("$limit", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.min", 0).annotate("expect.int", true).optional().$type).prop("$count", defineAnnotatedType().designType("boolean").tags("boolean").optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType().refTo(SelectControlDto).optional().$type).prop("$search", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$index", defineAnnotatedType().designType("string").tags("string").optional().$type);
39
39
  defineAnnotatedType("object", PagesControlsDto).prop("$page", defineAnnotatedType().designType("string").tags("string").annotate("expect.pattern", {
40
40
  pattern: "^\\d+$",
41
41
  flags: "u",
@@ -44,7 +44,7 @@ defineAnnotatedType("object", PagesControlsDto).prop("$page", defineAnnotatedTyp
44
44
  pattern: "^\\d+$",
45
45
  flags: "u",
46
46
  message: "Expected positive number"
47
- }, true).optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType().refTo(SelectControlDto).optional().$type);
47
+ }, true).optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType().refTo(SelectControlDto).optional().$type).prop("$search", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$index", defineAnnotatedType().designType("string").tags("string").optional().$type);
48
48
  defineAnnotatedType("object", GetOneControlsDto).prop("$select", defineAnnotatedType().refTo(SelectControlDto).optional().$type);
49
49
  defineAnnotatedType("object", SortControlDto).propPattern(/./, defineAnnotatedType("union").item(defineAnnotatedType().designType("number").value(1).$type).item(defineAnnotatedType().designType("number").value(-1).$type).$type);
50
50
  defineAnnotatedType("object", SelectControlDto).propPattern(/./, defineAnnotatedType("union").item(defineAnnotatedType().designType("number").value(1).$type).item(defineAnnotatedType().designType("number").value(0).$type).$type);
@@ -182,6 +182,14 @@ var AsMongoController = class {
182
182
  return projection;
183
183
  }
184
184
  /**
185
+ * Allows subclasses to transform or modify the filter before querying.
186
+ *
187
+ * @param filter - The original filter object.
188
+ * @returns The transformed filter object (may return `Promise`).
189
+ */ transformFilter(filter) {
190
+ return filter;
191
+ }
192
+ /**
185
193
  * Builds MongoDB `FindOptions` object out of URLQL controls.
186
194
  *
187
195
  * @param controls - Parsed `controls` object.
@@ -194,6 +202,25 @@ var AsMongoController = class {
194
202
  };
195
203
  }
196
204
  /**
205
+ * Prepares a MongoDB $search stage for text-based searching.
206
+ *
207
+ * @param searchTerm - The text string to search for. If not provided, no search is performed.
208
+ * @param indexName - The name of the Atlas Search index to use. If not provided, the default index is used.
209
+ * @returns A $search pipeline stage or an error string if the index is not found. Returns undefined if no searchTerm is provided.
210
+ */ prepareSearch(searchTerm, indexName) {
211
+ if (!searchTerm) return undefined;
212
+ const index = this.asCollection.getSearchIndex(indexName);
213
+ if (!index) return indexName ? `Search index "${indexName}" does not exist` : "No search index found";
214
+ if (!index.key) return `Invalid index definition: missing index key`;
215
+ return { $search: {
216
+ index: index.key,
217
+ text: {
218
+ query: searchTerm,
219
+ path: { wildcard: "*" }
220
+ }
221
+ } };
222
+ }
223
+ /**
197
224
  * **GET /query** – returns an array of documents or a count depending on
198
225
  * presence of `$count` control.
199
226
  *
@@ -204,7 +231,19 @@ var AsMongoController = class {
204
231
  const parsed = parseUrlql(query);
205
232
  const error = await this.validateUrlql(parsed, "query");
206
233
  if (error) return error;
207
- return parsed.controls.$count ? this.asCollection.collection.countDocuments(parsed.filter) : this.asCollection.collection.find(parsed.filter, this.prepareQueryOptions(parsed.controls)).toArray();
234
+ if (parsed.controls.$count) return this.asCollection.collection.countDocuments(parsed.filter);
235
+ const search = this.prepareSearch(parsed.controls.$search, parsed.controls.$index);
236
+ if (typeof search === "string") return new HttpError(400, search);
237
+ const { projection, sort, limit, skip } = this.prepareQueryOptions(parsed.controls);
238
+ const pipeline = [];
239
+ if (search) pipeline.push(search);
240
+ pipeline.push({ $match: this.transformFilter(parsed.filter) });
241
+ if (sort) pipeline.push({ $sort: sort });
242
+ if (skip) pipeline.push({ $skip: skip });
243
+ if (limit) pipeline.push({ $limit: limit });
244
+ else pipeline.push({ $limit: 1e3 });
245
+ if (projection) pipeline.push({ $project: projection });
246
+ return this.asCollection.collection.aggregate(pipeline).toArray();
208
247
  }
209
248
  /**
210
249
  * **GET /pages** – returns paginated documents plus basic pagination meta.
@@ -216,19 +255,24 @@ var AsMongoController = class {
216
255
  const parsed = parseUrlql(query);
217
256
  const error = await this.validateUrlql(parsed, "pages");
218
257
  if (error) return error;
258
+ const search = this.prepareSearch(parsed.controls.$search, parsed.controls.$index);
259
+ if (typeof search === "string") return new HttpError(400, search);
219
260
  const controls = parsed.controls;
220
261
  const page = Math.max(Number(controls.$page || 1), 1);
221
262
  const size = Math.max(Number(controls.$size || 10), 1);
222
263
  const skip = (page - 1) * size;
223
- const result = await this.asCollection.collection.aggregate([{ $match: parsed.filter }, { $facet: {
264
+ const pipeline = [];
265
+ if (search) pipeline.push(search);
266
+ pipeline.push({ $match: this.transformFilter(parsed.filter) }, { $facet: {
224
267
  documents: [
225
268
  controls.$sort ? { $sort: controls.$sort } : undefined,
226
269
  { $skip: skip },
227
270
  { $limit: size },
228
- controls.$select ? { $project: controls.$select } : undefined
271
+ controls.$select ? { $project: this.transformProjection(controls.$select) } : undefined
229
272
  ].filter(Boolean),
230
273
  meta: [{ $count: "count" }]
231
- } }]).toArray();
274
+ } });
275
+ const result = await this.asCollection.collection.aggregate(pipeline).toArray();
232
276
  const totalDocuments = result[0].meta[0].count;
233
277
  return {
234
278
  documents: result[0].documents,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/moost-mongo",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Atscript Mongo for Moost.",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -44,8 +44,8 @@
44
44
  "@moostjs/event-http": "^0.5.31",
45
45
  "mongodb": "^6.17.0",
46
46
  "moost": "^0.5.31",
47
- "@atscript/mongo": "^0.0.19",
48
- "@atscript/typescript": "^0.0.19"
47
+ "@atscript/mongo": "^0.0.21",
48
+ "@atscript/typescript": "^0.0.21"
49
49
  },
50
50
  "scripts": {
51
51
  "pub": "pnpm publish --access public",