@owlmeans/mongo 0.1.18-rc.3 → 0.1.18-rc.31

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 CHANGED
@@ -6,13 +6,13 @@ MongoDB service for OwlMeans server contexts — connection management with repl
6
6
 
7
7
  - `makeMongoDbService(alias?)` — creates a MongoDB connection service
8
8
  - `appendMongo(context, alias?)` — registers the service in the context
9
- - Reads connection config from `context.cfg.dbs[alias]` (supports `kluster:` directives)
9
+ - Reads connection config from the `dbs` entry whose alias matches (supports `kluster:` directives)
10
10
  - Used as the database provider for `@owlmeans/mongo-resource`
11
11
 
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- bun add @owlmeans/mongo
15
+ bun add @owlmeans/mongo@^0.1.18-rc.31
16
16
  ```
17
17
 
18
18
  ## Usage
@@ -24,16 +24,20 @@ import { appendMongo, DEFAULT_ALIAS as MONGO_SERVICE } from '@owlmeans/mongo'
24
24
  appendMongo<C, T>(context)
25
25
  ```
26
26
 
27
- Config (`config.json`):
27
+ Config (`config.json`) — `dbs` is a list, and `schema` names the database every resource on this
28
+ connection reads and writes (falling back to the config `alias`, then the service alias):
28
29
 
29
30
  ```json
30
31
  {
31
- "dbs": {
32
- "mongo": {
33
- "url": "mongodb://localhost:27017",
34
- "dbName": "myapp"
32
+ "dbs": [
33
+ {
34
+ "service": "mongo",
35
+ "alias": "mongo",
36
+ "host": "localhost",
37
+ "port": 27017,
38
+ "schema": "myapp"
35
39
  }
36
- }
40
+ ]
37
41
  }
38
42
  ```
39
43
 
@@ -66,7 +70,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
66
70
  your project's skill store (`.agents/skills/`):
67
71
 
68
72
  ```sh
69
- npx @owlmeans/agent-skills
73
+ npx @owlmeans/agent-skills@^0.1.18-rc.33
70
74
  ```
71
75
 
72
76
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/mongo",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.509Z",
4
+ "version": "0.1.18-rc.31",
5
+ "generatedAt": "2026-09-22T17:37:10.919Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mongo
3
- description: How to use @owlmeans/mongo — MongoDB connection service (makeMongoDbService / appendMongo) registered on a server context; cluster setup, layer sensitivity, field encryption backend. Auto-invoked when wiring MongoDB into a server app.
3
+ description: How to use @owlmeans/mongo — MongoDB connection service (makeMongoDbService / appendMongo) registered on a server context; cluster setup, field encryption backend. Auto-invoked when wiring MongoDB into a server app.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/mongo
9
9
 
10
10
  **Layer:** Infra
11
- **Install:** `"@owlmeans/mongo": "^0.1.18-rc.0"` in `dependencies` (peer `mongodb`)
11
+ **Install:** `"@owlmeans/mongo": "^0.1.18-rc.31"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
@@ -16,7 +16,8 @@ user-invocable: false
16
16
  |--------|-------------|
17
17
  | `makeMongoDbService(alias?)` | Factory for the MongoDB connection service (implements `MongoDbService` from [[mongo-resource]]). |
18
18
  | `appendMongo(context, alias?)` | Register the service on a server context. Default alias `'mongo'` (`DEFAULT_ALIAS`). |
19
- | `DEFAULT_ALIAS` | `'mongo'`. |
19
+ | `DEFAULT_ALIAS`, `DEF_REPLSET` | `'mongo'`; `'rs-main'`, the replica set an array `host` bootstraps. |
20
+ | `MongoMeta` | The `DbConfig.meta` shape this package reads — `{ replicaSet }`. |
20
21
 
21
22
  ## Usage
22
23
 
@@ -31,38 +32,70 @@ cfg.dbs = [{
31
32
  host: '127.0.0.1', // or string[] for a cluster — triggers replica set setup
32
33
  port: 27017,
33
34
  user: 'admin', secret: '...',
34
- schema: 'my-app', // the DATABASE name (layer-suffixed by dbName())
35
+ schema: 'my-app', // the DATABASE name
35
36
  encryptionKey: '...', // enables lock()/unlock() field encryption
36
- entitySensitive: true, // per-Entity-layer databases
37
+ meta: { replicaSet: 'rs-main' }, // only read for an array host
37
38
  }]
38
39
  ```
