@classytic/primitives 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
3
3
  Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
4
  adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## [0.26.0] — 2026-08-27
7
+
8
+ ### Added
9
+
10
+ - **`./deadline` subpath** — `Deadline` value type + helpers for date-bounded
11
+ obligations (reservations, SLA windows, contract terms).
12
+ - **`./stock-level` subpath** — canonical stock-level representation and helpers.
13
+ - **`./item-facts` subpath** — cross-kernel item fact contract (weight, dimensions,
14
+ hazmat flags) that catalog declares and logistics/inventory reads.
15
+ - **`StockKey` branded type** (`./item-identity`) — prevents a bare merchandising
16
+ label or ObjectId being passed where a quant key is expected; the only source is
17
+ `stockKeyOf()` / `requireStockKey()`. Return types of both functions updated.
18
+ - **Upfront collection policy** (`./monetization`) — `UpfrontCollectionPolicy`
19
+ (`'full_payment' | 'deposit' | 'deferred'`) + `UPFRONT_COLLECTION_POLICIES`
20
+ const. Cross-kernel: catalog declares the policy, order turns it into tenders,
21
+ revenue settles them.
22
+
6
23
  ## [0.25.0] - 2026-08-24
7
24
 
8
25
  ### Added
@@ -0,0 +1,16 @@
1
+ //#region src/composition/deadline.d.ts
2
+ /**
3
+ * Race a promise against a deadline without leaking the loser.
4
+ *
5
+ * A call that can hang — no throw, no response, the underlying layer just
6
+ * never settles — must not block its caller forever. Nothing here cancels
7
+ * the original promise; it keeps running after the deadline, so its eventual
8
+ * settlement is swallowed rather than surfaced a second time to a caller
9
+ * that already moved on.
10
+ */
11
+ declare class DeadlineExceededError extends Error {
12
+ constructor(label: string, timeoutMs: number);
13
+ }
14
+ declare function withDeadline<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T>;
15
+ //#endregion
16
+ export { DeadlineExceededError, withDeadline };
@@ -0,0 +1,31 @@
1
+ //#region src/composition/deadline.ts
2
+ /**
3
+ * Race a promise against a deadline without leaking the loser.
4
+ *
5
+ * A call that can hang — no throw, no response, the underlying layer just
6
+ * never settles — must not block its caller forever. Nothing here cancels
7
+ * the original promise; it keeps running after the deadline, so its eventual
8
+ * settlement is swallowed rather than surfaced a second time to a caller
9
+ * that already moved on.
10
+ */
11
+ var DeadlineExceededError = class extends Error {
12
+ constructor(label, timeoutMs) {
13
+ super(`${label} timed out after ${timeoutMs}ms`);
14
+ this.name = "DeadlineExceededError";
15
+ }
16
+ };
17
+ function withDeadline(promise, timeoutMs, label) {
18
+ return new Promise((resolve, reject) => {
19
+ const timer = setTimeout(() => reject(new DeadlineExceededError(label, timeoutMs)), timeoutMs);
20
+ timer.unref?.();
21
+ promise.then((value) => {
22
+ clearTimeout(timer);
23
+ resolve(value);
24
+ }, (err) => {
25
+ clearTimeout(timer);
26
+ reject(err);
27
+ });
28
+ });
29
+ }
30
+ //#endregion
31
+ export { DeadlineExceededError, withDeadline };
@@ -0,0 +1,73 @@
1
+ import { ShelfLifePolicy } from "./shelf-life.mjs";
2
+ //#region src/inventory/item-facts.d.ts
3
+ /** Coarse lot/serial tracking. `trackingPolicy.mode` wins when both are set. */
4
+ type TrackingMode = "none" | "lot" | "serial";
5
+ /** The stock facts an Item contributes. Persistence and classification are the owning package's. */
6
+ interface ItemStockSource {
7
+ skuRef: string;
8
+ sku: string;
9
+ displayName: string;
10
+ /** The canonical STOCK unit — what quants and moves are balanced in. */
11
+ uom: string;
12
+ trackingMode: TrackingMode;
13
+ trackingPolicy?: ShelfLifePolicy | undefined;
14
+ /**
15
+ * Variable-weight item (deli, meat, loose produce): a measured weight is
16
+ * captured per received lot while the piece count stays the stock dimension.
17
+ */
18
+ catchWeight?: boolean | undefined;
19
+ weightUom?: string | undefined;
20
+ weight?: number | undefined;
21
+ volume?: number | undefined;
22
+ barcode?: string[] | undefined;
23
+ /** Retired items are not stockable. Independent of whether a Product sells. */
24
+ isActive: boolean;
25
+ }
26
+ /** The merchandising side. Every stock fact is optional — the Item owns those. */
27
+ interface ProductFacet {
28
+ skuRef: string;
29
+ sku: string;
30
+ displayName: string;
31
+ uom?: string | undefined;
32
+ trackingMode?: TrackingMode | undefined;
33
+ trackingPolicy?: ShelfLifePolicy | undefined;
34
+ catchWeight?: boolean | undefined;
35
+ weightUom?: string | undefined;
36
+ weight?: number | undefined;
37
+ volume?: number | undefined;
38
+ barcode?: string[] | undefined;
39
+ isActive?: boolean | undefined;
40
+ }
41
+ /** The resolved answer. `@classytic/flow`'s `SkuDetails` IS this type. */
42
+ interface ItemStockFacts {
43
+ skuRef: string;
44
+ sku: string;
45
+ displayName: string;
46
+ trackingMode: TrackingMode;
47
+ trackingPolicy?: ShelfLifePolicy;
48
+ uom: string;
49
+ catchWeight?: boolean;
50
+ weightUom?: string;
51
+ weight?: number;
52
+ volume?: number;
53
+ barcode?: string[];
54
+ isActive: boolean;
55
+ }
56
+ /**
57
+ * Merge an Item with its Product facet.
58
+ *
59
+ * - **Neither** ⇒ `null`. An unknown SKU is ordinary (a deleted line), not an error.
60
+ * - **Product only** ⇒ the product's own facts — every deployment before an Item exists.
61
+ * - **Item only** ⇒ the item's facts. A raw material nobody sells.
62
+ * - **Both** ⇒ stock facts from the Item, name and label from the Product.
63
+ *
64
+ * `isActive` follows the ITEM when one exists: a product withdrawn from sale is
65
+ * still stock on a shelf, whereas a retired item is not stockable at all. Taking
66
+ * the product's flag would make unlisting a product silently orphan its stock.
67
+ */
68
+ declare function resolveStockFacts(input: {
69
+ item?: ItemStockSource | null | undefined;
70
+ product?: ProductFacet | null | undefined;
71
+ }): ItemStockFacts | null;
72
+ //#endregion
73
+ export { ItemStockFacts, ItemStockSource, ProductFacet, TrackingMode, resolveStockFacts };
@@ -0,0 +1,53 @@
1
+ //#region src/inventory/item-facts.ts
2
+ /** Unit assumed when neither side declares one. A deployment-wide guess, and a bad one — set a real `uom` on the Item. */
3
+ const FALLBACK_UOM = "unit";
4
+ const defined = (key, value) => value === void 0 ? {} : { [key]: value };
5
+ /**
6
+ * Merge an Item with its Product facet.
7
+ *
8
+ * - **Neither** ⇒ `null`. An unknown SKU is ordinary (a deleted line), not an error.
9
+ * - **Product only** ⇒ the product's own facts — every deployment before an Item exists.
10
+ * - **Item only** ⇒ the item's facts. A raw material nobody sells.
11
+ * - **Both** ⇒ stock facts from the Item, name and label from the Product.
12
+ *
13
+ * `isActive` follows the ITEM when one exists: a product withdrawn from sale is
14
+ * still stock on a shelf, whereas a retired item is not stockable at all. Taking
15
+ * the product's flag would make unlisting a product silently orphan its stock.
16
+ */
17
+ function resolveStockFacts(input) {
18
+ const { item, product } = input;
19
+ if (!item && !product) return null;
20
+ if (!item) {
21
+ const p = product;
22
+ return {
23
+ skuRef: p.skuRef,
24
+ sku: p.sku,
25
+ displayName: p.displayName,
26
+ uom: p.uom ?? FALLBACK_UOM,
27
+ trackingMode: p.trackingMode ?? "none",
28
+ ...defined("trackingPolicy", p.trackingPolicy),
29
+ ...defined("catchWeight", p.catchWeight),
30
+ ...defined("weightUom", p.weightUom),
31
+ ...defined("weight", p.weight),
32
+ ...defined("volume", p.volume),
33
+ ...defined("barcode", p.barcode),
34
+ isActive: p.isActive ?? true
35
+ };
36
+ }
37
+ return {
38
+ skuRef: item.skuRef,
39
+ sku: product?.sku ?? item.sku,
40
+ displayName: product?.displayName ?? item.displayName,
41
+ uom: item.uom,
42
+ trackingMode: item.trackingMode,
43
+ ...defined("trackingPolicy", item.trackingPolicy),
44
+ ...defined("catchWeight", item.catchWeight),
45
+ ...defined("weightUom", item.weightUom),
46
+ ...defined("weight", item.weight),
47
+ ...defined("volume", item.volume),
48
+ ...defined("barcode", item.barcode),
49
+ isActive: item.isActive
50
+ };
51
+ }
52
+ //#endregion
53
+ export { resolveStockFacts };
@@ -1,24 +1,14 @@
1
+ import { t as Brand } from "./brand-Lj_75Y_r.mjs";
1
2
  //#region src/identity/item-identity.d.ts
