@atscript/mongo 0.1.31 → 0.1.32

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.d.ts CHANGED
@@ -1,10 +1,7 @@
1
- import { TAtscriptPlugin } from '@atscript/core';
1
+ import { TAtscriptAnnotatedType, TValidatorOptions, Validator, TAtscriptTypeArray, TValidatorPlugin, TMetadataMap } from '@atscript/typescript/utils';
2
+ import { AtscriptDbTable, BaseDbAdapter, FilterExpr, TDbUpdateResult, TSearchIndexInfo, DbQuery, TDbInsertResult, TDbInsertManyResult, TDbDeleteResult } from '@atscript/utils-db';
2
3
  import * as mongodb from 'mongodb';
3
- import { MongoClient, Collection, ObjectId, InsertOneOptions, ReplaceOptions, UpdateOptions, Filter } from 'mongodb';
4
- import * as _atscript_typescript_annotated_type from '@atscript/typescript/annotated-type';
5
- import { TAtscriptAnnotatedType, TAtscriptDataType, Validator, TValidatorOptions, TAtscriptTypeObject, TMetadataMap } from '@atscript/typescript/utils';
6
-
7
- declare const MongoPlugin: () => TAtscriptPlugin;
4
+ import { MongoClient, Filter, UpdateFilter, Document, UpdateOptions, Db, Collection, AggregationCursor, ObjectId } from 'mongodb';
8
5
 
