@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.
@@ -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.1.4",
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.15.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.15.0",
58
+ "@nxgt/mongo": "^0.16.0",
58
59
  "mongodb": ">=7.0.0 <8",
59
60
  "typescript": "^6.0.3"
60
61
  }