@nxgt/mongo-kit 0.1.4 → 0.2.0
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 +56 -4
- package/dist/config/checks.d.ts.map +1 -1
- package/dist/config/define-config.d.ts.map +1 -1
- package/dist/discover.d.ts.map +1 -1
- package/dist/errors/kit-error.d.ts +41 -0
- package/dist/errors/kit-error.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -30
- package/dist/index.js.map +11 -10
- package/dist/kit/context.d.ts.map +1 -1
- package/dist/kit/create-kit.d.ts.map +1 -1
- package/dist/kit/derive.d.ts.map +1 -1
- package/dist/kit/transaction.d.ts.map +1 -1
- package/docs/README.md +15 -0
- package/docs/guide/actor-and-transactions.md +196 -0
- package/docs/guide/configuration.md +230 -0
- package/docs/guide/db-scope.md +174 -0
- package/docs/guide/discover-collections.md +85 -0
- package/docs/guide/errors.md +198 -0
- package/docs/guide/sync.md +108 -0
- package/docs/roadmap.md +68 -0
- package/docs/troubleshooting.md +595 -0
- package/package.json +4 -3
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Every heading is the text the error prints, so the page can be searched with
|
|
4
|
+
what you have in front of you. Stacks, ids and paths are cut.
|
|
5
|
+
|
|
6
|
+
Everything this package refuses is a `KitError`, exported from
|
|
7
|
+
`@nxgt/mongo-kit`. It carries a `code` — `CONFIG`, `COLLISION`,
|
|
8
|
+
`NO_DATABASE`, `SEVERAL_DATABASES`, `TRANSACTION`, `DERIVED` or `DISCOVERY` —
|
|
9
|
+
beside the `database` and the `key` it is about, so a caller switches on the
|
|
10
|
+
code instead of matching the sentence. It extends `TypeError`, which these
|
|
11
|
+
were before 0.2.0, so a `catch` written against `TypeError` still catches
|
|
12
|
+
them. The driver's own errors, and `@nxgt/mongo`'s `DataError`s, reach you
|
|
13
|
+
unchanged.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { KitError } from '@nxgt/mongo-kit';
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await kit.transaction(work, { on: 'main' });
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error instanceof KitError) {
|
|
22
|
+
log.error({ code: error.code, database: error.database, key: error.key });
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Area | Entries |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| [Install](#install) | [ERESOLVE](#npm-error-eresolve-unable-to-resolve-dependency-tree) · [incorrect peer dependency](#warn-incorrect-peer-dependency-nxgtmongo0140) · [TS2307](#error-ts2307-cannot-find-module-nxgtmongo-or-its-corresponding-type-declarations) |
|
|
31
|
+
| [Types](#types) | [a key the `Db` has](#command-is-a-member-of-the-drivers-db-wire-this-collection-under-another-key) · [options for a key that is not wired](#posts-is-not-wired-by-this-database-there-are-no-options-for-it) |
|
|
32
|
+
| [Configuration](#configuration) | [no configuration at all](#defineconfig-a-configuration-object-is-required) · [`databases` is not an object of databases by name](#defineconfig-databases-must-be-an-object-of-databases-by-name-as--databases--main----one-database-is-the-configuration-itself-and-names-itself-with-database) · [neither a uri nor a client](#defineconfig-database-main-has-neither-a-uri-nor-a-client) · [both](#defineconfig-database-main-has-both-a-uri-and-a-client-pass-the-one-it-should-use) · [client options](#defineconfig-database-main-has-client-options-beside-a-client-it-did-not-open-pass-them-where-the-client-is-made) · [no definition in it](#defineconfig-database-main-has-a-collections-object-with-no-definition-in-it-pass-the-module-as-in-import--as-collections) · [two keys, one collection](#defineconfig-database-main-wires-users-and-people-to-the-same-collection-users) · [an option the kit decides](#defineconfig-database-main-has-session-in-options-which-the-kit-decides-) · [options for a key it does not wire](#defineconfig-database-main-has-options-for-posts-which-it-does-not-wire) · [no databases](#defineconfig-databases-names-none-give-it-at-least-one-as--databases--main---) |
|
|
33
|
+
| [Runtime](#runtime) | [a key the `Db` has, at creation](#createkit-database-main-wires-a-collection-under-command-which-is-a-member-of-the-drivers-db-it-would-be-unreachable-export-that-definition-under-another-name) · [ECONNREFUSED](#mongoserverselectionerror-connect-econnrefused-12700127017) · [one URI, two option sets](#connectmongo-this-uri-is-already-connected-with-other-options-pass-the-same-options-everywhere-or-close-the-first-connection) · [`kit.db` with several databases](#kitdb-this-kit-has-several-databases-read-the-one-you-mean-as-kitdatabasesmain) · [an unknown database](#this-kit-has-no-database-reporting-it-has-main-analytics) · [closing a derived kit](#close-this-kit-came-from-as-withsession-or-a-transaction-close-the-kit-createkit-returned--the-clients-are-shared) · [`sync()` and privileges](#not-authorized-on-app-to-execute-command--collmod-users--) |
|
|
34
|
+
| [Transactions](#transactions) | [more than one client](#transaction-this-kit-holds-more-than-one-client-and-a-transaction-lives-on-one-name-the-database-it-runs-on-as--on-main-) · [already in a session](#transaction-this-kit-is-already-in-a-session-which-this-call-joins-so-on-has-no-client-left-to-choose) · [a session from another client](#clientsession-must-be-from-the-same-mongoclient) · [no replica set](#this-mongodb-deployment-does-not-support-retryable-writes-please-add-retrywritesfalse-to-your-connection-string) |
|
|
35
|
+
| [Scripts](#scripts) | [`Bun is not defined`](#referenceerror-bun-is-not-defined) · [no glob](#discovercollections-a-glob-is-required) · [two files, one collection](#discovercollections-srcmodelsonemodelts-and-srcmodelstwomodelts-both-define-the-collection-twice) · [no definition of that name](#discovercollections-srcmodelsnotests-exports-no-definition-named-definition) |
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
### `npm error ERESOLVE unable to resolve dependency tree`
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
npm error Found: @nxgt/mongo@0.14.0
|
|
43
|
+
npm error Could not resolve dependency:
|
|
44
|
+
npm error peer @nxgt/mongo@"^0.15.0" from @nxgt/mongo-kit@0.1.4
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**When:** `npm install`, before anything is downloaded.
|
|
48
|
+
|
|
49
|
+
**Why:** `@nxgt/mongo` is a **required peer**, and the range is a caret on a
|
|
50
|
+
0.x version, so it accepts one minor only. The collections, their options and
|
|
51
|
+
their behaviour are `@nxgt/mongo`'s; this package only wires them, and the two
|
|
52
|
+
have to be the same copy. `mongodb` (`>=7.0.0 <8`) and `typescript` (`^6.0.3`)
|
|
53
|
+
are peers on the same terms.
|
|
54
|
+
|
|
55
|
+
**Fix:**
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
npm install @nxgt/mongo@^0.15.0 @nxgt/mongo-kit mongodb zod
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Raise the sibling rather than install past the conflict: `--force` and
|
|
62
|
+
`--legacy-peer-deps` leave two copies of `@nxgt/mongo` in the tree, and the
|
|
63
|
+
definitions one of them builds are not the ones the other wires.
|
|
64
|
+
|
|
65
|
+
### `warn: incorrect peer dependency "@nxgt/mongo@0.14.0"`
|
|
66
|
+
|
|
67
|
+
**When:** `bun install`, which prints it and carries on.
|
|
68
|
+
|
|
69
|
+
**Why:** bun does not fail on an unmet peer — it warns and installs what the
|
|
70
|
+
manifest asked for. The kit then runs against a `@nxgt/mongo` it was not built
|
|
71
|
+
against: a stamp option or a collection option added in the newer minor is
|
|
72
|
+
missing at run time, with no error until the call that needs it.
|
|
73
|
+
|
|
74
|
+
**Fix:**
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
bun add @nxgt/mongo@^0.15.0
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Treat that warning as an error. `bun pm ls` shows which version was resolved.
|
|
81
|
+
|
|
82
|
+
### `error TS2307: Cannot find module '@nxgt/mongo' or its corresponding type declarations.`
|
|
83
|
+
|
|
84
|
+
At run time the same tree gives
|
|
85
|
+
`Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@nxgt/mongo'` under Node,
|
|
86
|
+
and `error: Cannot find module '@nxgt/mongo'` under Bun.
|
|
87
|
+
|
|
88
|
+
**When:** the first build or the first import of your own code, typically
|
|
89
|
+
under pnpm or npm with a strict `node_modules` layout.
|
|
90
|
+
|
|
91
|
+
**Why:** a peer installed *for* the kit is not a dependency of **your**
|
|
92
|
+
package. pnpm hoists it under the kit alone, so `@nxgt/mongo-kit` resolves it
|
|
93
|
+
and your `import { defineCollection } from '@nxgt/mongo'` does not.
|
|
94
|
+
|
|
95
|
+
**Fix:**
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
bun add @nxgt/mongo @nxgt/mongo-kit mongodb zod
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Declare every peer you import from yourself — you import `@nxgt/mongo` as soon
|
|
102
|
+
as you write a `defineCollection`.
|
|
103
|
+
|
|
104
|
+
## Types
|
|
105
|
+
|
|
106
|
+
### `"command" is a member of the driver's Db: wire this collection under another key`
|
|
107
|
+
|
|
108
|
+
The whole line is a `TS2322`:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
error TS2322: Type 'CollectionDefinition<…>' is not assignable to type
|
|
112
|
+
'CollectionDefinition<…> & "\"command\" is a member of the driver's Db: wire this collection under another key"'.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**When:** compiling the file that calls `defineConfig`.
|
|
116
|
+
|
|
117
|
+
**Why:** `kit.db` is the driver's `Db` with the collections on it, so a key the
|
|
118
|
+
`Db` already answers to — `command`, `watch`, `collection`, `admin`,
|
|
119
|
+
`databaseName`… — would be unreachable, and reading it would give the driver's
|
|
120
|
+
member instead of your collection. The reserved names are read from the
|
|
121
|
+
driver's own type (`ReservedName = keyof Db`), so a member a later driver
|
|
122
|
+
release adds is refused the day the pin moves.
|
|
123
|
+
|
|
124
|
+
**Fix:** export the definition under another name.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// src/models/commands.model.ts
|
|
128
|
+
export const commandLog = defineCollection({ name: 'commands', schema });
|
|
129
|
+
// ^ the key on `kit.db`; `name` is the collection on the server
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The key is the **export name**, not the collection's `name`: only the export
|
|
133
|
+
name has to change.
|
|
134
|
+
|
|
135
|
+
### `"posts" is not wired by this database: there are no options for it`
|
|
136
|
+
|
|
137
|
+
**When:** compiling a `defineConfig` whose `optionsFor` names a key its
|
|
138
|
+
`collections` does not hold.
|
|
139
|
+
|
|
140
|
+
**Why:** `optionsFor` is keyed by the same export names as `collections`, and
|
|
141
|
+
options written for a key that is not wired would silently do nothing —
|
|
142
|
+
usually a renamed or moved model.
|
|
143
|
+
|
|
144
|
+
**Fix:**
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
defineConfig({
|
|
148
|
+
uri: process.env.MONGO_URI!,
|
|
149
|
+
collections, // `import * as collections from './models'`
|
|
150
|
+
optionsFor: { articles: { maxPageSize: 200 } }, // a key `collections` exports
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Configuration
|
|
155
|
+
|
|
156
|
+
`defineConfig` connects to nothing: everything below is a `KitError` with
|
|
157
|
+
`code: 'CONFIG'`, thrown where the configuration is written, before the
|
|
158
|
+
application starts. It names the database it is about as `error.database`,
|
|
159
|
+
and the collection key as `error.key` when one is at fault.
|
|
160
|
+
|
|
161
|
+
### `defineConfig: a configuration object is required`
|
|
162
|
+
|
|
163
|
+
**When:** calling `defineConfig` with nothing, `undefined` or `null` — usually
|
|
164
|
+
a config read from a module that exports it under another name, or a value
|
|
165
|
+
built at run time that came out empty.
|
|
166
|
+
|
|
167
|
+
**Why:** the configuration is read as an object before anything else is
|
|
168
|
+
checked, so there is nothing to name a database with. A `defineConfig(config)`
|
|
169
|
+
where `config` is `undefined` is most often an import that resolved to
|
|
170
|
+
`undefined`: a default export read as a named one, or two modules importing
|
|
171
|
+
each other, so one of them is still empty when the other runs.
|
|
172
|
+
|
|
173
|
+
**Fix:** write the config as a literal, where the compiler sees its shape:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import * as collections from './models';
|
|
177
|
+
|
|
178
|
+
export const config = defineConfig({ uri, collections });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### ``defineConfig: databases must be an object of databases by name, as `{ databases: { main: … } }`. One database is the configuration itself, and names itself with `database`.``
|
|
182
|
+
|
|
183
|
+
**When:** calling `defineConfig` with a `databases` that is a string, a number
|
|
184
|
+
or `null`.
|
|
185
|
+
|
|
186
|
+
**Why:** `databases` is the multi-database shape, and its **keys** are the
|
|
187
|
+
names — `databases: 'main'` looks like naming the one database, which is what
|
|
188
|
+
`database` does. A single database says `database`, several say `databases`.
|
|
189
|
+
|
|
190
|
+
**Fix:**
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
defineConfig({ uri, database: 'analytics', collections }); // one database
|
|
194
|
+
defineConfig({ databases: { main: { uri, collections } } }); // several
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`database` is the database on the server; the key under `databases` is the
|
|
198
|
+
name your code reads it by, as `kit.databases.main`.
|
|
199
|
+
|
|
200
|
+
### `defineConfig: database "main" has neither a uri nor a client`
|
|
201
|
+
|
|
202
|
+
**When:** calling `defineConfig`.
|
|
203
|
+
|
|
204
|
+
**Why:** a database says where it is exactly once. This is most often an
|
|
205
|
+
environment variable that was not read — `process.env.MONGO_URI` is
|
|
206
|
+
`undefined`, so the property is absent.
|
|
207
|
+
|
|
208
|
+
**Fix:**
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const uri = process.env.MONGO_URI;
|
|
212
|
+
if (!uri) throw new Error('MONGO_URI is not set');
|
|
213
|
+
export const config = defineConfig({ uri, collections });
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### `defineConfig: database "main" has both a uri and a client: pass the one it should use`
|
|
217
|
+
|
|
218
|
+
**When:** calling `defineConfig`.
|
|
219
|
+
|
|
220
|
+
**Why:** the two mean different things about closing: with a `uri` the kit
|
|
221
|
+
opens the client and `close()` gives it back, with a `client` it uses yours and
|
|
222
|
+
never closes it. It will not guess which you meant.
|
|
223
|
+
|
|
224
|
+
**Fix:**
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
defineConfig({ client, database: 'main', collections }); // yours to close
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### `defineConfig: database "main" has client options beside a client it did not open: pass them where the client is made`
|
|
231
|
+
|
|
232
|
+
**When:** calling `defineConfig` with both `client` and `clientOptions`.
|
|
233
|
+
|
|
234
|
+
**Why:** `clientOptions` is what the kit passes to the driver when it opens a
|
|
235
|
+
client. A client that is already open cannot take them, so they would be
|
|
236
|
+
ignored.
|
|
237
|
+
|
|
238
|
+
**Fix:**
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const client = new MongoClient(uri, { maxPoolSize: 50 }); // here
|
|
242
|
+
defineConfig({ client, collections });
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### `` defineConfig: database "main" has a collections object with no definition in it: pass the module, as in `import * as collections` ``
|
|
246
|
+
|
|
247
|
+
**When:** calling `defineConfig`.
|
|
248
|
+
|
|
249
|
+
**Why:** the object holds no value that looks like a `defineCollection` —
|
|
250
|
+
usually a module of *types* only, a default export, or a barrel whose files
|
|
251
|
+
export builders rather than definitions.
|
|
252
|
+
|
|
253
|
+
**Fix:**
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
// src/models/index.ts
|
|
257
|
+
export * from './users.model'; // `export const users = defineCollection(…)`
|
|
258
|
+
export * from './posts.model';
|
|
259
|
+
|
|
260
|
+
// src/db.ts
|
|
261
|
+
import * as collections from './models';
|
|
262
|
+
defineConfig({ uri, collections });
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Anything in that module that is not a definition — a function, a constant, a
|
|
266
|
+
type — is left out of the scope rather than refused.
|
|
267
|
+
|
|
268
|
+
### `defineConfig: database "main" wires "users" and "people" to the same collection, "users"`
|
|
269
|
+
|
|
270
|
+
**When:** calling `defineConfig`.
|
|
271
|
+
|
|
272
|
+
**Why:** two exports carry definitions with the same `name`, so two keys on
|
|
273
|
+
`kit.db` would write to one server collection with two schemas and two sets of
|
|
274
|
+
options. Usually a copy-and-pasted model whose `name` was not changed.
|
|
275
|
+
|
|
276
|
+
**Fix:**
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
export const people = defineCollection({ name: 'people', schema });
|
|
280
|
+
// ^ one `name` per collection
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### `defineConfig: database "main" has "session" in options, which the kit decides: …`
|
|
284
|
+
|
|
285
|
+
The full message names what to use instead:
|
|
286
|
+
|
|
287
|
+
```text
|
|
288
|
+
defineConfig: database "main" has "session" in options, which the kit decides:
|
|
289
|
+
a database is named by its key, `as` and `withSession` carry the actor and the
|
|
290
|
+
session, and `autoSync` is the database's
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The same refusal covers `db`, `actor` and `autoSync`, in `options` and under
|
|
294
|
+
`optionsFor` — where the text reads `has "session" in the options of "users"`.
|
|
295
|
+
|
|
296
|
+
**When:** calling `defineConfig`.
|
|
297
|
+
|
|
298
|
+
**Why:** those four are the kit's. A `session` pinned in the config would
|
|
299
|
+
outrank the one a transaction hands the collection, and the write would land
|
|
300
|
+
outside the transaction.
|
|
301
|
+
|
|
302
|
+
**Fix:**
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const kit = await createKit(config);
|
|
306
|
+
await kit.transaction(async (tx) => { // the session is the kit's
|
|
307
|
+
await tx.db.users.create({ email: 'ada@example.com' });
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`autoSync` belongs to the database, beside `collections`, not to a collection's
|
|
312
|
+
options.
|
|
313
|
+
|
|
314
|
+
### `defineConfig: database "main" has options for "posts", which it does not wire`
|
|
315
|
+
|
|
316
|
+
**When:** calling `defineConfig`, when the types were bypassed — an `as never`,
|
|
317
|
+
a config built at run time, or JavaScript.
|
|
318
|
+
|
|
319
|
+
**Why:** the same cause as the type error
|
|
320
|
+
[above](#posts-is-not-wired-by-this-database-there-are-no-options-for-it):
|
|
321
|
+
`optionsFor` names a key `collections` does not export.
|
|
322
|
+
|
|
323
|
+
**Fix:** write the config as a literal, so the compiler refuses it first:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
export const config = defineConfig({ uri, collections, optionsFor: { users: {} } });
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### ``defineConfig: databases names none. Give it at least one, as `{ databases: { main: … } }`.``
|
|
330
|
+
|
|
331
|
+
**When:** calling `defineConfig` with `databases: {}`.
|
|
332
|
+
|
|
333
|
+
**Why:** the multi-database shape was used and the object came out empty —
|
|
334
|
+
typically built from environment variables that were not set.
|
|
335
|
+
|
|
336
|
+
**Fix:**
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
defineConfig({ databases: { main: { uri, collections } } });
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Runtime
|
|
343
|
+
|
|
344
|
+
### `createKit: database "main" wires a collection under "command", which is a member of the driver's Db: it would be unreachable. Export that definition under another name.`
|
|
345
|
+
|
|
346
|
+
**When:** `await createKit(config)`, and the connections opened before it are
|
|
347
|
+
given back before it throws.
|
|
348
|
+
|
|
349
|
+
**Why:** the same collision as the
|
|
350
|
+
[type error](#command-is-a-member-of-the-drivers-db-wire-this-collection-under-another-key),
|
|
351
|
+
asked of the live `Db` object rather than of its type. A `KitError` with
|
|
352
|
+
`code: 'COLLISION'`, carrying the `database` and the `key` it refused. It
|
|
353
|
+
fires when the types were bypassed, and when a driver release adds a member
|
|
354
|
+
your key already uses.
|
|
355
|
+
|
|
356
|
+
**Fix:** rename the export, as above. If the driver added the member, raising
|
|
357
|
+
`mongodb` is what surfaced it — the check is deliberate, not a regression.
|
|
358
|
+
|
|
359
|
+
### `MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017`
|
|
360
|
+
|
|
361
|
+
**When:** `await createKit(config)` — never at `defineConfig`.
|
|
362
|
+
|
|
363
|
+
**Why:** `defineConfig` connects to nothing; `createKit` is the one call that
|
|
364
|
+
opens clients. A wrong URI, a server that is not up, or an unreachable host
|
|
365
|
+
therefore fails at start-up, not at the first query.
|
|
366
|
+
|
|
367
|
+
**Fix:** check the connection where the application starts, and let it fail
|
|
368
|
+
there:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
export const kit = await createKit(config); // top level: a bad URI stops the boot
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### `connectMongo: this URI is already connected with other options. Pass the same options everywhere, or close the first connection.`
|
|
375
|
+
|
|
376
|
+
**When:** `createKit`, or a second `createKit` in the same process.
|
|
377
|
+
|
|
378
|
+
**Why:** `@nxgt/mongo` shares one client per URI, and a shared client can only
|
|
379
|
+
have one set of options. Two databases on one URI with different
|
|
380
|
+
`clientOptions` — or a test that builds a second kit with other options —
|
|
381
|
+
ask for two.
|
|
382
|
+
|
|
383
|
+
**Fix:** give the same options on that URI, and name the databases instead:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
defineConfig({
|
|
387
|
+
databases: {
|
|
388
|
+
main: { uri, database: 'main', collections },
|
|
389
|
+
analytics: { uri, database: 'analytics', collections: events },
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Both databases then share the one client, which is the point.
|
|
395
|
+
|
|
396
|
+
### ``kit.db: this kit has several databases. Read the one you mean, as `kit.databases.main`.``
|
|
397
|
+
|
|
398
|
+
**When:** reading `kit.db` on a kit built from a `databases` config.
|
|
399
|
+
|
|
400
|
+
**Why:** `db` is the sole database's scope. With several there is no sole one,
|
|
401
|
+
so its type is already `never` — this is what a cast or a JavaScript call-site
|
|
402
|
+
gets at run time. `code: 'SEVERAL_DATABASES'`.
|
|
403
|
+
|
|
404
|
+
**Fix:**
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
await kit.databases.main.users.create({ email: 'ada@example.com' });
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### `This kit has no database "reporting": it has "main", "analytics".`
|
|
411
|
+
|
|
412
|
+
**When:** `transaction(fn, { on })` with a name the config does not hold.
|
|
413
|
+
|
|
414
|
+
**Why:** the names are the keys of `databases` in the config, nothing else —
|
|
415
|
+
not the database names on the server. `code: 'NO_DATABASE'`, with the name
|
|
416
|
+
that was asked for as `error.database`. (Reading `kit.databases.<name>` for a
|
|
417
|
+
name that is not there does not throw: it does not compile, and gives
|
|
418
|
+
`undefined` where the types were bypassed.)
|
|
419
|
+
|
|
420
|
+
**Fix:**
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
await kit.transaction(fn, { on: 'main' }); // a key of `config.databases`
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### ``close: this kit came from `as`, `withSession` or a transaction. Close the kit `createKit` returned — the clients are shared.``
|
|
427
|
+
|
|
428
|
+
**When:** `close()` — including the implicit one of `await using` — on a kit
|
|
429
|
+
that came from `as`, `withSession`, or the one handed to a transaction body.
|
|
430
|
+
|
|
431
|
+
**Why:** a derived kit shares the databases and the clients of the kit
|
|
432
|
+
`createKit` returned. Closing it would take the connections from every other
|
|
433
|
+
kit derived from the same root. `code: 'DERIVED'`.
|
|
434
|
+
|
|
435
|
+
**Fix:** keep `await using` for the root, and let the derived ones fall away:
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
await using kit = await createKit(config); // the only one to close
|
|
439
|
+
const actorKit = kit.as(userId); // no close, nothing to give back
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### `not authorized on app to execute command { collMod: "users", … }`
|
|
443
|
+
|
|
444
|
+
**When:** `kit.sync()`, or the first operation of a collection when
|
|
445
|
+
`autoSync: true`.
|
|
446
|
+
|
|
447
|
+
**Why:** applying a `$jsonSchema` validator and the collection options is
|
|
448
|
+
`collMod`, which needs `dbAdmin`; `readWrite` alone is enough for the indexes
|
|
449
|
+
and every data operation but not for that.
|
|
450
|
+
|
|
451
|
+
**Fix:** run `sync()` as a deployment step, with a user that has `dbAdmin`, and
|
|
452
|
+
leave the application's own user on `readWrite`:
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
// scripts/sync.ts — run with the migration user, not the app's
|
|
456
|
+
await using kit = await createKit(config);
|
|
457
|
+
await kit.sync();
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`autoSync` is for tests and development for the same reason.
|
|
461
|
+
|
|
462
|
+
## Transactions
|
|
463
|
+
|
|
464
|
+
### ``transaction: this kit holds more than one client, and a transaction lives on one. Name the database it runs on, as `{ on: 'main' }`.``
|
|
465
|
+
|
|
466
|
+
**When:** `kit.transaction(fn)` on a kit whose databases are on more than one
|
|
467
|
+
client.
|
|
468
|
+
|
|
469
|
+
**Why:** a transaction lives on a single client, and the types cannot decide:
|
|
470
|
+
two databases on one URI share a client and need no `on`, so what matters is
|
|
471
|
+
the number of *clients*, which is known only once they are open.
|
|
472
|
+
`code: 'TRANSACTION'`.
|
|
473
|
+
|
|
474
|
+
**Fix:**
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
await kit.transaction((tx) => tx.databases.main.users.create(user), { on: 'main' });
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### ``transaction: this kit is already in a session, which this call joins, so `on` has no client left to choose.``
|
|
481
|
+
|
|
482
|
+
**When:** `transaction({ on })` inside a transaction body, or on a kit from
|
|
483
|
+
`withSession`.
|
|
484
|
+
|
|
485
|
+
**Why:** a nested transaction **joins** the outer one rather than opening a
|
|
486
|
+
second beside it, so it runs on the session that is already open — there is no
|
|
487
|
+
client left to pick. `code: 'TRANSACTION'`, as above.
|
|
488
|
+
|
|
489
|
+
**Fix:**
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
await kit.transaction(async (tx) => {
|
|
493
|
+
await service(tx); // its own `tx.transaction(fn)` joins this one
|
|
494
|
+
}, { on: 'main' }); // `on` belongs to the outermost call
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### `ClientSession must be from the same MongoClient`
|
|
498
|
+
|
|
499
|
+
A `MongoInvalidArgumentError`, from the driver.
|
|
500
|
+
|
|
501
|
+
**When:** inside a transaction body, touching a database that is on another
|
|
502
|
+
client than the one the transaction runs on.
|
|
503
|
+
|
|
504
|
+
**Why:** a transaction reaches the databases of **one** client. With
|
|
505
|
+
`{ on: 'main' }`, an operation on a database of a second client carries a
|
|
506
|
+
session that client does not own, and the driver refuses it.
|
|
507
|
+
|
|
508
|
+
**Fix:** put the two databases on one client, or write two transactions and
|
|
509
|
+
accept that they do not commit together:
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
await kit.transaction((tx) => tx.databases.main.users.create(user), { on: 'main' });
|
|
513
|
+
await kit.databases.analytics.events.create({ kind: 'signup' }); // outside it
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### `This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.`
|
|
517
|
+
|
|
518
|
+
**When:** the first `kit.transaction(…)` against a standalone `mongod`.
|
|
519
|
+
|
|
520
|
+
**Why:** the message is the driver's, and it is misleading: transactions need a
|
|
521
|
+
**replica set**, and a standalone has no transactions with or without retryable
|
|
522
|
+
writes. Turning `retryWrites` off does not help.
|
|
523
|
+
|
|
524
|
+
**Fix:** run a replica set — a single node is enough, and is what this
|
|
525
|
+
package's own specs use:
|
|
526
|
+
|
|
527
|
+
```sh
|
|
528
|
+
mongod --replSet rs0 --dbpath ./data # then, once: rs.initiate()
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Scripts
|
|
532
|
+
|
|
533
|
+
`discoverCollections` is for scripts run from the repository: it reads a glob
|
|
534
|
+
from the file system, gives no types, and does not survive bundling. What it
|
|
535
|
+
refuses is a `KitError` with `code: 'DISCOVERY'`, and the path it was reading
|
|
536
|
+
as `error.key`.
|
|
537
|
+
|
|
538
|
+
### `ReferenceError: Bun is not defined`
|
|
539
|
+
|
|
540
|
+
**When:** calling `discoverCollections` from a script run by Node.
|
|
541
|
+
|
|
542
|
+
**Why:** the glob is `Bun.Glob`. This one function needs the Bun runtime;
|
|
543
|
+
everything else in the package runs anywhere.
|
|
544
|
+
|
|
545
|
+
**Fix:** run the script with Bun, or wire the collections statically — which is
|
|
546
|
+
what an application does anyway:
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import * as collections from './models'; // a bundler follows this, Bun.Glob is not needed
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### `discoverCollections: a glob is required`
|
|
553
|
+
|
|
554
|
+
**When:** running the script, with `glob` empty or absent — typically an
|
|
555
|
+
argument the script was not given, or an environment variable that is unset.
|
|
556
|
+
|
|
557
|
+
**Why:** the glob is the only thing that says which files to read. An empty
|
|
558
|
+
one matches nothing, and a discovery that came back empty would sync no
|
|
559
|
+
collection at all without saying so, so it is refused instead.
|
|
560
|
+
|
|
561
|
+
**Fix:** give the option a default the script can run with:
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
const glob = process.argv[2] ?? 'src/models/*.model.ts';
|
|
565
|
+
const definitions = await discoverCollections({ glob });
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### `discoverCollections: src/models/one.model.ts and src/models/two.model.ts both define the collection "twice"`
|
|
569
|
+
|
|
570
|
+
**When:** running the script, while reading the files the glob matched.
|
|
571
|
+
|
|
572
|
+
**Why:** two matched files export definitions with the same collection `name`.
|
|
573
|
+
A sync driven from them would apply two schemas to one server collection.
|
|
574
|
+
|
|
575
|
+
**Fix:** give each collection its own `name`, or narrow the glob so only the
|
|
576
|
+
files you mean are read:
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
await discoverCollections({ glob: 'src/models/*.model.ts' });
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### `discoverCollections: src/models/notes.ts exports no definition named "definition"`
|
|
583
|
+
|
|
584
|
+
**When:** running the script with the `export` option set.
|
|
585
|
+
|
|
586
|
+
**Why:** `export` names the single export to read in each matched file, and one
|
|
587
|
+
of them does not have it — or has it under that name holding something that is
|
|
588
|
+
not a definition.
|
|
589
|
+
|
|
590
|
+
**Fix:** drop the option and let every definition in each file be read, which
|
|
591
|
+
is what `import * as collections` gives:
|
|
592
|
+
|
|
593
|
+
```ts
|
|
594
|
+
await discoverCollections({ glob: 'src/models/*.model.ts' });
|
|
595
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/mongo-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "An application's MongoDB wiring in one object: a validated config, the clients it opens from it, and every collection typed on the database it lives in",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
+
"docs",
|
|
11
12
|
"README.md",
|
|
12
13
|
"package.json",
|
|
13
14
|
"LICENSE"
|
|
@@ -47,14 +48,14 @@
|
|
|
47
48
|
]
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"@nxgt/mongo": "^0.
|
|
51
|
+
"@nxgt/mongo": "^0.16.0",
|
|
51
52
|
"@types/bun": "^1.4.0",
|
|
52
53
|
"mongodb": "7.6.0",
|
|
53
54
|
"mongodb-memory-server-core": "11.2.0",
|
|
54
55
|
"zod": "4.6.5"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
57
|
-
"@nxgt/mongo": "^0.
|
|
58
|
+
"@nxgt/mongo": "^0.16.0",
|
|
58
59
|
"mongodb": ">=7.0.0 <8",
|
|
59
60
|
"typescript": "^6.0.3"
|
|
60
61
|
}
|