39
40
 
40
- - The service lazily creates one `MongoClient` per config alias; an array `host` runs the
41
- replica-set bootstrap (`setUpCluster`) first.
41
+ - The service lazily creates one `MongoClient` per config alias. An array `host` bootstraps the
42
+ replica set first — `replSetInitiate` when the set was never configured, a forced
43
+ `replSetReconfig` when the members' addresses moved, up to three attempts — then reconnects
44
+ through `?replicaSet=<meta.replicaSet ?? DEF_REPLSET>`.
42
45
  - `lock`/`unlock` encrypt/decrypt record fields with `encryptionKey` via
43
46
  `@owlmeans/basic-keys` — the backend behind `MongoResource.lock()`.
44
- - Layer sensitivity: `serviceSensitive`/`entitySensitive` add `Layer.Service`/`Layer.Entity`
45
- to the service's layers, which makes `dbName()` derive per-layer database names — each
46
- such database carries its own data **and its own migration ledger**.
47
+ - **`user` decides how the connection string is assembled.** With `user` set, a single `host`
48
+ becomes `mongodb://<host>[:<port>]` and the client gets `directConnection: true`, which is what a
49
+ standalone node needs: advertising a replica set instead would block the driver on server
50
+ selection looking for a primary that never appears. `meta.replicaSet` is read only on the
51
+ multi-host path.
52
+ - **An entry with no `user` is passed through verbatim.** No scheme is prepended, `port` is not
53
+ appended and no client options are set at all — so such an entry must spell the whole connection
54
+ string in `host` (`mongodb://127.0.0.1:27017/?directConnection=true`). A bare `127.0.0.1` reaches
55
+ the driver as-is and is rejected for having no scheme.
56
+ - The database name is `config.schema ?? config.alias ?? service.alias`, taken as given — the
57
+ service's `name(alias?)` returns it. One database per config entry, each carrying its own data
58
+ **and its own migration ledger**; a second database is a second `cfg.dbs` entry.
47
59
 
48
60
  ## Tests
49
61
 
50
62
  This package hosts the integration suites for the whole Mongo pair (a devDependency in the
51
- other direction would be a cycle): `tests/migration.spec.ts` (ledger end to end),
52
- `tests/references.spec.ts` (ObjectId reference conversion, system `$ref:` migration, drift
53
- repair). Gated on `MONGO_URL` — see [[testing-integration]]; the repo `.env` expects a
54
- port-forward to the cluster mongo.
63
+ other direction would be a cycle): `tests/crud.spec.ts` (the `Resource` contract against a real
64
+ collection), `tests/migration.spec.ts` (ledger end to end), `tests/references.spec.ts` (ObjectId
65
+ reference conversion, system `$ref:` migration, drift repair). Gated on `MONGO_URL` — see
66
+ [[testing-integration]]; a dev port-forward to the cluster mongo fills the `MONGO_URL` the
67
+ repo's `.env.example` describes.
55
68
 
56
69
  ## Depends On
57
70
 
58
71
  - `@owlmeans/resource` (`createDbService`) · `@owlmeans/mongo-resource` (service contract)
72
+ - `@owlmeans/context` · `@owlmeans/server-context` — the service lifecycle and the context it asserts
59
73
  - `@owlmeans/basic-keys` — field encryption
60
- - peer `mongodb` (currently `^7.5.0`)
74
+ - `mongodb` (`^7.5.0`) — a direct dependency, not a peer: the driver is resolved here and the
75
+ resource package takes it as a peer
61
76
 