2
3
  /**
3
- * `sku` is a LABEL; `skuRef` is the stock IDENTITY. Never one field.
4
+ * A resolved STOCK key — the string quants, moves and reservations are keyed by.
4
5
  *
5
- * | field | mutable? | job |
6
- * |---|---|---|
7
- * | `skuRef` | never | keys quants, moves and ledger rows |
8
- * | `sku` | operator edits it | what a human reads, types, searches |
9
- *
10
- * Use `stockKeyOf` for anything that reserves or moves stock, `skuDisplay` for
11
- * anything a human reads. Never hand-roll either: both fall back, in OPPOSITE
12
- * directions, and getting one backwards type-checks and returns a plausible
13
- * string.
14
- *
15
- * - stock key = `skuRef ?? sku` — absent `skuRef` means a pre-split producer
16
- * whose `sku` still IS the key.
17
- * - display = `skuLabel ?? sku ?? skuRef` — the ref last, and flagged, because
18
- * it can be an opaque id with no merchandising meaning.
19
- *
20
- * Pinned by `tests/unit/item-identity.test.ts`.
6
+ * Branded so a port that moves stock cannot accept a bare string: the only way
7
+ * to obtain one is {@link stockKeyOf} / {@link requireStockKey}, which apply the
8
+ * `skuRef ?? sku` rule. A merchandising label passed where a stock key belongs
9
+ * finds no quant and reports "out of stock" against a full shelf.
21
10
  */
