@happyvertical/smrt-products 0.32.0 → 0.32.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.
Files changed (38) hide show
  1. package/dist/lib/chunks/{ProductAssetCollection-DFPXN43q.js → ProductAssetCollection-B93HdSlX.js} +3 -3
  2. package/dist/lib/chunks/{ProductAssetCollection-DFPXN43q.js.map → ProductAssetCollection-B93HdSlX.js.map} +1 -1
  3. package/dist/lib/chunks/{ProductForm-DHeb2L24.js → ProductForm-p4-xbubZ.js} +27 -1
  4. package/dist/lib/chunks/ProductForm-p4-xbubZ.js.map +1 -0
  5. package/dist/lib/chunks/{Sku-DUKtbYWT.js → Sku-sl6xf1PR.js} +2 -19
  6. package/dist/lib/chunks/Sku-sl6xf1PR.js.map +1 -0
  7. package/dist/lib/chunks/{SkuCollection-C0tdkEdL.js → SkuCollection-f_a3k93c.js} +2 -2
  8. package/dist/lib/chunks/{SkuCollection-C0tdkEdL.js.map → SkuCollection-f_a3k93c.js.map} +1 -1
  9. package/dist/lib/collections.js +2 -2
  10. package/dist/lib/components.js +1 -1
  11. package/dist/lib/index.js +5 -5
  12. package/dist/lib/lib/components/ProductCard.svelte +1 -1
  13. package/dist/lib/lib/components/ProductForm.svelte +3 -3
  14. package/dist/lib/lib/components/auto-generated/AutoForm.svelte +7 -6
  15. package/dist/lib/lib/components/auto-generated/AutoForm.svelte.d.ts.map +1 -1
  16. package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte +8 -8
  17. package/dist/lib/lib/features/ProductCatalog.svelte +30 -20
  18. package/dist/lib/lib/features/ProductCatalog.svelte.d.ts.map +1 -1
  19. package/dist/lib/lib/mock-smrt-client.d.ts +2 -20
  20. package/dist/lib/lib/mock-smrt-client.d.ts.map +1 -1
  21. package/dist/lib/lib/mock-smrt-client.js +10 -8
  22. package/dist/lib/lib/mock-smrt-client.js.map +1 -1
  23. package/dist/lib/lib/models/Category.d.ts +0 -4
  24. package/dist/lib/lib/models/Category.d.ts.map +1 -1
  25. package/dist/lib/lib/models/Product.d.ts +0 -2
  26. package/dist/lib/lib/models/Product.d.ts.map +1 -1
  27. package/dist/lib/lib/stores/product-store.svelte.d.ts +1 -1
  28. package/dist/lib/lib/stores/product-store.svelte.d.ts.map +1 -1
  29. package/dist/lib/lib/utils/index.d.ts.map +1 -1
  30. package/dist/lib/manifest.json +2 -90
  31. package/dist/lib/models.js +1 -1
  32. package/dist/lib/smrt-knowledge.json +5 -13
  33. package/dist/lib/smrt-products.css +3 -3
  34. package/dist/lib/utils.js +1 -1
  35. package/dist/lib/utils.js.map +1 -1
  36. package/package.json +7 -7
  37. package/dist/lib/chunks/ProductForm-DHeb2L24.js.map +0 -1
  38. package/dist/lib/chunks/Sku-DUKtbYWT.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { getOwnedAssetsFromCollection, addOwnedAssetFromCollection, removeOwnedAssetFromCollection } from "@happyvertical/smrt-assets";
2
2
  import { smrt, SmrtJunction } from "@happyvertical/smrt-core";
