@classytic/transfer
v0.4.0
Published
Inter-branch stock transfer and stock request domain kernel — FSM lifecycle, FlowBridge port for WMS integration, approval chain support. Framework-agnostic; hosts wire Flow, catalog, and sequence bridges.
Readme
@classytic/transfer
Inter-branch stock transfer and stock request domain kernel for
Classytic commerce, built on @classytic/mongokit + @classytic/repo-core +
@classytic/primitives. Framework-agnostic — hosts wire Arc, Flow (WMS), and a
sequence generator via bridges.
Architecture
- Repositories are the API surface.
StockTransferRepositoryandStockRequestRepositoryextendmongokit.Repositoryand carry the domain verbs (createTransfer,approve,dispatch,markInTransit,receive,cancel,forceCancel;createRequest,approve,reject,fulfill,cancel). Arc plugs in viacreateAdapter(). - FSM via CAS. Every state transition uses
repo.claim()— a lost race returns a typed 409 (TransferTransitionError), never a silent overwrite. - Two-sided quantity integrity.
receive()clamps over-receipt to the remaining quantity, CASes the items array with a per-line exact-match guard so concurrent partial receives can't clobber each other, and compensates the inbound bridge atomically on failure. - Money is paisa integers. No floats;
computeTransferTotals/addPaisaguard against drift.
Multi-tenancy
organizationId is the company/tenant scope — distinct from senderBranch
/ receiverBranch, which are physical locations a transfer moves stock between.
multiTenantPlugin scopes every read and write at POLICY priority. The field
type drives both the schema and the plugin from one resolved config.
const transfer = createTransferEngine({
connection: mongoose.connection,
// multiTenant: false, // single-tenant deployments
// tenantFieldType: 'string', // UUID/slug auth systems
bridges: { sequence, flow },
});
await transfer.syncIndexes(); // deploy-time (autoIndex: false in prod)
const t = await transfer.repositories.stockTransfer.createTransfer(input, ctx);ctx must carry organizationId (defaults are fail-closed: required: true).
Bridges (all optional)
sequence—nextDocumentNumber()forTRF-/REQ-numbers. Falls back to a non-unique dev placeholder when absent.flow— WMS hand-off.checkAvailability(approve),createOutboundMoveGroup(dispatch),createInboundMoveGroup(receive). CAS happens BEFORE the bridge call so only the single winner fires a move group; failures compensate back. Implement per PACKAGE_RULES rule 7 (String + bridge) — never import Flow directly.
Events
12 events under the transfer: prefix. Pass any Arc EventTransport
(eventTransport) and/or a durable OutboxStore (outbox) at construction —
every verb emits through the P8 dispatch (outbox save propagates, transport
publish swallows). The Zod catalog ships as transferEventDefinitions from the
root and /events for Arc's EventRegistry.
Boot requirements
The engine asserts the transactions capability at construction — MongoDB must
run as a replica set (receive/dispatch compensation is transactional).
Subpaths
/engine · /domain · /events · /schemas · /testing
Testing
Real MongoDB via mongodb-memory-server (replica set). Unit tier covers paisa
math + the event-catalog no-drift invariant; integration tier covers the FSM,
CAS races, receive integrity, tenant isolation (+ tenant probe), and P8 dispatch.
Arc smoke tests live in the host app, not here.
