@openlup/platform 0.27.0 → 0.29.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 (232) hide show
  1. package/CHANGELOG.md +386 -127
  2. package/README.md +251 -9
  3. package/dist/account-support/accountReadFacade.d.ts +8 -0
  4. package/dist/account-support/accountReadFacade.js +10 -0
  5. package/dist/account-support/accountReadModels.d.ts +69 -0
  6. package/dist/account-support/accountReadModels.js +272 -0
  7. package/dist/account-support/bindings.d.ts +23 -0
  8. package/dist/account-support/bindings.js +16 -0
  9. package/dist/account-support/contracts/account.d.ts +281 -0
  10. package/dist/account-support/contracts/account.js +1 -0
  11. package/dist/account-support/contracts/capabilities.d.ts +44 -0
  12. package/dist/account-support/contracts/capabilities.js +1 -0
  13. package/dist/account-support/contracts/commands.d.ts +307 -0
  14. package/dist/account-support/contracts/commands.js +1 -0
  15. package/dist/account-support/contracts/customer360.d.ts +154 -0
  16. package/dist/account-support/contracts/customer360.js +1 -0
  17. package/dist/account-support/contracts/index.d.ts +10 -0
  18. package/dist/account-support/contracts/index.js +1 -0
  19. package/dist/account-support/contracts/invoice.d.ts +79 -0
  20. package/dist/account-support/contracts/invoice.js +1 -0
  21. package/dist/account-support/contracts/operator.d.ts +289 -0
  22. package/dist/account-support/contracts/operator.js +1 -0
  23. package/dist/account-support/contribution.d.ts +24 -0
  24. package/dist/account-support/contribution.js +22 -0
  25. package/dist/account-support/customer360.d.ts +106 -0
  26. package/dist/account-support/customer360.js +163 -0
  27. package/dist/account-support/customerAccountReadDiagnostics.d.ts +11 -0
  28. package/dist/account-support/customerAccountReadDiagnostics.js +71 -0
  29. package/dist/account-support/index.d.ts +26 -0
  30. package/dist/account-support/index.js +15 -0
  31. package/dist/account-support/invoiceAccess.d.ts +57 -0
  32. package/dist/account-support/invoiceAccess.js +40 -0
  33. package/dist/account-support/migrations.d.ts +14 -0
  34. package/dist/account-support/migrations.js +7 -0
  35. package/dist/account-support/operatorAuthority.d.ts +67 -0
  36. package/dist/account-support/operatorAuthority.js +379 -0
  37. package/dist/account-support/operatorRecovery.d.ts +66 -0
  38. package/dist/account-support/operatorRecovery.js +129 -0
  39. package/dist/account-support/orderHistory.d.ts +50 -0
  40. package/dist/account-support/orderHistory.js +188 -0
  41. package/dist/account-support/paymentMethodComposition.d.ts +5 -0
  42. package/dist/account-support/paymentMethodComposition.js +4 -0
  43. package/dist/account-support/postgresOperatorAuthority.d.ts +16 -0
  44. package/dist/account-support/postgresOperatorAuthority.js +93 -0
  45. package/dist/account-support/selfService.d.ts +6 -0
  46. package/dist/account-support/selfService.js +81 -0
  47. package/dist/account-support/subscriptionCommandCodec.d.ts +10 -0
  48. package/dist/account-support/subscriptionCommandCodec.js +38 -0
  49. package/dist/catalog/draftSchemas.d.ts +1 -1
  50. package/dist/catalog/foundationSchemas.d.ts +3 -3
  51. package/dist/catalog/resolvedSkuProjection.d.ts +2 -2
  52. package/dist/catalog/sellableSchemas.d.ts +2 -2
  53. package/dist/commerce/checkoutCommandSchemas.d.ts +2 -2
  54. package/dist/commerce/checkoutResumeSchemas.d.ts +6 -6
  55. package/dist/commerce/checkoutSchemas.d.ts +12 -12
  56. package/dist/commerce/commerceReturnsPort.d.ts +2 -2
  57. package/dist/commerce/configuratorIntentSchemas.d.ts +2 -2
  58. package/dist/commerce/contractPrimitives.d.ts +8 -8
  59. package/dist/commerce/customerDefaultsSnapshotSchemas.d.ts +4 -4
  60. package/dist/commerce/model/omsReadModelHelpers.d.ts +4 -4
  61. package/dist/commerce/offerAvailabilitySchemas.d.ts +1 -1
  62. package/dist/commerce/oms/omsContractBase.d.ts +15 -15
  63. package/dist/commerce/oms/omsContractReadAuxSchemas.d.ts +11 -11
  64. package/dist/commerce/oms/omsContractReadSchemas.d.ts +121 -121
  65. package/dist/commerce/oms/omsContractRequests.d.ts +13 -13
  66. package/dist/commerce/oms/omsControlPlane.d.ts +22 -22
  67. package/dist/commerce/oms/omsFulfillmentContractSchemas.d.ts +12 -12
  68. package/dist/commerce/oms/omsFulfillmentDebugSchemas.d.ts +12 -12
  69. package/dist/commerce/orderReviewContracts.d.ts +33 -33
  70. package/dist/commerce/outboxEventSchemas.d.ts +4 -4
  71. package/dist/commerce/schemas.d.ts +21 -21
  72. package/dist/commerce/server/orderReviewHandlers.d.ts +10 -10
  73. package/dist/communications/consentPurposeRegistry.d.ts +1 -1
  74. package/dist/communications/newsletterIntegrationContracts.d.ts +8 -8
  75. package/dist/composition/accountSupportContribution.d.ts +18 -0
  76. package/dist/composition/accountSupportContribution.js +21 -0
  77. package/dist/composition/index.d.ts +2 -0
  78. package/dist/composition/index.js +2 -0
  79. package/dist/composition/marketingContribution.d.ts +21 -0
  80. package/dist/composition/marketingContribution.js +29 -0
  81. package/dist/fulfillment/handlerSchemas.d.ts +40 -40
  82. package/dist/identity/customerContracts.d.ts +2 -2
  83. package/dist/invoicing/snapshot.d.ts +1 -1
  84. package/dist/marketing/acquisitionCaseSchemas.d.ts +103 -0
  85. package/dist/marketing/acquisitionCaseSchemas.js +53 -0
  86. package/dist/marketing/acquisitionEvidenceSchemas.d.ts +105 -0
  87. package/dist/marketing/acquisitionEvidenceSchemas.js +55 -0
  88. package/dist/marketing/analytics/dataLayer.d.ts +18 -0
  89. package/dist/marketing/analytics/dataLayer.js +111 -0
  90. package/dist/marketing/analytics/noop.d.ts +9 -0
  91. package/dist/marketing/analytics/noop.js +8 -0
  92. package/dist/marketing/analytics/purchaseFacts.d.ts +4 -0
  93. package/dist/marketing/analytics/purchaseFacts.js +69 -0
  94. package/dist/marketing/analytics/routePolicy.d.ts +10 -0
  95. package/dist/marketing/analytics/routePolicy.js +59 -0
  96. package/dist/marketing/consentEvidence.d.ts +8 -0
  97. package/dist/marketing/consentEvidence.js +32 -0
  98. package/dist/marketing/contactLegal.d.ts +14 -0
  99. package/dist/marketing/contactLegal.js +19 -0
  100. package/dist/marketing/contracts/acquisition.d.ts +88 -0
  101. package/dist/marketing/contracts/acquisition.js +1 -0
  102. package/dist/marketing/contracts/acquisitionPorts.d.ts +49 -0
  103. package/dist/marketing/contracts/acquisitionPorts.js +1 -0
  104. package/dist/marketing/contracts/analytics.d.ts +144 -0
  105. package/dist/marketing/contracts/analytics.js +1 -0
  106. package/dist/marketing/contracts/contactLegal.d.ts +53 -0
  107. package/dist/marketing/contracts/contactLegal.js +1 -0
  108. package/dist/marketing/contracts/index.d.ts +6 -0
  109. package/dist/marketing/contracts/index.js +1 -0
  110. package/dist/marketing/contracts/prelaunch.d.ts +39 -0
  111. package/dist/marketing/contracts/prelaunch.js +1 -0
  112. package/dist/marketing/index.d.ts +16 -0
  113. package/dist/marketing/index.js +14 -0
  114. package/dist/marketing/prelaunchAcquisitionSchemas.d.ts +116 -0
  115. package/dist/marketing/prelaunchAcquisitionSchemas.js +51 -0
  116. package/dist/marketing/seo/jsonLd.d.ts +157 -0
  117. package/dist/marketing/seo/jsonLd.js +127 -0
  118. package/dist/marketing/seo/metadata.d.ts +41 -0
  119. package/dist/marketing/seo/metadata.js +36 -0
  120. package/dist/marketing/seo/serializeJsonLd.d.ts +1 -0
  121. package/dist/marketing/seo/serializeJsonLd.js +6 -0
  122. package/dist/marketing/seo/urls.d.ts +14 -0
  123. package/dist/marketing/seo/urls.js +23 -0
  124. package/dist/marketing/server/acquisitionCaseDirect.d.ts +4 -0
  125. package/dist/marketing/server/acquisitionCaseDirect.js +88 -0
  126. package/dist/marketing/server/acquisitionCaseHandlers.d.ts +28 -0
  127. package/dist/marketing/server/acquisitionCaseHandlers.js +40 -0
  128. package/dist/marketing/server/acquisitionCasePorts.d.ts +42 -0
  129. package/dist/marketing/server/acquisitionCasePorts.js +1 -0
  130. package/dist/marketing/server/acquisitionEvidenceDirect.d.ts +5 -0
  131. package/dist/marketing/server/acquisitionEvidenceDirect.js +100 -0
  132. package/dist/marketing/server/analyticsContext.d.ts +3 -0
  133. package/dist/marketing/server/analyticsContext.js +65 -0
  134. package/dist/marketing/server/analyticsContextPorts.d.ts +31 -0
  135. package/dist/marketing/server/analyticsContextPorts.js +1 -0
  136. package/dist/marketing/server/bindings.d.ts +14 -0
  137. package/dist/marketing/server/bindings.js +18 -0
  138. package/dist/marketing/server/directPorts.d.ts +26 -0
  139. package/dist/marketing/server/directPorts.js +1 -0
  140. package/dist/marketing/server/index.d.ts +14 -0
  141. package/dist/marketing/server/index.js +11 -0
  142. package/dist/marketing/server/postgres/acquisitionCaseRoutines.d.ts +10 -0
  143. package/dist/marketing/server/postgres/acquisitionCaseRoutines.js +204 -0
  144. package/dist/marketing/server/postgres/acquisitionEvidenceRoutines.d.ts +10 -0
  145. package/dist/marketing/server/postgres/acquisitionEvidenceRoutines.js +107 -0
  146. package/dist/marketing/server/postgres/index.d.ts +3 -0
  147. package/dist/marketing/server/postgres/index.js +3 -0
  148. package/dist/marketing/server/prelaunchAcquisitionDirect.d.ts +4 -0
  149. package/dist/marketing/server/prelaunchAcquisitionDirect.js +122 -0
  150. package/dist/marketing/server/privacyContributor.d.ts +66 -0
  151. package/dist/marketing/server/privacyContributor.js +87 -0
  152. package/dist/marketing/server/research/adminSurveyResponsesHandler.d.ts +9 -0
  153. package/dist/marketing/server/research/adminSurveyResponsesHandler.js +53 -0
  154. package/dist/marketing/server/research/managedSurveyResponseSubmit.d.ts +39 -0
  155. package/dist/marketing/server/research/managedSurveyResponseSubmit.js +102 -0
  156. package/dist/marketing/server/research/surveyResponseSubmitHandler.d.ts +8 -0
  157. package/dist/marketing/server/research/surveyResponseSubmitHandler.js +41 -0
  158. package/dist/marketing/validation.d.ts +2 -0
  159. package/dist/marketing/validation.js +3 -0
  160. package/dist/pricing/acquisitionQuoteBinding.d.ts +1 -1
  161. package/dist/pricing/catalogFactsSchemas.d.ts +1 -1
  162. package/dist/pricing/quotePromotionEvidence.d.ts +1 -1
  163. package/dist/pricing/quoteSchemas.d.ts +56 -56
  164. package/dist/promotions/adminPromotionCodesContracts.d.ts +30 -30
  165. package/dist/promotions/adminPromotionsContracts.d.ts +14 -14
  166. package/package.json +32 -2
  167. package/release-gates.json +99 -4
  168. package/sql/20261026120000_account_support_operator_authority.sql +693 -0
  169. package/src/account-support/accountReadFacade.ts +15 -0
  170. package/src/account-support/accountReadModels.ts +371 -0
  171. package/src/account-support/bindings.ts +28 -0
  172. package/src/account-support/contracts/account.ts +306 -0
  173. package/src/account-support/contracts/capabilities.ts +30 -0
  174. package/src/account-support/contracts/commands.ts +243 -0
  175. package/src/account-support/contracts/customer360.ts +150 -0
  176. package/src/account-support/contracts/index.ts +141 -0
  177. package/src/account-support/contracts/invoice.ts +44 -0
  178. package/src/account-support/contracts/operator.ts +123 -0
  179. package/src/account-support/contribution.ts +35 -0
  180. package/src/account-support/customer360.ts +268 -0
  181. package/src/account-support/customerAccountReadDiagnostics.ts +83 -0
  182. package/src/account-support/index.ts +72 -0
  183. package/src/account-support/invoiceAccess.ts +63 -0
  184. package/src/account-support/migrations.ts +7 -0
  185. package/src/account-support/operatorAuthority.ts +393 -0
  186. package/src/account-support/operatorRecovery.ts +136 -0
  187. package/src/account-support/orderHistory.ts +309 -0
  188. package/src/account-support/paymentMethodComposition.ts +8 -0
  189. package/src/account-support/postgresOperatorAuthority.ts +92 -0
  190. package/src/account-support/selfService.ts +83 -0
  191. package/src/account-support/subscriptionCommandCodec.ts +48 -0
  192. package/src/composition/accountSupportContribution.ts +28 -0
  193. package/src/composition/index.ts +2 -0
  194. package/src/composition/marketingContribution.ts +37 -0
  195. package/src/marketing/acquisitionCaseSchemas.ts +75 -0
  196. package/src/marketing/acquisitionEvidenceSchemas.ts +72 -0
  197. package/src/marketing/analytics/dataLayer.ts +100 -0
  198. package/src/marketing/analytics/noop.ts +14 -0
  199. package/src/marketing/analytics/purchaseFacts.ts +63 -0
  200. package/src/marketing/analytics/routePolicy.ts +55 -0
  201. package/src/marketing/consentEvidence.ts +36 -0
  202. package/src/marketing/contactLegal.ts +22 -0
  203. package/src/marketing/contracts/acquisition.ts +50 -0
  204. package/src/marketing/contracts/acquisitionPorts.ts +48 -0
  205. package/src/marketing/contracts/analytics.ts +88 -0
  206. package/src/marketing/contracts/contactLegal.ts +41 -0
  207. package/src/marketing/contracts/index.ts +17 -0
  208. package/src/marketing/contracts/prelaunch.ts +15 -0
  209. package/src/marketing/index.ts +16 -0
  210. package/src/marketing/prelaunchAcquisitionSchemas.ts +64 -0
  211. package/src/marketing/seo/jsonLd.ts +153 -0
  212. package/src/marketing/seo/metadata.ts +62 -0
  213. package/src/marketing/seo/serializeJsonLd.ts +6 -0
  214. package/src/marketing/seo/urls.ts +34 -0
  215. package/src/marketing/server/acquisitionCaseDirect.ts +67 -0
  216. package/src/marketing/server/acquisitionCaseHandlers.ts +57 -0
  217. package/src/marketing/server/acquisitionCasePorts.ts +42 -0
  218. package/src/marketing/server/acquisitionEvidenceDirect.ts +76 -0
  219. package/src/marketing/server/analyticsContext.ts +55 -0
  220. package/src/marketing/server/analyticsContextPorts.ts +28 -0
  221. package/src/marketing/server/bindings.ts +27 -0
  222. package/src/marketing/server/directPorts.ts +27 -0
  223. package/src/marketing/server/index.ts +14 -0
  224. package/src/marketing/server/postgres/acquisitionCaseRoutines.ts +218 -0
  225. package/src/marketing/server/postgres/acquisitionEvidenceRoutines.ts +103 -0
  226. package/src/marketing/server/postgres/index.ts +3 -0
  227. package/src/marketing/server/prelaunchAcquisitionDirect.ts +121 -0
  228. package/src/marketing/server/privacyContributor.ts +123 -0
  229. package/src/marketing/server/research/adminSurveyResponsesHandler.ts +78 -0
  230. package/src/marketing/server/research/managedSurveyResponseSubmit.ts +158 -0
  231. package/src/marketing/server/research/surveyResponseSubmitHandler.ts +57 -0
  232. package/src/marketing/validation.ts +3 -0