62
- An app consuming this package under Bun must carry `"bson": "7.2.0"` in its root `overrides`, as
63
- this repo does. `bson >= 7.3.0` throws `ERR_NOT_IMPLEMENTED` (`v8.startupSnapshot.isBuildingSnapshot`)
64
- the moment `mongodb` is imported, on every Bun through 1.3.14 — so the server never starts. The pin
65
- is inside the driver's declared `bson: ^7.2.0` range. Details in the `bun` skill.
77
+ ## Runtime floor
78
+
79
+ On Bun, loading this package requires **Bun 1.4.0 or newer**, and that floor is the whole
80
+ requirement — `bson` then resolves freely inside the driver's own `^7.2.0` range and needs no
81
+ override or pin. `bson@7.3.x` runs a static initializer that calls
82
+ `process.getBuiltinModule('v8').startupSnapshot.isBuildingSnapshot()`, which Bun implements from
83
+ 1.4.0 on. Below that, `import 'mongodb'` throws `NotImplementedError: node:v8 isBuildingSnapshot is
84
+ not yet implemented in Bun` before any application code runs, so the symptom is a process that
85
+ never starts rather than a query that misbehaves. Every runtime that loads the driver counts: the
86
+ local shell, CI, and the container image.
87
+
88
+ An app that cannot raise its runtime pins the older bson in its own root manifest instead:
89
+
90
+ ```json
91
+ { "overrides": { "bson": "7.2.0" } }
92
+ ```
93
+
94
+ Check the runtime, not the lockfile, before lowering one:
95
+
96
+ ```bash
97
+ bun -e "import('mongodb').then(() => console.log('OK')).catch(e => console.log(e.message))"
98
+ ```
66
99
 
67
100
  ## Related
68
101
 
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAK3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAI9D,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE,eAAO,MAAM,kBAAkB,WAAW,MAAM,KAAmB,cA2HlE,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WACpE,CAAC,UAAS,MAAM,KACxB,CAMF,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAK3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAI9D,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE,eAAO,MAAM,kBAAkB,WAAW,MAAM,KAAmB,cAuGlE,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WACpE,CAAC,UAAS,MAAM,KACxB,CAMF,CAAA"}
package/build/service.js CHANGED
@@ -1,4 +1,4 @@
1
- import { assertContext, Layer } from '@owlmeans/context';
1
+ import { assertContext } from '@owlmeans/context';
2
2
  import { DEFAULT_ALIAS } from './consts.js';
3
3
  import { MongoClient } from 'mongodb';
4
4
  import { prepareConfig } from './utils/config.js';
@@ -19,15 +19,6 @@ export const makeMongoDbService = (alias = DEFAULT_ALIAS) => {
19
19
  if (service.clients[configAlias] != null) {
20
20
  return;
21
21
  }
22
- if (service.layers == null) {
23
- service.layers = [Layer.Global];
24
- }
25
- if (config.serviceSensitive && service.layers.includes(Layer.Service)) {
26
- service.layers.push(Layer.Service);
27
- }
28
- if (config.entitySensitive && service.layers.includes(Layer.Entity)) {
29
- service.layers.push(Layer.Entity);
30
- }
31
22
  let [url, options] = prepareConfig(config);
32
23
  let client = new MongoClient(url, options);
33
24
  if (Array.isArray(config.host)) {
@@ -78,12 +69,6 @@ export const makeMongoDbService = (alias = DEFAULT_ALIAS) => {
78
69
  field,
79
70
  fields.includes(field) ? await key.decrypt(value).catch(() => value) : value
80
71
  ])));
81
- },
82
- reinitializeContext: (context) => {
83
- const _service = makeMongoDbService(alias);
84
- _service.ctx = context;
85
- _service.layers = service.layers;
86
- return _service;
87
72
  }
88
73
  }, service => async () => {
89
74
  const context = assertContext(service.ctx, location);
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/mongo",
3
- "version": "0.1.18-rc.3",
3
+ "version": "0.1.18-rc.31",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -23,19 +23,19 @@
23
23
  },
24
24
  "devDependencies": {
25
25
  "@owlmeans/dep-config": "workspace:*",
26
- "@owlmeans/test-integration": "^0.1.18-rc.3",
27
- "@types/bun": "^1.3.14",
26
+ "@owlmeans/test-integration": "^0.1.18-rc.26",
27
+ "@types/bun": "^1.4.0",
28
28
  "@types/node": "^26.1.0",
29
29
  "nodemon": "^3.1.14",
30
30
  "typescript": "^7.0.2"
31
31
  },
