@atscript/db-mongo 0.1.39 → 0.1.40

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.
@@ -7,16 +7,16 @@
7
7
  Entry point for MongoDB operations. Wraps a `MongoClient` and provides a collection registry.
8
8
 
9
9
  ```typescript
10
- import { AsMongo } from '@atscript/db-mongo'
10
+ import { AsMongo } from "@atscript/db-mongo";
11
11
 
12
12
  // From connection string
13
- const asMongo = new AsMongo('mongodb://localhost:27017/mydb')
13
+ const asMongo = new AsMongo("mongodb://localhost:27017/mydb");
14
14
 
15
15
  // From existing MongoClient
16
- const asMongo = new AsMongo(existingClient)
16
+ const asMongo = new AsMongo(existingClient);
17
17
 
18
18
  // With logger
19
- const asMongo = new AsMongo(connectionString, myLogger)
19
+ const asMongo = new AsMongo(connectionString, myLogger);
20
20
  ```
21
21
 
22
22
  ### `getCollection<T>(type, logger?)`
@@ -24,9 +24,9 @@ const asMongo = new AsMongo(connectionString, myLogger)
24
24
  Returns an `AsCollection<T>` for the given Atscript annotated type. Collections are cached per type.
25
25
 
26
26
  ```typescript
27
- import { User } from './user.as'
27
+ import { User } from "./user.as";
28
28
 
29
- const users = asMongo.getCollection(User)
29
+ const users = asMongo.getCollection(User);
30
30
  ```
31
31
 
32
32
  ## AsCollection
@@ -44,16 +44,16 @@ Core collection abstraction providing validation, CRUD operations, and index man
44
44
  ```typescript
45
45
  // Insert one
46
46
  const result = await users.insert({
47
- email: 'alice@example.com',
48
- name: 'Alice',
47
+ email: "alice@example.com",
48
+ name: "Alice",
49
49
  isActive: true,
50
- })
50
+ });
51
51
 
52
52
  // Insert many
53
53
  const result = await users.insert([
54
- { email: 'alice@example.com', name: 'Alice', isActive: true },
55
- { email: 'bob@example.com', name: 'Bob', isActive: false },
56
- ])
54
+ { email: "alice@example.com", name: "Alice", isActive: true },
55
+ { email: "bob@example.com", name: "Bob", isActive: false },
56
+ ]);
57
57
  ```
58
58
 
59
59
  Validates the payload before inserting. Auto-generates `ObjectId` for `_id` if type is `mongo.objectId`.
@@ -62,11 +62,11 @@ Validates the payload before inserting. Auto-generates `ObjectId` for `_id` if t
62
62
 
63
63
  ```typescript
64
64
  await users.replace({
65
- _id: '507f1f77bcf86cd799439011',
66
- email: 'alice@new.com',
67
- name: 'Alice Updated',
65
+ _id: "507f1f77bcf86cd799439011",
66
+ email: "alice@new.com",
67
+ name: "Alice Updated",
68
68
  isActive: true,
69
- })
69
+ });
70
70
  ```
71
71
 
72
72
  Validates the full document and replaces by `_id`.
@@ -75,10 +75,10 @@ Validates the full document and replaces by `_id`.
75
75
 
76
76
  ```typescript
77
77
  await users.update({
78
- _id: '507f1f77bcf86cd799439011',
79
- name: 'New Name',
78
+ _id: "507f1f77bcf86cd799439011",
79
+ name: "New Name",
80
80
  // Only updates specified fields
81
- })
81
+ });
82
82
  ```
83
83
 
84
84
  Uses `CollectionPatcher` internally to build MongoDB aggregation pipelines. See [patches.md](patches.md) for array patch operations.
@@ -87,28 +87,29 @@ Uses `CollectionPatcher` internally to build MongoDB aggregation pipelines. See
87
87
 
88
88
  ```typescript
89
89
  // Get a validator for different contexts
90
- const insertValidator = users.getValidator('insert')
91
- const updateValidator = users.getValidator('update')
92
- const patchValidator = users.getValidator('patch')
90
+ const insertValidator = users.getValidator("insert");
91
+ const updateValidator = users.getValidator("update");
92
+ const patchValidator = users.getValidator("patch");
93
93
 
94
94
  // Create a custom validator
95
95
  const validator = users.createValidator({
96
96
  partial: true,
97
97
  plugins: [myPlugin],
98
- skipList: new Set(['internalField']),
99
- })
98
+ skipList: new Set(["internalField"]),
99
+ });
100
100
  ```
101
101
 
102
102
  ### Index Management
103
103
 
