@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,127 @@
1
+ export function jsonLdGraph(...nodes) {
2
+ const seen = new Set();
3
+ const graph = nodes.flatMap(({ "@context": _context, ...node }) => {
4
+ if (node["@id"] && seen.has(node["@id"]))
5
+ return [];
6
+ if (node["@id"])
7
+ seen.add(node["@id"]);
8
+ return [node];
9
+ });
10
+ return { "@context": "https://schema.org", "@graph": graph };
11
+ }
12
+ export function entityIds(origin) {
13
+ const site = origin.replace(/\/$/, "");
14
+ return {
15
+ organization: `${site}/#organization`,
16
+ website: `${site}/#website`,
17
+ brand: `${site}/#brand`,
18
+ };
19
+ }
20
+ export function organizationLd(origin, type, facts) {
21
+ return {
22
+ "@context": "https://schema.org",
23
+ "@id": entityIds(origin).organization,
24
+ "@type": type,
25
+ name: facts.name,
26
+ legalName: facts.legalName,
27
+ url: facts.url,
28
+ logo: {
29
+ "@type": "ImageObject",
30
+ url: facts.logo.url,
31
+ contentUrl: facts.logo.contentUrl,
32
+ width: facts.logo.width,
33
+ height: facts.logo.height,
34
+ },
35
+ email: facts.email,
36
+ vatID: facts.vatID,
37
+ taxID: facts.taxID,
38
+ areaServed: facts.areaServed,
39
+ sameAs: facts.sameAs,
40
+ contactPoint: {
41
+ "@type": "ContactPoint",
42
+ email: facts.contactPoint.email,
43
+ contactType: facts.contactPoint.contactType,
44
+ availableLanguage: facts.contactPoint.availableLanguage,
45
+ },
46
+ address: {
47
+ "@type": "PostalAddress",
48
+ streetAddress: facts.address.streetAddress,
49
+ addressLocality: facts.address.addressLocality,
50
+ postalCode: facts.address.postalCode,
51
+ addressRegion: facts.address.addressRegion,
52
+ addressCountry: facts.address.addressCountry,
53
+ },
54
+ };
55
+ }
56
+ export function brandLd(origin, name, url) {
57
+ return { "@context": "https://schema.org", "@id": entityIds(origin).brand, "@type": "Brand", name, url };
58
+ }
59
+ export function websiteLd(origin, name, url, inLanguage) {
60
+ return {
61
+ "@context": "https://schema.org",
62
+ "@id": entityIds(origin).website,
63
+ "@type": "WebSite",
64
+ name,
65
+ url,
66
+ inLanguage,
67
+ publisher: { "@id": entityIds(origin).organization },
68
+ };
69
+ }
70
+ export function contactPageLd(origin, pageUrl) {
71
+ return {
72
+ "@context": "https://schema.org",
73
+ "@id": `${pageUrl.replace(/\/$/, "")}#webpage`,
74
+ "@type": "ContactPage",
75
+ url: pageUrl,
76
+ publisher: { "@id": entityIds(origin).organization },
77
+ mainEntity: { "@id": entityIds(origin).organization },
78
+ };
79
+ }
80
+ export function schemaWeight(netContent) {
81
+ const { unscaled, scale, unit } = netContent;
82
+ const unitCode = unit === "GRAM" || unit === "G" ? "GRM" : unit;
83
+ return { "@type": "QuantitativeValue", value: unscaled / 10 ** scale, unitCode };
84
+ }
85
+ export function productLd(origin, product) {
86
+ return {
87
+ "@context": "https://schema.org",
88
+ "@id": `${product.url.replace(/\/$/, "")}#product`,
89
+ "@type": "Product",
90
+ name: product.name,
91
+ inLanguage: product.inLanguage,
92
+ brand: { "@id": entityIds(origin).brand, "@type": "Brand", name: product.brandName },
93
+ manufacturer: { "@id": entityIds(origin).organization },
94
+ ...(product.category !== undefined ? { category: product.category } : {}),
95
+ image: product.images,
96
+ ...(product.sku !== undefined ? { sku: product.sku } : {}),
97
+ ...(product.gtin13 !== undefined ? { gtin13: product.gtin13 } : {}),
98
+ ...(product.netContent ? { weight: schemaWeight(product.netContent) } : {}),
99
+ url: product.url,
100
+ };
101
+ }
102
+ const pageEntityId = (pageUrl, fragment) => `${pageUrl.replace(/\/$/, "")}#${fragment}`;
103
+ export function faqPageLd(faqs, pageUrl) {
104
+ return {
105
+ "@context": "https://schema.org",
106
+ "@id": pageEntityId(pageUrl, "faq"),
107
+ "@type": "FAQPage",
108
+ mainEntity: faqs.map((faq) => ({
109
+ "@type": "Question",
110
+ name: faq.question,
111
+ acceptedAnswer: { "@type": "Answer", text: faq.answer },
112
+ })),
113
+ };
114
+ }
115
+ export function breadcrumbLd(items, pageUrl) {
116
+ return {
117
+ "@context": "https://schema.org",
118
+ "@id": pageEntityId(pageUrl, "breadcrumb"),
119
+ "@type": "BreadcrumbList",
120
+ itemListElement: items.map((item, index) => ({
121
+ "@type": "ListItem",
122
+ position: index + 1,
123
+ name: item.name,
124
+ ...(item.url ? { item: item.url } : {}),
125
+ })),
126
+ };
127
+ }
@@ -0,0 +1,41 @@
1
+ import { type JsonLdNode } from "./jsonLd.js";
2
+ import { type SiteRoute } from "./urls.js";
3
+ export type HeadTag = {
4
+ kind: "meta";
5
+ name: string;
6
+ content: string;
7
+ } | {
8
+ kind: "meta";
9
+ property: string;
10
+ content: string;
11
+ } | {
12
+ kind: "link";
13
+ rel: "canonical" | "alternate";
14
+ href: string;
15
+ hrefLang?: string;
16
+ };
17
+ export interface HeadFacts {
18
+ origin: string;
19
+ route: SiteRoute;
20
+ routes: readonly SiteRoute[];
21
+ xDefaultLocale: string;
22
+ htmlLang: string;
23
+ title: string;
24
+ description: string;
25
+ siteName: string;
26
+ ogImage: string;
27
+ ogImageWidth: number;
28
+ ogImageHeight: number;
29
+ ogLocale: string;
30
+ alternateOgLocale?: string;
31
+ twitterImage: string;
32
+ twitterHandle: string;
33
+ noindex?: boolean;
34
+ jsonLd?: JsonLdNode | JsonLdNode[];
35
+ }
36
+ export declare function buildHeadDescriptor(facts: HeadFacts): {
37
+ htmlLang: string;
38
+ title: string;
39
+ tags: HeadTag[];
40
+ jsonLd: string | null;
41
+ };
@@ -0,0 +1,36 @@
1
+ import { jsonLdGraph } from "./jsonLd.js";
2
+ import { serializeJsonLd } from "./serializeJsonLd.js";
3
+ import { canonicalUrlForRoute, hreflangLinksForRoute } from "./urls.js";
4
+ export function buildHeadDescriptor(facts) {
5
+ const canonicalUrl = canonicalUrlForRoute(facts.origin, facts.route);
6
+ const nodes = facts.jsonLd ? (Array.isArray(facts.jsonLd) ? facts.jsonLd : [facts.jsonLd]) : [];
7
+ const tags = [
8
+ { kind: "meta", name: "description", content: facts.description },
9
+ { kind: "link", rel: "canonical", href: canonicalUrl },
10
+ ...(facts.noindex ? [{ kind: "meta", name: "robots", content: "noindex, nofollow" }] : []),
11
+ { kind: "meta", property: "og:title", content: facts.title },
12
+ { kind: "meta", property: "og:description", content: facts.description },
13
+ { kind: "meta", property: "og:image", content: facts.ogImage },
14
+ { kind: "meta", property: "og:image:width", content: String(facts.ogImageWidth) },
15
+ { kind: "meta", property: "og:image:height", content: String(facts.ogImageHeight) },
16
+ { kind: "meta", property: "og:type", content: "website" },
17
+ { kind: "meta", property: "og:site_name", content: facts.siteName },
18
+ { kind: "meta", property: "og:url", content: canonicalUrl },
19
+ { kind: "meta", property: "og:locale", content: facts.ogLocale },
20
+ ...(facts.alternateOgLocale ? [{ kind: "meta", property: "og:locale:alternate", content: facts.alternateOgLocale }] : []),
21
+ { kind: "meta", name: "twitter:card", content: "summary_large_image" },
22
+ { kind: "meta", name: "twitter:site", content: facts.twitterHandle },
23
+ { kind: "meta", name: "twitter:title", content: facts.title },
24
+ { kind: "meta", name: "twitter:description", content: facts.description },
25
+ { kind: "meta", name: "twitter:image", content: facts.twitterImage },
26
+ ...hreflangLinksForRoute(facts.origin, facts.route, facts.routes, facts.xDefaultLocale).map((link) => ({
27
+ kind: "link", rel: "alternate", hrefLang: link.hrefLang, href: link.href,
28
+ })),
29
+ ];
30
+ return {
31
+ htmlLang: facts.htmlLang,
32
+ title: facts.title,
33
+ tags,
34
+ jsonLd: nodes.length > 0 ? serializeJsonLd(jsonLdGraph(...nodes)) : null,
35
+ };
36
+ }
@@ -0,0 +1 @@
1
+ export declare function serializeJsonLd(value: object): string;
@@ -0,0 +1,6 @@
1
+ export function serializeJsonLd(value) {
2
+ return JSON.stringify(value)
3
+ .replace(/</g, "\\u003c")
4
+ .replace(/\u2028/g, "\\u2028")
5
+ .replace(/\u2029/g, "\\u2029");
6
+ }
@@ -0,0 +1,14 @@
1
+ export interface SiteRoute {
2
+ id: string;
3
+ locale: string;
4
+ canonicalPath: string;
5
+ alternateRouteId?: string;
6
+ }
7
+ export interface HreflangLink {
8
+ hrefLang: string;
9
+ href: string;
10
+ }
11
+ export declare function normalizeSitePath(pathname: string): string;
12
+ export declare function absoluteSiteUrl(origin: string, pathname: string): string;
13
+ export declare function canonicalUrlForRoute(origin: string, route: SiteRoute): string;
14
+ export declare function hreflangLinksForRoute(origin: string, route: SiteRoute, routes: readonly SiteRoute[], xDefaultLocale: string): HreflangLink[];
@@ -0,0 +1,23 @@
1
+ export function normalizeSitePath(pathname) {
2
+ const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
3
+ return path.length > 1 ? path.replace(/\/$/, "") : path;
4
+ }
5
+ export function absoluteSiteUrl(origin, pathname) {
6
+ const site = origin.replace(/\/$/, "");
7
+ const path = normalizeSitePath(pathname);
8
+ return path === "/" ? `${site}/` : `${site}${path}`;
9
+ }
10
+ export function canonicalUrlForRoute(origin, route) {
11
+ return absoluteSiteUrl(origin, route.canonicalPath);
12
+ }
13
+ export function hreflangLinksForRoute(origin, route, routes, xDefaultLocale) {
14
+ const alternate = route.alternateRouteId ? routes.find((candidate) => candidate.id === route.alternateRouteId) : undefined;
15
+ if (!alternate)
16
+ return [];
17
+ const pair = [route, alternate].sort((a, b) => a.locale.localeCompare(b.locale));
18
+ const preferred = pair.find((candidate) => candidate.locale === xDefaultLocale);
19
+ return [
20
+ ...pair.map((candidate) => ({ hrefLang: candidate.locale, href: canonicalUrlForRoute(origin, candidate) })),
21
+ { hrefLang: "x-default", href: canonicalUrlForRoute(origin, preferred ?? pair[0]) },
22
+ ];
23
+ }
@@ -0,0 +1,4 @@
1
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
2
+ import type { AcquisitionCaseDirectDeps } from "./directPorts.js";
3
+ export declare function createAcquisitionCaseListDirectHandler(options: AcquisitionCaseDirectDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
4
+ export declare function createAcquisitionCaseActiveCountDirectHandler(options: AcquisitionCaseDirectDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,88 @@
1
+ import { ACQUISITION_CASE_VIEW, acquisitionCaseActiveCountResponseSchema, acquisitionCaseListRequestSchema, acquisitionCaseListResponseSchema } from "../acquisitionCaseSchemas.js";
2
+ export function createAcquisitionCaseListDirectHandler(options) {
3
+ const { respond, authorize, resolveBinding, isDirect } = options;
4
+ const { sendBffSuccess, sendMethodNotAllowed } = respond;
5
+ return async (req, res) => {
6
+ if (!isDirect() || first(req.query.view) !== ACQUISITION_CASE_VIEW)
7
+ return unavailable(res, respond);
8
+ if (req.method !== "GET")
9
+ return sendMethodNotAllowed(res, ["GET"]);
10
+ const actorRef = await authorize(req, res);
11
+ if (!actorRef)
12
+ return;
13
+ const request = acquisitionCaseListRequestSchema.safeParse({
14
+ view: first(req.query.view), cursor: first(req.query.cursor), limit: integer(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, request.data));
22
+ if (!result)
23
+ return;
24
+ if (result.ok === false)
25
+ return failure(res, respond, result.error.kind);
26
+ const response = acquisitionCaseListResponseSchema.safeParse(result.value);
27
+ if (!response.success)
28
+ return invalidResponse(res, respond);
29
+ sendBffSuccess(res, response.data);
30
+ };
31
+ }
32
+ export function createAcquisitionCaseActiveCountDirectHandler(options) {
33
+ const { respond, resolveBinding, isDirect } = options;
34
+ const { sendBffSuccess, sendMethodNotAllowed } = respond;
35
+ return async (req, res) => {
36
+ if (!isDirect())
37
+ return unavailable(res, respond);
38
+ if (req.method !== "GET")
39
+ return sendMethodNotAllowed(res, ["GET"]);
40
+ const binding = await bindingOrError(res, respond, resolveBinding);
41
+ if (!binding)
42
+ return;
43
+ const result = await operationOrError(res, respond, () => binding.handlers.activeCount());
44
+ if (!result)
45
+ return;
46
+ if (result.ok === false)
47
+ return failure(res, respond, result.error.kind);
48
+ const response = acquisitionCaseActiveCountResponseSchema.safeParse(result.value);
49
+ if (!response.success)
50
+ return invalidResponse(res, respond);
51
+ sendBffSuccess(res, response.data);
52
+ };
53
+ }
54
+ async function bindingOrError(res, respond, resolve) {
55
+ try {
56
+ const binding = await resolve();
57
+ if (binding)
58
+ return binding;
59
+ }
60
+ catch { /* fail closed below */ }
61
+ respond.sendBffError(res, "UPSTREAM_UNAVAILABLE", "Acquisition case service unavailable");
62
+ return null;
63
+ }
64
+ async function operationOrError(res, respond, operation) {
65
+ try {
66
+ return await operation();
67
+ }
68
+ catch {
69
+ respond.sendBffError(res, "UPSTREAM_UNAVAILABLE", "Acquisition case service unavailable");
70
+ return null;
71
+ }
72
+ }
73
+ function first(value) { return Array.isArray(value) ? value[0] : value; }
74
+ function integer(value, fallback) { return value === undefined ? fallback : Number(value); }
75
+ function badRequest(res, respond, details) { respond.sendBffError(res, "BAD_REQUEST", "Invalid acquisition case request", { details }); }
76
+ function unavailable(res, respond) { respond.sendBffError(res, "NOT_FOUND", "Acquisition case unavailable"); }
77
+ function invalidResponse(res, respond) { respond.sendBffError(res, "INVALID_RESPONSE", "Acquisition case returned invalid response"); }
78
+ function failure(res, respond, kind) {
79
+ if (kind === "invalid")
80
+ return badRequest(res, respond);
81
+ if (kind === "conflict")
82
+ return respond.sendBffError(res, "CONFLICT", "Acquisition case request conflicts with prior state");
83
+ if (kind === "rate_limited")
84
+ return respond.sendBffError(res, "RATE_LIMITED", "Acquisition submission rate limited");
85
+ if (kind === "not_found")
86
+ return unavailable(res, respond);
87
+ respond.sendBffError(res, "UPSTREAM_UNAVAILABLE", "Acquisition case service unavailable");
88
+ }
@@ -0,0 +1,28 @@
1
+ import type { AcquisitionCaseListRequest } from "../contracts/index.js";
2
+ import { type AcquisitionCaseSubmitRequest, type AcquisitionCaseTransitionRequest } from "./acquisitionCasePorts.js";
3
+ import type { AcquisitionCasePort } from "./acquisitionCasePorts.js";
4
+ export interface AcquisitionCaseHandlerOptions {
5
+ now?: () => string;
6
+ }
7
+ export declare function createAcquisitionCaseHandlers(port: AcquisitionCasePort, options?: AcquisitionCaseHandlerOptions): {
8
+ submit(request: AcquisitionCaseSubmitRequest, context: {
9
+ idempotencyKey: string;
10
+ requesterKey: string;
11
+ }): Promise<import("./acquisitionCasePorts.js").AcquisitionCaseResult<import("../contracts/acquisition.js").AcquisitionCaseProjection>>;
12
+ list(actorRef: string, request: AcquisitionCaseListRequest): Promise<import("./acquisitionCasePorts.js").AcquisitionCaseResult<import("../contracts/acquisition.js").AcquisitionCaseListResponse>>;
13
+ get(actorRef: string, caseRef: string): Promise<import("./acquisitionCasePorts.js").AcquisitionCaseResult<import("../contracts/acquisition.js").AcquisitionCaseProjection>>;
14
+ transition(actorRef: string, idempotencyKey: string, request: AcquisitionCaseTransitionRequest): Promise<import("./acquisitionCasePorts.js").AcquisitionCaseResult<import("../contracts/acquisition.js").AcquisitionCaseProjection>>;
15
+ activeCount(): Promise<{
16
+ ok: false;
17
+ error: {
18
+ kind: import("./acquisitionCasePorts.js").AcquisitionCaseFailureKind;
19
+ };
20
+ } | {
21
+ ok: true;
22
+ value: {
23
+ activeTesterCount: number;
24
+ };
25
+ }>;
26
+ contractVersion: "tester_application_v1";
27
+ };
28
+ export type AcquisitionCaseHandlers = ReturnType<typeof createAcquisitionCaseHandlers>;
@@ -0,0 +1,40 @@
1
+ import { ACQUISITION_CASE_CONTRACT_VERSION, ACQUISITION_CASE_SOURCE } from "../acquisitionCaseSchemas.js";
2
+ const SCOPE = "public_tester_application";
3
+ export function createAcquisitionCaseHandlers(port, options = {}) {
4
+ const now = options.now ?? (() => new Date().toISOString());
5
+ return {
6
+ submit(request, context) {
7
+ return port.submit({
8
+ scope: SCOPE,
9
+ sourceKind: ACQUISITION_CASE_SOURCE,
10
+ idempotencyKey: context.idempotencyKey,
11
+ requesterKey: `${SCOPE}:${context.requesterKey}`,
12
+ acceptedAt: now(),
13
+ sourcePath: "/tester-application",
14
+ request,
15
+ });
16
+ },
17
+ list(actorRef, request) {
18
+ return port.list({
19
+ actorRef,
20
+ scope: SCOPE,
21
+ ...(request.cursor ? { cursor: request.cursor } : {}),
22
+ limit: request.limit,
23
+ });
24
+ },
25
+ get(actorRef, caseRef) {
26
+ return port.get({ actorRef, scope: SCOPE, caseRef });
27
+ },
28
+ transition(actorRef, idempotencyKey, request) {
29
+ return port.transition({ actorRef, scope: SCOPE, idempotencyKey, request });
30
+ },
31
+ async activeCount() {
32
+ const result = await port.activeCount(SCOPE);
33
+ return result.ok === false ? result : {
34
+ ok: true,
35
+ value: { activeTesterCount: result.value },
36
+ };
37
+ },
38
+ contractVersion: ACQUISITION_CASE_CONTRACT_VERSION,
39
+ };
40
+ }
@@ -0,0 +1,42 @@
1
+ import type { AcquisitionCaseProjection, AcquisitionCaseResult, AcquisitionCasePort as PortableAcquisitionCasePort } from "../contracts/index.js";
2
+ import type { AcquisitionAddressReference, AcquisitionCaseProjection as Projection } from "../contracts/index.js";
3
+ /** Historical commands retained by the server adapter; public admission routes remain host-owned. */
4
+ export interface AcquisitionCaseSubmitRequest {
5
+ contact: {
6
+ email: string;
7
+ };
8
+ consent: {
9
+ accepted: true;
10
+ consentVersion: string;
11
+ policyVersion: string;
12
+ locale: Projection["consent"]["locale"];
13
+ };
14
+ addressReference: AcquisitionAddressReference;
15
+ }
16
+ export interface AcquisitionCaseTransitionRequest {
17
+ view: "acquisition_case_v1";
18
+ caseRef: string;
19
+ expectedVersion: number;
20
+ transition: "approve" | "activate" | "reject" | "withdraw";
21
+ reason?: string | undefined;
22
+ }
23
+ export interface AcquisitionCaseSubmitCommand {
24
+ scope: "public_tester_application";
25
+ sourceKind: "tester_application";
26
+ idempotencyKey: string;
27
+ requesterKey: string;
28
+ acceptedAt: string;
29
+ sourcePath: "/tester-application";
30
+ request: AcquisitionCaseSubmitRequest;
31
+ }
32
+ export interface AcquisitionCaseTransitionCommand {
33
+ actorRef: string;
34
+ scope: "public_tester_application";
35
+ idempotencyKey: string;
36
+ request: AcquisitionCaseTransitionRequest;
37
+ }
38
+ export interface AcquisitionCasePort extends PortableAcquisitionCasePort {
39
+ submit(command: AcquisitionCaseSubmitCommand): Promise<AcquisitionCaseResult<AcquisitionCaseProjection>>;
40
+ transition(command: AcquisitionCaseTransitionCommand): Promise<AcquisitionCaseResult<AcquisitionCaseProjection>>;
41
+ }
42
+ export type { AcquisitionCaseFailureKind, AcquisitionCaseResult, AcquisitionCaseListQuery, AcquisitionCaseGetQuery, } from "../contracts/index.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
2
+ import type { AcquisitionEvidenceDirectDeps } from "./directPorts.js";
3
+ export declare function createAcquisitionSurveySubmitDirectHandler(options: AcquisitionEvidenceDirectDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
4
+ export declare function createAcquisitionSurveyListDirectHandler(options: AcquisitionEvidenceDirectDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
5
+ export declare function createAcquisitionNewsletterConsentDirectHandler(options: AcquisitionEvidenceDirectDeps): (value: unknown, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,100 @@
1
+ import { acquisitionNewsletterConsentEventSchema, acquisitionSurveyListQuerySchema, acquisitionSurveySubmitSchema } from "../acquisitionEvidenceSchemas.js";
2
+ export function createAcquisitionSurveySubmitDirectHandler(options) {
3
+ const { respond, resolveBinding } = options;
4
+ const { sendBffSuccess, sendMethodNotAllowed } = respond;
5
+ return async (req, res) => {
6
+ if (req.method !== "POST")
7
+ return sendMethodNotAllowed(res, ["POST"]);
8
+ const request = acquisitionSurveySubmitSchema.safeParse(req.body);
9
+ const idempotencyKey = firstHeader(req.headers["idempotency-key"]);
10
+ if (!request.success || !idempotencyKey)
11
+ return badRequest(res, respond, request.success ? undefined : request.error.flatten());
12
+ const binding = await bindingOrError(res, respond, resolveBinding);
13
+ if (!binding)
14
+ return;
15
+ const result = await safeOperation(() => binding.submitSurvey({ ...request.data, idempotencyKey }));
16
+ if (!result)
17
+ return unavailable(res, respond);
18
+ if (result.ok === false)
19
+ return failure(res, respond, result.error.kind);
20
+ sendBffSuccess(res, result.value);
21
+ };
22
+ }
23
+ export function createAcquisitionSurveyListDirectHandler(options) {
24
+ const { respond, authorize, resolveBinding } = options;
25
+ const { sendBffSuccess, sendMethodNotAllowed } = respond;
26
+ return async (req, res) => {
27
+ if (req.method !== "GET")
28
+ return sendMethodNotAllowed(res, ["GET"]);
29
+ const actorReference = await authorize(req, res);
30
+ if (!actorReference)
31
+ return;
32
+ const request = acquisitionSurveyListQuerySchema.safeParse({
33
+ view: first(req.query.view), surveyType: first(req.query.surveyType), limit: first(req.query.limit) ?? "50",
34
+ });
35
+ if (!request.success)
36
+ return badRequest(res, respond, request.error.flatten());
37
+ const binding = await bindingOrError(res, respond, resolveBinding);
38
+ if (!binding)
39
+ return;
40
+ const result = await safeOperation(() => binding.listSurveys({ ...request.data, actorReference }));
41
+ if (!result)
42
+ return unavailable(res, respond);
43
+ if (result.ok === false)
44
+ return failure(res, respond, result.error.kind);
45
+ sendBffSuccess(res, result.value);
46
+ };
47
+ }
48
+ export function createAcquisitionNewsletterConsentDirectHandler(options) {
49
+ const { respond, resolveBinding } = options;
50
+ const { sendBffSuccess } = respond;
51
+ return async (value, res) => {
52
+ const request = acquisitionNewsletterConsentEventSchema.safeParse(value);
53
+ if (!request.success)
54
+ return badRequest(res, respond, request.error.flatten());
55
+ const binding = await bindingOrError(res, respond, resolveBinding);
56
+ if (!binding)
57
+ return;
58
+ const result = await safeOperation(() => binding.applyNewsletterConsent(request.data));
59
+ if (!result)
60
+ return unavailable(res, respond);
61
+ if (result.ok === false)
62
+ return failure(res, respond, result.error.kind);
63
+ sendBffSuccess(res, result.value);
64
+ };
65
+ }
66
+ async function bindingOrError(res, respond, resolve) {
67
+ try {
68
+ const binding = await resolve();
69
+ if (binding)
70
+ return binding;
71
+ }
72
+ catch { /* fail closed below */ }
73
+ unavailable(res, respond);
74
+ return null;
75
+ }
76
+ async function safeOperation(work) {
77
+ try {
78
+ return await work();
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ function failure(res, respond, kind) {
85
+ if (kind === "invalid")
86
+ return badRequest(res, respond);
87
+ if (kind === "not_found")
88
+ return respond.sendBffError(res, "NOT_FOUND", "Acquisition evidence subject not found");
89
+ if (kind === "conflict")
90
+ return respond.sendBffError(res, "CONFLICT", "Acquisition evidence request conflicts with prior state");
91
+ unavailable(res, respond);
92
+ }
93
+ function badRequest(res, respond, details) {
94
+ respond.sendBffError(res, "BAD_REQUEST", "Invalid acquisition evidence request", { details });
95
+ }
96
+ function unavailable(res, respond) {
97
+ respond.sendBffError(res, "UPSTREAM_UNAVAILABLE", "Acquisition evidence service unavailable");
98
+ }
99
+ function first(value) { return Array.isArray(value) ? value[0] : value; }
100
+ function firstHeader(value) { return first(value); }
@@ -0,0 +1,3 @@
1
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
2
+ import type { AnalyticsContextHandlerDeps } from "./analyticsContextPorts.js";
3
+ export declare function createAnalyticsContextHandler(deps: AnalyticsContextHandlerDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { z } from "../validation.js";
2
+ export function createAnalyticsContextHandler(deps) {
3
+ const { respond, isEnabled, resolveStore, readAccessToken, resolveTrustedSubject, capabilityCrypto, cookieName, clientIdField } = deps;
4
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = respond;
5
+ const requestSchema = z.object({
6
+ granted: z.boolean(),
7
+ [clientIdField]: z.string().regex(/^\d+\.\d+$/).max(64).optional(),
8
+ explicitGrant: z.boolean().default(false),
9
+ }).strict();
10
+ return async (req, res) => {
11
+ if (req.method !== "POST")
12
+ return sendMethodNotAllowed(res, ["POST"]);
13
+ if (!isEnabled())
14
+ return sendBffError(res, "NOT_FOUND", "Analytics context unavailable");
15
+ // No URL, IP, contact, session, form or account data is accepted or logged.
16
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : "";
17
+ if (!origin || origin !== `https://${req.headers.host}`)
18
+ return sendBffError(res, "FORBIDDEN", "Same-origin request required");
19
+ const parsed = requestSchema.safeParse(req.body);
20
+ const clientId = parsed.success ? parsed.data[clientIdField] : undefined;
21
+ if (!parsed.success || (parsed.data.granted && !clientId))
22
+ return sendBffError(res, "BAD_REQUEST", "Invalid analytics context");
23
+ const store = resolveStore();
24
+ if (!store)
25
+ return sendBffError(res, "UPSTREAM_UNAVAILABLE", "Analytics context unavailable");
26
+ // The computed shape key widens inference; the strict schema validates these fixed fields.
27
+ const data = parsed.data;
28
+ let hash = contextHash(typeof req.headers.cookie === "string" ? req.headers.cookie : undefined, cookieName, (value) => capabilityCrypto.hashToken(value));
29
+ // Explicit user re-consent gets a new capability; withdrawn old bindings stay withdrawn.
30
+ let token = null;
31
+ if (data.granted && !hash) {
32
+ token = capabilityCrypto.mintToken();
33
+ hash = capabilityCrypto.hashToken(token);
34
+ }
35
+ if (!hash) {
36
+ sendBffSuccess(res, { active: false });
37
+ return;
38
+ }
39
+ try {
40
+ const accessToken = readAccessToken(req);
41
+ const subject = data.granted && accessToken ? await resolveTrustedSubject(accessToken) : null;
42
+ if (accessToken && data.granted && !subject)
43
+ return sendBffError(res, "UNAUTHORIZED", "Customer session required");
44
+ let active = await store.syncContext({ tokenHash: hash, granted: data.granted, clientId: clientId ?? null });
45
+ if (active !== true && data.granted && data.explicitGrant && !token) {
46
+ token = capabilityCrypto.mintToken();
47
+ hash = capabilityCrypto.hashToken(token);
48
+ active = await store.syncContext({ tokenHash: hash, granted: true, clientId: clientId });
49
+ }
50
+ if (active === true && subject)
51
+ await store.enrollSubscriptions({ subject, contextHash: hash });
52
+ if (token && active === true)
53
+ res.setHeader("Set-Cookie", `${cookieName}=${token}; Path=/; Max-Age=31536000; HttpOnly; Secure; SameSite=Lax`);
54
+ res.setHeader("Cache-Control", "no-store");
55
+ sendBffSuccess(res, { active: active === true });
56
+ }
57
+ catch {
58
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Analytics context unavailable");
59
+ }
60
+ };
61
+ }
62
+ function contextHash(cookie, cookieName, hashToken) {
63
+ const token = cookie?.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${cookieName}=`))?.split("=")[1];
64
+ return token && /^[a-f0-9]{64}$/.test(token) ? hashToken(token) : null;
65
+ }