@pintahub/database-schemas 6.0.0 → 6.0.1

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,278 @@
1
+ # Contributing — schemas, interfaces, and releases
2
+
3
+ This is the editing guide for anyone adding fields, schemas, or indexes to `@pintahub/database-schemas`. Read [CLAUDE.md](../CLAUDE.md) for the short-form rules; this doc expands on the *why* and walks the common workflows.
4
+
5
+ ## Repo layout
6
+
7
+ ```
8
+ src/
9
+ ├── index.ts # public CJS entry — exports the path to dist/schemas
10
+ ├── types.ts # public type re-exports — consumed via /types subpath
11
+ ├── interfaces/ # pure TypeScript types, no runtime
12
+ │ ├── Order.ts # export interface IOrder { … }
13
+ │ ├── …
14
+ │ ├── types/ # interfaces for embedded sub-schemas
15
+ │ └── products/ # product-specific interface helpers
16
+ └── schemas/ # Mongoose Schema runtime
17
+ ├── Order.ts # export = new Schema({…})
18
+ ├── …
19
+ ├── types/ # embedded sub-schemas (BrandSettings, …)
20
+ └── products/ # product-specific embedded schemas
21
+
22
+ dist/ # compiled output (gitignored, but shipped to npm)
23
+ scripts/
24
+ ├── convert.js # one-off v5 → v6 converter, historical
25
+ ├── snapshot.js # parity check against the legacy schemas/ folder
26
+ └── smoke-schemas-ts.mts # loads dist/ through schemas-ts to verify the bridge
27
+ ```
28
+
29
+ ## Editing rules (must-follow)
30
+
31
+ These come from [CLAUDE.md](../CLAUDE.md). Repeated here so contributors see them in one place.
32
+
33
+ - **CommonJS at runtime.** Each schema file uses `export = SchemaInstance`. TypeScript compiles this to `module.exports = SchemaInstance`, which is what both `schemis` (via `require`) and `schemas-ts` (via dynamic `import()` with `.default || .module.exports`) expect.
34
+ - **Export Schema, not Model.** Never call `mongoose.model(...)` in this package. Consumers register models via their resolver.
35
+ - **JSDoc every field.** A `/** ... */` block immediately above each field is the documentation source of truth. There is no separate docs site.
36
+ - **Embedded sub-documents use `{_id: false}`.** Anything in `schemas/types/`, `schemas/products/`, or any inline embedded schema in `schemas/` must declare `_id: false`. Sub-docs are pure value objects; auto-generated `_id`s create churn on parent updates.
37
+ - **Multi-tenant `store` ref.** Domain schemas keep an indexed `store: {type: Schema.Types.ObjectId, ref: 'Store', index: true}`. New tenant-scoped schemas must include it.
38
+ - **Explicit timestamps.** Use `created_at` and `updated_at` as `Date` fields. **Do not** add Mongoose's `timestamps: true` option — historical writes in the codebase set these fields manually, and `timestamps: true` would silently overwrite them.
39
+ - **No schema-level pre/post hooks** unless explicitly requested. Compute logic happens at the consumer write path so it's visible to service authors.
40
+
41
+ ## Index policy
42
+
43
+ - Single-field index → put `index: true` on the field def.
44
+ - Compound, text, partial, or TTL index → use `Schema.index({...}, {...})` at the **bottom** of the file.
45
+ - Compound indexes should put the highest-selectivity field first (typically `store`).
46
+ - **Do not add a B-tree index for fields whose only purpose is Atlas Search.** Atlas Search builds its own Lucene index; a duplicate B-tree just wastes write throughput.
47
+ - Drop indexes that are superseded by a new compound index in the same PR. `git log` makes the intent clear.
48
+
49
+ ## Adding a field to an existing schema
50
+
51
+ 1. Open `src/schemas/<Name>.ts`. Add the field def with a `/** … */` block above it.
52
+ 2. Open `src/interfaces/<Name>.ts`. Add the matching field to the interface. Default to optional (`field?: T`) unless the field is required at insert time.
53
+ 3. If the field is a sub-document, reuse an existing embedded schema from `schemas/types/` or `schemas/products/` if one fits; otherwise add a new one (see below).
54
+ 4. Bump `package.json` — **minor** for any new field.
55
+ 5. Commit. The build runs in CI on tag publish, but locally you can run `yarn build` to sanity-check.
56
+
57
+ Example diff:
58
+
59
+ ```ts
60
+ // src/schemas/Store.ts
61
+ /** Time the store was first set up (separate from created_at, which can be backdated for imports) */
62
+ onboarded_at: {
63
+ type: Date,
64
+ },
65
+ ```
66
+
67
+ ```ts
68
+ // src/interfaces/Store.ts
69
+ export interface IStore {
70
+ // …
71
+ onboarded_at?: Date
72
+ }
73
+ ```
74
+
75
+ ## Adding a new schema
76
+
77
+ 1. Create `src/schemas/<Name>.ts`:
78
+
79
+ ```ts
80
+ import {Schema} from 'mongoose'
81
+
82
+ /** Top-of-file JSDoc describing the collection */
83
+ const MySchema = new Schema(<Record<string, any>>{
84
+ /** @ref Store */
85
+ store: {
86
+ type: Schema.Types.ObjectId,
87
+ ref: 'Store',
88
+ index: true,
89
+ },
90
+
91
+ /** Display name */
92
+ name: {
93
+ type: String,
94
+ trim: true,
95
+ },
96
+
97
+ /** Created timestamp — set by the service at write time */
98
+ created_at: {
99
+ type: Date,
100
+ default: Date.now,
101
+ },
102
+ updated_at: Date,
103
+ })
104
+
105
+ MySchema.index({store: 1, created_at: -1})
106
+
107
+ export = MySchema
108
+ ```
109
+
110
+ Notes:
111
+ - `<Record<string, any>>` on the schema definition silences Mongoose's strict shape-inference; the public type comes from the interface file.
112
+ - `export = MySchema` (not `export default`) — this compiles to `module.exports = MySchema`, which is what both resolvers expect.
113
+
114
+ 2. Create `src/interfaces/<Name>.ts`:
115
+
116
+ ```ts
117
+ import type {Types} from 'mongoose'
118
+
119
+ export interface IMy {
120
+ store: Types.ObjectId
121
+ name?: string
122
+ created_at?: Date
123
+ updated_at?: Date
124
+ }
125
+ ```
126
+
127
+ - The `store` ref is `Types.ObjectId` and **not** optional, because multi-tenant queries always require it.
128
+ - Other fields default to optional.
129
+
130
+ 3. Re-export the interface from `src/types.ts` (keep the file alphabetically sorted):
131
+
132
+ ```ts
133
+ export type {IMy} from './interfaces/My'
134
+ ```
135
+
136
+ 4. Bump `package.json` — **minor** for a new schema.
137
+
138
+ 5. Run `yarn build` and verify `dist/schemas/<Name>.js` exists and compiles.
139
+
140
+ ## Adding an embedded type
141
+
142
+ Embedded types live in `src/schemas/types/` (generic) or `src/schemas/products/` (product-specific). They are reused across multiple parent schemas.
143
+
144
+ ```ts
145
+ // src/schemas/types/Coordinates.ts
146
+ import {Schema} from 'mongoose'
147
+
148
+ /** Embedded geo coordinates */
149
+ const Coordinates = new Schema(<Record<string, any>>{
150
+ _id: false,
151
+
152
+ /** Latitude, decimal degrees, WGS84 */
153
+ lat: {
154
+ type: Number,
155
+ },
156
+
157
+ /** Longitude, decimal degrees, WGS84 */
158
+ lng: {
159
+ type: Number,
160
+ },
161
+ })
162
+
163
+ export = Coordinates
164
+ ```
165
+
166
+ ```ts
167
+ // src/interfaces/types/Coordinates.ts
168
+ export interface ICoordinates {
169
+ lat?: number
170
+ lng?: number
171
+ }
172
+ ```
173
+
174
+ Re-export from `src/types.ts`. Use the embedded schema in parent schemas via `import = require('...')`:
175
+
176
+ ```ts
177
+ // src/schemas/Store.ts
178
+ import Coordinates = require('./types/Coordinates')
179
+
180
+ const Store = new Schema(<Record<string, any>>{
181
+ // …
182
+ location: {
183
+ type: Coordinates,
184
+ },
185
+ })
186
+ ```
187
+
188
+ And on the interface side, reference the embedded type:
189
+
190
+ ```ts
191
+ import type {ICoordinates} from './types/Coordinates'
192
+
193
+ export interface IStore {
194
+ location?: ICoordinates
195
+ }
196
+ ```
197
+
198
+ ## Versioning
199
+
200
+ Semver, no exceptions:
201
+
202
+ | Change | Bump |
203
+ | ------------------------------------------------------------ | ----- |
204
+ | New field, new schema, new index (backward compatible) | minor |
205
+ | JSDoc/comment-only changes | patch |
206
+ | Renaming a field, removing a field/schema, changing a type | major |
207
+ | Removing/changing an existing index (writes behave differently) | major |
208
+ | Build/format change (e.g. v6.0.0 TS rewrite) | major |
209
+
210
+ Bump in the **same commit** as the change. If you forget, the publish will still go through but consumers won't know they need to update.
211
+
212
+ ## Build & verification
213
+
214
+ ```bash
215
+ yarn install
216
+ yarn build # tsc → dist/
217
+ yarn dev # tsc --watch during development
218
+ node scripts/snapshot.js # diffs dist/schemas/ vs legacy schemas/ (when present)
219
+ node --import tsx scripts/smoke-schemas-ts.mts
220
+ # loads dist/ through schemas-ts to verify the bridge works
221
+ ```
222
+
223
+ The smoke script is the most useful check before publishing — it catches the case where a `export =` was accidentally changed to `export default`, which breaks `schemis`'s `require()` consumer.
224
+
225
+ ## Publishing
226
+
227
+ The npm tarball is `dist/` only (see `"files": ["dist"]` in `package.json`). Make sure:
228
+
229
+ 1. `yarn build` produced a clean `dist/`.
230
+ 2. `package.json` version is bumped.
231
+ 3. Commit, tag, push. CI publishes on tag.
232
+
233
+ Do **not** edit `dist/` directly. It is gitignored and regenerated on every build.
234
+
235
+ ## House style for JSDoc
236
+
237
+ Field-level JSDoc is the doc site. Conventions:
238
+
239
+ - Lead with what the field stores, not its type — the type is right there in the def.
240
+ - For enums, list every legal value:
241
+
242
+ ```ts
243
+ /**
244
+ * Order status:
245
+ * - pending: awaiting payment
246
+ * - paid: payment captured
247
+ * - cancelled: cancelled by buyer or merchant
248
+ */
249
+ status: {
250
+ type: String,
251
+ enum: ['pending', 'paid', 'cancelled'],
252
+ },
253
+ ```
254
+
255
+ - For ObjectId refs, use `@ref ModelName`:
256
+
257
+ ```ts
258
+ /** @ref Store */
259
+ store: {
260
+ type: Schema.Types.ObjectId,
261
+ ref: 'Store',
262
+ index: true,
263
+ },
264
+ ```
265
+
266
+ - For TTL or partial indexes, document the rationale at the `Schema.index` call:
267
+
268
+ ```ts
269
+ // 60-day TTL — ShortLog entries are only interesting for the recent past.
270
+ ShortLog.index({created_at: 1}, {expireAfterSeconds: 60 * 24 * 60 * 60})
271
+ ```
272
+
273
+ ## Useful references
274
+
275
+ - [usage-js.md](./usage-js.md) — consumer guide for CJS services
276
+ - [usage-ts.md](./usage-ts.md) — consumer guide for TS services
277
+ - [migration.md](./migration.md) — v5 → v6 upgrade walkthrough
278
+ - [CLAUDE.md](../CLAUDE.md) — short-form rules (the one Claude reads)
@@ -0,0 +1,242 @@
1
+ # Migration — v5 → v6 (TypeScript rewrite)
2
+
3
+ `@pintahub/database-schemas@6.0.0` is a TypeScript rewrite. Runtime behaviour for existing CommonJS services is **backward compatible** — the public entry still resolves to an absolute path string to a schemas directory. The breaking changes are mostly around peer-dep wiring and the new typed `/types` subpath.
4
+
5
+ This guide covers the two upgrade paths:
6
+
7
+ 1. **JS service staying on `schemis`** — minimal change, mostly `package.json`.
8
+ 2. **JS service moving to TypeScript + `schemas-ts`** — bigger, but unblocks typed models.
9
+
10
+ If you are starting a new service, skip this file and go straight to [usage-ts.md](./usage-ts.md).
11
+
12
+ ---
13
+
14
+ ## What changed in v6
15
+
16
+ | Area | v5 (5.x) | v6 (6.x) |
17
+ | -------------------- | ---------------------------------------------- | -------------------------------------------------------------- |
18
+ | Source language | JavaScript (CJS) | TypeScript, compiled to CJS in `dist/` |
19
+ | Entry | `index.js` → `path.join(__dirname, 'schemas')` | `dist/index.js` → `path.join(__dirname, 'schemas')` |
20
+ | Schema files | `schemas/*.js` (`module.exports = Schema`) | `dist/schemas/*.js` (`module.exports = Schema` via `export =`) |
21
+ | Types | none | `@pintahub/database-schemas/types` re-exports `IXxx` |
22
+ | `peerDependencies` | `mongoose`, `schemis` (both required) | `mongoose` (required); `schemis` / `schemas-ts` (optional) |
23
+ | Files in npm tarball | `index.js`, `schemas/` | `dist/` only |
24
+
25
+ **No schema field, index, or collection name changed.** A v5 consumer that bumps to v6 sees the same MongoDB behaviour. The breaking part is `peerDependencies` — `schemis` is no longer pulled in automatically, you must install it (or `schemas-ts`) explicitly.
26
+
27
+ ---
28
+
29
+ ## Path 1 — Stay on JS, upgrade in place
30
+
31
+ For services that currently look like this:
32
+
33
+ ```js
34
+ // src/connections/database.js (v5)
35
+ const {createConnection, createStore} = require('schemis')
36
+ const schemas = require('@pintahub/database-schemas')
37
+
38
+ const connection = createConnection(process.env.MONGODB_URI)
39
+ const store = createStore({connection, schemas})
40
+
41
+ module.exports = store
42
+ ```
43
+
44
+ ### Step 1 — bump deps
45
+
46
+ ```bash
47
+ yarn add @pintahub/database-schemas@^6.0.0 schemis@^2.0.2
48
+ ```
49
+
50
+ In v5, `schemis` was a peer of this package and you might have been getting it transitively. In v6 it is **optional**, so add it to your own `dependencies` explicitly.
51
+
52
+ ### Step 2 — confirm no code change
53
+
54
+ The entry `require('@pintahub/database-schemas')` still returns an absolute path string. `schemis.createStore` still walks that directory. No source-code edits required.
55
+
56
+ ### Step 3 — smoke test
57
+
58
+ ```bash
59
+ node -e "const s = require('@pintahub/database-schemas'); console.log(s)"
60
+ # /…/node_modules/@pintahub/database-schemas/dist/schemas
61
+ ```
62
+
63
+ ```bash
64
+ node -e "
65
+ const {createStore, createConnection} = require('schemis');
66
+ const schemas = require('@pintahub/database-schemas');
67
+ const conn = createConnection('mongodb://localhost:27017/test');
68
+ const store = createStore({connection: conn, schemas});
69
+ console.log(Object.keys(store.getModel('Store').schema.paths).slice(0, 5));
70
+ conn.close();
71
+ "
72
+ ```
73
+
74
+ Expected: five field names from the `Store` schema. If you see `Cannot find module 'schemis'`, finish Step 1.
75
+
76
+ ### Step 4 — deploy
77
+
78
+ Standard CI/CD. Nothing collection-shaped changed, so there is no data migration.
79
+
80
+ ---
81
+
82
+ ## Path 2 — Migrate a service from JS+`schemis` to TS+`schemas-ts`
83
+
84
+ This is a per-service decision. Do it when the service is being touched substantially anyway — don't migrate for its own sake.
85
+
86
+ ### Step 0 — pre-flight
87
+
88
+ - Node version ≥ 20.11.0 (`schemas-ts` requires it).
89
+ - The service has no remaining CJS-only deps that block ESM (rare; check `package.json` for `"type": "module"` viability).
90
+
91
+ ### Step 1 — set up TypeScript
92
+
93
+ `package.json`:
94
+
95
+ ```json
96
+ {
97
+ "type": "module",
98
+ "scripts": {
99
+ "build": "tsc",
100
+ "dev": "tsx watch src/index.ts",
101
+ "start": "node dist/index.js"
102
+ }
103
+ }
104
+ ```
105
+
106
+ `tsconfig.json`:
107
+
108
+ ```json
109
+ {
110
+ "compilerOptions": {
111
+ "target": "ES2022",
112
+ "module": "NodeNext",
113
+ "moduleResolution": "NodeNext",
114
+ "esModuleInterop": true,
115
+ "skipLibCheck": true,
116
+ "strict": true,
117
+ "outDir": "./dist",
118
+ "rootDir": "./src"
119
+ },
120
+ "include": ["src/**/*"]
121
+ }
122
+ ```
123
+
124
+ ### Step 2 — swap the resolver
125
+
126
+ ```bash
127
+ yarn remove schemis
128
+ yarn add @pintahub/database-schemas@^6.0.0 schemas-ts@^1.0.2
129
+ yarn add -D @types/node typescript tsx
130
+ ```
131
+
132
+ ### Step 3 — rewrite the connection module
133
+
134
+ Before (`src/connections/database.js`):
135
+
136
+ ```js
137
+ const {createConnection, createStore} = require('schemis')
138
+ const schemas = require('@pintahub/database-schemas')
139
+
140
+ const connection = createConnection(process.env.MONGODB_URI)
141
+ const store = createStore({connection, schemas})
142
+
143
+ module.exports = store
144
+ ```
145
+
146
+ After (`src/connections/database.ts`):
147
+
148
+ ```ts
149
+ import {createConnection, createStore} from 'schemas-ts'
150
+ import schemasPath from '@pintahub/database-schemas'
151
+
152
+ const connection = createConnection(process.env.MONGODB_URI!)
153
+ const store = createStore({connection, schemas: schemasPath})
154
+
155
+ export default store
156
+ ```
157
+
158
+ The API surface (`getModel`, `getConnection`) is identical between `schemis` and `schemas-ts`, so call sites that only use those two methods do not need to change.
159
+
160
+ ### Step 4 — type the call sites incrementally
161
+
162
+ You can migrate file by file. Start with the hottest collections.
163
+
164
+ Before:
165
+
166
+ ```js
167
+ const Product = store.getModel('Product')
168
+ const doc = await Product.findOne({_id: id}).lean()
169
+ // doc is `any`
170
+ ```
171
+
172
+ After:
173
+
174
+ ```ts
175
+ import type {IProduct} from '@pintahub/database-schemas/types'
176
+
177
+ const Product = store.getModel<IProduct>('Product')
178
+ const doc = await Product.findOne({_id: id}).lean()
179
+ // doc is IProduct | null (well, the lean variant — see usage-ts.md)
180
+ ```
181
+
182
+ Untyped `getModel('Product')` still works during the migration; the generic is opt-in.
183
+
184
+ ### Step 5 — extension-explicit imports
185
+
186
+ ESM under `NodeNext` requires `.js` extensions on relative imports — even for files that are `.ts` on disk (TypeScript rewrites the import on the way out, the runtime sees `.js`):
187
+
188
+ ```ts
189
+ // in src/foo.ts
190
+ import store from './connections/database.js' // .js, not .ts
191
+ ```
192
+
193
+ The compiled output lands at `dist/connections/database.js`, so this is correct at runtime.
194
+
195
+ ### Step 6 — verify
196
+
197
+ ```bash
198
+ yarn build
199
+ node --input-type=module -e "
200
+ import('./dist/connections/database.js').then(async ({default: store}) => {
201
+ const Store = store.getModel('Store')
202
+ console.log('paths:', Object.keys(Store.schema.paths).length)
203
+ await store.getConnection().close()
204
+ })
205
+ "
206
+ ```
207
+
208
+ Then run the actual service against a staging Mongo and exercise a couple of read paths.
209
+
210
+ ### Step 7 — clean up
211
+
212
+ - Remove the now-unused `schemis` line from `package.json` (Step 2 already did this with `yarn remove`).
213
+ - Update CI: build step is now `yarn build`, runtime is `node dist/index.js`.
214
+ - Re-enable `"strict": true` if you suppressed it during the bulk rewrite.
215
+
216
+ ---
217
+
218
+ ## Mapping cheatsheet
219
+
220
+ | Concept | v5 (`schemis`) | v6 (`schemas-ts`) |
221
+ | --------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------- |
222
+ | Import the schemas path | `const s = require('@pintahub/database-schemas')` | `import s from '@pintahub/database-schemas'` |
223
+ | Import a typed interface | n/a (no types in v5) | `import type {IOrder} from '@pintahub/database-schemas/types'` |
224
+ | Create connection | `require('schemis').createConnection(uri)` | `import {createConnection} from 'schemas-ts'` |
225
+ | Create store | `createStore({connection, schemas})` | `createStore({connection, schemas: schemasPath})` |
226
+ | Get a model | `store.getModel('Order')` | `store.getModel<IOrder>('Order')` |
227
+ | Custom collection name | `store.getModel('Order', 'archived_orders')` | `store.getModel<IOrder>('Order', 'archived_orders')` |
228
+ | Populate (always explicit `model`) | `.populate({path, model, select})` | `.populate({path, model, select})` |
229
+
230
+ ## FAQ
231
+
232
+ **Can two services share one Mongo connection with different resolvers?**
233
+ Yes. The package only exposes a path string; each service builds its own store. Nothing about the Mongo wire protocol cares which resolver wrapped it.
234
+
235
+ **Can a v5 service and a v6 service write the same collection at the same time?**
236
+ Yes. v6 did not change any field name, type, index, or collection name. The only thing that changed is how the schemas are *loaded* in Node.
237
+
238
+ **My CI pulled in `schemis@1.x` after upgrading.**
239
+ v6 marks `schemis` as optional, so your lockfile drops it if you don't list it explicitly. Add `"schemis": "^2.0.2"` (or `"schemas-ts": "^1.0.2"`) to your own `dependencies`.
240
+
241
+ **I see `dist/schemas/Order.js` but the package source is `src/schemas/Order.ts`.**
242
+ Correct — only `dist/` ships in the npm tarball (`"files": ["dist"]` in `package.json`). When you edit a schema, the build step (`yarn build`) is what produces the file your consumers see.
@@ -0,0 +1,114 @@
1
+ # Usage — CommonJS (JS) services
2
+
3
+ For services written in plain JavaScript (CommonJS) — e.g. `admin-api`, `admin-worker`, legacy workers. Use the [`schemis`](https://www.npmjs.com/package/schemis) resolver.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ yarn add @pintahub/database-schemas schemis mongoose
9
+ ```
10
+
11
+ Peer-dep matrix:
12
+
13
+ | Package | Version | Notes |
14
+ | ------------------------------- | --------- | --------------------------- |
15
+ | `mongoose` | `^9.1.3` | required peer |
16
+ | `schemis` | `^2.0.2` | CJS resolver |
17
+ | `@pintahub/database-schemas` | `^6.0.0` | this package |
18
+
19
+ `schemas-ts` is **not** needed for JS services.
20
+
21
+ ## Bootstrap the store
22
+
23
+ ```js
24
+ // src/connections/database.js
25
+ const {createConnection, createStore} = require('schemis')
26
+ const schemasPath = require('@pintahub/database-schemas')
27
+
28
+ const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/dev'
29
+ const connection = createConnection(uri)
30
+
31
+ const store = createStore({connection, schemas: schemasPath})
32
+
33
+ module.exports = store
34
+ ```
35
+
36
+ `require('@pintahub/database-schemas')` resolves to an **absolute path string** pointing at `dist/schemas/`. `schemis.createStore` walks that directory and lazy-loads each schema on first `getModel` call.
37
+
38
+ ## Use models
39
+
40
+ ```js
41
+ const store = require('./connections/database')
42
+
43
+ const Product = store.getModel('Product')
44
+
45
+ const doc = await Product
46
+ .findOne({_id: productId, store: storeId})
47
+ .lean()
48
+
49
+ await Product.updateOne(
50
+ {_id: productId},
51
+ {$set: {status: 'active', updated_at: new Date()}}
52
+ )
53
+ ```
54
+
55
+ Always filter by `store` for tenant-scoped collections.
56
+
57
+ ## Custom collection name
58
+
59
+ Pass a second argument to override the default collection name (Mongoose pluralisation):
60
+
61
+ ```js
62
+ const ArchivedOrder = store.getModel('Order', 'archived_orders')
63
+ ```
64
+
65
+ ## Populate with explicit `model`
66
+
67
+ `schemis` does not register cross-schema refs globally, so always pass the populated model explicitly:
68
+
69
+ ```js
70
+ const TransferJob = store.getModel('TransferJob')
71
+ const Product = store.getModel('Product')
72
+ const Store = store.getModel('Store')
73
+
74
+ const jobs = await TransferJob
75
+ .find({store: storeId})
76
+ .populate({path: 'product', model: Product, select: {title: 1, code: 1}})
77
+ .populate({path: 'destination_store', model: Store, select: {name: 1}})
78
+ .lean()
79
+ ```
80
+
81
+ ## Connection lifecycle
82
+
83
+ `createConnection(uri)` returns a Mongoose `Connection` immediately and connects in the background. Wait for it before serving traffic:
84
+
85
+ ```js
86
+ const store = require('./connections/database')
87
+
88
+ store.getConnection().once('open', () => {
89
+ console.log('mongo connected')
90
+ // start http server here
91
+ })
92
+
93
+ store.getConnection().on('error', err => {
94
+ console.error('mongo error', err)
95
+ })
96
+ ```
97
+
98
+ For graceful shutdown:
99
+
100
+ ```js
101
+ process.on('SIGTERM', async () => {
102
+ await store.getConnection().close()
103
+ process.exit(0)
104
+ })
105
+ ```
106
+
107
+ ## Common pitfalls
108
+
109
+ - **`Cannot find module '@pintahub/database-schemas'`** — make sure the package is added to `dependencies`, not `devDependencies`.
110
+ - **`Schema hasn't been registered for model "X"`** — you called `mongoose.model('X')` directly instead of `store.getModel('X')`. Always go through the store.
111
+ - **Stale schema after publish** — clear `node_modules/@pintahub/database-schemas` and re-install; the package is pinned by lockfile, not auto-updated.
112
+ - **Two stores for the same connection** — `getModel` caches per store; instantiate one `createStore` per connection and reuse it.
113
+
114
+ See [Contributing](./contributing.md) for adding/changing schemas and bumping the version.
@@ -0,0 +1,141 @@
1
+ # Usage — TypeScript / ESM services
2
+
3
+ For services written in TypeScript with ESM output — e.g. `shopify-gateway` and any new service. Use the [`schemas-ts`](https://www.npmjs.com/package/schemas-ts) resolver.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ yarn add @pintahub/database-schemas schemas-ts mongoose
9
+ yarn add -D @types/node typescript
10
+ ```
11
+
12
+ Peer-dep matrix:
13
+
14
+ | Package | Version | Notes |
15
+ | ------------------------------- | --------- | ------------------------------------ |
16
+ | `mongoose` | `^9.1.3` | required peer |
17
+ | `schemas-ts` | `^1.0.2` | ESM resolver (Node ≥ 20.11) |
18
+ | `@pintahub/database-schemas` | `^6.0.0` | this package |
19
+
20
+ `schemis` is **not** needed for TS services.
21
+
22
+ ## `tsconfig.json` baseline
23
+
24
+ ```json
25
+ {
26
+ "compilerOptions": {
27
+ "target": "ES2022",
28
+ "module": "NodeNext",
29
+ "moduleResolution": "NodeNext",
30
+ "esModuleInterop": true,
31
+ "skipLibCheck": true,
32
+ "strict": true,
33
+ "outDir": "./dist",
34
+ "rootDir": "./src"
35
+ },
36
+ "include": ["src/**/*"]
37
+ }
38
+ ```
39
+
40
+ `module: "NodeNext"` is required for `schemas-ts` (it ships ESM-only) and is what the package's `import.meta.dirname` example relies on.
41
+
42
+ ## Bootstrap the store
43
+
44
+ ```ts
45
+ // src/connections/database.ts
46
+ import {createConnection, createStore} from 'schemas-ts'
47
+ import schemasPath from '@pintahub/database-schemas'
48
+
49
+ const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/dev'
50
+ const connection = createConnection(uri)
51
+
52
+ const store = createStore({connection, schemas: schemasPath})
53
+
54
+ export default store
55
+ ```
56
+
57
+ The package's default export is an **absolute string** pointing at `dist/schemas/`. `schemas-ts` loads each schema via dynamic `import()` and supports both `export default Schema` and `module.exports = Schema` (this package uses the latter via TypeScript's `export = Schema`).
58
+
59
+ ## Typed models
60
+
61
+ `store.getModel<T>(name)` returns `Model<T>`. Use the interface re-exports from the `/types` subpath:
62
+
63
+ ```ts
64
+ import store from './connections/database.js'
65
+ import type {IStore, IProduct, IOrder} from '@pintahub/database-schemas/types'
66
+ import type {Model} from 'mongoose'
67
+
68
+ const Store: Model<IStore> = store.getModel<IStore>('Store')
69
+ const Product: Model<IProduct> = store.getModel<IProduct>('Product')
70
+
71
+ const found = await Store.findOne({subdomain: 'foo'}).lean()
72
+ // ^? IStore | null
73
+
74
+ const product = await Product.findOne(
75
+ {_id: productId, store: storeId},
76
+ ).lean()
77
+ ```
78
+
79
+ Every schema in `src/schemas/` has a matching interface in `src/interfaces/`, re-exported through `@pintahub/database-schemas/types`. The interface name is the file name prefixed with `I` — e.g. `Order.ts` → `IOrder`.
80
+
81
+ > **Note** — the runtime export at `@pintahub/database-schemas/types` is empty (`export type` is erased by TypeScript). The subpath exists purely for type imports. Always use `import type {...}` to keep it tree-shake-safe.
82
+
83
+ ## Populate with explicit `model`
84
+
85
+ `schemas-ts` does not register cross-schema refs globally either, so pass the populated model explicitly:
86
+
87
+ ```ts
88
+ import type {ITransferJob, IProduct, IStore} from '@pintahub/database-schemas/types'
89
+
90
+ const TransferJob = store.getModel<ITransferJob>('TransferJob')
91
+ const Product = store.getModel<IProduct>('Product')
92
+ const Store = store.getModel<IStore>('Store')
93
+
94
+ const jobs = await TransferJob
95
+ .find({store: storeId})
96
+ .populate({path: 'product', model: Product, select: {title: 1, code: 1}})
97
+ .populate({path: 'destination_store', model: Store, select: {name: 1}})
98
+ .lean()
99
+ ```
100
+
101
+ ## ObjectId fields in the interfaces
102
+
103
+ Foreign-key fields in `IXxx` interfaces are typed as `Types.ObjectId`:
104
+
105
+ ```ts
106
+ import type {Types} from 'mongoose'
107
+ import type {IOrder} from '@pintahub/database-schemas/types'
108
+
109
+ const order: IOrder = await Order.findOne({...}).lean() as IOrder
110
+ order.store // Types.ObjectId
111
+ order.store.toString()
112
+ ```
113
+
114
+ When you `.populate()` a ref, cast the result to the populated shape — the base interface intentionally keeps the un-populated ObjectId type for accuracy.
115
+
116
+ ## Connection lifecycle
117
+
118
+ ```ts
119
+ import store from './connections/database.js'
120
+
121
+ const conn = store.getConnection()
122
+
123
+ conn.once('open', () => {
124
+ // safe to start serving traffic
125
+ })
126
+
127
+ process.on('SIGTERM', async () => {
128
+ await conn.close()
129
+ process.exit(0)
130
+ })
131
+ ```
132
+
133
+ ## Common pitfalls
134
+
135
+ - **`ERR_REQUIRE_ESM`** — you set `module: "commonjs"` in `tsconfig.json` but `schemas-ts` is ESM-only. Switch to `NodeNext`, or stay on JS and use `schemis` instead (see [usage-js.md](./usage-js.md)).
136
+ - **`Cannot find module '@pintahub/database-schemas/types'`** — your `moduleResolution` is `node` (legacy). Use `NodeNext` or `bundler` so subpath exports resolve.
137
+ - **Type for a field is `any`** — that field is missing from the interface in `src/interfaces/`. Open a PR adding it; both the schema file and interface must move together.
138
+ - **`Model<T>.findOne(...).lean()` returns `T & Document`** — that's normal Mongoose typing; cast or use `.lean<T>()` if you need a plain shape.
139
+ - **Hot reload re-creates the connection on every change** — wrap the bootstrap in a module-level cache or use the dev-server's HMR hooks; `createConnection` is not idempotent.
140
+
141
+ See [Contributing](./contributing.md) for adding/changing schemas and interfaces.
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@pintahub/database-schemas",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "Pintahub MongoDB schemas (TypeScript). Compatible with both schemis (CJS) and schemas-ts (ESM) resolvers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
8
- "dist"
8
+ "dist",
9
+ "docs"
9
10
  ],
10
11
  "exports": {
11
12
  ".": {