3
- import { a as ProductAsset } from "./Sku-DUKtbYWT.js";
3
+ import { a as ProductAsset } from "./Sku-sl6xf1PR.js";
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -18,7 +18,7 @@ let ProductAssetCollection = class extends SmrtJunction {
18
18
  productCollectionPromise = null;
19
19
  async getProductCollection() {
20
20
  if (!this.productCollectionPromise) {
21
- const { ProductCollection } = await import("./SkuCollection-C0tdkEdL.js").then((n) => n.b);
21
+ const { ProductCollection } = await import("./SkuCollection-f_a3k93c.js").then((n) => n.b);
22
22
  this.productCollectionPromise = ProductCollection.create({ db: this.db });
23
23
  }
24
24
  return this.productCollectionPromise;
@@ -61,4 +61,4 @@ ProductAssetCollection = __decorateClass([
61
61
  export {
62
62
  ProductAssetCollection
63
63
  };
64
- //# sourceMappingURL=ProductAssetCollection-DFPXN43q.js.map
64
+ //# sourceMappingURL=ProductAssetCollection-B93HdSlX.js.map
@@ -1 +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;"}
1
+ {"version":3,"file":"ProductAssetCollection-B93HdSlX.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;"}
@@ -225,6 +225,32 @@ function ProductForm($$anchor, $$props) {
225
225
  tags: product().tags?.join(", ") || ""
226
226
  });
227
227
  let errors = $.state($.proxy({}));
228
+ function validateForm() {
229
+ $.set(errors, {}, true);
230
+ if (!formData.name.trim()) {
231
+ $.get(errors).name = "Product name is required";
232
+ }
233
+ if (formData.price < 0) {
234
+ $.get(errors).price = "Price must be non-negative";
235
+ }
236
+ return Object.keys($.get(errors)).length === 0;
237
+ }
238
+ function handleSubmit(event) {
239
+ event.preventDefault();
240
+ if (!validateForm()) {
241
+ return;
242
+ }
243
+ const productData = {
244
+ ...product(),
245
+ name: formData.name.trim(),
246
+ description: formData.description.trim() || void 0,
247
+ price: formData.price,
248
+ inStock: formData.inStock,
249
+ category: formData.category.trim(),
250
+ tags: formData.tags ? formData.tags.split(",").map((tag) => tag.trim()).filter(Boolean) : []
251
+ };
252
+ $$props.onSubmit(productData);
253
+ }
228
254
  var form = root();
229
255
  var div = $.child(form);
230
256
  var label = $.child(div);
@@ -368,4 +394,4 @@ export {
368
394
  ProductCard as P,
369
395
  ProductForm as a
370
396
  };
371
- //# sourceMappingURL=ProductForm-DHeb2L24.js.map
397
+ //# sourceMappingURL=ProductForm-p4-xbubZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductForm-p4-xbubZ.js","sources":["../../../src/lib/components/ProductCard.svelte","../../../src/lib/i18n.ts","../../../src/lib/components/ProductForm.svelte"],"sourcesContent":["<script lang=\"ts\">\nimport type { ProductData } from '../types';\n\ninterface Props {\n product: ProductData;\n onEdit?: (product: ProductData) => void;\n onDelete?: (id: string) => void;\n}\n\nconst { product, onEdit, onDelete }: Props = $props();\n</script>\n\n<div class=\"product-card\">\n <div class=\"product-header\">\n <h3 class=\"product-name\">{product.name}</h3>\n {#if product.manufacturer}\n <div class=\"product-manufacturer\">{product.manufacturer}</div>\n {/if}\n </div>\n\n {#if product.model}\n <div class=\"product-model\">Model: {product.model}</div>\n {/if}\n\n {#if product.description}\n <p class=\"product-description\">{product.description}</p>\n {/if}\n\n <div class=\"product-meta\">\n {#if product.category}\n <div class=\"product-category\">Category: {product.category}</div>\n {/if}\n \n {#if product.tags && product.tags.length > 0}\n <div class=\"product-tags\">\n {#each product.tags as tag}\n <span class=\"tag\">{tag}</span>\n {/each}\n </div>\n {/if}\n </div>\n \n <div class=\"product-actions\">\n {#if onEdit}\n <button type=\"button\" onclick={() => onEdit?.(product)} class=\"edit-btn\">\n Edit\n </button>\n {/if}\n \n {#if onDelete}\n <button type=\"button\" onclick={() => onDelete?.(product.id)} class=\"delete-btn\">\n Delete\n </button>\n {/if}\n </div>\n</div>\n\n<style>\n .product-card {\n border: 1px solid var(--smrt-color-outline-variant, #e2e8f0);\n border-radius: var(--smrt-radius-md, 8px);\n padding: 1rem;\n background: var(--smrt-color-surface, #fff);\n box-shadow: var(--smrt-elevation-1, 0 1px 3px color-mix(in srgb, var(--smrt-color-shadow, #000) 10%, transparent));\n transition: box-shadow 0.2s;\n }\n\n .product-card:hover {\n box-shadow: var(--smrt-elevation-2, 0 4px 6px color-mix(in srgb, var(--smrt-color-shadow, #000) 10%, transparent));\n }\n \n .product-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 0.5rem;\n }\n \n .product-name {\n margin: 0;\n font-size: var(--smrt-typography-title-medium-size, 1.125rem);\n font-weight: var(--smrt-typography-weight-semibold, 600);\n color: var(--smrt-color-on-surface, #1f2937);\n }\n\n .product-manufacturer {\n font-size: var(--smrt-typography-title-small-size, 0.875rem);\n font-weight: var(--smrt-typography-weight-medium, 500);\n color: var(--smrt-color-on-surface-variant, #6b7280);\n }\n\n .product-model {\n font-size: var(--smrt-typography-body-medium-size, 0.875rem);\n color: var(--smrt-color-on-surface-variant, #6b7280);\n margin-bottom: 0.5rem;\n }\n\n .product-category {\n font-size: var(--smrt-typography-label-medium-size, 0.75rem);\n font-weight: var(--smrt-typography-weight-medium, 500);\n color: var(--smrt-color-on-surface, #374151);\n background: var(--smrt-color-surface-container, #f3f4f6);\n padding: 0.25rem 0.5rem;\n border-radius: var(--smrt-radius-sm, 4px);\n display: inline-block;\n margin-bottom: 0.5rem;\n }\n \n .product-description {\n margin: 0.5rem 0;\n color: var(--smrt-color-on-surface-variant, #6b7280);\n font-size: var(--smrt-typography-body-medium-size, 0.875rem);\n line-height: var(--smrt-typography-body-medium-line-height, 1.4);\n }\n \n .product-meta {\n margin: 0.75rem 0;\n }\n \n \n .product-tags {\n margin-top: 0.5rem;\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n }\n \n .tag {\n background: var(--smrt-color-surface-container, #f3f4f6);\n color: var(--smrt-color-on-surface, #374151);\n padding: 0.125rem 0.5rem;\n border-radius: var(--smrt-radius-full, 9999px);\n font-size: var(--smrt-typography-label-medium-size, 0.75rem);\n }\n\n .product-actions {\n display: flex;\n gap: 0.5rem;\n margin-top: 1rem;\n padding-top: 0.75rem;\n border-top: 1px solid var(--smrt-color-outline-variant, #f3f4f6);\n }\n \n .edit-btn, .delete-btn {\n padding: 0.375rem 0.75rem;\n border-radius: var(--smrt-radius-sm, 4px);\n font-size: var(--smrt-typography-label-large-size, 0.875rem);\n font-weight: var(--smrt-typography-weight-medium, 500);\n border: 1px solid;\n cursor: pointer;\n transition: all 0.2s;\n }\n \n .edit-btn {\n background: var(--smrt-color-surface-container-low, #f9fafb);\n border-color: var(--smrt-color-outline-variant, #d1d5db);\n color: var(--smrt-color-on-surface, #374151);\n }\n\n .edit-btn:hover {\n background: var(--smrt-color-surface-container, #f3f4f6);\n }\n\n .delete-btn {\n background: var(--smrt-color-error-container, #fef2f2);\n border-color: var(--smrt-color-error, #fecaca);\n color: var(--smrt-color-error, #dc2626);\n }\n\n .delete-btn:hover {\n background: var(--smrt-color-error-container, #fee2e2);\n }\n</style>","import { defineMessages } from '@happyvertical/smrt-ui/i18n';\n\nexport const M = defineMessages({\n // App\n 'products.app.categories_coming_soon': 'Category management coming soon...',\n 'products.app.analytics_coming_soon': 'Analytics dashboard coming soon...',\n\n // AppLayout\n 'products.app_layout.service_title': 'Product Service',\n 'products.app_layout.footer_copyright':\n '2024 SMRT Product Service - Auto-generated with ❤️',\n 'products.app_layout.api_docs': 'API Docs',\n 'products.app_layout.mcp_tools': 'MCP Tools',\n\n // DemoPage\n 'products.demo_page.title': 'SMRT Framework Demo',\n 'products.demo_page.subtitle':\n 'Define Once, Consume Everywhere - Progressive Customization',\n 'products.demo_page.custom_components_tab': 'Custom Components',\n 'products.demo_page.auto_generated_heading':\n 'Auto-Generated UI from SMRT Object',\n 'products.demo_page.auto_generated_description':\n 'This form is automatically generated from the Product class definition.\\n The field types, labels, and validation rules are inferred from the TypeScript schema.',\n 'products.demo_page.generated_form_heading': 'Generated Form',\n 'products.demo_page.auto_form_title': 'Auto-Generated Product Form',\n 'products.demo_page.generated_display_heading': 'Generated Display',\n 'products.demo_page.custom_components_heading':\n 'Custom Components with SMRT Integration',\n 'products.demo_page.custom_components_description':\n 'These are hand-crafted components that still leverage the SMRT data structure\\n but provide custom UI/UX for specific business requirements.',\n 'products.demo_page.custom_form_heading': 'Custom Form',\n 'products.demo_page.custom_display_heading': 'Custom Display',\n 'products.demo_page.progressive_heading': 'Progressive Customization',\n 'products.demo_page.progressive_description':\n 'Start with auto-generated components, then progressively customize as needed.\\n Both approaches use the same underlying SMRT Product model.',\n 'products.demo_page.feature_zero_config': '✅ Zero configuration',\n 'products.demo_page.feature_instant_ui': '✅ Instant UI from schema',\n 'products.demo_page.feature_type_safe': '✅ Type-safe by default',\n 'products.demo_page.feature_prototyping': '⚡ Perfect for prototyping',\n 'products.demo_page.custom_components_label': '🎨 Custom Components',\n 'products.demo_page.feature_tailored_ux': '✅ Tailored UX',\n 'products.demo_page.feature_business_workflows':\n '✅ Business-specific workflows',\n 'products.demo_page.feature_advanced_interactions':\n '✅ Advanced interactions',\n 'products.demo_page.simple_auto_form_title': 'Auto Form',\n 'products.demo_page.benefits_heading': 'SMRT Framework Benefits',\n 'products.demo_page.benefit_define_once_label': 'Define Once:',\n 'products.demo_page.benefit_define_once_text':\n 'Product class with @smrt decorator',\n 'products.demo_page.benefit_auto_generate_label': 'Auto-Generate:',\n 'products.demo_page.benefit_auto_generate_text':\n 'REST APIs, MCP tools, TypeScript clients, default UI',\n 'products.demo_page.benefit_progressive_label': 'Progressive Enhancement:',\n 'products.demo_page.benefit_progressive_text':\n 'Start with defaults, customize as needed',\n 'products.demo_page.benefit_type_safety_label': 'Type Safety:',\n 'products.demo_page.benefit_type_safety_text':\n 'End-to-end TypeScript integration',\n 'products.demo_page.benefit_multiple_consumption_label':\n 'Multiple Consumption:',\n 'products.demo_page.benefit_multiple_consumption_text':\n 'Library, federation, standalone',\n\n // ProductsPage\n 'products.products_page.description':\n 'Manage your product catalog with auto-generated CRUD operations, \\n real-time updates, and AI-powered tools via MCP.',\n 'products.products_page.auto_generated_text':\n 'REST API endpoints automatically created from @smrt() decorated Product class',\n 'products.products_page.ai_ready_heading': '🤖 AI Ready',\n 'products.products_page.ai_ready_text':\n 'MCP tools available for Claude and other AI models to interact with products',\n 'products.products_page.federatable_text':\n 'Components can be consumed by other applications via module federation',\n 'products.products_page.library_text':\n 'Install as NPM package: npm install @have/smrt-template',\n\n // ProductForm\n 'products.product_form.name_label': 'Product Name *',\n 'products.product_form.name_placeholder': 'Enter product name',\n 'products.product_form.description_placeholder':\n 'Product description (optional)',\n 'products.product_form.category_placeholder': 'Product category',\n 'products.product_form.tags_placeholder': 'tag1, tag2, tag3',\n 'products.product_form.tags_hint': 'Separate tags with commas',\n 'products.product_form.in_stock_label': 'In Stock',\n\n // TestComponent\n 'products.test_component.title': 'Test Component',\n\n // AutoForm\n 'products.auto_form.subtitle': 'Auto-generated from SMRT Product model',\n 'products.auto_form.debug_summary': 'Form Data (Debug)',\n\n // FieldRenderer\n 'products.field_renderer.array_hint': 'Enter values separated by commas',\n 'products.field_renderer.object_hint': 'Enter valid JSON',\n\n // CategoryManager\n 'products.category_manager.title': 'Category Manager',\n 'products.category_manager.subtitle': 'Manage product categories',\n 'products.category_manager.coming_soon':\n 'Category management feature coming soon...',\n 'products.category_manager.will_include': 'This will include:',\n 'products.category_manager.create_edit': 'Create and edit categories',\n 'products.category_manager.organize_hierarchy': 'Organize category hierarchy',\n 'products.category_manager.manage_permissions': 'Manage category permissions',\n 'products.category_manager.analytics': 'Category analytics',\n\n // ProductCatalog\n 'products.product_catalog.title': 'Product Catalog',\n 'products.product_catalog.in_stock': 'in stock',\n 'products.product_catalog.total_value': 'Total value:',\n 'products.product_catalog.search_placeholder': 'Search products...',\n 'products.product_catalog.all_categories': 'All Categories',\n 'products.product_catalog.add_product': 'Add Product',\n 'products.product_catalog.loading': 'Loading products...',\n 'products.product_catalog.empty':\n 'No products yet. Create your first product to get started!',\n 'products.product_catalog.create_first': 'Create First Product',\n 'products.product_catalog.no_match':\n 'No products match your search criteria.',\n});\n","<script lang=\"ts\">\nimport { useI18n } from '@happyvertical/smrt-ui/i18n';\nimport { M } from '../i18n.js';\nimport type { ProductData } from '../types';\n\nconst { t } = useI18n();\n\ninterface Props {\n product?: Partial<ProductData>;\n onSubmit: (product: Partial<ProductData>) => void;\n onCancel?: () => void;\n loading?: boolean;\n}\n\nconst { product = {}, onSubmit, onCancel, loading = false }: Props = $props();\n\nconst formData = $state({\n name: product.name || '',\n description: product.description || '',\n price: product.price || 0,\n inStock: product.inStock ?? true,\n category: product.category || '',\n tags: product.tags?.join(', ') || '',\n});\n\nlet errors = $state<Record<string, string>>({});\n\nfunction validateForm() {\n errors = {};\n\n if (!formData.name.trim()) {\n errors.name = 'Product name is required';\n }\n\n if (formData.price < 0) {\n errors.price = 'Price must be non-negative';\n }\n\n return Object.keys(errors).length === 0;\n}\n\nfunction handleSubmit(event: Event) {\n event.preventDefault();\n\n if (!validateForm()) {\n return;\n }\n\n const productData: Partial<ProductData> = {\n ...product,\n name: formData.name.trim(),\n description: formData.description.trim() || undefined,\n price: formData.price,\n inStock: formData.inStock,\n category: formData.category.trim(),\n tags: formData.tags\n ? formData.tags\n .split(',')\n .map((tag) => tag.trim())\n .filter(Boolean)\n : [],\n };\n\n onSubmit(productData);\n}\n</script>\n\n<form onsubmit={handleSubmit} class=\"product-form\">\n <div class=\"form-group\">\n <label for=\"name\">{t(M['products.product_form.name_label'])}</label>\n <input\n id=\"name\"\n type=\"text\"\n bind:value={formData.name}\n disabled={loading}\n class=\"form-input\"\n class:error={errors.name}\n placeholder={t(M['products.product_form.name_placeholder'])}\n />\n {#if errors.name}\n <span class=\"error-message\">{errors.name}</span>\n {/if}\n </div>\n\n <div class=\"form-group\">\n <label for=\"description\">Description</label>\n <textarea\n id=\"description\"\n bind:value={formData.description}\n disabled={loading}\n class=\"form-textarea\"\n placeholder={t(M['products.product_form.description_placeholder'])}\n rows=\"3\"\n ></textarea>\n </div>\n\n <div class=\"form-row\">\n <div class=\"form-group\">\n <label for=\"price\">Price *</label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n bind:value={formData.price}\n disabled={loading}\n class=\"form-input\"\n class:error={errors.price}\n placeholder=\"0.00\"\n />\n {#if errors.price}\n <span class=\"error-message\">{errors.price}</span>\n {/if}\n </div>\n\n <div class=\"form-group\">\n <label for=\"category\">Category</label>\n <input\n id=\"category\"\n type=\"text\"\n bind:value={formData.category}\n disabled={loading}\n class=\"form-input\"\n placeholder={t(M['products.product_form.category_placeholder'])}\n />\n </div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"tags\">Tags</label>\n <input\n id=\"tags\"\n type=\"text\"\n bind:value={formData.tags}\n disabled={loading}\n class=\"form-input\"\n placeholder={t(M['products.product_form.tags_placeholder'])}\n />\n <small class=\"form-hint\">{t(M['products.product_form.tags_hint'])}</small>\n </div>\n\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n bind:checked={formData.inStock}\n disabled={loading}\n class=\"form-checkbox\"\n />\n {t(M['products.product_form.in_stock_label'])}\n </label>\n </div>\n\n <div class=\"form-actions\">\n {#if onCancel}\n <button type=\"button\" onclick={onCancel} disabled={loading} class=\"cancel-btn\">\n Cancel\n </button>\n {/if}\n \n <button type=\"submit\" disabled={loading} class=\"submit-btn\">\n {#if loading}\n Saving...\n {:else}\n {product.id ? 'Update Product' : 'Create Product'}\n {/if}\n </button>\n </div>\n</form>\n\n<style>\n .product-form {\n max-width: 500px;\n padding: 1.5rem;\n background: var(--smrt-color-surface, #fff);\n border-radius: var(--smrt-radius-md, 8px);\n border: 1px solid var(--smrt-color-outline-variant, #e2e8f0);\n }\n \n .form-group {\n margin-bottom: 1rem;\n }\n \n .form-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 1rem;\n }\n \n label {\n display: block;\n margin-bottom: 0.25rem;\n font-weight: var(--smrt-typography-weight-medium, 500);\n color: var(--smrt-color-on-surface, #374151);\n font-size: var(--smrt-typography-label-large-size, 0.875rem);\n }\n\n .form-input, .form-textarea {\n width: 100%;\n padding: 0.5rem;\n border: 1px solid var(--smrt-color-outline-variant, #d1d5db);\n border-radius: var(--smrt-radius-sm, 4px);\n font-size: var(--smrt-typography-body-medium-size, 0.875rem);\n transition: border-color 0.2s;\n }\n\n .form-input:focus, .form-textarea:focus {\n outline: none;\n border-color: var(--smrt-color-primary, #3b82f6);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary, #3b82f6) 10%, transparent);\n }\n\n .form-input.error {\n border-color: var(--smrt-color-error, #dc2626);\n }\n \n .form-textarea {\n resize: vertical;\n min-height: 80px;\n }\n \n .checkbox-label {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n cursor: pointer;\n }\n \n .form-checkbox {\n width: auto;\n }\n \n .form-hint {\n color: var(--smrt-color-on-surface-variant, #6b7280);\n font-size: var(--smrt-typography-body-small-size, 0.75rem);\n margin-top: 0.25rem;\n }\n\n .error-message {\n color: var(--smrt-color-error, #dc2626);\n font-size: var(--smrt-typography-label-medium-size, 0.75rem);\n margin-top: 0.25rem;\n display: block;\n }\n\n .form-actions {\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n margin-top: 1.5rem;\n padding-top: 1rem;\n border-top: 1px solid var(--smrt-color-outline-variant, #f3f4f6);\n }\n \n .cancel-btn, .submit-btn {\n padding: 0.5rem 1rem;\n border-radius: var(--smrt-radius-sm, 4px);\n font-size: var(--smrt-typography-label-large-size, 0.875rem);\n font-weight: var(--smrt-typography-weight-medium, 500);\n cursor: pointer;\n border: 1px solid;\n transition: all 0.2s;\n }\n \n .cancel-btn {\n background: var(--smrt-color-surface, #fff);\n border-color: var(--smrt-color-outline-variant, #d1d5db);\n color: var(--smrt-color-on-surface, #374151);\n }\n\n .cancel-btn:hover:not(:disabled) {\n background: var(--smrt-color-surface-container-low, #f9fafb);\n }\n\n .submit-btn {\n background: var(--smrt-color-primary, #3b82f6);\n border-color: var(--smrt-color-primary, #3b82f6);\n color: var(--smrt-color-on-primary, white);\n }\n\n .submit-btn:hover:not(:disabled) {\n background: var(--smrt-color-primary, #2563eb);\n }\n \n .submit-btn:disabled, .cancel-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n</style>"],"names":["root","root_1","root_2","root_3","$$anchor"],"mappings":"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"}
@@ -32,17 +32,6 @@ let Category = class extends SmrtObject {
32
32
  this.productCount = options.productCount || 0;
33
33
  this.active = options.active !== void 0 ? options.active : true;
34
34
  }
35
- async getProducts() {
36
- return [];
37
- }
38
- async getSubcategories() {
39
- return [];
40
- }
41
- async updateProductCount() {
42
- }
43
- static async getRootCategories() {
44
- return [];
45
- }
46
35
  };
47
36
  __decorateClass$5([
48
37
  tenantId({ nullable: true })
@@ -127,7 +116,7 @@ let Product = class extends SmrtObject {
127
116
  this.specifications[key] = value;
128
117
  }
129
118
  async getProductAssetCollection() {
130
- const { ProductAssetCollection } = await import("./ProductAssetCollection-DFPXN43q.js");
119
+ const { ProductAssetCollection } = await import("./ProductAssetCollection-B93HdSlX.js");
131
120
  return ProductAssetCollection.create({ db: this.db });
132
121
  }
133
122
  async getAssets(relationship) {
@@ -169,12 +158,6 @@ let Product = class extends SmrtObject {
169
158
  relationship ? { relationship } : {}
170
159
  );
171
160
  }
172
- static async searchByText(_query) {
173
- return [];
174
- }
175
- static async findByManufacturer(_manufacturer) {
176
- return [];
177
- }
178
161
  };
179
162
  __decorateClass$4([
180
163
  tenantId({ nullable: true })
@@ -508,4 +491,4 @@ export {
508
491
  ProductType as b,
509
492
  ProductVariant as c
510
493
  };
511
- //# sourceMappingURL=Sku-DUKtbYWT.js.map
494
+ //# sourceMappingURL=Sku-sl6xf1PR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sku-sl6xf1PR.js","sources":["../../../src/lib/models/Category.ts","../../../src/lib/models/types.ts","../../../src/lib/models/Product.ts","../../../src/lib/models/Material.ts","../../../src/lib/models/ProductAsset.ts","../../../src/lib/models/ProductVariant.ts","../../../src/lib/models/Sku.ts"],"sourcesContent":["/**\n * Product knowledge base category model\n *\n * SMRT auto-generates REST APIs, MCP tools, and TypeScript clients from this class.\n */\n\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\n/**\n * Options for Category initialization\n */\nexport interface CategoryOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n name?: string;\n description?: string;\n parentId?: string;\n level?: number;\n productCount?: number;\n active?: boolean;\n}\n\n/**\n * Product knowledge base category for organizing product information.\n *\n * Optional tenancy — categories may be shared globally (tenantId=null) or\n * scoped to a tenant. Hierarchical via parentId.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n // See the matching note on `Product` — overriding `conflictColumns`\n // to include `tenant_id` causes a SQLite `ON CONFLICT clause does not\n // match any … UNIQUE constraint` error because the core schema\n // generator's hardcoded STI unique index is `(slug, context,\n // _meta_type)` and does not include the tenant column. Until the\n // upstream framework fix lands, two tenants cannot share a category\n // slug at the schema level.\n api: {\n include: ['list', 'get', 'create', 'update'], // Standard CRUD except delete\n },\n mcp: {\n include: ['list', 'get'], // AI tools for category discovery\n },\n cli: true, // Enable CLI commands for admin\n})\nexport class Category extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation.\n * Nullable to support both tenant-scoped and global categories.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n name = '';\n description = '';\n @foreignKey('Category')\n parentId?: string; // For hierarchical categories\n level = 0; // Category depth in hierarchy\n productCount = 0; // Number of products in this category\n active = true;\n\n constructor(options: CategoryOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n this.name = options.name || '';\n this.description = options.description || '';\n this.parentId = options.parentId;\n this.level = options.level || 0;\n this.productCount = options.productCount || 0;\n this.active = options.active !== undefined ? options.active : true;\n }\n}\n","/**\n * Shared types for the products package.\n *\n * @packageDocumentation\n */\n\n/**\n * Discriminator for Product STI subtypes.\n *\n * Industry-vertical templates extend `Product` with their own STI subtypes\n * (apparel uses `Style`/`Makeup`; furniture might use `Design`/`Finish`;\n * automotive `Model`/`Trim`) — the upstream package only ships the truly\n * generic primitives:\n *\n * - {@link ProductType.PRODUCT} — plain catalog item (base)\n * - {@link ProductType.MATERIAL} — raw input consumed by manufacturing\n *\n * The axis-declaration concept (\"this product varies along `size` with\n * values `[XS, S, M, L, XL]`\") is NOT a Product STI subtype — it lives in\n * its own model, {@link ProductVariant}, with a separate table. Per-SKU\n * value pins live on `Sku.attributes` (also in this package — `Sku` was\n * relocated from `@happyvertical/smrt-inventory` to here in Phase 1 so\n * every catalog primitive lives under one roof; inventory only holds\n * stock levels and movements now).\n */\nexport enum ProductType {\n /** Generic catalog item (default). */\n PRODUCT = 'product',\n /** A raw input — fabric, trim, packaging, etc. — consumed by manufacturing. */\n MATERIAL = 'material',\n}\n\n/**\n * High-level classification of a {@link ProductType.MATERIAL} item.\n *\n * Open string so consumers can extend with vertical-specific kinds without\n * an enum change. The `(string & {})` branch preserves the literal-string\n * autocomplete suggestions in IDEs — a bare `| string` would collapse the\n * whole union to `string` and erase the named hints (same pattern as\n * `StockMovementReason` in `@happyvertical/smrt-inventory`).\n */\nexport type MaterialKind =\n | 'fabric'\n | 'trim'\n | 'thread'\n | 'label'\n | 'packaging'\n | 'component'\n | (string & {});\n","/**\n * Product knowledge base model\n *\n * SMRT auto-generates REST APIs, MCP tools, and TypeScript clients from this class.\n */\n\nimport type { Asset } from '@happyvertical/smrt-assets';\nimport {\n assertValidOwnedAssetRelationship,\n assertValidOwnedAssetSortOrder,\n resolveOwnedAssetsById,\n} from '@happyvertical/smrt-assets';\nimport {\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport { ProductType } from './types';\n\n/**\n * Options for Product initialization\n */\nexport interface ProductOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n productType?: ProductType;\n name?: string;\n description?: string;\n category?: string;\n manufacturer?: string;\n model?: string;\n price?: number;\n inStock?: boolean;\n specifications?: Record<string, any>;\n tags?: string[];\n}\n\n/**\n * Product information for knowledge base queries.\n *\n * STI base — subclasses (e.g. `Material` upstream, `Style`/`Makeup` in the\n * apparel template) share this table via the `_meta_type` discriminator\n * and `productType` field. Optional tenancy lets the same package serve\n * shared global catalogs OR per-merchant catalogs.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n // KNOWN LIMITATION (tracked for framework follow-up): the core schema\n // generator hardcodes the STI unique index as\n // `(slug, context, _meta_type)` and does not include `tenant_id`\n // even when the class is `@TenantScoped`. As a result two tenants\n // cannot save a row with the same slug+context+type — the UNIQUE\n // constraint at the SQL layer rejects the second insert.\n //\n // We deliberately do NOT override `conflictColumns` here to add\n // `tenant_id`: doing so would put the runtime upsert path\n // (`ON CONFLICT ('slug','context','_meta_type','tenant_id')`) out of\n // step with the actual unique index, producing `SQLITE_ERROR: ON\n // CONFLICT clause does not match any … UNIQUE constraint` on every\n // save. Production callers should either (a) namespace their slugs\n // per tenant on the application side (e.g. `${tenantId}-widget`), or\n // (b) wait for the upstream framework fix that extends the STI\n // unique index with `tenant_id` for tenant-scoped tables.\n api: {\n include: ['list', 'get', 'create', 'update'], // Standard CRUD except delete\n },\n mcp: {\n include: ['list', 'get'], // AI tools for product discovery\n },\n cli: true, // Enable CLI commands for admin\n})\nexport class Product extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation.\n * Nullable to support both tenant-scoped and global catalogs.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * STI discriminator. Subclasses override this with their type.\n */\n productType: ProductType = ProductType.PRODUCT;\n\n name = '';\n description = '';\n category = ''; // Reference to category\n manufacturer = '';\n model = '';\n price = 0;\n inStock = true;\n specifications: Record<string, any> = {};\n tags: string[] = [];\n\n constructor(options: ProductOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n // Treat `null` like `undefined`: existing product rows written\n // before the `product_type` column existed hydrate with the field\n // as SQL `NULL`, and without this guard the SmrtObject loader would\n // overwrite the `ProductType.PRODUCT` initializer with `null`. The\n // STI discriminator `_meta_type` still identifies the row's\n // subtype; `productType` is the secondary human-readable label and\n // should fall back to the base default for legacy rows.\n if (options.productType !== undefined && options.productType !== null) {\n this.productType = options.productType;\n }\n this.name = options.name || '';\n this.description = options.description || '';\n this.category = options.category || '';\n this.manufacturer = options.manufacturer || '';\n this.model = options.model || '';\n this.price = options.price || 0;\n this.inStock = options.inStock !== undefined ? options.inStock : true;\n this.specifications = options.specifications || {};\n this.tags = options.tags || [];\n }\n\n async getSpecification(key: string): Promise<any> {\n return this.specifications[key];\n }\n\n async updateSpecification(key: string, value: any): Promise<void> {\n this.specifications[key] = value;\n }\n\n private async getProductAssetCollection() {\n const { ProductAssetCollection } = await import(\n '../collections/ProductAssetCollection'\n );\n return ProductAssetCollection.create({ db: this.db });\n }\n async getAssets(relationship?: string): Promise<Asset[]> {\n if (!this.id) {\n return [];\n }\n\n const productAssets = await this.getProductAssetCollection();\n // Don't pass `this.tenantId` here. ProductAsset is @TenantScoped, so the\n // tenancy interceptor auto-filters the underlying list() by the active\n // context. Passing `this.tenantId` would apply a second explicit filter on\n // TOP of the interceptor's — and when this Product is a global/shared row\n // (`tenantId === null`), that second filter would drop every per-tenant\n // link to it. The interceptor is the right boundary: in a tenant scope it\n // returns that tenant's links; outside any scope it returns everything. See\n // the `ProductAsset` model comment about cross-tenant linking.\n const linkedAssets = await productAssets.byLeft(\n this.id,\n relationship ? { relationship } : {},\n );\n\n // Use the ACTIVE tenant context's tenantId, not `this.tenantId`,\n // when resolving the underlying assets. `resolveOwnedAssetsById`\n // treats its `tenantId` arg as the caller's identity: when set, it\n // enters `withSystemContext` to bypass the interceptor and then\n // includes both that-tenant-owned AND global (tenantId === null)\n // assets in the visible set. Passing `this.tenantId` would force\n // a global Product (`tenantId === null`) running inside a tenant\n // request into the \"no caller tenant\" branch — the interceptor\n // would then filter to only the tenant's own assets and drop\n // every global asset linked to the shared product. Reading the\n // active context fixes both directions: tenant requests see\n // tenant + global assets; system requests see everything.\n const ctxTenantId = getCurrentTenant()?.tenantId ?? null;\n return resolveOwnedAssetsById(\n this.db,\n linkedAssets.map((link) => link.assetId),\n ctxTenantId,\n );\n }\n\n async addAsset(\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n if (!this.id || !asset.id) {\n throw new Error('Cannot associate unsaved product or asset');\n }\n\n assertValidOwnedAssetRelationship(relationship);\n assertValidOwnedAssetSortOrder(sortOrder);\n\n const productAssets = await this.getProductAssetCollection();\n // Same rationale as `getAssets` — the interceptor's auto-populate sets the\n // new row's `tenantId` from the active context, so we don't hard-wire\n // `this.tenantId`. When this Product is global (`tenantId === null`) but a\n // tenant is linking it, the link should land under that tenant, not under\n // the global namespace.\n await productAssets.attach(this.id, asset.id, {\n relationship,\n sortOrder,\n });\n }\n\n async removeAsset(assetId: string, relationship?: string): Promise<void> {\n if (!this.id) {\n return;\n }\n\n const productAssets = await this.getProductAssetCollection();\n // Tenant-scoped delete: pass only the relationship filter and let the\n // interceptor scope the underlying list() to the active tenant, rather\n // than constraining to `this.tenantId` (which on a global product would\n // never match a tenant-owned link). System-style callers without a tenant\n // context sweep every matching row — the existing `detach` semantics.\n await productAssets.detach(\n this.id,\n assetId,\n relationship ? { relationship } : {},\n );\n }\n}\n","/**\n * Material — STI subtype of Product representing a raw input consumed by\n * manufacturing: fabric, thread, trim, labels, packaging, components.\n *\n * Materials are first-class products in the catalog (the SAP/NetSuite pattern):\n * they have a name, description, vendor, unit cost, and live in inventory just\n * like finished goods. Bills of materials in `@happyvertical/smrt-manufacturing`\n * reference Materials by id.\n *\n * @packageDocumentation\n */\n\nimport { type Meta, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped } from '@happyvertical/smrt-tenancy';\nimport { Product, type ProductOptions } from './Product';\nimport { type MaterialKind, ProductType } from './types';\n\nexport interface MaterialOptions extends ProductOptions {\n materialKind?: MaterialKind;\n uom?: string;\n costPerUnit?: number;\n}\n\n// @TenantScoped is registered per concrete class, so inheriting from\n// Product is NOT enough — `MaterialCollection.list()` passes 'Material'\n// (not 'Product') to the interceptor, which then fails its\n// `isTenantScopedClass(className)` lookup and skips tenant auto-filter\n// + auto-populate. Repeat the decorator here so material rows\n// participate in the same tenant isolation as their Product parent.\n@TenantScoped({ mode: 'optional' })\n// SECURITY (S5 #1406): @smrt() generation config is ALSO registered per\n// concrete class and is NOT inherited from the STI parent. An empty\n// `@smrt()` here would resolve `getConfig('Material').api/.mcp` to\n// `undefined`, which the REST + MCP generators treat as \"expose\n// EVERYTHING\" — including `delete`, `create`, and `update` MCP tools —\n// even though the `Product` base deliberately restricts itself to CRUD\n// (no delete) over REST and read-only (`list`/`get`) over MCP. The\n// products `mcp.ts` generator enumerates the whole registry, so a\n// silently-wide-open Material surface ships the moment the package's own\n// server is generated. Re-declare the parent's restricted posture so the\n// STI child matches its base — and so consumers copying this canonical\n// subtype example (apparel `Style`/`Makeup`, automotive `Model`/`Trim`,\n// …) inherit the secure pattern instead of an accidental open surface.\n@smrt({\n api: {\n include: ['list', 'get', 'create', 'update'], // no delete, matches Product\n },\n mcp: {\n include: ['list', 'get'], // read-only AI tools, matches Product\n },\n cli: true,\n})\nexport class Material extends Product {\n override productType: ProductType = ProductType.MATERIAL;\n\n // STI child-specific fields use the `Meta<T>` type wrapper rather than\n // the runtime `@meta()` decorator. The scanner detects the wrapper at\n // build time (via the type annotation) and emits the field with\n // `type: 'meta'` in the manifest, which routes it through `_meta_data`\n // JSON storage at schema-generation time. The runtime decorator path\n // doesn't reach the manifest, so STI children that only use `@meta()`\n // get materialized as ordinary columns on the parent's table.\n\n /** Classification — fabric, trim, thread, label, packaging, component, ... */\n materialKind: Meta<MaterialKind> = 'component';\n\n /** Unit of measure — \"yards\", \"meters\", \"each\", \"grams\", \"lbs\". */\n uom: Meta<string> = 'each';\n\n /**\n * Latest known cost per UOM. For sophisticated cost rollup, manufacturing\n * may pull from purchase-order history instead of this denormalised value.\n */\n costPerUnit: Meta<number> = 0.0;\n\n constructor(options: MaterialOptions = {}) {\n super(options);\n if (options.materialKind !== undefined)\n this.materialKind = options.materialKind;\n if (options.uom !== undefined) this.uom = options.uom;\n if (options.costPerUnit !== undefined)\n this.costPerUnit = options.costPerUnit;\n }\n}\n","import type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\nexport interface ProductAssetOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n productId?: string;\n assetId?: string;\n relationship?: string;\n sortOrder?: number;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'product_assets',\n // tenant_id is included so two tenants can independently link the same\n // shared product/asset under the same relationship without their rows\n // colliding (or worse, one tenant silently overwriting another's link\n // via UPSERT).\n conflictColumns: ['product_id', 'asset_id', 'relationship', 'tenant_id'],\n api: false,\n mcp: false,\n cli: false,\n})\nexport class ProductAsset extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Product', { required: true })\n productId = '';\n\n @crossPackageRef('@happyvertical/smrt-assets:Asset', { required: true })\n assetId = '';\n\n @field({ required: true })\n relationship = 'attachment';\n\n @field()\n sortOrder = 0;\n\n constructor(options: ProductAssetOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.productId) this.productId = options.productId;\n if (options.assetId) this.assetId = options.assetId;\n if (options.relationship) this.relationship = options.relationship;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n}\n","/**\n * ProductVariant — declarative description of one axis along which a\n * product's SKUs differ.\n *\n * A `ProductVariant` row says \"for product X, axis Y has the following\n * allowed values\". The actual per-SKU value lives on `Sku.attributes`\n * (also in this package — `Sku` was relocated to `@happyvertical/smrt-products`\n * in Phase 1; inventory only holds stock motion now), keyed by `axisName`.\n * This split keeps the axis catalog (form choices, UI grouping, ordering)\n * separate from the per-unit instances.\n *\n * Deliberately generic. `axisName` is free-form — apparel teams might\n * use `'size'` and `'color'`, automotive teams `'trim'` and `'engine'`,\n * CPG teams `'packSize'` and `'flavor'`. The framework never inspects\n * the axis name.\n *\n * NOT a Product STI subtype — it's its own model with its own table,\n * because the shape (axis declaration) doesn't fit the Product schema\n * (no name, no price, no category — it's metadata about a Product, not\n * a Product itself).\n *\n * @packageDocumentation\n */\n\nimport {\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\n/**\n * Options accepted by the {@link ProductVariant} constructor.\n */\nexport interface ProductVariantOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n productId?: string;\n axisName?: string;\n label?: string;\n allowedValues?: string[] | string;\n sortOrder?: number;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'product_variants',\n conflictColumns: ['product_id', 'axis_name', 'tenant_id'],\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class ProductVariant extends SmrtObject {\n /** Tenant scope. `null` means the variant axis is global. */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Plain string reference to the {@link Product} this axis belongs to\n * (or any Product STI subtype — `Material`, or vertical subtypes\n * defined in templates such as the apparel `Style` / `Makeup`).\n */\n @field({ required: true })\n productId: string = '';\n\n /**\n * The name of the axis (`'size'`, `'color'`, `'finish'`, `'voltage'`,\n * `'packSize'`, …). Free-form — the framework treats this as opaque.\n */\n @field({ required: true })\n axisName: string = '';\n\n /** Optional human-friendly label for forms / UIs (defaults to `axisName`). */\n label: string = '';\n\n /**\n * Allowed values for this axis, stored as a JSON string. Named\n * `allowedValues` (rather than `values`) because the unprefixed name\n * collides with the SQL `VALUES` keyword on several engines. Use\n * {@link getValues} / {@link setValues} to round-trip the array form.\n */\n allowedValues: string = '[]';\n\n /** Sort order for displaying multiple axes in a consistent column order. */\n sortOrder: number = 0;\n\n constructor(options: ProductVariantOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.productId !== undefined) this.productId = options.productId;\n if (options.axisName !== undefined) this.axisName = options.axisName;\n if (options.label !== undefined) this.label = options.label;\n if (options.allowedValues !== undefined)\n this.setValues(options.allowedValues);\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n\n // Default the label to axisName when no explicit label was supplied.\n // The field docstring promises this behavior and admin UIs render\n // `variant.label` directly; without this fallback an axis declared\n // as `{ axisName: 'size' }` would render with a blank label.\n if (!this.label && this.axisName) {\n this.label = this.axisName;\n }\n }\n\n /**\n * Parse and return the {@link allowedValues} JSON array. Returns an\n * empty array if the stored value cannot be parsed as an array.\n */\n getValues(): string[] {\n if (!this.allowedValues) return [];\n try {\n const parsed = JSON.parse(this.allowedValues);\n return Array.isArray(parsed) ? parsed.map((value) => String(value)) : [];\n } catch {\n return [];\n }\n }\n\n /**\n * Serialize the allowed values back into the stored\n * {@link allowedValues} string. Accepts either an array or a\n * pre-serialized JSON string.\n */\n setValues(value: string[] | string): void {\n if (typeof value === 'string') {\n this.allowedValues = value;\n return;\n }\n this.allowedValues = JSON.stringify(Array.isArray(value) ? value : []);\n }\n}\n","/**\n * Sku — smallest sellable and countable unit.\n *\n * A SKU is the concrete catalog row that downstream inventory counts.\n * It references a higher-level concept in this same package (a\n * {@link Product} or any of its STI subtypes) via the plain string\n * `productId` field. The same pattern serves apparel (one SKU per\n * size/color), furniture (one SKU per finish), automotive parts,\n * grocery (one SKU per pack size), or any other vertical that needs\n * to count discrete units.\n *\n * Axis values such as `{ size: 'M', color: 'navy' }`, `{ finish: 'walnut' }`,\n * or `{ packSize: '12oz' }` are stored as opaque JSON in `attributes`;\n * the declarative axis catalog lives in {@link ProductVariant} in this\n * same package.\n *\n * Stock balance and movement history for a SKU live in\n * `@happyvertical/smrt-inventory` as `StockLevel` and `StockMovement`.\n *\n * @packageDocumentation\n */\n\nimport {\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\n/**\n * Options accepted by the {@link Sku} constructor.\n */\nexport interface SkuOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n productId?: string;\n code?: string;\n barcode?: string;\n name?: string;\n attributes?: Record<string, unknown> | string;\n weightGrams?: number;\n parentSkuId?: string;\n active?: boolean;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'product_skus',\n conflictColumns: ['code', 'tenant_id'],\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class Sku extends SmrtObject {\n /** Tenant scope. `null` means the SKU is a global record. */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Plain string reference to a row in this package — typically a\n * `Product` id (or any of its STI subtypes, such as `Material`\n * upstream or `Style` / `Makeup` in the apparel template).\n *\n * Marked required so the framework rejects a save with `productId`\n * empty or unset. Without this, downstream consumers (inventory\n * stock motion, manufacturing BOM resolution) would have to filter\n * out orphan SKU rows that point at no product — a sharp footgun\n * the moment one slips through. The `@field({ required: true })`\n * decorator also surfaces the constraint in generated REST/MCP\n * schemas.\n */\n @field({ required: true })\n productId: string = '';\n\n /**\n * Stable, human-meaningful identifier such as a UPC, internal SKU\n * code, or part number. Together with `tenantId` this is the natural\n * key (`conflictColumns: ['code', 'tenant_id']`), so re-saving an\n * identical row is an upsert rather than a UNIQUE violation.\n */\n @field({ required: true })\n code: string = '';\n\n /** Optional scannable barcode (EAN/UPC/Code-128/etc.). */\n barcode: string = '';\n\n /** Display name for UIs. Optional — `code` is enough for machines. */\n name: string = '';\n\n /**\n * Axis values for this SKU expressed as JSON, e.g. `{ size: 'M' }`,\n * `{ finish: 'walnut' }`, or `{ voltage: '230V' }`.\n *\n * The persisted column type is text; use {@link getAttributes} and\n * {@link setAttributes} to round-trip parsed values without worrying\n * about parse errors.\n */\n attributes: string = '{}';\n\n /** Optional physical weight in grams for shipping / cost calculations. */\n @field({ type: 'decimal' })\n weightGrams: number = 0.0;\n\n /**\n * Optional self-reference for bundles / kits — set to the id of the\n * \"parent\" SKU that this SKU is a component of.\n */\n parentSkuId: string = '';\n\n /**\n * Soft-active flag. Inactive SKUs stay queryable for history but\n * should not be offered for sale or production planning.\n */\n active: boolean = true;\n\n constructor(options: SkuOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.productId !== undefined) this.productId = options.productId;\n if (options.code !== undefined) this.code = options.code;\n if (options.barcode !== undefined) this.barcode = options.barcode;\n if (options.name !== undefined) this.name = options.name;\n if (options.attributes !== undefined)\n this.setAttributes(options.attributes);\n if (options.weightGrams !== undefined)\n this.weightGrams = options.weightGrams;\n if (options.parentSkuId !== undefined)\n this.parentSkuId = options.parentSkuId;\n if (options.active !== undefined) this.active = options.active;\n }\n\n /**\n * Parse and return the {@link attributes} JSON. Returns an empty\n * object if the stored value is invalid JSON so callers don't have\n * to wrap every read in a try/catch.\n */\n getAttributes(): Record<string, unknown> {\n if (!this.attributes) return {};\n try {\n const parsed = JSON.parse(this.attributes);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /**\n * Serialize axis values back into the stored {@link attributes}\n * string. Accepts either an object (will be `JSON.stringify`'d) or a\n * raw JSON string that the caller has already serialized.\n */\n setAttributes(value: Record<string, unknown> | string): void {\n if (typeof value === 'string') {\n this.attributes = value;\n return;\n }\n this.attributes = JSON.stringify(value ?? {});\n }\n}\n"],"names":["__decorateClass","ProductType"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { SmrtCollection } from "@happyvertical/smrt-core";
2
- import { C as Category, P as Product, M as Material, c as ProductVariant, S as Sku } from "./Sku-DUKtbYWT.js";
2
+ import { C as Category, P as Product, M as Material, c as ProductVariant, S as Sku } from "./Sku-sl6xf1PR.js";
3
3
  import { getOwnedAssetsFromCollection, addOwnedAssetFromCollection, removeOwnedAssetFromCollection } from "@happyvertical/smrt-assets";
4
4
  class CategoryCollection extends SmrtCollection {
5
5
  static _itemClass = Category;
@@ -157,4 +157,4 @@ export {
157
157
  ProductVariantCollection as a,
158
158
  ProductCollection$1 as b
159
159
  };
160
- //# sourceMappingURL=SkuCollection-C0tdkEdL.js.map
160
+ //# sourceMappingURL=SkuCollection-f_a3k93c.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SkuCollection-C0tdkEdL.js","sources":["../../../src/lib/collections/CategoryCollection.ts","../../../src/lib/collections/ProductCollection.ts","../../../src/lib/collections/MaterialCollection.ts","../../../src/lib/collections/ProductVariantCollection.ts","../../../src/lib/collections/SkuCollection.ts"],"sourcesContent":["/**\n * CategoryCollection — Collection manager for {@link Category} objects.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Category } from '../models/Category';\n\nexport class CategoryCollection extends SmrtCollection<Category> {\n static readonly _itemClass = Category;\n\n /**\n * Return every top-level category. `parentId` on `Category` is typed\n * `string | undefined`; SMRT serializes undefined string fields as `''`\n * rather than `NULL`, so a category created without an explicit\n * `parentId` lands in the DB with `parent_id = ''`, not `parent_id IS NULL`.\n *\n * We accept both shapes — historical data, migrations, and direct DB\n * imports can produce either — so consumers don't have to normalize\n * `parentId` on write to be discoverable as a root. SQL's `IN` operator\n * does NOT match `NULL` (NULL is not equal to anything, including itself\n * inside an IN list), so the empty-string and NULL cases must run as\n * separate queries and merge.\n */\n async getRootCategories(): Promise<Category[]> {\n const [emptyParents, nullParents] = await Promise.all([\n this.list({ where: { parentId: '' } }),\n this.list({ where: { parentId: null } }),\n ]);\n\n if (emptyParents.length === 0) return nullParents;\n if (nullParents.length === 0) return emptyParents;\n\n // De-dupe by id in case a row somehow shows up in both lists.\n const seen = new Set<string>();\n const merged: Category[] = [];\n for (const row of [...emptyParents, ...nullParents]) {\n const id = row.id ?? '';\n if (!id || seen.has(id)) continue;\n seen.add(id);\n merged.push(row);\n }\n return merged;\n }\n}\n","import type { Asset } from '@happyvertical/smrt-assets';\nimport {\n addOwnedAssetFromCollection,\n getOwnedAssetsFromCollection,\n removeOwnedAssetFromCollection,\n} from '@happyvertical/smrt-assets';\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Product } from '../models/Product';\n\nexport class ProductCollection extends SmrtCollection<Product> {\n static readonly _itemClass = Product;\n\n async findByManufacturer(manufacturer: string): Promise<Product[]> {\n return this.list({ where: { manufacturer } });\n }\n\n async findInStock(): Promise<Product[]> {\n return this.list({ where: { inStock: true } });\n }\n\n async getAssets(productId: string, relationship?: string): Promise<Asset[]> {\n return getOwnedAssetsFromCollection(this, productId, relationship);\n }\n\n async addAsset(\n productId: string,\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n await addOwnedAssetFromCollection(\n this,\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 this,\n 'Product',\n productId,\n assetId,\n relationship,\n );\n }\n}\n","/**\n * MaterialCollection — Collection manager for {@link Material} objects.\n *\n * STI framework auto-filters by `_meta_type` to return only Material rows\n * from the shared `products` table.\n */\n\nimport { Material } from '../models/Material';\nimport type { MaterialKind } from '../models/types';\nimport { ProductCollection } from './ProductCollection';\n\nexport class MaterialCollection extends ProductCollection {\n static override readonly _itemClass = Material;\n\n /**\n * Find every {@link Material} with the given kind.\n *\n * `materialKind` is an `@meta()` field — it lives inside the shared\n * `_meta_data` JSON column on `products`, not as its own SQL column.\n * That means a naive `list({ where: { materialKind } })` would generate\n * a WHERE on a non-existent column. We list every Material STI row and\n * filter the hydrated instances in JS.\n *\n * For very large catalogs, consumers should add a generated column or\n * an expression index over the JSON path and override this helper.\n */\n async findByKind(materialKind: MaterialKind): Promise<Material[]> {\n const items = (await this.list({})) as Material[];\n return items.filter((m) => m.materialKind === materialKind);\n }\n}\n","/**\n * ProductVariantCollection — read helpers for {@link ProductVariant} rows.\n *\n * {@link ProductVariant} is a standalone model (not a Product STI subtype),\n * so this collection drives its own table (`product_variants`). Helper\n * methods cover the two common query patterns: \"what axes does this\n * product vary along?\" and \"tell me about the size axis specifically.\"\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ProductVariant } from '../models/ProductVariant';\n\nexport class ProductVariantCollection extends SmrtCollection<ProductVariant> {\n static readonly _itemClass = ProductVariant;\n\n /** Every axis declaration for a given product, in display order. */\n async findForProduct(productId: string): Promise<ProductVariant[]> {\n return this.list({\n where: { productId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n /** The axis declaration for a `(productId, axisName)` pair, or null. */\n async findAxis(\n productId: string,\n axisName: string,\n ): Promise<ProductVariant | null> {\n const matches = await this.list({\n where: { productId, axisName },\n limit: 1,\n });\n return matches[0] ?? null;\n }\n}\n","/**\n * SkuCollection — query helpers for {@link Sku} rows.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Sku } from '../models/Sku';\n\nexport class SkuCollection extends SmrtCollection<Sku> {\n static readonly _itemClass = Sku;\n\n /**\n * Look up a SKU by its tenant-scoped `code` (UPC, internal part\n * number, etc.). Returns `null` when no row matches.\n */\n async findByCode(code: string): Promise<Sku | null> {\n const matches = await this.list({ where: { code }, limit: 1 });\n return matches[0] ?? null;\n }\n\n /**\n * Look up a SKU by its scannable barcode. Returns `null` when no row\n * matches; pass `''` to skip the query early.\n */\n async findByBarcode(barcode: string): Promise<Sku | null> {\n if (!barcode) return null;\n const matches = await this.list({ where: { barcode }, limit: 1 });\n return matches[0] ?? null;\n }\n\n /**\n * Find every SKU pointing at the given `Product` id (or any Product\n * STI subtype id — `Material` upstream, vertical subtypes defined in\n * templates). Useful for listing the SKUs that back a single product\n * card.\n */\n async findByProduct(productId: string): Promise<Sku[]> {\n return this.list({ where: { productId }, orderBy: 'code ASC' });\n }\n\n /**\n * Find every SKU that is a component of the given parent SKU (for\n * bundles / kits). Returns an empty array when the parent has no\n * children.\n */\n async findByParent(parentSkuId: string): Promise<Sku[]> {\n return this.list({ where: { parentSkuId }, orderBy: 'code ASC' });\n }\n\n /** Find every active SKU, optionally narrowed by parent product id. */\n async findActive(productId?: string): Promise<Sku[]> {\n const where: Record<string, unknown> = { active: true };\n if (productId) where.productId = productId;\n return this.list({ where, orderBy: 'code ASC' });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SkuCollection-f_a3k93c.js","sources":["../../../src/lib/collections/CategoryCollection.ts","../../../src/lib/collections/ProductCollection.ts","../../../src/lib/collections/MaterialCollection.ts","../../../src/lib/collections/ProductVariantCollection.ts","../../../src/lib/collections/SkuCollection.ts"],"sourcesContent":["/**\n * CategoryCollection — Collection manager for {@link Category} objects.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Category } from '../models/Category';\n\nexport class CategoryCollection extends SmrtCollection<Category> {\n static readonly _itemClass = Category;\n\n /**\n * Return every top-level category. `parentId` on `Category` is typed\n * `string | undefined`; SMRT serializes undefined string fields as `''`\n * rather than `NULL`, so a category created without an explicit\n * `parentId` lands in the DB with `parent_id = ''`, not `parent_id IS NULL`.\n *\n * We accept both shapes — historical data, migrations, and direct DB\n * imports can produce either — so consumers don't have to normalize\n * `parentId` on write to be discoverable as a root. SQL's `IN` operator\n * does NOT match `NULL` (NULL is not equal to anything, including itself\n * inside an IN list), so the empty-string and NULL cases must run as\n * separate queries and merge.\n */\n async getRootCategories(): Promise<Category[]> {\n const [emptyParents, nullParents] = await Promise.all([\n this.list({ where: { parentId: '' } }),\n this.list({ where: { parentId: null } }),\n ]);\n\n if (emptyParents.length === 0) return nullParents;\n if (nullParents.length === 0) return emptyParents;\n\n // De-dupe by id in case a row somehow shows up in both lists.\n const seen = new Set<string>();\n const merged: Category[] = [];\n for (const row of [...emptyParents, ...nullParents]) {\n const id = row.id ?? '';\n if (!id || seen.has(id)) continue;\n seen.add(id);\n merged.push(row);\n }\n return merged;\n }\n}\n","import type { Asset } from '@happyvertical/smrt-assets';\nimport {\n addOwnedAssetFromCollection,\n getOwnedAssetsFromCollection,\n removeOwnedAssetFromCollection,\n} from '@happyvertical/smrt-assets';\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Product } from '../models/Product';\n\nexport class ProductCollection extends SmrtCollection<Product> {\n static readonly _itemClass = Product;\n\n async findByManufacturer(manufacturer: string): Promise<Product[]> {\n return this.list({ where: { manufacturer } });\n }\n\n async findInStock(): Promise<Product[]> {\n return this.list({ where: { inStock: true } });\n }\n\n async getAssets(productId: string, relationship?: string): Promise<Asset[]> {\n return getOwnedAssetsFromCollection(this, productId, relationship);\n }\n\n async addAsset(\n productId: string,\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n await addOwnedAssetFromCollection(\n this,\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 this,\n 'Product',\n productId,\n assetId,\n relationship,\n );\n }\n}\n","/**\n * MaterialCollection — Collection manager for {@link Material} objects.\n *\n * STI framework auto-filters by `_meta_type` to return only Material rows\n * from the shared `products` table.\n */\n\nimport { Material } from '../models/Material';\nimport type { MaterialKind } from '../models/types';\nimport { ProductCollection } from './ProductCollection';\n\nexport class MaterialCollection extends ProductCollection {\n static override readonly _itemClass = Material;\n\n /**\n * Find every {@link Material} with the given kind.\n *\n * `materialKind` is an `@meta()` field — it lives inside the shared\n * `_meta_data` JSON column on `products`, not as its own SQL column.\n * That means a naive `list({ where: { materialKind } })` would generate\n * a WHERE on a non-existent column. We list every Material STI row and\n * filter the hydrated instances in JS.\n *\n * For very large catalogs, consumers should add a generated column or\n * an expression index over the JSON path and override this helper.\n */\n async findByKind(materialKind: MaterialKind): Promise<Material[]> {\n const items = (await this.list({})) as Material[];\n return items.filter((m) => m.materialKind === materialKind);\n }\n}\n","/**\n * ProductVariantCollection — read helpers for {@link ProductVariant} rows.\n *\n * {@link ProductVariant} is a standalone model (not a Product STI subtype),\n * so this collection drives its own table (`product_variants`). Helper\n * methods cover the two common query patterns: \"what axes does this\n * product vary along?\" and \"tell me about the size axis specifically.\"\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ProductVariant } from '../models/ProductVariant';\n\nexport class ProductVariantCollection extends SmrtCollection<ProductVariant> {\n static readonly _itemClass = ProductVariant;\n\n /** Every axis declaration for a given product, in display order. */\n async findForProduct(productId: string): Promise<ProductVariant[]> {\n return this.list({\n where: { productId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n /** The axis declaration for a `(productId, axisName)` pair, or null. */\n async findAxis(\n productId: string,\n axisName: string,\n ): Promise<ProductVariant | null> {\n const matches = await this.list({\n where: { productId, axisName },\n limit: 1,\n });\n return matches[0] ?? null;\n }\n}\n","/**\n * SkuCollection — query helpers for {@link Sku} rows.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Sku } from '../models/Sku';\n\nexport class SkuCollection extends SmrtCollection<Sku> {\n static readonly _itemClass = Sku;\n\n /**\n * Look up a SKU by its tenant-scoped `code` (UPC, internal part\n * number, etc.). Returns `null` when no row matches.\n */\n async findByCode(code: string): Promise<Sku | null> {\n const matches = await this.list({ where: { code }, limit: 1 });\n return matches[0] ?? null;\n }\n\n /**\n * Look up a SKU by its scannable barcode. Returns `null` when no row\n * matches; pass `''` to skip the query early.\n */\n async findByBarcode(barcode: string): Promise<Sku | null> {\n if (!barcode) return null;\n const matches = await this.list({ where: { barcode }, limit: 1 });\n return matches[0] ?? null;\n }\n\n /**\n * Find every SKU pointing at the given `Product` id (or any Product\n * STI subtype id — `Material` upstream, vertical subtypes defined in\n * templates). Useful for listing the SKUs that back a single product\n * card.\n */\n async findByProduct(productId: string): Promise<Sku[]> {\n return this.list({ where: { productId }, orderBy: 'code ASC' });\n }\n\n /**\n * Find every SKU that is a component of the given parent SKU (for\n * bundles / kits). Returns an empty array when the parent has no\n * children.\n */\n async findByParent(parentSkuId: string): Promise<Sku[]> {\n return this.list({ where: { parentSkuId }, orderBy: 'code ASC' });\n }\n\n /** Find every active SKU, optionally narrowed by parent product id. */\n async findActive(productId?: string): Promise<Sku[]> {\n const where: Record<string, unknown> = { active: true };\n if (productId) where.productId = productId;\n return this.list({ where, orderBy: 'code ASC' });\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import "./chunks/__smrt-register__-BIgFaVKn.js";
2
- import { C, M, P, a, S } from "./chunks/SkuCollection-C0tdkEdL.js";
3
- import { ProductAssetCollection } from "./chunks/ProductAssetCollection-DFPXN43q.js";
2
+ import { C, M, P, a, S } from "./chunks/SkuCollection-f_a3k93c.js";
3
+ import { ProductAssetCollection } from "./chunks/ProductAssetCollection-B93HdSlX.js";
4
4
  export {
5
5
  C as CategoryCollection,
6
6
  M as MaterialCollection,
@@ -1,4 +1,4 @@
1
- import { P, a } from "./chunks/ProductForm-DHeb2L24.js";
1
+ import { P, a } from "./chunks/ProductForm-p4-xbubZ.js";
2
2
  export {
3
3
  P as ProductCard,
4
4
  a as ProductForm
package/dist/lib/index.js CHANGED
@@ -4,12 +4,12 @@ import { default as default2 } from "@smrt/client";
4
4
  import { manifest } from "@smrt/manifest";
5
5
  import { default as default3 } from "@smrt/mcp";
6
6
  import { default as default4 } from "@smrt/routes";
7
- import { C, M, P, a, S } from "./chunks/SkuCollection-C0tdkEdL.js";
8
- import { ProductAssetCollection } from "./chunks/ProductAssetCollection-DFPXN43q.js";
9
- import { P as P2, a as a2 } from "./chunks/ProductForm-DHeb2L24.js";
7
+ import { C, M, P, a, S } from "./chunks/SkuCollection-f_a3k93c.js";
8
+ import { ProductAssetCollection } from "./chunks/ProductAssetCollection-B93HdSlX.js";
9
+ import { P as P2, a as a2 } from "./chunks/ProductForm-p4-xbubZ.js";
10
10
  import { a as a3 } from "./chunks/index-i3-ci1FB.js";
11
- import { P as Product, C as Category } from "./chunks/Sku-DUKtbYWT.js";
12
- import { M as M2, a as a4, b, c, S as S2 } from "./chunks/Sku-DUKtbYWT.js";
11
+ import { P as Product, C as Category } from "./chunks/Sku-sl6xf1PR.js";
12
+ import { M as M2, a as a4, b, c, S as S2 } from "./chunks/Sku-sl6xf1PR.js";
13
13
  import { P as P3, p } from "./chunks/product-store.svelte-Dayd5n3W.js";
14
14
  import { formatDate, formatPrice, generateId, slugify } from "./utils.js";
15
15
  import { MCPGenerator } from "@happyvertical/smrt-core/generators/mcp";
@@ -60,7 +60,7 @@ const { product, onEdit, onDelete }: Props = $props();
60
60
  border: 1px solid var(--smrt-color-outline-variant, #e2e8f0);
61
61
  border-radius: var(--smrt-radius-md, 8px);
62
62
  padding: 1rem;
63
- background: white;
63
+ background: var(--smrt-color-surface, #fff);
64
64
  box-shadow: var(--smrt-elevation-1, 0 1px 3px color-mix(in srgb, var(--smrt-color-shadow, #000) 10%, transparent));
65
65
  transition: box-shadow 0.2s;
66
66
  }
@@ -39,7 +39,7 @@ function validateForm() {
39
39
  return Object.keys(errors).length === 0;
40
40
  }
41
41
 
42
- function _handleSubmit(event: Event) {
42
+ function handleSubmit(event: Event) {
43
43
  event.preventDefault();
44
44
 
45
45
  if (!validateForm()) {
@@ -172,7 +172,7 @@ function _handleSubmit(event: Event) {
172
172
  .product-form {
173
173
  max-width: 500px;
174
174
  padding: 1.5rem;
175
- background: white;
175
+ background: var(--smrt-color-surface, #fff);
176
176
  border-radius: var(--smrt-radius-md, 8px);
177
177
  border: 1px solid var(--smrt-color-outline-variant, #e2e8f0);
178
178
  }
@@ -263,7 +263,7 @@ function _handleSubmit(event: Event) {
263
263
  }
264
264
 
265
265
  .cancel-btn {
266
- background: white;
266
+ background: var(--smrt-color-surface, #fff);
267
267
  border-color: var(--smrt-color-outline-variant, #d1d5db);
268
268
  color: var(--smrt-color-on-surface, #374151);
269
269
  }
@@ -7,6 +7,7 @@
7
7
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
8
8
  import { M } from '../../i18n.js';
9
9
  import type { ProductData } from '../../types';
10
+ import FieldRenderer from './FieldRenderer.svelte';
10
11
 
11
12
  const { t } = useI18n();
12
13
 
@@ -79,7 +80,7 @@ $effect(() => {
79
80
  formData = { ...data };
80
81
  });
81
82
 
82
- function _updateField(fieldName: string, value: any) {
83
+ function updateField(fieldName: string, value: any) {
83
84
  formData[fieldName as keyof ProductData] = value;
84
85
 
85
86
  // Trigger change callback
@@ -88,7 +89,7 @@ function _updateField(fieldName: string, value: any) {
88
89
  }
89
90
  }
90
91
 
91
- function _handleSubmit(event: Event) {
92
+ function handleSubmit(event: Event) {
92
93
  event.preventDefault();
93
94
  if (onSubmit && !readonly) {
94
95
  onSubmit({ ...formData });
@@ -149,7 +150,7 @@ function _getFieldType(
149
150
  max-width: 600px;
150
151
  margin: 0 auto;
151
152
  padding: 1.5rem;
152
- background: white;
153
+ background: var(--smrt-color-surface, #fff);
153
154
  border-radius: var(--smrt-radius-md, 8px);
154
155
  box-shadow: var(--smrt-elevation-1, 0 1px 3px color-mix(in srgb, var(--smrt-color-shadow, #000) 10%, transparent));
155
156
  }
@@ -191,7 +192,7 @@ function _getFieldType(
191
192
  color: var(--smrt-color-on-primary, white);
192
193
  border: none;
193
194
  padding: 0.75rem 1.5rem;
194
- border-radius: 0.375rem;
195
+ border-radius: var(--smrt-radius-sm, 0.375rem);
195
196
  font-weight: var(--smrt-typography-weight-medium, 500);
196
197
  cursor: pointer;
197
198
  transition: background-color 0.2s;
@@ -206,7 +207,7 @@ function _getFieldType(
206
207
  color: var(--smrt-color-on-surface, #374151);
207
208
  border: 1px solid var(--smrt-color-outline-variant, #d1d5db);
208
209
  padding: 0.75rem 1.5rem;
209
- border-radius: 0.375rem;
210
+ border-radius: var(--smrt-radius-sm, 0.375rem);
210
211
  font-weight: var(--smrt-typography-weight-medium, 500);
211
212
  cursor: pointer;
212
213
  transition: background-color 0.2s;
@@ -220,7 +221,7 @@ function _getFieldType(
220
221
  margin-top: 2rem;
221
222
  padding: 1rem;
222
223
  background: var(--smrt-color-surface-container-low, #f9fafb);
223
- border-radius: 0.375rem;
224
+ border-radius: var(--smrt-radius-sm, 0.375rem);
224
225
  border: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
225
226
  }
226
227
 
@@ -1 +1 @@
1
- {"version":3,"file":"AutoForm.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/auto-generated/AutoForm.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC;AAkID,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"AutoForm.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/auto-generated/AutoForm.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC;AAmID,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -32,12 +32,12 @@ const {
32
32
  }: Props = $props();
33
33
 
34
34
  // Auto-generate label from field name if not provided
35
- const _displayLabel =
35
+ const displayLabel =
36
36
  label ||
37
37
  fieldName
38
38
  .replace(/([A-Z])/g, ' $1')
39
39
  .replace(/^./, (str) => str.toUpperCase());
40
- const _fieldId = `field-${fieldName}`;
40
+ const fieldId = `field-${fieldName}`;
41
41
 
42
42
  function handleUpdate(newValue: any) {
43
43
  if (onUpdate && !readonly) {
@@ -45,22 +45,22 @@ function handleUpdate(newValue: any) {
45
45
  }
46
46
  }
47
47
 
48
- function _handleStringInput(event: Event) {
48
+ function handleStringInput(event: Event) {
49
49
  const target = event.target as HTMLInputElement;
50
50
  handleUpdate(target.value);
51
51
  }
52
52
 
53
- function _handleNumberInput(event: Event) {
53
+ function handleNumberInput(event: Event) {
54
54
  const target = event.target as HTMLInputElement;
55
55
  handleUpdate(Number.parseFloat(target.value) || 0);
56
56
  }
57
57
 
58
- function _handleBooleanInput(event: Event) {
58
+ function handleBooleanInput(event: Event) {
59
59
  const target = event.target as HTMLInputElement;
60
60
  handleUpdate(target.checked);
61
61
  }
62
62
 
63
- function _handleArrayInput(event: Event) {
63
+ function handleArrayInput(event: Event) {
64
64
  const target = event.target as HTMLTextAreaElement;
65
65
  try {
66
66
  // Simple array handling - comma separated values
@@ -74,7 +74,7 @@ function _handleArrayInput(event: Event) {
74
74
  }
75
75
  }
76
76
 
77
- function _handleObjectInput(event: Event) {
77
+ function handleObjectInput(event: Event) {
78
78
  const target = event.target as HTMLTextAreaElement;
79
79
  try {
80
80
  const objectValue = JSON.parse(target.value);
@@ -169,7 +169,7 @@ function _handleObjectInput(event: Event) {
169
169
  .field-textarea {
170
170
  padding: 0.5rem 0.75rem;
171
171
  border: 1px solid var(--smrt-color-outline-variant, #d1d5db);
172
- border-radius: 0.375rem;
172
+ border-radius: var(--smrt-radius-sm, 0.375rem);
173
173
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
174
174
  transition: border-color 0.2s, box-shadow 0.2s;
175
175
  }