9
6
  interface TGenericLogger {
10
7
  error(...messages: any[]): void;
@@ -22,8 +19,157 @@ declare class AsMongo {
22
19
  protected collectionsList?: Promise<Set<string>>;
23
20
  protected getCollectionsList(): Promise<Set<string>>;
24
21
  collectionExists(name: string): Promise<boolean>;
25
- getCollection<T extends TAtscriptAnnotatedType>(type: T, logger?: TGenericLogger): AsCollection<T>;
26
- private _collections;
22
+ getAdapter<T extends TAtscriptAnnotatedType>(type: T): MongoAdapter;
23
+ getTable<T extends TAtscriptAnnotatedType>(type: T, logger?: TGenericLogger): AtscriptDbTable<T, any, any, any>;
24
+ private _ensureCreated;
25
+ private _adapters;
26
+ private _tables;
27
+ }
28
+
29
+ /**
30
+ * Context interface for CollectionPatcher.
31
+ * Decouples the patcher from AsCollection, allowing MongoAdapter to provide this.
32
+ */
33
+ interface TCollectionPatcherContext {
34
+ flatMap: Map<string, TAtscriptAnnotatedType>;
35
+ prepareId(id: any): any;
36
+ createValidator(opts?: Partial<TValidatorOptions>): Validator<any>;
37
+ }
38
+ /**
39
+ * CollectionPatcher is a small helper that converts a *patch payload* produced
40
+ * by Atscript into a shape that the official MongoDB driver understands – a
41
+ * triple of `(filter, update, options)` to be fed to `collection.updateOne()`.
42
+ *
43
+ * Supported high‑level operations for *top‑level arrays* (see the attached
44
+ * spreadsheet in the chat):
45
+ *
46
+ * | Payload field | MongoDB operator | Purpose |
47
+ * |-------------- |-------------------------|----------------------------------------|
48
+ * | `$replace` | full `$set` | Replace the whole array. |
49
+ * | `$insert` | `$push` | Append new items (duplicates allowed). |
50
+ * | `$upsert` | custom | Insert or update by *key* (see TODO). |
51
+ * | `$update` | `$set` + `arrayFilters` | Update array elements matched by *key* |
52
+ * | `$remove` | `$pullAll` / `$pull` | Remove by value or by *key*. |
53
+ *
54
+ * The class walks through the incoming payload, detects which of the above
55
+ * operations applies to each top‑level array and builds the corresponding
56
+ * MongoDB update document. Primitive fields are flattened into a regular
57
+ * `$set` map.
58
+ */
59
+ declare class CollectionPatcher {
60
+ private collection;
61
+ private payload;
62
+ constructor(collection: TCollectionPatcherContext, payload: any);
63
+ /**
64
+ * Extract a set of *key properties* (annotated with `@expect.array.key`) from an
65
+ * array‐of‐objects type definition. These keys uniquely identify an element
66
+ * inside the array and are later used for `$update`, `$remove` and `$upsert`.
67
+ *
68
+ * @param def Atscript array type
69
+ * @returns Set of property names marked as keys; empty set if none
70
+ */
71
+ static getKeyProps(def: TAtscriptAnnotatedType<TAtscriptTypeArray>): Set<string>;
72
+ /**
73
+ * Build a runtime *Validator* that understands the extended patch payload.
74
+ *
75
+ * * Adds per‑array *patch* wrappers (the `$replace`, `$insert`, … fields).
76
+ * * Honors `db.patch.strategy === "merge"` metadata.
77
+ *
78
+ * @param collection Target collection wrapper
79
+ * @returns Atscript Validator
80
+ */
81
+ static prepareValidator(context: TCollectionPatcherContext): Validator<any, unknown>;
82
+ /**
83
+ * Internal accumulator: filter passed to `updateOne()`.
84
+ * Filled only with the `_id` field right now.
85
+ */
86
+ private filterObj;
87
+ /** MongoDB *update* document being built. */
88
+ private updatePipeline;
89
+ /** Additional *options* (mainly `arrayFilters`). */
90
+ private optionsObj;
91
+ /**
92
+ * Entry point – walk the payload, build `filter`, `update` and `options`.
93
+ *
94
+ * @returns Helper object exposing both individual parts and
95
+ * a `.toArgs()` convenience callback.
96
+ */
97
+ preparePatch(): {
98
+ toArgs: () => [Filter<any>, UpdateFilter<any> | Document[], UpdateOptions];
99
+ filter: Filter<any>;
100
+ updateFilter: Document[];
101
+ updateOptions: UpdateOptions;
102
+ };
103
+ /**
104
+ * Helper – lazily create `$set` section and assign *key* → *value*.
105
+ *
106
+ * @param key Fully‑qualified dotted path
107
+ * @param val Value to be written
108
+ * @private
109
+ */
110
+ private _set;
111
+ /**
112
+ * Recursively walk through the patch *payload* and convert it into `$set`/…
113
+ * statements. Top‑level arrays are delegated to {@link parseArrayPatch}.
114
+ *
115
+ * @param payload Current payload chunk
116
+ * @param prefix Dotted path accumulated so far
117
+ * @private
118
+ */
119
+ private flattenPayload;
120
+ /**
121
+ * Dispatch a *single* array patch. Exactly one of `$replace`, `$insert`,
122
+ * `$upsert`, `$update`, `$remove` must be present – otherwise we throw.
123
+ *
124
+ * @param key Dotted path to the array field
125
+ * @param value Payload slice for that field
126
+ * @private
127
+ */
128
+ private parseArrayPatch;
129
+ /**
130
+ * Build an *aggregation‐expression* that checks equality by **all** keys in
131
+ * `keys`. Example output for keys `["id", "lang"]` and bases `a`, `b`:
132
+ * ```json
133
+ * { "$and": [ { "$eq": ["$$a.id", "$$b.id"] }, { "$eq": ["$$a.lang", "$$b.lang"] } ] }
134
+ * ```
135
+ *
136
+ * @param keys Ordered list of key property names
137
+ * @param left Base token for *left* expression (e.g. `"$$el"`)
138
+ * @param right Base token for *right* expression (e.g. `"$$this"`)
139
+ */
140
+ private _keysEqual;
141
+ /**
142
+ * `$replace` – overwrite the entire array with `input`.
143
+ *
144
+ * @param key Dotted path to the array
145
+ * @param input New array value (may be `undefined`)
146
+ * @private
147
+ */
148
+ private _replace;
149
+ /**
150
+ * `$insert`
151
+ * - plain append → $concatArrays
152
+ * - unique / keyed → delegate to _upsert (insert-or-update)
153
+ */
154
+ private _insert;
155
+ /**
156
+ * `$upsert`
157
+ * - keyed → remove existing matching by key(s) then append candidate
158
+ * - unique → $setUnion (deep equality)
159
+ */
160
+ private _upsert;
161
+ /**
162
+ * `$update`
163
+ * - keyed → map array and merge / replace matching element(s)
164
+ * - non-keyed → behave like `$addToSet` (insert only when not present)
165
+ */
166
+ private _update;
167
+ /**
168
+ * `$remove`
169
+ * - keyed → filter out any element whose key set matches a payload item
170
+ * - non-keyed → deep equality remove (`$setDifference`)
171
+ */
172
+ private _remove;
27
173
  }
28
174
 
29
175
  interface TPlainIndex {
@@ -39,99 +185,97 @@ interface TSearchIndex {
39
185
  type: 'dynamic_text' | 'search_text' | 'vector';
40
186
  definition: TMongoSearchIndexDefinition;
41
187
  }
42
- type TIndex = TPlainIndex | TSearchIndex;
43
- type TValidatorPurpose = 'insert' | 'update' | 'patch';
44
- declare class AsCollection<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> {
45
- protected readonly asMongo: AsMongo;
46
- protected readonly _type: T;
47
- protected readonly logger: TGenericLogger;
48
- readonly name: string;
49
- readonly collection: Collection<any>;
50
- protected readonly validators: Map<TValidatorPurpose, Validator<T, DataType>>;
51
- createValidator(opts?: Partial<TValidatorOptions>): Validator<T, DataType>;
52
- protected _indexes: Map<string, TIndex>;
188
+ type TMongoIndex = TPlainIndex | TSearchIndex;
189
+ declare class MongoAdapter extends BaseDbAdapter {
190
+ protected readonly db: Db;
191
+ protected readonly asMongo?: AsMongo | undefined;
192
+ private _collection?;
193
+ /** MongoDB-specific indexes (search, vector) — separate from table.indexes. */
194
+ protected _mongoIndexes: Map<string, TMongoIndex>;
195
+ /** Vector search filter associations built during flattening. */
53
196
  protected _vectorFilters: Map<string, string>;
54
- protected _flatMap?: Map<string, TAtscriptAnnotatedType>;
55
- constructor(asMongo: AsMongo, _type: T, logger?: TGenericLogger);
56
- exists(): Promise<boolean>;
57
- ensureExists(): Promise<void>;
197
+ /** Cached search index lookup. */
198
+ protected _searchIndexesMap?: Map<string, TMongoIndex>;
199
+ /** Physical field names with @db.default.fn "increment". */
200
+ protected _incrementFields: Set<string>;
201
+ constructor(db: Db, asMongo?: AsMongo | undefined);
202
+ get collection(): Collection<any>;
203
+ aggregate(pipeline: Document[]): AggregationCursor;
204
+ get idType(): 'string' | 'number' | 'objectId';
205
+ prepareId(id: unknown, fieldType: TAtscriptAnnotatedType): unknown;
58
206
  /**
59
- * Returns the a type definition of the "_id" prop.
207
+ * Convenience method that uses `idType` to transform an ID value.
208
+ * For use in controllers that don't have access to the field type.
60
209
  */
61
- get idType(): 'string' | 'number' | 'objectId';
210
+ prepareIdFromIdType<D = string | number | ObjectId>(id: string | number | ObjectId): D;
211
+ supportsNestedObjects(): boolean;
212
+ supportsNativePatch(): boolean;
213
+ getValidatorPlugins(): TValidatorPlugin[];
214
+ getTopLevelArrayTag(): string;
215
+ getAdapterTableName(type: TAtscriptAnnotatedType): string | undefined;
216
+ buildInsertValidator(table: AtscriptDbTable): any;
217
+ buildPatchValidator(table: AtscriptDbTable): any;
218
+ /** Returns the context object used by CollectionPatcher. */
219
+ getPatcherContext(): TCollectionPatcherContext;
220
+ nativePatch(filter: FilterExpr, patch: unknown): Promise<TDbUpdateResult>;
221
+ onBeforeFlatten(type: TAtscriptAnnotatedType): void;
222
+ onFieldScanned(field: string, type: TAtscriptAnnotatedType, metadata: TMetadataMap<AtscriptMetadata>): void;
223
+ onAfterFlatten(): void;
224
+ /** Returns MongoDB-specific search index map (internal). */
225
+ getMongoSearchIndexes(): Map<string, TMongoIndex>;
226
+ /** Returns a specific MongoDB search index by name. */
227
+ getMongoSearchIndex(name?: string): TMongoIndex | undefined;
228
+ /** Returns available search indexes as generic metadata for UI. */
229
+ getSearchIndexes(): TSearchIndexInfo[];
62
230
  /**
63
- * Transforms an "_id" value to the expected type (`ObjectId`, `number`, or `string`).
64
- * Assumes input has already been validated.
65
- *
66
- * @param {string | number | ObjectId} id - The validated ID.
67
- * @returns {string | number | ObjectId} - The transformed ID.
68
- * @throws {Error} If the `_id` type is unknown.
231
+ * Builds a MongoDB `$search` pipeline stage.
232
+ * Override `buildVectorSearchStage` in subclasses to provide embeddings.
69
233
  */
70
- prepareId<D = string | number | ObjectId>(id: string | number | ObjectId): D;
234
+ protected buildSearchStage(text: string, indexName?: string): Document | undefined;
71
235
  /**
72
- * Retrieves a validator for a given purpose. If the validator is not already cached,
73
- * it creates and stores a new one based on the purpose.
74
- *
75
- * @param {TValidatorPurpose} purpose - The validation purpose (`input`, `update`, `patch`).
76
- * @returns {Validator} The corresponding validator instance.
77
- * @throws {Error} If an unknown purpose is provided.
78
- */
79
- getValidator(purpose: TValidatorPurpose): Validator<T, DataType> | undefined;
80
- get type(): TAtscriptAnnotatedType<TAtscriptTypeObject>;
81
- get indexes(): Map<string, TIndex>;
82
- protected _addIndexField(type: TPlainIndex['type'], name: string, field: string, weight?: number): void;
236
+ * Builds a vector search stage. Override in subclasses to generate embeddings.
237
+ * Returns `undefined` by default (vector search requires custom implementation).
238
+ */
239
+ protected buildVectorSearchStage(text: string, index: TMongoIndex): Document | undefined;
240
+ search(text: string, query: DbQuery, indexName?: string): Promise<Array<Record<string, unknown>>>;
241
+ searchWithCount(text: string, query: DbQuery, indexName?: string): Promise<{
242
+ data: Array<Record<string, unknown>>;
243
+ count: number;
244
+ }>;
245
+ findManyWithCount(query: DbQuery): Promise<{
246
+ data: Array<Record<string, unknown>>;
247
+ count: number;
248
+ }>;
249
+ collectionExists(): Promise<boolean>;
250
+ ensureCollectionExists(): Promise<void>;
251
+ insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
252
+ insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
253
+ findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
254
+ findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
255
+ count(query: DbQuery): Promise<number>;
256
+ updateOne(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
257
+ replaceOne(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
258
+ deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
259
+ updateMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
260
+ replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
261
+ deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
262
+ ensureTable(): Promise<void>;
263
+ syncIndexes(): Promise<void>;
264
+ /** Returns physical field names of increment fields that are undefined in the data. */
265
+ private _fieldsNeedingIncrement;
266
+ /** Reads current max value for each field via $group aggregation. */
267
+ private _getMaxValues;
268
+ private _buildFindOptions;
269
+ protected _addMongoIndexField(type: TPlainIndex['type'], name: string, field: string, weight?: number): void;
83
270
  protected _setSearchIndex(type: TSearchIndex['type'], name: string | undefined, definition: TMongoSearchIndexDefinition): void;
84
271
  protected _addFieldToSearchIndex(type: TSearchIndex['type'], _name: string | undefined, fieldName: string, analyzer?: string): void;
85
- protected _prepareIndexesForCollection(): void;
86
- protected _uniqueProps: Set<string>;
87
- get uniqueProps(): Set<string>;
88
- protected _finalizeIndexesForCollection(): void;
89
- protected _prepareIndexesForField(fieldName: string, metadata: TMetadataMap<AtscriptMetadata>): void;
90
- protected _flatten(): void;
91
- protected _searchIndexesMap?: Map<string, TIndex>;
92
- getSearchIndexes(): Map<string, TIndex>;
93
- getSearchIndex(name?: string): TIndex | undefined;
94
- get flatMap(): Map<string, TAtscriptAnnotatedType<_atscript_typescript_annotated_type.TAtscriptTypeDef<unknown>, unknown>>;
95
- syncIndexes(): Promise<void>;
96
- insert(payload: (Omit<DataType, '_id'> & {
97
- _id?: string | number | ObjectId;
98
- }) | (Omit<DataType, '_id'> & {
99
- _id?: string | number | ObjectId;
100
- })[], options?: InsertOneOptions): Promise<mongodb.InsertManyResult<any>> | Promise<mongodb.InsertOneResult<any>>;
101
- replace(payload: Omit<DataType, '_id'> & {
102
- _id: string | number | ObjectId;
103
- }, options?: ReplaceOptions): Promise<mongodb.UpdateResult<any>>;
104
- update(payload: AsMongoPatch<Omit<DataType, '_id'>> & {
105
- _id: string | number | ObjectId;
106
- }, options?: UpdateOptions): Promise<mongodb.UpdateResult<any>>;
107
- prepareInsert(payload: (Omit<DataType, '_id'> & {
108
- _id?: string | number | ObjectId;
109
- }) | (Omit<DataType, '_id'> & {
110
- _id?: string | number | ObjectId;
111
- })[]): DataType | DataType[];
112
- prepareReplace(payload: Omit<DataType, '_id'> & {
113
- _id: string | number | ObjectId;
114
- }): {
115
- toArgs: () => [Filter<any>, any, ReplaceOptions];
116
- filter: Filter<any>;
117
- updateFilter: any;
118
- updateOptions: ReplaceOptions;
119
- };
120
- prepareUpdate(payload: AsMongoPatch<Omit<DataType, '_id'>> & {
121
- _id: string | number | ObjectId;
122
- }): {
123
- toArgs: () => [Filter<any>, mongodb.UpdateFilter<any> | mongodb.Document[], UpdateOptions];
124
- filter: Filter<any>;
125
- updateFilter: mongodb.Document[];
126
- updateOptions: UpdateOptions;
127
- };
128
272
  }
129
273
  type TVectorSimilarity = 'cosine' | 'euclidean' | 'dotProduct';
130
274
  interface TMongoSearchIndexDefinition {
131
275
  mappings?: {
132
276
  dynamic?: boolean;
133
277
  fields?: Record<string, {
134
- type: 'string' | 'number' | 'boolean' | 'date' | 'object' | 'array';
278
+ type: string;
135
279
  analyzer?: string;
136
280
  }>;
137
281
  };
@@ -148,26 +292,17 @@ interface TMongoSearchIndexDefinition {
148
292
  };
149
293
  };
150
294
  }
151
- interface TArrayPatch<A extends readonly unknown[]> {
152
- $replace?: A;
153
- $insert?: A;
154
- $upsert?: A;
155
- $update?: Array<Partial<TArrayElement<A>>>;
156
- $remove?: Array<Partial<TArrayElement<A>>>;
157
- }
158
- type TArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends ReadonlyArray<infer ElementType> ? ElementType : never;
295
+
159
296
  /**
160
- * AsMongoPatch<T>
161
- * ─────────────────
162
- * - For every key K in T:
163
- * • if T[K] is `X[]`, rewrite it to `TArrayPatch<X[]>`
164
- * • otherwise omit the key (feel free to keep it if you want)
297
+ * Translates a generic {@link FilterExpr} into a MongoDB-compatible
298
+ * {@link Filter} document.
165
299
  *
166
- * The result is an *optional* property bag that matches a patch payload
167
- * for array fields only.
300
+ * MongoDB's query language is nearly identical to the `FilterExpr` structure,
301
+ * so this is largely a structural pass-through via the `walkFilter` visitor.
168
302
  */
169
- type AsMongoPatch<T> = {
170
- [K in keyof T]?: T[K] extends Array<infer _> ? TArrayPatch<T[K]> : Partial<T[K]>;
171
- };
303
+ declare function buildMongoFilter(filter: FilterExpr): Filter<any>;
304
+
305
+ declare const validateMongoIdPlugin: TValidatorPlugin;
172
306
 
173
- export { AsCollection, AsMongo, MongoPlugin };
307
+ export { AsMongo, CollectionPatcher, MongoAdapter, buildMongoFilter, validateMongoIdPlugin };
308
+ export type { TCollectionPatcherContext, TMongoIndex, TMongoSearchIndexDefinition, TPlainIndex, TSearchIndex };