104
104
  ```typescript
105
105
  // Sync indexes — creates/drops to match .as definitions
106
- await users.syncIndexes()
106
+ await users.syncIndexes();
107
107
  ```
108
108
 
109
109
  Only manages indexes prefixed with `atscript__`. User-created indexes are not touched.
110
110
 
111
111
  Reads index definitions from:
112
+
112
113
  - `@db.index.plain` → standard indexes
113
114
  - `@db.index.unique` → unique indexes
114
115
  - `@db.index.fulltext` → text indexes (weight 1)
@@ -119,10 +120,10 @@ Reads index definitions from:
119
120
 
120
121
  ```typescript
121
122
  // Find documents
122
- const cursor = users.collection.find({ isActive: true })
123
+ const cursor = users.collection.find({ isActive: true });
123
124
 
124
125
  // Use the raw MongoDB collection for queries
125
- const doc = await users.collection.findOne({ _id: users.prepareId(id) })
126
+ const doc = await users.collection.findOne({ _id: users.prepareId(id) });
126
127
  ```
127
128
 
128
129
  ### `prepareId(id)`
@@ -130,7 +131,7 @@ const doc = await users.collection.findOne({ _id: users.prepareId(id) })
130
131
  Converts a string ID to `ObjectId` if the collection uses `mongo.objectId` type for `_id`.
131
132
 
132
133
  ```typescript
133
- const objectId = users.prepareId('507f1f77bcf86cd799439011')
134
+ const objectId = users.prepareId("507f1f77bcf86cd799439011");
134
135
  ```
135
136
 
136
137
  ## Best Practices
@@ -15,17 +15,18 @@ npm install @atscript/core @atscript/typescript mongodb
15
15
  Add `MongoPlugin()` to your `atscript.config.ts`:
16
16
 
17
17
  ```typescript
18
- import { defineConfig } from '@atscript/core'
19
- import { ts } from '@atscript/typescript'
20
- import MongoPlugin from '@atscript/db-mongo/plugin'
18
+ import { defineConfig } from "@atscript/core";
19
+ import { ts } from "@atscript/typescript";
20
+ import MongoPlugin from "@atscript/db-mongo/plugin";
21
21
 
22
22
  export default defineConfig({
23
- rootDir: 'src',
23
+ rootDir: "src",
24
24
  plugins: [ts(), MongoPlugin()],
25
- })
25
+ });
26
26
  ```
27
27
 
28
28
  The plugin registers:
29
+
29
30
  - **Primitives**: `mongo.objectId` (24-char hex string), `mongo.vector` (number array)
30
31
  - **Annotations**: All `@db.mongo.*` annotations (collection, indexes, search, patch, array)
31
32
 
@@ -30,8 +30,8 @@ export interface User {
30
30
  // This replaces the entire address object
31
31
  await users.update({
32
32
  _id: id,
33
- address: { line1: '123 Main St', city: 'NYC', zip: '10001' },
34
- })
33
+ address: { line1: "123 Main St", city: "NYC", zip: "10001" },
34
+ });
35
35
  ```
36
36
 
37
37
  ### `@db.mongo.patch.strategy 'replace'`
@@ -57,8 +57,8 @@ export interface User {
57
57
  // Only updates phone, email is preserved
58
58
  await users.update({
59
59
  _id: id,
60
- contacts: { phone: '+1-555-0100' },
61
- })
60
+ contacts: { phone: "+1-555-0100" },
61
+ });
62
62
  ```
63
63
 
64
64
  ### Nested strategies
@@ -90,8 +90,8 @@ Replaces the entire array:
90
90
  ```typescript
91
91
  await collection.update({
92
92
  _id: id,
93
- tags: { $replace: ['new', 'tags', 'only'] },
94
- })
93
+ tags: { $replace: ["new", "tags", "only"] },
94
+ });
95
95
  ```
96
96
 
97
97
  ### `$insert`
@@ -101,8 +101,8 @@ Appends items to the array:
101
101
  ```typescript
102
102
  await collection.update({
103
103
  _id: id,
104
- tags: { $insert: ['newTag1', 'newTag2'] },
105
- })
104
+ tags: { $insert: ["newTag1", "newTag2"] },
105
+ });
106
106
  ```
107
107
 
108
108
  If `@db.mongo.array.uniqueItems` is set, duplicates are silently dropped (uses `$setUnion`).
