@pintahub/database-schemas 4.7.1 → 4.8.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/CLAUDE.md +26 -117
- package/README.md +149 -0
- package/package.json +1 -1
- package/schemas/Product.js +69 -0
package/CLAUDE.md
CHANGED
|
@@ -1,125 +1,34 @@
|
|
|
1
|
-
# @pintahub/database-schemas
|
|
1
|
+
# CLAUDE.md — @pintahub/database-schemas
|
|
2
2
|
|
|
3
|
-
Shared Mongoose schema
|
|
3
|
+
Shared Mongoose schema package for PintaHub microservices. See [README.md](./README.md) for package overview, install, usage examples, full schema catalog, and conventions.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This file contains only the rules Claude needs to edit safely. Do not duplicate README content here.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Editing rules
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **CommonJS only.** This codebase uses `require` / `module.exports`. Do **not** convert to ES modules — overrides the global "prefer ES modules" preference.
|
|
10
|
+
- **Export Schema, not Model.** Each file in `schemas/` exports a `new Schema({...})` instance. Consumers register models themselves through `schemis`.
|
|
11
|
+
- **JSDoc every field.** Use `/** ... */` immediately above each field — this is the documentation source of truth (no separate docs site).
|
|
12
|
+
- **Embedded sub-documents** declared in `schemas/types/`, `schemas/products/`, or co-located in `schemas/` (e.g. `MoneyObject`) must be created with `{_id: false}`.
|
|
13
|
+
- **Multi-tenancy.** Domain schemas keep an indexed `store` ObjectId ref. When adding a new schema with tenant data, include `store: {type: Schema.Types.ObjectId, ref: 'Store', index: true}`.
|
|
14
|
+
- **Timestamps.** Use explicit `created_at` / `updated_at` Date fields. Do **not** add Mongoose's `timestamps: true` option — it breaks consistency with the rest of the codebase.
|
|
15
|
+
- **Indexes.**
|
|
16
|
+
- Default to `index: true` on the field for single-field indexes.
|
|
17
|
+
- Use `Schema.index({...})` at the bottom of the file for compound or text indexes.
|
|
18
|
+
- **Do not add a B-tree index for fields whose only purpose is Atlas Search** — Atlas Search uses its own Lucene index, and a redundant B-tree wastes write throughput.
|
|
19
|
+
- **No schema-level pre/post hooks** unless explicitly requested. Compute logic happens at the consumer write-path so behavior is visible to service authors.
|
|
20
|
+
- **Version bump on every schema change.** See README "Versioning". Update `package.json` in the same commit:
|
|
21
|
+
- new field / new schema / new index → minor
|
|
22
|
+
- rename / remove / type change → major
|
|
23
|
+
- JSDoc-only → patch
|
|
10
24
|
|
|
11
|
-
|
|
12
|
-
// src/connections/database.js
|
|
13
|
-
const {createConnection, createStore} = require('schemis')
|
|
14
|
-
const schemas = require('@pintahub/database-schemas')
|
|
25
|
+
## Project context
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
- **Atlas Search migration in progress** for `storefront-api` product search (replacing the legacy `$text` index on Product). The legacy text index stays in place until the migration is complete.
|
|
28
|
+
- **`Product.display_title`** (added 2026-04-17) is the precomputed `alternative_title || title` value used by Atlas Search and frontend display. Consumer services set it at write time — do not add a schema hook for it.
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
connection,
|
|
21
|
-
schemas
|
|
22
|
-
})
|
|
30
|
+
## Common tasks
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### Using Models in Actions
|
|
28
|
-
|
|
29
|
-
```js
|
|
30
|
-
const {getModel} = require('../../connections/database')
|
|
31
|
-
|
|
32
|
-
const Product = getModel('Product')
|
|
33
|
-
|
|
34
|
-
// Standard Mongoose operations
|
|
35
|
-
const product = await Product.findOne({_id: productId, store: storeId}).lean()
|
|
36
|
-
await Product.updateOne({_id: productId}, {$set: {status: 'active', updated_at: Date.now()}})
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Populate with Explicit Model
|
|
40
|
-
|
|
41
|
-
```js
|
|
42
|
-
const {getModel} = require('../../connections/database')
|
|
43
|
-
|
|
44
|
-
const TransferJob = getModel('TransferJob')
|
|
45
|
-
const Product = getModel('Product')
|
|
46
|
-
const Store = getModel('Store')
|
|
47
|
-
|
|
48
|
-
const items = await TransferJob
|
|
49
|
-
.find({store: storeId})
|
|
50
|
-
.populate({path: 'product', model: Product, select: {title: 1, code: 1}})
|
|
51
|
-
.populate({path: 'destination_store', model: Store, select: {name: 1}})
|
|
52
|
-
.lean()
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Dependencies
|
|
56
|
-
|
|
57
|
-
- **mongoose** `^9.1.3` (peer dependency)
|
|
58
|
-
- **schemis** `^2.0.2` — used by consumers to create database store and register models from schemas
|
|
59
|
-
|
|
60
|
-
## Directory Structure
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
schemas/ # Main schema files (~60 schemas)
|
|
64
|
-
schemas/types/ # Embedded sub-document types (BrandSettings, FacebookObject, etc.)
|
|
65
|
-
schemas/products/ # Product-specific embedded types (MediaObject, SeoObject, VideoObject)
|
|
66
|
-
index.js # Exports path to schemas/ directory
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Schema Catalog
|
|
70
|
-
|
|
71
|
-
> Field-level documentation is in JSDoc comments within each schema file. This catalog provides a quick overview.
|
|
72
|
-
|
|
73
|
-
### Accounts & Auth
|
|
74
|
-
Account, User, Store, Shop, ShopifyAPI
|
|
75
|
-
|
|
76
|
-
### Products
|
|
77
|
-
Product, ProductFeature, ProductImage, ProductRaw, ProductType, ProductTag, ProductImport, Customize, CustomField
|
|
78
|
-
|
|
79
|
-
### Collections & Groups
|
|
80
|
-
Collection, Group, GroupItem, GroupArtwork
|
|
81
|
-
|
|
82
|
-
### Orders & Fulfillment
|
|
83
|
-
Order, OrderItem, Fulfillment, Payout
|
|
84
|
-
|
|
85
|
-
### Content & Pages
|
|
86
|
-
Post, Menu, MenuItem, AnnouncementBar
|
|
87
|
-
|
|
88
|
-
### Media & Assets
|
|
89
|
-
Image, Artwork, Media, MediaUpload
|
|
90
|
-
|
|
91
|
-
### Analytics & Tracking
|
|
92
|
-
StoreEvent, LatestEvent, RecentView, SearchTerm, FavoriteItem, TrackPage, MarketingCost
|
|
93
|
-
|
|
94
|
-
### Short URLs
|
|
95
|
-
ShortUrl, ShortDomain, ShortLog, LogURL
|
|
96
|
-
|
|
97
|
-
### Jobs & Events
|
|
98
|
-
ExportJob, TransferJob, WebhookEvent
|
|
99
|
-
|
|
100
|
-
### Settings & Config
|
|
101
|
-
StoreSetting, BlockedLocation, Publication, ShopifyObject
|
|
102
|
-
|
|
103
|
-
### Reports
|
|
104
|
-
CreatorReport, ProductReport
|
|
105
|
-
|
|
106
|
-
### Reviews
|
|
107
|
-
Review
|
|
108
|
-
|
|
109
|
-
### Embedded Types (schemas/types/, schemas/products/)
|
|
110
|
-
MoneyObject, ImageObject, DimensionObject, PriceRange, SeoObject, VideoObject, MediaObject, BrandSettings, DMCASetting, FacebookObject, FooterSetting, FreeShippingSetting, GoogleAnalytics, KlaviyoObject, MerchizeSettings, SocialsObject, TopBarSettings, TrustpilotObject
|
|
111
|
-
|
|
112
|
-
## Conventions
|
|
113
|
-
|
|
114
|
-
- Each schema file exports a Mongoose Schema (not a Model) — consumers register models themselves
|
|
115
|
-
- All schemas with store data include a `store` ref field indexed for query performance
|
|
116
|
-
- Timestamps: most schemas use `created_at` / `updated_at` fields (not Mongoose timestamps option)
|
|
117
|
-
- Status fields use string enums (e.g., `'active'|'inactive'`, `'pending'|'completed'|'failed'`)
|
|
118
|
-
- Embedded types in `schemas/types/` and `schemas/products/` have `{ _id: false }`
|
|
119
|
-
- TTL indexes are used for ephemeral data (events, webhooks, page tracking)
|
|
120
|
-
- Text indexes exist on Product (title, alternative_title, code, tags) and Order (name, id, email, phone)
|
|
121
|
-
- Consumers use `schemis` to create a store: `createStore({connection, schemas})` → `store.getModel('Name')`
|
|
122
|
-
- Use `.lean()` for read-only queries (returns plain objects instead of Mongoose documents)
|
|
123
|
-
- Use `.populate()` with explicit `model` parameter for references
|
|
124
|
-
- Use `Promise.all()` for parallel queries (e.g., fetch + countDocuments)
|
|
125
|
-
- Multi-tenancy: most queries filter by `store` field
|
|
32
|
+
- **Add a field to an existing schema:** edit the schema file, add JSDoc + field def, bump `package.json` minor, commit.
|
|
33
|
+
- **Add a new schema:** create `schemas/<Name>.js` exporting `new Schema({...})`, follow conventions above, bump minor.
|
|
34
|
+
- **Add an index:** prefer `index: true` on the field; use `Schema.index({...})` for compound/text indexes; never duplicate Atlas Search coverage.
|
package/README.md
CHANGED
|
@@ -1 +1,150 @@
|
|
|
1
1
|
# @pintahub/database-schemas
|
|
2
|
+
|
|
3
|
+
Shared Mongoose schema definitions for PintaHub microservices. This package is the single source of truth for all MongoDB data models — services import schemas from here to ensure consistency.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @pintahub/database-schemas
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies (must be installed by the consumer):
|
|
12
|
+
|
|
13
|
+
- `mongoose` `^9.1.3`
|
|
14
|
+
- `schemis` `^2.0.2`
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
This package does not work standalone — it ships raw schemas that are registered through the [`schemis`](https://www.npmjs.com/package/schemis) library.
|
|
19
|
+
|
|
20
|
+
### 1. Set up the database store
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
// src/connections/database.js
|
|
24
|
+
const {createConnection, createStore} = require('schemis')
|
|
25
|
+
const schemas = require('@pintahub/database-schemas')
|
|
26
|
+
|
|
27
|
+
const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/dev'
|
|
28
|
+
const connection = createConnection(uri)
|
|
29
|
+
|
|
30
|
+
const store = createStore({
|
|
31
|
+
connection,
|
|
32
|
+
schemas
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
module.exports = store
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Use models in actions
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const {getModel} = require('../../connections/database')
|
|
42
|
+
|
|
43
|
+
const Product = getModel('Product')
|
|
44
|
+
|
|
45
|
+
const product = await Product.findOne({_id: productId, store: storeId}).lean()
|
|
46
|
+
await Product.updateOne(
|
|
47
|
+
{_id: productId},
|
|
48
|
+
{$set: {status: 'active', updated_at: Date.now()}}
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Populate references with explicit model
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
const {getModel} = require('../../connections/database')
|
|
56
|
+
|
|
57
|
+
const TransferJob = getModel('TransferJob')
|
|
58
|
+
const Product = getModel('Product')
|
|
59
|
+
const Store = getModel('Store')
|
|
60
|
+
|
|
61
|
+
const items = await TransferJob
|
|
62
|
+
.find({store: storeId})
|
|
63
|
+
.populate({path: 'product', model: Product, select: {title: 1, code: 1}})
|
|
64
|
+
.populate({path: 'destination_store', model: Store, select: {name: 1}})
|
|
65
|
+
.lean()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Directory Structure
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
schemas/ # Main schema files (~60 schemas)
|
|
72
|
+
schemas/types/ # Embedded sub-document types (BrandSettings, FacebookObject, …)
|
|
73
|
+
schemas/products/ # Product-specific embedded types (MediaObject, SeoObject, VideoObject)
|
|
74
|
+
index.js # Exports the absolute path to schemas/ for schemis to load
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Schema Catalog
|
|
78
|
+
|
|
79
|
+
> Field-level documentation lives in JSDoc comments inside each schema file. This catalog is a quick map.
|
|
80
|
+
|
|
81
|
+
### Accounts & Auth
|
|
82
|
+
Account, User, Store, Shop, ShopifyAPI
|
|
83
|
+
|
|
84
|
+
### Products
|
|
85
|
+
Product, ProductFeature, ProductImage, ProductRaw, ProductType, ProductTag, ProductImport, Customize, CustomField, FieldSetting
|
|
86
|
+
|
|
87
|
+
### Collections & Groups
|
|
88
|
+
Collection, Group, GroupItem, GroupArtwork
|
|
89
|
+
|
|
90
|
+
### Orders & Fulfillment
|
|
91
|
+
Order, OrderItem, Fulfillment, Payout
|
|
92
|
+
|
|
93
|
+
### Content & Pages
|
|
94
|
+
Post, Menu, MenuItem, AnnouncementBar
|
|
95
|
+
|
|
96
|
+
### Media & Assets
|
|
97
|
+
Image, Artwork, Media, MediaUpload
|
|
98
|
+
|
|
99
|
+
### Analytics & Tracking
|
|
100
|
+
StoreEvent, LatestEvent, RecentView, SearchTerm, FavoriteItem, TrackPage, MarketingCost
|
|
101
|
+
|
|
102
|
+
### Short URLs
|
|
103
|
+
ShortUrl, ShortDomain, ShortLog, LogURL
|
|
104
|
+
|
|
105
|
+
### Jobs & Events
|
|
106
|
+
ExportJob, TransferJob, WebhookEvent
|
|
107
|
+
|
|
108
|
+
### Settings & Config
|
|
109
|
+
StoreSetting, BlockedLocation, Publication, ShopifyObject
|
|
110
|
+
|
|
111
|
+
### Reports
|
|
112
|
+
CreatorReport, ProductReport
|
|
113
|
+
|
|
114
|
+
### Reviews
|
|
115
|
+
Review
|
|
116
|
+
|
|
117
|
+
### Embedded Types
|
|
118
|
+
`schemas/types/`: BrandSettings, DMCASetting, FacebookObject, FooterSetting, FreeShippingSetting, GoogleAnalytics, KlaviyoObject, MerchizeSettings, SocialsObject, TopBarSettings, TrustpilotObject
|
|
119
|
+
|
|
120
|
+
`schemas/products/`: MediaObject, SeoObject, VideoObject
|
|
121
|
+
|
|
122
|
+
`schemas/` (root, embedded): MoneyObject, ImageObject, DimensionObject, PriceRange, FieldSetting
|
|
123
|
+
|
|
124
|
+
## Conventions
|
|
125
|
+
|
|
126
|
+
- Each schema file exports a Mongoose **Schema** (not a Model) — consumers register models themselves via `schemis`.
|
|
127
|
+
- Multi-tenancy: most domain schemas include a `store` ObjectId ref, indexed for query performance.
|
|
128
|
+
- Timestamps: schemas use explicit `created_at` / `updated_at` Date fields (not Mongoose's `timestamps` option).
|
|
129
|
+
- Status fields use string enums (e.g. `'active'|'inactive'`, `'pending'|'completed'|'failed'`).
|
|
130
|
+
- Embedded types in `schemas/types/` and `schemas/products/` are declared with `{_id: false}`.
|
|
131
|
+
- TTL indexes are used for ephemeral data (events, webhooks, page tracking).
|
|
132
|
+
- Text indexes exist on Product (`title`, `alternative_title`, `code`, `tags`) and Order (`name`, `id`, `email`, `phone`).
|
|
133
|
+
- The Product schema also exposes a precomputed `display_title` field (`alternative_title || title`) for storefront search/display. Consumer services are responsible for setting it at write time.
|
|
134
|
+
|
|
135
|
+
## Querying Tips
|
|
136
|
+
|
|
137
|
+
- Use `.lean()` for read-only queries — returns plain objects instead of Mongoose documents.
|
|
138
|
+
- Use `.populate()` with an explicit `model` parameter when crossing collections.
|
|
139
|
+
- Use `Promise.all()` for independent queries (e.g. `find` + `countDocuments`).
|
|
140
|
+
- Always filter by `store` for multi-tenant data.
|
|
141
|
+
|
|
142
|
+
## Versioning
|
|
143
|
+
|
|
144
|
+
This package follows semver:
|
|
145
|
+
|
|
146
|
+
- **Patch** — JSDoc / docs only.
|
|
147
|
+
- **Minor** — new fields, new schemas, new indexes (backward compatible).
|
|
148
|
+
- **Major** — removing/renaming fields or schemas, changing field types, breaking index changes.
|
|
149
|
+
|
|
150
|
+
Bump the version in `package.json` whenever a schema change is committed.
|
package/package.json
CHANGED
package/schemas/Product.js
CHANGED
|
@@ -403,5 +403,74 @@ Product.index({
|
|
|
403
403
|
tags: 'text',
|
|
404
404
|
})
|
|
405
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Atlas Search index for storefront `/products/v2/search`.
|
|
408
|
+
*
|
|
409
|
+
* Full-text scored fields: display_title (primary), tags, code.
|
|
410
|
+
* Other mappings are filter/sort-only — allows the storefront to combine
|
|
411
|
+
* the full query into a single $search stage (compound.must + compound.filter + sort).
|
|
412
|
+
*/
|
|
413
|
+
Product.searchIndex({
|
|
414
|
+
name: 'products_search',
|
|
415
|
+
definition: {
|
|
416
|
+
mappings: {
|
|
417
|
+
dynamic: false,
|
|
418
|
+
fields: {
|
|
419
|
+
// ---- Full-text searchable ----
|
|
420
|
+
display_title: {
|
|
421
|
+
type: 'string',
|
|
422
|
+
analyzer: 'lucene.standard',
|
|
423
|
+
searchAnalyzer: 'lucene.standard',
|
|
424
|
+
},
|
|
425
|
+
tags: [
|
|
426
|
+
{type: 'string', analyzer: 'lucene.standard'},
|
|
427
|
+
{type: 'token', normalizer: 'lowercase'},
|
|
428
|
+
],
|
|
429
|
+
code: [
|
|
430
|
+
// Keyword analyzer: whole-string match (8-char SKU)
|
|
431
|
+
{type: 'string', analyzer: 'lucene.keyword'},
|
|
432
|
+
// Token: for $eq filter + $ne highlight exclusion
|
|
433
|
+
{type: 'token', normalizer: 'none'},
|
|
434
|
+
],
|
|
435
|
+
|
|
436
|
+
// ---- Tenant / scoping ----
|
|
437
|
+
store: {type: 'objectId'},
|
|
438
|
+
shop: {type: 'objectId'},
|
|
439
|
+
collections: {type: 'objectId'},
|
|
440
|
+
|
|
441
|
+
// ---- Enum/status filters ----
|
|
442
|
+
status: {type: 'token', normalizer: 'lowercase'},
|
|
443
|
+
visibility: {type: 'token', normalizer: 'lowercase'},
|
|
444
|
+
publications: {type: 'token', normalizer: 'lowercase'},
|
|
445
|
+
product_type: {type: 'token', normalizer: 'lowercase'},
|
|
446
|
+
|
|
447
|
+
// ---- Boolean ----
|
|
448
|
+
is_primary: {type: 'boolean'},
|
|
449
|
+
customizable: {type: 'boolean'},
|
|
450
|
+
|
|
451
|
+
// ---- Numeric filters & sort keys ----
|
|
452
|
+
sales_count: {type: 'number'},
|
|
453
|
+
views_count: {type: 'number'},
|
|
454
|
+
published_at: {type: 'date'},
|
|
455
|
+
|
|
456
|
+
// ---- Nested price_range ----
|
|
457
|
+
price_range: {
|
|
458
|
+
type: 'document',
|
|
459
|
+
dynamic: false,
|
|
460
|
+
fields: {
|
|
461
|
+
minVariantPrice: {
|
|
462
|
+
type: 'document',
|
|
463
|
+
dynamic: false,
|
|
464
|
+
fields: {
|
|
465
|
+
amount: {type: 'number'},
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
})
|
|
474
|
+
|
|
406
475
|
|
|
407
476
|
module.exports = Product
|