@hartl-services/medusa-base 0.4.0 → 1.0.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.
Files changed (235) hide show
  1. package/.medusa/server/src/admin/index.js +1517 -87
  2. package/.medusa/server/src/admin/index.mjs +1518 -88
  3. package/.medusa/server/src/api/admin/email/messages/[id]/resends/route.js +24 -0
  4. package/.medusa/server/src/api/admin/email/messages/[id]/route.js +12 -0
  5. package/.medusa/server/src/api/admin/email/messages/route.js +12 -0
  6. package/.medusa/server/src/api/admin/email/terms-versions/[id]/download/route.js +16 -0
  7. package/.medusa/server/src/api/admin/email/terms-versions/route.js +47 -0
  8. package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/download/route.js +21 -0
  9. package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/regenerate/route.js +38 -0
  10. package/.medusa/server/src/api/admin/orders/[id]/documents/regenerate/route.js +32 -0
  11. package/.medusa/server/src/api/admin/orders/[id]/documents/route.js +36 -0
  12. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/download/route.js +24 -0
  13. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/retry/route.js +44 -0
  14. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/route.js +82 -0
  15. package/.medusa/server/src/api/document-download-response.js +16 -0
  16. package/.medusa/server/src/api/document-response.js +23 -0
  17. package/.medusa/server/src/api/files/[asset]/route.js +24 -0
  18. package/.medusa/server/src/api/files/file-response.js +43 -0
  19. package/.medusa/server/src/api/files/gateway-options.js +31 -0
  20. package/.medusa/server/src/api/files/private/[token]/route.js +29 -0
  21. package/.medusa/server/src/api/files/upload/[token]/route.js +66 -0
  22. package/.medusa/server/src/api/middlewares.js +168 -1
  23. package/.medusa/server/src/api/packing-list-response.js +22 -0
  24. package/.medusa/server/src/api/route.js +27 -0
  25. package/.medusa/server/src/api/store/documents/[id]/download/route.js +21 -0
  26. package/.medusa/server/src/api/store/documents/[id]/route.js +15 -0
  27. package/.medusa/server/src/api/store/documents/route.js +11 -0
  28. package/.medusa/server/src/api/terms-download-response.js +12 -0
  29. package/.medusa/server/src/documents/config/index.js +18 -0
  30. package/.medusa/server/src/documents/paths/documents.js +6 -0
  31. package/.medusa/server/src/documents/paths/index.js +18 -0
  32. package/.medusa/server/src/documents/plugin-features.js +24 -0
  33. package/.medusa/server/src/documents/schemas/documents.js +18 -0
  34. package/.medusa/server/src/documents/schemas/index.js +18 -0
  35. package/.medusa/server/src/documents/scripts/verify-documents-file-storage.js +55 -0
  36. package/.medusa/server/src/documents/templates/financial-document.js +142 -0
  37. package/.medusa/server/src/documents/templates/format.js +31 -0
  38. package/.medusa/server/src/documents/templates/html-factory.js +40 -0
  39. package/.medusa/server/src/documents/templates/html-utils.js +13 -0
  40. package/.medusa/server/src/documents/templates/layout.js +97 -0
  41. package/.medusa/server/src/documents/templates/packing-list.js +50 -0
  42. package/.medusa/server/src/documents/types/admin-api.js +3 -0
  43. package/.medusa/server/src/documents/types/document-summary.js +3 -0
  44. package/.medusa/server/src/documents/types/documents.js +3 -0
  45. package/.medusa/server/src/documents/types/html-factory.js +6 -0
  46. package/.medusa/server/src/documents/types/index.js +22 -0
  47. package/.medusa/server/src/documents/types/store-api.js +3 -0
  48. package/.medusa/server/src/documents/utils/document-merchant.js +22 -0
  49. package/.medusa/server/src/documents/utils/html-to-pdf.js +61 -0
  50. package/.medusa/server/src/documents/utils/private-file-storage.js +64 -0
  51. package/.medusa/server/src/documents/utils/resolve-services.js +7 -0
  52. package/.medusa/server/src/email/application/handle-invite-event.js +312 -0
  53. package/.medusa/server/src/email/application/handle-password-reset-event.js +354 -0
  54. package/.medusa/server/src/email/config/index.js +84 -0
  55. package/.medusa/server/src/email/config/notification-module.js +20 -0
  56. package/.medusa/server/src/email/domain/business-keys.js +50 -0
  57. package/.medusa/server/src/email/domain/documents-fact-fingerprint.js +7 -0
  58. package/.medusa/server/src/email/domain/event-schemas.js +36 -0
  59. package/.medusa/server/src/email/domain/failure-classification.js +53 -0
  60. package/.medusa/server/src/email/plugin-features.js +31 -0
  61. package/.medusa/server/src/email/rendering/default-templates.js +91 -0
  62. package/.medusa/server/src/email/rendering/render-email.js +37 -0
  63. package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +48 -0
  64. package/.medusa/server/src/email/types/email-kinds.js +18 -0
  65. package/.medusa/server/src/email/types/index.js +21 -0
  66. package/.medusa/server/src/email/types/shop-configuration.js +3 -0
  67. package/.medusa/server/src/email/types/smtp-configuration.js +3 -0
  68. package/.medusa/server/src/email/types/template-data.js +79 -0
  69. package/.medusa/server/src/files/config/index.js +256 -0
  70. package/.medusa/server/src/files/gateway/index.js +115 -0
  71. package/.medusa/server/src/files/types/index.js +6 -0
  72. package/.medusa/server/src/files/types/public.js +6 -0
  73. package/.medusa/server/src/jobs/document-fact-publication-reconciliation.js +18 -0
  74. package/.medusa/server/src/jobs/document-resolution-reconciliation.js +20 -0
  75. package/.medusa/server/src/jobs/packing-list-reconciliation.js +49 -0
  76. package/.medusa/server/src/jobs/reconcile-email-attempts.js +63 -0
  77. package/.medusa/server/src/jobs/reconcile-pending-email-messages.js +41 -0
  78. package/.medusa/server/src/jobs/retry-email-messages.js +27 -0
  79. package/.medusa/server/src/migration-scripts/dismiss-orphaned-cart-promotion-links.js +26 -0
  80. package/.medusa/server/src/modules/documents/domain/cancellation-snapshot.js +97 -0
  81. package/.medusa/server/src/modules/documents/domain/document-number.js +36 -0
  82. package/.medusa/server/src/modules/documents/domain/document-resolution.js +24 -0
  83. package/.medusa/server/src/modules/documents/domain/issuance-rules.js +37 -0
  84. package/.medusa/server/src/modules/documents/domain/merchant.js +86 -0
  85. package/.medusa/server/src/modules/documents/domain/options.js +40 -0
  86. package/.medusa/server/src/modules/documents/domain/packing-list.js +90 -0
  87. package/.medusa/server/src/modules/documents/domain/snapshot-normalization.js +17 -0
  88. package/.medusa/server/src/modules/documents/domain/tax-breakdown.js +66 -0
  89. package/.medusa/server/src/modules/documents/index.js +15 -0
  90. package/.medusa/server/src/modules/documents/loaders/assert-private-file-storage.js +20 -0
  91. package/.medusa/server/src/modules/documents/migrations/Migration20260827061749.js +75 -0
  92. package/.medusa/server/src/modules/documents/migrations/Migration20260830111207.js +28 -0
  93. package/.medusa/server/src/modules/documents/models/document-artifact.js +25 -0
  94. package/.medusa/server/src/modules/documents/models/document-number-sequence.js +29 -0
  95. package/.medusa/server/src/modules/documents/models/document.js +82 -0
  96. package/.medusa/server/src/modules/documents/models/order-cancellation-resolution.js +51 -0
  97. package/.medusa/server/src/modules/documents/models/packing-list-revision.js +36 -0
  98. package/.medusa/server/src/modules/documents/models/packing-list.js +27 -0
  99. package/.medusa/server/src/modules/documents/models/refund-document-resolution.js +66 -0
  100. package/.medusa/server/src/modules/documents/models/shipment-invoice-assignment.js +51 -0
  101. package/.medusa/server/src/modules/documents/models/shipment-invoice-coordinator.js +44 -0
  102. package/.medusa/server/src/modules/documents/service.js +1701 -0
  103. package/.medusa/server/src/modules/email/domain/attachment.js +25 -0
  104. package/.medusa/server/src/modules/email/domain/message-state.js +69 -0
  105. package/.medusa/server/src/modules/email/index.js +13 -0
  106. package/.medusa/server/src/modules/email/migrations/Migration20260826173422.js +32 -0
  107. package/.medusa/server/src/modules/email/migrations/Migration20260827172945.js +16 -0
  108. package/.medusa/server/src/modules/email/migrations/Migration20260827194804.js +22 -0
  109. package/.medusa/server/src/modules/email/migrations/Migration20260906195205.js +16 -0
  110. package/.medusa/server/src/modules/email/models/email-attempt.js +56 -0
  111. package/.medusa/server/src/modules/email/models/email-message.js +71 -0
  112. package/.medusa/server/src/modules/email/models/email-terms-version.js +37 -0
  113. package/.medusa/server/src/modules/email/service.js +1285 -0
  114. package/.medusa/server/src/providers/s3-cache/cache/atomic-write.js +25 -0
  115. package/.medusa/server/src/providers/s3-cache/cache/cache-manager.js +115 -0
  116. package/.medusa/server/src/providers/s3-cache/cache/keyed-lock.js +28 -0
  117. package/.medusa/server/src/providers/s3-cache/index.js +8 -0
  118. package/.medusa/server/src/providers/s3-cache/object-key.js +53 -0
  119. package/.medusa/server/src/providers/s3-cache/s3-cache-file.js +229 -0
  120. package/.medusa/server/src/providers/s3-cache/s3-object-store.js +86 -0
  121. package/.medusa/server/src/providers/smtp/index.js +8 -0
  122. package/.medusa/server/src/providers/smtp/service.js +236 -0
  123. package/.medusa/server/src/providers/smtp/types.js +11 -0
  124. package/.medusa/server/src/public/config.js +186 -0
  125. package/.medusa/server/src/public/gateway.js +19 -0
  126. package/.medusa/server/src/public/instrumentation.js +56 -0
  127. package/.medusa/server/src/public/shop-config.js +12 -0
  128. package/.medusa/server/src/public/shop-types.js +24 -0
  129. package/.medusa/server/src/shop-config/build-food-shop-config.js +248 -0
  130. package/.medusa/server/src/shop-config/options.js +71 -0
  131. package/.medusa/server/src/subscribers/affiliate-consignment-reminder-due.js +26 -0
  132. package/.medusa/server/src/subscribers/documents-order-canceled.js +24 -0
  133. package/.medusa/server/src/subscribers/documents-order-cancellation-ready.js +26 -0
  134. package/.medusa/server/src/subscribers/documents-refund-ready.js +24 -0
  135. package/.medusa/server/src/subscribers/documents-shipment-created.js +36 -0
  136. package/.medusa/server/src/subscribers/documents-shipment-invoice-assignment-decided.js +26 -0
  137. package/.medusa/server/src/subscribers/email-order-canceled.js +28 -0
  138. package/.medusa/server/src/subscribers/email-shipment-created.js +32 -0
  139. package/.medusa/server/src/subscribers/order-placed.js +27 -0
  140. package/.medusa/server/src/subscribers/packing-list-allocation-revised.js +32 -0
  141. package/.medusa/server/src/subscribers/packing-list-fulfillment-canceled.js +54 -0
  142. package/.medusa/server/src/subscribers/packing-list-fulfillment-created.js +31 -0
  143. package/.medusa/server/src/subscribers/password-reset.js +23 -0
  144. package/.medusa/server/src/subscribers/payment-refunded.js +38 -0
  145. package/.medusa/server/src/subscribers/return-received.js +27 -0
  146. package/.medusa/server/src/subscribers/return-requested.js +27 -0
  147. package/.medusa/server/src/subscribers/user-invite.js +23 -0
  148. package/.medusa/server/src/utils/cart-promotion-links.js +101 -0
  149. package/.medusa/server/src/workflows/documents/issue-cancellation-after-refund.js +246 -0
  150. package/.medusa/server/src/workflows/documents/issue-document.js +163 -0
  151. package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +521 -0
  152. package/.medusa/server/src/workflows/documents/issue-packing-list.js +384 -0
  153. package/.medusa/server/src/workflows/documents/publish-document-facts.js +53 -0
  154. package/.medusa/server/src/workflows/documents/reconcile-document-resolutions.js +48 -0
  155. package/.medusa/server/src/workflows/documents/reconcile-order-cancellation.js +92 -0
  156. package/.medusa/server/src/workflows/documents/regenerate-issued-document.js +61 -0
  157. package/.medusa/server/src/workflows/email/dispatch-email-message.js +299 -0
  158. package/.medusa/server/src/workflows/email/documents-required-facts.js +30 -0
  159. package/.medusa/server/src/workflows/email/prepare-affiliate-reminder-email.js +58 -0
  160. package/.medusa/server/src/workflows/email/prepare-order-canceled-email.js +64 -0
  161. package/.medusa/server/src/workflows/email/prepare-order-received-email.js +56 -0
  162. package/.medusa/server/src/workflows/email/prepare-refund-email.js +47 -0
  163. package/.medusa/server/src/workflows/email/prepare-return-email.js +75 -0
  164. package/.medusa/server/src/workflows/email/prepare-shipment-email.js +82 -0
  165. package/.medusa/server/src/workflows/email/retry-email-messages.js +31 -0
  166. package/.medusa/server/src/workflows/hooks/dismiss-deleted-promotion-cart-links.js +23 -0
  167. package/README.md +1619 -7
  168. package/dist/documents/config/index.d.ts +11 -0
  169. package/dist/documents/config/index.js +17 -0
  170. package/dist/documents/templates/financial-document.d.ts +3 -0
  171. package/dist/documents/templates/financial-document.js +141 -0
  172. package/dist/documents/templates/format.d.ts +16 -0
  173. package/dist/documents/templates/format.js +30 -0
  174. package/dist/documents/templates/html-factory.d.ts +11 -0
  175. package/dist/documents/templates/html-factory.js +39 -0
  176. package/dist/documents/templates/html-utils.d.ts +2 -0
  177. package/dist/documents/templates/html-utils.js +12 -0
  178. package/dist/documents/templates/layout.d.ts +17 -0
  179. package/dist/documents/templates/layout.js +96 -0
  180. package/dist/documents/templates/packing-list.d.ts +3 -0
  181. package/dist/documents/templates/packing-list.js +49 -0
  182. package/dist/documents/types/admin-api.d.ts +1 -0
  183. package/dist/documents/types/admin-api.js +2 -0
  184. package/dist/documents/types/document-summary.d.ts +1 -0
  185. package/dist/documents/types/document-summary.js +2 -0
  186. package/dist/documents/types/documents.d.ts +174 -0
  187. package/dist/documents/types/documents.js +2 -0
  188. package/dist/documents/types/html-factory.d.ts +7 -0
  189. package/dist/documents/types/html-factory.js +5 -0
  190. package/dist/documents/types/index.d.ts +5 -0
  191. package/dist/documents/types/index.js +21 -0
  192. package/dist/documents/types/store-api.d.ts +1 -0
  193. package/dist/documents/types/store-api.js +2 -0
  194. package/dist/email/config/index.d.ts +17 -0
  195. package/dist/email/config/index.js +83 -0
  196. package/dist/email/config/notification-module.d.ts +22 -0
  197. package/dist/email/config/notification-module.js +19 -0
  198. package/dist/email/types/email-kinds.d.ts +15 -0
  199. package/dist/email/types/email-kinds.js +17 -0
  200. package/dist/email/types/index.d.ts +4 -0
  201. package/dist/email/types/index.js +20 -0
  202. package/dist/email/types/shop-configuration.d.ts +43 -0
  203. package/dist/email/types/shop-configuration.js +2 -0
  204. package/dist/email/types/smtp-configuration.d.ts +9 -0
  205. package/dist/email/types/smtp-configuration.js +2 -0
  206. package/dist/email/types/template-data.d.ts +100 -0
  207. package/dist/email/types/template-data.js +78 -0
  208. package/dist/files/config/index.d.ts +32 -0
  209. package/dist/files/config/index.js +255 -0
  210. package/dist/files/gateway/index.d.ts +30 -0
  211. package/dist/files/gateway/index.js +114 -0
  212. package/dist/files/types/index.d.ts +2 -0
  213. package/dist/files/types/index.js +5 -0
  214. package/dist/files/types/public.d.ts +90 -0
  215. package/dist/files/types/public.js +5 -0
  216. package/dist/modules/documents/domain/merchant.d.ts +22 -0
  217. package/dist/modules/documents/domain/merchant.js +85 -0
  218. package/dist/modules/documents/domain/options.d.ts +34 -0
  219. package/dist/modules/documents/domain/options.js +39 -0
  220. package/dist/public/config.d.ts +59 -0
  221. package/dist/public/config.js +185 -0
  222. package/dist/public/gateway.d.ts +2 -0
  223. package/dist/public/gateway.js +18 -0
  224. package/dist/public/instrumentation.d.ts +12 -0
  225. package/dist/public/instrumentation.js +55 -0
  226. package/dist/public/shop-config.d.ts +8 -0
  227. package/dist/public/shop-config.js +11 -0
  228. package/dist/public/shop-types.d.ts +7 -0
  229. package/dist/public/shop-types.js +23 -0
  230. package/dist/public/types.d.ts +17 -1
  231. package/dist/shop-config/build-food-shop-config.d.ts +44 -0
  232. package/dist/shop-config/build-food-shop-config.js +247 -0
  233. package/dist/shop-config/options.d.ts +41 -0
  234. package/dist/shop-config/options.js +70 -0
  235. package/package.json +48 -5
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveExpirationStrategy = exports.resolveS3Credentials = exports.S3_BUILD_PLACEHOLDERS = exports.createS3CacheStorageConfig = exports.createS3CacheFileModuleConfig = exports.validateS3CacheProviderOptions = void 0;
4
+ const PACKAGE_NAME = "medusa-s3-cache-storage";
5
+ const DEFAULT_CACHE = {
6
+ cleanupIntervalMs: 900_000,
7
+ dir: "./.medusa-s3-cache",
8
+ expirationStrategy: "absolute",
9
+ ttlMs: 3_600_000,
10
+ };
11
+ const DEFAULT_PRIVATE_DOWNLOAD_DURATION_SECONDS = 3_600;
12
+ const invalid = (message) => {
13
+ throw new TypeError(`${PACKAGE_NAME}: ${message}`);
14
+ };
15
+ const requireNonBlankString = (value, field) => {
16
+ if (typeof value === "string") {
17
+ const trimmed = value.trim();
18
+ if (trimmed) {
19
+ return trimmed;
20
+ }
21
+ }
22
+ return invalid(`${field} must be a non-blank string`);
23
+ };
24
+ const validateHttpUrl = (value, field) => {
25
+ const url = requireNonBlankString(value, field);
26
+ try {
27
+ const parsed = new URL(url);
28
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
29
+ invalid(`${field} must be an HTTP(S) URL`);
30
+ }
31
+ if (parsed.username || parsed.password || parsed.search || parsed.hash) {
32
+ invalid(`${field} must not include credentials, a query, or a fragment`);
33
+ }
34
+ return parsed.toString().replace(/\/$/u, "");
35
+ }
36
+ catch (error) {
37
+ if (error instanceof TypeError && error.message.startsWith(PACKAGE_NAME)) {
38
+ throw error;
39
+ }
40
+ return invalid(`${field} must be an HTTP(S) URL`);
41
+ }
42
+ };
43
+ const validateSafePrefix = (value, field) => {
44
+ if (value === undefined) {
45
+ return "";
46
+ }
47
+ const prefix = requireNonBlankString(value, field).replaceAll("\\", "/");
48
+ const segments = prefix.split("/");
49
+ if (segments.some((segment) => !segment ||
50
+ segment === "." ||
51
+ segment === ".." ||
52
+ /[\u0000-\u001F\u007F]/u.test(segment))) {
53
+ invalid(`${field} must contain only safe path segments`);
54
+ }
55
+ return segments.join("/");
56
+ };
57
+ const validateFiniteNumber = (value, field, minimum, maximum, defaultValue) => {
58
+ if (value === undefined) {
59
+ return defaultValue;
60
+ }
61
+ if (typeof value !== "number") {
62
+ return invalid(`${field} must be an integer from ${minimum} through ${maximum}`);
63
+ }
64
+ if (!Number.isFinite(value) ||
65
+ !Number.isInteger(value) ||
66
+ value < minimum ||
67
+ value > maximum) {
68
+ return invalid(`${field} must be an integer from ${minimum} through ${maximum}`);
69
+ }
70
+ return value;
71
+ };
72
+ const validateCacheOptions = (cache) => {
73
+ const expirationStrategy = cache?.expirationStrategy ?? DEFAULT_CACHE.expirationStrategy;
74
+ if (expirationStrategy !== "absolute" && expirationStrategy !== "sliding") {
75
+ invalid("cache.expirationStrategy must be 'absolute' or 'sliding'");
76
+ }
77
+ return {
78
+ cleanupIntervalMs: validateFiniteNumber(cache?.cleanupIntervalMs, "cache.cleanupIntervalMs", 1, 86_400_000, DEFAULT_CACHE.cleanupIntervalMs),
79
+ dir: cache?.dir
80
+ ? requireNonBlankString(cache.dir, "cache.dir")
81
+ : DEFAULT_CACHE.dir,
82
+ expirationStrategy,
83
+ ttlMs: validateFiniteNumber(cache?.ttlMs, "cache.ttlMs", 0, 604_800_000, DEFAULT_CACHE.ttlMs),
84
+ };
85
+ };
86
+ const validateCredentials = (credentials) => {
87
+ if (!credentials) {
88
+ return undefined;
89
+ }
90
+ const accessKeyId = credentials.accessKeyId;
91
+ const secretAccessKey = credentials.secretAccessKey;
92
+ if (!accessKeyId && !secretAccessKey && !credentials.sessionToken) {
93
+ invalid("s3.credentials must contain accessKeyId and secretAccessKey");
94
+ }
95
+ return {
96
+ accessKeyId: requireNonBlankString(accessKeyId, "s3.credentials.accessKeyId"),
97
+ secretAccessKey: requireNonBlankString(secretAccessKey, "s3.credentials.secretAccessKey"),
98
+ ...(credentials.sessionToken
99
+ ? {
100
+ sessionToken: requireNonBlankString(credentials.sessionToken, "s3.credentials.sessionToken"),
101
+ }
102
+ : {}),
103
+ };
104
+ };
105
+ const validateGatewayOptions = (gateway) => {
106
+ if (!gateway) {
107
+ return invalid("gateway options are required when enabled is true");
108
+ }
109
+ return {
110
+ baseUrl: validateHttpUrl(gateway.baseUrl, "gateway.baseUrl"),
111
+ privateDownloadDurationSeconds: validateFiniteNumber(gateway.privateDownloadDurationSeconds, "gateway.privateDownloadDurationSeconds", 1, 604_800, DEFAULT_PRIVATE_DOWNLOAD_DURATION_SECONDS),
112
+ signingSecret: requireNonBlankString(gateway.signingSecret, "gateway.signingSecret"),
113
+ };
114
+ };
115
+ const validateS3CacheProviderOptions = (input) => {
116
+ const s3 = input.s3;
117
+ if (!s3) {
118
+ return invalid("s3 options are required when enabled is true");
119
+ }
120
+ if (s3.forcePathStyle !== undefined &&
121
+ typeof s3.forcePathStyle !== "boolean") {
122
+ invalid("s3.forcePathStyle must be a boolean");
123
+ }
124
+ if (s3.endpoint !== undefined) {
125
+ validateHttpUrl(s3.endpoint, "s3.endpoint");
126
+ }
127
+ return {
128
+ cache: validateCacheOptions(input.cache),
129
+ gateway: validateGatewayOptions(input.gateway),
130
+ s3: {
131
+ bucket: requireNonBlankString(s3.bucket, "s3.bucket"),
132
+ credentials: validateCredentials(s3.credentials),
133
+ endpoint: s3.endpoint === undefined
134
+ ? undefined
135
+ : validateHttpUrl(s3.endpoint, "s3.endpoint"),
136
+ forcePathStyle: s3.forcePathStyle ?? false,
137
+ region: requireNonBlankString(s3.region, "s3.region"),
138
+ storagePrefix: validateSafePrefix(s3.storagePrefix, "s3.storagePrefix"),
139
+ },
140
+ };
141
+ };
142
+ exports.validateS3CacheProviderOptions = validateS3CacheProviderOptions;
143
+ const buildLocalProviderOptions = (options) => ({
144
+ ...(options?.backendUrl ? { backend_url: options.backendUrl } : {}),
145
+ ...(options?.privateUploadDir
146
+ ? { private_upload_dir: options.privateUploadDir }
147
+ : {}),
148
+ ...(options?.uploadDir ? { upload_dir: options.uploadDir } : {}),
149
+ });
150
+ const createS3CacheFileModuleConfig = (options = {}) => {
151
+ if (options.enabled !== true) {
152
+ return {
153
+ options: {
154
+ providers: [
155
+ {
156
+ id: "local",
157
+ options: buildLocalProviderOptions(options.local),
158
+ resolve: "@medusajs/medusa/file-local",
159
+ },
160
+ ],
161
+ },
162
+ resolve: "@medusajs/medusa/file",
163
+ };
164
+ }
165
+ return s3FileModule((0, exports.validateS3CacheProviderOptions)(options));
166
+ };
167
+ exports.createS3CacheFileModuleConfig = createS3CacheFileModuleConfig;
168
+ const s3FileModule = (validated) => ({
169
+ options: {
170
+ providers: [
171
+ {
172
+ id: "s3-cache",
173
+ options: {
174
+ cache: validated.cache,
175
+ gateway: validated.gateway,
176
+ s3: validated.s3,
177
+ },
178
+ resolve: "@hartl-services/medusa-base/providers/s3-cache",
179
+ },
180
+ ],
181
+ },
182
+ resolve: "@medusajs/medusa/file",
183
+ });
184
+ /**
185
+ * Validates the options once and returns both parts S3 Cache Storage needs:
186
+ * the File Module with the S3 provider (under `modules`) and the `gateway`
187
+ * part of the base plugin's `files` option that serves its `/files` routes.
188
+ * Both carry the same signing secret, so every URL the provider signs is one
189
+ * the routes accept.
190
+ */
191
+ const createS3CacheStorageConfig = (options) => {
192
+ const validated = (0, exports.validateS3CacheProviderOptions)(options);
193
+ return {
194
+ fileModule: s3FileModule(validated),
195
+ gateway: { signingSecret: validated.gateway.signingSecret },
196
+ };
197
+ };
198
+ exports.createS3CacheStorageConfig = createS3CacheStorageConfig;
199
+ /**
200
+ * `medusa build` loads the configuration without deployment secrets. These
201
+ * placeholders only satisfy the required-variable checks of a build; they
202
+ * never reach a running server.
203
+ */
204
+ exports.S3_BUILD_PLACEHOLDERS = {
205
+ S3_CACHE_STORAGE_BUCKET: "build-bucket",
206
+ S3_CACHE_STORAGE_REGION: "build-placeholder",
207
+ };
208
+ const optionalEnvironment = (environment, name) => {
209
+ const value = environment[name]?.trim();
210
+ return value ? value : undefined;
211
+ };
212
+ /**
213
+ * Static S3 credentials are optional (the AWS credential chain applies
214
+ * otherwise) but must be complete when present. They are never read during
215
+ * a build.
216
+ */
217
+ const resolveS3Credentials = (environment, phase) => {
218
+ if (phase === "build") {
219
+ return undefined;
220
+ }
221
+ const accessKeyId = optionalEnvironment(environment, "S3_CACHE_STORAGE_ACCESS_KEY_ID");
222
+ const secretAccessKey = optionalEnvironment(environment, "S3_CACHE_STORAGE_SECRET_ACCESS_KEY");
223
+ const sessionToken = optionalEnvironment(environment, "S3_CACHE_STORAGE_SESSION_TOKEN");
224
+ // A declared but blank key still counts: it signals intended static
225
+ // credentials, which must then be complete.
226
+ if (!("S3_CACHE_STORAGE_ACCESS_KEY_ID" in environment) &&
227
+ !("S3_CACHE_STORAGE_SECRET_ACCESS_KEY" in environment) &&
228
+ !sessionToken) {
229
+ return undefined;
230
+ }
231
+ const missing = [
232
+ accessKeyId ? undefined : "S3_CACHE_STORAGE_ACCESS_KEY_ID",
233
+ secretAccessKey ? undefined : "S3_CACHE_STORAGE_SECRET_ACCESS_KEY",
234
+ ].filter((name) => name !== undefined);
235
+ if (missing.length > 0) {
236
+ throw new TypeError(`Missing required environment variables: ${missing.join(", ")}.`);
237
+ }
238
+ return {
239
+ accessKeyId: accessKeyId,
240
+ secretAccessKey: secretAccessKey,
241
+ ...(sessionToken ? { sessionToken } : {}),
242
+ };
243
+ };
244
+ exports.resolveS3Credentials = resolveS3Credentials;
245
+ const resolveExpirationStrategy = (environment, defaultValue) => {
246
+ const value = optionalEnvironment(environment, "S3_CACHE_STORAGE_EXPIRATION_STRATEGY");
247
+ if (value === undefined) {
248
+ return defaultValue;
249
+ }
250
+ if (value !== "absolute" && value !== "sliding") {
251
+ throw new TypeError('S3_CACHE_STORAGE_EXPIRATION_STRATEGY must be either "absolute" or "sliding".');
252
+ }
253
+ return value;
254
+ };
255
+ exports.resolveExpirationStrategy = resolveExpirationStrategy;
@@ -0,0 +1,30 @@
1
+ export declare const toPublicFileGatewayUrl: (baseUrl: string, logicalFileKey: string) => string;
2
+ export declare const publicFileGatewayKey: (asset: string) => string;
3
+ export declare const createPrivateFileGatewayUrl: (input: {
4
+ baseUrl: string;
5
+ durationSeconds: number;
6
+ fileKey: string;
7
+ now?: number;
8
+ signingSecret: string;
9
+ }) => string;
10
+ export declare const createFileGatewayUploadUrl: (input: {
11
+ baseUrl: string;
12
+ durationSeconds: number;
13
+ fileKey: string;
14
+ now?: number;
15
+ signingSecret: string;
16
+ }) => string;
17
+ export declare const verifyPrivateFileGatewayToken: (input: {
18
+ expires: number;
19
+ now?: number;
20
+ signature: string;
21
+ signingSecret: string;
22
+ token: string;
23
+ }) => string | undefined;
24
+ export declare const verifyFileGatewayUploadToken: (input: {
25
+ expires: number;
26
+ now?: number;
27
+ signature: string;
28
+ signingSecret: string;
29
+ token: string;
30
+ }) => string | undefined;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyFileGatewayUploadToken = exports.verifyPrivateFileGatewayToken = exports.createFileGatewayUploadUrl = exports.createPrivateFileGatewayUrl = exports.publicFileGatewayKey = exports.toPublicFileGatewayUrl = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const PRIVATE_SIGNATURE_VERSION = "v1";
6
+ const safeLogicalKey = (value) => {
7
+ if (!value ||
8
+ value.startsWith("/") ||
9
+ value.includes("\\") ||
10
+ value
11
+ .split("/")
12
+ .some((segment) => !segment ||
13
+ segment === "." ||
14
+ segment === ".." ||
15
+ /[\u0000-\u001F\u007F]/u.test(segment))) {
16
+ throw new TypeError("File gateway key must be a safe relative path");
17
+ }
18
+ return value;
19
+ };
20
+ const gatewayUrl = (baseUrl, path) => {
21
+ const url = new URL(`${baseUrl.replace(/\/$/u, "")}/`);
22
+ url.pathname = `${url.pathname.replace(/\/$/u, "")}/${path}`;
23
+ return url;
24
+ };
25
+ const signatureFor = (input) => (0, node_crypto_1.createHmac)("sha256", input.secret)
26
+ .update(`${PRIVATE_SIGNATURE_VERSION}\n${input.action}\n${input.token}\n${input.expires}`)
27
+ .digest("base64url");
28
+ const createPrivateCapabilityUrl = (input) => {
29
+ const fileKey = safeLogicalKey(input.fileKey);
30
+ if (input.action === "download" && fileKey.startsWith("public/")) {
31
+ throw new TypeError("Public files must not use private gateway URLs");
32
+ }
33
+ const token = Buffer.from(fileKey, "utf8").toString("base64url");
34
+ const expires = Math.floor((input.now ?? Date.now()) / 1_000) + input.durationSeconds;
35
+ const url = gatewayUrl(input.baseUrl, `${input.path}/${token}`);
36
+ url.searchParams.set("expires", String(expires));
37
+ url.searchParams.set("signature", signatureFor({
38
+ action: input.action,
39
+ expires,
40
+ secret: input.signingSecret,
41
+ token,
42
+ }));
43
+ return url.toString();
44
+ };
45
+ const verifyPrivateCapabilityToken = (input) => {
46
+ if (!Number.isSafeInteger(input.expires) ||
47
+ input.expires < Math.floor((input.now ?? Date.now()) / 1_000)) {
48
+ return undefined;
49
+ }
50
+ const expected = Buffer.from(signatureFor({
51
+ action: input.action,
52
+ expires: input.expires,
53
+ secret: input.signingSecret,
54
+ token: input.token,
55
+ }), "base64url");
56
+ const provided = Buffer.from(input.signature, "base64url");
57
+ if (provided.toString("base64url") !== input.signature ||
58
+ provided.length !== expected.length ||
59
+ !(0, node_crypto_1.timingSafeEqual)(provided, expected)) {
60
+ return undefined;
61
+ }
62
+ try {
63
+ const fileKeyBuffer = Buffer.from(input.token, "base64url");
64
+ if (fileKeyBuffer.toString("base64url") !== input.token) {
65
+ return undefined;
66
+ }
67
+ const fileKey = safeLogicalKey(fileKeyBuffer.toString("utf8"));
68
+ return input.action === "download" && fileKey.startsWith("public/")
69
+ ? undefined
70
+ : fileKey;
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ };
76
+ const toPublicFileGatewayUrl = (baseUrl, logicalFileKey) => {
77
+ const key = safeLogicalKey(logicalFileKey);
78
+ if (!key.startsWith("public/")) {
79
+ throw new TypeError("Only public file keys can have public gateway URLs");
80
+ }
81
+ const asset = key.slice("public/".length);
82
+ if (!asset || asset.includes("/")) {
83
+ throw new TypeError("Public gateway files must have one opaque asset name");
84
+ }
85
+ return gatewayUrl(baseUrl, encodeURIComponent(asset)).toString();
86
+ };
87
+ exports.toPublicFileGatewayUrl = toPublicFileGatewayUrl;
88
+ const publicFileGatewayKey = (asset) => {
89
+ if (!asset || asset.includes("/") || asset.includes("\\")) {
90
+ throw new TypeError("Public gateway asset is invalid");
91
+ }
92
+ return safeLogicalKey(`public/${asset}`);
93
+ };
94
+ exports.publicFileGatewayKey = publicFileGatewayKey;
95
+ const createPrivateFileGatewayUrl = (input) => {
96
+ return createPrivateCapabilityUrl({
97
+ ...input,
98
+ action: "download",
99
+ path: "private",
100
+ });
101
+ };
102
+ exports.createPrivateFileGatewayUrl = createPrivateFileGatewayUrl;
103
+ const createFileGatewayUploadUrl = (input) => createPrivateCapabilityUrl({
104
+ ...input,
105
+ action: "upload",
106
+ path: "upload",
107
+ });
108
+ exports.createFileGatewayUploadUrl = createFileGatewayUploadUrl;
109
+ const verifyPrivateFileGatewayToken = (input) => {
110
+ return verifyPrivateCapabilityToken({ ...input, action: "download" });
111
+ };
112
+ exports.verifyPrivateFileGatewayToken = verifyPrivateFileGatewayToken;
113
+ const verifyFileGatewayUploadToken = (input) => verifyPrivateCapabilityToken({ ...input, action: "upload" });
114
+ exports.verifyFileGatewayUploadToken = verifyFileGatewayUploadToken;
@@ -0,0 +1,2 @@
1
+ export type { CacheExpirationStrategy, FileGatewayOptions, FilesPluginOptions, FileModuleConfiguration, FileModuleProviderConfiguration, LocalFileProviderOptions, S3CacheCredentials, S3CacheFileModuleConfigOptions, S3CacheOptions, S3CacheOptionsCache, S3CacheStorageConfiguration, ValidatedS3CacheCredentials, ValidatedFileGatewayOptions, ValidatedS3CacheOptions, ValidatedS3CacheProviderOptions, } from "./public";
2
+ export { BASE_PLUGIN_RESOLVE } from "./public";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASE_PLUGIN_RESOLVE = void 0;
4
+ var public_1 = require("./public");
5
+ Object.defineProperty(exports, "BASE_PLUGIN_RESOLVE", { enumerable: true, get: function () { return public_1.BASE_PLUGIN_RESOLVE; } });
@@ -0,0 +1,90 @@
1
+ export type CacheExpirationStrategy = "absolute" | "sliding";
2
+ export interface S3CacheCredentials {
3
+ accessKeyId?: string;
4
+ secretAccessKey?: string;
5
+ sessionToken?: string;
6
+ }
7
+ export interface S3CacheOptions {
8
+ bucket?: string;
9
+ credentials?: S3CacheCredentials;
10
+ endpoint?: string;
11
+ forcePathStyle?: boolean;
12
+ region?: string;
13
+ storagePrefix?: string;
14
+ }
15
+ export interface FileGatewayOptions {
16
+ baseUrl?: string;
17
+ privateDownloadDurationSeconds?: number;
18
+ signingSecret?: string;
19
+ }
20
+ export interface S3CacheOptionsCache {
21
+ cleanupIntervalMs?: number;
22
+ dir?: string;
23
+ expirationStrategy?: CacheExpirationStrategy;
24
+ ttlMs?: number;
25
+ }
26
+ export interface LocalFileProviderOptions {
27
+ backendUrl?: string;
28
+ privateUploadDir?: string;
29
+ uploadDir?: string;
30
+ }
31
+ export interface S3CacheFileModuleConfigOptions {
32
+ cache?: S3CacheOptionsCache;
33
+ enabled?: boolean;
34
+ gateway?: FileGatewayOptions;
35
+ local?: LocalFileProviderOptions;
36
+ s3?: S3CacheOptions;
37
+ }
38
+ export interface FileModuleProviderConfiguration {
39
+ id: string;
40
+ options: Record<string, unknown>;
41
+ resolve: string;
42
+ }
43
+ export interface FileModuleConfiguration {
44
+ options: {
45
+ providers: FileModuleProviderConfiguration[];
46
+ };
47
+ resolve: "@medusajs/medusa/file";
48
+ }
49
+ export interface ValidatedS3CacheCredentials {
50
+ accessKeyId: string;
51
+ secretAccessKey: string;
52
+ sessionToken?: string;
53
+ }
54
+ export interface ValidatedS3CacheOptions {
55
+ bucket: string;
56
+ credentials?: ValidatedS3CacheCredentials;
57
+ endpoint?: string;
58
+ forcePathStyle: boolean;
59
+ region: string;
60
+ storagePrefix: string;
61
+ }
62
+ export interface ValidatedFileGatewayOptions {
63
+ baseUrl: string;
64
+ privateDownloadDurationSeconds: number;
65
+ signingSecret: string;
66
+ }
67
+ export interface ValidatedS3CacheProviderOptions {
68
+ cache: Required<S3CacheOptionsCache>;
69
+ gateway: ValidatedFileGatewayOptions;
70
+ s3: ValidatedS3CacheOptions;
71
+ }
72
+ /** Package name under which the gateway routes read the `files` options. */
73
+ export declare const BASE_PLUGIN_RESOLVE = "@hartl-services/medusa-base";
74
+ /**
75
+ * The `files` namespace of the base plugin options: what the `/files` gateway
76
+ * routes sign and verify with. `createS3CacheStorageConfig` returns it from the
77
+ * same validated input as the File Module provider, so every URL the provider
78
+ * signs is one the routes accept.
79
+ */
80
+ export type FilesPluginOptions = {
81
+ gateway: {
82
+ signingSecret: string;
83
+ };
84
+ };
85
+ export interface S3CacheStorageConfiguration {
86
+ /** Register under `modules`: the File Module with this S3 provider. */
87
+ fileModule: FileModuleConfiguration;
88
+ /** Set as `files.gateway` in the options of the base plugin. */
89
+ gateway: FilesPluginOptions["gateway"];
90
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASE_PLUGIN_RESOLVE = void 0;
4
+ /** Package name under which the gateway routes read the `files` options. */
5
+ exports.BASE_PLUGIN_RESOLVE = "@hartl-services/medusa-base";
@@ -0,0 +1,22 @@
1
+ import type { DocumentMerchant, DocumentMerchantOption } from "../../../documents/types/documents";
2
+ /** Shop master data keys (base plugin, `plugin_key: "shop"`) documents read. */
3
+ export type DocumentMerchantKey = keyof DocumentMerchant;
4
+ export type DocumentMerchantValues = Partial<Record<DocumentMerchantKey, string>>;
5
+ /** Without these a financial document or packing list has no legal sender. */
6
+ export declare const DOCUMENT_MERCHANT_REQUIRED_FIELDS: readonly ["legal_name", "address_line1", "postal_code", "city", "country_code"];
7
+ /** Development placeholder UID: never printed on a document. */
8
+ export declare const MERCHANT_VAT_PLACEHOLDER = "ATU00000000";
9
+ /**
10
+ * The plugin's `merchant` option as a per-field fallback for the shop master
11
+ * data. Empty values are left out so they never shadow a declared default.
12
+ */
13
+ export declare const merchantOptionToFallback: (option: DocumentMerchantOption | undefined) => DocumentMerchantValues;
14
+ export type DocumentMerchantResolution = {
15
+ merchant: DocumentMerchant;
16
+ missing: [];
17
+ } | {
18
+ merchant: null;
19
+ missing: DocumentMerchantKey[];
20
+ };
21
+ /** Maps resolved shop master data to the render input's `merchant`. */
22
+ export declare const toDocumentMerchant: (values: DocumentMerchantValues) => DocumentMerchantResolution;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toDocumentMerchant = exports.merchantOptionToFallback = exports.MERCHANT_VAT_PLACEHOLDER = exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS = void 0;
4
+ /** Without these a financial document or packing list has no legal sender. */
5
+ exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS = [
6
+ "legal_name",
7
+ "address_line1",
8
+ "postal_code",
9
+ "city",
10
+ "country_code",
11
+ ];
12
+ /** Development placeholder UID: never printed on a document. */
13
+ exports.MERCHANT_VAT_PLACEHOLDER = "ATU00000000";
14
+ const text = (value) => {
15
+ if (typeof value !== "string") {
16
+ return null;
17
+ }
18
+ const trimmed = value.trim();
19
+ return trimmed ? trimmed : null;
20
+ };
21
+ const vatNumber = (value) => {
22
+ const normalized = text(value);
23
+ return normalized &&
24
+ normalized.replace(/\s+/g, "").toUpperCase() === exports.MERCHANT_VAT_PLACEHOLDER
25
+ ? null
26
+ : normalized;
27
+ };
28
+ /**
29
+ * The plugin's `merchant` option as a per-field fallback for the shop master
30
+ * data. Empty values are left out so they never shadow a declared default.
31
+ */
32
+ const merchantOptionToFallback = (option) => {
33
+ if (!option) {
34
+ return {};
35
+ }
36
+ const address = option.registeredAddress;
37
+ const entries = [
38
+ ["legal_name", option.legalName],
39
+ ["address_line1", address?.address1],
40
+ ["address_line2", address?.address2],
41
+ ["postal_code", address?.postalCode],
42
+ ["city", address?.city],
43
+ [
44
+ "country_code",
45
+ text(address?.countryCode ?? option.countryCode)?.toLowerCase(),
46
+ ],
47
+ ["vat_number", option.vatNumber],
48
+ ];
49
+ const fallback = {};
50
+ for (const [key, value] of entries) {
51
+ const normalized = text(value);
52
+ if (normalized) {
53
+ fallback[key] = normalized;
54
+ }
55
+ }
56
+ return fallback;
57
+ };
58
+ exports.merchantOptionToFallback = merchantOptionToFallback;
59
+ /** Maps resolved shop master data to the render input's `merchant`. */
60
+ const toDocumentMerchant = (values) => {
61
+ const missing = exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS.filter((key) => !text(values[key]));
62
+ if (missing.length > 0) {
63
+ return { merchant: null, missing };
64
+ }
65
+ return {
66
+ merchant: {
67
+ legal_name: text(values.legal_name),
68
+ address_line1: text(values.address_line1),
69
+ address_line2: text(values.address_line2),
70
+ postal_code: text(values.postal_code),
71
+ city: text(values.city),
72
+ country_code: text(values.country_code).toLowerCase(),
73
+ vat_number: vatNumber(values.vat_number),
74
+ email: text(values.email),
75
+ phone: text(values.phone),
76
+ website: text(values.website),
77
+ iban: text(values.iban),
78
+ bic: text(values.bic),
79
+ company_register_number: text(values.company_register_number),
80
+ company_register_court: text(values.company_register_court),
81
+ },
82
+ missing: [],
83
+ };
84
+ };
85
+ exports.toDocumentMerchant = toDocumentMerchant;
@@ -0,0 +1,34 @@
1
+ import type { DocumentHtmlFactory } from "../../../documents/types/html-factory";
2
+ import type { DocumentMerchantOption, DocumentPdfOptions } from "../../../documents/types/documents";
3
+ import { type DocumentMerchantValues } from "./merchant";
4
+ /** Used when the `documents.numbering.timeZone` option is not given. */
5
+ export declare const DEFAULT_NUMBERING_TIME_ZONE = "Europe/Vienna";
6
+ /** The `documents` namespace of the base plugin options. */
7
+ export type DocumentPluginOptions = {
8
+ /**
9
+ * Replaces the built-in German document markup. Omit it to use
10
+ * `createDefaultDocumentHtmlFactory` with the `numbering.timeZone`.
11
+ */
12
+ htmlFactory?: DocumentHtmlFactory;
13
+ /**
14
+ * Fallback for the shop master data of the base plugin: used per field
15
+ * only where no value is stored there.
16
+ */
17
+ merchant?: DocumentMerchantOption;
18
+ numbering?: {
19
+ timeZone?: string;
20
+ };
21
+ pdf?: DocumentPdfOptions;
22
+ };
23
+ export type ResolvedDocumentPluginOptions = {
24
+ htmlFactory: DocumentHtmlFactory;
25
+ /** The `merchant` option keyed by shop master data field. */
26
+ merchantFallback: DocumentMerchantValues;
27
+ numbering: {
28
+ timeZone: string;
29
+ };
30
+ pdf: {
31
+ executablePath: string;
32
+ };
33
+ };
34
+ export declare const resolveDocumentPluginOptions: (options: DocumentPluginOptions | undefined) => ResolvedDocumentPluginOptions;