@atscript/mongo 0.1.26 → 0.1.28

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.
@@ -0,0 +1,168 @@
1
+ # Annotations Reference — @atscript/mongo
2
+
3
+ > All database and MongoDB-specific annotations available when using the mongo plugin.
4
+
5
+ ## Annotation Namespaces
6
+
7
+ Annotations are split between core `@db.*` (database-generic) and `@db.mongo.*` (MongoDB-specific).
8
+
9
+ ## Core `@db.*` Annotations
10
+
11
+ These come from `@atscript/core` and are used by the mongo plugin at runtime.
12
+
13
+ ### `@db.table "name"` (interface-level)
14
+
15
+ Names the collection. **Required** for `AsCollection` to work.
16
+
17
+ ```atscript
18
+ @db.table 'users'
19
+ export interface User {
20
+ name: string
21
+ }
22
+ ```
23
+
24
+ ### `@db.index.plain "name?", "sort?"` (field-level, multiple)
25
+
26
+ Standard index. Fields sharing the same name form a compound index.
27
+
28
+ ```atscript
29
+ @db.table 'products'
30
+ export interface Product {
31
+ @db.index.plain 'cat_status'
32
+ category: string
33
+
34
+ @db.index.plain 'cat_status'
35
+ status: string
36
+ }
37
+ ```
38
+
39
+ ### `@db.index.unique "name?"` (field-level, multiple)
40
+
41
+ Unique constraint index.
42
+
43
+ ```atscript
44
+ @db.table 'users'
45
+ export interface User {
46
+ @db.index.unique 'email_idx'
47
+ email: string.email
48
+ }
49
+ ```
50
+
51
+ ### `@db.index.fulltext "name?"` (field-level, multiple)
52
+
53
+ Generic fulltext index (always weight 1 in MongoDB).
54
+
55
+ ```atscript
56
+ @db.table 'articles'
57
+ export interface Article {
58
+ @db.index.fulltext
59
+ title: string
60
+ }
61
+ ```
62
+
63
+ ## MongoDB-Specific `@db.mongo.*` Annotations
64
+
65
+ ### `@db.mongo.collection` (interface-level, no args)
66
+
67
+ Optional convenience annotation. When present, auto-injects `_id: mongo.objectId` if the interface doesn't define one. Validates that `_id` (if present) is not optional and is of type string, number, or mongo.objectId.
68
+
69
+ ```atscript
70
+ @db.table 'users'
71
+ @db.mongo.collection
72
+ export interface User {
73
+ // _id: mongo.objectId — auto-injected
74
+ name: string
75
+ }
76
+ ```
77
+
78
+ ### `@db.mongo.autoIndexes true|false` (interface-level)
79
+
80
+ Toggle automatic index creation when `syncIndexes()` is called. Default: true.
81
+
82
+ ### `@db.mongo.index.text weight?` (field-level)
83
+
84
+ MongoDB-specific text index with optional weight (number). Extends `@db.index.fulltext` with weight support.
85
+
86
+ ```atscript
87
+ @db.table 'articles'
88
+ export interface Article {
89
+ @db.mongo.index.text 10
90
+ title: string
91
+
92
+ @db.mongo.index.text 1
93
+ body: string
94
+ }
95
+ ```
96
+
97
+ ### `@db.mongo.search.dynamic "analyzer?", fuzzy?` (interface-level)
98
+
99
+ Dynamic Atlas Search index.
100
+
101
+ ### `@db.mongo.search.static "analyzer?", fuzzy?, "indexName?"` (interface-level, multiple)
102
+
103
+ Named static Atlas Search index.
104
+
105
+ ### `@db.mongo.search.text "analyzer?", "indexName?"` (field-level, multiple)
106
+
107
+ Atlas Search text field mapping.
108
+
109
+ ### `@db.mongo.search.vector dimensions, "similarity?", "indexName?"` (field-level)
110
+
111
+ Vector search index. Similarity: `"cosine"`, `"euclidean"`, or `"dotProduct"`.
112
+
113
+ ```atscript
114
+ @db.table 'documents'
115
+ export interface Document {
116
+ @db.mongo.search.vector 1536, "cosine", "vector_idx"
117
+ embedding: mongo.vector
118
+ }
119
+ ```
120
+
121
+ ### `@db.mongo.search.filter "indexName"` (field-level, multiple)
122
+
123
+ Pre-filter field for vector search.
124
+
125
+ ### `@db.mongo.patch.strategy "replace"|"merge"` (field-level)
126
+
127
+ Controls how nested objects and arrays are updated during patch operations. See [patches.md](patches.md) for details.
128
+
129
+ ### `@db.mongo.array.uniqueItems` (field-level)
130
+
131
+ Enforces set-semantics on array `$insert` operations — duplicates are silently dropped.
132
+
133
+ ```atscript
134
+ @db.table 'tags'
135
+ export interface TaggedItem {
136
+ @db.mongo.array.uniqueItems
137
+ tags: string[]
138
+ }
139
+ ```
140
+
141
+ ## Common Patterns
142
+
143
+ ### Full collection definition
144
+
145
+ ```atscript
146
+ @db.table 'users'
147
+ @db.mongo.collection
148
+ export interface User {
149
+ @db.index.unique 'email_idx'
150
+ email: string.email
151
+
152
+ @db.mongo.index.text 5
153
+ @expect.minLength 2
154
+ name: string
155
+
156
+ @db.index.plain 'status_idx'
157
+ isActive: boolean
158
+
159
+ @db.mongo.patch.strategy 'merge'
160
+ profile: {
161
+ bio?: string
162
+ avatar?: string
163
+ }
164
+
165
+ @db.mongo.array.uniqueItems
166
+ tags?: string[]
167
+ }
168
+ ```
@@ -0,0 +1,141 @@
1
+ # Collections & CRUD — @atscript/mongo
2
+
3
+ > Using AsMongo and AsCollection for database operations.
4
+
5
+ ## AsMongo
6
+
7
+ Entry point for MongoDB operations. Wraps a `MongoClient` and provides a collection registry.
8
+
9
+ ```typescript
10
+ import { AsMongo } from '@atscript/mongo'
11
+
12
+ // From connection string
13
+ const asMongo = new AsMongo('mongodb://localhost:27017/mydb')
14
+
15
+ // From existing MongoClient
16
+ const asMongo = new AsMongo(existingClient)
17
+
18
+ // With logger
19
+ const asMongo = new AsMongo(connectionString, myLogger)
20
+ ```
21
+
22
+ ### `getCollection<T>(type, logger?)`
23
+
24
+ Returns an `AsCollection<T>` for the given Atscript annotated type. Collections are cached per type.
25
+
26
+ ```typescript
27
+ import { User } from './user.as'
28
+
29
+ const users = asMongo.getCollection(User)
30
+ ```
31
+
32
+ ## AsCollection
33
+
34
+ Core collection abstraction providing validation, CRUD operations, and index management.
35
+
36
+ ### Properties
37
+
38
+ - **`name`** — Collection name (from `@db.table`)
39
+ - **`collection`** — Raw MongoDB `Collection` instance
40
+ - **`flatMap`** — `Map<string, TAtscriptAnnotatedType>` of all fields in dot-notation
41
+
42
+ ### Insert
43
+
44
+ ```typescript
45
+ // Insert one
46
+ const result = await users.insert({
47
+ email: 'alice@example.com',
48
+ name: 'Alice',
49
+ isActive: true,
50
+ })
51
+
52
+ // Insert many
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
+ ])
57
+ ```
58
+
59
+ Validates the payload before inserting. Auto-generates `ObjectId` for `_id` if type is `mongo.objectId`.
60
+
61
+ ### Replace
62
+
63
+ ```typescript
64
+ await users.replace({
65
+ _id: '507f1f77bcf86cd799439011',
66
+ email: 'alice@new.com',
67
+ name: 'Alice Updated',
68
+ isActive: true,
69
+ })
70
+ ```
71
+
72
+ Validates the full document and replaces by `_id`.
73
+
74
+ ### Update (Patch)
75
+
76
+ ```typescript
77
+ await users.update({
78
+ _id: '507f1f77bcf86cd799439011',
79
+ name: 'New Name',
80
+ // Only updates specified fields
81
+ })
82
+ ```
83
+
84
+ Uses `CollectionPatcher` internally to build MongoDB aggregation pipelines. See [patches.md](patches.md) for array patch operations.
85
+
86
+ ### Validation
87
+
88
+ ```typescript
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')
93
+
94
+ // Create a custom validator
95
+ const validator = users.createValidator({
96
+ partial: true,
97
+ plugins: [myPlugin],
98
+ skipList: new Set(['internalField']),
99
+ })
100
+ ```
101
+
102
+ ### Index Management
103
+
104
+ ```typescript
105
+ // Sync indexes — creates/drops to match .as definitions
106
+ await users.syncIndexes()
107
+ ```
108
+
109
+ Only manages indexes prefixed with `atscript__`. User-created indexes are not touched.
110
+
111
+ Reads index definitions from:
112
+ - `@db.index.plain` → standard indexes
113
+ - `@db.index.unique` → unique indexes
114
+ - `@db.index.fulltext` → text indexes (weight 1)
115
+ - `@db.mongo.index.text` → text indexes (custom weight)
116
+ - `@db.mongo.search.*` → Atlas Search indexes
117
+
118
+ ### Querying
119
+
120
+ ```typescript
121
+ // Find documents
122
+ const cursor = users.collection.find({ isActive: true })
123
+
124
+ // Use the raw MongoDB collection for queries
125
+ const doc = await users.collection.findOne({ _id: users.prepareId(id) })
126
+ ```
127
+
128
+ ### `prepareId(id)`
129
+
130
+ Converts a string ID to `ObjectId` if the collection uses `mongo.objectId` type for `_id`.
131
+
132
+ ```typescript
133
+ const objectId = users.prepareId('507f1f77bcf86cd799439011')
134
+ ```
135
+
136
+ ## Best Practices
137
+
138
+ - Use `getValidator()` for context-specific validation before custom operations
139
+ - Call `syncIndexes()` on application startup to ensure indexes match definitions
140
+ - Use `prepareId()` when working with raw MongoDB queries to handle ObjectId conversion
141
+ - The `flatMap` property is lazily built — first access triggers computation
@@ -0,0 +1,83 @@
1
+ # Core Setup — @atscript/mongo
2
+
3
+ > Plugin installation, configuration, and architecture overview.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @atscript/mongo
9
+ # peer dependencies:
10
+ npm install @atscript/core @atscript/typescript mongodb
11
+ ```
12
+
13
+ ## Plugin Configuration
14
+
15
+ Add `MongoPlugin()` to your `atscript.config.ts`:
16
+
17
+ ```typescript
18
+ import { defineConfig } from '@atscript/core'
19
+ import { ts } from '@atscript/typescript'
20
+ import { MongoPlugin } from '@atscript/mongo'
21
+
22
+ export default defineConfig({
23
+ rootDir: 'src',
24
+ plugins: [ts(), MongoPlugin()],
25
+ })
26
+ ```
27
+
28
+ The plugin registers:
29
+ - **Primitives**: `mongo.objectId` (24-char hex string), `mongo.vector` (number array)
30
+ - **Annotations**: All `@db.mongo.*` annotations (collection, indexes, search, patch, array)
31
+
32
+ ## Architecture
33
+
34
+ ```
35
+ @atscript/mongo
36
+ ├── plugin/
37
+ │ ├── index.ts — MongoPlugin factory
38
+ │ ├── annotations.ts — All db.mongo.* annotation specs
39
+ │ └── primitives.ts — mongo.objectId, mongo.vector
40
+ └── lib/
41
+ ├── as-mongo.ts — AsMongo: MongoDB client wrapper
42
+ ├── as-collection.ts — AsCollection: collection abstraction (validation, indexes, CRUD)
43
+ ├── collection-patcher.ts — Converts patch payloads to MongoDB aggregation pipelines
44
+ └── validate-plugins.ts — Validator plugins for ObjectId and unique arrays
45
+ ```
46
+
47
+ ## Primitives
48
+
49
+ ### `mongo.objectId`
50
+
51
+ A string type constrained to `/^[a-fA-F0-9]{24}$/`. Used for MongoDB `_id` fields.
52
+
53
+ ```atscript
54
+ export interface User {
55
+ _id: mongo.objectId
56
+ name: string
57
+ }
58
+ ```
59
+
60
+ ### `mongo.vector`
61
+
62
+ An alias for `number[]`. Used for vector search fields.
63
+
64
+ ```atscript
65
+ export interface Document {
66
+ embedding: mongo.vector
67
+ }
68
+ ```
69
+
70
+ ## Regenerating atscript.d.ts
71
+
72
+ After annotation changes, regenerate the type declarations:
73
+
74
+ ```bash
75
+ cd packages/mongo && node ../typescript/dist/cli.cjs -f dts
76
+ ```
77
+
78
+ ## Best Practices
79
+
80
+ - Always use `@db.table` to name your collections — it's required by `AsCollection`
81
+ - `@db.mongo.collection` is optional — it only auto-injects `_id: mongo.objectId` if missing
82
+ - Use `mongo.objectId` type for `_id` fields when you want ObjectId-based IDs
83
+ - Use `string` type for `_id` when you want string-based IDs
@@ -0,0 +1,205 @@
1
+ # Patch Strategies — @atscript/mongo
2
+
3
+ > How `@db.mongo.patch.strategy` and array patch operations work.
4
+
5
+ ## Overview
6
+
7
+ When updating documents via `AsCollection.update()`, the `CollectionPatcher` converts your patch payload into MongoDB aggregation pipeline stages. The behavior depends on two things:
8
+
9
+ 1. **`@db.mongo.patch.strategy`** on objects — controls whether nested objects are replaced or merged
10
+ 2. **Array key fields** (`@expect.array.key`) and patch operations — controls how array elements are matched and modified
11
+
12
+ ## Object Patch Strategies
13
+
14
+ ### Default (no annotation) — Replace
15
+
16
+ Without `@db.mongo.patch.strategy`, nested objects are fully replaced:
17
+
18
+ ```atscript
19
+ @db.table 'users'
20
+ export interface User {
21
+ address: {
22
+ line1: string
23
+ city: string
24
+ zip: string
25
+ }
26
+ }
27
+ ```
28
+
29
+ ```typescript
30
+ // This replaces the entire address object
31
+ await users.update({
32
+ _id: id,
33
+ address: { line1: '123 Main St', city: 'NYC', zip: '10001' },
34
+ })
35
+ ```
36
+
37
+ ### `@db.mongo.patch.strategy 'replace'`
38
+
39
+ Explicit replacement — same as default. The entire nested object is overwritten.
40
+
41
+ ### `@db.mongo.patch.strategy 'merge'`
42
+
43
+ Individual fields within the nested object are updated without affecting unspecified fields:
44
+
45
+ ```atscript
46
+ @db.table 'users'
47
+ export interface User {
48
+ @db.mongo.patch.strategy 'merge'
49
+ contacts: {
50
+ email: string
51
+ phone: string
52
+ }
53
+ }
54
+ ```
55
+
56
+ ```typescript
57
+ // Only updates phone, email is preserved
58
+ await users.update({
59
+ _id: id,
60
+ contacts: { phone: '+1-555-0100' },
61
+ })
62
+ ```
63
+
64
+ ### Nested strategies
65
+
66
+ Strategies can be applied at any nesting level:
67
+
68
+ ```atscript
69
+ @db.table 'config'
70
+ export interface Config {
71
+ @db.mongo.patch.strategy 'merge'
72
+ settings: {
73
+ @db.mongo.patch.strategy 'replace'
74
+ theme: { primary: string, secondary: string }
75
+
76
+ @db.mongo.patch.strategy 'merge'
77
+ notifications: { email: boolean, push: boolean }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## Array Patch Operations
83
+
84
+ Top-level arrays in a patch payload use a structured format with operation keys.
85
+
86
+ ### `$replace`
87
+
88
+ Replaces the entire array:
89
+
90
+ ```typescript
91
+ await collection.update({
92
+ _id: id,
93
+ tags: { $replace: ['new', 'tags', 'only'] },
94
+ })
95
+ ```
96
+
97
+ ### `$insert`
98
+
99
+ Appends items to the array:
100
+
101
+ ```typescript
102
+ await collection.update({
103
+ _id: id,
104
+ tags: { $insert: ['newTag1', 'newTag2'] },
105
+ })
106
+ ```
107
+
108
+ If `@db.mongo.array.uniqueItems` is set, duplicates are silently dropped (uses `$setUnion`).
109
+
110
+ ### `$upsert`
111
+
112
+ Insert-or-update by key. For keyed arrays (`@expect.array.key`), removes existing elements matching the key(s) and appends the new ones:
113
+
114
+ ```atscript
115
+ @db.table 'products'
116
+ export interface Product {
117
+ items: {
118
+ @expect.array.key
119
+ sku: string
120
+ quantity: number
121
+ price: number
122
+ }[]
123
+ }
124
+ ```
125
+
126
+ ```typescript
127
+ await products.update({
128
+ _id: id,
129
+ items: {
130
+ $upsert: [
131
+ { sku: 'ABC', quantity: 10, price: 9.99 }, // replaces existing ABC or inserts
132
+ ],
133
+ },
134
+ })
135
+ ```
136
+
137
+ For non-keyed arrays, behaves like `$addToSet` (deep equality).
138
+
139
+ ### `$update`
140
+
141
+ Updates existing array elements matched by key:
142
+
143
+ ```typescript
144
+ await products.update({
145
+ _id: id,
146
+ items: {
147
+ $update: [
148
+ { sku: 'ABC', quantity: 20 }, // updates only quantity for sku=ABC
149
+ ],
150
+ },
151
+ })
152
+ ```
153
+
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.
155
+
156
+ ### `$remove`
157
+
158
+ Removes array elements:
159
+
160
+ ```typescript
161
+ // Keyed — removes by key match
162
+ await products.update({
163
+ _id: id,
164
+ items: {
165
+ $remove: [{ sku: 'ABC' }],
166
+ },
167
+ })
168
+
169
+ // Non-keyed — removes by deep equality
170
+ await collection.update({
171
+ _id: id,
172
+ tags: {
173
+ $remove: ['obsoleteTag'],
174
+ },
175
+ })
176
+ ```
177
+
178
+ ## Array Keys
179
+
180
+ Use `@expect.array.key` to mark fields that uniquely identify array elements:
181
+
182
+ ```atscript
183
+ export interface Translations {
184
+ entries: {
185
+ @expect.array.key
186
+ lang: string
187
+ @expect.array.key
188
+ key: string
189
+ value: string
190
+ }[]
191
+ }
192
+ ```
193
+
194
+ Multiple key fields form a composite key — elements are matched when ALL key fields match.
195
+
196
+ ## Implementation Details
197
+
198
+ The `CollectionPatcher` converts patch payloads into MongoDB aggregation pipeline stages using:
199
+ - `$reduce` + `$filter` + `$concatArrays` for keyed upsert/remove
200
+ - `$map` + `$cond` + `$mergeObjects` for keyed update with merge
201
+ - `$setUnion` for unique/non-keyed insert
202
+ - `$setDifference` for non-keyed remove
203
+ - `$concatArrays` for plain append
204
+
205
+ All operations are performed atomically in a single `updateOne()` call using an aggregation pipeline.