@@ -128,10 +128,10 @@ await products.update({
128
128
  _id: id,
129
129
  items: {
130
130
  $upsert: [
131
- { sku: 'ABC', quantity: 10, price: 9.99 }, // replaces existing ABC or inserts
131
+ { sku: "ABC", quantity: 10, price: 9.99 }, // replaces existing ABC or inserts
132
132
  ],
133
133
  },
134
- })
134
+ });
135
135
  ```
136
136
 
137
137
  For non-keyed arrays, behaves like `$addToSet` (deep equality).
@@ -145,10 +145,10 @@ await products.update({
145
145
  _id: id,
146
146
  items: {
147
147
  $update: [
148
- { sku: 'ABC', quantity: 20 }, // updates only quantity for sku=ABC
148
+ { sku: "ABC", quantity: 20 }, // updates only quantity for sku=ABC
149
149
  ],
150
150
  },
151
- })
151
+ });
152
152
  ```
153
153
 
154
154
  With `@db.mongo.patch.strategy 'merge'` on the array field, uses `$mergeObjects` to merge into the matched element. Without it, replaces the matched element entirely.
@@ -162,17 +162,17 @@ Removes array elements:
162
162
  await products.update({
163
163
  _id: id,
164
164
  items: {
165
- $remove: [{ sku: 'ABC' }],
165
+ $remove: [{ sku: "ABC" }],
166
166
  },
167
- })
167
+ });
168
168
 
169
169
  // Non-keyed — removes by deep equality
