@atscript/db-mongo 0.1.39 → 0.1.41
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 +11 -11
- package/dist/agg.cjs +26 -65
- package/dist/{agg.d.ts → agg.d.cts} +5 -12
- package/dist/agg.d.mts +18 -0
- package/dist/agg.mjs +19 -35
- package/dist/index.cjs +374 -313
- package/dist/index.d.cts +344 -0
- package/dist/index.d.mts +344 -0
- package/dist/index.mjs +364 -301
- package/dist/mongo-filter-B5ZINZPF.cjs +40 -0
- package/dist/{mongo-filter-CL69Yhcm.mjs → mongo-filter-CcQO-sEh.mjs} +9 -4
- package/dist/plugin.cjs +37 -47
- package/dist/plugin.d.cts +6 -0
- package/dist/plugin.d.mts +6 -0
- package/dist/plugin.mjs +22 -11
- package/package.json +25 -44
- package/scripts/setup-skills.js +53 -43
- package/skills/atscript-db-mongo/SKILL.md +11 -11
- package/skills/atscript-db-mongo/collections.md +29 -28
- package/skills/atscript-db-mongo/core.md +6 -5
- package/skills/atscript-db-mongo/patches.md +17 -16
- package/LICENSE +0 -21
- package/dist/agg-CUX5Jb_A.mjs +0 -75
- package/dist/agg-FBVtOv9k.cjs +0 -77
- package/dist/index.d.ts +0 -336
- package/dist/mongo-filter-C8w5by9H.cjs +0 -65
- package/dist/plugin.d.ts +0 -5
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbForeignKey, TDbInsertManyResult, TDbInsertResult, TDbRelation, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TSearchIndexInfo, TSyncColumnResult, TTableResolver, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
2
|
+
import { AggregationCursor, ClientSession, Collection, Db, Document, Filter, MongoClient, ObjectId, UpdateFilter, UpdateOptions } from "mongodb";
|
|
3
|
+
import { TAtscriptAnnotatedType, TMetadataMap, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/collection-patcher.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Context interface for CollectionPatcher.
|
|
8
|
+
* Decouples the patcher from AsCollection, allowing MongoAdapter to provide this.
|
|
9
|
+
*/
|
|
10
|
+
interface TCollectionPatcherContext {
|
|
11
|
+
flatMap: Map<string, TAtscriptAnnotatedType>;
|
|
12
|
+
prepareId(id: any): any;
|
|
13
|
+
createValidator(opts?: Partial<TValidatorOptions>): Validator<any>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* CollectionPatcher is a small helper that converts a *patch payload* produced
|
|
17
|
+
* by Atscript into a shape that the official MongoDB driver understands – a
|
|
18
|
+
* triple of `(filter, update, options)` to be fed to `collection.updateOne()`.
|
|
19
|
+
*
|
|
20
|
+
* Supported high‑level operations for *top‑level arrays* (see the attached
|
|
21
|
+
* spreadsheet in the chat):
|
|
22
|
+
*
|
|
23
|
+
* | Payload field | MongoDB operator | Purpose |
|
|
24
|
+
* |-------------- |-------------------------|----------------------------------------|
|
|
25
|
+
* | `$replace` | full `$set` | Replace the whole array. |
|
|
26
|
+
* | `$insert` | `$push` | Append new items (duplicates allowed). |
|
|
27
|
+
* | `$upsert` | custom | Insert or update by *key* (see TODO). |
|
|
28
|
+
* | `$update` | `$set` + `arrayFilters` | Update array elements matched by *key* |
|
|
29
|
+
* | `$remove` | `$pullAll` / `$pull` | Remove by value or by *key*. |
|
|
30
|
+
*
|
|
31
|
+
* The class walks through the incoming payload, detects which of the above
|
|
32
|
+
* operations applies to each top‑level array and builds the corresponding
|
|
33
|
+
* MongoDB update document. Primitive fields are flattened into a regular
|
|
34
|
+
* `$set` map.
|
|
35
|
+
*/
|
|
36
|
+
declare class CollectionPatcher {
|
|
37
|
+
private collection;
|
|
38
|
+
private payload;
|
|
39
|
+
private ops?;
|
|
40
|
+
constructor(collection: TCollectionPatcherContext, payload: any, ops?: TFieldOps | undefined);
|
|
41
|
+
static getKeyProps: typeof getKeyProps;
|
|
42
|
+
/**
|
|
43
|
+
* Internal accumulator: filter passed to `updateOne()`.
|
|
44
|
+
* Filled only with the `_id` field right now.
|
|
45
|
+
*/
|
|
46
|
+
private filterObj;
|
|
47
|
+
/** MongoDB *update* document being built. */
|
|
48
|
+
private updatePipeline;
|
|
49
|
+
/** Current `$set` stage being populated. */
|
|
50
|
+
private currentSetStage;
|
|
51
|
+
/** Additional *options* (mainly `arrayFilters`). */
|
|
52
|
+
private optionsObj;
|
|
53
|
+
/**
|
|
54
|
+
* Entry point – walk the payload, build `filter`, `update` and `options`.
|
|
55
|
+
*
|
|
56
|
+
* @returns Helper object exposing both individual parts and
|
|
57
|
+
* a `.toArgs()` convenience callback.
|
|
58
|
+
*/
|
|
59
|
+
preparePatch(): {
|
|
60
|
+
toArgs: () => [Filter<any>, UpdateFilter<any> | Document[], UpdateOptions];
|
|
61
|
+
filter: Filter<any>;
|
|
62
|
+
updateFilter: Document[];
|
|
63
|
+
updateOptions: UpdateOptions;
|
|
64
|
+
};
|
|
65
|
+
/** Builds a MongoDB aggregation expression for an $inc or $mul field op. */
|
|
66
|
+
private _fieldOpExpr;
|
|
67
|
+
/**
|
|
68
|
+
* Helper – lazily create `$set` section and assign *key* → *value*.
|
|
69
|
+
*
|
|
70
|
+
* @param key Fully‑qualified dotted path
|
|
71
|
+
* @param val Value to be written
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
private _set;
|
|
75
|
+
/**
|
|
76
|
+
* Recursively walk through the patch *payload* and convert it into `$set`/…
|
|
77
|
+
* statements. Top‑level arrays are delegated to {@link parseArrayPatch}.
|
|
78
|
+
*
|
|
79
|
+
* @param payload Current payload chunk
|
|
80
|
+
* @param prefix Dotted path accumulated so far
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
private flattenPayload;
|
|
84
|
+
/**
|
|
85
|
+
* Dispatch a *single* array patch. Exactly one of `$replace`, `$insert`,
|
|
86
|
+
* `$upsert`, `$update`, `$remove` must be present – otherwise we throw.
|
|
87
|
+
*
|
|
88
|
+
* @param key Dotted path to the array field
|
|
89
|
+
* @param value Payload slice for that field
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
private parseArrayPatch;
|
|
93
|
+
/**
|
|
94
|
+
* Build an *aggregation‐expression* that checks equality by **all** keys in
|
|
95
|
+
* `keys`. Example output for keys `["id", "lang"]` and bases `a`, `b`:
|
|
96
|
+
* ```json
|
|
97
|
+
* { "$and": [ { "$eq": ["$$a.id", "$$b.id"] }, { "$eq": ["$$a.lang", "$$b.lang"] } ] }
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @param keys Ordered list of key property names
|
|
101
|
+
* @param left Base token for *left* expression (e.g. `"$$el"`)
|
|
102
|
+
* @param right Base token for *right* expression (e.g. `"$$this"`)
|
|
103
|
+
*/
|
|
104
|
+
private _keysEqual;
|
|
105
|
+
/**
|
|
106
|
+
* `$replace` – overwrite the entire array with `input`.
|
|
107
|
+
*
|
|
108
|
+
* @param key Dotted path to the array
|
|
109
|
+
* @param input New array value (may be `undefined`)
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
private _replace;
|
|
113
|
+
/**
|
|
114
|
+
* `$insert`
|
|
115
|
+
* - plain append → $concatArrays
|
|
116
|
+
* - unique / keyed → delegate to _upsert (insert-or-update)
|
|
117
|
+
*/
|
|
118
|
+
private _insert;
|
|
119
|
+
/**
|
|
120
|
+
* `$upsert`
|
|
121
|
+
* - keyed → remove existing matching by key(s) then append candidate
|
|
122
|
+
* - unique → $setUnion (deep equality)
|
|
123
|
+
*/
|
|
124
|
+
private _upsert;
|
|
125
|
+
/**
|
|
126
|
+
* `$update`
|
|
127
|
+
* - keyed → map array and merge / replace matching element(s)
|
|
128
|
+
* - non-keyed → behave like `$addToSet` (insert only when not present)
|
|
129
|
+
*/
|
|
130
|
+
private _update;
|
|
131
|
+
/**
|
|
132
|
+
* `$remove`
|
|
133
|
+
* - keyed → filter out any element whose key set matches a payload item
|
|
134
|
+
* - non-keyed → deep equality remove (`$setDifference`)
|
|
135
|
+
*/
|
|
136
|
+
private _remove;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/lib/mongo-types.d.ts
|
|
140
|
+
interface TPlainIndex {
|
|
141
|
+
key: string;
|
|
142
|
+
name: string;
|
|
143
|
+
type: "plain" | "unique" | "text";
|
|
144
|
+
fields: Record<string, 1 | "text">;
|
|
145
|
+
weights: Record<string, number>;
|
|
146
|
+
}
|
|
147
|
+
interface TSearchIndex {
|
|
148
|
+
key: string;
|
|
149
|
+
name: string;
|
|
150
|
+
type: "dynamic_text" | "search_text" | "vector";
|
|
151
|
+
definition: TMongoSearchIndexDefinition;
|
|
152
|
+
}
|
|
153
|
+
type TMongoIndex = TPlainIndex | TSearchIndex;
|
|
154
|
+
type TVectorSimilarity = "cosine" | "euclidean" | "dotProduct";
|
|
155
|
+
interface TMongoSearchIndexDefinition {
|
|
156
|
+
mappings?: {
|
|
157
|
+
dynamic?: boolean;
|
|
158
|
+
fields?: Record<string, {
|
|
159
|
+
type: string;
|
|
160
|
+
analyzer?: string;
|
|
161
|
+
}>;
|
|
162
|
+
};
|
|
163
|
+
fields?: Array<{
|
|
164
|
+
path: string;
|
|
165
|
+
type: "filter" | "vector";
|
|
166
|
+
similarity?: TVectorSimilarity;
|
|
167
|
+
numDimensions?: number;
|
|
168
|
+
}>;
|
|
169
|
+
analyzer?: string;
|
|
170
|
+
text?: {
|
|
171
|
+
fuzzy?: {
|
|
172
|
+
maxEdits: number;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/lib/mongo-adapter.d.ts
|
|
178
|
+
declare class MongoAdapter extends BaseDbAdapter {
|
|
179
|
+
protected readonly db: Db;
|
|
180
|
+
protected readonly client?: MongoClient | undefined;
|
|
181
|
+
private _collection?;
|
|
182
|
+
/** MongoDB-specific indexes (search, vector) — separate from table.indexes. */
|
|
183
|
+
protected _mongoIndexes: Map<string, TMongoIndex>;
|
|
184
|
+
/** Vector search filter associations built during flattening. */
|
|
185
|
+
protected _vectorFilters: Map<string, string>;
|
|
186
|
+
/** Default similarity thresholds per vector index (from @db.search.vector.threshold). */
|
|
187
|
+
protected _vectorThresholds: Map<string, number>;
|
|
188
|
+
/** Cached search index lookup. */
|
|
189
|
+
protected _searchIndexesMap?: Map<string, TMongoIndex>;
|
|
190
|
+
/** Physical field names with @db.default.increment → optional start value. */
|
|
191
|
+
protected _incrementFields: Map<string, number | undefined>;
|
|
192
|
+
/** Physical field names that have a non-binary collation (nocase/unicode). */
|
|
193
|
+
private _collateFields?;
|
|
194
|
+
/** Capped collection options from @db.mongo.capped. */
|
|
195
|
+
protected _cappedOptions?: {
|
|
196
|
+
size: number;
|
|
197
|
+
max?: number;
|
|
198
|
+
};
|
|
199
|
+
/** Whether the schema explicitly defines _id (via @db.mongo.collection or manual _id field). */
|
|
200
|
+
protected _hasExplicitId: boolean;
|
|
201
|
+
/** Unique fields accumulated during onFieldScanned, returned via getMetadataOverrides. */
|
|
202
|
+
private _pendingUniqueFields;
|
|
203
|
+
constructor(db: Db, client?: MongoClient | undefined);
|
|
204
|
+
private get _client();
|
|
205
|
+
/**
|
|
206
|
+
* Per-client cache: whether transactions are unavailable (standalone MongoDB).
|
|
207
|
+
* Shared across all adapter instances for the same client so topology is probed once.
|
|
208
|
+
*/
|
|
209
|
+
private static readonly _txDisabledClients;
|
|
210
|
+
private get _txDisabled();
|
|
211
|
+
private set _txDisabled(value);
|
|
212
|
+
/**
|
|
213
|
+
* Uses MongoDB's Convenient Transaction API (`session.withTransaction()`).
|
|
214
|
+
* This handles txnNumber management and automatic retry for
|
|
215
|
+
* `TransientTransactionError` / `UnknownTransactionCommitResult`.
|
|
216
|
+
*/
|
|
217
|
+
withTransaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
218
|
+
private static readonly _noSession;
|
|
219
|
+
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */
|
|
220
|
+
protected _getSessionOpts(): {
|
|
221
|
+
session: ClientSession;
|
|
222
|
+
} | Record<string, never>;
|
|
223
|
+
get collection(): Collection<any>;
|
|
224
|
+
aggregatePipeline(pipeline: Document[]): AggregationCursor;
|
|
225
|
+
aggregate(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
226
|
+
get idType(): "string" | "number" | "objectId";
|
|
227
|
+
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
228
|
+
/**
|
|
229
|
+
* Convenience method that uses `idType` to transform an ID value.
|
|
230
|
+
* For use in controllers that don't have access to the field type.
|
|
231
|
+
*/
|
|
232
|
+
prepareIdFromIdType<D = string | number | ObjectId>(id: string | number | ObjectId): D;
|
|
233
|
+
supportsNestedObjects(): boolean;
|
|
234
|
+
supportsNativePatch(): boolean;
|
|
235
|
+
getValidatorPlugins(): ReturnType<BaseDbAdapter["getValidatorPlugins"]>;
|
|
236
|
+
getAdapterTableName(_type: unknown): string | undefined;
|
|
237
|
+
supportsNativeRelations(): boolean;
|
|
238
|
+
loadRelations(rows: Array<Record<string, unknown>>, withRelations: WithRelation[], relations: ReadonlyMap<string, TDbRelation>, foreignKeys: ReadonlyMap<string, TDbForeignKey>, tableResolver?: TTableResolver): Promise<void>;
|
|
239
|
+
/** Returns the context object used by CollectionPatcher. */
|
|
240
|
+
getPatcherContext(): TCollectionPatcherContext;
|
|
241
|
+
nativePatch(filter: FilterExpr, patch: unknown, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
242
|
+
onBeforeFlatten(_type: unknown): void;
|
|
243
|
+
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
244
|
+
getMetadataOverrides(meta: TableMetadata): TMetadataOverrides;
|
|
245
|
+
onAfterFlatten(): void;
|
|
246
|
+
/** Returns MongoDB-specific search index map (internal). */
|
|
247
|
+
getMongoSearchIndexes(): Map<string, TMongoIndex>;
|
|
248
|
+
/** Returns a specific MongoDB search index by name. */
|
|
249
|
+
getMongoSearchIndex(name?: string): TMongoIndex | undefined;
|
|
250
|
+
/** Returns the default similarity threshold for a vector index (from @db.search.vector.threshold). */
|
|
251
|
+
getVectorThreshold(indexName?: string): number | undefined;
|
|
252
|
+
getSearchIndexes(): TSearchIndexInfo[];
|
|
253
|
+
isVectorSearchable(): boolean;
|
|
254
|
+
search(text: string, query: DbQuery, indexName?: string): Promise<Record<string, unknown>[]>;
|
|
255
|
+
searchWithCount(text: string, query: DbQuery, indexName?: string): Promise<{
|
|
256
|
+
data: Array<Record<string, unknown>>;
|
|
257
|
+
count: number;
|
|
258
|
+
}>;
|
|
259
|
+
vectorSearch(vector: number[], query: DbQuery, indexName?: string): Promise<Record<string, unknown>[]>;
|
|
260
|
+
vectorSearchWithCount(vector: number[], query: DbQuery, indexName?: string): Promise<{
|
|
261
|
+
data: Array<Record<string, unknown>>;
|
|
262
|
+
count: number;
|
|
263
|
+
}>;
|
|
264
|
+
findManyWithCount(query: DbQuery): Promise<{
|
|
265
|
+
data: Array<Record<string, unknown>>;
|
|
266
|
+
count: number;
|
|
267
|
+
}>;
|
|
268
|
+
collectionExists(): Promise<boolean>;
|
|
269
|
+
ensureCollectionExists(): Promise<void>;
|
|
270
|
+
/**
|
|
271
|
+
* Wraps an async operation to catch MongoDB duplicate key errors
|
|
272
|
+
* (code 11000) and rethrow as structured `DbError`.
|
|
273
|
+
*/
|
|
274
|
+
private _wrapDuplicateKeyError;
|
|
275
|
+
insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
276
|
+
insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
277
|
+
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
278
|
+
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
279
|
+
count(query: DbQuery): Promise<number>;
|
|
280
|
+
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
281
|
+
replaceOne(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
282
|
+
deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
283
|
+
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
284
|
+
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
285
|
+
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
286
|
+
clearCollectionCache(): void;
|
|
287
|
+
tableExists(): Promise<boolean>;
|
|
288
|
+
ensureTable(): Promise<void>;
|
|
289
|
+
syncIndexes(): Promise<void>;
|
|
290
|
+
syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
|
|
291
|
+
dropColumns(columns: string[]): Promise<void>;
|
|
292
|
+
renameTable(oldName: string): Promise<void>;
|
|
293
|
+
recreateTable(): Promise<void>;
|
|
294
|
+
dropTable(): Promise<void>;
|
|
295
|
+
dropViewByName(viewName: string): Promise<void>;
|
|
296
|
+
dropTableByName(tableName: string): Promise<void>;
|
|
297
|
+
getDesiredTableOptions(): TExistingTableOption[];
|
|
298
|
+
getExistingTableOptions(): Promise<TExistingTableOption[]>;
|
|
299
|
+
destructiveOptionKeys(): ReadonlySet<string>;
|
|
300
|
+
/** Returns the counters collection used for atomic auto-increment. */
|
|
301
|
+
protected get _countersCollection(): Collection<{
|
|
302
|
+
_id: string;
|
|
303
|
+
seq: number;
|
|
304
|
+
}>;
|
|
305
|
+
/** Returns physical field names of increment fields that are undefined in the data. */
|
|
306
|
+
private _fieldsNeedingIncrement;
|
|
307
|
+
/**
|
|
308
|
+
* Atomically allocates `count` sequential values for each increment field
|
|
309
|
+
* using a counter collection. Returns a map of field → first allocated value.
|
|
310
|
+
*/
|
|
311
|
+
private _allocateIncrementValues;
|
|
312
|
+
/** Reads current max value for a single field via $group aggregation. */
|
|
313
|
+
private _getCurrentFieldMax;
|
|
314
|
+
/** Allocates increment values for a batch of items, assigning in order. */
|
|
315
|
+
private _assignBatchIncrements;
|
|
316
|
+
private _buildFindOptions;
|
|
317
|
+
/**
|
|
318
|
+
* Returns MongoDB collation options if any filter field has a non-binary collation.
|
|
319
|
+
* Uses pre-computed insights when available, falls back to computing them on demand.
|
|
320
|
+
* Maps: nocase → strength 2 (case-insensitive), unicode → strength 1 (case+accent-insensitive).
|
|
321
|
+
*/
|
|
322
|
+
private _getCollationOpts;
|
|
323
|
+
protected _addMongoIndexField(type: TPlainIndex["type"], name: string, field: string, weight?: number): void;
|
|
324
|
+
protected _setSearchIndex(type: TSearchIndex["type"], name: string | undefined, definition: TMongoSearchIndexDefinition): void;
|
|
325
|
+
protected _addFieldToSearchIndex(type: TSearchIndex["type"], _name: string | undefined, fieldName: string, analyzer?: string): void;
|
|
326
|
+
}
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/lib/mongo-filter.d.ts
|
|
329
|
+
/**
|
|
330
|
+
* Translates a generic {@link FilterExpr} into a MongoDB-compatible
|
|
331
|
+
* {@link Filter} document.
|
|
332
|
+
*
|
|
333
|
+
* MongoDB's query language is nearly identical to the `FilterExpr` structure,
|
|
334
|
+
* so this is largely a structural pass-through via the `walkFilter` visitor.
|
|
335
|
+
*/
|
|
336
|
+
declare function buildMongoFilter(filter: FilterExpr): Filter<any>;
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/lib/validate-plugins.d.ts
|
|
339
|
+
declare const validateMongoIdPlugin: TValidatorPlugin;
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/lib/index.d.ts
|
|
342
|
+
declare function createAdapter(connection: string, _options?: Record<string, unknown>): DbSpace;
|
|
343
|
+
//#endregion
|
|
344
|
+
export { CollectionPatcher, MongoAdapter, TCollectionPatcherContext, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, createAdapter, validateMongoIdPlugin };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbForeignKey, TDbInsertManyResult, TDbInsertResult, TDbRelation, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TSearchIndexInfo, TSyncColumnResult, TTableResolver, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
2
|
+
import { AggregationCursor, ClientSession, Collection, Db, Document, Filter, MongoClient, ObjectId, UpdateFilter, UpdateOptions } from "mongodb";
|
|
3
|
+
import { TAtscriptAnnotatedType, TMetadataMap, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/collection-patcher.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Context interface for CollectionPatcher.
|
|
8
|
+
* Decouples the patcher from AsCollection, allowing MongoAdapter to provide this.
|
|
9
|
+
*/
|
|
10
|
+
interface TCollectionPatcherContext {
|
|
11
|
+
flatMap: Map<string, TAtscriptAnnotatedType>;
|
|
12
|
+
prepareId(id: any): any;
|
|
13
|
+
createValidator(opts?: Partial<TValidatorOptions>): Validator<any>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* CollectionPatcher is a small helper that converts a *patch payload* produced
|
|
17
|
+
* by Atscript into a shape that the official MongoDB driver understands – a
|
|
18
|
+
* triple of `(filter, update, options)` to be fed to `collection.updateOne()`.
|
|
19
|
+
*
|
|
20
|
+
* Supported high‑level operations for *top‑level arrays* (see the attached
|
|
21
|
+
* spreadsheet in the chat):
|
|
22
|
+
*
|
|
23
|
+
* | Payload field | MongoDB operator | Purpose |
|
|
24
|
+
* |-------------- |-------------------------|----------------------------------------|
|
|
25
|
+
* | `$replace` | full `$set` | Replace the whole array. |
|
|
26
|
+
* | `$insert` | `$push` | Append new items (duplicates allowed). |
|
|
27
|
+
* | `$upsert` | custom | Insert or update by *key* (see TODO). |
|
|
28
|
+
* | `$update` | `$set` + `arrayFilters` | Update array elements matched by *key* |
|
|
29
|
+
* | `$remove` | `$pullAll` / `$pull` | Remove by value or by *key*. |
|
|
30
|
+
*
|
|
31
|
+
* The class walks through the incoming payload, detects which of the above
|
|
32
|
+
* operations applies to each top‑level array and builds the corresponding
|
|
33
|
+
* MongoDB update document. Primitive fields are flattened into a regular
|
|
34
|
+
* `$set` map.
|
|
35
|
+
*/
|
|
36
|
+
declare class CollectionPatcher {
|
|
37
|
+
private collection;
|
|
38
|
+
private payload;
|
|
39
|
+
private ops?;
|
|
40
|
+
constructor(collection: TCollectionPatcherContext, payload: any, ops?: TFieldOps | undefined);
|
|
41
|
+
static getKeyProps: typeof getKeyProps;
|
|
42
|
+
/**
|
|
43
|
+
* Internal accumulator: filter passed to `updateOne()`.
|
|
44
|
+
* Filled only with the `_id` field right now.
|
|
45
|
+
*/
|
|
46
|
+
private filterObj;
|
|
47
|
+
/** MongoDB *update* document being built. */
|
|
48
|
+
private updatePipeline;
|
|
49
|
+
/** Current `$set` stage being populated. */
|
|
50
|
+
private currentSetStage;
|
|
51
|
+
/** Additional *options* (mainly `arrayFilters`). */
|
|
52
|
+
private optionsObj;
|
|
53
|
+
/**
|
|
54
|
+
* Entry point – walk the payload, build `filter`, `update` and `options`.
|
|
55
|
+
*
|
|
56
|
+
* @returns Helper object exposing both individual parts and
|
|
57
|
+
* a `.toArgs()` convenience callback.
|
|
58
|
+
*/
|
|
59
|
+
preparePatch(): {
|
|
60
|
+
toArgs: () => [Filter<any>, UpdateFilter<any> | Document[], UpdateOptions];
|
|
61
|
+
filter: Filter<any>;
|
|
62
|
+
updateFilter: Document[];
|
|
63
|
+
updateOptions: UpdateOptions;
|
|
64
|
+
};
|
|
65
|
+
/** Builds a MongoDB aggregation expression for an $inc or $mul field op. */
|
|
66
|
+
private _fieldOpExpr;
|
|
67
|
+
/**
|
|
68
|
+
* Helper – lazily create `$set` section and assign *key* → *value*.
|
|
69
|
+
*
|
|
70
|
+
* @param key Fully‑qualified dotted path
|
|
71
|
+
* @param val Value to be written
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
private _set;
|
|
75
|
+
/**
|
|
76
|
+
* Recursively walk through the patch *payload* and convert it into `$set`/…
|
|
77
|
+
* statements. Top‑level arrays are delegated to {@link parseArrayPatch}.
|
|
78
|
+
*
|
|
79
|
+
* @param payload Current payload chunk
|
|
80
|
+
* @param prefix Dotted path accumulated so far
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
private flattenPayload;
|
|
84
|
+
/**
|
|
85
|
+
* Dispatch a *single* array patch. Exactly one of `$replace`, `$insert`,
|
|
86
|
+
* `$upsert`, `$update`, `$remove` must be present – otherwise we throw.
|
|
87
|
+
*
|
|
88
|
+
* @param key Dotted path to the array field
|
|
89
|
+
* @param value Payload slice for that field
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
private parseArrayPatch;
|
|
93
|
+
/**
|
|
94
|
+
* Build an *aggregation‐expression* that checks equality by **all** keys in
|
|
95
|
+
* `keys`. Example output for keys `["id", "lang"]` and bases `a`, `b`:
|
|
96
|
+
* ```json
|
|
97
|
+
* { "$and": [ { "$eq": ["$$a.id", "$$b.id"] }, { "$eq": ["$$a.lang", "$$b.lang"] } ] }
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @param keys Ordered list of key property names
|
|
101
|
+
* @param left Base token for *left* expression (e.g. `"$$el"`)
|
|
102
|
+
* @param right Base token for *right* expression (e.g. `"$$this"`)
|
|
103
|
+
*/
|
|
104
|
+
private _keysEqual;
|
|
105
|
+
/**
|
|
106
|
+
* `$replace` – overwrite the entire array with `input`.
|
|
107
|
+
*
|
|
108
|
+
* @param key Dotted path to the array
|
|
109
|
+
* @param input New array value (may be `undefined`)
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
private _replace;
|
|
113
|
+
/**
|
|
114
|
+
* `$insert`
|
|
115
|
+
* - plain append → $concatArrays
|
|
116
|
+
* - unique / keyed → delegate to _upsert (insert-or-update)
|
|
117
|
+
*/
|
|
118
|
+
private _insert;
|
|
119
|
+
/**
|
|
120
|
+
* `$upsert`
|
|
121
|
+
* - keyed → remove existing matching by key(s) then append candidate
|
|
122
|
+
* - unique → $setUnion (deep equality)
|
|
123
|
+
*/
|
|
124
|
+
private _upsert;
|
|
125
|
+
/**
|
|
126
|
+
* `$update`
|
|
127
|
+
* - keyed → map array and merge / replace matching element(s)
|
|
128
|
+
* - non-keyed → behave like `$addToSet` (insert only when not present)
|
|
129
|
+
*/
|
|
130
|
+
private _update;
|
|
131
|
+
/**
|
|
132
|
+
* `$remove`
|
|
133
|
+
* - keyed → filter out any element whose key set matches a payload item
|
|
134
|
+
* - non-keyed → deep equality remove (`$setDifference`)
|
|
135
|
+
*/
|
|
136
|
+
private _remove;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/lib/mongo-types.d.ts
|
|
140
|
+
interface TPlainIndex {
|
|
141
|
+
key: string;
|
|
142
|
+
name: string;
|
|
143
|
+
type: "plain" | "unique" | "text";
|
|
144
|
+
fields: Record<string, 1 | "text">;
|
|
145
|
+
weights: Record<string, number>;
|
|
146
|
+
}
|
|
147
|
+
interface TSearchIndex {
|
|
148
|
+
key: string;
|
|
149
|
+
name: string;
|
|
150
|
+
type: "dynamic_text" | "search_text" | "vector";
|
|
151
|
+
definition: TMongoSearchIndexDefinition;
|
|
152
|
+
}
|
|
153
|
+
type TMongoIndex = TPlainIndex | TSearchIndex;
|
|
154
|
+
type TVectorSimilarity = "cosine" | "euclidean" | "dotProduct";
|
|
155
|
+
interface TMongoSearchIndexDefinition {
|
|
156
|
+
mappings?: {
|
|
157
|
+
dynamic?: boolean;
|
|
158
|
+
fields?: Record<string, {
|
|
159
|
+
type: string;
|
|
160
|
+
analyzer?: string;
|
|
161
|
+
}>;
|
|
162
|
+
};
|
|
163
|
+
fields?: Array<{
|
|
164
|
+
path: string;
|
|
165
|
+
type: "filter" | "vector";
|
|
166
|
+
similarity?: TVectorSimilarity;
|
|
167
|
+
numDimensions?: number;
|
|
168
|
+
}>;
|
|
169
|
+
analyzer?: string;
|
|
170
|
+
text?: {
|
|
171
|
+
fuzzy?: {
|
|
172
|
+
maxEdits: number;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/lib/mongo-adapter.d.ts
|
|
178
|
+
declare class MongoAdapter extends BaseDbAdapter {
|
|
179
|
+
protected readonly db: Db;
|
|
180
|
+
protected readonly client?: MongoClient | undefined;
|
|
181
|
+
private _collection?;
|
|
182
|
+
/** MongoDB-specific indexes (search, vector) — separate from table.indexes. */
|
|
183
|
+
protected _mongoIndexes: Map<string, TMongoIndex>;
|
|
184
|
+
/** Vector search filter associations built during flattening. */
|
|
185
|
+
protected _vectorFilters: Map<string, string>;
|
|
186
|
+
/** Default similarity thresholds per vector index (from @db.search.vector.threshold). */
|
|
187
|
+
protected _vectorThresholds: Map<string, number>;
|
|
188
|
+
/** Cached search index lookup. */
|
|
189
|
+
protected _searchIndexesMap?: Map<string, TMongoIndex>;
|
|
190
|
+
/** Physical field names with @db.default.increment → optional start value. */
|
|
191
|
+
protected _incrementFields: Map<string, number | undefined>;
|
|
192
|
+
/** Physical field names that have a non-binary collation (nocase/unicode). */
|
|
193
|
+
private _collateFields?;
|
|
194
|
+
/** Capped collection options from @db.mongo.capped. */
|
|
195
|
+
protected _cappedOptions?: {
|
|
196
|
+
size: number;
|
|
197
|
+
max?: number;
|
|
198
|
+
};
|
|
199
|
+
/** Whether the schema explicitly defines _id (via @db.mongo.collection or manual _id field). */
|
|
200
|
+
protected _hasExplicitId: boolean;
|
|
201
|
+
/** Unique fields accumulated during onFieldScanned, returned via getMetadataOverrides. */
|
|
202
|
+
private _pendingUniqueFields;
|
|
203
|
+
constructor(db: Db, client?: MongoClient | undefined);
|
|
204
|
+
private get _client();
|
|
205
|
+
/**
|
|
206
|
+
* Per-client cache: whether transactions are unavailable (standalone MongoDB).
|
|
207
|
+
* Shared across all adapter instances for the same client so topology is probed once.
|
|
208
|
+
*/
|
|
209
|
+
private static readonly _txDisabledClients;
|
|
210
|
+
private get _txDisabled();
|
|
211
|
+
private set _txDisabled(value);
|
|
212
|
+
/**
|
|
213
|
+
* Uses MongoDB's Convenient Transaction API (`session.withTransaction()`).
|
|
214
|
+
* This handles txnNumber management and automatic retry for
|
|
215
|
+
* `TransientTransactionError` / `UnknownTransactionCommitResult`.
|
|
216
|
+
*/
|
|
217
|
+
withTransaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
218
|
+
private static readonly _noSession;
|
|
219
|
+
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */
|
|
220
|
+
protected _getSessionOpts(): {
|
|
221
|
+
session: ClientSession;
|
|
222
|
+
} | Record<string, never>;
|
|
223
|
+
get collection(): Collection<any>;
|
|
224
|
+
aggregatePipeline(pipeline: Document[]): AggregationCursor;
|
|
225
|
+
aggregate(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
226
|
+
get idType(): "string" | "number" | "objectId";
|
|
227
|
+
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
228
|
+
/**
|
|
229
|
+
* Convenience method that uses `idType` to transform an ID value.
|
|
230
|
+
* For use in controllers that don't have access to the field type.
|
|
231
|
+
*/
|
|
232
|
+
prepareIdFromIdType<D = string | number | ObjectId>(id: string | number | ObjectId): D;
|
|
233
|
+
supportsNestedObjects(): boolean;
|
|
234
|
+
supportsNativePatch(): boolean;
|
|
235
|
+
getValidatorPlugins(): ReturnType<BaseDbAdapter["getValidatorPlugins"]>;
|
|
236
|
+
getAdapterTableName(_type: unknown): string | undefined;
|
|
237
|
+
supportsNativeRelations(): boolean;
|
|
238
|
+
loadRelations(rows: Array<Record<string, unknown>>, withRelations: WithRelation[], relations: ReadonlyMap<string, TDbRelation>, foreignKeys: ReadonlyMap<string, TDbForeignKey>, tableResolver?: TTableResolver): Promise<void>;
|
|
239
|
+
/** Returns the context object used by CollectionPatcher. */
|
|
240
|
+
getPatcherContext(): TCollectionPatcherContext;
|
|
241
|
+
nativePatch(filter: FilterExpr, patch: unknown, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
242
|
+
onBeforeFlatten(_type: unknown): void;
|
|
243
|
+
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
244
|
+
getMetadataOverrides(meta: TableMetadata): TMetadataOverrides;
|
|
245
|
+
onAfterFlatten(): void;
|
|
246
|
+
/** Returns MongoDB-specific search index map (internal). */
|
|
247
|
+
getMongoSearchIndexes(): Map<string, TMongoIndex>;
|
|
248
|
+
/** Returns a specific MongoDB search index by name. */
|
|
249
|
+
getMongoSearchIndex(name?: string): TMongoIndex | undefined;
|
|
250
|
+
/** Returns the default similarity threshold for a vector index (from @db.search.vector.threshold). */
|
|
251
|
+
getVectorThreshold(indexName?: string): number | undefined;
|
|
252
|
+
getSearchIndexes(): TSearchIndexInfo[];
|
|
253
|
+
isVectorSearchable(): boolean;
|
|
254
|
+
search(text: string, query: DbQuery, indexName?: string): Promise<Record<string, unknown>[]>;
|
|
255
|
+
searchWithCount(text: string, query: DbQuery, indexName?: string): Promise<{
|
|
256
|
+
data: Array<Record<string, unknown>>;
|
|
257
|
+
count: number;
|
|
258
|
+
}>;
|
|
259
|
+
vectorSearch(vector: number[], query: DbQuery, indexName?: string): Promise<Record<string, unknown>[]>;
|
|
260
|
+
vectorSearchWithCount(vector: number[], query: DbQuery, indexName?: string): Promise<{
|
|
261
|
+
data: Array<Record<string, unknown>>;
|
|
262
|
+
count: number;
|
|
263
|
+
}>;
|
|
264
|
+
findManyWithCount(query: DbQuery): Promise<{
|
|
265
|
+
data: Array<Record<string, unknown>>;
|
|
266
|
+
count: number;
|
|
267
|
+
}>;
|
|
268
|
+
collectionExists(): Promise<boolean>;
|
|
269
|
+
ensureCollectionExists(): Promise<void>;
|
|
270
|
+
/**
|
|
271
|
+
* Wraps an async operation to catch MongoDB duplicate key errors
|
|
272
|
+
* (code 11000) and rethrow as structured `DbError`.
|
|
273
|
+
*/
|
|
274
|
+
private _wrapDuplicateKeyError;
|
|
275
|
+
insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
276
|
+
insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
277
|
+
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
278
|
+
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
279
|
+
count(query: DbQuery): Promise<number>;
|
|
280
|
+
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
281
|
+
replaceOne(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
282
|
+
deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
283
|
+
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
284
|
+
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
285
|
+
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
286
|
+
clearCollectionCache(): void;
|
|
287
|
+
tableExists(): Promise<boolean>;
|
|
288
|
+
ensureTable(): Promise<void>;
|
|
289
|
+
syncIndexes(): Promise<void>;
|
|
290
|
+
syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
|
|
291
|
+
dropColumns(columns: string[]): Promise<void>;
|
|
292
|
+
renameTable(oldName: string): Promise<void>;
|
|
293
|
+
recreateTable(): Promise<void>;
|
|
294
|
+
dropTable(): Promise<void>;
|
|
295
|
+
dropViewByName(viewName: string): Promise<void>;
|
|
296
|
+
dropTableByName(tableName: string): Promise<void>;
|
|
297
|
+
getDesiredTableOptions(): TExistingTableOption[];
|
|
298
|
+
getExistingTableOptions(): Promise<TExistingTableOption[]>;
|
|
299
|
+
destructiveOptionKeys(): ReadonlySet<string>;
|
|
300
|
+
/** Returns the counters collection used for atomic auto-increment. */
|
|
301
|
+
protected get _countersCollection(): Collection<{
|
|
302
|
+
_id: string;
|
|
303
|
+
seq: number;
|
|
304
|
+
}>;
|
|
305
|
+
/** Returns physical field names of increment fields that are undefined in the data. */
|
|
306
|
+
private _fieldsNeedingIncrement;
|
|
307
|
+
/**
|
|
308
|
+
* Atomically allocates `count` sequential values for each increment field
|
|
309
|
+
* using a counter collection. Returns a map of field → first allocated value.
|
|
310
|
+
*/
|
|
311
|
+
private _allocateIncrementValues;
|
|
312
|
+
/** Reads current max value for a single field via $group aggregation. */
|
|
313
|
+
private _getCurrentFieldMax;
|
|
314
|
+
/** Allocates increment values for a batch of items, assigning in order. */
|
|
315
|
+
private _assignBatchIncrements;
|
|
316
|
+
private _buildFindOptions;
|
|
317
|
+
/**
|
|
318
|
+
* Returns MongoDB collation options if any filter field has a non-binary collation.
|
|
319
|
+
* Uses pre-computed insights when available, falls back to computing them on demand.
|
|
320
|
+
* Maps: nocase → strength 2 (case-insensitive), unicode → strength 1 (case+accent-insensitive).
|
|
321
|
+
*/
|
|
322
|
+
private _getCollationOpts;
|
|
323
|
+
protected _addMongoIndexField(type: TPlainIndex["type"], name: string, field: string, weight?: number): void;
|
|
324
|
+
protected _setSearchIndex(type: TSearchIndex["type"], name: string | undefined, definition: TMongoSearchIndexDefinition): void;
|
|
325
|
+
protected _addFieldToSearchIndex(type: TSearchIndex["type"], _name: string | undefined, fieldName: string, analyzer?: string): void;
|
|
326
|
+
}
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/lib/mongo-filter.d.ts
|
|
329
|
+
/**
|
|
330
|
+
* Translates a generic {@link FilterExpr} into a MongoDB-compatible
|
|
331
|
+
* {@link Filter} document.
|
|
332
|
+
*
|
|
333
|
+
* MongoDB's query language is nearly identical to the `FilterExpr` structure,
|
|
334
|
+
* so this is largely a structural pass-through via the `walkFilter` visitor.
|
|
335
|
+
*/
|
|
336
|
+
declare function buildMongoFilter(filter: FilterExpr): Filter<any>;
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/lib/validate-plugins.d.ts
|
|
339
|
+
declare const validateMongoIdPlugin: TValidatorPlugin;
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/lib/index.d.ts
|
|
342
|
+
declare function createAdapter(connection: string, _options?: Record<string, unknown>): DbSpace;
|
|
343
|
+
//#endregion
|
|
344
|
+
export { CollectionPatcher, MongoAdapter, TCollectionPatcherContext, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, createAdapter, validateMongoIdPlugin };
|