@hartl-services/medusa-base 0.1.1 → 0.3.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.
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcGxldGUtY2FydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9wdWJsaWMvY29tcGxldGUtY2FydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.withSentryErrorReporting = withSentryErrorReporting;
37
+ const Sentry = __importStar(require("@sentry/node"));
38
+ /**
39
+ * Medusa's own HTTP error-handler middleware fully "handles" every thrown
40
+ * route/workflow error via logger.error(...) before it can ever reach an
41
+ * uncaught-exception process hook, so Sentry's default Node integrations
42
+ * never see it. Bridging logger.error(...) itself is the only place that
43
+ * reliably captures every error Medusa's framework already logs internally.
44
+ * Errors that Medusa logs through logger.warn(err) (failing event
45
+ * subscribers) are reported too, at warning level.
46
+ */
47
+ function withSentryErrorReporting(logger) {
48
+ const originalError = logger.error.bind(logger);
49
+ const originalWarn = logger.warn.bind(logger);
50
+ return {
51
+ panic: logger.panic.bind(logger),
52
+ shouldLog: logger.shouldLog.bind(logger),
53
+ setLogLevel: logger.setLogLevel.bind(logger),
54
+ unsetLogLevel: logger.unsetLogLevel.bind(logger),
55
+ activity: logger.activity.bind(logger),
56
+ progress: logger.progress.bind(logger),
57
+ failure: logger.failure.bind(logger),
58
+ success: logger.success.bind(logger),
59
+ silly: logger.silly.bind(logger),
60
+ debug: logger.debug.bind(logger),
61
+ verbose: logger.verbose.bind(logger),
62
+ http: logger.http.bind(logger),
63
+ info: logger.info.bind(logger),
64
+ // Medusa's Redis Event Bus logs a failing subscriber's exception with
65
+ // warn(err), even on the final retry, so an Error passed to warn is a real
66
+ // failure too. Plain warning messages stay out of Sentry.
67
+ warn: (message) => {
68
+ if (message instanceof Error) {
69
+ Sentry.captureException(message, { level: "warning" });
70
+ }
71
+ return originalWarn(message);
72
+ },
73
+ log: logger.log.bind(logger),
74
+ error: (messageOrError, error) => {
75
+ const exception = error ?? (messageOrError instanceof Error ? messageOrError : new Error(messageOrError));
76
+ const loggerMessage = error && typeof messageOrError === "string" ? messageOrError : undefined;
77
+ Sentry.captureException(exception, loggerMessage ? { extra: { loggerMessage } } : undefined);
78
+ return originalError(messageOrError, error);
79
+ },
80
+ };
81
+ }
82
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VudHJ5LWxvZ2dlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9wdWJsaWMvc2VudHJ5LWxvZ2dlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQVlBLDREQTBDQztBQXRERCxxREFBc0M7QUFHdEM7Ozs7Ozs7O0dBUUc7QUFDSCxTQUFnQix3QkFBd0IsQ0FBQyxNQUFjO0lBQ3JELE1BQU0sYUFBYSxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFBO0lBQy9DLE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFBO0lBRTdDLE9BQU87UUFDTCxLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQ2hDLFNBQVMsRUFBRSxNQUFNLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7UUFDeEMsV0FBVyxFQUFFLE1BQU0sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUM1QyxhQUFhLEVBQUUsTUFBTSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQ2hELFFBQVEsRUFBRSxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7UUFDdEMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUN0QyxPQUFPLEVBQUUsTUFBTSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQ3BDLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7UUFDcEMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUNoQyxLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQ2hDLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7UUFDcEMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUM5QixJQUFJLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQzlCLHNFQUFzRTtRQUN0RSwyRUFBMkU7UUFDM0UsMERBQTBEO1FBQzFELElBQUksRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ2hCLElBQUssT0FBbUIsWUFBWSxLQUFLLEVBQUUsQ0FBQztnQkFDMUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sRUFBRSxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFBO1lBQ3hELENBQUM7WUFDRCxPQUFPLFlBQVksQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUM5QixDQUFDO1FBQ0QsR0FBRyxFQUFFLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUM1QixLQUFLLEVBQUUsQ0FBQyxjQUFjLEVBQUUsS0FBSyxFQUFFLEVBQUU7WUFDL0IsTUFBTSxTQUFTLEdBQ2IsS0FBSyxJQUFJLENBQUMsY0FBYyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFBO1lBQ3pGLE1BQU0sYUFBYSxHQUNqQixLQUFLLElBQUksT0FBTyxjQUFjLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQTtZQUUxRSxNQUFNLENBQUMsZ0JBQWdCLENBQ3JCLFNBQVMsRUFDVCxhQUFhLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLEVBQUUsYUFBYSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUN6RCxDQUFBO1lBRUQsT0FBTyxhQUFhLENBQUMsY0FBYyxFQUFFLEtBQUssQ0FBQyxDQUFBO1FBQzdDLENBQUM7S0FDRixDQUFBO0FBQ0gsQ0FBQyJ9
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.selectCompleteCartParticipants = selectCompleteCartParticipants;
4
+ exports.listCompleteCartParticipants = listCompleteCartParticipants;
5
+ const utils_1 = require("@medusajs/framework/utils");
6
+ const discover_plugin_features_1 = require("./discover-plugin-features");
7
+ const HOOK_NAMES = [
8
+ "validate",
9
+ "orderCreated",
10
+ ];
11
+ const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
12
+ const invalid = (moduleKey, reason) => new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Modul "${moduleKey}" deklariert ungültige Complete-Cart-Hooks: ${reason}`);
13
+ const readDeclarations = (moduleKey, service) => {
14
+ if (service === null || typeof service !== "object") {
15
+ return undefined;
16
+ }
17
+ const declare = service
18
+ .completeCartHooks;
19
+ if (typeof declare !== "function") {
20
+ return undefined;
21
+ }
22
+ let declarations;
23
+ try {
24
+ declarations = declare.call(service);
25
+ }
26
+ catch (error) {
27
+ throw invalid(moduleKey, error instanceof Error ? error.message : String(error));
28
+ }
29
+ if (declarations === null ||
30
+ typeof declarations !== "object" ||
31
+ Array.isArray(declarations)) {
32
+ throw invalid(moduleKey, "completeCartHooks() liefert kein Objekt.");
33
+ }
34
+ return declarations;
35
+ };
36
+ const validateStep = (moduleKey, hook, step) => {
37
+ if (!HOOK_NAMES.includes(hook)) {
38
+ throw invalid(moduleKey, `unbekannter Hook "${hook}".`);
39
+ }
40
+ const candidate = step;
41
+ if (candidate === null ||
42
+ typeof candidate !== "object" ||
43
+ typeof candidate.workflow_id !== "string" ||
44
+ !candidate.workflow_id.trim() ||
45
+ typeof candidate.order !== "number" ||
46
+ !Number.isFinite(candidate.order)) {
47
+ throw invalid(moduleKey, `"${hook}" braucht eine workflow_id (String) und eine endliche order (Zahl).`);
48
+ }
49
+ return { workflow_id: candidate.workflow_id, order: candidate.order };
50
+ };
51
+ /**
52
+ * Selects the participants of one hook from the given modules: every service
53
+ * with a `completeCartHooks()` function. Fails closed: an invalid declaration
54
+ * (for any hook) is `UNEXPECTED_STATE`, never skipped. Sorted by `order`,
55
+ * then module key, then workflow id.
56
+ */
57
+ function selectCompleteCartParticipants(modules, hook) {
58
+ const participants = [];
59
+ for (const { key, service } of modules) {
60
+ const declarations = readDeclarations(key, service);
61
+ if (!declarations) {
62
+ continue;
63
+ }
64
+ for (const [declaredHook, step] of Object.entries(declarations)) {
65
+ if (step === undefined) {
66
+ continue;
67
+ }
68
+ const valid = validateStep(key, declaredHook, step);
69
+ if (declaredHook === hook) {
70
+ participants.push({ module_key: key, ...valid });
71
+ }
72
+ }
73
+ }
74
+ participants.sort((left, right) => left.order - right.order ||
75
+ compare(left.module_key, right.module_key) ||
76
+ compare(left.workflow_id, right.workflow_id));
77
+ const workflowIds = new Set();
78
+ for (const participant of participants) {
79
+ if (workflowIds.has(participant.workflow_id)) {
80
+ throw invalid(participant.module_key, `Workflow "${participant.workflow_id}" ist für "${hook}" mehrfach deklariert.`);
81
+ }
82
+ workflowIds.add(participant.workflow_id);
83
+ }
84
+ return participants;
85
+ }
86
+ /** The participants of one hook among the modules registered in the app. */
87
+ function listCompleteCartParticipants(container, hook) {
88
+ return selectCompleteCartParticipants((0, discover_plugin_features_1.listRegisteredModules)(container), hook);
89
+ }
90
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlzY292ZXItY29tcGxldGUtY2FydC1wYXJ0aWNpcGFudHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdXRpbHMvZGlzY292ZXItY29tcGxldGUtY2FydC1wYXJ0aWNpcGFudHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUE0RkEsd0VBc0NDO0FBR0Qsb0VBS0M7QUF6SUQscURBQXVEO0FBR3ZELHlFQUdtQztBQVFuQyxNQUFNLFVBQVUsR0FBc0I7SUFDcEMsVUFBVTtJQUNWLGNBQWM7Q0FDa0IsQ0FBQTtBQUVsQyxNQUFNLE9BQU8sR0FBRyxDQUFDLElBQVksRUFBRSxLQUFhLEVBQVUsRUFBRSxDQUN0RCxJQUFJLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFFMUMsTUFBTSxPQUFPLEdBQUcsQ0FBQyxTQUFpQixFQUFFLE1BQWMsRUFBZSxFQUFFLENBQ2pFLElBQUksbUJBQVcsQ0FDYixtQkFBVyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFDbEMsVUFBVSxTQUFTLCtDQUErQyxNQUFNLEVBQUUsQ0FDM0UsQ0FBQTtBQUVILE1BQU0sZ0JBQWdCLEdBQUcsQ0FDdkIsU0FBaUIsRUFDakIsT0FBZ0IsRUFDcUIsRUFBRTtJQUN2QyxJQUFJLE9BQU8sS0FBSyxJQUFJLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDcEQsT0FBTyxTQUFTLENBQUE7SUFDbEIsQ0FBQztJQUNELE1BQU0sT0FBTyxHQUFJLE9BQTJDO1NBQ3pELGlCQUFpQixDQUFBO0lBQ3BCLElBQUksT0FBTyxPQUFPLEtBQUssVUFBVSxFQUFFLENBQUM7UUFDbEMsT0FBTyxTQUFTLENBQUE7SUFDbEIsQ0FBQztJQUVELElBQUksWUFBcUIsQ0FBQTtJQUN6QixJQUFJLENB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
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listRegisteredModules = listRegisteredModules;
3
4
  exports.discoverPluginFeatureDescriptors = discoverPluginFeatureDescriptors;
4
5
  exports.findPluginFeatureDescriptor = findPluginFeatureDescriptor;
5
6
  const utils_1 = require("@medusajs/framework/utils");
@@ -7,6 +8,16 @@ const descriptor_1 = require("../public/descriptor");
7
8
  // Keyed by the app's config object: stable for the process lifetime, unlike
8
9
  // `req.scope`, which is a new scope per request.
9
10
  const discovered = new WeakMap();
11
+ const registeredModules = (container, configModule) => Object.keys(configModule.modules ?? {})
12
+ .filter((moduleKey) => container.hasRegistration(moduleKey))
13
+ .map((moduleKey) => ({
14
+ key: moduleKey,
15
+ service: container.resolve(moduleKey),
16
+ }));
17
+ /** Every module listed in medusa-config that is registered in the container. */
18
+ function listRegisteredModules(container) {
19
+ return registeredModules(container, container.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE));
20
+ }
10
21
  const readDescriptor = (service) => service !== null && typeof service === "object" && "pluginFeatures" in service
11
22
  ? service.pluginFeatures
12
23
  : undefined;
@@ -21,11 +32,8 @@ function discoverPluginFeatureDescriptors(container) {
21
32
  return memo;
22
33
  }
23
34
  const byPluginKey = new Map();
24
- for (const moduleKey of Object.keys(configModule.modules ?? {})) {
25
- if (!container.hasRegistration(moduleKey)) {
26
- continue;
27
- }
28
- const candidate = readDescriptor(container.resolve(moduleKey));
35
+ for (const { key: moduleKey, service } of registeredModules(container, configModule)) {
36
+ const candidate = readDescriptor(service);
29
37
  if (candidate === undefined) {
30
38
  continue;
31
39
  }
@@ -48,4 +56,4 @@ function discoverPluginFeatureDescriptors(container) {
48
56
  function findPluginFeatureDescriptor(container, pluginKey) {
49
57
  return discoverPluginFeatureDescriptors(container).find((descriptor) => descriptor.plugin_key === pluginKey);
50
58
  }
51
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlzY292ZXItcGx1Z2luLWZlYXR1cmVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL2Rpc2NvdmVyLXBsdWdpbi1mZWF0dXJlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQXVCQSw0RUFnREM7QUFFRCxrRUFPQztBQS9FRCxxREFBa0Y7QUFFbEYscURBQXNFO0FBT3RFLDRFQUE0RTtBQUM1RSxpREFBaUQ7QUFDakQsTUFBTSxVQUFVLEdBQUcsSUFBSSxPQUFPLEVBQXFDLENBQUE7QUFFbkUsTUFBTSxjQUFjLEdBQUcsQ0FBQyxPQUFnQixFQUFXLEVBQUUsQ0FDbkQsT0FBTyxLQUFLLElBQUksSUFBSSxPQUFPLE9BQU8sS0FBSyxRQUFRLElBQUksZ0JBQWdCLElBQUksT0FBTztJQUM1RSxDQUFDLENBQUUsT0FBdUMsQ0FBQyxjQUFjO0lBQ3pELENBQUMsQ0FBQyxTQUFTLENBQUE7QUFFZjs7O0dBR0c7QUFDSCxTQUFnQixnQ0FBZ0MsQ0FDOUMsU0FBMEI7SUFFMUIsTUFBTSxZQUFZLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FDcEMsaUNBQXlCLENBQUMsYUFBYSxDQUN4QyxDQUFBO0lBQ0QsTUFBTSxJQUFJLEdBQUcsVUFBVSxDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsQ0FBQTtJQUN6QyxJQUFJLElBQUksRUFBRSxDQUFDO1FBQ1QsT0FBTyxJQUFJLENBQUE7SUFDYixDQUFDO0lBRUQsTUFBTSxXQUFXLEdBQUcsSUFBSSxHQUFHLEVBQW1DLENBQUE7SUFDOUQsS0FBSyxNQUFNLFNBQVMsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDLEVBQUUsQ0FBQztRQUNoRSxJQUFJLENBQUMsU0FBUyxDQUFDLGVBQWUsQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO1lBQzFDLFNBQVE7UUFDVixDQUFDO1FBRUQsTUFBTSxTQUFTLEdBQUcsY0FBYyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQVUsU0FBUyxDQUFDLENBQUMsQ0FBQTtRQUN2RSxJQUFJLFNBQVMsS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUM1QixTQUFRO1FBQ1YsQ0FBQztRQUVELE1BQU0sVUFBVSxHQUFHLFNBQW9DLENBQUE7UUFDdkQsSUFBSSxDQUFDO1lBQ0gsSUFBQSw0Q0FBK0IsRUFBQyxVQUFVLENBQUMsQ0FBQTtRQUM3QyxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFDbEMsVUFBVSxTQUFTLDZDQUNqQixLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUN2RCxFQUFFLENBQ0gsQ0FBQTtRQUNILENBQUM7UUFFRCxJQUFJLFdBQVcsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDM0MsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLGdCQUFnQixFQUNsQyxlQUFlLFVBQVUsQ0FBQyxVQUFVLHlDQUF5QyxDQUM5RSxDQUFBO1FBQ0gsQ0FBQztRQUNELFdBQVcsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLFVBQVUsRUFBRSxVQUFVLENBQUMsQ0FBQTtJQUNwRCxDQUFDO0lBRUQsTUFBTSxNQUFNLEdBQUcsQ0FBQyxHQUFHLFdBQVcsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUM1RCxJQUFJLENBQUMsVUFBVSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQ2hELENBQUE7SUFDRCxVQUFVLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUNwQyxPQUFPLE1BQU0sQ0FBQTtBQUNmLENBQUM7QUFFRCxTQUFnQiwyQkFBMkIsQ0FDekMsU0FBMEIsRUFDMUIsU0FBaUI7SUFFakIsT0FBTyxnQ0FBZ0MsQ0FBQyxTQUFTLENBQUMsQ0FBQyxJQUFJLENBQ3JELENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsVUFBVSxLQUFLLFNBQVMsQ0FDcEQsQ0FBQTtBQUNILENBQUMifQ==
59
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlzY292ZXItcGx1Z2luLWZlYXR1cmVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL2Rpc2NvdmVyLXBsdWdpbi1mZWF0dXJlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQTRCQSxzREFPQztBQVdELDRFQStDQztBQUVELGtFQU9DO0FBckdELHFEQUFrRjtBQUVsRixxREFBc0U7QUFTdEUsNEVBQTRFO0FBQzVFLGlEQUFpRDtBQUNqRCxNQUFNLFVBQVUsR0FBRyxJQUFJLE9BQU8sRUFBcUMsQ0FBQTtBQUVuRSxNQUFNLGlCQUFpQixHQUFHLENBQ3hCLFNBQTBCLEVBQzFCLFlBQThCLEVBQ1YsRUFBRSxDQUN0QixNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDO0tBQ3BDLE1BQU0sQ0FBQyxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQUMsU0FBUyxDQUFDLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQztLQUMzRCxHQUFHLENBQUMsQ0FBQyxTQUFTLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDbkIsR0FBRyxFQUFFLFNBQVM7SUFDZCxPQUFPLEVBQUUsU0FBUyxDQUFDLE9BQU8sQ0FBVSxTQUFTLENBQUM7Q0FDL0MsQ0FBQyxDQUFDLENBQUE7QUFFUCxnRkFBZ0Y7QUFDaEYsU0FBZ0IscUJBQXFCLENBQ25DLFNBQTBCO0lBRTFCLE9BQU8saUJBQWlCLENBQ3RCLFNBQVMsRUFDVCxTQUFTLENBQUMsT0FBTyxDQUFtQixpQ0FBeUIsQ0FBQyxhQUFhLENBQUMsQ0FDN0UsQ0FBQTtBQUNILENBQUM7QUFFRCxNQUFNLGNBQWMsR0FBRyxDQUFDLE9BQWdCLEVBQVcsRUFBRSxDQUNuRCxPQUFPLEtBQUssSUFBSSxJQUFJLE9BQU8sT0FBTyxLQUFLLFFBQVEsSUFBSSxnQkFBZ0IsSUFBSSxPQUFPO0lBQzVFLENBQUMsQ0FBRSxPQUF1QyxDQUFDLGNBQWM7SUFDekQsQ0FBQyxDQUFDLFNBQVMsQ0FBQTtBQUVmOzs7R0FHRztBQUNILFNBQWdCLGdDQUFnQyxDQUM5QyxTQUEwQjtJQUUxQixNQUFNLFlBQVksR0FBRyxTQUFTLENBQUMsT0FBTyxDQUNwQyxpQ0FBeUIsQ0FBQyxhQUFhLENBQ3hDLENBQUE7SUFDRCxNQUFNLElBQUksR0FBRyxVQUFVLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFBO0lBQ3pDLElBQUksSUFBSSxFQUFFLENBQUM7UUFDVCxPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7SUFFRCxNQUFNLFdBQVcsR0FBRyxJQUFJLEdBQUcsRUFBbUMsQ0FBQTtJQUM5RCxLQUFLLE1BQU0sRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxJQUFJLGlCQUFpQixDQUN6RCxTQUFTLEVBQ1QsWUFBWSxDQUNiLEVBQUUsQ0FBQztRQUNGLE1BQU0sU0FBUyxHQUFHLGNBQWMsQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUN6QyxJQUFJLFNBQVMsS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUM1QixTQUFRO1FBQ1YsQ0FBQztRQUVELE1BQU0sVUFBVSxHQUFHLFNBQW9DLENBQUE7UUFDdkQsSUFBSSxDQUFDO1lBQ0gsSUFBQSw0Q0FBK0IsRUFBQyxVQUFVLENBQUMsQ0FBQTtRQUM3QyxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFDbEMsVUFBVSxTQUFTLDZDQUNqQixLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUN2RCxFQUFFLENBQ0gsQ0FBQTtRQUNILENBQUM7UUFFRCxJQUFJLFdBQVcsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDM0MsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLGdCQUFnQixFQUNsQyxlQUFlLFVBQVUsQ0FBQyxVQUFVLHlDQUF5QyxDQUM5RSxDQUFBO1FBQ0gsQ0FBQztRQUNELFdBQVcsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLFVBQVUsRUFBRSxVQUFVLENBQUMsQ0FBQTtJQUNwRCxDQUFDO0lBRUQsTUFBTSxNQUFNLEdBQUcsQ0FBQyxHQUFHLFdBQVcsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUM1RCxJQUFJLENBQUMsVUFBVSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQ2hELENBQUE7SUFDRCxVQUFVLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUNwQyxPQUFPLE1BQU0sQ0FBQTtBQUNmLENBQUM7QUFFRCxTQUFnQiwyQkFBMkIsQ0FDekMsU0FBMEIsRUFDMUIsU0FBaUI7SUFFakIsT0FBTyxnQ0FBZ0MsQ0FBQyxTQUFTLENBQUMsQ0FBQyxJQUFJLENBQ3JELENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsVUFBVSxLQUFLLFNBQVMsQ0FDcEQsQ0FBQTtBQUNILENBQUMifQ==
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compensateCompleteCartOrderCreatedParticipants = exports.runCompleteCartOrderCreatedParticipants = exports.runCompleteCartValidateParticipants = void 0;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
6
+ const discover_complete_cart_participants_1 = require("../../utils/discover-complete-cart-participants");
7
+ const participantsFor = (hook, container, modules) => modules
8
+ ? (0, discover_complete_cart_participants_1.selectCompleteCartParticipants)(modules, hook)
9
+ : (0, discover_complete_cart_participants_1.listCompleteCartParticipants)(container, hook);
10
+ const logError = (container, message) => {
11
+ try {
12
+ container
13
+ .resolve(utils_1.ContainerRegistrationKeys.LOGGER)
14
+ .error(message);
15
+ }
16
+ catch {
17
+ // Logging must not replace the original workflow outcome.
18
+ }
19
+ };
20
+ /**
21
+ * Cancels (compensates) one participant run by transaction id, also after it
22
+ * finished; the participant workflow must be stored with a retention time.
23
+ * Not `engine.cancel()`: in Medusa 2.19.0 it loads the transaction through a
24
+ * workflow runner without the container, so the participant's compensation
25
+ * cannot resolve its module. Built with the container like `runAsStep` does.
26
+ * Throws the participant's own compensation error, not "Unknown error".
27
+ */
28
+ const cancelParticipant = async (workflowId, transactionId, container, sharedContext) => {
29
+ const workflow = workflows_sdk_1.MedusaWorkflow.getWorkflow(workflowId);
30
+ if (!workflow) {
31
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Workflow "${workflowId}" is not registered.`);
32
+ }
33
+ const { transaction } = await workflow(container).cancel({
34
+ transactionId,
35
+ container,
36
+ context: { ...sharedContext },
37
+ throwOnError: false,
38
+ });
39
+ if (transaction.getState() !== utils_1.TransactionState.REVERTED) {
40
+ const [first] = transaction.getErrors();
41
+ throw (first?.error ??
42
+ new Error(`Workflow "${workflowId}" ended in state "${transaction.getState()}" instead of reverted.`));
43
+ }
44
+ };
45
+ const describeError = (error) => error instanceof Error
46
+ ? error.message
47
+ : typeof error?.message === "string"
48
+ ? error.message
49
+ : String(error);
50
+ /**
51
+ * Runs every `validate` participant in order. No transaction id: validation
52
+ * writes nothing, so there is nothing to compensate. The first error is
53
+ * rethrown unchanged, keeping its MedusaError type for the HTTP status.
54
+ */
55
+ const runCompleteCartValidateParticipants = async (input, context, modules) => {
56
+ const { container, ...sharedContext } = context;
57
+ const participants = participantsFor("validate", container, modules);
58
+ if (!participants.length) {
59
+ return new workflows_sdk_1.StepResponse(undefined);
60
+ }
61
+ const workflowEngine = container.resolve(utils_1.Modules.WORKFLOW_ENGINE);
62
+ for (const participant of participants) {
63
+ // The engine JSON-clones its options, so the container is never passed
64
+ // to run(); the engine uses the shared app container.
65
+ await workflowEngine.run(participant.workflow_id, {
66
+ input,
67
+ context: {
68
+ ...sharedContext,
69
+ preventReleaseEvents: true,
70
+ },
71
+ throwOnError: true,
72
+ });
73
+ }
74
+ return new workflows_sdk_1.StepResponse(undefined);
75
+ };
76
+ exports.runCompleteCartValidateParticipants = runCompleteCartValidateParticipants;
77
+ /**
78
+ * Runs every `orderCreated` participant in order under the complete-cart
79
+ * transaction id. If participant k fails, participants 0..k-1 are cancelled
80
+ * in reverse order; a rollback failure is logged, and the ORIGINAL error is
81
+ * rethrown either way.
82
+ */
83
+ const runCompleteCartOrderCreatedParticipants = async (input, context, modules) => {
84
+ const { container, ...sharedContext } = context;
85
+ const transactionId = context.transactionId;
86
+ if (!transactionId) {
87
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, "Complete-cart orderCreated hook ran without a transaction id.");
88
+ }
89
+ const participants = participantsFor("orderCreated", container, modules);
90
+ const workflowIds = participants.map(({ workflow_id }) => workflow_id);
91
+ if (!participants.length) {
92
+ return new workflows_sdk_1.StepResponse(undefined, {
93
+ transaction_id: transactionId,
94
+ workflow_ids: workflowIds,
95
+ });
96
+ }
97
+ const workflowEngine = container.resolve(utils_1.Modules.WORKFLOW_ENGINE);
98
+ const completed = [];
99
+ for (const participant of participants) {
100
+ try {
101
+ await workflowEngine.run(participant.workflow_id, {
102
+ input,
103
+ transactionId,
104
+ context: {
105
+ ...sharedContext,
106
+ preventReleaseEvents: true,
107
+ },
108
+ throwOnError: true,
109
+ });
110
+ }
111
+ catch (error) {
112
+ for (const done of [...completed].reverse()) {
113
+ try {
114
+ await cancelParticipant(done.workflow_id, transactionId, container, sharedContext);
115
+ }
116
+ catch (rollbackError) {
117
+ logError(container, `Complete-cart orderCreated rollback failed for module "${done.module_key}", workflow "${done.workflow_id}" (cart ${input.cart_id}, order ${input.order_id}, transaction ${transactionId}) after "${participant.workflow_id}" failed: ${describeError(rollbackError)}`);
118
+ }
119
+ }
120
+ throw error;
121
+ }
122
+ completed.push(participant);
123
+ }
124
+ return new workflows_sdk_1.StepResponse(undefined, {
125
+ transaction_id: transactionId,
126
+ workflow_ids: workflowIds,
127
+ });
128
+ };
129
+ exports.runCompleteCartOrderCreatedParticipants = runCompleteCartOrderCreatedParticipants;
130
+ /**
131
+ * Cancels the participants in reverse order. Every participant is attempted;
132
+ * failures are logged and the first one is thrown so the parent compensation
133
+ * stays failed. `undefined` means the invoke already rolled back.
134
+ */
135
+ const compensateCompleteCartOrderCreatedParticipants = async (compensation, context) => {
136
+ if (!compensation) {
137
+ return;
138
+ }
139
+ const { container, ...sharedContext } = context;
140
+ const workflowIds = [...compensation.workflow_ids].reverse();
141
+ if (!workflowIds.length) {
142
+ return;
143
+ }
144
+ let failed = false;
145
+ let firstFailure;
146
+ for (const workflowId of workflowIds) {
147
+ try {
148
+ await cancelParticipant(workflowId, compensation.transaction_id, container, sharedContext);
149
+ }
150
+ catch (error) {
151
+ logError(container, `Complete-cart orderCreated compensation failed for workflow "${workflowId}" (transaction ${compensation.transaction_id}): ${describeError(error)}`);
152
+ if (!failed) {
153
+ failed = true;
154
+ firstFailure = error;
155
+ }
156
+ }
157
+ }
158
+ if (failed) {
159
+ throw firstFailure;
160
+ }
161
+ };
162
+ exports.compensateCompleteCartOrderCreatedParticipants = compensateCompleteCartOrderCreatedParticipants;
163
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcGxldGUtY2FydC1wYXJ0aWNpcGFudHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL2hvb2tzL2NvbXBsZXRlLWNhcnQtcGFydGljaXBhbnRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUlBLHFEQUtrQztBQUNsQyxxRUFBZ0Y7QUFRaEYseUdBSXdEO0FBbUJ4RCxNQUFNLGVBQWUsR0FBRyxDQUN0QixJQUEwQixFQUMxQixTQUEwQixFQUMxQixPQUFnRCxFQUNoQixFQUFFLENBQ2xDLE9BQU87SUFDTCxDQUFDLENBQUMsSUFBQSxvRUFBOEIsRUFBQyxPQUFPLEVBQUUsSUFBSSxDQUFDO0lBQy9DLENBQUMsQ0FBQyxJQUFBLGtFQUE0QixFQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQTtBQUVuRCxNQUFNLFFBQVEsR0FBRyxDQUFDLFNBQTBCLEVBQUUsT0FBZSxFQUFRLEVBQUU7SUFDckUsSUFBSSxDQUFDO1FBQ0gsU0FBUzthQUNOLE9BQU8sQ0FDTixpQ0FBeUIsQ0FBQyxNQUFNLENBQ2pDO2FBQ0EsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO0lBQ25CLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCwwREFBMEQ7SUFDNUQsQ0FBQztBQUNILENBQUMsQ0FBQTtBQWdCRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxpQkFBaUIsR0FBRyxLQUFLLEVBQzdCLFVBQWtCLEVBQ2xCLGFBQXFCLEVBQ3JCLFNBQTBCLEVBQzFCLGFBQXNDLEVBQ3ZCLEVBQUU7SUFDakIsTUFBTSxRQUFRLEdBQ1osOEJBR0QsQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUE7SUFDekIsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ2QsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLFNBQVMsRUFDM0IsYUFBYSxVQUFVLHNCQUFzQixDQUM5QyxDQUFBO0lBQ0gsQ0FBQztJ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
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_flows_1 = require("@medusajs/medusa/core-flows");
4
+ const complete_cart_participants_1 = require("./complete-cart-participants");
5
+ /**
6
+ * The ONLY registrations of these two hooks in the whole shop. Medusa 2.19.0
7
+ * throws "Cannot define multiple hook handlers" for a second handler
8
+ * (workflows-sdk dist/utils/composer/create-hook.js), so plugins declare
9
+ * `completeCartHooks()` on their module service instead and the base runs
10
+ * their workflows. The cart path never reads feature toggles.
11
+ */
12
+ core_flows_1.completeCartWorkflow.hooks.validate((input, context) => (0, complete_cart_participants_1.runCompleteCartValidateParticipants)(input, context));
13
+ // Medusa 2.19.0 creates this hook internally but omits it from complete cart's
14
+ // public declaration. Keep the cast isolated to this version-specific edge.
15
+ const { orderCreated } = core_flows_1.completeCartWorkflow.hooks;
16
+ orderCreated((input, context) => (0, complete_cart_participants_1.runCompleteCartOrderCreatedParticipants)(input, context), complete_cart_participants_1.compensateCompleteCartOrderCreatedParticipants);
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcGxldGUtY2FydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy93b3JrZmxvd3MvaG9va3MvY29tcGxldGUtY2FydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUNBLDREQUFrRTtBQUdsRSw2RUFNcUM7QUFFckM7Ozs7OztHQU1HO0FBRUgsaUNBQW9CLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRSxDQUNyRCxJQUFBLGdFQUFtQyxFQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FDcEQsQ0FBQTtBQWFELCtFQUErRTtBQUMvRSw0RUFBNEU7QUFDNUUsTUFBTSxFQUFFLFlBQVksRUFBRSxHQUFHLGlDQUFvQixDQUFDLEtBRTdDLENBQUE7QUFFRCxZQUFZLENBQ1YsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUUsQ0FBQyxJQUFBLG9FQUF1QyxFQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsRUFDM0UsMkVBQThDLENBQy9DLENBQUEifQ==
package/README.md CHANGED
@@ -16,6 +16,9 @@ helpers that plugins use to enforce their toggles.
16
16
  values to the Admin.