170
170
  await collection.update({
171
171
  _id: id,
172
172
  tags: {
173
- $remove: ['obsoleteTag'],
173
+ $remove: ["obsoleteTag"],
174
174
  },
175
- })
175
+ });
176
176
  ```
177
177
 
178
178
  ## Array Keys
@@ -196,6 +196,7 @@ Multiple key fields form a composite key — elements are matched when ALL key f
196
196
  ## Implementation Details
197
197
 
198
198
  The `CollectionPatcher` converts patch payloads into MongoDB aggregation pipeline stages using:
199
+
199
200
  - `$reduce` + `$filter` + `$concatArrays` for keyed upsert/remove
200
201
  - `$map` + `$cond` + `$mergeObjects` for keyed update with merge
201
202
  - `$setUnion` for unique/non-keyed insert
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025-present Artem Maltsev
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,75 +0,0 @@
1
- import { buildMongoFilter } from "./mongo-filter-CL69Yhcm.mjs";
2
- import { resolveAlias } from "@atscript/db/agg";
3
-
4
- //#region packages/db-mongo/src/agg.ts
5
- /** Simple accumulators that map directly to `{ $<fn>: '$field' }`. */ const SIMPLE_ACCUMULATORS = {
6
- sum: "$sum",
7
- avg: "$avg",
8
- min: "$min",
9
- max: "$max"
10
- };
11
- /**
12
- * Maps an AggregateExpr to a MongoDB $group accumulator expression.
13
- */ function toAccumulator(expr) {
14
- const simple = SIMPLE_ACCUMULATORS[expr.$fn];
15
- if (simple) return { [simple]: `$${expr.$field}` };
16
- if (expr.$fn === "count") {
17
- if (expr.$field === "*") return { $sum: 1 };
18
- return { $sum: { $cond: [
19
- { $ne: [`$${expr.$field}`, null] },
20
- 1,
21
- 0
22
- ] } };
23
- }
24
- throw new Error(`Unsupported aggregate function: ${expr.$fn}`);
25
- }
26
- /**
27
- * Builds the common prefix stages: $match + $group._id from groupBy fields.
28
- * Shared by both full aggregate and count pipelines.
29
- */ function buildPrefix(query) {
30
- const controls = query.controls || {};
31
- const groupBy = controls.$groupBy ?? [];
32
- const pipeline = [{ $match: buildMongoFilter(query.filter) }];
33
- const groupId = {};
34
- for (const field of groupBy) groupId[field] = `$${field}`;
35
- return {
36
- pipeline,
37
- groupId,
38
- groupBy,
39
- controls
40
- };
41
- }
42
- function buildAggregatePipeline(query) {
43
- const { pipeline, groupId, groupBy, controls } = buildPrefix(query);
44
- const groupStage = { _id: groupId };
45
- const project = { _id: 0 };
46
- const aggregates = controls.$select?.aggregates;
47
- for (const field of groupBy) project[field] = `$_id.${field}`;
48
- if (aggregates) for (const expr of aggregates) {
49
- const alias = resolveAlias(expr);
50
- groupStage[alias] = toAccumulator(expr);
51
- project[alias] = 1;
52
- }
53
- pipeline.push({ $group: groupStage });
54
- pipeline.push({ $project: project });
55
- if (controls.$having) pipeline.push({ $match: buildMongoFilter(controls.$having) });
56
- if (controls.$sort) pipeline.push({ $sort: controls.$sort });
57
- if (controls.$skip) pipeline.push({ $skip: controls.$skip });
58
- if (controls.$limit) pipeline.push({ $limit: controls.$limit });
59
- return pipeline;
60
- }
61
- function buildCountPipeline(query) {
62
- const { pipeline, groupId, groupBy, controls } = buildPrefix(query);
63
- pipeline.push({ $group: { _id: groupId } });
64
- if (controls.$having) {
65
- const project = { _id: 0 };
66
- for (const field of groupBy) project[field] = `$_id.${field}`;
67
- pipeline.push({ $project: project });
68
- pipeline.push({ $match: buildMongoFilter(controls.$having) });
69
- }
70
- pipeline.push({ $count: "count" });
71
- return pipeline;
72
- }
73
-
74
- //#endregion
75
- export { buildAggregatePipeline, buildCountPipeline };
@@ -1,77 +0,0 @@
1
- "use strict";
2
- const require_mongo_filter = require('./mongo-filter-C8w5by9H.cjs');
3
- const __atscript_db_agg = require_mongo_filter.__toESM(require("@atscript/db/agg"));
4
-
5
- //#region packages/db-mongo/src/agg.ts
6
- /** Simple accumulators that map directly to `{ $<fn>: '$field' }`. */ const SIMPLE_ACCUMULATORS = {
7
- sum: "$sum",
8
- avg: "$avg",
9
- min: "$min",
10
- max: "$max"
11
- };
12
- /**
13
- * Maps an AggregateExpr to a MongoDB $group accumulator expression.
14
- */ function toAccumulator(expr) {
15
- const simple = SIMPLE_ACCUMULATORS[expr.$fn];
16
- if (simple) return { [simple]: `$${expr.$field}` };
17
- if (expr.$fn === "count") {
18
- if (expr.$field === "*") return { $sum: 1 };
19
- return { $sum: { $cond: [
20
- { $ne: [`$${expr.$field}`, null] },
21
- 1,
22
- 0
23
- ] } };
24
- }
25
- throw new Error(`Unsupported aggregate function: ${expr.$fn}`);
26
- }
27
- /**
28
- * Builds the common prefix stages: $match + $group._id from groupBy fields.
29
- * Shared by both full aggregate and count pipelines.
30
- */ function buildPrefix(query) {
31
- const controls = query.controls || {};
32
- const groupBy = controls.$groupBy ?? [];
33
- const pipeline = [{ $match: require_mongo_filter.buildMongoFilter(query.filter) }];
34
- const groupId = {};
35
- for (const field of groupBy) groupId[field] = `$${field}`;
36
- return {
37
- pipeline,
38
- groupId,
39
- groupBy,
40
- controls
41
- };
42
- }
43
- function buildAggregatePipeline(query) {
44
- const { pipeline, groupId, groupBy, controls } = buildPrefix(query);
45
- const groupStage = { _id: groupId };
46
- const project = { _id: 0 };
47
- const aggregates = controls.$select?.aggregates;
48
- for (const field of groupBy) project[field] = `$_id.${field}`;
49
- if (aggregates) for (const expr of aggregates) {
50
- const alias = (0, __atscript_db_agg.resolveAlias)(expr);
51
- groupStage[alias] = toAccumulator(expr);
52
- project[alias] = 1;
53
- }
54
- pipeline.push({ $group: groupStage });
55
- pipeline.push({ $project: project });
56
- if (controls.$having) pipeline.push({ $match: require_mongo_filter.buildMongoFilter(controls.$having) });
57
- if (controls.$sort) pipeline.push({ $sort: controls.$sort });
58
- if (controls.$skip) pipeline.push({ $skip: controls.$skip });
59
- if (controls.$limit) pipeline.push({ $limit: controls.$limit });
60
- return pipeline;
61
- }
62
- function buildCountPipeline(query) {
63
- const { pipeline, groupId, groupBy, controls } = buildPrefix(query);
64
- pipeline.push({ $group: { _id: groupId } });
65
- if (controls.$having) {
66
- const project = { _id: 0 };
67
- for (const field of groupBy) project[field] = `$_id.${field}`;
68
- pipeline.push({ $project: project });
69
- pipeline.push({ $match: require_mongo_filter.buildMongoFilter(controls.$having) });
70
- }
71
- pipeline.push({ $count: "count" });
72
- return pipeline;
73
- }
74
-
75
- //#endregion
76
- exports.buildAggregatePipeline = buildAggregatePipeline
77
- exports.buildCountPipeline = buildCountPipeline