@hanzo/commerce 7.6.1 → 7.6.4

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 (264) hide show
  1. package/LICENSE.md +21 -0
  2. package/client.ts +124 -64
  3. package/components/add-to-cart-widget.tsx +6 -0
  4. package/components/item/product-card.tsx +18 -1
  5. package/dist/billing.d.ts +2 -0
  6. package/dist/billing.js +3 -0
  7. package/dist/billing.js.map +1 -0
  8. package/dist/client.d.ts +432 -0
  9. package/dist/client.js +392 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/components/Icons.d.ts +18 -0
  12. package/dist/components/Icons.js +19 -0
  13. package/dist/components/Icons.js.map +1 -0
  14. package/dist/components/add-to-cart-widget.d.ts +11 -0
  15. package/dist/components/add-to-cart-widget.js +85 -0
  16. package/dist/components/add-to-cart-widget.js.map +1 -0
  17. package/dist/components/buy/buy-card.d.ts +30 -0
  18. package/dist/components/buy/buy-card.js +109 -0
  19. package/dist/components/buy/buy-card.js.map +1 -0
  20. package/dist/components/buy/carousel-buy-card.d.ts +12 -0
  21. package/dist/components/buy/carousel-buy-card.js +94 -0
  22. package/dist/components/buy/carousel-buy-card.js.map +1 -0
  23. package/dist/components/buy/multi-family/all-variants-carousel.d.ts +4 -0
  24. package/dist/components/buy/multi-family/all-variants-carousel.js +115 -0
  25. package/dist/components/buy/multi-family/all-variants-carousel.js.map +1 -0
  26. package/dist/components/buy/multi-family/family-carousel/index.d.ts +4 -0
  27. package/dist/components/buy/multi-family/family-carousel/index.js +27 -0
  28. package/dist/components/buy/multi-family/family-carousel/index.js.map +1 -0
  29. package/dist/components/buy/multi-family/family-carousel/slide.d.ts +11 -0
  30. package/dist/components/buy/multi-family/family-carousel/slide.js +35 -0
  31. package/dist/components/buy/multi-family/family-carousel/slide.js.map +1 -0
  32. package/dist/components/buy/multi-family/family-carousel/state.d.ts +20 -0
  33. package/dist/components/buy/multi-family/family-carousel/state.js +59 -0
  34. package/dist/components/buy/multi-family/family-carousel/state.js.map +1 -0
  35. package/dist/components/buy/multi-family/index.d.ts +2 -0
  36. package/dist/components/buy/multi-family/index.js +3 -0
  37. package/dist/components/buy/multi-family/index.js.map +1 -0
  38. package/dist/components/buy/single-family-selector.d.ts +15 -0
  39. package/dist/components/buy/single-family-selector.js +28 -0
  40. package/dist/components/buy/single-family-selector.js.map +1 -0
  41. package/dist/components/buy/title-and-byline.d.ts +8 -0
  42. package/dist/components/buy/title-and-byline.js +7 -0
  43. package/dist/components/buy/title-and-byline.js.map +1 -0
  44. package/dist/components/cart/cart-panel/cart-line-item.d.ts +11 -0
  45. package/dist/components/cart/cart-panel/cart-line-item.js +25 -0
  46. package/dist/components/cart/cart-panel/cart-line-item.js.map +1 -0
  47. package/dist/components/cart/cart-panel/index.d.ts +19 -0
  48. package/dist/components/cart/cart-panel/index.js +65 -0
  49. package/dist/components/cart/cart-panel/index.js.map +1 -0
  50. package/dist/components/cart/cart-panel/promo-code.d.ts +4 -0
  51. package/dist/components/cart/cart-panel/promo-code.js +62 -0
  52. package/dist/components/cart/cart-panel/promo-code.js.map +1 -0
  53. package/dist/components/cart/cart-panel/total-area.d.ts +7 -0
  54. package/dist/components/cart/cart-panel/total-area.js +14 -0
  55. package/dist/components/cart/cart-panel/total-area.js.map +1 -0
  56. package/dist/components/checkout/payment-step-form/card-icon-row.d.ts +2 -0
  57. package/dist/components/checkout/payment-step-form/card-icon-row.js +14 -0
  58. package/dist/components/checkout/payment-step-form/card-icon-row.js.map +1 -0
  59. package/dist/components/checkout/payment-step-form/card-icons/amex.d.ts +4 -0
  60. package/dist/components/checkout/payment-step-form/card-icons/amex.js +6 -0
  61. package/dist/components/checkout/payment-step-form/card-icons/amex.js.map +1 -0
  62. package/dist/components/checkout/payment-step-form/card-icons/diners-club.d.ts +4 -0
  63. package/dist/components/checkout/payment-step-form/card-icons/diners-club.js +6 -0
  64. package/dist/components/checkout/payment-step-form/card-icons/diners-club.js.map +1 -0
  65. package/dist/components/checkout/payment-step-form/card-icons/discover.d.ts +4 -0
  66. package/dist/components/checkout/payment-step-form/card-icons/discover.js +6 -0
  67. package/dist/components/checkout/payment-step-form/card-icons/discover.js.map +1 -0
  68. package/dist/components/checkout/payment-step-form/card-icons/jcb.d.ts +4 -0
  69. package/dist/components/checkout/payment-step-form/card-icons/jcb.js +6 -0
  70. package/dist/components/checkout/payment-step-form/card-icons/jcb.js.map +1 -0
  71. package/dist/components/checkout/payment-step-form/card-icons/mastercard.d.ts +4 -0
  72. package/dist/components/checkout/payment-step-form/card-icons/mastercard.js +6 -0
  73. package/dist/components/checkout/payment-step-form/card-icons/mastercard.js.map +1 -0
  74. package/dist/components/checkout/payment-step-form/card-icons/visa.d.ts +4 -0
  75. package/dist/components/checkout/payment-step-form/card-icons/visa.js +6 -0
  76. package/dist/components/checkout/payment-step-form/card-icons/visa.js.map +1 -0
  77. package/dist/components/checkout/payment-step-form/cc-button.d.ts +3 -0
  78. package/dist/components/checkout/payment-step-form/cc-button.js +6 -0
  79. package/dist/components/checkout/payment-step-form/cc-button.js.map +1 -0
  80. package/dist/components/checkout/payment-step-form/contact-form.d.ts +5 -0
  81. package/dist/components/checkout/payment-step-form/contact-form.js +6 -0
  82. package/dist/components/checkout/payment-step-form/contact-form.js.map +1 -0
  83. package/dist/components/checkout/payment-step-form/crypto-icons/btc.d.ts +4 -0
  84. package/dist/components/checkout/payment-step-form/crypto-icons/btc.js +6 -0
  85. package/dist/components/checkout/payment-step-form/crypto-icons/btc.js.map +1 -0
  86. package/dist/components/checkout/payment-step-form/crypto-icons/eth.d.ts +4 -0
  87. package/dist/components/checkout/payment-step-form/crypto-icons/eth.js +6 -0
  88. package/dist/components/checkout/payment-step-form/crypto-icons/eth.js.map +1 -0
  89. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.d.ts +4 -0
  90. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.js +6 -0
  91. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.js.map +1 -0
  92. package/dist/components/checkout/payment-step-form/index.d.ts +4 -0
  93. package/dist/components/checkout/payment-step-form/index.js +66 -0
  94. package/dist/components/checkout/payment-step-form/index.js.map +1 -0
  95. package/dist/components/checkout/payment-step-form/methods/bank-transfer.d.ts +4 -0
  96. package/dist/components/checkout/payment-step-form/methods/bank-transfer.js +24 -0
  97. package/dist/components/checkout/payment-step-form/methods/bank-transfer.js.map +1 -0
  98. package/dist/components/checkout/payment-step-form/methods/card.d.ts +4 -0
  99. package/dist/components/checkout/payment-step-form/methods/card.js +160 -0
  100. package/dist/components/checkout/payment-step-form/methods/card.js.map +1 -0
  101. package/dist/components/checkout/payment-step-form/methods/crypto.d.ts +9 -0
  102. package/dist/components/checkout/payment-step-form/methods/crypto.js +137 -0
  103. package/dist/components/checkout/payment-step-form/methods/crypto.js.map +1 -0
  104. package/dist/components/checkout/payment-step-form/methods/index.d.ts +6 -0
  105. package/dist/components/checkout/payment-step-form/methods/index.js +21 -0
  106. package/dist/components/checkout/payment-step-form/methods/index.js.map +1 -0
  107. package/dist/components/checkout/shipping-step-form.d.ts +3 -0
  108. package/dist/components/checkout/shipping-step-form.js +53 -0
  109. package/dist/components/checkout/shipping-step-form.js.map +1 -0
  110. package/dist/components/index.d.ts +8 -0
  111. package/dist/components/index.js +9 -0
  112. package/dist/components/index.js.map +1 -0
  113. package/dist/components/item/product-card.d.ts +7 -0
  114. package/dist/components/item/product-card.js +9 -0
  115. package/dist/components/item/product-card.js.map +1 -0
  116. package/dist/components/item-selector/button.d.ts +4 -0
  117. package/dist/components/item-selector/button.js +47 -0
  118. package/dist/components/item-selector/button.js.map +1 -0
  119. package/dist/components/item-selector/carousel/index.d.ts +12 -0
  120. package/dist/components/item-selector/carousel/index.js +74 -0
  121. package/dist/components/item-selector/carousel/index.js.map +1 -0
  122. package/dist/components/item-selector/carousel/slider.d.ts +8 -0
  123. package/dist/components/item-selector/carousel/slider.js +12 -0
  124. package/dist/components/item-selector/carousel/slider.js.map +1 -0
  125. package/dist/components/item-selector/index.d.ts +2 -0
  126. package/dist/components/item-selector/index.js +3 -0
  127. package/dist/components/item-selector/index.js.map +1 -0
  128. package/dist/components/item-selector/quantity-indicator.d.ts +9 -0
  129. package/dist/components/item-selector/quantity-indicator.js +16 -0
  130. package/dist/components/item-selector/quantity-indicator.js.map +1 -0
  131. package/dist/components/node-tabs/index.d.ts +14 -0
  132. package/dist/components/node-tabs/index.js +42 -0
  133. package/dist/components/node-tabs/index.js.map +1 -0
  134. package/dist/components/node-tabs/node-image.d.ts +6 -0
  135. package/dist/components/node-tabs/node-image.js +13 -0
  136. package/dist/components/node-tabs/node-image.js.map +1 -0
  137. package/dist/index.d.ts +9 -0
  138. package/dist/index.js +9 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/metering.d.ts +162 -0
  141. package/dist/metering.js +204 -0
  142. package/dist/metering.js.map +1 -0
  143. package/dist/metering.test.d.ts +13 -0
  144. package/dist/metering.test.js +181 -0
  145. package/dist/metering.test.js.map +1 -0
  146. package/dist/product-card.test.d.ts +14 -0
  147. package/dist/product-card.test.js +96 -0
  148. package/dist/product-card.test.js.map +1 -0
  149. package/dist/service/context.d.ts +8 -0
  150. package/dist/service/context.js +19 -0
  151. package/dist/service/context.js.map +1 -0
  152. package/dist/service/debug.d.ts +10 -0
  153. package/dist/service/debug.js +30 -0
  154. package/dist/service/debug.js.map +1 -0
  155. package/dist/service/impls/standalone/actual-line-item.d.ts +40 -0
  156. package/dist/service/impls/standalone/actual-line-item.js +84 -0
  157. package/dist/service/impls/standalone/actual-line-item.js.map +1 -0
  158. package/dist/service/impls/standalone/get-instance.d.ts +2 -0
  159. package/dist/service/impls/standalone/get-instance.js +39 -0
  160. package/dist/service/impls/standalone/get-instance.js.map +1 -0
  161. package/dist/service/impls/standalone/index.d.ts +67 -0
  162. package/dist/service/impls/standalone/index.js +416 -0
  163. package/dist/service/impls/standalone/index.js.map +1 -0
  164. package/dist/service/impls/standalone/order/firebase.d.ts +6 -0
  165. package/dist/service/impls/standalone/order/firebase.js +6 -0
  166. package/dist/service/impls/standalone/order/firebase.js.map +1 -0
  167. package/dist/service/impls/standalone/order/index.d.ts +30 -0
  168. package/dist/service/impls/standalone/order/index.js +21 -0
  169. package/dist/service/impls/standalone/order/index.js.map +1 -0
  170. package/dist/service/impls/standalone/persistence.d.ts +4 -0
  171. package/dist/service/impls/standalone/persistence.js +22 -0
  172. package/dist/service/impls/standalone/persistence.js.map +1 -0
  173. package/dist/service/path-utils.d.ts +7 -0
  174. package/dist/service/path-utils.js +16 -0
  175. package/dist/service/path-utils.js.map +1 -0
  176. package/dist/service/sep.d.ts +6 -0
  177. package/dist/service/sep.js +6 -0
  178. package/dist/service/sep.js.map +1 -0
  179. package/dist/types/category-node.d.ts +36 -0
  180. package/dist/types/category-node.js +2 -0
  181. package/dist/types/category-node.js.map +1 -0
  182. package/dist/types/checkout.d.ts +33 -0
  183. package/dist/types/checkout.js +2 -0
  184. package/dist/types/checkout.js.map +1 -0
  185. package/dist/types/commerce-config.d.ts +11 -0
  186. package/dist/types/commerce-config.js +2 -0
  187. package/dist/types/commerce-config.js.map +1 -0
  188. package/dist/types/commerce-service.d.ts +109 -0
  189. package/dist/types/commerce-service.js +2 -0
  190. package/dist/types/commerce-service.js.map +1 -0
  191. package/dist/types/family.d.ts +16 -0
  192. package/dist/types/family.js +2 -0
  193. package/dist/types/family.js.map +1 -0
  194. package/dist/types/index.d.ts +13 -0
  195. package/dist/types/index.js +8 -0
  196. package/dist/types/index.js.map +1 -0
  197. package/dist/types/item-selector.d.ts +72 -0
  198. package/dist/types/item-selector.js +2 -0
  199. package/dist/types/item-selector.js.map +1 -0
  200. package/dist/types/line-item.d.ts +14 -0
  201. package/dist/types/line-item.js +2 -0
  202. package/dist/types/line-item.js.map +1 -0
  203. package/dist/types/multi-family-selector-props.d.ts +16 -0
  204. package/dist/types/multi-family-selector-props.js +2 -0
  205. package/dist/types/multi-family-selector-props.js.map +1 -0
  206. package/dist/types/product.d.ts +15 -0
  207. package/dist/types/product.js +2 -0
  208. package/dist/types/product.js.map +1 -0
  209. package/dist/types/promo.d.ts +7 -0
  210. package/dist/types/promo.js +2 -0
  211. package/dist/types/promo.js.map +1 -0
  212. package/dist/types/selection-ui-specifier.d.ts +40 -0
  213. package/dist/types/selection-ui-specifier.js +2 -0
  214. package/dist/types/selection-ui-specifier.js.map +1 -0
  215. package/dist/types/string-mutator.d.ts +9 -0
  216. package/dist/types/string-mutator.js +2 -0
  217. package/dist/types/string-mutator.js.map +1 -0
  218. package/dist/types/token-separators.d.ts +6 -0
  219. package/dist/types/token-separators.js +2 -0
  220. package/dist/types/token-separators.js.map +1 -0
  221. package/dist/util/analytics.d.ts +9 -0
  222. package/dist/util/analytics.js +10 -0
  223. package/dist/util/analytics.js.map +1 -0
  224. package/dist/util/countries.d.ts +7 -0
  225. package/dist/util/countries.js +197 -0
  226. package/dist/util/countries.js.map +1 -0
  227. package/dist/util/error.d.ts +1 -0
  228. package/dist/util/error.js +22 -0
  229. package/dist/util/error.js.map +1 -0
  230. package/dist/util/index.d.ts +15 -0
  231. package/dist/util/index.js +54 -0
  232. package/dist/util/index.js.map +1 -0
  233. package/dist/util/item-selector-options-accessor.d.ts +3 -0
  234. package/dist/util/item-selector-options-accessor.js +27 -0
  235. package/dist/util/item-selector-options-accessor.js.map +1 -0
  236. package/dist/util/line-item-ref.d.ts +8 -0
  237. package/dist/util/line-item-ref.js +15 -0
  238. package/dist/util/line-item-ref.js.map +1 -0
  239. package/dist/util/multi-family-selector-options-accessor.d.ts +3 -0
  240. package/dist/util/multi-family-selector-options-accessor.js +11 -0
  241. package/dist/util/multi-family-selector-options-accessor.js.map +1 -0
  242. package/dist/util/obs-string-mutator.d.ts +8 -0
  243. package/dist/util/obs-string-mutator.js +15 -0
  244. package/dist/util/obs-string-mutator.js.map +1 -0
  245. package/dist/util/product-media-accessor.d.ts +29 -0
  246. package/dist/util/product-media-accessor.js +22 -0
  247. package/dist/util/product-media-accessor.js.map +1 -0
  248. package/dist/util/promo-codes.d.ts +3 -0
  249. package/dist/util/promo-codes.js +100 -0
  250. package/dist/util/promo-codes.js.map +1 -0
  251. package/dist/util/selection-ui-specifiers.d.ts +3 -0
  252. package/dist/util/selection-ui-specifiers.js +24 -0
  253. package/dist/util/selection-ui-specifiers.js.map +1 -0
  254. package/dist/util/square-payment.d.ts +7 -0
  255. package/dist/util/square-payment.js +37 -0
  256. package/dist/util/square-payment.js.map +1 -0
  257. package/dist/util/use-sync-sku-param-w-current-item.d.ts +2 -0
  258. package/dist/util/use-sync-sku-param-w-current-item.js +61 -0
  259. package/dist/util/use-sync-sku-param-w-current-item.js.map +1 -0
  260. package/index.ts +18 -3
  261. package/metering.test.ts +199 -0
  262. package/metering.ts +289 -0
  263. package/package.json +19 -17
  264. package/product-card.test.tsx +104 -0