32
32
  "dependencies": {
33
33
  "@noble/hashes": "^1.5.0",
34
- "@owlmeans/basic-keys": "^0.1.18-rc.3",
35
- "@owlmeans/context": "^0.1.18-rc.3",
36
- "@owlmeans/mongo-resource": "^0.1.18-rc.3",
37
- "@owlmeans/resource": "^0.1.18-rc.3",
38
- "@owlmeans/server-context": "^0.1.18-rc.3",
34
+ "@owlmeans/basic-keys": "^0.1.18-rc.34",
35
+ "@owlmeans/context": "^0.1.18-rc.29",
36
+ "@owlmeans/mongo-resource": "^0.1.18-rc.32",
37
+ "@owlmeans/resource": "^0.1.18-rc.31",
38
+ "@owlmeans/server-context": "^0.1.18-rc.32",
39
39
  "@scure/base": "^2.3.0",
40
40
  "mongodb": "^7.5.0"
41
41
  },
package/src/service.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { assertContext, Layer } from '@owlmeans/context'
2
- import type { BasicContext } from '@owlmeans/context'
1
+ import { assertContext } from '@owlmeans/context'
3
2
  import { DEFAULT_ALIAS } from './consts.js'
4
3
  import type { ServerContext, ServerConfig } from '@owlmeans/server-context'
5
4
  import { MongoClient } from 'mongodb'
@@ -33,16 +32,6 @@ export const makeMongoDbService = (alias: string = DEFAULT_ALIAS): MongoDbServic
33
32
  return
34
33
  }
35
34
 
36
- if (service.layers == null) {
37
- service.layers = [Layer.Global]
38
- }
39
- if (config.serviceSensitive && service.layers.includes(Layer.Service)) {
40
- service.layers.push(Layer.Service)
41
- }
42
- if (config.entitySensitive && service.layers.includes(Layer.Entity)) {
43
- service.layers.push(Layer.Entity)
44
- }
45
-
46
35
  let [url, options] = prepareConfig(config)
47
36
 
48
37
  let client = new MongoClient(url, options)
@@ -112,16 +101,6 @@ export const makeMongoDbService = (alias: string = DEFAULT_ALIAS): MongoDbServic
112
101
  ]
113
102
  ))
114
103
  )
115
- },
116
-
117
- reinitializeContext: <T>(context: BasicContext<ServerConfig>) => {
118
- const _service = makeMongoDbService(alias)
119
-
120
- _service.ctx = context
121
-
122
- _service.layers = service.layers
123
-
124
- return _service as T
125
104
  }