@@ -0,0 +1,31 @@
1
+ import type { HttpRequest } from "../../http/index.js";
2
+ import type { BffResponder } from "../../bff/index.js";
3
+ /** Host persistence rejects on failure; only literal true means an active context. */
4
+ export interface AnalyticsContextPersistencePort {
5
+ syncContext(input: {
6
+ tokenHash: string;
7
+ granted: boolean;
8
+ clientId: string | null;
9
+ }): Promise<unknown>;
10
+ enrollSubscriptions(input: {
11
+ subject: string;
12
+ contextHash: string;
13
+ }): Promise<unknown>;
14
+ }
15
+ export type AnalyticsContextStoreResolver = () => AnalyticsContextPersistencePort | null;
16
+ /** The host supplies opaque 32-byte hexadecimal tokens and their cryptographic hash. */
17
+ export interface AnalyticsContextCapabilityCrypto {
18
+ mintToken(): string;
19
+ hashToken(token: string): string;
20
+ }
21
+ export interface AnalyticsContextHandlerDeps {
22
+ respond: BffResponder;
23
+ isEnabled: () => boolean;
24
+ resolveStore: AnalyticsContextStoreResolver;
25
+ readAccessToken: (req: HttpRequest) => string | null;
26
+ resolveTrustedSubject: (accessToken: string) => Promise<string | null>;
27
+ capabilityCrypto: AnalyticsContextCapabilityCrypto;
28
+ cookieName: string;
29
+ /** Historical request field name supplied by the host. */
30
+ clientIdField: string;
31
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { ReadinessIssue } from "@openlup/core/readiness";
2
+ import { type MarketingPrivacyContributor, type MarketingPrivacyDependencies } from "./privacyContributor.js";
3
+ /** Select one fully bound privacy contributor; absent activation leaves the host registry unchanged. */
4
+ export declare function createMarketingPrivacyBindings<Context>(input: MarketingPrivacyDependencies<Context> & {
5
+ enabled?: boolean;
6
+ }): {
7
+ contributor: MarketingPrivacyContributor<Context> | null;
8
+ issues: ReadinessIssue[];
9
+ callables: Readonly<{
10
+ exportPersonalData: MarketingPrivacyContributor<Context>["export"] | null;
11
+ assessErasure: MarketingPrivacyContributor<Context>["assessErasure"] | null;
12
+ eraseOrRetain: MarketingPrivacyContributor<Context>["eraseOrRetain"] | null;
13
+ }>;
14
+ };
@@ -0,0 +1,18 @@
1
+ import { createMarketingPrivacyContributor } from "./privacyContributor.js";
2
+ /** Select one fully bound privacy contributor; absent activation leaves the host registry unchanged. */
3
+ export function createMarketingPrivacyBindings(input) {
4
+ const acquisition = input.acquisition && [input.acquisition.export, input.acquisition.assessErasure, input.acquisition.eraseOrRetain].every(fn => typeof fn === "function") ? input.acquisition : null;
5
+ const remoteErasure = input.remoteErasure && [input.remoteErasure.read, input.remoteErasure.openAndFence].every(fn => typeof fn === "function") ? input.remoteErasure : null;
6
+ const issues = [];
7
+ for (const [name, port] of [["acquisition", acquisition], ["remoteErasure", remoteErasure]]) {
8
+ if (!port)
9
+ issues.push({ code: "OPENLUP_E_PORT_MISSING", package: "@openlup/platform", module: "marketing", subject: name, fix: `Bind the marketing ${name} owner port.` });
10
+ }
11
+ const selected = input.enabled === true && issues.length === 0
12
+ ? createMarketingPrivacyContributor({ ...input, acquisition, remoteErasure }) : null;
13
+ return { contributor: selected, issues, callables: Object.freeze({
14
+ exportPersonalData: selected ? selected.export : null,
15
+ assessErasure: selected ? selected.assessErasure : null,
16
+ eraseOrRetain: selected ? selected.eraseOrRetain : null,
17
+ }) };
18
+ }
@@ -0,0 +1,26 @@
1
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
2
+ import type { BffResponder } from "../../bff/index.js";
3
+ import type { AcquisitionCaseHandlers } from "./acquisitionCaseHandlers.js";
4
+ import type { AcquisitionEvidencePort } from "../contracts/index.js";
5
+ export interface AcquisitionCaseReadBinding {
6
+ handlers: Pick<AcquisitionCaseHandlers, "list" | "get" | "activeCount">;
7
+ }
8
+ export type AcquisitionCaseBindingResolver = () => Promise<AcquisitionCaseReadBinding | null>;
9
+ export type AcquisitionEvidenceBindingResolver = () => Promise<AcquisitionEvidencePort | null>;
10
+ export type AcquisitionAuthorize = (req: HttpRequest, res: HttpResponse) => Promise<string | null>;
11
+ export interface AcquisitionCaseDirectDeps {
12
+ respond: BffResponder;
13
+ authorize: AcquisitionAuthorize;
14
+ resolveBinding: AcquisitionCaseBindingResolver;
15
+ isDirect: () => boolean;
16
+ }
17
+ export interface PrelaunchAcquisitionDirectDeps {
18
+ respond: BffResponder;
19
+ authorize: AcquisitionAuthorize;
20
+ resolveBinding: AcquisitionCaseBindingResolver;
21
+ }
22
+ export interface AcquisitionEvidenceDirectDeps {
23
+ respond: BffResponder;
24
+ authorize: AcquisitionAuthorize;
25
+ resolveBinding: AcquisitionEvidenceBindingResolver;
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ /** Node-only acquisition and research algorithms. Hosts own routing, authorization and presentation. */
2
+ export * from "./acquisitionCaseHandlers.js";
3
+ export type * from "./acquisitionCasePorts.js";
4
+ export type * from "./directPorts.js";
5
+ export * from "./acquisitionCaseDirect.js";
6
+ export * from "./prelaunchAcquisitionDirect.js";
7
+ export * from "./acquisitionEvidenceDirect.js";
8
+ export * from "./research/surveyResponseSubmitHandler.js";
9
+ export * from "./research/adminSurveyResponsesHandler.js";
10
+ export * from "./research/managedSurveyResponseSubmit.js";
11
+ export { createAnalyticsContextHandler } from "./analyticsContext.js";
12
+ export type * from "./analyticsContextPorts.js";
13
+ export * from "./privacyContributor.js";
14
+ export * from "./bindings.js";
@@ -0,0 +1,11 @@
1
+ /** Node-only acquisition and research algorithms. Hosts own routing, authorization and presentation. */
2
+ export * from "./acquisitionCaseHandlers.js";
3
+ export * from "./acquisitionCaseDirect.js";
4
+ export * from "./prelaunchAcquisitionDirect.js";
5
+ export * from "./acquisitionEvidenceDirect.js";
6
+ export * from "./research/surveyResponseSubmitHandler.js";
7
+ export * from "./research/adminSurveyResponsesHandler.js";
8
+ export * from "./research/managedSurveyResponseSubmit.js";
9
+ export { createAnalyticsContextHandler } from "./analyticsContext.js";
10
+ export * from "./privacyContributor.js";
11
+ export * from "./bindings.js";
@@ -0,0 +1,10 @@
1
+ import { Pool } from "pg";
2
+ import type { AcquisitionCasePort } from "../acquisitionCasePorts.js";
3
+ type Env = {
4
+ connectionString: string;
5
+ };
6
+ type Options = {
7
+ poolFactory?: (env: Env) => Pool | Promise<Pool>;
8
+ };
9
+ export declare function createPostgresAcquisitionCasePort(env: Env, options?: Options): AcquisitionCasePort;
10
+ export {};
@@ -0,0 +1,204 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { Pool } from "pg";
3
+ import { ACQUISITION_CASE_CONTRACT_VERSION, acquisitionCaseListResponseSchema, acquisitionCaseProjectionSchema } from "../../acquisitionCaseSchemas.js";
4
+ const ROLE = "SET LOCAL ROLE platform_acquisition_runtime";
5
+ const SUBMIT = `SELECT public.acquisition_case_submit_v1(
6
+ $1::text,$2::text,$3::text,$4::text,$5::boolean,$6::text,$7::text,
7
+ $8::text,$9::text,$10::text,$11::text,$12::text,$13::text,$14::timestamptz
8
+ ) AS response`;
9
+ const LIST = "SELECT public.acquisition_case_operator_list_v1($1::uuid,$2::timestamptz,$3::uuid,$4::integer) AS response";
10
+ const GET = "SELECT public.acquisition_case_operator_get_v1($1::uuid,$2::text) AS response";
11
+ const TRANSITION = `SELECT public.acquisition_case_transition_v1(
12
+ $1::uuid,$2::text,$3::bigint,$4::text,$5::text,$6::text,$7::timestamptz
13
+ ) AS response`;
14
+ const ACTIVE_COUNT = "SELECT public.acquisition_case_active_count_v1() AS response";
15
+ export function createPostgresAcquisitionCasePort(env, options = {}) {
16
+ if (!env.connectionString.trim())
17
+ throw new Error("acquisition_case_database_url_required");
18
+ let poolPromise = null;
19
+ const pool = () => poolPromise ??= Promise.resolve(options.poolFactory ? options.poolFactory(env) : new Pool({ connectionString: env.connectionString }));
20
+ const call = async (sql, values) => withRole(await pool(), async (client) => {
21
+ const result = await client.query(sql, values);
22
+ return object(result.rows[0])?.response ?? null;
23
+ });
24
+ return {
25
+ async submit(command) {
26
+ if (command.scope !== "public_tester_application" || command.sourceKind !== "tester_application") {
27
+ return failure("invalid");
28
+ }
29
+ try {
30
+ const request = command.request;
31
+ const response = object(await call(SUBMIT, [
32
+ command.idempotencyKey,
33
+ command.requesterKey,
34
+ request.contact.email.trim().toLowerCase(),
35
+ null,
36
+ request.consent.accepted,
37
+ request.consent.consentVersion,
38
+ request.consent.policyVersion,
39
+ request.consent.locale,
40
+ command.sourcePath,
41
+ request.addressReference.source,
42
+ request.addressReference.reference,
43
+ request.addressReference.revision,
44
+ request.addressReference.provenance,
45
+ command.acceptedAt,
46
+ ]));
47
+ return projectionResult(response);
48
+ }
49
+ catch (error) {
50
+ return databaseFailure(error);
51
+ }
52
+ },
53
+ async list(query) {
54
+ if (query.scope !== "public_tester_application")
55
+ return failure("invalid");
56
+ const cursor = decodeCursor(query.cursor);
57
+ if (cursor === undefined)
58
+ return failure("invalid");
59
+ try {
60
+ const response = object(await call(LIST, [
61
+ query.actorRef,
62
+ cursor?.createdAt ?? null,
63
+ cursor?.id ?? null,
64
+ query.limit,
65
+ ]));
66
+ if (!response || !Array.isArray(response.cases))
67
+ return failure("unavailable");
68
+ const parsed = acquisitionCaseListResponseSchema.safeParse({
69
+ contractVersion: response.contractVersion ?? ACQUISITION_CASE_CONTRACT_VERSION,
70
+ cases: response.cases,
71
+ nextCursor: response.nextCursor === null ? null : encodeCursor(response.nextCursor),
72
+ });
73
+ return parsed.success ? { ok: true, value: parsed.data } : failure("unavailable");
74
+ }
75
+ catch (error) {
76
+ return databaseFailure(error);
77
+ }
78
+ },
79
+ async get(query) {
80
+ if (query.scope !== "public_tester_application")
81
+ return failure("invalid");
82
+ try {
83
+ const response = object(await call(GET, [query.actorRef, query.caseRef]));
84
+ if (response?.outcome === "not_found")
85
+ return failure("not_found");
86
+ return projectionResult(response);
87
+ }
88
+ catch (error) {
89
+ return databaseFailure(error);
90
+ }
91
+ },
92
+ async transition(command) {
93
+ if (command.scope !== "public_tester_application")
94
+ return failure("invalid");
95
+ const target = transitionTarget(command);
96
+ try {
97
+ const response = object(await call(TRANSITION, [
98
+ command.actorRef,
99
+ command.request.caseRef,
100
+ command.request.expectedVersion,
101
+ target,
102
+ command.request.reason ?? null,
103
+ command.idempotencyKey,
104
+ new Date().toISOString(),
105
+ ]));
106
+ return projectionResult(response);
107
+ }
108
+ catch (error) {
109
+ return databaseFailure(error);
110
+ }
111
+ },
112
+ async activeCount(scope) {
113
+ if (scope !== "public_tester_application")
114
+ return failure("invalid");
115
+ try {
116
+ const raw = await call(ACTIVE_COUNT, []);
117
+ const count = typeof raw === "number" ? raw : Number(raw);
118
+ return Number.isSafeInteger(count) && count >= 0
119
+ ? { ok: true, value: count }
120
+ : failure("unavailable");
121
+ }
122
+ catch (error) {
123
+ return databaseFailure(error);
124
+ }
125
+ },
126
+ async close() {
127
+ if (poolPromise)
128
+ await (await poolPromise).end();
129
+ },
130
+ };
131
+ }
132
+ async function withRole(pool, work) {
133
+ const client = await pool.connect();
134
+ try {
135
+ await client.query("BEGIN");
136
+ await client.query(ROLE);
137
+ const value = await work(client);
138
+ await client.query("COMMIT");
139
+ return value;
140
+ }
141
+ catch (error) {
142
+ await client.query("ROLLBACK").catch(() => { });
143
+ throw error;
144
+ }
145
+ finally {
146
+ client.release();
147
+ }
148
+ }
149
+ function projectionResult(response) {
150
+ if (!response)
151
+ return failure("unavailable");
152
+ const outcome = response.outcome;
153
+ if (outcome === "created" || outcome === "replayed" || outcome === "transitioned" || outcome === "found") {
154
+ const parsed = acquisitionCaseProjectionSchema.safeParse(response.acquisitionCase);
155
+ return parsed.success
156
+ ? { ok: true, value: parsed.data, replayed: response.replayed === true }
157
+ : failure("unavailable");
158
+ }
159
+ return failure(outcomeFailure(outcome));
160
+ }
161
+ function outcomeFailure(outcome) {
162
+ if (outcome === "invalid_address")
163
+ return "invalid";
164
+ if (outcome === "rate_limited")
165
+ return "rate_limited";
166
+ if (outcome === "not_found")
167
+ return "not_found";
168
+ if (outcome === "idempotency_conflict" || outcome === "contact_conflict"
169
+ || outcome === "version_conflict" || outcome === "transition_conflict")
170
+ return "conflict";
171
+ return "unavailable";
172
+ }
173
+ function databaseFailure(error) {
174
+ return failure(object(error)?.code === "22023" ? "invalid" : "unavailable");
175
+ }
176
+ function failure(kind) {
177
+ return { ok: false, error: { kind } };
178
+ }
179
+ function transitionTarget(command) {
180
+ return { approve: "approved", activate: "active", reject: "rejected", withdraw: "withdrawn" }[command.request.transition];
181
+ }
182
+ function encodeCursor(value) {
183
+ const cursor = object(value);
184
+ if (!cursor || typeof cursor.createdAt !== "string" || typeof cursor.id !== "string") {
185
+ throw new Error("acquisition_case_cursor_invalid");
186
+ }
187
+ return Buffer.from(JSON.stringify({ createdAt: cursor.createdAt, id: cursor.id }), "utf8").toString("base64url");
188
+ }
189
+ function decodeCursor(value) {
190
+ if (value === undefined)
191
+ return null;
192
+ try {
193
+ const parsed = object(JSON.parse(Buffer.from(value, "base64url").toString("utf8")));
194
+ return parsed && typeof parsed.createdAt === "string" && typeof parsed.id === "string"
195
+ ? { createdAt: parsed.createdAt, id: parsed.id }
196
+ : undefined;
197
+ }
198
+ catch {
199
+ return undefined;
200
+ }
201
+ }
202
+ function object(value) {
203
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
204
+ }
@@ -0,0 +1,10 @@
1
+ import { Pool } from "pg";
2
+ import type { AcquisitionEvidencePort } from "../../contracts/index.js";
3
+ type Env = {
4
+ connectionString: string;
5
+ };
6
+ type Options = {
7
+ poolFactory?: (env: Env) => Pool | Promise<Pool>;
8
+ };
9
+ export declare function createPostgresAcquisitionEvidencePort(env: Env, options?: Options): AcquisitionEvidencePort;
10
+ export {};
@@ -0,0 +1,107 @@
1
+ import { Pool } from "pg";
2
+ import { ACQUISITION_NEWSLETTER_VERSION, ACQUISITION_SURVEY_VERSION, acquisitionNewsletterConsentResponseSchema, acquisitionSurveyListResponseSchema, acquisitionSurveySubmitResponseSchema } from "../../acquisitionEvidenceSchemas.js";
3
+ const ROLE = "SET LOCAL ROLE platform_acquisition_runtime";
4
+ const SUBMIT = "SELECT public.acquisition_survey_submit_v1($1::text,$2::text,$3::text,$4::jsonb,$5::timestamptz) AS response";
5
+ const LIST = "SELECT public.acquisition_survey_operator_list_v1($1::uuid,$2::text,$3::integer) AS response";
6
+ const CONSENT = "SELECT public.acquisition_newsletter_consent_v1($1::text,$2::text,$3::text,$4::text,$5::text,$6::boolean,$7::timestamptz,$8::timestamptz) AS response";
7
+ export function createPostgresAcquisitionEvidencePort(env, options = {}) {
8
+ if (!env.connectionString.trim())
9
+ throw new Error("acquisition_evidence_database_url_required");
10
+ let poolPromise = null;
11
+ const pool = () => poolPromise ??= Promise.resolve(options.poolFactory ? options.poolFactory(env) : new Pool({ connectionString: env.connectionString }));
12
+ const call = async (sql, values) => withRole(await pool(), async (client) => object(object((await client.query(sql, values)).rows[0])?.response));
13
+ return {
14
+ async submitSurvey(input) {
15
+ try {
16
+ const response = await call(SUBMIT, [input.idempotencyKey, input.caseReference,
17
+ input.surveyType, JSON.stringify(input.responseData), new Date().toISOString()]);
18
+ const failure = outcomeFailure(response?.outcome);
19
+ if (failure)
20
+ return fail(failure);
21
+ const parsed = acquisitionSurveySubmitResponseSchema.safeParse({
22
+ contractVersion: ACQUISITION_SURVEY_VERSION,
23
+ evidence: response?.evidence,
24
+ replayed: response?.replayed === true,
25
+ });
26
+ return parsed.success ? { ok: true, value: parsed.data } : fail("unavailable");
27
+ }
28
+ catch (error) {
29
+ return databaseFailure(error);
30
+ }
31
+ },
32
+ async listSurveys(input) {
33
+ try {
34
+ const response = await call(LIST, [input.actorReference, input.surveyType ?? null, input.limit]);
35
+ const parsed = acquisitionSurveyListResponseSchema.safeParse({
36
+ contractVersion: ACQUISITION_SURVEY_VERSION,
37
+ rows: response?.rows,
38
+ totalCount: number(response?.totalCount),
39
+ });
40
+ return parsed.success ? { ok: true, value: parsed.data } : fail("unavailable");
41
+ }
42
+ catch (error) {
43
+ return databaseFailure(error);
44
+ }
45
+ },
46
+ async applyNewsletterConsent(input) {
47
+ try {
48
+ const response = await call(CONSENT, [input.caseReference, input.contactReference,
49
+ input.eventReference, input.eventType, input.purpose, input.explicitOptInEvidence,
50
+ input.occurredAt, new Date().toISOString()]);
51
+ const failure = outcomeFailure(response?.outcome);
52
+ if (failure)
53
+ return fail(failure);
54
+ const parsed = acquisitionNewsletterConsentResponseSchema.safeParse({
55
+ contractVersion: ACQUISITION_NEWSLETTER_VERSION,
56
+ eventReference: response?.eventReference,
57
+ outcome: response?.outcome,
58
+ consentState: response?.consentState ?? null,
59
+ auditReference: response?.auditReference,
60
+ replayed: response?.replayed === true,
61
+ });
62
+ return parsed.success ? { ok: true, value: parsed.data } : fail("unavailable");
63
+ }
64
+ catch (error) {
65
+ return databaseFailure(error);
66
+ }
67
+ },
68
+ async close() { if (poolPromise)
69
+ await (await poolPromise).end(); },
70
+ };
71
+ }
72
+ async function withRole(pool, work) {
73
+ const client = await pool.connect();
74
+ try {
75
+ await client.query("BEGIN");
76
+ await client.query(ROLE);
77
+ const value = await work(client);
78
+ await client.query("COMMIT");
79
+ return value;
80
+ }
81
+ catch (error) {
82
+ await client.query("ROLLBACK").catch(() => { });
83
+ throw error;
84
+ }
85
+ finally {
86
+ client.release();
87
+ }
88
+ }
89
+ function outcomeFailure(outcome) {
90
+ if (outcome === "not_found")
91
+ return "not_found";
92
+ if (outcome === "idempotency_conflict")
93
+ return "conflict";
94
+ if (["recorded", "replayed", "applied", "ignored", "stale"].includes(String(outcome)))
95
+ return null;
96
+ return "unavailable";
97
+ }
98
+ function databaseFailure(error) {
99
+ return fail(object(error)?.code === "22023" ? "invalid" : "unavailable");
100
+ }
101
+ function fail(kind) {
102
+ return { ok: false, error: { kind } };
103
+ }
104
+ function object(value) {
105
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
106
+ }
107
+ function number(value) { return typeof value === "number" ? value : Number(value); }
@@ -0,0 +1,3 @@
1
+ /** PostgreSQL acquisition ports; select with the optional pg peer. */
2
+ export { createPostgresAcquisitionCasePort } from "./acquisitionCaseRoutines.js";
3
+ export { createPostgresAcquisitionEvidencePort } from "./acquisitionEvidenceRoutines.js";
@@ -0,0 +1,3 @@
1
+ /** PostgreSQL acquisition ports; select with the optional pg peer. */
2
+ export { createPostgresAcquisitionCasePort } from "./acquisitionCaseRoutines.js";
3
+ export { createPostgresAcquisitionEvidencePort } from "./acquisitionEvidenceRoutines.js";
@@ -0,0 +1,4 @@
1
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
2
+ import type { PrelaunchAcquisitionDirectDeps } from "./directPorts.js";
3
+ export declare function createPrelaunchAcquisitionListHandler(options: PrelaunchAcquisitionDirectDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
4
+ export declare function createPrelaunchAcquisitionDetailHandler(options: PrelaunchAcquisitionDirectDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,122 @@
1
+ import { PRELAUNCH_ACQUISITION_CONTRACT_VERSION, prelaunchAcquisitionDetailRequestSchema, prelaunchAcquisitionDetailResponseSchema, prelaunchAcquisitionListRequestSchema, prelaunchAcquisitionListResponseSchema, } from "../prelaunchAcquisitionSchemas.js";
2
+ export function createPrelaunchAcquisitionListHandler(options) {
3
+ const { respond, authorize, resolveBinding } = options;
4
+ const { sendBffSuccess, sendMethodNotAllowed } = respond;
5
+ return async (req, res) => {
6
+ if (req.method !== "GET")
7
+ return sendMethodNotAllowed(res, ["GET"]);
8
+ const actorRef = await authorize(req, res);
9
+ if (!actorRef)
10
+ return;
11
+ const request = prelaunchAcquisitionListRequestSchema.safeParse({
12
+ view: first(req.query.view),
13
+ cursor: first(req.query.cursor),
14
+ limit: first(req.query.limit) ?? "25",
15
+ });
16
+ if (!request.success)
17
+ return badRequest(res, respond, request.error.flatten());
18
+ const binding = await bindingOrError(res, respond, resolveBinding);
19
+ if (!binding)
20
+ return;
21
+ const result = await operationOrError(res, respond, () => binding.handlers.list(actorRef, {
22
+ view: "acquisition_case_v1",
23
+ ...(request.data.cursor ? { cursor: request.data.cursor } : {}),
24
+ limit: request.data.limit,
25
+ }));
26
+ if (!result)
27
+ return;
28
+ if (result.ok === false)
29
+ return failure(res, respond, result.error.kind);
30
+ const response = prelaunchAcquisitionListResponseSchema.safeParse({
31
+ contractVersion: PRELAUNCH_ACQUISITION_CONTRACT_VERSION,
32
+ leads: result.value.cases.map(toLead),
33
+ nextCursor: result.value.nextCursor,
34
+ });
35
+ if (!response.success)
36
+ return invalidResponse(res, respond);
37
+ sendBffSuccess(res, response.data);
38
+ };
39
+ }
40
+ export function createPrelaunchAcquisitionDetailHandler(options) {
41
+ const { respond, authorize, resolveBinding } = options;
42
+ const { sendBffSuccess, sendMethodNotAllowed } = respond;
43
+ return async (req, res) => {
44
+ if (req.method !== "GET")
45
+ return sendMethodNotAllowed(res, ["GET"]);
46
+ const actorRef = await authorize(req, res);
47
+ if (!actorRef)
48
+ return;
49
+ const request = prelaunchAcquisitionDetailRequestSchema.safeParse({
50
+ sourceRef: first(req.query.sourceRef),
51
+ });
52
+ if (!request.success)
53
+ return badRequest(res, respond, request.error.flatten());
54
+ const binding = await bindingOrError(res, respond, resolveBinding);
55
+ if (!binding)
56
+ return;
57
+ const result = await operationOrError(res, respond, () => binding.handlers.get(actorRef, request.data.sourceRef));
58
+ if (!result)
59
+ return;
60
+ if (result.ok === false)
61
+ return failure(res, respond, result.error.kind);
62
+ const response = prelaunchAcquisitionDetailResponseSchema.safeParse({
63
+ contractVersion: PRELAUNCH_ACQUISITION_CONTRACT_VERSION,
64
+ lead: toLead(result.value),
65
+ });
66
+ if (!response.success)
67
+ return invalidResponse(res, respond);
68
+ sendBffSuccess(res, response.data);
69
+ };
70
+ }
71
+ function toLead(value) {
72
+ return {
73
+ sourceRef: value.caseRef,
74
+ contactRef: value.contactRef,
75
+ sourceKind: value.sourceKind,
76
+ consent: value.consent,
77
+ addressReference: value.addressReference,
78
+ state: value.state,
79
+ version: value.version,
80
+ createdAt: value.createdAt,
81
+ updatedAt: value.updatedAt,
82
+ };
83
+ }
84
+ async function bindingOrError(res, respond, resolve) {
85
+ try {
86
+ const binding = await resolve();
87
+ if (binding)
88
+ return binding;
89
+ }
90
+ catch { /* fail closed below */ }
91
+ respond.sendBffError(res, "UPSTREAM_UNAVAILABLE", "Prelaunch acquisition service unavailable");
92
+ return null;
93
+ }
94
+ async function operationOrError(res, respond, operation) {
95
+ try {
96
+ return await operation();
97
+ }
98
+ catch {
99
+ respond.sendBffError(res, "UPSTREAM_UNAVAILABLE", "Prelaunch acquisition service unavailable");
100
+ return null;
101
+ }
102
+ }
103
+ function first(value) {
104
+ return Array.isArray(value) ? value[0] : value;
105
+ }
106
+ function badRequest(res, respond, details) {
107
+ respond.sendBffError(res, "BAD_REQUEST", "Invalid prelaunch acquisition request", { details });
108
+ }
109
+ function invalidResponse(res, respond) {
110
+ respond.sendBffError(res, "INVALID_RESPONSE", "Prelaunch acquisition returned invalid response");
111
+ }
112
+ function failure(res, respond, kind) {
113
+ if (kind === "invalid")
114
+ return badRequest(res, respond);
115
+ if (kind === "not_found")
116
+ return respond.sendBffError(res, "NOT_FOUND", "Prelaunch acquisition case not found");
117
+ if (kind === "conflict")
118
+ return respond.sendBffError(res, "CONFLICT", "Prelaunch acquisition request conflicts with prior state");
119
+ if (kind === "rate_limited")
120
+ return respond.sendBffError(res, "RATE_LIMITED", "Prelaunch acquisition request rate limited");
121
+ respond.sendBffError(res, "UPSTREAM_UNAVAILABLE", "Prelaunch acquisition service unavailable");
122
+ }