17
17
  - Exports `definePluginFeatures`, `requirePluginFeature` and
18
18
  `isPluginFeatureEnabled` from `@hartl-services/medusa-base` for plugins.
19
+ - Owns the `completeCartWorkflow` hooks `validate` and `orderCreated` and runs
20
+ the workflows that plugins declare for them (see "Complete-cart hook
21
+ composition").
19
22
 
20
23
  ## Installation in a shop
21
24
 
@@ -164,3 +167,89 @@ existing data.
164
167
  - Updates are optimistic: `expected_version` 0 creates the row, any other
165
168
  value updates exactly that version. A concurrent change answers 409; the
166
169
  Admin page reloads and shows a notice.
170
+
171
+ ## Complete-cart hook composition
172
+
173
+ **Why.** Medusa 2.19.0 allows exactly one handler per workflow hook; a second
174
+ registration throws "Cannot define multiple hook handlers" at boot. Several
175
+ plugins need `completeCartWorkflow.hooks.validate` and the (undeclared, but
176
+ existing) `orderCreated` hook, so the base registers both, once
177
+ (`src/workflows/hooks/complete-cart.ts`), and runs the plugins' workflows.
178
+ **A plugin must never register these two hooks itself.**
179
+
180
+ **How a plugin participates.** Its module service implements
181
+ `CompleteCartParticipant` (plain data, `import type` only):
182
+
183
+ ```ts
184
+ import type { CompleteCartHookDeclarations } from "@hartl-services/medusa-base"
185
+
186
+ class MyModuleService extends MedusaService({ /* … */ }) {
187
+ completeCartHooks(): CompleteCartHookDeclarations {
188
+ return {
189
+ validate: { workflow_id: "my-validate-complete-cart", order: 150 },
190
+ orderCreated: { workflow_id: "my-order-created", order: 150 },
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ - A `validate` workflow receives `CompleteCartValidateInput` (`{ input, cart }`)
197
+ and fails the checkout by throwing (a `MedusaError` keeps its type, so
198
+ `NOT_ALLOWED`/`INVALID_DATA` answer 400). It must not write.
199
+ - An `orderCreated` workflow receives `CompleteCartOrderCreatedInput`
200
+ (`{ order_id, cart_id }`) and must be compensable. Create it with
201
+ `store: true` and a `retentionTime` of at least three days (Medusa's own
202
+ `completeCartWorkflow` retention): the base cancels it by transaction id also
203
+ after it finished, and the in-memory workflow engine deletes an unstored
204
+ finished transaction.
205
+ - The base discovers participants like feature descriptors (every module in
206
+ `medusa-config` that is registered in the container). Fail closed: an
207
+ invalid declaration (non-string or blank `workflow_id`, non-finite `order`,
208
+ unknown hook name, the same workflow twice for one hook) is
209
+ `UNEXPECTED_STATE` and blocks checkout, naming the module key.
210
+
211
+ **Ordering.** Ascending `order`, then module key, then workflow id. In use:
212
+ Affiliate 100 (`affiliate-validate-complete-cart`,
213
+ `finalize-affiliate-order-attribution`), tax compliance 200
214
+ (`freeze-tax-compliance-order-evidence`, `orderCreated` only).
215
+
216
+ **Rollback semantics.**
217
+
218
+ - `validate` participants run one after another without their own transaction
219
+ id (they write nothing). The first error stops the chain and is rethrown
220
+ unchanged.
221
+ - `orderCreated` participants run one after another through the workflow
222
+ engine under the complete-cart transaction id, with the parent's event group
223
+ (`preventReleaseEvents`). If participant *k* fails, the base cancels
224
+ participants *k-1 … 0* in reverse order, logs every failed cancellation
225
+ (module key, workflow id, cart, order, transaction), and rethrows the
226
+ original error.
227
+ - When a later complete-cart step fails, the hook's compensation cancels every
228
+ participant in reverse order, attempts all of them, logs each failure and
229
+ then throws the first one, so the complete-cart compensation stays failed
230
+ instead of silently leaving partial state.
231
+ - The base never swallows a participant error and never reads toggles here.
232
+
233
+
234
+ ## Sentry logger wrapper
235
+
236
+ `@hartl-services/medusa-base/sentry-logger` exports `withSentryErrorReporting(logger)`,
237
+ formerly published as `@hartl-services/medusa-sentry-logger`. Medusa's HTTP
238
+ error handler catches thrown route and workflow errors and only logs them, so
239
+ Sentry's default Node integrations never see them; wrapping the logger reports
240
+ every `logger.error(...)` and every `Error` passed to `logger.warn(...)` (at
241
+ level `warning`). It is a plain library import for `medusa-config.ts`, not a
242
+ Medusa plugin feature, and needs `@sentry/node` (an optional peer dependency):
243
+
244
+ ```ts
245
+ import { logger } from "@medusajs/framework/logger";
246
+ import { withSentryErrorReporting } from "@hartl-services/medusa-base/sentry-logger";
247
+
248
+ module.exports = defineConfig({
249
+ logger: withSentryErrorReporting(logger),
250
+ // ...
251
+ });
252
+ ```
253
+
254
+ Initialize Sentry in `instrumentation.ts` as usual; without a `SENTRY_DSN` the
255
+ wrapper is inert.
@@ -0,0 +1,21 @@
1
+ import type { completeCartWorkflow } from "@medusajs/medusa/core-flows";
2
+ /** The two complete-cart hooks the base composes (see README). */
3
+ export type CompleteCartHookName = "validate" | "orderCreated";
4
+ /** One participant workflow, run by id through the workflow engine. */
5
+ export type CompleteCartParticipantStep = {
6
+ readonly workflow_id: string;
7
+ /** Lower runs first; ties sort by module key, then workflow id. */
8
+ readonly order: number;
9
+ };
10
+ export type CompleteCartHookDeclarations = Partial<Record<CompleteCartHookName, CompleteCartParticipantStep>>;
11
+ /** Implemented by a plugin's module service. Plain data only: the base runs the workflows by id. */
12
+ export interface CompleteCartParticipant {
13
+ completeCartHooks(): CompleteCartHookDeclarations;
14
+ }
15
+ /** Input of a `validate` participant workflow: `{ input, cart }`. */
16
+ export type CompleteCartValidateInput = Parameters<Parameters<typeof completeCartWorkflow.hooks.validate>[0]>[0];
17
+ /** Input of an `orderCreated` participant workflow. */
18
+ export type CompleteCartOrderCreatedInput = {
19
+ order_id: string;
20
+ cart_id: string;
21
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -3,3 +3,4 @@ export { definePluginFeatures } from "./descriptor";
3
3
  export { resolveEffectivePluginFeatures } from "./effective-features";
4
4
  export { isPluginFeatureEnabled, requirePluginFeature } from "./enforcement";
5
5
  export type { EffectivePluginFeatures, PluginFeatureDefinition, PluginFeatureDescriptor, PluginFeatureProvider, } from "./types";
6
+ export type { CompleteCartHookDeclarations, CompleteCartHookName, CompleteCartOrderCreatedInput, CompleteCartParticipant, CompleteCartParticipantStep, CompleteCartValidateInput, } from "./complete-cart";
@@ -0,0 +1,11 @@
1
+ import type { Logger } from "@medusajs/framework/types";
2
+ /**
3
+ * Medusa's own HTTP error-handler middleware fully "handles" every thrown
4
+ * route/workflow error via logger.error(...) before it can ever reach an
5
+ * uncaught-exception process hook, so Sentry's default Node integrations
6
+ * never see it. Bridging logger.error(...) itself is the only place that
7
+ * reliably captures every error Medusa's framework already logs internally.
8
+ * Errors that Medusa logs through logger.warn(err) (failing event
9
+ * subscribers) are reported too, at warning level.
10
+ */
11
+ export declare function withSentryErrorReporting(logger: Logger): Logger;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.withSentryErrorReporting = withSentryErrorReporting;
37
+ const Sentry = __importStar(require("@sentry/node"));
38
+ /**
39
+ * Medusa's own HTTP error-handler middleware fully "handles" every thrown
40
+ * route/workflow error via logger.error(...) before it can ever reach an
41
+ * uncaught-exception process hook, so Sentry's default Node integrations
42
+ * never see it. Bridging logger.error(...) itself is the only place that
43
+ * reliably captures every error Medusa's framework already logs internally.
44
+ * Errors that Medusa logs through logger.warn(err) (failing event
45
+ * subscribers) are reported too, at warning level.
46
+ */
47
+ function withSentryErrorReporting(logger) {
48
+ const originalError = logger.error.bind(logger);
49
+ const originalWarn = logger.warn.bind(logger);
50
+ return {
51
+ panic: logger.panic.bind(logger),
52
+ shouldLog: logger.shouldLog.bind(logger),
53
+ setLogLevel: logger.setLogLevel.bind(logger),
54
+ unsetLogLevel: logger.unsetLogLevel.bind(logger),
55
+ activity: logger.activity.bind(logger),
56
+ progress: logger.progress.bind(logger),
57
+ failure: logger.failure.bind(logger),
58
+ success: logger.success.bind(logger),
59
+ silly: logger.silly.bind(logger),
60
+ debug: logger.debug.bind(logger),
61
+ verbose: logger.verbose.bind(logger),
62
+ http: logger.http.bind(logger),
63
+ info: logger.info.bind(logger),
64
+ // Medusa's Redis Event Bus logs a failing subscriber's exception with
65
+ // warn(err), even on the final retry, so an Error passed to warn is a real
66
+ // failure too. Plain warning messages stay out of Sentry.
67
+ warn: (message) => {
68
+ if (message instanceof Error) {
69
+ Sentry.captureException(message, { level: "warning" });
70
+ }
71
+ return originalWarn(message);
72
+ },
73
+ log: logger.log.bind(logger),
74
+ error: (messageOrError, error) => {
75
+ const exception = error ?? (messageOrError instanceof Error ? messageOrError : new Error(messageOrError));
76
+ const loggerMessage = error && typeof messageOrError === "string" ? messageOrError : undefined;
77
+ Sentry.captureException(exception, loggerMessage ? { extra: { loggerMessage } } : undefined);
78
+ return originalError(messageOrError, error);
79
+ },
80
+ };
81
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hartl-services/medusa-base",
3
- "version": "0.1.1",
4
- "description": "Base plugin for Hartl Services Medusa shops: per-plugin feature toggles with a generic Admin settings page, a Store endpoint for storefronts, and enforcement helpers for plugin routes, subscribers and jobs.",
3
+ "version": "0.3.0",
4
+ "description": "Base plugin for Hartl Services Medusa shops: per-plugin feature toggles with a generic Admin settings page, a Store endpoint for storefronts, enforcement helpers, the complete-cart hook composition, and the Sentry logger wrapper.",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  ".medusa/server",
@@ -28,6 +28,11 @@
28
28
  "import": "./dist/public/index.js",
29
29
  "require": "./dist/public/index.js"
30
30
  },
31
+ "./sentry-logger": {
32
+ "types": "./dist/public/sentry-logger.d.ts",
33
+ "import": "./dist/public/sentry-logger.js",
34
+ "require": "./dist/public/sentry-logger.js"
35
+ },
31
36
  "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
32
37
  "./.medusa/server/src/*": "./.medusa/server/src/*",
33
38
  "./admin": {
@@ -41,12 +46,13 @@
41
46
  "@medusajs/js-sdk": "^2.19.0",
42
47
  "@medusajs/medusa": "^2.19.0",
43
48
  "@medusajs/ui": "^4.2.1",
49
+ "@sentry/node": "^10.74.0",
44
50
  "@tanstack/react-query": "^5.64.2",
45
51
  "react": "^18.3.1",
46
52
  "react-router-dom": "^7.18.2"
47
53
  },
48
54
  "dependencies": {
49
- "@hartl-services/medusa-food-supplements-contracts": "^0.10.0"
55
+ "@hartl-services/medusa-food-supplements-contracts": "^0.11.0"
50
56
  },
51
57
  "devDependencies": {
52
58
  "@medusajs/admin-sdk": "2.19.0",
@@ -55,6 +61,7 @@
55
61
  "@medusajs/js-sdk": "2.19.0",
56
62
  "@medusajs/medusa": "2.19.0",
57
63
  "@medusajs/ui": "4.2.1",
64
+ "@sentry/node": "^10.74.0",
58
65
  "@swc/core": "^1.7.28",
59
66
  "@swc/jest": "^0.2.39",
60
67
  "@tanstack/react-query": "5.64.2",
@@ -72,6 +79,11 @@
72
79
  "engines": {
73
80
  "node": ">=20"
74
81
  },
82
+ "peerDependenciesMeta": {
83
+ "@sentry/node": {
84
+ "optional": true
85
+ }
86
+ },
75
87
  "scripts": {
76
88
  "build": "medusa plugin:build && tsc -p tsconfig.public.json",
77
89
  "dev": "medusa plugin:develop",