11
+ type StockKey = Brand<string, "StockKey">;
22
12
  /** Identifier fields a record may carry — each shape populates a different subset. */
23
13
  interface SkuFields {
24
14
  /** Merchandising label — mutable, human-facing. */
@@ -40,12 +30,12 @@ interface SkuFields {
40
30
  */
41
31
  declare function stockKeyOf(item: SkuFields, fallback?: string | {
42
32
  toString(): string;
43
- }): string | undefined;
33
+ }): StockKey | undefined;
44
34
  /**
45
35
  * {@link stockKeyOf} that THROWS. Use wherever the key feeds a stock movement:
46
36
  * a reservation on `undefined` returns "out of stock" instead of failing.
47
37
  */
48
- declare function requireStockKey(item: SkuFields, context?: string): string;
38
+ declare function requireStockKey(item: SkuFields, context?: string): StockKey;
49
39
  /** What a surface should render for an item, and whether it had a real label. */
50
40
  interface SkuDisplay {
51
41
  /** Best human-facing value. `undefined` = no identifier at all; render a dash. */
@@ -68,4 +58,4 @@ declare function skuDisplay(item: SkuFields): SkuDisplay;
68
58
  */
69
59
  declare function looksLikeOpaqueId(value: string | undefined): boolean;
70
60
  //#endregion
71
- export { SkuDisplay, SkuFields, looksLikeOpaqueId, requireStockKey, skuDisplay, stockKeyOf };
61
+ export { SkuDisplay, SkuFields, StockKey, looksLikeOpaqueId, requireStockKey, skuDisplay, stockKeyOf };
@@ -22,6 +22,45 @@ declare const MONETIZATION_KINDS: readonly ["free", "one_time", "subscription",
22
22
  type MonetizationKind = (typeof MONETIZATION_KINDS)[number];
23
23
  /** True when `value` is one of the canonical kinds. */
24
24
  declare function isMonetizationKind(value: unknown): value is MonetizationKind;
25
+ /**
26
+ * HOW MUCH of a price is collected at the point of sale.
27
+ *
28
+ * Distinct from every neighbouring vocabulary, which is why it is its own:
29
+ *
30
+ * - order's `PAYMENT_TERMS` (`prepaid` | `collect_on_delivery`) says WHEN.
31
+ * - `PaymentAllocationStatus` (`partial`, `allocated`…) says how much of a
32
+ * RECEIVED payment has been applied to what it settles.
33
+ * - revenue's `PARTIALLY_REFUNDED` is a transaction's own state.
34
+ *
35
+ * None of them answers "what do we charge this customer right now", and a host
36
+ * that guesses charges either nothing or everything — opposite answers to the
37
+ * same question.
38
+ *
39
+ * It lives here, beside {@link MONETIZATION_KINDS}, for the reason stated in
40
+ * catalog's barrel: monetization is a CROSS-KERNEL contract. Catalog declares
41
+ * the policy on a product, order turns it into tenders, revenue settles them.
42
+ * Three packages reading one definition, none re-exporting it.
43
+ *
44
+ * - `full_payment` — the whole price, before the sale completes.
45
+ * - `deposit` — a fixed amount now, the balance outstanding on the
46
+ * order and settled later by another tender. One provider
47
+ * line instead of two, which matters when card fees are
48
+ * charged per transaction.
49
+ * - `pay_later` — nothing now. The order still records what is owed.
50
+ *
51
+ * ABSENCE means `full_payment` — the behaviour before this existed — so no
52
+ * stored record changes meaning when a build that knows this field ships.
53
+ */
54
+ declare const UPFRONT_COLLECTION_MODES: readonly ["full_payment", "deposit", "pay_later"];
55
+ type UpfrontCollectionMode = (typeof UPFRONT_COLLECTION_MODES)[number];
56
+ /**
57
+ * Narrow an unknown value to a collection mode.
58
+ *
59
+ * Readers MUST treat `false` as UNKNOWN, never as `full_payment`: a record
60
+ * written by a build that knows a mode this one does not has not said "charge
61
+ * everything", and assuming so takes money nobody agreed to.
62
+ */
63
+ declare function isUpfrontCollectionMode(value: unknown): value is UpfrontCollectionMode;
25
64
  /** A quantity-based price break. `price` (fixed override) XOR `discountPercent`. */
26
65
  interface PriceTier {
27
66
  /** Minimum quantity to qualify. */
@@ -191,4 +230,4 @@ declare function toRevenueMonetizationType(kind: MonetizationKind): RevenueMonet
191
230
  /** Revenue wire type → canonical kind (`purchase` → `one_time`). */
192
231
  declare function fromRevenueMonetizationType(type: RevenueMonetizationType): MonetizationKind;
193
232
  //#endregion
194
- export { BundleMonetization, DurationUnit, FreeMonetization, MONETIZATION_KINDS, Monetization, MonetizationKind, MoneyByCurrency, OneTimeMonetization, OneTimePricing, PriceTier, RevenueMonetizationType, SubscriptionMonetization, SubscriptionPlan, UsageMonetization, UsageRating, UsageTier, compareAtPriceOf, fromRevenueMonetizationType, isBundleMonetization, isFreeMonetization, isMonetizationKind, isOneTimeMonetization, isSubscriptionMonetization, isUsageMonetization, monetizationKindOf, toRevenueMonetizationType, unitPriceOf };
233
+ export { BundleMonetization, DurationUnit, FreeMonetization, MONETIZATION_KINDS, Monetization, MonetizationKind, MoneyByCurrency, OneTimeMonetization, OneTimePricing, PriceTier, RevenueMonetizationType, SubscriptionMonetization, SubscriptionPlan, UPFRONT_COLLECTION_MODES, UpfrontCollectionMode, UsageMonetization, UsageRating, UsageTier, compareAtPriceOf, fromRevenueMonetizationType, isBundleMonetization, isFreeMonetization, isMonetizationKind, isOneTimeMonetization, isSubscriptionMonetization, isUpfrontCollectionMode, isUsageMonetization, monetizationKindOf, toRevenueMonetizationType, unitPriceOf };
@@ -21,6 +21,51 @@ const MONETIZATION_KIND_SET = new Set(MONETIZATION_KINDS);
21
21
  function isMonetizationKind(value) {
22
22
  return typeof value === "string" && MONETIZATION_KIND_SET.has(value);
23
23
  }
24
+ /**
25
+ * HOW MUCH of a price is collected at the point of sale.
26
+ *
27
+ * Distinct from every neighbouring vocabulary, which is why it is its own:
28
+ *
29
+ * - order's `PAYMENT_TERMS` (`prepaid` | `collect_on_delivery`) says WHEN.
30
+ * - `PaymentAllocationStatus` (`partial`, `allocated`…) says how much of a
31
+ * RECEIVED payment has been applied to what it settles.
32
+ * - revenue's `PARTIALLY_REFUNDED` is a transaction's own state.
33
+ *
34
+ * None of them answers "what do we charge this customer right now", and a host
35
+ * that guesses charges either nothing or everything — opposite answers to the
36
+ * same question.
37
+ *
38
+ * It lives here, beside {@link MONETIZATION_KINDS}, for the reason stated in
39
+ * catalog's barrel: monetization is a CROSS-KERNEL contract. Catalog declares
40
+ * the policy on a product, order turns it into tenders, revenue settles them.
41
+ * Three packages reading one definition, none re-exporting it.
42
+ *
43
+ * - `full_payment` — the whole price, before the sale completes.
44
+ * - `deposit` — a fixed amount now, the balance outstanding on the
45
+ * order and settled later by another tender. One provider
46
+ * line instead of two, which matters when card fees are
47
+ * charged per transaction.
48
+ * - `pay_later` — nothing now. The order still records what is owed.
49
+ *
50
+ * ABSENCE means `full_payment` — the behaviour before this existed — so no
51
+ * stored record changes meaning when a build that knows this field ships.
52
+ */
53
+ const UPFRONT_COLLECTION_MODES = [
54
+ "full_payment",
55
+ "deposit",
56
+ "pay_later"
57
+ ];
58
+ const UPFRONT_COLLECTION_MODE_SET = new Set(UPFRONT_COLLECTION_MODES);
59
+ /**
60
+ * Narrow an unknown value to a collection mode.
61
+ *
62
+ * Readers MUST treat `false` as UNKNOWN, never as `full_payment`: a record
63
+ * written by a build that knows a mode this one does not has not said "charge
64
+ * everything", and assuming so takes money nobody agreed to.
65
+ */
66
+ function isUpfrontCollectionMode(value) {
67
+ return typeof value === "string" && UPFRONT_COLLECTION_MODE_SET.has(value);
68
+ }
24
69
  const isFreeMonetization = (m) => m.type === "free";
25
70
  const isOneTimeMonetization = (m) => m.type === "one_time";
26
71
  const isSubscriptionMonetization = (m) => m.type === "subscription";
@@ -76,4 +121,4 @@ function fromRevenueMonetizationType(type) {
76
121
  return "one_time";
77
122
  }
78
123
  //#endregion
79
- export { MONETIZATION_KINDS, compareAtPriceOf, fromRevenueMonetizationType, isBundleMonetization, isFreeMonetization, isMonetizationKind, isOneTimeMonetization, isSubscriptionMonetization, isUsageMonetization, monetizationKindOf, toRevenueMonetizationType, unitPriceOf };
124
+ export { MONETIZATION_KINDS, UPFRONT_COLLECTION_MODES, compareAtPriceOf, fromRevenueMonetizationType, isBundleMonetization, isFreeMonetization, isMonetizationKind, isOneTimeMonetization, isSubscriptionMonetization, isUpfrontCollectionMode, isUsageMonetization, monetizationKindOf, toRevenueMonetizationType, unitPriceOf };
@@ -0,0 +1,59 @@
1
+ import { t as Brand } from "./brand-Lj_75Y_r.mjs";
2
+ //#region src/inventory/stock-level.d.ts
3
+ /**
4
+ * Stock quantities as NOMINAL types, so "how many are there" and "how many may
5
+ * I still promise" cannot be passed for one another.
6
+ *
7
+ * They are different questions with the same JavaScript type, and every layer
8
+ * re-derived the relationship between them: the flow ledger, the catalog
9
+ * bridge, the quant invariants, and the storefront cache each knew that
10
+ * available is on-hand minus reserved, and one of them still shipped on-hand
11
+ * into a field named `totalAvailable` — so the shop advertised units already
12
+ * held in someone else's cart and checkout refused the sale it had promised.
13
+ *
14
+ * Same fix as money's `MinorUnits<C>` brand, and for the same reason: a unit
15
+ * confusion that a reviewer must notice becomes one the compiler refuses.
16
+ * Zero runtime cost — the brands erase at compile time.
17
+ */
18
+ /** Units physically present. INCLUDES units promised to open carts. */
19
+ type OnHand = Brand<number, "OnHand">;
20
+ /** Units held against an owner (cart, order, move group) — present, not free. */
21
+ type Reserved = Brand<number, "Reserved">;
22
+ /**
23
+ * Units that may still be promised to a NEW buyer: `onHand - reserved`.
24
+ *
25
+ * Constructible only via {@link sellable}, which is what makes passing on-hand
26
+ * where sellable is required a type error rather than an oversell.
27
+ */
28
+ type Sellable = Brand<number, "Sellable">;
29
+ /** What a location holds, as the ledger records it. */
30
+ interface StockLevel {
31
+ readonly onHand: OnHand;
32
+ readonly reserved: Reserved;
33
+ }
34
+ declare const onHand: (n: number) => OnHand;
35
+ declare const reserved: (n: number) => Reserved;
36
+ /**
37
+ * The ONE definition of sellable stock.
38
+ *
39
+ * Deliberately NOT clamped at zero. Writing stock off below what is already
40
+ * reserved (goods damaged after being promised) is a real state, not an error —
41
+ * you cannot un-promise a cart. A negative reads as OVERSOLD, and every
42
+ * consumer already treats `<= 0` as unsellable; clamping would erase the only
43
+ * signal operations has that the condition exists.
44
+ */
45
+ declare const sellable: (level: StockLevel) => Sellable;
46
+ /** `true` when more is promised than is physically present. */
47
+ declare const isOversold: (level: StockLevel) => boolean;
48
+ /**
49
+ * Build a level from a ledger row, whatever it calls its fields.
50
+ *
51
+ * A quant reports all three; `available` is IGNORED on purpose and recomputed,
52
+ * so a source whose stored total has drifted cannot import that drift.
53
+ */
54
+ declare const levelOf: (row: {
55
+ quantityOnHand?: number;
56
+ quantityReserved?: number;
57
+ }) => StockLevel;
58
+ //#endregion
59
+ export { OnHand, Reserved, Sellable, StockLevel, isOversold, levelOf, onHand, reserved, sellable };
@@ -0,0 +1,27 @@
1
+ //#region src/inventory/stock-level.ts
2
+ const onHand = (n) => n;
3
+ const reserved = (n) => n;
4
+ /**
5
+ * The ONE definition of sellable stock.
6
+ *
7
+ * Deliberately NOT clamped at zero. Writing stock off below what is already
8
+ * reserved (goods damaged after being promised) is a real state, not an error —
9
+ * you cannot un-promise a cart. A negative reads as OVERSOLD, and every
10
+ * consumer already treats `<= 0` as unsellable; clamping would erase the only
11
+ * signal operations has that the condition exists.
12
+ */
13
+ const sellable = (level) => level.onHand - level.reserved;
14
+ /** `true` when more is promised than is physically present. */
15
+ const isOversold = (level) => sellable(level) < 0;
16
+ /**
17
+ * Build a level from a ledger row, whatever it calls its fields.
18
+ *
19
+ * A quant reports all three; `available` is IGNORED on purpose and recomputed,
20
+ * so a source whose stored total has drifted cannot import that drift.
21
+ */
22
+ const levelOf = (row) => ({
23
+ onHand: onHand(row.quantityOnHand ?? 0),
24
+ reserved: reserved(row.quantityReserved ?? 0)
25
+ });
26
+ //#endregion
27
+ export { isOversold, levelOf, onHand, reserved, sellable };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/primitives",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Framework-agnostic TypeScript primitives shared across Classytic packages — Money, events, pagination, tenant config, and other building blocks.",
5
5
  "author": "Classytic",
6
6
  "license": "MIT",
@@ -185,6 +185,10 @@
185
185
  "types": "./dist/mixin.d.mts",
186
186
  "default": "./dist/mixin.mjs"
187
187
  },
188
+ "./deadline": {
189
+ "types": "./dist/deadline.d.mts",
190
+ "default": "./dist/deadline.mjs"
191
+ },
188
192
  "./sla-policy": {
189
193
  "types": "./dist/sla-policy.d.mts",
190
194
  "default": "./dist/sla-policy.mjs"
@@ -213,6 +217,14 @@
213
217
  "./item-identity": {
214
218
  "types": "./dist/item-identity.d.mts",
215
219
  "default": "./dist/item-identity.mjs"
220
+ },
221
+ "./stock-level": {
222
+ "types": "./dist/stock-level.d.mts",
223
+ "default": "./dist/stock-level.mjs"
224
+ },
225
+ "./item-facts": {
226
+ "types": "./dist/item-facts.d.mts",
227
+ "default": "./dist/item-facts.mjs"
216
228
  }
217
229
  },
218
230
  "files": [