@classytic/flow 0.1.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 (102) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/README.md +258 -0
  4. package/dist/allocation-policy-my_HfzdV.d.mts +23 -0
  5. package/dist/base-MWBqRFM2.mjs +16 -0
  6. package/dist/catalog-bridge-K8bdkncJ.d.mts +29 -0
  7. package/dist/cost-layer.port-iH9pvZqB.d.mts +30 -0
  8. package/dist/cost-layer.service-BQ1bs-XN.mjs +86 -0
  9. package/dist/cost-layer.service-DWmo9dQz.d.mts +53 -0
  10. package/dist/count.port-BRqwGbi3.d.mts +57 -0
  11. package/dist/counting/index.d.mts +2 -0
  12. package/dist/counting/index.mjs +2 -0
  13. package/dist/counting.service-BiQXqorv.mjs +232 -0
  14. package/dist/counting.service-CpAxU2G0.d.mts +74 -0
  15. package/dist/domain/contracts/index.d.mts +3 -0
  16. package/dist/domain/contracts/index.mjs +1 -0
  17. package/dist/domain/enums/index.d.mts +2 -0
  18. package/dist/domain/enums/index.mjs +4 -0
  19. package/dist/domain/index.d.mts +24 -0
  20. package/dist/domain/index.mjs +10 -0
  21. package/dist/domain/policies/index.d.mts +4 -0
  22. package/dist/domain/policies/index.mjs +1 -0
  23. package/dist/domain-D5cpMpR0.mjs +96 -0
  24. package/dist/domain-errors-D7S9ydNF.mjs +133 -0
  25. package/dist/enums-C3_z6aHC.mjs +82 -0
  26. package/dist/event-bus-BNmyoJb4.mjs +37 -0
  27. package/dist/event-bus-Um_xrcMY.d.mts +21 -0
  28. package/dist/event-emitter.port-BFh2pasY.d.mts +183 -0
  29. package/dist/event-types-BSqQOvXv.mjs +29 -0
  30. package/dist/events/index.d.mts +3 -0
  31. package/dist/events/index.mjs +3 -0
  32. package/dist/idempotency.port-CTC70JON.d.mts +55 -0
  33. package/dist/index-Bia4m8d2.d.mts +67 -0
  34. package/dist/index-BmNm3oNU2.d.mts +107 -0
  35. package/dist/index-C5PciI9P.d.mts +203 -0
  36. package/dist/index-CMTUKEK_.d.mts +308 -0
  37. package/dist/index-C_aEnozN.d.mts +220 -0
  38. package/dist/index-CulWO137.d.mts +107 -0
  39. package/dist/index-DFF0GJ4J.d.mts +36 -0
  40. package/dist/index-DsE7lZdO.d.mts +11 -0
  41. package/dist/index-DwO9IdNa.d.mts +1 -0
  42. package/dist/index-dtWUZr2a2.d.mts +350 -0
  43. package/dist/index.d.mts +128 -0
  44. package/dist/index.mjs +102 -0
  45. package/dist/insufficient-stock.error-Dyr4BYaV.mjs +15 -0
  46. package/dist/location.port-CValXIpb.d.mts +52 -0
  47. package/dist/lot.port-ChsmvZqs.d.mts +32 -0
  48. package/dist/models/index.d.mts +2 -0
  49. package/dist/models/index.mjs +2 -0
  50. package/dist/models-CHTMbp-G.mjs +1020 -0
  51. package/dist/move-group.port-DHGoQA3d.d.mts +56 -0
  52. package/dist/move-status-DkaFp2GD.mjs +38 -0
  53. package/dist/move.port-Qg1CYp7h.d.mts +89 -0
  54. package/dist/package.service-4tcAwBbr.mjs +95 -0
  55. package/dist/package.service-C605NaBQ.d.mts +42 -0
  56. package/dist/packaging/index.d.mts +2 -0
  57. package/dist/packaging/index.mjs +2 -0
  58. package/dist/procurement/index.d.mts +2 -0
  59. package/dist/procurement/index.mjs +2 -0
  60. package/dist/quant.port-BBa66PBT.d.mts +42 -0
  61. package/dist/removal-policy-BItBB8FD.d.mts +29 -0
  62. package/dist/replenishment-rule.port-DnEYtbyD.d.mts +78 -0
  63. package/dist/replenishment.service-BT9P-HKM.mjs +284 -0
  64. package/dist/replenishment.service-HO0sDhB_.d.mts +89 -0
  65. package/dist/reporting/index.d.mts +2 -0
  66. package/dist/reporting/index.mjs +2 -0
  67. package/dist/reporting-CL5ffrKM.mjs +243 -0
  68. package/dist/repositories/index.d.mts +2 -0
  69. package/dist/repositories/index.mjs +2 -0
  70. package/dist/repositories-nZXJKvLW.mjs +842 -0
  71. package/dist/reservation-status-ZfuTaWG0.mjs +22 -0
  72. package/dist/reservation.port-l9NFQ0si.d.mts +85 -0
  73. package/dist/reservations/index.d.mts +2 -0
  74. package/dist/reservations/index.mjs +2 -0
  75. package/dist/reservations-Cg4wN0QB.mjs +112 -0
  76. package/dist/routing/index.d.mts +362 -0
  77. package/dist/routing/index.mjs +582 -0
  78. package/dist/runtime-config-C0ggPkiK.mjs +40 -0
  79. package/dist/runtime-config-CQLtPPqY.d.mts +38 -0
  80. package/dist/scan-token-CNM9QVLY.d.mts +26 -0
  81. package/dist/scanning/index.d.mts +45 -0
  82. package/dist/scanning/index.mjs +228 -0
  83. package/dist/services/index.d.mts +8 -0
  84. package/dist/services/index.mjs +8 -0
  85. package/dist/services-_lLO4Xbl.mjs +1009 -0
  86. package/dist/stock-move-group-C0DqUfPY.mjs +88 -0
  87. package/dist/stock-package-BIarxbDS.d.mts +19 -0
  88. package/dist/stock-quant-CZhgvTu7.d.mts +41 -0
  89. package/dist/tenant-guard-6Ne-BILP.mjs +12 -0
  90. package/dist/tenant-isolation.error-D3OcKUdx.mjs +11 -0
  91. package/dist/trace.service-B9vAh-l-.d.mts +55 -0
  92. package/dist/trace.service-DE6Eh8_8.mjs +71 -0
  93. package/dist/traceability/index.d.mts +2 -0
  94. package/dist/traceability/index.mjs +2 -0
  95. package/dist/types/index.d.mts +2 -0
  96. package/dist/types/index.mjs +1 -0
  97. package/dist/unit-of-work.port-CWEkrDKu.d.mts +17 -0
  98. package/dist/valuation/index.d.mts +78 -0
  99. package/dist/valuation/index.mjs +103 -0
  100. package/dist/valuation-policy-Dco8c9Vw.d.mts +14 -0
  101. package/dist/virtual-locations-B9zXqPdi.d.mts +38 -0
  102. package/package.json +155 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,70 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@classytic/flow` are documented here.
4
+
5
+ ## [0.1.0] - 2026-03-29
6
+
7
+ Initial public release.
8
+
9
+ ### Features
10
+
11
+ - Double-entry inventory engine with location hierarchy
12
+ - Stock quants (materialized read model)
13
+ - Reservation system with expiry and partial consumption
14
+ - FIFO/FEFO/WAC/specific/standard valuation + landed cost allocation
15
+ - Lot and serial traceability with recall support
16
+ - Putaway and removal routing strategies (ABC-velocity, category-zone, closest-to-pick, empty-bin, fixed-location)
17
+ - Route expansion engine (1/2/3-step reception and delivery)
18
+ - Wave picking engine with serpentine (boustrophedon) path optimization
19
+ - 3D warehouse coordinates: zone/aisle/bay/level/bin
20
+ - Odd aisles ascending, even aisles descending — eliminates dead-walking
21
+ - Location consolidation (merge picks for same SKU at same bin)
22
+ - Weight/volume/item cart batching with oversized pick splitting
23
+ - Cross-dock engine with multi-destination routing
24
+ - FIFO assignment to waiting outbound orders
25
+ - Per-destination quantity tracking with remaining-to-storage calculation
26
+ - Procurement order lifecycle (draft → approved → received)
27
+ - Cycle counting with auto-reconciliation and freeze policies
28
+ - Replenishment rules (reorder point, min/max, scheduled)
29
+ - GS1-128 barcode parser with dynamic year rollover
30
+ - Scan resolution chain (GS1 → location → lot → serial → SKU)
31
+ - Multi-tenant support via MongoKit plugins
32
+ - In-process event bus with error isolation
33
+ - Stock packaging with parent/child nesting
34
+ - Config-driven deployment modes (`simple` / `standard` / `enterprise`)
35
+ - `allocation.defaultPolicy` wired to built-in FifoStrategy/FefoStrategy/LifoStrategy
36
+ - `mode`, `routing`, `valuation` accessible at runtime via `flow.services.mode`
37
+
38
+ ### Safety & Data Integrity
39
+
40
+ - Atomic quant upsert via MongoDB aggregation pipeline — `quantityAvailable = quantityOnHand - quantityReserved` computed in a single operation, no race window
41
+ - Negative stock prevention — `postMove()` checks `location.allowNegativeStock` before decrementing; throws `NegativeStockError` if insufficient
42
+ - Canonical posting path — `receiveGroup()` delegates to `PostingService.postMove()` per move, enforcing negative stock guard, move transition validation, and `inventory.move.done` event emission for every move
43
+ - Reservation lifecycle correctness:
44
+ - `expire()` releases `quantityReserved` from the quant (matches `cleanupExpired()`)
45
+ - `consume()` validates `quantity > 0`, caps at remaining, throws on overconsumption
46
+ - `consume()` decrements `quantityReserved` on the quant
47
+ - Group fulfillment (`receiveGroup`) consumes + releases all move reservations
48
+ - `releaseReservation` uses correct `consumed` vs `partially_consumed` status and computes `remaining` from pre-update state (no stale reads)
49
+ - Partially done move reconciliation — `receiveGroup()` includes `partially_done` in committable statuses and completes the remaining quantity
50
+ - Input validation — `quantityDone > 0`, `quantity > 0`, non-empty `items[]` enforced at service entry with `ValidationError`
51
+ - Tenant-scoped idempotency keys — all services (reservation, move, procurement) prefix keys with `organizationId:` to prevent cross-org collisions
52
+ - WAC precision — weighted average cost rounded to 4 decimal places; freezes at last known cost when stock goes negative
53
+ - Penny-leak prevention — landed cost post-capitalization uses remainder absorption per item with cross-item correction on the last item
54
+ - Concurrent allocation safety — parallel availability checks + `batchUpsert` for quant reservation locks
55
+ - Event handler error isolation — logged but never propagate to emitter
56
+ - FIFO/FEFO zero-quantity guard — valuation engines return empty result for `quantity <= 0`
57
+ - Move group state machine — explicit status validation in `receiveGroup()` (rejects done/cancelled/draft)
58
+ - Reservation cleanup batching — `cleanupExpired()` processes all expirations in a single transaction
59
+ - Quant `deleteAll()` uses consistent ObjectId filter (matches reads) — `rebuildFromMoveHistory()` correctly clears stale quants
60
+
61
+ ### Dependencies
62
+
63
+ - `mongoose` (peer, >=9.0.0) — required
64
+ - `@classytic/mongokit` (peer, >=3.0.0) — required, provides repository pattern + plugins
65
+
66
+ ### Test Coverage
67
+
68
+ - 60 test files, 560 test cases
69
+ - Coverage layers: domain unit, engine unit (mocked), service integration (MongoDB), E2E workflow, concurrency stress, data integrity, algorithm correctness
70
+ - Pure-interface files excluded from coverage denominator
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Classytic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # @classytic/flow
2
+
3
+ Production-grade inventory kernel and supply chain engine for MongoDB.
4
+
5
+ Double-entry inventory, location-based stock tracking, reservations, FIFO/FEFO/WAC valuation, lot/serial traceability, putaway/removal routing, wave picking with serpentine path optimization, cross-dock multi-destination routing, procurement, cycle counting, and replenishment — all as pure domain services with zero framework coupling.
6
+
7
+ ## Quick Start
8
+
9
+ ```typescript
10
+ import { createFlowEngine } from '@classytic/flow';
11
+ import mongoose from 'mongoose';
12
+
13
+ // 1. Create the engine
14
+ const flow = createFlowEngine({
15
+ mongoose: mongoose.connection,
16
+ mode: 'standard',
17
+ catalog: {
18
+ resolveSku: async (skuRef) => productService.findBySku(skuRef),
19
+ },
20
+ });
21
+
22
+ // 2. Access models
23
+ const { InventoryNode, Location, StockMoveGroup, StockMove, StockQuant, Reservation } = flow.models;
24
+
25
+ // 3. Context carries tenant + actor — never mixed into domain inputs
26
+ const ctx = { organizationId: 'org_1', actorId: 'user_1' };
27
+
28
+ // 4. Use services (pure async functions — no HTTP, no framework)
29
+ const availability = await flow.services.quant.getAvailability({
30
+ skuRef: 'SKU-RED-M',
31
+ nodeId: 'warehouse_1',
32
+ }, ctx);
33
+
34
+ // 5. Create and execute a transfer
35
+ const group = await flow.services.moveGroup.create({
36
+ groupType: 'transfer',
37
+ sourceNodeId: 'warehouse_1',
38
+ destinationNodeId: 'store_1',
39
+ items: [{
40
+ moveGroupId: '', // auto-assigned
41
+ operationType: 'transfer',
42
+ skuRef: 'SKU-RED-M',
43
+ sourceLocationId: 'loc_storage_wh1',
44
+ destinationLocationId: 'loc_storage_store1',
45
+ quantityPlanned: 50,
46
+ }],
47
+ }, ctx);
48
+
49
+ await flow.services.moveGroup.executeAction(group._id, 'confirm', {}, ctx);
50
+ await flow.services.moveGroup.executeAction(group._id, 'dispatch', {}, ctx);
51
+ await flow.services.moveGroup.executeAction(group._id, 'receive', {}, ctx);
52
+
53
+ // 6. Subscribe to events (bridge to your ledger, notifications, etc.)
54
+ flow.events.on('inventory.move.done', async (payload) => {
55
+ // payload: { organizationId, moveId, skuRef, quantityDone, ... }
56
+ await ledger.postInventoryMovement(payload);
57
+ });
58
+
59
+ // 7. Query active config at runtime
60
+ flow.services.mode; // 'standard'
61
+ flow.services.routing; // { putaway: false, removal: false, crossDock: false }
62
+ flow.services.valuation; // { method: 'wac' }
63
+ ```
64
+
65
+ ### Simple Mode (single store, basic stock)
66
+
67
+ Flow scales down to simple apps. You don't need locations, routing, or lots — just stock queries and moves:
68
+
69
+ ```typescript
70
+ const ctx = { organizationId: 'org_1', actorId: 'user_1' };
71
+
72
+ // Check availability
73
+ const stock = await flow.services.quant.getAvailability({ skuRef: 'SKU-RED-M' }, ctx);
74
+ // → { quantityOnHand: 50, quantityReserved: 5, quantityAvailable: 45, quantityIncoming: 20, quantityOutgoing: 0 }
75
+
76
+ // Reserve stock for an order
77
+ await flow.services.reservation.reserve({
78
+ reservationType: 'hard',
79
+ skuRef: 'SKU-RED-M',
80
+ locationId: 'loc_default',
81
+ quantity: 3,
82
+ ownerType: 'order',
83
+ ownerId: 'order_123',
84
+ }, ctx);
85
+ ```
86
+
87
+ ## Features
88
+
89
+ | Feature | Description |
90
+ |---------|-------------|
91
+ | **Double-entry inventory** | Every stock change is a move between locations — never direct mutation |
92
+ | **Location hierarchy** | Warehouse > zone > aisle > bin. Virtual locations for vendor, customer, scrap, transit |
93
+ | **Stock quants** | Materialized read model of on-hand, reserved, available, incoming, outgoing |
94
+ | **Reservations** | First-class records with expiry, partial consumption, overconsumption guard, and pluggable allocation |
95
+ | **Valuation** | WAC (default), FIFO, FEFO, specific identification, standard cost, landed cost with penny-leak prevention |
96
+ | **Lot/serial tracking** | Full traceability with expiry, recall support, and FEFO allocation |
97
+ | **Routing engine** | Putaway strategies, removal strategies, route expansion |
98
+ | **Wave picking** | Serpentine (boustrophedon) path optimization with 3D coordinates, weight/volume/item cart batching with oversized pick splitting |
99
+ | **Cross-dock routing** | Multi-destination assignments — incoming stock auto-routed to waiting outbound orders in FIFO order |
100
+ | **Procurement** | Purchase order lifecycle, receiving, vendor management |
101
+ | **Cycle counting** | Full/cycle/blind counts with auto-reconciliation |
102
+ | **Replenishment** | Min/max, reorder point, safety stock, EOQ, multi-echelon |
103
+ | **Scan resolution + GS1-128** | Device-agnostic barcode/QR/RFID → entity resolution. Built-in GS1-128 parser for GTIN, lot, expiry, serial. |
104
+ | **Stock states** | Sellable, damaged, quarantine, hold, returns, expired, in-transit |
105
+ | **UoM conversion** | Each/case/carton/pallet with per-SKU conversion factors |
106
+ | **Packaging** | StockPackage with parent/child nesting (carton → case → pallet), weight calculation |
107
+ | **Stock ownership** | `ownerRef` on quants — owned vs consignment/third-party in same location |
108
+ | **QC workflow** | Quality hold location + inspection route step |
109
+ | **Multi-tenant** | Organization-scoped via MongoKit multi-tenant plugin |
110
+ | **Events** | In-process emitter — bridge to Redis, webhooks, ledger, notifications |
111
+ | **Capability tiers** | `simple` / `standard` / `enterprise` — config-driven feature gating |
112
+
113
+ ## Safety & Data Integrity
114
+
115
+ Flow enforces invariants at the engine level so consumers don't have to:
116
+
117
+ | Guard | Behavior |
118
+ |-------|----------|
119
+ | **Atomic quant updates** | `quantityAvailable = quantityOnHand - quantityReserved` computed in a single MongoDB aggregation pipeline — no race window |
120
+ | **Negative stock prevention** | `postMove()` checks `location.allowNegativeStock` before decrementing; throws `NegativeStockError` if insufficient. Virtual sources (returns, receipts) auto-bypass. |
121
+ | **Canonical posting path** | `receiveGroup()` delegates to `PostingService.postMove()` per move — negative stock guard, move transition validation, and `inventory.move.done` events fire for every move |
122
+ | **Reservation lifecycle** | `expire()` and `consume()` both release `quantityReserved` from the quant. `consume()` validates `quantity > 0` and caps at remaining — no overconsumption. Fulfillment releases all reservations. |
123
+ | **Input validation** | `quantityDone > 0`, `quantity > 0`, non-empty `items[]` enforced at service entry |
124
+ | **Tenant-scoped idempotency** | All idempotency keys prefixed with `organizationId:` (reservation, move, procurement) — no cross-tenant collision |
125
+ | **WAC precision** | Weighted average cost rounded to 4 decimal places; freezes at last known cost when stock goes negative |
126
+ | **Penny-leak prevention** | Landed cost post-capitalization uses remainder absorption — COGS + remaining = exactly the allocated cost per item, with cross-item correction on the last item |
127
+ | **Concurrent allocation safety** | Parallel availability check + `batchUpsert` for quant reservation locks within a single transaction |
128
+ | **Event error isolation** | Handler errors logged but never propagate to emitter |
129
+ | **GS1 year rollover** | Dynamic sliding window (±50 years from current year) instead of hardcoded cutoff |
130
+ | **FIFO/FEFO zero-qty guard** | Valuation engines return empty result for `quantity <= 0` |
131
+ | **Partially done moves** | `receiveGroup()` reconciles `partially_done` moves — completes remaining quantity instead of skipping them |
132
+
133
+ ## Subpath Exports
134
+
135
+ | Import | Contents |
136
+ |--------|----------|
137
+ | `@classytic/flow` | `createFlowEngine()` factory + full API |
138
+ | `@classytic/flow/domain` | Entities, value objects, errors |
139
+ | `@classytic/flow/domain/contracts` | `CatalogBridge`, `AuthContext` interfaces |
140
+ | `@classytic/flow/domain/enums` | `LocationType`, `MoveStatus`, `OperationType`, etc. |
141
+ | `@classytic/flow/domain/policies` | `AllocationPolicy`, `RemovalPolicy`, `ValuationPolicy` |
142
+ | `@classytic/flow/models` | Mongoose schemas |
143
+ | `@classytic/flow/repositories` | Repository factories (MongoKit) |
144
+ | `@classytic/flow/services` | All domain services |
145
+ | `@classytic/flow/valuation` | Cost layers, WAC/FIFO/FEFO engine, landed cost |
146
+ | `@classytic/flow/routing` | Putaway, removal, wave engine, cross-dock engine |
147
+ | `@classytic/flow/reservations` | Allocation strategies and reservation service |
148
+ | `@classytic/flow/procurement` | Procurement order lifecycle |
149
+ | `@classytic/flow/counting` | Inventory count and reconciliation |
150
+ | `@classytic/flow/traceability` | Lot/serial trace queries |
151
+ | `@classytic/flow/scanning` | Scan resolution + GS1-128 parser |
152
+ | `@classytic/flow/packaging` | StockPackage service |
153
+ | `@classytic/flow/reporting` | Read models, metrics, stock aging |
154
+ | `@classytic/flow/events` | Event definitions and emitter |
155
+ | `@classytic/flow/types` | All TypeScript types |
156
+
157
+ ## Configuration
158
+
159
+ ```typescript
160
+ const flow = createFlowEngine({
161
+ // Required
162
+ mongoose: connection,
163
+
164
+ // Required — how Flow resolves product details
165
+ catalog: {
166
+ resolveSku: async (skuRef) => ({ sku: skuRef, displayName: '...', trackingMode: 'none', uom: 'unit' }),
167
+ },
168
+
169
+ // Optional — deployment mode (default: 'standard')
170
+ mode: 'standard',
171
+
172
+ // Optional — multi-tenant (omit for single-tenant)
173
+ multiTenant: { orgField: 'organizationId' },
174
+
175
+ // Optional — valuation method (default: 'wac')
176
+ valuation: { method: 'wac' },
177
+
178
+ // Optional — allocation policy (default: 'fifo') — resolves to built-in FifoStrategy/FefoStrategy/LifoStrategy
179
+ allocation: { defaultPolicy: 'fifo' },
180
+
181
+ // Optional — routing (enterprise mode)
182
+ routing: { putaway: true, removal: true, crossDock: false },
183
+
184
+ // Optional — custom allocation policy (overrides allocation.defaultPolicy)
185
+ policies: { allocation: myCustomPolicy },
186
+
187
+ // Optional — custom event emitter (default: in-process)
188
+ events: { adapter: myRedisEventBus },
189
+
190
+ // Optional — idempotency store
191
+ idempotency: myRedisIdempotency,
192
+
193
+ // Optional — MongoKit plugins per repository
194
+ plugins: {
195
+ quant: [cachePlugin({ adapter: redis, ttl: 30 })],
196
+ move: [auditTrailPlugin()],
197
+ },
198
+ });
199
+ ```
200
+
201
+ | Option | Type | Default | Description |
202
+ |--------|------|---------|-------------|
203
+ | `mongoose` | `Connection` | required | Mongoose connection |
204
+ | `catalog` | `CatalogBridge` | required | Product/SKU resolution |
205
+ | `mode` | `FlowMode` | `'standard'` | `simple`, `standard`, or `enterprise` |
206
+ | `multiTenant` | `object` | `undefined` | `{ orgField }` for org-scoped isolation |
207
+ | `valuation.method` | `string` | `'wac'` | `wac`, `fifo`, `fefo`, `specific`, `standard` |
208
+ | `allocation.defaultPolicy` | `string` | `'fifo'` | `fifo`, `fefo`, `lifo` — maps to built-in strategy |
209
+ | `routing.putaway` | `boolean` | `false` | Enable putaway strategy engine |
210
+ | `routing.removal` | `boolean` | `false` | Enable removal strategy engine |
211
+ | `routing.crossDock` | `boolean` | `false` | Enable cross-dock multi-destination routing |
212
+
213
+ ## What Flow Returns
214
+
215
+ ```typescript
216
+ const flow = createFlowEngine(config);
217
+
218
+ flow.models // All Mongoose models
219
+ flow.repositories // All MongoKit repositories
220
+ flow.services // All domain services + active config (mode, routing, valuation)
221
+ flow.events // In-process event emitter
222
+ flow.registerPolicy // Register custom allocation policies at runtime
223
+ ```
224
+
225
+ ## What Flow Does NOT Include
226
+
227
+ Flow is a pure domain kernel. These are consumer responsibilities:
228
+
229
+ - **HTTP routes** — use Arc, Express, Fastify, NestJS, Next.js
230
+ - **Durable workflows** — use Streamline, BullMQ, cron
231
+ - **Agent tooling** — wrap `flow.services` in your agent tools or MCP servers
232
+ - **Ledger integration** — subscribe to `flow.events`
233
+ - **Authentication** — pass `AuthContext` to service methods
234
+
235
+ ## Dependencies
236
+
237
+ | Dependency | Type | Required |
238
+ |------------|------|----------|
239
+ | `mongoose` | peer | Yes (>=9.0.0) |
240
+ | `@classytic/mongokit` | peer | Yes (>=3.0.0) — provides repository pattern, pagination, plugins |
241
+
242
+ ## Reference Docs
243
+
244
+ | Doc | Description |
245
+ |-----|-------------|
246
+ | [Architecture](docs/architecture.md) | Domain model, clean architecture layers, invariants, capability tiers |
247
+ | [Algorithms](docs/algorithms.md) | Valuation, allocation, routing, replenishment formulas |
248
+ | [Workflows](docs/workflows.md) | Inbound, outbound, transfer, counting, return flows |
249
+ | [Plugins & Adapters](docs/plugins-and-adapters.md) | Scan, events, labels, catalog, policies, workflow adapters |
250
+ | [Integration](docs/integration.md) | MongoKit, Arc, Streamline, framework examples |
251
+ | [API Reference](docs/api-reference.md) | Service methods, entity fields, events, errors |
252
+ | [Test Strategy](docs/test-strategy.md) | Test layers, coverage targets, fixtures |
253
+ | [Migration Plan](docs/migration-plan.md) | Step-by-step guide to integrate Flow into an Arc backend |
254
+ | [Changelog](CHANGELOG.md) | Version history and bug fixes |
255
+
256
+ ## License
257
+
258
+ MIT
@@ -0,0 +1,23 @@
1
+ import { n as StockQuant } from "./stock-quant-CZhgvTu7.mjs";
2
+
3
+ //#region src/domain/policies/allocation-policy.d.ts
4
+ interface AllocationResult {
5
+ fulfilled: boolean;
6
+ allocations: Array<{
7
+ quantId: string;
8
+ locationId: string;
9
+ lotId?: string;
10
+ quantity: number;
11
+ }>;
12
+ shortfall: number;
13
+ }
14
+ /**
15
+ * Allocation policy — decides which quants fulfill a demand.
16
+ * Consumer can provide custom implementations.
17
+ */
18
+ interface AllocationPolicy {
19
+ name: string;
20
+ resolve(skuRef: string, quantity: number, candidates: StockQuant[]): AllocationResult;
21
+ }
22
+ //#endregion
23
+ export { AllocationResult as n, AllocationPolicy as t };
@@ -0,0 +1,16 @@
1
+ //#region src/domain/errors/base.ts
2
+ var FlowError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = this.constructor.name;
6
+ }
7
+ toJSON() {
8
+ return {
9
+ code: this.code,
10
+ message: this.message,
11
+ name: this.name
12
+ };
13
+ }
14
+ };
15
+ //#endregion
16
+ export { FlowError as t };
@@ -0,0 +1,29 @@
1
+ //#region src/domain/contracts/catalog-bridge.d.ts
2
+ /**
3
+ * CatalogBridge — required contract.
4
+ * Consumer implements this to tell Flow how to resolve SKU references.
5
+ * Flow does not own the product catalog.
6
+ */
7
+ interface CatalogBridge {
8
+ resolveSku(skuRef: string): Promise<SkuDetails | null>;
9
+ syncAvailability?(updates: AvailabilityUpdate[]): Promise<void>;
10
+ }
11
+ interface SkuDetails {
12
+ skuRef: string;
13
+ sku: string;
14
+ displayName: string;
15
+ trackingMode: 'none' | 'lot' | 'serial';
16
+ uom: string;
17
+ weight?: number;
18
+ volume?: number;
19
+ barcode?: string[];
20
+ isActive: boolean;
21
+ }
22
+ interface AvailabilityUpdate {
23
+ skuRef: string;
24
+ nodeId: string;
25
+ available: number;
26
+ inStock: boolean;
27
+ }
28
+ //#endregion
29
+ export { CatalogBridge as n, SkuDetails as r, AvailabilityUpdate as t };
@@ -0,0 +1,30 @@
1
+ import { a as QueryOptions, t as FlowContext } from "./index-DFF0GJ4J.mjs";
2
+ import { t as TransactionSession } from "./unit-of-work.port-CWEkrDKu.mjs";
3
+
4
+ //#region src/domain/entities/cost-layer.d.ts
5
+ interface CostLayer {
6
+ _id: string;
7
+ organizationId: string;
8
+ skuRef: string;
9
+ locationId: string;
10
+ lotId?: string;
11
+ remainingQty: number;
12
+ unitCost: number;
13
+ receivedAt: Date;
14
+ expiresAt?: Date;
15
+ procurementRef?: string;
16
+ moveRef: string;
17
+ createdAt?: Date;
18
+ updatedAt?: Date;
19
+ }
20
+ //#endregion
21
+ //#region src/domain/ports/cost-layer.port.d.ts
22
+ interface CostLayerPort {
23
+ create(input: Omit<CostLayer, '_id' | 'createdAt' | 'updatedAt'>, session?: TransactionSession): Promise<CostLayer>;
24
+ drain(id: string, quantity: number, ctx: FlowContext, session?: TransactionSession): Promise<CostLayer>;
25
+ findBySkuRef(skuRef: string, locationId: string, ctx: FlowContext, session?: TransactionSession): Promise<CostLayer[]>;
26
+ findMany(filter: Record<string, unknown>, ctx: FlowContext, session?: TransactionSession, options?: QueryOptions): Promise<CostLayer[]>;
27
+ findOrderedForConsumption(skuRef: string, locationId: string, method: 'fifo' | 'fefo', ctx: FlowContext, session?: TransactionSession): Promise<CostLayer[]>;
28
+ }
29
+ //#endregion
30
+ export { CostLayer as n, CostLayerPort as t };
@@ -0,0 +1,86 @@
1
+ import { t as InsufficientStockError } from "./insufficient-stock.error-Dyr4BYaV.mjs";
2
+ import { t as assertTenantContext } from "./tenant-guard-6Ne-BILP.mjs";
3
+ //#region src/valuation/fifo.engine.ts
4
+ var FifoEngine = class {
5
+ consume(layers, quantity) {
6
+ if (quantity <= 0) return {
7
+ consumed: [],
8
+ totalCost: 0,
9
+ totalQuantity: 0,
10
+ remainingUncosted: 0,
11
+ insufficientLayers: false
12
+ };
13
+ const consumed = [];
14
+ let remaining = quantity;
15
+ let totalCost = 0;
16
+ for (const layer of layers) {
17
+ if (remaining <= 0) break;
18
+ if (layer.remainingQty <= 0) continue;
19
+ const take = Math.min(layer.remainingQty, remaining);
20
+ const cost = take * layer.unitCost;
21
+ consumed.push({
22
+ layerId: layer._id,
23
+ quantity: take,
24
+ unitCost: layer.unitCost,
25
+ totalCost: cost
26
+ });
27
+ totalCost += cost;
28
+ remaining -= take;
29
+ }
30
+ return {
31
+ consumed,
32
+ totalCost,
33
+ totalQuantity: quantity - remaining,
34
+ remainingUncosted: remaining,
35
+ insufficientLayers: remaining > 0
36
+ };
37
+ }
38
+ };
39
+ //#endregion
40
+ //#region src/valuation/fefo.engine.ts
41
+ var FefoEngine = class {
42
+ fifoEngine = new FifoEngine();
43
+ consume(layers, quantity) {
44
+ return this.fifoEngine.consume(layers, quantity);
45
+ }
46
+ };
47
+ //#endregion
48
+ //#region src/valuation/cost-layer.service.ts
49
+ var CostLayerService = class {
50
+ fifoEngine = new FifoEngine();
51
+ fefoEngine = new FefoEngine();
52
+ constructor(costLayerPort, _unitOfWork) {
53
+ this.costLayerPort = costLayerPort;
54
+ }
55
+ async createLayer(input, session) {
56
+ return this.costLayerPort.create(input, session);
57
+ }
58
+ async consumeLayers(skuRef, locationId, quantity, method, ctx, session) {
59
+ assertTenantContext(ctx);
60
+ const layers = await this.costLayerPort.findOrderedForConsumption(skuRef, locationId, method, ctx, session);
61
+ const result = (method === "fefo" ? this.fefoEngine : this.fifoEngine).consume(layers, quantity);
62
+ if (result.insufficientLayers) throw new InsufficientStockError(skuRef, quantity, quantity - result.remainingUncosted, locationId);
63
+ for (const entry of result.consumed) await this.costLayerPort.drain(entry.layerId, entry.quantity, ctx, session);
64
+ return result;
65
+ }
66
+ async getValuation(skuRef, locationId, ctx) {
67
+ assertTenantContext(ctx);
68
+ const layers = await this.costLayerPort.findBySkuRef(skuRef, locationId, ctx);
69
+ let totalQuantity = 0;
70
+ let totalValue = 0;
71
+ for (const layer of layers) if (layer.remainingQty > 0) {
72
+ totalQuantity += layer.remainingQty;
73
+ totalValue += layer.remainingQty * layer.unitCost;
74
+ }
75
+ return {
76
+ skuRef,
77
+ locationId,
78
+ totalQuantity,
79
+ totalValue: Math.round(totalValue * 100) / 100,
80
+ averageUnitCost: totalQuantity > 0 ? Math.round(totalValue / totalQuantity * 100) / 100 : 0,
81
+ layerCount: layers.filter((l) => l.remainingQty > 0).length
82
+ };
83
+ }
84
+ };
85
+ //#endregion
86
+ export { FefoEngine as n, FifoEngine as r, CostLayerService as t };
@@ -0,0 +1,53 @@
1
+ import { t as FlowContext } from "./index-DFF0GJ4J.mjs";
2
+ import { n as CostLayer, t as CostLayerPort } from "./cost-layer.port-iH9pvZqB.mjs";
3
+ import { n as UnitOfWork, t as TransactionSession } from "./unit-of-work.port-CWEkrDKu.mjs";
4
+
5
+ //#region src/valuation/fifo.engine.d.ts
6
+ interface ConsumptionResult {
7
+ consumed: Array<{
8
+ layerId: string;
9
+ quantity: number;
10
+ unitCost: number;
11
+ totalCost: number;
12
+ }>;
13
+ totalCost: number;
14
+ totalQuantity: number;
15
+ remainingUncosted: number;
16
+ insufficientLayers: boolean;
17
+ }
18
+ declare class FifoEngine {
19
+ consume(layers: CostLayer[], quantity: number): ConsumptionResult;
20
+ }
21
+ //#endregion
22
+ //#region src/valuation/cost-layer.service.d.ts
23
+ interface CreateCostLayerInput {
24
+ organizationId: string;
25
+ skuRef: string;
26
+ locationId: string;
27
+ lotId?: string;
28
+ remainingQty: number;
29
+ unitCost: number;
30
+ receivedAt: Date;
31
+ expiresAt?: Date;
32
+ procurementRef?: string;
33
+ moveRef: string;
34
+ }
35
+ interface InventoryValuation {
36
+ skuRef: string;
37
+ locationId: string;
38
+ totalQuantity: number;
39
+ totalValue: number;
40
+ averageUnitCost: number;
41
+ layerCount: number;
42
+ }
43
+ declare class CostLayerService {
44
+ private costLayerPort;
45
+ private fifoEngine;
46
+ private fefoEngine;
47
+ constructor(costLayerPort: CostLayerPort, _unitOfWork: UnitOfWork);
48
+ createLayer(input: CreateCostLayerInput, session?: TransactionSession): Promise<CostLayer>;
49
+ consumeLayers(skuRef: string, locationId: string, quantity: number, method: 'fifo' | 'fefo', ctx: FlowContext, session?: TransactionSession): Promise<ConsumptionResult>;
50
+ getValuation(skuRef: string, locationId: string, ctx: FlowContext): Promise<InventoryValuation>;
51
+ }
52
+ //#endregion
53
+ export { FifoEngine as a, ConsumptionResult as i, CreateCostLayerInput as n, InventoryValuation as r, CostLayerService as t };
@@ -0,0 +1,57 @@
1
+ import { t as FlowContext } from "./index-DFF0GJ4J.mjs";
2
+ import { t as TransactionSession } from "./unit-of-work.port-CWEkrDKu.mjs";
3
+
4
+ //#region src/domain/entities/count-line.d.ts
5
+ interface CountLine {
6
+ _id: string;
7
+ organizationId: string;
8
+ countId: string;
9
+ skuRef: string;
10
+ locationId: string;
11
+ lotId?: string;
12
+ serialCode?: string;
13
+ expectedQuantity: number;
14
+ countedQuantity: number;
15
+ variance: number;
16
+ varianceReason?: string;
17
+ recountFlag?: boolean;
18
+ createdAt?: Date;
19
+ updatedAt?: Date;
20
+ }
21
+ //#endregion
22
+ //#region src/domain/entities/inventory-count.d.ts
23
+ type CountStatus = 'draft' | 'in_progress' | 'submitted' | 'reconciled' | 'approved';
24
+ interface CountScope {
25
+ nodeId?: string;
26
+ locationId?: string;
27
+ skuRefs?: string[];
28
+ }
29
+ interface InventoryCount {
30
+ _id: string;
31
+ organizationId: string;
32
+ countNumber: string;
33
+ countType: 'full' | 'cycle' | 'spot';
34
+ scope: CountScope;
35
+ status: CountStatus;
36
+ freezePolicy: 'hard_freeze' | 'soft_freeze' | 'none';
37
+ startedAt?: Date;
38
+ submittedAt?: Date;
39
+ reconciledAt?: Date;
40
+ createdBy?: string;
41
+ modifiedBy?: string;
42
+ approvedBy?: string;
43
+ metadata?: Record<string, unknown>;
44
+ createdAt?: Date;
45
+ updatedAt?: Date;
46
+ }
47
+ //#endregion
48
+ //#region src/domain/ports/count.port.d.ts
49
+ interface CountPort {
50
+ create(input: Omit<InventoryCount, '_id' | 'createdAt' | 'updatedAt'>, session?: TransactionSession): Promise<InventoryCount>;
51
+ findById(id: string, ctx: FlowContext, session?: TransactionSession): Promise<InventoryCount | null>;
52
+ updateStatus(id: string, status: CountStatus, updates: Partial<InventoryCount>, session?: TransactionSession): Promise<InventoryCount>;
53
+ submitLines(countId: string, lines: Omit<CountLine, '_id' | 'createdAt' | 'updatedAt'>[], session?: TransactionSession): Promise<CountLine[]>;
54
+ getLines(countId: string, ctx: FlowContext, session?: TransactionSession): Promise<CountLine[]>;
55
+ }
56
+ //#endregion
57
+ export { CountLine as a, InventoryCount as i, CountScope as n, CountStatus as r, CountPort as t };
@@ -0,0 +1,2 @@
1
+ import { a as ReconcileResult, i as ReconcileOptions, n as CountingService, o as VarianceReport, r as CreateCountInput, t as CountLineInput } from "../counting.service-CpAxU2G0.mjs";
2
+ export { type CountLineInput, CountingService, type CreateCountInput, type ReconcileOptions, type ReconcileResult, type VarianceReport };
@@ -0,0 +1,2 @@
1
+ import { t as CountingService } from "../counting.service-BiQXqorv.mjs";
2
+ export { CountingService };