@happyvertical/smrt-products 0.30.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/AGENTS.md +122 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +115 -0
- package/dist/lib/__smrt-register__.d.ts +2 -0
- package/dist/lib/__smrt-register__.d.ts.map +1 -0
- package/dist/lib/app/main.d.ts +6 -0
- package/dist/lib/app/main.d.ts.map +1 -0
- package/dist/lib/chunks/ProductAssetCollection-DFPXN43q.js +64 -0
- package/dist/lib/chunks/ProductAssetCollection-DFPXN43q.js.map +1 -0
- package/dist/lib/chunks/ProductForm-DHeb2L24.js +371 -0
- package/dist/lib/chunks/ProductForm-DHeb2L24.js.map +1 -0
- package/dist/lib/chunks/Sku-DUKtbYWT.js +511 -0
- package/dist/lib/chunks/Sku-DUKtbYWT.js.map +1 -0
- package/dist/lib/chunks/SkuCollection-C0tdkEdL.js +160 -0
- package/dist/lib/chunks/SkuCollection-C0tdkEdL.js.map +1 -0
- package/dist/lib/chunks/__smrt-register__-BIgFaVKn.js +5 -0
- package/dist/lib/chunks/__smrt-register__-BIgFaVKn.js.map +1 -0
- package/dist/lib/chunks/index-i3-ci1FB.js +6 -0
- package/dist/lib/chunks/index-i3-ci1FB.js.map +1 -0
- package/dist/lib/chunks/product-store.svelte-Dayd5n3W.js +132 -0
- package/dist/lib/chunks/product-store.svelte-Dayd5n3W.js.map +1 -0
- package/dist/lib/client.d.ts +9 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/collections.d.ts +2 -0
- package/dist/lib/collections.d.ts.map +1 -0
- package/dist/lib/collections.js +12 -0
- package/dist/lib/collections.js.map +1 -0
- package/dist/lib/components.d.ts +2 -0
- package/dist/lib/components.d.ts.map +1 -0
- package/dist/lib/components.js +6 -0
- package/dist/lib/components.js.map +1 -0
- package/dist/lib/generated.d.ts +2 -0
- package/dist/lib/generated.d.ts.map +1 -0
- package/dist/lib/generated.js +5 -0
- package/dist/lib/generated.js.map +1 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +228 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/lib/collections/CategoryCollection.d.ts +20 -0
- package/dist/lib/lib/collections/CategoryCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/MaterialCollection.d.ts +20 -0
- package/dist/lib/lib/collections/MaterialCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/ProductAssetCollection.d.ts +16 -0
- package/dist/lib/lib/collections/ProductAssetCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/ProductCollection.d.ts +12 -0
- package/dist/lib/lib/collections/ProductCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/ProductVariantCollection.d.ts +10 -0
- package/dist/lib/lib/collections/ProductVariantCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/SkuCollection.d.ts +31 -0
- package/dist/lib/lib/collections/SkuCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/index.d.ts +7 -0
- package/dist/lib/lib/collections/index.d.ts.map +1 -0
- package/dist/lib/lib/components/ProductCard.svelte +173 -0
- package/dist/lib/lib/components/ProductCard.svelte.d.ts +10 -0
- package/dist/lib/lib/components/ProductCard.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/ProductForm.svelte +289 -0
- package/dist/lib/lib/components/ProductForm.svelte.d.ts +11 -0
- package/dist/lib/lib/components/ProductForm.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/TestComponent.svelte +25 -0
- package/dist/lib/lib/components/TestComponent.svelte.d.ts +7 -0
- package/dist/lib/lib/components/TestComponent.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/auto-generated/AutoForm.svelte +240 -0
- package/dist/lib/lib/components/auto-generated/AutoForm.svelte.d.ts +13 -0
- package/dist/lib/lib/components/auto-generated/AutoForm.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte +205 -0
- package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte.d.ts +14 -0
- package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/index.d.ts +12 -0
- package/dist/lib/lib/components/index.d.ts.map +1 -0
- package/dist/lib/lib/components/index.js +11 -0
- package/dist/lib/lib/features/CategoryManager.svelte +80 -0
- package/dist/lib/lib/features/CategoryManager.svelte.d.ts +7 -0
- package/dist/lib/lib/features/CategoryManager.svelte.d.ts.map +1 -0
- package/dist/lib/lib/features/ProductCatalog.svelte +299 -0
- package/dist/lib/lib/features/ProductCatalog.svelte.d.ts +8 -0
- package/dist/lib/lib/features/ProductCatalog.svelte.d.ts.map +1 -0
- package/dist/lib/lib/federation-entry.d.ts +11 -0
- package/dist/lib/lib/federation-entry.d.ts.map +1 -0
- package/dist/lib/lib/generated/index.d.ts +2 -0
- package/dist/lib/lib/generated/index.d.ts.map +1 -0
- package/dist/lib/lib/i18n.d.ts +79 -0
- package/dist/lib/lib/i18n.d.ts.map +1 -0
- package/dist/lib/lib/index.d.ts +20 -0
- package/dist/lib/lib/index.d.ts.map +1 -0
- package/dist/lib/lib/mock-smrt-client.d.ts +40 -0
- package/dist/lib/lib/mock-smrt-client.d.ts.map +1 -0
- package/dist/lib/lib/mock-smrt-client.js +129 -0
- package/dist/lib/lib/mock-smrt-client.js.map +1 -0
- package/dist/lib/lib/models/Category.d.ts +38 -0
- package/dist/lib/lib/models/Category.d.ts.map +1 -0
- package/dist/lib/lib/models/Material.d.ts +22 -0
- package/dist/lib/lib/models/Material.d.ts.map +1 -0
- package/dist/lib/lib/models/Product.d.ts +57 -0
- package/dist/lib/lib/models/Product.d.ts.map +1 -0
- package/dist/lib/lib/models/ProductAsset.d.ts +17 -0
- package/dist/lib/lib/models/ProductAsset.d.ts.map +1 -0
- package/dist/lib/lib/models/ProductVariant.d.ts +51 -0
- package/dist/lib/lib/models/ProductVariant.d.ts.map +1 -0
- package/dist/lib/lib/models/Sku.d.ts +79 -0
- package/dist/lib/lib/models/Sku.d.ts.map +1 -0
- package/dist/lib/lib/models/index.d.ts +15 -0
- package/dist/lib/lib/models/index.d.ts.map +1 -0
- package/dist/lib/lib/models/types.d.ts +41 -0
- package/dist/lib/lib/models/types.d.ts.map +1 -0
- package/dist/lib/lib/stores/index.d.ts +8 -0
- package/dist/lib/lib/stores/index.d.ts.map +1 -0
- package/dist/lib/lib/stores/index.js +7 -0
- package/dist/lib/lib/stores/product-store.client.svelte.d.ts +45 -0
- package/dist/lib/lib/stores/product-store.client.svelte.d.ts.map +1 -0
- package/dist/lib/lib/stores/product-store.client.svelte.js +147 -0
- package/dist/lib/lib/stores/product-store.svelte.d.ts +29 -0
- package/dist/lib/lib/stores/product-store.svelte.d.ts.map +1 -0
- package/dist/lib/lib/stores/product-store.svelte.js +144 -0
- package/dist/lib/lib/types.d.ts +43 -0
- package/dist/lib/lib/types.d.ts.map +1 -0
- package/dist/lib/lib/utils/index.d.ts +10 -0
- package/dist/lib/lib/utils/index.d.ts.map +1 -0
- package/dist/lib/main.d.ts +5 -0
- package/dist/lib/main.d.ts.map +1 -0
- package/dist/lib/manifest.json +3758 -0
- package/dist/lib/mcp.d.ts +14 -0
- package/dist/lib/mcp.d.ts.map +1 -0
- package/dist/lib/models.d.ts +2 -0
- package/dist/lib/models.d.ts.map +1 -0
- package/dist/lib/models.js +12 -0
- package/dist/lib/models.js.map +1 -0
- package/dist/lib/native-api-server.d.ts +7 -0
- package/dist/lib/native-api-server.d.ts.map +1 -0
- package/dist/lib/server.d.ts +11 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/simple-api-server.d.ts +7 -0
- package/dist/lib/simple-api-server.d.ts.map +1 -0
- package/dist/lib/simple-server.d.ts +6 -0
- package/dist/lib/simple-server.d.ts.map +1 -0
- package/dist/lib/smrt-knowledge.json +1584 -0
- package/dist/lib/smrt-products.css +233 -0
- package/dist/lib/stores.d.ts +2 -0
- package/dist/lib/stores.d.ts.map +1 -0
- package/dist/lib/stores.js +6 -0
- package/dist/lib/stores.js.map +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +27 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +127 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# @happyvertical/smrt-products
|
|
2
|
+
|
|
3
|
+
Product catalog — reference template for triple-consumption: npm package library, module federation, and standalone REST API server.
|
|
4
|
+
|
|
5
|
+
## Models
|
|
6
|
+
|
|
7
|
+
- **Product**: STI base. Tenant-scoped (`@TenantScoped({ mode: 'optional' })`, nullable `tenantId`). Knowledge base product with specs, tags, and the `productType` discriminator. Consumers subclass this with vertical-specific subtypes (apparel `Style`, automotive `Model`, furniture `Design`, etc.).
|
|
8
|
+
- **Material**: STI subtype. Raw input consumed by manufacturing (fabric, trim, thread, packaging, component). Meta fields: `materialKind`, `uom`, `costPerUnit`. Materials are first-class products in the catalog — the SAP/NetSuite pattern. Bills of materials in `@happyvertical/smrt-manufacturing` reference Materials by id.
|
|
9
|
+
- **ProductVariant**: standalone (NOT a Product STI subtype). Declarative axis definition: `productId`, `axisName` (e.g. `'size'`, `'color'`, `'finish'`), `allowedValues` (JSON-stored array), optional `label` and `sortOrder`. Per-SKU value pins live on `Sku.attributes`. Lives in its own `product_variants` table; `conflictColumns: ['product_id', 'axis_name', 'tenant_id']`.
|
|
10
|
+
- **Sku**: standalone. The smallest sellable / countable unit. `productId` points at a `Product` or any of its STI subtypes; `code` is the human-meaningful identifier; `attributes` JSON pins each axis value (`{ size: 'M', color: 'navy' }`). Lives in its own `product_skus` table; `conflictColumns: ['code', 'tenant_id']`. Stock balance and movement history for a Sku live in `@happyvertical/smrt-inventory`.
|
|
11
|
+
- **ProductAsset**: dedicated owned-asset join in `product_assets` with `relationship` and `sortOrder`. Tenant-scoped to match Product.
|
|
12
|
+
- **Category**: hierarchical (parentId, level, productCount). STI base, tenant-scoped.
|
|
13
|
+
|
|
14
|
+
## Vertical-specific Product subtypes
|
|
15
|
+
|
|
16
|
+
This package deliberately ships ONLY the generic primitives. Domain-specific top-level item types live in the consumer's template:
|
|
17
|
+
|
|
18
|
+
- Apparel: `Style extends Product`, `Makeup extends Product`
|
|
19
|
+
- Furniture: `Design extends Product`, `Configuration extends Product`
|
|
20
|
+
- Automotive: `Model extends Product`, `Trim extends Product`
|
|
21
|
+
- CPG: `Brand extends Product`, `Recipe extends Product`
|
|
22
|
+
|
|
23
|
+
Each is a small subclass: `@smrt()`, override `productType`, add `@meta()` fields. See `packages/template-apparel-erp` for a worked example.
|
|
24
|
+
|
|
25
|
+
## Variants — the two concepts
|
|
26
|
+
|
|
27
|
+
The framework's variant story uses two distinct primitives that look similar but do different jobs:
|
|
28
|
+
|
|
29
|
+
- **`ProductVariant`** — *axis declaration*. "For product X, axis `size` accepts the values `[XS, S, M, L, XL]`." Drives form/UI choices.
|
|
30
|
+
- **`Sku.attributes`** — *per-unit value pins*. `{ color: 'navy', size: 'M' }` on each concrete sellable SKU.
|
|
31
|
+
|
|
32
|
+
Both live in this package — all catalog shapes are here. Stock motion (where the SKU is, how many, history) lives in `@happyvertical/smrt-inventory`.
|
|
33
|
+
|
|
34
|
+
There is deliberately no separate "catalog grouping above SKU" row (a la a "Navy colorway" row sitting between the Product and its Skus). Group SKUs by axis value via `attributes.color = 'navy'` queries; attach per-axis-value assets via `ProductAsset` rows with relationship metadata. Matches how Shopify, Stripe Products, and most e-commerce platforms model the same shape.
|
|
35
|
+
|
|
36
|
+
## Collections
|
|
37
|
+
|
|
38
|
+
- **ProductCollection** — base. Polymorphic queries return the correct subclass instance per row.
|
|
39
|
+
- **MaterialCollection** — STI-filtered subclass. Override `_itemClass`; framework auto-filters by `_meta_type`.
|
|
40
|
+
- **ProductVariantCollection** — standalone collection over `product_variants`. Helpers: `findForProduct(productId)`, `findAxis(productId, axisName)`.
|
|
41
|
+
- **SkuCollection** — standalone collection over `product_skus`. Helpers: `findByCode`, `findByBarcode`, `findByProduct`, `findByParent`, `findActive`.
|
|
42
|
+
- **CategoryCollection** — standard Category CRUD with `getRootCategories()`.
|
|
43
|
+
- **ProductAssetCollection** — direct access to `product_assets` rows and product asset helper wrappers.
|
|
44
|
+
|
|
45
|
+
## Multi-tenancy
|
|
46
|
+
|
|
47
|
+
Optional. With `withTenant(id, fn)` (from `@happyvertical/smrt-tenancy`), all queries auto-filter and inserts auto-stamp `tenantId`. Without it, models behave globally (`tenantId = null`). This lets the same package serve both shared reference catalogs and per-merchant SaaS catalogs.
|
|
48
|
+
|
|
49
|
+
## Triple-Consumption Pattern
|
|
50
|
+
|
|
51
|
+
Same codebase consumed three ways:
|
|
52
|
+
1. **NPM library**: import classes directly
|
|
53
|
+
2. **Module federation**: runtime component sharing (experimental)
|
|
54
|
+
3. **Standalone API**: `startRestServer([Product, Category])`
|
|
55
|
+
|
|
56
|
+
## Virtual Modules (Vite)
|
|
57
|
+
|
|
58
|
+
Auto-generated via Vite plugins: `@happyvertical/smrt-client` (TypeScript client), `@happyvertical/smrt-types`, `@happyvertical/smrt-routes` (Express), `@happyvertical/smrt-mcp`, `@happyvertical/smrt-manifest`.
|
|
59
|
+
|
|
60
|
+
Svelte 5 stores use runes (`$state`, `$derived`, `$effect`). Separate `product-store.server.svelte.ts` vs `product-store.client.svelte.ts` for SSR safety.
|
|
61
|
+
|
|
62
|
+
## Schema migrations (Phase 1 release)
|
|
63
|
+
|
|
64
|
+
This package's schema changed shape between the previous release and the Phase 1 apparel-ERP release. Consumers upgrading need to migrate two tables.
|
|
65
|
+
|
|
66
|
+
### `Sku` rows moved tables
|
|
67
|
+
|
|
68
|
+
Previously `Sku` shipped in `@happyvertical/smrt-inventory` under table `inventory_skus`. It now lives here under `product_skus`. Cross-package refs (`StockLevel.skuId`, `BomLine.componentSkuId`) carry plain string ids that still resolve.
|
|
69
|
+
|
|
70
|
+
**Upgrade procedure** (works on SQLite + Postgres; does NOT rely on `CREATE TABLE AS` which strips constraints):
|
|
71
|
+
|
|
72
|
+
1. **Boot the new version once** so the framework's lazy `syncSchema` creates `product_skus` with the right PRIMARY KEY, NOT NULL, UNIQUE (`code`, `tenant_id`), and indexes derived from the `Sku` model.
|
|
73
|
+
|
|
74
|
+
2. **Idempotently copy rows**:
|
|
75
|
+
|
|
76
|
+
```sql
|
|
77
|
+
BEGIN;
|
|
78
|
+
INSERT INTO product_skus (
|
|
79
|
+
id, slug, context, created_at, updated_at,
|
|
80
|
+
tenant_id, product_id, code, barcode, name,
|
|
81
|
+
attributes, weight_grams, parent_sku_id, active
|
|
82
|
+
)
|
|
83
|
+
SELECT
|
|
84
|
+
id, slug, context, created_at, updated_at,
|
|
85
|
+
tenant_id, product_id, code, barcode, name,
|
|
86
|
+
attributes, weight_grams, parent_sku_id, active
|
|
87
|
+
FROM inventory_skus
|
|
88
|
+
WHERE NOT EXISTS (
|
|
89
|
+
SELECT 1 FROM product_skus p WHERE p.id = inventory_skus.id
|
|
90
|
+
);
|
|
91
|
+
COMMIT;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
3. **Drop the legacy table** once row counts match:
|
|
95
|
+
|
|
96
|
+
```sql
|
|
97
|
+
DROP TABLE IF EXISTS inventory_skus;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `ProductVariant` changed shape entirely
|
|
101
|
+
|
|
102
|
+
Previously a Product STI subtype carrying `parentProductId` + `axisValues` JSON inside `_meta_data` on the shared `products` table (`_meta_type='@happyvertical/smrt-products:ProductVariant'`). It is now a **standalone model** on its own `product_variants` table, with columns `productId`, `axisName`, `allowedValues`, `label`, `sortOrder`.
|
|
103
|
+
|
|
104
|
+
**There is no automatic data conversion.** The old "catalog grouping above SKU" concept doesn't map 1:1 to the new "per-axis declaration" concept. Recommended procedure:
|
|
105
|
+
|
|
106
|
+
1. Inspect the old rows: `SELECT * FROM products WHERE _meta_type = '@happyvertical/smrt-products:ProductVariant';`. Treat them as historical reference.
|
|
107
|
+
2. Re-author axis declarations against the new shape (one `ProductVariant` row per `(productId, axisName)` pair, with `allowedValues` listing the values).
|
|
108
|
+
3. Once the new declarations are populated and verified, remove the legacy rows: `DELETE FROM products WHERE _meta_type = '@happyvertical/smrt-products:ProductVariant';`.
|
|
109
|
+
|
|
110
|
+
If you had per-colorway / per-variant images attached via `ProductAsset` rows pointing at old ProductVariant ids, repoint those to the parent Product id; group-by-axis-value queries on `Sku.attributes` cover the same use case at the SKU level.
|
|
111
|
+
|
|
112
|
+
## Gotchas
|
|
113
|
+
|
|
114
|
+
- **`ProductVariant` and `Sku` are NOT Product STI subtypes** — they each have their own table (`product_variants`, `product_skus`) because their shapes don't fit the Product schema. Don't try to query them via `ProductCollection`.
|
|
115
|
+
- **`npm run build` emits the published library surface directly**: package consumers read model, collection, and helper exports from `dist/lib`. Cross-package imports from this package should target `/models` or `/collections` subpaths (not the main entry) when the consumer isn't a vite app — the main entry pulls in vite virtual modules.
|
|
116
|
+
- **Use `npm run build:all` only when you need standalone or federation bundles** in addition to the library output
|
|
117
|
+
- **Constructor must explicitly assign all properties**: `Object.assign` doesn't work reliably with decorators
|
|
118
|
+
- **STI subtype-specific fields use `Meta<T>`** — declare them as `fieldName: Meta<FieldType> = defaultValue`. The AST scanner detects the `Meta<T>` type wrapper at build time and routes the field through `_meta_data` JSON storage instead of materializing it as a column on the parent's table. Do **NOT** use the runtime `@meta()` decorator on STI children — it never reaches the manifest, so the schema generator treats the field as an ordinary column on the parent table and the framework's hydration path can't tell it's meta. Override `productType` on each subclass.
|
|
119
|
+
- **Tenant-scoped STI children must repeat `@TenantScoped`** — `@TenantScoped` registers per concrete className, so `Material extends Product` inheriting from a tenant-scoped `Product` is NOT automatically tenant-scoped itself. `MaterialCollection.list/save` passes `'Material'` to the tenant interceptor, which looks up `'Material'` (not `'Product'`) in the per-class registry. Without an explicit `@TenantScoped({ mode: 'optional' })` on `Material`, material rows skip the tenant auto-filter and auto-populate.
|
|
120
|
+
- **STI children must repeat `@smrt({ api, mcp, cli })` generation config (S5 #1406)** — like `@TenantScoped`, the generation config is registered per concrete className and is NOT inherited from the STI parent. An empty `@smrt()` on a child resolves `getConfig(child).api/.mcp` to `undefined`, which the REST and MCP generators treat as "expose EVERYTHING" — including `delete` and write-capable MCP tools — even when the parent deliberately restricts its surface. The package's `mcp.ts` generator enumerates the whole registry, so an under-configured child ships a wide-open surface silently. Re-declare the parent's `api`/`mcp`/`cli` posture on every subtype (see `Material`). Consumer subtypes (apparel `Style`/`Makeup`, automotive `Model`/`Trim`, …) must do the same.
|
|
121
|
+
- **Two-tenant same-slug is currently a hard error** for tenant-scoped STI bases (`Product`, `Category`). The core schema generator hardcodes the STI unique index as `(slug, context, _meta_type)` and does NOT include `tenant_id` even for `@TenantScoped` classes. Two tenants saving a row with the same slug + context + meta_type collide at the SQL layer. Workaround: namespace slugs per tenant on the application side (e.g. `${tenantId}-widget`) until the upstream framework fix lands.
|
|
122
|
+
- **Module Federation marked experimental**: may change
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright <2025> <Happy Vertical Corporation>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @happyvertical/smrt-products
|
|
2
|
+
|
|
3
|
+
Product catalog reference template demonstrating triple-consumption: npm package library, module federation, and standalone REST API server.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @happyvertical/smrt-products
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Import as npm library
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Product, ProductCollection, Category } from '@happyvertical/smrt-products';
|
|
17
|
+
import { startServer } from '@happyvertical/smrt-products';
|
|
18
|
+
import { generateMCPServer } from '@happyvertical/smrt-products';
|
|
19
|
+
import { AssetCollection } from '@happyvertical/smrt-assets';
|
|
20
|
+
|
|
21
|
+
// Start standalone REST API server
|
|
22
|
+
const { shutdown } = await startServer();
|
|
23
|
+
|
|
24
|
+
const products = await ProductCollection.create();
|
|
25
|
+
const assets = await AssetCollection.create();
|
|
26
|
+
const product = await products.create({
|
|
27
|
+
name: 'Demo Product',
|
|
28
|
+
price: 29.99,
|
|
29
|
+
});
|
|
30
|
+
const hero = await assets.create({
|
|
31
|
+
name: 'demo-product-hero.jpg',
|
|
32
|
+
sourceUri: 'file:///tmp/demo-product-hero.jpg',
|
|
33
|
+
mimeType: 'image/jpeg',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await product.addAsset(hero, 'hero');
|
|
37
|
+
await products.addAsset(product.id!, hero, 'gallery', 1);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Three consumption modes
|
|
41
|
+
|
|
42
|
+
1. **NPM library** -- import classes, components, and stores directly
|
|
43
|
+
2. **Module federation** -- runtime component sharing (experimental)
|
|
44
|
+
3. **Standalone API** -- `startServer()` launches Express with auto-generated routes
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
### Top-Level Exports
|
|
49
|
+
|
|
50
|
+
| Export | Description |
|
|
51
|
+
|--------|------------|
|
|
52
|
+
| `startServer` | Launch standalone REST API server |
|
|
53
|
+
| `generateMCPServer` | Generate MCP server for AI tool integration |
|
|
54
|
+
| `demonstrateClient` | Demo of auto-generated TypeScript client |
|
|
55
|
+
| `startAll` | Start all services (REST + MCP) |
|
|
56
|
+
|
|
57
|
+
### Models (from `lib/models`)
|
|
58
|
+
|
|
59
|
+
| Export | Description |
|
|
60
|
+
|--------|------------|
|
|
61
|
+
| `Product` | STI-enabled product with specs and tags |
|
|
62
|
+
| `Category` | Hierarchical category (parentId, level, productCount), STI enabled |
|
|
63
|
+
| `ProductAsset` | Dedicated owned-asset join stored in `product_assets` with `relationship` and `sortOrder`; intentionally not tenant-scoped because `Product` is not tenant-scoped |
|
|
64
|
+
|
|
65
|
+
### Collections (from `lib/collections`)
|
|
66
|
+
|
|
67
|
+
| Export | Description |
|
|
68
|
+
|--------|------------|
|
|
69
|
+
| `ProductCollection` | CRUD plus `findByManufacturer()`, `findInStock()`, and owned asset wrappers |
|
|
70
|
+
| `ProductAssetCollection` | Direct access to `product_assets` rows plus asset helper wrappers |
|
|
71
|
+
|
|
72
|
+
### Components (from `lib/components`)
|
|
73
|
+
|
|
74
|
+
| Export | Description |
|
|
75
|
+
|--------|------------|
|
|
76
|
+
| `ProductCard` | Svelte 5 product display component |
|
|
77
|
+
| `ProductForm` | Svelte 5 product edit form |
|
|
78
|
+
|
|
79
|
+
### Stores (from `lib/stores`)
|
|
80
|
+
|
|
81
|
+
| Export | Description |
|
|
82
|
+
|--------|------------|
|
|
83
|
+
| `ProductStoreClass` | Svelte 5 rune-based state management class |
|
|
84
|
+
| `productStore` | Singleton store instance |
|
|
85
|
+
|
|
86
|
+
### Utilities (from `lib/utils`)
|
|
87
|
+
|
|
88
|
+
| Export | Description |
|
|
89
|
+
|--------|------------|
|
|
90
|
+
| `formatPrice` | Format number as USD currency string |
|
|
91
|
+
| `formatDate` | Format date as human-readable string |
|
|
92
|
+
| `slugify` | Convert text to URL-friendly slug |
|
|
93
|
+
| `generateId` | Generate random ID string |
|
|
94
|
+
|
|
95
|
+
### Virtual Modules (Vite plugin)
|
|
96
|
+
|
|
97
|
+
| Export | Description |
|
|
98
|
+
|--------|------------|
|
|
99
|
+
| `createClient` | Auto-generated TypeScript API client |
|
|
100
|
+
| `setupRoutes` | Auto-generated Express routes |
|
|
101
|
+
| `createMCPServer` | Auto-generated MCP server |
|
|
102
|
+
| `manifest` | SMRT object metadata |
|
|
103
|
+
|
|
104
|
+
Owned asset helpers are available on both `Product` and `ProductCollection` via
|
|
105
|
+
`getAssets()`, `addAsset()`, and `removeAsset()`. Common relationships include
|
|
106
|
+
`hero`, `gallery`, `attachment`, and `thumbnail`.
|
|
107
|
+
|
|
108
|
+
## Dependencies
|
|
109
|
+
|
|
110
|
+
| Package | Purpose |
|
|
111
|
+
|---------|---------|
|
|
112
|
+
| `@happyvertical/smrt-core` | SmrtObject/SmrtCollection base classes, REST server, MCP generator |
|
|
113
|
+
| `@happyvertical/smrt-assets` | Shared Asset / AssetCollection types used by product-owned asset helpers |
|
|
114
|
+
| `@happyvertical/sql` | Database operations |
|
|
115
|
+
| `@happyvertical/ai` | AI integration |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../../src/__smrt-register__.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/app/main.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,WAAW,CAAC;AAInB,QAAA,MAAM,GAAG;;;uBAEP,CAAC;AAEH,eAAe,GAAG,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getOwnedAssetsFromCollection, addOwnedAssetFromCollection, removeOwnedAssetFromCollection } from "@happyvertical/smrt-assets";
|
|
2
|
+
import { smrt, SmrtJunction } from "@happyvertical/smrt-core";
|
|
3
|
+
import { a as ProductAsset } from "./Sku-DUKtbYWT.js";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
8
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
9
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
10
|
+
if (decorator = decorators[i])
|
|
11
|
+
result = decorator(result) || result;
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "", value);
|
|
15
|
+
let ProductAssetCollection = class extends SmrtJunction {
|
|
16
|
+
leftField = "productId";
|
|
17
|
+
rightField = "assetId";
|
|
18
|
+
productCollectionPromise = null;
|
|
19
|
+
async getProductCollection() {
|
|
20
|
+
if (!this.productCollectionPromise) {
|
|
21
|
+
const { ProductCollection } = await import("./SkuCollection-C0tdkEdL.js").then((n) => n.b);
|
|
22
|
+
this.productCollectionPromise = ProductCollection.create({ db: this.db });
|
|
23
|
+
}
|
|
24
|
+
return this.productCollectionPromise;
|
|
25
|
+
}
|
|
26
|
+
async getAssets(productId, relationship) {
|
|
27
|
+
return getOwnedAssetsFromCollection(
|
|
28
|
+
await this.getProductCollection(),
|
|
29
|
+
productId,
|
|
30
|
+
relationship
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
async addAsset(productId, asset, relationship = "attachment", sortOrder = 0) {
|
|
34
|
+
await addOwnedAssetFromCollection(
|
|
35
|
+
await this.getProductCollection(),
|
|
36
|
+
"Product",
|
|
37
|
+
productId,
|
|
38
|
+
asset,
|
|
39
|
+
relationship,
|
|
40
|
+
sortOrder
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
async removeAsset(productId, assetId, relationship) {
|
|
44
|
+
await removeOwnedAssetFromCollection(
|
|
45
|
+
await this.getProductCollection(),
|
|
46
|
+
"Product",
|
|
47
|
+
productId,
|
|
48
|
+
assetId,
|
|
49
|
+
relationship
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
__publicField(ProductAssetCollection, "_itemClass", ProductAsset);
|
|
54
|
+
ProductAssetCollection = __decorateClass([
|
|
55
|
+
smrt({
|
|
56
|
+
api: false,
|
|
57
|
+
mcp: false,
|
|
58
|
+
cli: false
|
|
59
|
+
})
|
|
60
|
+
], ProductAssetCollection);
|
|
61
|
+
export {
|
|
62
|
+
ProductAssetCollection
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=ProductAssetCollection-DFPXN43q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductAssetCollection-DFPXN43q.js","sources":["../../../src/lib/collections/ProductAssetCollection.ts"],"sourcesContent":["import type { Asset } from '@happyvertical/smrt-assets';\nimport {\n addOwnedAssetFromCollection,\n getOwnedAssetsFromCollection,\n removeOwnedAssetFromCollection,\n} from '@happyvertical/smrt-assets';\nimport type { SmrtCollectionOptions } from '@happyvertical/smrt-core';\nimport { SmrtJunction, smrt } from '@happyvertical/smrt-core';\nimport { ProductAsset } from '../models/ProductAsset';\nimport type { ProductCollection } from './ProductCollection';\n\nexport interface ProductAssetCollectionOptions extends SmrtCollectionOptions {}\n\n@smrt({\n api: false,\n mcp: false,\n cli: false,\n})\nexport class ProductAssetCollection extends SmrtJunction<ProductAsset> {\n static readonly _itemClass = ProductAsset;\n protected leftField = 'productId';\n protected rightField = 'assetId';\n\n private productCollectionPromise: Promise<ProductCollection> | null = null;\n\n private async getProductCollection(): Promise<ProductCollection> {\n if (!this.productCollectionPromise) {\n const { ProductCollection } = await import('./ProductCollection');\n this.productCollectionPromise = ProductCollection.create({ db: this.db });\n }\n\n return this.productCollectionPromise;\n }\n\n async getAssets(productId: string, relationship?: string): Promise<Asset[]> {\n return getOwnedAssetsFromCollection(\n await this.getProductCollection(),\n productId,\n relationship,\n );\n }\n\n async addAsset(\n productId: string,\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n await addOwnedAssetFromCollection(\n await this.getProductCollection(),\n 'Product',\n productId,\n asset,\n relationship,\n sortOrder,\n );\n }\n\n async removeAsset(\n productId: string,\n assetId: string,\n relationship?: string,\n ): Promise<void> {\n await removeOwnedAssetFromCollection(\n await this.getProductCollection(),\n 'Product',\n productId,\n assetId,\n relationship,\n );\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAkBO,IAAM,yBAAN,cAAqC,aAA2B;AAAA,EAE3D,YAAY;AAAA,EACZ,aAAa;AAAA,EAEf,2BAA8D;AAAA,EAEtE,MAAc,uBAAmD;AAC/D,QAAI,CAAC,KAAK,0BAA0B;AAClC,YAAM,EAAE,kBAAA,IAAsB,MAAM,OAAO,6BAAqB,EAAA,KAAA,OAAA,EAAA,CAAA;AAChE,WAAK,2BAA2B,kBAAkB,OAAO,EAAE,IAAI,KAAK,IAAI;AAAA,IAC1E;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAU,WAAmB,cAAyC;AAC1E,WAAO;AAAA,MACL,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,SACJ,WACA,OACA,eAAe,cACf,YAAY,GACG;AACf,UAAM;AAAA,MACJ,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,YACJ,WACA,SACA,cACe;AACf,UAAM;AAAA,MACJ,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AACF;AApDE,cADW,wBACK,cAAa,YAAA;AADlB,yBAAN,gBAAA;AAAA,EALN,KAAK;AAAA,IACJ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EAAA,CACN;AAAA,GACY,sBAAA;"}
|