@@ -0,0 +1,204 @@
1
+ /**
2
+ * @hanzo/commerce/metering
3
+ *
4
+ * The ONE way every Hanzo product meters usage to commerce — the single
5
+ * billing source of truth — so that everything (not only the LLM/cloud path)
6
+ * can be paid for. This is the TypeScript counterpart of
7
+ * github.com/hanzoai/go-sdk/metering; the two share an identical wire contract.
8
+ *
9
+ * It provides two operations against commerce's canonical billing API:
10
+ *
11
+ * - authorize(): a pre-request balance gate. Fail-closed by default — if the
12
+ * balance cannot be determined the request is denied, exactly like the
13
+ * gateway's prepaid-balance gate. With tierAware it consults the effective
14
+ * balance (prepaid + included plan allotment, e.g. the free-tier daily
15
+ * credit) so included usage is honored before prepaid funds.
16
+ *
17
+ * - record(): a post-request usage write that debits the user's balance.
18
+ *
19
+ * Composition over duplication: this wraps the existing {@link Commerce}
20
+ * client (GET /v1/billing/balance, GET /v1/billing/tier, POST /v1/billing/usage)
21
+ * rather than re-implementing the HTTP layer.
22
+ *
23
+ * Auth is the commerce service token (admin-scoped S2S), sent as
24
+ * `Authorization: Bearer <token>` plus the tenant org as `X-IAM-Org-Id`. The
25
+ * token is a secret and MUST come from KMS (the operator wires it into
26
+ * COMMERCE_SERVICE_TOKEN); this module never reads it from disk.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { Metering } from '@hanzo/commerce/metering'
31
+ *
32
+ * const meter = Metering.fromEnv() // COMMERCE_URL + COMMERCE_SERVICE_TOKEN (KMS) + COMMERCE_SERVICE_ORG
33
+ *
34
+ * // Pre-request gate (fail-closed).
35
+ * const gate = await meter.authorize({ user: 'hanzo/alice' })
36
+ * if (!gate.allowed) {
37
+ * res.status(gate.reason === 'insufficient_balance' ? 402 : 503).end()
38
+ * return
39
+ * }
40
+ *
41
+ * // ... do the work, compute cost ...
42
+ *
43
+ * // Post-request record (best-effort).
44
+ * await meter.record({ user: 'hanzo/alice', amount: 5, provider: 'search' })
45
+ * ```
46
+ */
47
+ import { Commerce, CommerceApiError } from './client';
48
+ /** Canonical in-cluster commerce address (matches the gateway default). */
49
+ export const DEFAULT_COMMERCE_URL = 'http://commerce.hanzo.svc.cluster.local:8001';
50
+ /**
51
+ * Metering is the server-side hook a product uses to charge for usage. It is
52
+ * safe to share a single instance across requests.
53
+ */
54
+ export class Metering {
55
+ constructor(config = {}) {
56
+ const baseUrl = (config.baseUrl ?? '').replace(/\/+$/, '');
57
+ this.commerce = baseUrl
58
+ ? new Commerce({ baseUrl, token: config.token, timeoutMs: config.timeoutMs ?? 5000 })
59
+ : null;
60
+ this.org = (config.org ?? '').trim();
61
+ this.tierAware = config.tierAware ?? false;
62
+ this.failOpen = config.failOpen ?? false;
63
+ }
64
+ /**
65
+ * Build a Metering instance from the canonical environment variables. This
66
+ * is the one-liner products use at startup. Honors METERING_DISABLED for
67
+ * local dev.
68
+ */
69
+ static fromEnv(env = readEnv()) {
70
+ if (truthy(env.METERING_DISABLED))
71
+ return new Metering({});
72
+ return new Metering({
73
+ baseUrl: (env.COMMERCE_URL ?? '').trim() || DEFAULT_COMMERCE_URL,
74
+ token: env.COMMERCE_SERVICE_TOKEN,
75
+ org: (env.COMMERCE_SERVICE_ORG ?? '').trim() || 'hanzo',
76
+ tierAware: truthy(env.METERING_TIER_AWARE),
77
+ failOpen: truthy(env.METERING_FAIL_OPEN),
78
+ });
79
+ }
80
+ /** Whether a commerce base URL is configured. */
81
+ get enabled() {
82
+ return this.commerce !== null;
83
+ }
84
+ /**
85
+ * Pre-request balance gate. Never throws — returns a decision so callers map
86
+ * it to a status code. When not configured it always allows.
87
+ *
88
+ * Outcomes (matching the gateway):
89
+ * { allowed: true } -> allow.
90
+ * { allowed: false, reason: 'insufficient_balance' } -> 402 (out of funds).
91
+ * { allowed: false, reason: 'balance_unavailable' } -> 503 (fail-closed).
92
+ */
93
+ async authorize(id) {
94
+ if (!this.commerce)
95
+ return { allowed: true };
96
+ const user = (id.user ?? '').trim();
97
+ if (!user) {
98
+ return this.failOpen ? { allowed: true } : { allowed: false, reason: 'balance_unavailable' };
99
+ }
100
+ const headers = orgHeader(id.org ?? this.org);
101
+ try {
102
+ const available = this.tierAware
103
+ ? (await this.commerce.getTier(user, { headers })).balance.effectiveAvailable
104
+ : (await this.commerce.getBalance(user, id.currency ?? 'usd', undefined, headers)).available;
105
+ if (available > 0)
106
+ return { allowed: true, available };
107
+ return { allowed: false, reason: 'insufficient_balance', available };
108
+ }
109
+ catch {
110
+ // Balance unknown (commerce unreachable / non-2xx).
111
+ if (this.failOpen)
112
+ return { allowed: true };
113
+ return { allowed: false, reason: 'balance_unavailable' };
114
+ }
115
+ }
116
+ /**
117
+ * Record a usage event, debiting the user's balance. No-op when not
118
+ * configured or amount <= 0 (commerce treats zero-cost usage as skipped).
119
+ * Decoupled from gating: the work already happened, so balance is not
120
+ * re-checked here. Returns null on no-op.
121
+ */
122
+ async record(usage) {
123
+ if (!this.commerce || usage.amount <= 0)
124
+ return null;
125
+ if (!usage.user?.trim())
126
+ throw new Error('metering: record requires a user');
127
+ const headers = orgHeader(usage.org ?? this.org);
128
+ // Commerce's usageRequest field names map one-for-one; org travels via the
129
+ // header, never the body.
130
+ const body = {
131
+ user: usage.user,
132
+ currency: usage.currency ?? 'usd',
133
+ amount: usage.amount,
134
+ model: usage.model,
135
+ provider: usage.provider,
136
+ promptTokens: usage.promptTokens,
137
+ completionTokens: usage.completionTokens,
138
+ totalTokens: usage.totalTokens,
139
+ requestId: usage.requestId,
140
+ premium: usage.premium,
141
+ stream: usage.stream,
142
+ status: usage.status,
143
+ clientIp: usage.clientIp,
144
+ };
145
+ const tx = await this.commerce.addUsageRecord(body, undefined, headers);
146
+ // Commerce returns { transactionId, user, amount, currency, type }.
147
+ return tx;
148
+ }
149
+ /**
150
+ * Convenience for products that prefer to throw on denial. Throws a
151
+ * {@link CommerceApiError} with status 402 (insufficient) or 503 (unknown).
152
+ */
153
+ async authorizeOrThrow(id) {
154
+ const decision = await this.authorize(id);
155
+ if (decision.allowed)
156
+ return;
157
+ if (decision.reason === 'insufficient_balance') {
158
+ throw new CommerceApiError(402, 'Insufficient balance. Please add credits at console.hanzo.ai');
159
+ }
160
+ throw new CommerceApiError(503, 'Billing temporarily unavailable');
161
+ }
162
+ }
163
+ // ---------------------------------------------------------------------------
164
+ // Identity from gateway-minted headers (the trust boundary)
165
+ // ---------------------------------------------------------------------------
166
+ /** Gateway-minted identity headers. */
167
+ export const HEADER_USER_ID = 'x-user-id';
168
+ export const HEADER_ORG_ID = 'x-org-id';
169
+ /**
170
+ * Build a {@link MeteringIdentity} from gateway-minted identity headers. The
171
+ * user is "{org}/{sub}" (commerce's iam-user form) when both are present.
172
+ * Accepts a header bag with string | string[] | undefined values (Node http,
173
+ * Next.js, fetch Headers via Object.fromEntries).
174
+ */
175
+ export function identityFromHeaders(headers) {
176
+ const get = (k) => {
177
+ if (typeof headers.get === 'function') {
178
+ return (headers.get(k) ?? '').trim();
179
+ }
180
+ const v = headers[k];
181
+ return (Array.isArray(v) ? v[0] : v ?? '').trim();
182
+ };
183
+ const org = get(HEADER_ORG_ID);
184
+ const sub = get(HEADER_USER_ID);
185
+ const user = org && sub ? `${org}/${sub}` : sub;
186
+ return { user, org };
187
+ }
188
+ // ---------------------------------------------------------------------------
189
+ // internals
190
+ // ---------------------------------------------------------------------------
191
+ function orgHeader(org) {
192
+ const o = (org ?? '').trim();
193
+ return o ? { 'X-IAM-Org-Id': o } : undefined;
194
+ }
195
+ function truthy(v) {
196
+ const s = (v ?? '').trim().toLowerCase();
197
+ return s === 'true' || s === '1' || s === 'yes';
198
+ }
199
+ function readEnv() {
200
+ // Node/edge: process.env when present, else empty (browser).
201
+ const g = globalThis;
202
+ return g.process?.env ?? {};
203
+ }
204
+ //# sourceMappingURL=metering.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metering.js","sourceRoot":"","sources":["../metering.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Contract tests for @hanzo/commerce/metering.
3
+ *
4
+ * Self-contained: mocks global fetch and asserts the exact commerce wire
5
+ * contract, mirroring github.com/hanzoai/go-sdk/metering's tests so the two
6
+ * clients gate on the identical balance source. Runnable standalone with:
7
+ *
8
+ * npx -y -p typescript@5.9.3 -p tsx tsx pkgs/commerce/metering.test.ts
9
+ *
10
+ * It uses no test-runner dependency (the package ships none); a tiny harness
11
+ * below prints PASS/FAIL and exits non-zero on failure.
12
+ */
13
+ export {};
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Contract tests for @hanzo/commerce/metering.
3
+ *
4
+ * Self-contained: mocks global fetch and asserts the exact commerce wire
5
+ * contract, mirroring github.com/hanzoai/go-sdk/metering's tests so the two
6
+ * clients gate on the identical balance source. Runnable standalone with:
7
+ *
8
+ * npx -y -p typescript@5.9.3 -p tsx tsx pkgs/commerce/metering.test.ts
9
+ *
10
+ * It uses no test-runner dependency (the package ships none); a tiny harness
11
+ * below prints PASS/FAIL and exits non-zero on failure.
12
+ */
13
+ import { Metering, identityFromHeaders } from './metering';
14
+ // ---- tiny assert harness ----
15
+ let failures = 0;
16
+ let count = 0;
17
+ function test(name, fn) {
18
+ count++;
19
+ Promise.resolve()
20
+ .then(fn)
21
+ .then(() => console.log(`ok - ${name}`))
22
+ .catch((err) => {
23
+ failures++;
24
+ console.error(`FAIL - ${name}\n ${err?.message ?? err}`);
25
+ });
26
+ }
27
+ function eq(actual, expected, msg = '') {
28
+ if (actual !== expected)
29
+ throw new Error(`${msg} expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
30
+ }
31
+ function ok(cond, msg = '') {
32
+ if (!cond)
33
+ throw new Error(msg || 'expected true');
34
+ }
35
+ function mockFetch(status, json) {
36
+ const original = globalThis.fetch;
37
+ let last = null;
38
+ globalThis.fetch = (async (input, init) => {
39
+ const hdrs = {};
40
+ const h = init?.headers;
41
+ if (h)
42
+ for (const [k, v] of Object.entries(h))
43
+ hdrs[k.toLowerCase()] = v;
44
+ last = {
45
+ url: input.toString(),
46
+ method: init?.method ?? 'GET',
47
+ headers: hdrs,
48
+ body: init?.body ? JSON.parse(init.body) : undefined,
49
+ };
50
+ return {
51
+ ok: status >= 200 && status < 300,
52
+ status,
53
+ statusText: `HTTP ${status}`,
54
+ json: async () => json,
55
+ text: async () => JSON.stringify(json),
56
+ };
57
+ });
58
+ return { restore: () => { globalThis.fetch = original; }, last: () => last };
59
+ }
60
+ const BASE = 'http://commerce.test';
61
+ test('authorize allows when available > 0 and hits the canonical contract', async () => {
62
+ const fx = mockFetch(200, { user: 'hanzo/alice', currency: 'usd', balance: 5000, holds: 0, available: 5000 });
63
+ const meter = new Metering({ baseUrl: BASE, token: 'svc-token', org: 'hanzo' });
64
+ const d = await meter.authorize({ user: 'hanzo/alice' });
65
+ fx.restore();
66
+ ok(d.allowed, 'should allow');
67
+ eq(d.available, 5000, 'available');
68
+ const c = fx.last();
69
+ eq(c.method, 'GET', 'method');
70
+ ok(c.url.startsWith(`${BASE}/v1/billing/balance?`), `url=${c.url}`);
71
+ ok(c.url.includes('user=hanzo%2Falice'), `url must url-encode user: ${c.url}`);
72
+ ok(c.url.includes('currency=usd'), `url must carry currency: ${c.url}`);
73
+ eq(c.headers['authorization'], 'Bearer svc-token', 'bearer');
74
+ eq(c.headers['x-iam-org-id'], 'hanzo', 'org header');
75
+ });
76
+ test('authorize denies (insufficient) when available == 0', async () => {
77
+ const fx = mockFetch(200, { available: 0 });
78
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo' });
79
+ const d = await meter.authorize({ user: 'hanzo/alice' });
80
+ fx.restore();
81
+ ok(!d.allowed, 'should deny');
82
+ eq(d.reason, 'insufficient_balance', 'reason');
83
+ });
84
+ test('authorize fail-closed on commerce error (503/unavailable)', async () => {
85
+ const fx = mockFetch(500, { error: 'boom' });
86
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo' });
87
+ const d = await meter.authorize({ user: 'hanzo/alice' });
88
+ fx.restore();
89
+ ok(!d.allowed, 'fail-closed must deny on 500');
90
+ eq(d.reason, 'balance_unavailable', 'reason');
91
+ });
92
+ test('authorize fail-open allows on commerce error when configured', async () => {
93
+ const fx = mockFetch(503, { error: 'down' });
94
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo', failOpen: true });
95
+ const d = await meter.authorize({ user: 'hanzo/alice' });
96
+ fx.restore();
97
+ ok(d.allowed, 'fail-open must allow');
98
+ });
99
+ test('authorize not-configured allows', async () => {
100
+ const meter = new Metering({}); // no baseUrl
101
+ ok(!meter.enabled, 'enabled should be false');
102
+ const d = await meter.authorize({ user: 'hanzo/alice' });
103
+ ok(d.allowed, 'not-configured must allow');
104
+ });
105
+ test('tier-aware authorize uses effectiveAvailable and hits /v1/billing/tier', async () => {
106
+ const fx = mockFetch(200, { user: 'hanzo/alice', tier: { name: 'free' }, balance: { currency: 'usd', prepaidAvailable: 0, dailyRemaining: 100, effectiveAvailable: 100 } });
107
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo', tierAware: true });
108
+ const d = await meter.authorize({ user: 'hanzo/alice' });
109
+ fx.restore();
110
+ ok(d.allowed, 'included allotment should allow');
111
+ eq(d.available, 100, 'effective available');
112
+ ok(fx.last().url.startsWith(`${BASE}/v1/billing/tier?`), `tier url=${fx.last().url}`);
113
+ });
114
+ test('tier-aware authorize denies when effectiveAvailable == 0', async () => {
115
+ const fx = mockFetch(200, { balance: { effectiveAvailable: 0, prepaidAvailable: 0, dailyRemaining: 0, currency: 'usd' } });
116
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo', tierAware: true });
117
+ const d = await meter.authorize({ user: 'hanzo/alice' });
118
+ fx.restore();
119
+ ok(!d.allowed && d.reason === 'insufficient_balance', 'exhausted must deny insufficient');
120
+ });
121
+ test('per-call org overrides default', async () => {
122
+ const fx = mockFetch(200, { available: 1 });
123
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo' });
124
+ await meter.authorize({ user: 'zoo/bob', org: 'zoo' });
125
+ fx.restore();
126
+ eq(fx.last().headers['x-iam-org-id'], 'zoo', 'per-call org override');
127
+ });
128
+ test('record posts canonical payload', async () => {
129
+ const fx = mockFetch(201, { transactionId: 'tx_123', user: 'hanzo/alice', amount: 250, currency: 'usd', type: 'withdraw' });
130
+ const meter = new Metering({ baseUrl: BASE, token: 'svc-token', org: 'hanzo' });
131
+ const res = await meter.record({ user: 'hanzo/alice', amount: 250, provider: 'search', requestId: 'req-9', status: 'success' });
132
+ fx.restore();
133
+ ok(res !== null && res.transactionId === 'tx_123' && res.amount === 250, 'record result');
134
+ const c = fx.last();
135
+ eq(c.method, 'POST', 'method');
136
+ eq(c.url, `${BASE}/v1/billing/usage`, 'usage url');
137
+ eq(c.headers['authorization'], 'Bearer svc-token', 'bearer');
138
+ eq(c.headers['x-iam-org-id'], 'hanzo', 'org header');
139
+ const b = c.body;
140
+ eq(b.user, 'hanzo/alice', 'body.user');
141
+ eq(b.amount, 250, 'body.amount');
142
+ eq(b.currency, 'usd', 'body.currency defaulted');
143
+ eq(b.provider, 'search', 'body.provider');
144
+ eq(b.requestId, 'req-9', 'body.requestId');
145
+ ok(!('org' in b), 'org must not leak into the body');
146
+ });
147
+ test('record zero-amount is a no-op (no fetch)', async () => {
148
+ const fx = mockFetch(200, {});
149
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo' });
150
+ const res = await meter.record({ user: 'hanzo/alice', amount: 0 });
151
+ fx.restore();
152
+ ok(res === null, 'zero-amount returns null');
153
+ ok(fx.last() === null, 'zero-amount must not call commerce');
154
+ });
155
+ test('record not-configured is a no-op', async () => {
156
+ const meter = new Metering({});
157
+ const res = await meter.record({ user: 'hanzo/alice', amount: 100 });
158
+ ok(res === null, 'not-configured returns null');
159
+ });
160
+ test('fromEnv applies defaults and reads KMS-sourced token', () => {
161
+ const meter = Metering.fromEnv({ COMMERCE_SERVICE_TOKEN: 'kms-token', METERING_TIER_AWARE: 'true' });
162
+ ok(meter.enabled, 'fromEnv default base URL enables the meter');
163
+ });
164
+ test('fromEnv honors METERING_DISABLED', () => {
165
+ const meter = Metering.fromEnv({ METERING_DISABLED: 'true', COMMERCE_URL: BASE });
166
+ ok(!meter.enabled, 'disabled must not be enabled');
167
+ });
168
+ test('identityFromHeaders builds org/sub from gateway headers', () => {
169
+ const id = identityFromHeaders({ 'x-org-id': 'zoo', 'x-user-id': 'bob' });
170
+ eq(id.user, 'zoo/bob', 'user');
171
+ eq(id.org, 'zoo', 'org');
172
+ });
173
+ // summary
174
+ setTimeout(() => {
175
+ if (failures > 0) {
176
+ console.error(`\n${failures}/${count} tests FAILED`);
177
+ process.exit(1);
178
+ }
179
+ console.log(`\nall ${count} tests passed`);
180
+ }, 200);
181
+ //# sourceMappingURL=metering.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metering.test.js","sourceRoot":"","sources":["../metering.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Annotation contract test for @hanzo/commerce ProductCard + AddToCartWidget.
3
+ *
4
+ * Proves the zero-app-config promise: rendering <ProductCard item={...}/> emits
5
+ * NEUTRAL, STANDARD self-description — schema.org Product/Offer microdata plus a
6
+ * schema.org AddAction on the add control — with no analytics code in the app.
7
+ * A tracker (track.js `annotate`, GA4 Merchant, any reader) captures the item
8
+ * and the add-to-cart intent from the DOM alone.
9
+ *
10
+ * Self-contained, mirroring metering.test.ts: no test-runner dependency. Run with:
11
+ *
12
+ * npx -y tsx pkgs/commerce/product-card.test.tsx
13
+ */
14
+ export {};
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Annotation contract test for @hanzo/commerce ProductCard + AddToCartWidget.
4
+ *
5
+ * Proves the zero-app-config promise: rendering <ProductCard item={...}/> emits
6
+ * NEUTRAL, STANDARD self-description — schema.org Product/Offer microdata plus a
7
+ * schema.org AddAction on the add control — with no analytics code in the app.
8
+ * A tracker (track.js `annotate`, GA4 Merchant, any reader) captures the item
9
+ * and the add-to-cart intent from the DOM alone.
10
+ *
11
+ * Self-contained, mirroring metering.test.ts: no test-runner dependency. Run with:
12
+ *
13
+ * npx -y tsx pkgs/commerce/product-card.test.tsx
14
+ */
15
+ import React from 'react';
16
+ import { renderToStaticMarkup } from 'react-dom/server';
17
+ import ProductCard from './components/item/product-card';
18
+ // ---- tiny assert harness (mirrors metering.test.ts) ----
19
+ let failures = 0;
20
+ let count = 0;
21
+ function test(name, fn) {
22
+ count++;
23
+ try {
24
+ fn();
25
+ console.log(`ok - ${name}`);
26
+ }
27
+ catch (err) {
28
+ failures++;
29
+ console.error(`FAIL - ${name}`);
30
+ console.error(err.message);
31
+ }
32
+ }
33
+ function ok(cond, msg) {
34
+ if (!cond)
35
+ throw new Error(msg);
36
+ }
37
+ // A minimal LineItem-shaped mock: getters + cart methods, no media (MediaStack
38
+ // renders empty). Enough to drive the microdata the card carries.
39
+ const item = {
40
+ id: 'x1',
41
+ sku: 'LXB-AU-B',
42
+ familyId: 'LXB',
43
+ familyTitle: 'Bar',
44
+ optionLabel: '1oz',
45
+ price: 99.5,
46
+ get title() {
47
+ return 'Gold Bar, 1oz';
48
+ },
49
+ get quantity() {
50
+ return 0;
51
+ },
52
+ get isInCart() {
53
+ return false;
54
+ },
55
+ increment() { },
56
+ decrement() { },
57
+ };
58
+ const html = renderToStaticMarkup(_jsx(ProductCard, { item: item }));
59
+ // Attribute names are matched case-insensitively: HTML parsing and setAttribute
60
+ // both lowercase attribute names in the HTML namespace, so React's camelCase SSR
61
+ // serialization (itemType="…") becomes itemtype in the live DOM that annotate
62
+ // queries. The DOM-capture contract is proven end-to-end in track.js's tests.
63
+ const H = html.toLowerCase();
64
+ const has = (needle) => H.includes(needle.toLowerCase());
65
+ test('card is a schema.org Product', () => {
66
+ ok(has('itemtype="https://schema.org/Product"'), 'Product itemtype present');
67
+ ok(has('data-slot="card"'), 'shadcn data-slot preserved');
68
+ });
69
+ test('product name is machine-readable', () => {
70
+ ok(has('itemprop="name"'), 'name itemprop present');
71
+ ok(has('content="Gold Bar, 1oz"'), 'name value present');
72
+ });
73
+ test('product sku is machine-readable', () => {
74
+ ok(has('itemprop="sku"'), 'sku itemprop present');
75
+ ok(has('content="LXB-AU-B"'), 'sku value present');
76
+ });
77
+ test('price is a schema.org Offer with currency', () => {
78
+ ok(has('itemtype="https://schema.org/Offer"'), 'Offer itemtype present');
79
+ ok(has('itemprop="price"'), 'price itemprop present');
80
+ ok(has('content="99.5"'), 'raw price value present');
81
+ ok(has('itemprop="priceCurrency"'), 'priceCurrency itemprop present');
82
+ ok(has('content="USD"'), 'currency value present');
83
+ });
84
+ test('add control is a schema.org AddAction (maps to GA4 add_to_cart)', () => {
85
+ ok(has('itemtype="https://schema.org/AddAction"'), 'AddAction itemtype present');
86
+ ok(has('itemprop="potentialAction"'), 'action linked to the Product');
87
+ });
88
+ // summary
89
+ setTimeout(() => {
90
+ if (failures > 0) {
91
+ console.error(`\n${failures}/${count} tests FAILED`);
92
+ process.exit(1);
93
+ }
94
+ console.log(`\nall ${count} tests passed`);
95
+ }, 200);
96
+ //# sourceMappingURL=product-card.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-card.test.js","sourceRoot":"","sources":["../product-card.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import React, { type PropsWithChildren } from 'react';
2
+ import type CommerceService from '../types/commerce-service';
3
+ import type { CommerceConfig } from '../types';
4
+ declare const useCommerce: () => CommerceService;
5
+ declare const CommerceProvider: React.FC<PropsWithChildren & {
6
+ config: CommerceConfig;
7
+ }>;
8
+ export { useCommerce, CommerceProvider };
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import React, { createContext, useContext, useRef, } from 'react';
4
+ // https://dev.to/ivandotv/mobx-server-side-rendering-with-next-js-4m18
5
+ import { enableStaticRendering } from 'mobx-react-lite';
6
+ enableStaticRendering(typeof window === "undefined");
7
+ import { getInstance } from './impls/standalone';
8
+ const CommerceContext = createContext(undefined);
9
+ const useCommerce = () => {
10
+ return useContext(CommerceContext);
11
+ };
12
+ const CommerceProvider = ({ children, config }) => {
13
+ // TODO: Inject Promo fixture here from siteDef
14
+ const service = getInstance(config);
15
+ const valueRef = useRef(service);
16
+ return (_jsx(CommerceContext.Provider, { value: valueRef.current, children: children }));
17
+ };
18
+ export { useCommerce, CommerceProvider };
19
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../service/context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AACZ,OAAO,KAAK,EAAE,EACZ,aAAa,EACb,UAAU,EACV,MAAM,GAEP,MAAM,OAAO,CAAA;AAEd,uEAAuE;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,qBAAqB,CAAC,OAAO,MAAM,KAAK,WAAW,CAAC,CAAA;AAIpD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,MAAM,eAAe,GAAG,aAAa,CAA8B,SAAS,CAAC,CAAA;AAE7E,MAAM,WAAW,GAAG,GAAoB,EAAE;IACxC,OAAO,UAAU,CAAC,eAAe,CAAoB,CAAA;AACvD,CAAC,CAAA;AAED,MAAM,gBAAgB,GAEjB,CAAC,EACJ,QAAQ,EACR,MAAM,EACP,EAAE,EAAE;IAED,+CAA+C;IACjD,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IACnC,MAAM,QAAQ,GAAG,MAAM,CAAkB,OAAO,CAAC,CAAA;IAEjD,OAAO,CACL,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,YAC9C,QAAQ,GACgB,CAC5B,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EACL,WAAW,EACX,gBAAgB,EACjB,CAAA"}
@@ -0,0 +1,10 @@
1
+ import type { CategoryNode, CategoryNodeRole, Family, LineItem } from '../types';
2
+ export declare const categoryNodeDump: (node: CategoryNode, level?: number) => void;
3
+ export declare const selectedFamiliesDump: (result: Family[]) => string;
4
+ export declare const peekDump: (result: {
5
+ role: CategoryNodeRole;
6
+ family: Family | undefined;
7
+ families: Family[] | undefined;
8
+ node: CategoryNode | undefined;
9
+ item: LineItem | undefined;
10
+ } | string) => string;
@@ -0,0 +1,30 @@
1
+ // Node recursion
2
+ export const categoryNodeDump = (node, level = 0) => {
3
+ const spacers = [];
4
+ for (let i = 0; i < level; i++) {
5
+ spacers.push('----');
6
+ }
7
+ console.log("NODE:" + spacers.join(''), node.skuToken);
8
+ node.subNodes?.forEach((sn) => { categoryNodeDump(sn, level + 1); });
9
+ };
10
+ export const selectedFamiliesDump = (result) => {
11
+ const toDisplay = result.map((c) => (c.id));
12
+ return JSON.stringify(toDisplay, null, 2);
13
+ };
14
+ export const peekDump = (result) => {
15
+ if (typeof result === 'string') {
16
+ return result;
17
+ }
18
+ const toDisplay = {
19
+ role: result.role,
20
+ item: result.item ? result.item.sku : 'UNDEF',
21
+ family: result.family ? result.family.id : 'UNDEF',
22
+ families: result.families ? result.families.map((f) => (f.id)) : 'UNDEF',
23
+ node: result.node ?
24
+ (result.node.skuToken + (result.node.label ? (': ' + result.node.label) : ''))
25
+ :
26
+ 'UNDEF'
27
+ };
28
+ return JSON.stringify(toDisplay, null, 2);
29
+ };
30
+ //# sourceMappingURL=debug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug.js","sourceRoot":"","sources":["../../service/debug.ts"],"names":[],"mappings":"AAEE,iBAAiB;AACnB,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAkB,EAAE,QAAgB,CAAC,EAAQ,EAAE;IAC9E,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACtB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;IACtD,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAgB,EAAE,EAAE,GAAE,gBAAgB,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA,CAAA,CAAC,CAAC,CAAA;AACjF,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAgB,EAAU,EAAE;IAC/D,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;AAC3C,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAMf,EAAU,EAAE;IAEpB,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAA;IACf,CAAC;IAED,MAAM,SAAS,GAAG;QAChB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO;QAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;QAClD,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO;QACxE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9E,CAAC;gBACD,OAAO;KACV,CAAA;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;AAC3C,CAAC,CAAA"}