126
105
  }, service => async () => {
127
106
  const context = assertContext<Config, Context>(service.ctx as Context, location)
@@ -0,0 +1,153 @@
1
+ import { afterAll, describe, expect, test } from 'bun:test'
2
+ import { makeMongoResource } from '@owlmeans/mongo-resource'
3
+ import { UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource'
4
+
5
+ import { gate, makeSuite } from './context.js'
6
+ import type { MongoResource, Note } from './context.js'
7
+
8
+ const noteSchema = {
9
+ type: 'object',
10
+ properties: {
11
+ title: { type: 'string' },
12
+ slug: { type: 'string', nullable: true }
13
+ },
14
+ required: ['title']
15
+ }
16
+
17
+ const suite = makeSuite('crud')
18
+ const it = gate.skip ? test.skip : test
19
+
20
+ /** One alias — and so one collection — per test, so the sets never interfere. */
21
+ const notes = async (alias: string): Promise<MongoResource<Note>> => {
22
+ const resource = makeMongoResource<Note, MongoResource<Note>>(alias)
23
+ resource.schema = noteSchema as never
24
+ const { context } = await suite.boot({ resources: [resource] })
25
+
26
+ return context.resource<MongoResource<Note>>(alias)
27
+ }
28
+
29
+ describe('@owlmeans/mongo — the resource contract', () => {
30
+ if (gate.skip) {
31
+ test.skip(gate.reason ?? 'mongo gate closed', () => {})
32
+
33
+ return
34
+ }
35
+
36
+ afterAll(async () => {
37
+ await suite.teardown()
38
+ })
39
+
40
+ it('reads one record by id and by criteria', async () => {
41
+ const res = await notes('crud-read')
42
+ const note = await res.create({ title: 'first', slug: 'one' })
43
+ expect(typeof note.id).toBe('string')
44
+
45
+ expect((await res.get(note.id!)).title).toBe('first')
46
+ /** Several fields at once, without listing and taking the first element. */
47
+ expect((await res.get({ title: 'first', slug: 'one' })).id).toBe(note.id!)
48
+ expect(await res.load({ slug: 'nope' })).toBeNull()
49
+ })
50
+
51
+ it('answers an id that is not a mongo id rather than throwing a driver error', async () => {
52
+ const res = await notes('crud-badid')
53
+
54
+ expect(await res.load('not-an-object-id')).toBeNull()
55
+ await expect(res.get('not-an-object-id')).rejects.toThrow(UnknownRecordError)
56
+ })
57
+
58
+ it('pages and sorts, and always reports the total', async () => {
59
+ const res = await notes('crud-page')
60
+ for (const title of ['c', 'a', 'd', 'b']) {
61
+ await res.create({ title })
62
+ }
63
+
64
+ const first = await res.list({}, { size: 2, sort: ['title'] })
65
+ expect(first.items.map(note => note.title)).toEqual(['a', 'b'])
66
+ expect(first.total).toBe(4)
67
+ expect(first.page).toBe(0)
68
+ expect(first.size).toBe(2)
69
+
70
+ const second = await res.list({}, { page: 1, size: 2, sort: ['title'] })
71
+ expect(second.items.map(note => note.title)).toEqual(['c', 'd'])
72
+ expect(second.total).toBe(4)
73
+
74
+ const newest = await res.list(undefined, { size: 1, sort: [{ field: 'title', order: 'desc' }] })
75
+ expect(newest.items.map(note => note.title)).toEqual(['d'])
76
+
77
+ /** `size: 0` is the explicit ask for the whole result set. */
78
+ const all = await res.list({}, { size: 0, sort: ['title'] })
79
+ expect(all.items).toHaveLength(4)
80
+ expect(all.total).toBe(4)
81
+ expect(all.size).toBeUndefined()
82
+ })
83
+
84
+ it('counts without reading the records', async () => {
85
+ const res = await notes('crud-count')
86
+ await res.create({ title: 'x', slug: 'keep' })
87
+ await res.create({ title: 'y', slug: 'keep' })
88
+ await res.create({ title: 'z', slug: 'drop' })
89
+
90
+ expect(await res.count()).toBe(3)
91
+ expect(await res.count({ slug: 'keep' })).toBe(2)
92
+ expect(await res.count({ slug: 'absent' })).toBe(0)
93
+ })
94
+
95
+ it('answers the shared operators the same way the other stores do', async () => {
96
+ const res = await notes('crud-ops')
97
+ await res.create({ title: 'Alpha', slug: 'a-1' })
98
+ await res.create({ title: 'beta', slug: 'b-2' })
99
+ await res.create({ title: 'gamma' })
100
+
101
+ expect((await res.list({ title: { $ilike: 'al%' } })).items.map(note => note.title))
102
+ .toEqual(['Alpha'])
103
+ expect((await res.list({ slug: { $startsWith: 'b-' } })).items.map(note => note.title))
104
+ .toEqual(['beta'])
105
+ expect((await res.list({ slug: { $null: true } })).items.map(note => note.title))
106
+ .toEqual(['gamma'])
107
+ expect((await res.list({ title: ['Alpha', 'beta'] })).total).toBe(2)
108
+ expect((await res.list({ $or: [{ title: 'Alpha' }, { slug: 'b-2' }] })).total).toBe(2)
109
+ expect((await res.list({ $not: { title: 'gamma' } })).total).toBe(2)
110
+ })
111
+
112
+ it('purges by criteria and refuses to empty the collection', async () => {
113
+ const res = await notes('crud-purge')
114
+ await res.create({ title: 'x', slug: 'drop' })
115
+ await res.create({ title: 'y', slug: 'drop' })
116
+ await res.create({ title: 'z', slug: 'keep' })
117
+
118
+ /** An empty criteria object would delete everything — one call replacing a page loop. */
119
+ await expect(res.purge({})).rejects.toThrow(UnsupportedArgumentError)
120
+
121
+ expect(await res.purge({ slug: 'drop' })).toBe(2)
122
+ expect(await res.count()).toBe(1)
123
+ })
124
+
125
+ it('takes a record out and hands it back, where delete tolerates absence', async () => {
126
+ const res = await notes('crud-take')
127
+ const note = await res.create({ title: 'once' })
128
+
129
+ expect((await res.take(note.id!)).title).toBe('once')
130
+ await expect(res.take(note.id!)).rejects.toThrow(UnknownRecordError)
131
+ expect(await res.delete(note.id!)).toBeNull()
132
+ })
133
+
134
+ it('creates without an id, replaces with one', async () => {
135
+ const res = await notes('crud-save')
136
+ const created = await res.save({ title: 'draft', slug: 'kept' })
137
+ expect(typeof created.id).toBe('string')
138
+
139
+ const replaced = await res.save({ id: created.id, title: 'final' })
140
+ expect(replaced.id).toBe(created.id!)
141
+ expect(replaced.title).toBe('final')
142
+ /** A replace, not a patch — the field the new record omits is gone from the document. */
143
+ expect(replaced.slug).toBeUndefined()
144
+ expect(await res.count()).toBe(1)
145
+ })
146
+
147
+ it('refuses an expiry it cannot honour', async () => {
148
+ const res = await notes('crud-ttl')
149
+
150
+ await expect(res.create({ title: 'ephemeral' }, { ttl: 60 })).rejects
151
+ .toThrow(UnsupportedArgumentError)
152
+ })
153
+ })
@@ -108,7 +108,7 @@ describe('@owlmeans/mongo — ObjectId references', () => {
108
108
  expect(listed.items.map(i => i.title)).toEqual(['a'])
109
109
  expect(listed.items[0].ownerId).toBe(owner.id!)
110
110
 
111
- const viaIn = await items.list({ ownerId: { $in: [owner.id!, other.id!] } } as never)
111
+ const viaIn = await items.list({ ownerId: { $in: [owner.id!, other.id!] } })
112
112
  expect(viaIn.items).toHaveLength(2)
113
113
 
114
114
  /** `id` criteria address `_id` — documents never store an `id` field. */
@@ -119,7 +119,8 @@ describe('@owlmeans/mongo — ObjectId references', () => {
119
119
  const none = await items.list({ ownerId: 'ext:not-an-id' })
120
120
  expect(none.items).toHaveLength(0)
121
121
 
122
- const loaded = await items.load(owner.id!, 'ownerId')
122
+ /** A criteria read addresses the reference field directly — no list to take a first from. */
123
+ const loaded = await items.load({ ownerId: owner.id! })
123
124
  expect(loaded?.title).toBe('a')
124
125
  })
125
126
 
@@ -150,7 +151,7 @@ describe('@owlmeans/mongo — ObjectId references', () => {
150
151
  expect((stored?.ownerIds as unknown[]).every(v => v instanceof ObjectId)).toBe(true)
151
152
 
152
153
  /** Multikey criteria hit the converted elements. */
153
- const listed = await items.list({ ownerIds: second.id! })
154
+ const listed = await items.list({ ownerIds: { $contains: [second.id!] } })
154
155
  expect(listed.items.map(i => i.title)).toEqual(['multi'])
155
156
  })
156
157
 
@@ -281,15 +282,16 @@ describe('@owlmeans/mongo — ObjectId references', () => {
281
282
  expect(updated.ownerId).toBe(next.id!)
282
283
 
283
284
  /** Addressing the record BY its reference field converts the lookup value too. */
284
- const byRef = await items.update({ ...updated, title: 'by-ref' }, 'ownerId')
285
+ const byRef = await items.update({ ...await items.get({ ownerId: next.id! }), title: 'by-ref' })
285
286
  expect(byRef.title).toBe('by-ref')
286
287
 
287
288
  const stored = await raw(async client =>
288
289
  await client.db(suite.database).collection('ref-upd').findOne({ _id: new ObjectId(item.id!) }))
289
290
  expect(stored?.ownerId).toBeInstanceOf(ObjectId)
290
291
 
291
- const gone = await items.delete(next.id!, 'ownerId')
292
- expect(gone?.title).toBe('by-ref')
292
+ /** And the delete-and-return reached through the same converted lookup. */
293
+ const gone = await items.take((await items.get({ ownerId: next.id! })).id!)
294
+ expect(gone.title).toBe('by-ref')
293
295
  })
294
296
 
295
297
  it('survives redeclaration and reboot without conflicts', async () => {