@openlup/platform 0.21.0 → 0.22.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 +210 -47
- package/README.md +27 -4
- package/dist/composition/composeBundle.d.ts +7 -0
- package/dist/composition/composeBundle.js +2 -0
- package/dist/composition/fulfillmentContribution.d.ts +21 -0
- package/dist/composition/fulfillmentContribution.js +19 -0
- package/dist/composition/index.d.ts +1 -0
- package/dist/composition/index.js +1 -0
- package/dist/fulfillment/adapterConformance.d.ts +3 -0
- package/dist/fulfillment/adapterConformance.js +223 -0
- package/dist/fulfillment/bindings.d.ts +3 -0
- package/dist/fulfillment/bindings.js +29 -0
- package/dist/fulfillment/commerceFulfillmentHandlers.d.ts +29 -0
- package/dist/fulfillment/commerceFulfillmentHandlers.js +184 -0
- package/dist/fulfillment/commerceFulfillmentReadModel.d.ts +86 -0
- package/dist/fulfillment/commerceFulfillmentReadModel.js +131 -0
- package/dist/fulfillment/contracts/alignment.d.ts +25 -0
- package/dist/fulfillment/contracts/alignment.js +1 -0
- package/dist/fulfillment/contracts/bindings.d.ts +51 -0
- package/dist/fulfillment/contracts/bindings.js +1 -0
- package/dist/fulfillment/contracts/capabilities.d.ts +29 -0
- package/dist/fulfillment/contracts/capabilities.js +1 -0
- package/dist/fulfillment/contracts/conformance.d.ts +93 -0
- package/dist/fulfillment/contracts/conformance.js +1 -0
- package/dist/fulfillment/contracts/customerAlignment.d.ts +6 -0
- package/dist/fulfillment/contracts/customerAlignment.js +1 -0
- package/dist/fulfillment/contracts/dispatch.d.ts +105 -0
- package/dist/fulfillment/contracts/dispatch.js +1 -0
- package/dist/fulfillment/contracts/index.d.ts +18 -0
- package/dist/fulfillment/contracts/index.js +1 -0
- package/dist/fulfillment/contracts/label.d.ts +35 -0
- package/dist/fulfillment/contracts/label.js +1 -0
- package/dist/fulfillment/contracts/nativeUnsupported.d.ts +6 -0
- package/dist/fulfillment/contracts/nativeUnsupported.js +1 -0
- package/dist/fulfillment/contracts/observation.d.ts +52 -0
- package/dist/fulfillment/contracts/observation.js +1 -0
- package/dist/fulfillment/contracts/operator.d.ts +64 -0
- package/dist/fulfillment/contracts/operator.js +1 -0
- package/dist/fulfillment/contracts/read.d.ts +110 -0
- package/dist/fulfillment/contracts/read.js +1 -0
- package/dist/fulfillment/contracts/readback.d.ts +38 -0
- package/dist/fulfillment/contracts/readback.js +1 -0
- package/dist/fulfillment/contracts/replacement.d.ts +34 -0
- package/dist/fulfillment/contracts/replacement.js +1 -0
- package/dist/fulfillment/contracts/store.d.ts +86 -0
- package/dist/fulfillment/contracts/store.js +1 -0
- package/dist/fulfillment/contracts/sync.d.ts +170 -0
- package/dist/fulfillment/contracts/sync.js +1 -0
- package/dist/fulfillment/currentFulfillmentParcel.d.ts +2 -0
- package/dist/fulfillment/currentFulfillmentParcel.js +1 -0
- package/dist/fulfillment/delayReadModel.d.ts +11 -0
- package/dist/fulfillment/delayReadModel.js +45 -0
- package/dist/fulfillment/deliveryAlignmentReadModel.d.ts +4 -0
- package/dist/fulfillment/deliveryAlignmentReadModel.js +12 -0
- package/dist/fulfillment/dispatchProjection.d.ts +7 -0
- package/dist/fulfillment/dispatchProjection.js +18 -0
- package/dist/fulfillment/fulfillmentObservationEffects.d.ts +66 -0
- package/dist/fulfillment/fulfillmentObservationEffects.js +36 -0
- package/dist/fulfillment/handlerSchemas.d.ts +647 -0
- package/dist/fulfillment/handlerSchemas.js +213 -0
- package/dist/fulfillment/handlerSupport.d.ts +33 -0
- package/dist/fulfillment/handlerSupport.js +25 -0
- package/dist/fulfillment/index.d.ts +37 -0
- package/dist/fulfillment/index.js +37 -0
- package/dist/fulfillment/labelLifecycle.d.ts +24 -0
- package/dist/fulfillment/labelLifecycle.js +9 -0
- package/dist/fulfillment/lowStockEvidenceHandler.d.ts +36 -0
- package/dist/fulfillment/lowStockEvidenceHandler.js +46 -0
- package/dist/fulfillment/migrations.d.ts +27 -0
- package/dist/fulfillment/migrations.js +8 -0
- package/dist/fulfillment/nativeUnsupported.d.ts +10 -0
- package/dist/fulfillment/nativeUnsupported.js +18 -0
- package/dist/fulfillment/normalizedFulfillmentFacts.d.ts +2 -0
- package/dist/fulfillment/normalizedFulfillmentFacts.js +1 -0
- package/dist/fulfillment/observationProjection.d.ts +11 -0
- package/dist/fulfillment/observationProjection.js +37 -0
- package/dist/fulfillment/omnipack/capabilities.d.ts +3 -0
- package/dist/fulfillment/omnipack/capabilities.js +16 -0
- package/dist/fulfillment/omnipack/client.d.ts +37 -0
- package/dist/fulfillment/omnipack/client.js +162 -0
- package/dist/fulfillment/omnipack/index.d.ts +52 -0
- package/dist/fulfillment/omnipack/index.js +349 -0
- package/dist/fulfillment/omnipack/mappers.d.ts +73 -0
- package/dist/fulfillment/omnipack/mappers.js +177 -0
- package/dist/fulfillment/omnipack/observation.d.ts +21 -0
- package/dist/fulfillment/omnipack/observation.js +57 -0
- package/dist/fulfillment/omnipack/omnipackDispatchPayload.d.ts +36 -0
- package/dist/fulfillment/omnipack/omnipackDispatchPayload.js +63 -0
- package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.d.ts +83 -0
- package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.js +113 -0
- package/dist/fulfillment/omnipack/omnipackProductPackPayload.d.ts +11 -0
- package/dist/fulfillment/omnipack/omnipackProductPackPayload.js +38 -0
- package/dist/fulfillment/omnipack/omnipackProductPayload.d.ts +18 -0
- package/dist/fulfillment/omnipack/omnipackProductPayload.js +51 -0
- package/dist/fulfillment/omnipack/omnipackReconciliationPayload.d.ts +1 -0
- package/dist/fulfillment/omnipack/omnipackReconciliationPayload.js +3 -0
- package/dist/fulfillment/omnipack/omnipackStatusVocabulary.d.ts +4 -0
- package/dist/fulfillment/omnipack/omnipackStatusVocabulary.js +87 -0
- package/dist/fulfillment/omnipack/omnipackWebhookPayload.d.ts +9 -0
- package/dist/fulfillment/omnipack/omnipackWebhookPayload.js +76 -0
- package/dist/fulfillment/omnipack/productSyncMappers.d.ts +18 -0
- package/dist/fulfillment/omnipack/productSyncMappers.js +43 -0
- package/dist/fulfillment/omnipack/providerError.d.ts +25 -0
- package/dist/fulfillment/omnipack/providerError.js +47 -0
- package/dist/fulfillment/omnipack/trackingReferences.d.ts +16 -0
- package/dist/fulfillment/omnipack/trackingReferences.js +52 -0
- package/dist/fulfillment/paidFulfillmentRecovery.d.ts +67 -0
- package/dist/fulfillment/paidFulfillmentRecovery.js +154 -0
- package/dist/fulfillment/paidHandoff.d.ts +27 -0
- package/dist/fulfillment/paidHandoff.js +86 -0
- package/dist/fulfillment/paidHandoffPorts.d.ts +24 -0
- package/dist/fulfillment/paidHandoffPorts.js +3 -0
- package/dist/fulfillment/postgresDeliveryAlignment.d.ts +4 -0
- package/dist/fulfillment/postgresDeliveryAlignment.js +12 -0
- package/dist/fulfillment/postgresDispatchStore.d.ts +16 -0
- package/dist/fulfillment/postgresDispatchStore.js +44 -0
- package/dist/fulfillment/postgresEvidenceReadPort.d.ts +25 -0
- package/dist/fulfillment/postgresEvidenceReadPort.js +45 -0
- package/dist/fulfillment/postgresPaidFulfillmentRecovery.d.ts +25 -0
- package/dist/fulfillment/postgresPaidFulfillmentRecovery.js +26 -0
- package/dist/fulfillment/postgresPaidHandoffStore.d.ts +4 -0
- package/dist/fulfillment/postgresPaidHandoffStore.js +10 -0
- package/dist/fulfillment/postgresShipmentSpine.d.ts +63 -0
- package/dist/fulfillment/postgresShipmentSpine.js +232 -0
- package/dist/fulfillment/postgresStockSyncPort.d.ts +8 -0
- package/dist/fulfillment/postgresStockSyncPort.js +63 -0
- package/dist/fulfillment/productReconciliationEvidenceHandler.d.ts +36 -0
- package/dist/fulfillment/productReconciliationEvidenceHandler.js +50 -0
- package/dist/fulfillment/providerCapabilities.d.ts +9 -0
- package/dist/fulfillment/providerCapabilities.js +17 -0
- package/dist/fulfillment/providerCommandLedger.d.ts +143 -0
- package/dist/fulfillment/providerCommandLedger.js +14 -0
- package/dist/fulfillment/providerDispatchAcceptance.d.ts +26 -0
- package/dist/fulfillment/providerDispatchAcceptance.js +23 -0
- package/dist/fulfillment/providerDispatchError.d.ts +6 -0
- package/dist/fulfillment/providerDispatchError.js +5 -0
- package/dist/fulfillment/providerDispatchWorker.d.ts +31 -0
- package/dist/fulfillment/providerDispatchWorker.js +172 -0
- package/dist/fulfillment/providerProductSyncWorker.d.ts +28 -0
- package/dist/fulfillment/providerProductSyncWorker.js +122 -0
- package/dist/fulfillment/providerReconciliationError.d.ts +9 -0
- package/dist/fulfillment/providerReconciliationError.js +17 -0
- package/dist/fulfillment/providerReconciliationTerminalSkip.d.ts +2 -0
- package/dist/fulfillment/providerReconciliationTerminalSkip.js +6 -0
- package/dist/fulfillment/providerReconciliationTimestamp.d.ts +1 -0
- package/dist/fulfillment/providerReconciliationTimestamp.js +27 -0
- package/dist/fulfillment/providerReconciliationWorker.d.ts +82 -0
- package/dist/fulfillment/providerReconciliationWorker.js +151 -0
- package/dist/fulfillment/providerStockAvailability.d.ts +13 -0
- package/dist/fulfillment/providerStockAvailability.js +51 -0
- package/dist/fulfillment/providerStockSyncWorker.d.ts +37 -0
- package/dist/fulfillment/providerStockSyncWorker.js +186 -0
- package/dist/fulfillment/providerTrackingReferences.d.ts +15 -0
- package/dist/fulfillment/providerTrackingReferences.js +23 -0
- package/dist/fulfillment/replacementRefusals.d.ts +5 -0
- package/dist/fulfillment/replacementRefusals.js +38 -0
- package/dist/fulfillment/replacementShipmentHandler.d.ts +5 -0
- package/dist/fulfillment/replacementShipmentHandler.js +46 -0
- package/dist/fulfillment/schemaCapabilities.d.ts +13 -0
- package/dist/fulfillment/schemaCapabilities.js +7 -0
- package/dist/fulfillment/shipmentsOverviewHandler.d.ts +16 -0
- package/dist/fulfillment/shipmentsOverviewHandler.js +54 -0
- package/dist/fulfillment/statusMap.d.ts +2 -0
- package/dist/fulfillment/statusMap.js +1 -0
- package/dist/fulfillment/trackingProgress.d.ts +1 -0
- package/dist/fulfillment/trackingProgress.js +1 -0
- package/dist/fulfillment/verifiedIngress.d.ts +52 -0
- package/dist/fulfillment/verifiedIngress.js +66 -0
- package/dist/payments/stripe/index.d.ts +25 -0
- package/dist/payments/stripe/index.js +25 -0
- package/dist/payments/tpay/index.d.ts +30 -0
- package/dist/payments/tpay/index.js +30 -0
- package/package.json +17 -2
- package/release-gates.json +44 -3
- package/sql/20260808210000_fulfillment_shipment_rail.sql +810 -0
- package/sql/20260819150000_fulfillment_shipment_sequence.sql +98 -0
- package/sql/20261022080000_fulfillment_command_ledger.sql +786 -0
- package/sql/20261022090000_fulfillment_delivery_alignment.sql +1106 -0
- package/src/composition/composeBundle.ts +4 -0
- package/src/composition/fulfillmentContribution.ts +28 -0
- package/src/composition/index.ts +1 -0
- package/src/fulfillment/adapterConformance.ts +195 -0
- package/src/fulfillment/bindings.ts +26 -0
- package/src/fulfillment/commerceFulfillmentHandlers.ts +263 -0
- package/src/fulfillment/commerceFulfillmentReadModel.ts +257 -0
- package/src/fulfillment/contracts/alignment.ts +13 -0
- package/src/fulfillment/contracts/bindings.ts +30 -0
- package/src/fulfillment/contracts/capabilities.ts +15 -0
- package/src/fulfillment/contracts/conformance.ts +61 -0
- package/src/fulfillment/contracts/customerAlignment.ts +4 -0
- package/src/fulfillment/contracts/dispatch.ts +58 -0
- package/src/fulfillment/contracts/index.ts +18 -0
- package/src/fulfillment/contracts/label.ts +9 -0
- package/src/fulfillment/contracts/nativeUnsupported.ts +6 -0
- package/src/fulfillment/contracts/observation.ts +18 -0
- package/src/fulfillment/contracts/operator.ts +31 -0
- package/src/fulfillment/contracts/read.ts +42 -0
- package/src/fulfillment/contracts/readback.ts +11 -0
- package/src/fulfillment/contracts/replacement.ts +15 -0
- package/src/fulfillment/contracts/store.ts +26 -0
- package/src/fulfillment/contracts/sync.ts +61 -0
- package/src/fulfillment/currentFulfillmentParcel.ts +2 -0
- package/src/fulfillment/delayReadModel.ts +60 -0
- package/src/fulfillment/deliveryAlignmentReadModel.ts +15 -0
- package/src/fulfillment/dispatchProjection.ts +20 -0
- package/src/fulfillment/fulfillmentObservationEffects.ts +86 -0
- package/src/fulfillment/handlerSchemas.ts +255 -0
- package/src/fulfillment/handlerSupport.ts +25 -0
- package/src/fulfillment/index.ts +38 -0
- package/src/fulfillment/labelLifecycle.ts +11 -0
- package/src/fulfillment/lowStockEvidenceHandler.ts +60 -0
- package/src/fulfillment/migrations.ts +8 -0
- package/src/fulfillment/nativeUnsupported.ts +23 -0
- package/src/fulfillment/normalizedFulfillmentFacts.ts +2 -0
- package/src/fulfillment/observationProjection.ts +41 -0
- package/src/fulfillment/omnipack/capabilities.ts +18 -0
- package/src/fulfillment/omnipack/client.ts +258 -0
- package/src/fulfillment/omnipack/index.ts +312 -0
- package/src/fulfillment/omnipack/mappers.ts +261 -0
- package/src/fulfillment/omnipack/observation.ts +61 -0
- package/src/fulfillment/omnipack/omnipackDispatchPayload.ts +93 -0
- package/src/fulfillment/omnipack/omnipackOutboundOrderPayload.ts +194 -0
- package/src/fulfillment/omnipack/omnipackProductPackPayload.ts +47 -0
- package/src/fulfillment/omnipack/omnipackProductPayload.ts +67 -0
- package/src/fulfillment/omnipack/omnipackReconciliationPayload.ts +7 -0
- package/src/fulfillment/omnipack/omnipackStatusVocabulary.ts +103 -0
- package/src/fulfillment/omnipack/omnipackWebhookPayload.ts +73 -0
- package/src/fulfillment/omnipack/productSyncMappers.ts +74 -0
- package/src/fulfillment/omnipack/providerError.ts +64 -0
- package/src/fulfillment/omnipack/trackingReferences.ts +77 -0
- package/src/fulfillment/paidFulfillmentRecovery.ts +252 -0
- package/src/fulfillment/paidHandoff.ts +122 -0
- package/src/fulfillment/paidHandoffPorts.ts +21 -0
- package/src/fulfillment/postgresDeliveryAlignment.ts +14 -0
- package/src/fulfillment/postgresDispatchStore.ts +55 -0
- package/src/fulfillment/postgresEvidenceReadPort.ts +59 -0
- package/src/fulfillment/postgresPaidFulfillmentRecovery.ts +29 -0
- package/src/fulfillment/postgresPaidHandoffStore.ts +11 -0
- package/src/fulfillment/postgresShipmentSpine.ts +321 -0
- package/src/fulfillment/postgresStockSyncPort.ts +65 -0
- package/src/fulfillment/productReconciliationEvidenceHandler.ts +64 -0
- package/src/fulfillment/providerCapabilities.ts +18 -0
- package/src/fulfillment/providerCommandLedger.ts +53 -0
- package/src/fulfillment/providerDispatchAcceptance.ts +31 -0
- package/src/fulfillment/providerDispatchError.ts +7 -0
- package/src/fulfillment/providerDispatchWorker.ts +131 -0
- package/src/fulfillment/providerProductSyncWorker.ts +154 -0
- package/src/fulfillment/providerReconciliationError.ts +20 -0
- package/src/fulfillment/providerReconciliationTerminalSkip.ts +8 -0
- package/src/fulfillment/providerReconciliationTimestamp.ts +34 -0
- package/src/fulfillment/providerReconciliationWorker.ts +150 -0
- package/src/fulfillment/providerStockAvailability.ts +60 -0
- package/src/fulfillment/providerStockSyncWorker.ts +222 -0
- package/src/fulfillment/providerTrackingReferences.ts +31 -0
- package/src/fulfillment/replacementRefusals.ts +43 -0
- package/src/fulfillment/replacementShipmentHandler.ts +47 -0
- package/src/fulfillment/schemaCapabilities.ts +7 -0
- package/src/fulfillment/shipmentsOverviewHandler.ts +73 -0
- package/src/fulfillment/statusMap.ts +2 -0
- package/src/fulfillment/trackingProgress.ts +1 -0
- package/src/fulfillment/verifiedIngress.ts +80 -0
- package/src/payments/stripe/index.ts +30 -0
- package/src/payments/tpay/index.ts +36 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { dispatchEvidence, dispatchResult } from "./dispatchProjection.js";
|
|
2
|
+
import { projectObservation } from "./observationProjection.js";
|
|
3
|
+
import { readFulfillmentCapabilities } from "./providerCapabilities.js";
|
|
4
|
+
import { runFulfillmentDispatchWorker } from "./providerDispatchWorker.js";
|
|
5
|
+
/** Runner-independent offline proof using only adapter-owned fixtures. Throws on a violated contract. */
|
|
6
|
+
export async function runFulfillmentAdapterConformance(subjects) {
|
|
7
|
+
const evidence = [];
|
|
8
|
+
const cases = [];
|
|
9
|
+
for (const subject of subjects) {
|
|
10
|
+
const { adapter, fixtures } = subject;
|
|
11
|
+
const caps = adapter.capabilities;
|
|
12
|
+
const passed = (operation) => { cases.push({ subjectId: subject.subjectId, operation, passed: true }); };
|
|
13
|
+
requireFact(caps.defaultOff === true, "default-off");
|
|
14
|
+
requireFact(readFulfillmentCapabilities([caps], null).kind === "refused", "selection_required");
|
|
15
|
+
const first = fixtures.dispatch[0];
|
|
16
|
+
requireFact(Boolean(first), "dispatch fixture required");
|
|
17
|
+
const disabled = readFulfillmentCapabilities([{ ...caps, selected: false }], first.input.selection);
|
|
18
|
+
requireFact(disabled.kind === "refused" && disabled.refusal.reason === "disabled", "disabled selection");
|
|
19
|
+
const missing = await adapter.dispatch.prepare({ ...first.input, selection: { ...first.input.selection, providerRef: null } });
|
|
20
|
+
requireFact(missing.kind === "refused" && missing.refusal.reason === "selection_required", "adapter selection_required");
|
|
21
|
+
passed("default-off and selection_required");
|
|
22
|
+
for (const fixture of fixtures.dispatch) {
|
|
23
|
+
const before = subject.readIoCount();
|
|
24
|
+
const prepared = await adapter.dispatch.prepare(fixture.input);
|
|
25
|
+
requireSame(prepared, fixture.expectedPrepare, "prepare fixture");
|
|
26
|
+
requireFact(subject.readIoCount() === before, "prepare before I/O");
|
|
27
|
+
assertDurable(prepared, subject);
|
|
28
|
+
if (prepared.kind === "refused") {
|
|
29
|
+
passed("dispatch refused before I/O");
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
requireFact(caps.operations.dispatch.supported, "unsupported dispatch refuses");
|
|
33
|
+
requireSame(prepared.evidence, dispatchEvidence(prepared.evidence), "prepared evidence allowlist");
|
|
34
|
+
requireFact(prepared.evidence.providerRef === fixture.input.selection.providerRef, "prepared provider identity");
|
|
35
|
+
requireSame(prepared.evidence.contactRevision, fixture.input.contactRevision, "prepared contact identity");
|
|
36
|
+
const mismatch = await adapter.dispatch.dispatch(fixture.input, { ...prepared.evidence, commandFingerprint: `${prepared.evidence.commandFingerprint}:mismatch` });
|
|
37
|
+
requireSame(mismatch, { kind: "not_dispatched", effectMayHaveSucceeded: false,
|
|
38
|
+
refusal: { reason: "not_dispatchable", phase: "dispatch", retryable: false } }, "mismatched preparation refusal");
|
|
39
|
+
requireFact(subject.readIoCount() === before, "mismatch before I/O");
|
|
40
|
+
requireFact(fixture.expectedDispatch !== undefined, "dispatch expected result");
|
|
41
|
+
const result = await adapter.dispatch.dispatch(fixture.input, prepared.evidence);
|
|
42
|
+
requireSame(result, fixture.expectedDispatch, "dispatch fixture");
|
|
43
|
+
requireSame(result, dispatchResult(result), "dispatch allowlist");
|
|
44
|
+
assertDurable(result, subject);
|
|
45
|
+
if (result.kind === "accepted") {
|
|
46
|
+
requireSame(result.evidence, prepared.evidence, "dispatch fingerprint identity");
|
|
47
|
+
requireFact(result.externalRef.length > 0 && result.providerAttemptKey.length > 0 && result.labelKey.length > 0, "accepted proof before local ACK");
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
requireFact(result.kind === "unknown" ? result.effectMayHaveSucceeded : !result.effectMayHaveSucceeded, "unknown versus refused");
|
|
51
|
+
if (fixture.replay) {
|
|
52
|
+
const calls = subject.readIoCount();
|
|
53
|
+
requireSame(await adapter.dispatch.dispatch(fixture.input, prepared.evidence), result, "dispatch replay");
|
|
54
|
+
requireFact(subject.readIoCount() === calls, "replay no second I/O");
|
|
55
|
+
}
|
|
56
|
+
passed(`dispatch ${result.kind}, fingerprint, mismatch and replay`);
|
|
57
|
+
}
|
|
58
|
+
for (const operation of ["create", "read", "void"]) {
|
|
59
|
+
const supported = caps.operations[operation === "create" ? "labelCreate" : operation === "read" ? "labelRead" : "labelVoid"].supported;
|
|
60
|
+
const rows = fixtures.label?.[operation] ?? [];
|
|
61
|
+
if (supported)
|
|
62
|
+
requireFact(Boolean(adapter.label) && rows.length > 0, `label ${operation} fixture`);
|
|
63
|
+
if (rows.length)
|
|
64
|
+
requireFact(Boolean(adapter.label), "label port required");
|
|
65
|
+
for (const fixture of rows) {
|
|
66
|
+
const before = subject.readIoCount();
|
|
67
|
+
const result = operation === "create" ? await adapter.label.create(fixture.input[0])
|
|
68
|
+
: operation === "read" ? await adapter.label.read(fixture.input[0])
|
|
69
|
+
: await adapter.label.void(fixture.input[0]);
|
|
70
|
+
requireSame(result, fixture.expected, `label ${operation}`);
|
|
71
|
+
if (!supported && !(operation === "read" && caps.labelReadOwner === "host_storage")) {
|
|
72
|
+
requireFact("refusal" in result && result.refusal?.reason === "unsupported", `unsupported label ${operation}`);
|
|
73
|
+
requireFact(subject.readIoCount() === before, "unsupported label before I/O");
|
|
74
|
+
}
|
|
75
|
+
if (!(operation === "read" && "kind" in result && result.kind === "available"))
|
|
76
|
+
assertDurable(result, subject);
|
|
77
|
+
passed(`label ${operation}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (caps.operations.verifyObservation.supported)
|
|
81
|
+
requireFact(Boolean(adapter.observation) && Boolean(fixtures.observation?.length), "observation fixtures required");
|
|
82
|
+
const events = new Map();
|
|
83
|
+
for (const fixture of fixtures.observation ?? []) {
|
|
84
|
+
requireFact(Boolean(adapter.observation), "observation port required");
|
|
85
|
+
const result = await adapter.observation.verifyAndNormalize(...fixture.input);
|
|
86
|
+
requireSame(result, fixture.expected, "observation fixture");
|
|
87
|
+
assertDurable(result, subject);
|
|
88
|
+
if (!caps.operations.verifyObservation.supported)
|
|
89
|
+
requireFact(result.kind === "refused" && result.refusal.reason === "unsupported", "unsupported observation");
|
|
90
|
+
if (result.kind === "verified") {
|
|
91
|
+
requireSame(result.observation, projectObservation(result.observation), "observation allowlist");
|
|
92
|
+
const observation = result.observation;
|
|
93
|
+
const identity = stable({ provider: observation.fact.providerKind, reference: observation.fact.fulfillmentReference,
|
|
94
|
+
kind: observation.eventKind, outcome: observation.fact.canonicalStatus, occurredAt: observation.fact.occurredAt });
|
|
95
|
+
const previous = events.get(observation.eventKey);
|
|
96
|
+
if (previous)
|
|
97
|
+
requireFact(previous.identity === identity, "eventKey includes outcome and occurrence");
|
|
98
|
+
events.set(observation.eventKey, { identity, occurredAt: observation.fact.occurredAt });
|
|
99
|
+
const replay = await adapter.observation.verifyAndNormalize(...fixture.input);
|
|
100
|
+
requireSame(replay, result, "observation exact retry");
|
|
101
|
+
}
|
|
102
|
+
passed("observation identity, replay and redaction");
|
|
103
|
+
}
|
|
104
|
+
if (caps.operations.readback.supported)
|
|
105
|
+
requireFact(Boolean(adapter.readback) && Boolean(fixtures.readback?.length), "readback fixtures required");
|
|
106
|
+
for (const fixture of fixtures.readback ?? []) {
|
|
107
|
+
requireFact(Boolean(adapter.readback), "readback port required");
|
|
108
|
+
const result = await adapter.readback.read(...fixture.input);
|
|
109
|
+
requireSame(result, fixture.expected, "readback fixture");
|
|
110
|
+
assertDurable(result, subject);
|
|
111
|
+
if (!caps.operations.readback.supported)
|
|
112
|
+
requireFact(result.kind === "unsupported", "unsupported readback");
|
|
113
|
+
if (fixture.localMissing)
|
|
114
|
+
requireFact(result.kind === "unknown" && result.refusal.reason === "local_missing", "local missing is never absence");
|
|
115
|
+
if (result.kind === "absent") {
|
|
116
|
+
requireFact(caps.operations.exactAbsence.supported, "unsupported exact absence refuses");
|
|
117
|
+
const { mode: _mode, ...scope } = fixture.input[0];
|
|
118
|
+
requireSame(result.scope, scope, "absence scope");
|
|
119
|
+
requireFact(result.proof.kind === "authoritative_exact_lookup" && /^evidence:.+/.test(result.proof.evidenceRef) && Number.isFinite(Date.parse(result.proof.checkedAt)), "absence proof");
|
|
120
|
+
}
|
|
121
|
+
if (result.kind === "found")
|
|
122
|
+
requireFact(result.externalRef === fixture.input[0].externalRef || fixture.input[0].externalRef === null, "found identity");
|
|
123
|
+
passed(`readback ${result.kind}`);
|
|
124
|
+
}
|
|
125
|
+
for (const name of ["stockSync", "productSync"]) {
|
|
126
|
+
const supported = caps.operations[name === "stockSync" ? "syncStock" : "syncProducts"].supported;
|
|
127
|
+
const port = adapter[name];
|
|
128
|
+
const rows = fixtures[name] ?? [];
|
|
129
|
+
if (supported)
|
|
130
|
+
requireFact(Boolean(port) && rows.length > 0, `${name} fixtures required`);
|
|
131
|
+
for (const fixture of rows) {
|
|
132
|
+
requireFact(Boolean(port), `${name} port required`);
|
|
133
|
+
let returned;
|
|
134
|
+
try {
|
|
135
|
+
// Dispatch a typed fixture tuple to its adapter-owned operation.
|
|
136
|
+
const method = port[fixture.operation];
|
|
137
|
+
returned = await method.apply(port, fixture.input);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
const refusal = error;
|
|
141
|
+
requireFact(refusal.reason === "unsupported" && refusal.phase === "sync" && refusal.retryable === false, "typed unsupported sync refusal");
|
|
142
|
+
returned = { kind: "refused", refusal };
|
|
143
|
+
}
|
|
144
|
+
requireSame(returned, fixture.expected, `${name} ${fixture.operation}`);
|
|
145
|
+
assertDurable(returned, subject);
|
|
146
|
+
if (!supported)
|
|
147
|
+
requireFact(typeof returned === "object" && returned !== null && "kind" in returned && returned.kind === "refused", "unsupported sync");
|
|
148
|
+
passed(`${name} ${fixture.operation}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (subject.store) {
|
|
152
|
+
await checkStore(subject);
|
|
153
|
+
passed("store G14 replay, own-key remap and one-remap bound");
|
|
154
|
+
}
|
|
155
|
+
assertDurable({ subjectId: subject.subjectId, providerRef: caps.providerRef }, subject);
|
|
156
|
+
evidence.push({ subjectId: subject.subjectId, providerRef: caps.providerRef });
|
|
157
|
+
}
|
|
158
|
+
return { subjects: evidence, cases };
|
|
159
|
+
}
|
|
160
|
+
async function checkStore(subject) {
|
|
161
|
+
const fixture = subject.store;
|
|
162
|
+
const store = await fixture.create();
|
|
163
|
+
const first = await store.claim(fixture.claim);
|
|
164
|
+
requireFact(first.kind === "claimed", "store initial claim");
|
|
165
|
+
const replay = await store.claim(fixture.claim);
|
|
166
|
+
requireFact(replay.kind === "claimed" && replay.record.replayed, "store same-key claim replay");
|
|
167
|
+
const attempt = () => store.recordProviderAttempt(fixture.mappedAttemptKey, fixture.claim.fulfillmentRef, fixture.claim.evidence, fixture.claim.mode, "recorded");
|
|
168
|
+
requireFact(!(await attempt()).replayed && (await attempt()).replayed, "G14 same key replays");
|
|
169
|
+
requireFact(fixture.mappedAttemptKey !== fixture.remappedAttemptKey, "G14 remap own key");
|
|
170
|
+
requireFact(fixture.claim.requestKey === fixture.remappedClaim.requestKey && fixture.claim.evidence.commandFingerprint !== fixture.remappedClaim.evidence.commandFingerprint, "draft remap fixture");
|
|
171
|
+
const remap = await store.claim(fixture.remappedClaim);
|
|
172
|
+
requireFact(remap.kind === "claimed", "draft re-claim");
|
|
173
|
+
const row = await store.read(fixture.claim.requestKey);
|
|
174
|
+
requireSame(row?.evidence, fixture.remappedClaim.evidence, "atomic remap fingerprint and evidence");
|
|
175
|
+
assertDurable(row, subject);
|
|
176
|
+
requireFact(!(await store.recordProviderAttempt(fixture.remappedAttemptKey, fixture.claim.fulfillmentRef, fixture.remappedClaim.evidence, fixture.claim.mode, "recorded")).replayed, "G14 new mapped attempt");
|
|
177
|
+
const attempts = await store.readMappedAttempts();
|
|
178
|
+
requireFact(attempts.length === 2, "G14 no duplicate durable attempts");
|
|
179
|
+
requireSame(attempts.map(row => row.attemptKey).sort(), [fixture.mappedAttemptKey, fixture.remappedAttemptKey].sort(), "G14 durable remap identity");
|
|
180
|
+
for (const attempt of attempts) {
|
|
181
|
+
requireSame(attempt.preparedEvidence, dispatchEvidence(attempt.preparedEvidence), "G14 sanitized evidence only");
|
|
182
|
+
assertDurable(attempt, subject);
|
|
183
|
+
}
|
|
184
|
+
const stale = await store.begin({ dispatchRef: row.dispatchRef, commandFingerprint: fixture.claim.evidence.commandFingerprint });
|
|
185
|
+
requireFact(stale.kind === "refused", "stale prepared command refuses");
|
|
186
|
+
let claims = 0;
|
|
187
|
+
let calls = 0;
|
|
188
|
+
let reads = 0;
|
|
189
|
+
const bounded = await runFulfillmentDispatchWorker({
|
|
190
|
+
store: { ...await fixture.create(), async read() { return null; }, async readCandidate() { reads += 1; return reads === 1 ? fixture.request : fixture.remappedRequest; },
|
|
191
|
+
async claim() { claims += 1; return { kind: "refused", refusal: { reason: "contact_stale", phase: "claim", retryable: true } }; } },
|
|
192
|
+
provider: { prepare: request => subject.adapter.dispatch.prepare(request), async dispatch() { calls += 1; throw new Error("unexpected dispatch"); } },
|
|
193
|
+
listCandidates: async () => [fixture.request], config: { mode: "stage", batchLimit: 1 },
|
|
194
|
+
keys: { request: () => fixture.claim.requestKey, mappedAttempt: (_key, index) => index === 0 ? fixture.mappedAttemptKey : fixture.remappedAttemptKey,
|
|
195
|
+
acceptance: () => ({ providerAttemptKey: "fixture-attempt", labelKey: "fixture-label" }) },
|
|
196
|
+
});
|
|
197
|
+
requireFact(claims === 2 && calls === 0 && !bounded.ok, "one-remap limit before I/O");
|
|
198
|
+
}
|
|
199
|
+
function assertDurable(value, subject) {
|
|
200
|
+
const text = JSON.stringify(value) ?? "";
|
|
201
|
+
requireFact(!subject.sensitiveValues.filter(Boolean).some(secret => text.includes(secret) || text.includes(JSON.stringify(secret).slice(1, -1))), "durable secret redaction");
|
|
202
|
+
const visit = (node) => {
|
|
203
|
+
if (!node || typeof node !== "object")
|
|
204
|
+
return;
|
|
205
|
+
for (const [key, child] of Object.entries(node)) {
|
|
206
|
+
requireFact(!/^(rawBody|rawPayload|rawCommand|password|secret|credential|authorization|headers|signedUrl)$/i.test(key), "durable field allowlist");
|
|
207
|
+
if (typeof child === "string")
|
|
208
|
+
requireFact(!/[?&](?:signature|sig|token|x-amz-signature|credential)=/i.test(child), "durable signed URL");
|
|
209
|
+
visit(child);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
visit(value);
|
|
213
|
+
}
|
|
214
|
+
function stable(value) {
|
|
215
|
+
if (Array.isArray(value))
|
|
216
|
+
return `[${value.map(stable).join(",")}]`;
|
|
217
|
+
if (value && typeof value === "object")
|
|
218
|
+
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => `${JSON.stringify(key)}:${stable(child)}`).join(",")}}`;
|
|
219
|
+
return JSON.stringify(value);
|
|
220
|
+
}
|
|
221
|
+
function requireSame(actual, expected, label) { requireFact(stable(actual) === stable(expected), label); }
|
|
222
|
+
function requireFact(value, label) { if (!value)
|
|
223
|
+
throw new Error(`fulfillment_adapter_conformance:${label}`); }
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { FulfillmentServices, FulfillmentServicesOptions } from './contracts/index.js';
|
|
2
|
+
/** Explicit selection; no environment readers, implicit provider or automatic dispatch. */
|
|
3
|
+
export declare function createFulfillmentServices(options: FulfillmentServicesOptions): FulfillmentServices;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FULFILLMENT_NATIVE_UNSUPPORTED } from './nativeUnsupported.js';
|
|
2
|
+
import { readFulfillmentCapabilities } from './providerCapabilities.js';
|
|
3
|
+
import { runFulfillmentDispatchWorker } from './providerDispatchWorker.js';
|
|
4
|
+
/** Explicit selection; no environment readers, implicit provider or automatic dispatch. */
|
|
5
|
+
export function createFulfillmentServices(options) {
|
|
6
|
+
const { enabled, selection, capability, store, provider, readiness: observe, listCandidates, now } = options;
|
|
7
|
+
const config = { ...options.config }, keys = { ...options.keys };
|
|
8
|
+
const readiness = async () => {
|
|
9
|
+
if (enabled !== true)
|
|
10
|
+
return { ready: false, refusal: { reason: 'disabled', phase: 'selection', retryable: false } };
|
|
11
|
+
const selected = readFulfillmentCapabilities(capability ? [capability] : [], selection);
|
|
12
|
+
if (selected.kind === 'refused')
|
|
13
|
+
return { ready: false, refusal: selected.refusal };
|
|
14
|
+
if (!selected.descriptor.operations.dispatch.supported)
|
|
15
|
+
return { ready: false, refusal: { reason: 'unsupported', phase: 'selection', retryable: false } };
|
|
16
|
+
if (!store || !provider)
|
|
17
|
+
return { ready: false, refusal: FULFILLMENT_NATIVE_UNSUPPORTED };
|
|
18
|
+
return observe();
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
readiness,
|
|
22
|
+
async runDispatch() {
|
|
23
|
+
const state = await readiness();
|
|
24
|
+
if (!state.ready)
|
|
25
|
+
return 'kind' in state.refusal ? state.refusal : { kind: 'refused', refusal: state.refusal };
|
|
26
|
+
return runFulfillmentDispatchWorker({ store: store, provider, listCandidates, keys, config, now });
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FulfillmentOperatorMutationPort as CommerceFulfillmentMutationPort } from './contracts/operator.js';
|
|
2
|
+
import type { FulfillmentCommerceReadPort as CommerceFulfillmentReadPort } from './contracts/read.js';
|
|
3
|
+
import { type BaseDeps, type FulfillmentHandlerRequest } from './handlerSupport.js';
|
|
4
|
+
type PortRef<T> = T | (() => T);
|
|
5
|
+
export declare function createAdminCommerceFulfillmentOrdersListHandler<Response>({ authorizeAdmin, respond, fulfillmentPort, }: BaseDeps<Response> & {
|
|
6
|
+
fulfillmentPort: PortRef<Pick<CommerceFulfillmentReadPort, "listCommerceFulfillmentOrders">>;
|
|
7
|
+
}): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
|
|
8
|
+
export declare function createAdminCommerceFulfillmentOrderDetailHandler<Response>({ authorizeAdmin, respond, fulfillmentPort, }: BaseDeps<Response> & {
|
|
9
|
+
fulfillmentPort: PortRef<Pick<CommerceFulfillmentReadPort, "getCommerceFulfillmentOrderDetail">>;
|
|
10
|
+
}): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
|
|
11
|
+
export declare function createAdminCommerceFulfillmentCreateOrderHandler<Response>({ authorizeAdmin, respond, fulfillmentPort, }: BaseDeps<Response> & {
|
|
12
|
+
fulfillmentPort: PortRef<Pick<CommerceFulfillmentMutationPort, "createCommerceFulfillmentOrder">>;
|
|
13
|
+
}): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
|
|
14
|
+
export declare function createAdminCommerceFulfillmentRecordProviderAttemptHandler<Response>({ authorizeAdmin, respond, fulfillmentPort, }: BaseDeps<Response> & {
|
|
15
|
+
fulfillmentPort: PortRef<Pick<CommerceFulfillmentMutationPort, "recordCommerceFulfillmentProviderAttempt">>;
|
|
16
|
+
}): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
|
|
17
|
+
export declare function createAdminCommerceFulfillmentRecordLabelHandler<Response>({ authorizeAdmin, respond, fulfillmentPort, }: BaseDeps<Response> & {
|
|
18
|
+
fulfillmentPort: PortRef<Pick<CommerceFulfillmentMutationPort, "recordCommerceFulfillmentLabel">>;
|
|
19
|
+
}): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
|
|
20
|
+
export declare function createAdminCommerceFulfillmentHandOffHandler<Response>({ authorizeAdmin, respond, fulfillmentPort, }: BaseDeps<Response> & {
|
|
21
|
+
fulfillmentPort: PortRef<Pick<CommerceFulfillmentMutationPort, "handOffCommerceFulfillmentOrder">>;
|
|
22
|
+
}): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
|
|
23
|
+
export declare function createAdminCommerceFulfillmentTrackingEventHandler<Response>({ authorizeAdmin, respond, fulfillmentPort, }: BaseDeps<Response> & {
|
|
24
|
+
fulfillmentPort: PortRef<Pick<CommerceFulfillmentMutationPort, "recordCommerceFulfillmentTrackingEvent">>;
|
|
25
|
+
}): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
|
|
26
|
+
export declare function createAdminCommerceFulfillmentCancelOrderHandler<Response>({ authorizeAdmin, respond, fulfillmentPort, }: BaseDeps<Response> & {
|
|
27
|
+
fulfillmentPort: PortRef<Pick<CommerceFulfillmentMutationPort, "cancelCommerceFulfillmentOrder">>;
|
|
28
|
+
}): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { adminCommerceFulfillmentCancelRequestSchema, adminCommerceFulfillmentCreateRequestSchema, adminCommerceFulfillmentHandOffRequestSchema, adminCommerceFulfillmentMutationResponseSchema, adminCommerceFulfillmentOrderDetailRequestSchema, adminCommerceFulfillmentOrderDetailResponseSchema, adminCommerceFulfillmentOrdersListRequestSchema, adminCommerceFulfillmentOrdersListResponseSchema, adminCommerceFulfillmentRecordLabelRequestSchema, adminCommerceFulfillmentRecordProviderAttemptRequestSchema, adminCommerceFulfillmentTrackingEventRequestSchema, } from "./handlerSchemas.js";
|
|
2
|
+
import { CommerceFulfillmentConflictError, authorize, isNativeUnsupported } from './handlerSupport.js';
|
|
3
|
+
export function createAdminCommerceFulfillmentOrdersListHandler({ authorizeAdmin, respond, fulfillmentPort, }) {
|
|
4
|
+
const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = respond;
|
|
5
|
+
return async function handler(req, res) {
|
|
6
|
+
if (req.method !== "GET")
|
|
7
|
+
return sendMethodNotAllowed(res, ["GET"]);
|
|
8
|
+
if (!(await authorize(req, res, authorizeAdmin, respond)))
|
|
9
|
+
return;
|
|
10
|
+
const request = adminCommerceFulfillmentOrdersListRequestSchema.safeParse(req.query);
|
|
11
|
+
if (!request.success) {
|
|
12
|
+
sendBffError(res, "BAD_REQUEST", "Invalid admin commerce fulfillment orders request", {
|
|
13
|
+
details: request.error.flatten(),
|
|
14
|
+
});
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const port = resolvePort(fulfillmentPort);
|
|
19
|
+
const result = await port.listCommerceFulfillmentOrders(request.data);
|
|
20
|
+
if (isNativeUnsupported(result)) {
|
|
21
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Native fulfillment operation unsupported", { details: result });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const response = adminCommerceFulfillmentOrdersListResponseSchema.safeParse(result);
|
|
25
|
+
if (!response.success) {
|
|
26
|
+
sendBffError(res, "INVALID_RESPONSE", "Admin commerce fulfillment orders response invalid");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
sendBffSuccess(res, response.data);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin commerce fulfillment orders read failed");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function createAdminCommerceFulfillmentOrderDetailHandler({ authorizeAdmin, respond, fulfillmentPort, }) {
|
|
37
|
+
const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = respond;
|
|
38
|
+
return async function handler(req, res) {
|
|
39
|
+
if (req.method !== "GET")
|
|
40
|
+
return sendMethodNotAllowed(res, ["GET"]);
|
|
41
|
+
if (!(await authorize(req, res, authorizeAdmin, respond)))
|
|
42
|
+
return;
|
|
43
|
+
const request = adminCommerceFulfillmentOrderDetailRequestSchema.safeParse(req.query);
|
|
44
|
+
if (!request.success) {
|
|
45
|
+
sendBffError(res, "BAD_REQUEST", "Invalid admin commerce fulfillment detail request", {
|
|
46
|
+
details: request.error.flatten(),
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const port = resolvePort(fulfillmentPort);
|
|
52
|
+
const result = await port.getCommerceFulfillmentOrderDetail(request.data);
|
|
53
|
+
if (isNativeUnsupported(result)) {
|
|
54
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Native fulfillment operation unsupported", { details: result });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!result) {
|
|
58
|
+
sendBffError(res, "NOT_FOUND", "Commerce fulfillment order not found");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const response = adminCommerceFulfillmentOrderDetailResponseSchema.safeParse(result);
|
|
62
|
+
if (!response.success) {
|
|
63
|
+
sendBffError(res, "INVALID_RESPONSE", "Admin commerce fulfillment detail response invalid");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
sendBffSuccess(res, response.data);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin commerce fulfillment detail read failed");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function createAdminCommerceFulfillmentCreateOrderHandler({ authorizeAdmin, respond, fulfillmentPort, }) {
|
|
74
|
+
return createMutationHandler({
|
|
75
|
+
authorizeAdmin,
|
|
76
|
+
respond,
|
|
77
|
+
requestSchema: adminCommerceFulfillmentCreateRequestSchema,
|
|
78
|
+
invalidMessage: "Invalid admin commerce fulfillment create request",
|
|
79
|
+
execute: (request, actorUserId) => {
|
|
80
|
+
const port = resolvePort(fulfillmentPort);
|
|
81
|
+
return port.createCommerceFulfillmentOrder({ ...request, actorUserId });
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export function createAdminCommerceFulfillmentRecordProviderAttemptHandler({ authorizeAdmin, respond, fulfillmentPort, }) {
|
|
86
|
+
return createMutationHandler({
|
|
87
|
+
authorizeAdmin,
|
|
88
|
+
respond,
|
|
89
|
+
requestSchema: adminCommerceFulfillmentRecordProviderAttemptRequestSchema,
|
|
90
|
+
invalidMessage: "Invalid admin commerce fulfillment provider attempt request",
|
|
91
|
+
execute: (request, actorUserId) => {
|
|
92
|
+
const port = resolvePort(fulfillmentPort);
|
|
93
|
+
return port.recordCommerceFulfillmentProviderAttempt({ ...request, actorUserId });
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export function createAdminCommerceFulfillmentRecordLabelHandler({ authorizeAdmin, respond, fulfillmentPort, }) {
|
|
98
|
+
return createMutationHandler({
|
|
99
|
+
authorizeAdmin,
|
|
100
|
+
respond,
|
|
101
|
+
requestSchema: adminCommerceFulfillmentRecordLabelRequestSchema,
|
|
102
|
+
invalidMessage: "Invalid admin commerce fulfillment label request",
|
|
103
|
+
execute: (request, actorUserId) => {
|
|
104
|
+
const port = resolvePort(fulfillmentPort);
|
|
105
|
+
return port.recordCommerceFulfillmentLabel({ ...request, actorUserId });
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export function createAdminCommerceFulfillmentHandOffHandler({ authorizeAdmin, respond, fulfillmentPort, }) {
|
|
110
|
+
return createMutationHandler({
|
|
111
|
+
authorizeAdmin,
|
|
112
|
+
respond,
|
|
113
|
+
requestSchema: adminCommerceFulfillmentHandOffRequestSchema,
|
|
114
|
+
invalidMessage: "Invalid admin commerce fulfillment handoff request",
|
|
115
|
+
execute: (request, actorUserId) => {
|
|
116
|
+
const port = resolvePort(fulfillmentPort);
|
|
117
|
+
return port.handOffCommerceFulfillmentOrder({ ...request, actorUserId });
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
export function createAdminCommerceFulfillmentTrackingEventHandler({ authorizeAdmin, respond, fulfillmentPort, }) {
|
|
122
|
+
return createMutationHandler({
|
|
123
|
+
authorizeAdmin,
|
|
124
|
+
respond,
|
|
125
|
+
requestSchema: adminCommerceFulfillmentTrackingEventRequestSchema,
|
|
126
|
+
invalidMessage: "Invalid admin commerce fulfillment tracking event request",
|
|
127
|
+
execute: (request, actorUserId) => {
|
|
128
|
+
const port = resolvePort(fulfillmentPort);
|
|
129
|
+
return port.recordCommerceFulfillmentTrackingEvent({ ...request, actorUserId });
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export function createAdminCommerceFulfillmentCancelOrderHandler({ authorizeAdmin, respond, fulfillmentPort, }) {
|
|
134
|
+
return createMutationHandler({
|
|
135
|
+
authorizeAdmin,
|
|
136
|
+
respond,
|
|
137
|
+
requestSchema: adminCommerceFulfillmentCancelRequestSchema,
|
|
138
|
+
invalidMessage: "Invalid admin commerce fulfillment cancel request",
|
|
139
|
+
execute: (request, actorUserId) => {
|
|
140
|
+
const port = resolvePort(fulfillmentPort);
|
|
141
|
+
return port.cancelCommerceFulfillmentOrder({ ...request, actorUserId });
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function resolvePort(port) {
|
|
146
|
+
return typeof port === "function" ? port() : port;
|
|
147
|
+
}
|
|
148
|
+
function createMutationHandler({ authorizeAdmin, respond, requestSchema, invalidMessage, execute, }) {
|
|
149
|
+
const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = respond;
|
|
150
|
+
return async function handler(req, res) {
|
|
151
|
+
if (req.method !== "POST")
|
|
152
|
+
return sendMethodNotAllowed(res, ["POST"]);
|
|
153
|
+
const auth = await authorize(req, res, authorizeAdmin, respond);
|
|
154
|
+
if (!auth)
|
|
155
|
+
return;
|
|
156
|
+
const request = requestSchema.safeParse(req.body ?? {});
|
|
157
|
+
if (request.success === false) {
|
|
158
|
+
sendBffError(res, "BAD_REQUEST", invalidMessage, { details: request.error.flatten() });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const result = await execute(request.data, auth.userId);
|
|
163
|
+
if (isNativeUnsupported(result)) {
|
|
164
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Native fulfillment operation unsupported", { details: result });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const response = adminCommerceFulfillmentMutationResponseSchema.safeParse(result);
|
|
168
|
+
if (!response.success) {
|
|
169
|
+
sendBffError(res, "INVALID_RESPONSE", "Admin commerce fulfillment mutation response invalid");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
sendBffSuccess(res, response.data);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error instanceof CommerceFulfillmentConflictError) {
|
|
176
|
+
sendBffError(res, "CONFLICT", error.message, { details: error.details });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin commerce fulfillment mutation failed", {
|
|
180
|
+
details: { reason: "fulfillment_mutation_failed" },
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { FulfillmentCommerceReadPolicy, FulfillmentPaymentStatus, FulfillmentOrderStatus as OrderStatus } from './contracts/read.js';
|
|
2
|
+
import type { SubscriptionCycleStatus } from '@openlup/core/subscription';
|
|
3
|
+
import { type AdminCommerceFulfillmentOrderDetailResponse, type AdminCommerceFulfillmentOrdersListResponse, type CommerceFulfillmentOrder, type CommerceFulfillmentOperationType, type CommerceFulfillmentStatus } from "./handlerSchemas.js";
|
|
4
|
+
export interface CommerceFulfillmentOrderRow {
|
|
5
|
+
id: string;
|
|
6
|
+
order_id: string;
|
|
7
|
+
client_id: string | null;
|
|
8
|
+
status: CommerceFulfillmentStatus;
|
|
9
|
+
provider_kind: string | null;
|
|
10
|
+
shipping_address_snapshot: CommerceFulfillmentOrder["shippingAddress"];
|
|
11
|
+
created_at: string;
|
|
12
|
+
updated_at: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CommerceFulfillmentLineRow {
|
|
15
|
+
id: string;
|
|
16
|
+
fulfillment_order_id: string;
|
|
17
|
+
order_item_id: string;
|
|
18
|
+
sku_id: string;
|
|
19
|
+
sku: string;
|
|
20
|
+
title: string | null;
|
|
21
|
+
quantity: number;
|
|
22
|
+
inventory_reservation_ids: string[];
|
|
23
|
+
product_snapshot: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface CommerceFulfillmentOperationRow {
|
|
26
|
+
id: string;
|
|
27
|
+
fulfillment_order_id: string;
|
|
28
|
+
operation_type: CommerceFulfillmentOperationType;
|
|
29
|
+
actor_user_id: string | null;
|
|
30
|
+
occurred_at: string;
|
|
31
|
+
payload: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
export interface CommerceFulfillmentOrderContextRow {
|
|
34
|
+
id: string;
|
|
35
|
+
status: OrderStatus;
|
|
36
|
+
mode: "one_time" | "subscription_cycle";
|
|
37
|
+
shipping_address_id: string | null;
|
|
38
|
+
subscription_cycle_id: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface CommerceFulfillmentPaymentIntentRow {
|
|
41
|
+
id: string;
|
|
42
|
+
order_id: string;
|
|
43
|
+
status: FulfillmentPaymentStatus;
|
|
44
|
+
provider_payment_id: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface CommerceFulfillmentInventoryReservationRow {
|
|
47
|
+
id: string;
|
|
48
|
+
order_id: string;
|
|
49
|
+
status: "reserved" | "released" | "consumed" | "expired";
|
|
50
|
+
expires_at: string | null;
|
|
51
|
+
location_id: string | null;
|
|
52
|
+
inventory_locations?: {
|
|
53
|
+
code?: string | null;
|
|
54
|
+
} | null;
|
|
55
|
+
}
|
|
56
|
+
export interface CommerceFulfillmentShipmentRefRow {
|
|
57
|
+
order_id: string;
|
|
58
|
+
provider_tracking_id: string;
|
|
59
|
+
active: boolean;
|
|
60
|
+
created_at?: string | null;
|
|
61
|
+
}
|
|
62
|
+
export declare function buildCommerceFulfillmentOrdersListResponse(input: {
|
|
63
|
+
fulfillmentOrders: CommerceFulfillmentOrderRow[];
|
|
64
|
+
lines: CommerceFulfillmentLineRow[];
|
|
65
|
+
latestOperations: CommerceFulfillmentOperationRow[];
|
|
66
|
+
orderContexts: CommerceFulfillmentOrderContextRow[];
|
|
67
|
+
paymentIntents: CommerceFulfillmentPaymentIntentRow[];
|
|
68
|
+
inventoryReservations: CommerceFulfillmentInventoryReservationRow[];
|
|
69
|
+
shipmentRefs: CommerceFulfillmentShipmentRefRow[];
|
|
70
|
+
activeHoldCounts: Record<string, number>;
|
|
71
|
+
subscriptionCycleStatuses: Record<string, SubscriptionCycleStatus | null>;
|
|
72
|
+
totalCount: number;
|
|
73
|
+
page: number;
|
|
74
|
+
pageSize: number;
|
|
75
|
+
}, policy: FulfillmentCommerceReadPolicy): AdminCommerceFulfillmentOrdersListResponse;
|
|
76
|
+
export declare function buildCommerceFulfillmentOrderDetailResponse(input: {
|
|
77
|
+
fulfillmentOrder: CommerceFulfillmentOrderRow;
|
|
78
|
+
lines: CommerceFulfillmentLineRow[];
|
|
79
|
+
latestOperation: CommerceFulfillmentOperationRow | null;
|
|
80
|
+
orderContext: CommerceFulfillmentOrderContextRow;
|
|
81
|
+
paymentIntent: CommerceFulfillmentPaymentIntentRow;
|
|
82
|
+
inventoryReservations: CommerceFulfillmentInventoryReservationRow[];
|
|
83
|
+
shipmentRef: CommerceFulfillmentShipmentRefRow | null;
|
|
84
|
+
activeHoldCount: number;
|
|
85
|
+
subscriptionCycleStatus: SubscriptionCycleStatus | null;
|
|
86
|
+
}, policy: FulfillmentCommerceReadPolicy): AdminCommerceFulfillmentOrderDetailResponse;
|