@owlmeans/mongo-resource 0.1.18-rc.20 → 0.1.18-rc.22
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 +215 -89
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/mongo-resource/SKILL.md +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,27 +1,46 @@
|
|
|
1
1
|
# @owlmeans/mongo-resource
|
|
2
2
|
|
|
3
|
-
MongoDB-backed `Resource<T>`
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
- Code migrations run automatically at resource initialization, tracked in a per-database `_owlmeans_migrations` ledger
|
|
12
|
-
- Used for all persistent data models in server applications
|
|
3
|
+
MongoDB-backed `Resource<T>` for server apps. The AJV schema becomes the collection validator, and
|
|
4
|
+
the package also handles indexes, code migrations and the conversion between string ids and
|
|
5
|
+
`ObjectId`. Use it for documents whose shape varies per record or per tenant and that are read
|
|
6
|
+
whole by id: per-tenant form submissions, generated content documents, compacted agent
|
|
7
|
+
conversations. Postgres, not Mongo, is the default for records you filter, join, sum or report
|
|
8
|
+
across (see [`@owlmeans/postgres-resource`](../postgres-resource)). Expiring or coordinating data
|
|
9
|
+
(sessions, locks, counters, pub/sub) goes in [`@owlmeans/redis-resource`](../redis-resource).
|
|
10
|
+
Client-side records go in [`@owlmeans/state`](../state).
|
|
13
11
|
|
|
14
12
|
## Installation
|
|
15
13
|
|
|
16
14
|
```bash
|
|
17
|
-
bun add @owlmeans/mongo-resource@^0.1.18-rc.
|
|
15
|
+
bun add @owlmeans/mongo-resource@^0.1.18-rc.21
|
|
18
16
|
```
|
|
19
17
|
|
|
18
|
+
`mongodb` and `ajv` are peer dependencies. The connection service comes from
|
|
19
|
+
[`@owlmeans/mongo`](../mongo) (`appendMongo`).
|
|
20
|
+
|
|
21
|
+
## Concepts
|
|
22
|
+
|
|
23
|
+
- **Resource maker**: a `ResourceMaker<R, T>` function that calls `makeMongoResource`, assigns
|
|
24
|
+
`schema` and declares indexes, references and migrations. The server context factory registers
|
|
25
|
+
its result once with `context.registerResource(makeXResource())`.
|
|
26
|
+
- **Validator**: the resource's AJV schema compiled into a `$jsonSchema` collection validator. It
|
|
27
|
+
is reapplied, and indexes are reconciled, on every boot.
|
|
28
|
+
- **Reference**: a field declared with `reference(field, targetAlias?)` that stores another
|
|
29
|
+
record's id. Records and criteria carry strings, the collection stores `ObjectId`s, and the
|
|
30
|
+
resource converts between them the same way it does for `_id`.
|
|
31
|
+
- **Migration**: a code body registered with `migration(name, apply, stage?)`. It runs once per
|
|
32
|
+
database at `Pre` or `Post` stage around structure reconciliation, and is recorded in the
|
|
33
|
+
`_owlmeans_migrations` ledger.
|
|
34
|
+
- **Declaration**: the module-scope, per-alias store that holds references and migrations. A maker
|
|
35
|
+
called twice for the same alias extends it; the second call does not replace it.
|
|
36
|
+
- **Paged by default**: `list(where)` returns at most `DEFAULT_PAGE_SIZE` (100) documents.
|
|
37
|
+
`{ size: 0 }` explicitly asks for all of them.
|
|
38
|
+
|
|
20
39
|
## Usage
|
|
21
40
|
|
|
22
|
-
Define a resource
|
|
41
|
+
### Define and register a resource
|
|
23
42
|
|
|
24
|
-
```
|
|
43
|
+
```ts
|
|
25
44
|
import { makeMongoResource } from '@owlmeans/mongo-resource'
|
|
26
45
|
import type { MongoResource } from '@owlmeans/mongo-resource'
|
|
27
46
|
import type { ResourceMaker } from '@owlmeans/resource'
|
|
@@ -32,70 +51,142 @@ export const makeProjectResource: ResourceMaker<ProjectRecord, ProjectResource>
|
|
|
32
51
|
const resource = makeMongoResource<ProjectRecord, ProjectResource>(RES_PROJECT, dbAlias, serviceAlias)
|
|
33
52
|
resource.schema = ProjectSchema
|
|
34
53
|
resource.index('entity', { entityId: 1 })
|
|
35
|
-
resource.index('alias', { alias: 1 })
|
|
54
|
+
resource.index('alias', { entityId: 1, alias: 1 }, { unique: true })
|
|
55
|
+
|
|
36
56
|
return resource
|
|
37
57
|
}
|
|
38
|
-
```
|
|
39
58
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
59
|
+
// in the server context factory, next to appendMongo(context)
|
|
43
60
|
context.registerResource(makeProjectResource())
|
|
44
61
|
```
|
|
45
62
|
|
|
46
|
-
|
|
63
|
+
### Read and write from a handler
|
|
47
64
|
|
|
48
|
-
```
|
|
65
|
+
```ts
|
|
49
66
|
const projects = context.resource<ProjectResource>(RES_PROJECT)
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
67
|
+
|
|
68
|
+
const created = await projects.create({ entityId, alias, title, status: 'draft' })
|
|
69
|
+
const one = await projects.load({ entityId, alias }) // null when absent
|
|
70
|
+
const newest = await projects.get({ entityId }, { sort: [{ field: 'createdAt', order: 'desc' }] })
|
|
71
|
+
const page = await projects.list(
|
|
72
|
+
{ entityId, status: ['draft', 'active'] }, // an array means "any of these"
|
|
73
|
+
{ page: 0, size: 20, sort: [{ field: 'createdAt', order: 'desc' }] }
|
|
74
|
+
)
|
|
75
|
+
const open = await projects.count({ entityId, status: { $ne: 'archived' } })
|
|
76
|
+
|
|
77
|
+
await projects.update({ ...created, title: 'Renamed' }) // replaces the whole record
|
|
78
|
+
await projects.purge({ entityId, status: 'archived' }) // refuses empty criteria
|
|
54
79
|
```
|
|
55
80
|
|
|
81
|
+
`entityId` here is the organization's stable record id, taken from `requireEntityKey(req)` in the
|
|
82
|
+
handler, never from the token. Only `entitySlug` travels on the wire.
|
|
83
|
+
|
|
84
|
+
### References, compound indexes and a domain method
|
|
85
|
+
|
|
86
|
+
A revisioned design document per story, modelled on a real application resource. `current` is a
|
|
87
|
+
domain method the app adds to its own resource interface:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
export interface DesignResource extends MongoResource<DesignRecord> {
|
|
91
|
+
current: (projectId: string, code: string) => Promise<DesignRecord | null>
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const makeDesignResource: ResourceMaker<DesignRecord, DesignResource> = (dbAlias, serviceAlias) => {
|
|
95
|
+
const resource = makeMongoResource<DesignRecord, DesignResource>(RES_DESIGN, dbAlias, serviceAlias)
|
|
96
|
+
|
|
97
|
+
resource.current = async (projectId, code) => {
|
|
98
|
+
const { items } = await resource.list(
|
|
99
|
+
{ projectId, code }, { size: 1, sort: [{ field: 'revision', order: 'desc' }] }
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return items[0] ?? null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
resource.schema = DesignSchema
|
|
106
|
+
resource.reference('projectId', RES_PROJECT) // stored as ObjectId, indexed ref_projectId
|
|
107
|
+
resource.reference('storyId', RES_STORY)
|
|
108
|
+
resource.index('current', { projectId: 1, code: 1, revision: -1 })
|
|
109
|
+
resource.index('story', { storyId: 1, revision: -1 }, { sparse: true })
|
|
110
|
+
|
|
111
|
+
return resource
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`code` is a business key and stays a plain string. Only fields assigned from another record's
|
|
116
|
+
`.id` are references.
|
|
117
|
+
|
|
118
|
+
### A migration
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { MigrationStage } from '@owlmeans/resource'
|
|
122
|
+
import type { MongoTx } from '@owlmeans/mongo-resource'
|
|
123
|
+
|
|
124
|
+
// Module scope, so the checksum fingerprints this body and nothing else.
|
|
125
|
+
const backfillStatus = async (tx: MongoTx) => {
|
|
126
|
+
await tx.collection.updateMany({ status: { $exists: false } }, { $set: { status: 'draft' } })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
resource.migration('0001-backfill-status', backfillStatus, MigrationStage.Post)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The body receives a `MongoTx` (`db`, `collection`, `use(alias)`, `ref(alias)`) and must be
|
|
133
|
+
idempotent: without transactions an interrupted body can run again.
|
|
134
|
+
|
|
135
|
+
### Raw driver access with converted references
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { marshalReference } from '@owlmeans/mongo-resource'
|
|
139
|
+
|
|
140
|
+
await projects.collection.updateOne(
|
|
141
|
+
{ _id: marshalReference('id', projectId) as never, jobSequence },
|
|
142
|
+
{ $inc: { jobSequence: 1 }, $set: { updatedAt: new Date() } }
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`resource.collection.*` bypasses the conversion layer. Marshal ids yourself, or stay on the
|
|
147
|
+
resource API.
|
|
148
|
+
|
|
56
149
|
## API
|
|
57
150
|
|
|
58
151
|
### `makeMongoResource<R, T>(alias, dbAlias?, serviceAlias?, collectionName?): T`
|
|
59
152
|
|
|
60
|
-
Creates a MongoDB resource. `dbAlias`
|
|
61
|
-
`collectionName` overrides the physical collection name (otherwise `resourcePrefix + alias
|
|
62
|
-
`migration()` and `reference()` are kept in module-scope declarations keyed by
|
|
63
|
-
maker that runs more than once for the same alias re-declares the same entries and
|
|
64
|
-
nothing.
|
|
153
|
+
Creates a MongoDB resource. `dbAlias` and `serviceAlias` default to `DEFAULT_DB_ALIAS` (`'mongo'`).
|
|
154
|
+
`collectionName` overrides the physical collection name (otherwise `resourcePrefix + alias`, limited
|
|
155
|
+
to `[a-zA-Z0-9_-]`). `migration()` and `reference()` are kept in module-scope declarations keyed by
|
|
156
|
+
alias, so a maker that runs more than once for the same alias re-declares the same entries and
|
|
157
|
+
loses nothing.
|
|
65
158
|
|
|
66
159
|
### `MongoResource<T>`
|
|
67
160
|
|
|
68
161
|
Extends `Resource<T>` with the shared `MigratableResource<MongoTx, MongoResource<T>>` and
|
|
69
162
|
`LockableResource<T>` capabilities, plus:
|
|
70
|
-
- `collection: Collection
|
|
163
|
+
- `collection: Collection`, the MongoDB collection
|
|
71
164
|
- `db(): Promise<Db>` / `client(): Promise<MongoClient>`
|
|
72
|
-
- `index(name, spec, options?): this
|
|
73
|
-
- `reference(field, targetAlias?): this` / `references()
|
|
74
|
-
- `migration(name, apply, stage?)` / `migrations()
|
|
75
|
-
- `lock(record, fields?)` / `unlock(record, fields?)
|
|
76
|
-
- `getDefaults(): Partial<T
|
|
165
|
+
- `index(name, spec, options?): this`, which defines a collection index
|
|
166
|
+
- `reference(field, targetAlias?): this` / `references()`, which declare that a field stores another record's id (see below)
|
|
167
|
+
- `migration(name, apply, stage?)` / `migrations()`, which register a code migration (see below)
|
|
168
|
+
- `lock(record, fields?)` / `unlock(record, fields?)`, which encrypt/decrypt `secure: true` schema fields
|
|
169
|
+
- `getDefaults(): Partial<T>`, the default values derived from the schema
|
|
170
|
+
- `dbAlias` / `serviceAlias`, the aliases the resource was registered against
|
|
77
171
|
|
|
78
172
|
### Criteria, sorting and paging
|
|
79
173
|
|
|
80
174
|
`Criteria<T>` is the portable query shape from [`@owlmeans/resource`](../resource): a bare value
|
|
81
175
|
is equality, a bare array is "any of these", `null` asks for the absence of a value and
|
|
82
176
|
`undefined` is skipped so an untouched filter never empties a list. Every operator in the shared
|
|
83
|
-
vocabulary
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
field *has a value*, not whether the key is present. An operator outside the
|
|
88
|
-
`UnsupportedArgumentError`.
|
|
89
|
-
|
|
90
|
-
Paging is per call: `list(where, { page, size, sort })`. Mongo pages by default
|
|
91
|
-
`DEFAULT_PAGE_SIZE` records when no `size` is given
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
addresses `_id`.
|
|
96
|
-
|
|
97
|
-
`criteriaToFilter(criteria, refs)` and `sortToMongo(sort)` are exported for code driving
|
|
98
|
-
`resource.collection` directly.
|
|
177
|
+
vocabulary is translated into the Mongo expression that answers the same question, so one criteria
|
|
178
|
+
object means the same thing here as it does against Postgres or an in-memory store. The vocabulary
|
|
179
|
+
is `$eq $ne $gt $gte $lt $lte $in $nin $exists $null $like $ilike $regex $startsWith $endsWith
|
|
180
|
+
$between $contains $contained $overlaps`, plus `$and`/`$or`/`$not`. `$exists` and `$null` both ask
|
|
181
|
+
whether the field *has a value*, not whether the key is present. An operator outside the
|
|
182
|
+
vocabulary raises `UnsupportedArgumentError`.
|
|
183
|
+
|
|
184
|
+
Paging is per call: `list(where, { page, size, sort })`. Mongo pages by default and returns
|
|
185
|
+
`DEFAULT_PAGE_SIZE` records when no `size` is given, because a caller should not get an unbounded
|
|
186
|
+
read of a collection by omission. `list(where, { size: 0 })` asks for the whole result set
|
|
187
|
+
explicitly, and a `page` without a `size` raises `UnsupportedArgumentError('page-without-size')`.
|
|
188
|
+
`ListResult.total` is always filled; `page` and `size` come back only when a limit was applied.
|
|
189
|
+
`sort` takes field names (ascending) or `{ field, order: 'desc' }`, and `id` addresses `_id`.
|
|
99
190
|
|
|
100
191
|
### ObjectId references
|
|
101
192
|
|
|
@@ -103,62 +194,97 @@ addresses `_id`.
|
|
|
103
194
|
The resource then treats the field exactly like `_id`:
|
|
104
195
|
|
|
105
196
|
- Records and criteria carry **strings**; the collection stores **`ObjectId`s**. Conversion is
|
|
106
|
-
automatic on every read, write and lookup
|
|
197
|
+
automatic on every read, write and lookup, including `$in`-style operator objects,
|
|
107
198
|
`$and`/`$or`/`$nor` branches and arrays of ids. `id` criteria are mapped onto `_id`.
|
|
108
|
-
- Writes are strict
|
|
109
|
-
|
|
110
|
-
- The field gets a
|
|
199
|
+
- Writes are strict: a non-24-hex value throws `MisshapedRecord`. Reads and criteria are tolerant:
|
|
200
|
+
a non-id value simply matches nothing.
|
|
201
|
+
- The field gets a Mongo-level index (`ref_<field>`) automatically, unless an index with the
|
|
111
202
|
identical key pattern is already declared, and the collection validator declares it
|
|
112
203
|
`objectId`.
|
|
113
|
-
- Declaring a reference registers the system migration `$ref:<field>@1` (pre stage)
|
|
114
|
-
converts pre-existing string ids in place
|
|
115
|
-
the collection is
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
- Only declare fields whose values really are another record's `id`. Business keys, composite
|
|
119
|
-
keys, external provider ids and slugs must stay strings — converting them corrupts data.
|
|
120
|
-
- Raw `resource.collection.*` access bypasses the conversion: marshal filter values with
|
|
121
|
-
`marshalReference(field, value)` and convert read-back ids to strings yourself.
|
|
204
|
+
- Declaring a reference registers the system migration `$ref:<field>@1` (pre stage), which
|
|
205
|
+
converts pre-existing string ids in place and is idempotent and interrupt-safe. On every boot
|
|
206
|
+
the collection is also probed for convertible strings and repaired if the ledger and the data
|
|
207
|
+
disagree (the double check). Conversion bypasses document validation, which requires the
|
|
208
|
+
`bypassDocumentValidation` privilege (`dbOwner`/`root` hold it).
|
|
122
209
|
|
|
123
210
|
### Migrations
|
|
124
211
|
|
|
125
|
-
`migration(name, apply, stage?)` registers a code migration
|
|
126
|
-
declaration order and recorded in the `_owlmeans_migrations` collection
|
|
127
|
-
database, so each database tracks its own
|
|
212
|
+
`migration(name, apply, stage?)` registers a code migration. It is applied once per database, in
|
|
213
|
+
declaration order, and recorded in the `_owlmeans_migrations` collection. The ledger is per
|
|
214
|
+
database, so each database tracks its own.
|
|
128
215
|
|
|
129
216
|
- `MigrationStage.Pre` runs before the validator/index update, `Post` after. On a collection
|
|
130
217
|
created by this very boot, registered migrations are **baselined** (recorded, not run).
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
the ledger claims-then-completes and an interrupted body may re-run.
|
|
134
|
-
- The checksum fingerprints the body's source text: keep bodies at module scope; an edited
|
|
135
|
-
applied body raises `MigrationConflict` at boot.
|
|
218
|
+
- A replica that loses the race to claim a migration waits for the winner (up to
|
|
219
|
+
`DEF_MIGRATION_WAIT`), and a failed body withdraws its claim so the next boot retries.
|
|
136
220
|
|
|
137
221
|
### `Resource<T>` methods (all implemented)
|
|
138
222
|
|
|
139
223
|
`get`, `load`, `list`, `count`, `create`, `update`, `save`, `delete`, `take`, `purge`
|
|
140
224
|
|
|
141
225
|
- `get`/`load` take either an id or a `Criteria<T>` with an optional `{ sort }`; `get` throws
|
|
142
|
-
`UnknownRecordError` where `load` answers `null`. An id that is not a
|
|
226
|
+
`UnknownRecordError` where `load` answers `null`. An id that is not a Mongo id finds nothing
|
|
143
227
|
rather than raising a driver error.
|
|
144
|
-
- `
|
|
145
|
-
carries no id and replaces otherwise.
|
|
228
|
+
- `create` refuses a caller-supplied id (`RecordExists`). `update` replaces the whole record
|
|
229
|
+
addressed by its `id`; `save` creates when the record carries no id and replaces otherwise.
|
|
146
230
|
- `delete(id)` and `take(id)` remove atomically through `findOneAndDelete` and hand the record
|
|
147
|
-
back
|
|
231
|
+
back. `take` throws `UnknownRecordError` on absence where `delete` answers `null`.
|
|
148
232
|
- `purge(where)` is the bulk delete; it refuses an empty criteria object rather than emptying
|
|
149
233
|
the collection.
|
|
150
|
-
- `ttl` is refused with `UnsupportedArgumentError
|
|
151
|
-
|
|
152
|
-
###
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
234
|
+
- `ttl` is refused with `UnsupportedArgumentError`, because a collection has no per-record expiry.
|
|
235
|
+
|
|
236
|
+
### Exports
|
|
237
|
+
|
|
238
|
+
| Symbol | Kind | Purpose |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `makeMongoResource` | function | The resource factory |
|
|
241
|
+
| `MongoResource<T>` | type | The resource interface described above |
|
|
242
|
+
| `MongoDbService` | type | Connection service contract implemented by `@owlmeans/mongo` |
|
|
243
|
+
| `MongoTx` | type | Façade handed to migration bodies: `db`, `collection`, `use(alias)`, `ref(alias)` |
|
|
244
|
+
| `MongoReference`, `MongoRefOptions` | type | A declared reference and the `reference()` options (`resource`, `noIndex`) |
|
|
245
|
+
| `criteriaToFilter(criteria, refs)` | function | `Criteria<T>` to a Mongo filter, with references converted |
|
|
246
|
+
| `sortToMongo(sort)` | function | `Sort<T>[]` to a Mongo sort document (`id` becomes `_id`) |
|
|
247
|
+
| `marshalReference(field, value)` | function | String id(s) to `ObjectId` for a write; throws `MisshapedRecord` on non-ids |
|
|
248
|
+
| `demarshalReference(value)`, `demarshalRefs(record, refs)` | function | `ObjectId` back to strings for one value or a whole document |
|
|
249
|
+
| `marshalCriteria(filter, refs)`, `identityCriteria(field, id, refs)` | function | Convert a Mongo filter's id-addressed values; build a single-record lookup |
|
|
250
|
+
| `isObjectIdHex(value)` | function | Strict 24-hex test used by the conversion layer |
|
|
251
|
+
| `convertReferenceField`, `makeRefMigration`, `reconcileReferences`, `refMigrationName` | function | The system reference migration and its boot-time probe |
|
|
252
|
+
| `makeMongoTx`, `makeMongoMigrationStore` | function | The migration façade and the ledger implementation |
|
|
253
|
+
| `getDeclaration(alias)`, `resetDeclarations(alias?)`, `MongoDeclaration` | function / type | Module-scope per-alias declarations; `resetDeclarations` is the testing seam |
|
|
254
|
+
| `getSchemaSecureFeilds(schema)` | function | The `secure: true` properties `lock`/`unlock` use when no fields are named |
|
|
255
|
+
| `DEFAULT_DB_ALIAS` | const | `'mongo'` |
|
|
256
|
+
| `DEFAULT_PAGE_SIZE` | const | `100` |
|
|
257
|
+
| `DEF_MIGRATIONS_COLLECTION` | const | `'_owlmeans_migrations'` |
|
|
258
|
+
| `DEF_MIGRATION_WAIT`, `DEF_MIGRATION_POLL` | const | How long (60 000 ms) and how often (250 ms) a replica waits for another's migration |
|
|
259
|
+
| `MONGO_DUPLICATE_KEY` | const | `11000`, the driver's duplicate-key code |
|
|
260
|
+
|
|
261
|
+
## Common pitfalls
|
|
262
|
+
|
|
263
|
+
- **Declaring a non-id field as a reference.** `entityId`/`entitySlug` (the organization entity),
|
|
264
|
+
composite keys such as `profileId`, provider ids (Stripe, GitHub), slugs, aliases and codes must
|
|
265
|
+
stay strings. Converting them corrupts the collection and breaks unique indexes.
|
|
266
|
+
- **Two indexes over the same keys.** Mongo refuses them. Declare the index yourself under the key
|
|
267
|
+
pattern a reference would use and the automatic `ref_<field>` index is skipped.
|
|
268
|
+
- **Non-idempotent migration bodies.** There are no multi-document transactions on a standalone
|
|
269
|
+
`mongod`, so an interrupted body re-runs.
|
|
270
|
+
- **Editing an applied migration**, or defining its body inside a loop or closure. The checksum
|
|
271
|
+
fingerprints source text, so the boot fails with `MigrationConflict`.
|
|
272
|
+
- **A `Pre` body that `$unset`s a field under a unique index.** Indexes reconcile *after* `Pre`, so
|
|
273
|
+
the old index is still enforcing and the second document fails with E11000. Drop the stale index
|
|
274
|
+
in the body first.
|
|
275
|
+
- **Expecting `update` to merge.** It replaces the whole document. Pass every field that must survive.
|
|
276
|
+
- **Reading "everything" with `list(where)`.** It stops at 100. Ask with `{ size: 0 }`, or page.
|
|
277
|
+
- **Raw `resource.collection` calls with string ids.** They match nothing against `ObjectId`
|
|
278
|
+
fields. Use `marshalReference`.
|
|
279
|
+
- **Passing `{ ttl }`.** It is refused. Expiring records belong in Redis.
|
|
280
|
+
|
|
281
|
+
## Related packages
|
|
282
|
+
|
|
283
|
+
- [`@owlmeans/mongo`](../mongo): the MongoDB connection service this package resolves through
|
|
284
|
+
- [`@owlmeans/resource`](../resource): `Resource<T>`, criteria, `ResourceMaker`, the migration framework, errors
|
|
285
|
+
- [`@owlmeans/postgres-resource`](../postgres-resource): the relational counterpart and the default choice
|
|
286
|
+
- [`@owlmeans/redis-resource`](../redis-resource): expiring, cached and pub/sub data
|
|
287
|
+
- [`@owlmeans/server-context`](../server-context): the server context resources register on
|
|
162
288
|
|
|
163
289
|
<!-- owlmeans:agent-guidance:start -->
|
|
164
290
|
## Agent guidance
|
|
@@ -168,7 +294,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
168
294
|
your project's skill store (`.agents/skills/`):
|
|
169
295
|
|
|
170
296
|
```sh
|
|
171
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
297
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.22
|
|
172
298
|
```
|
|
173
299
|
|
|
174
300
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/mongo-resource",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.22",
|
|
5
|
+
"generatedAt": "2026-09-15T12:38:15.178Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/mongo-resource
|
|
9
9
|
|
|
10
10
|
**Layer:** Infra
|
|
11
|
-
**Install:** `"@owlmeans/mongo-resource": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/mongo-resource": "^0.1.18-rc.22"` in `dependencies` (peers `mongodb`, `ajv`)
|
|
12
12
|
|
|
13
13
|
The Mongo counterpart of [[postgres-resource]]. A collection has no structure of its own, so
|
|
14
14
|
here the resource layer owns the *validator* (`$jsonSchema` from the AJV schema), the indexes,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/mongo-resource",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.22",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"mongodb": "*"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/resource": "^0.1.18-rc.
|
|
31
|
-
"@owlmeans/server-context": "^0.1.18-rc.
|
|
29
|
+
"@owlmeans/context": "^0.1.18-rc.18",
|
|
30
|
+
"@owlmeans/resource": "^0.1.18-rc.19",
|
|
31
|
+
"@owlmeans/server-context": "^0.1.18-rc.22"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@owlmeans/dep-config": "workspace:*",
|
|
35
|
-
"@owlmeans/test-integration": "^0.1.18-rc.
|
|
35
|
+
"@owlmeans/test-integration": "^0.1.18-rc.18",
|
|
36
36
|
"@types/bun": "^1.4.0",
|
|
37
37
|
"@types/node": "^26.1.0",
|
|
38
38
|
"mongodb": "^7.5.0",
|