@aftership/ads-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ import type { Decision, DecisionRequest } from "./types.js";
2
+ /**
3
+ * Transport is injected by the host. The decisions endpoint is an intranet
4
+ * API (07-13 §1) — browsers never call it directly; a BTP/BFF proxy, an SSR
5
+ * data loader, or (for now) a mock supplies the payload. The fetcher may
6
+ * return either the bare decision object or the meta/data envelope.
7
+ */
8
+ export type DecisionFetcher = (request: DecisionRequest, options: {
9
+ signal: AbortSignal;
10
+ }) => Promise<unknown>;
11
+ export interface AdsClientOptions {
12
+ fetcher: DecisionFetcher;
13
+ /**
14
+ * Overall budget for one decision, ms. Contract end-to-end budget is ~2s
15
+ * (07-13 §9); hosts sit above it, so default leaves headroom.
16
+ */
17
+ timeoutMs?: number;
18
+ /** Observability hook; errors never propagate to the host (fail-safe). */
19
+ onError?: (error: unknown) => void;
20
+ }
21
+ export interface AdsClient {
22
+ /**
23
+ * Fetch + normalize one decision. Never throws and never rejects:
24
+ * timeout, transport failure, and malformed payloads all resolve to an
25
+ * empty decision so ad failures cannot break the host surface.
26
+ */
27
+ getDecision(request: DecisionRequest): Promise<Decision>;
28
+ }
29
+ export declare function createAdsClient(options: AdsClientOptions): AdsClient;
30
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG5D;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,KAC7B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC1D;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAyBpE"}
package/dist/client.js ADDED
@@ -0,0 +1,28 @@
1
+ import { EMPTY_DECISION, sanitizeDecision } from "./sanitize.js";
2
+ const DEFAULT_TIMEOUT_MS = 2500;
3
+ export function createAdsClient(options) {
4
+ const { fetcher, timeoutMs = DEFAULT_TIMEOUT_MS, onError } = options;
5
+ return {
6
+ async getDecision(request) {
7
+ const controller = new AbortController();
8
+ const timer = setTimeout(() => controller.abort(new Error(`ads decision timed out after ${timeoutMs}ms`)), timeoutMs);
9
+ try {
10
+ const raw = await fetcher(request, { signal: controller.signal });
11
+ return sanitizeDecision(raw);
12
+ }
13
+ catch (error) {
14
+ try {
15
+ onError?.(error);
16
+ }
17
+ catch {
18
+ // observability must never break the fail-safe path
19
+ }
20
+ return EMPTY_DECISION;
21
+ }
22
+ finally {
23
+ clearTimeout(timer);
24
+ }
25
+ },
26
+ };
27
+ }
28
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAiCjE,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,kBAAkB,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAErE,OAAO;QACL,KAAK,CAAC,WAAW,CAAC,OAAwB;YACxC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,gCAAgC,SAAS,IAAI,CAAC,CAAC,EAChF,SAAS,CACV,CAAC;YACF,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;gBAClE,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC;oBACH,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;gBACnB,CAAC;gBAAC,MAAM,CAAC;oBACP,oDAAoD;gBACtD,CAAC;gBACD,OAAO,cAAc,CAAC;YACxB,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export type { AdsEvent, AdsEventSink, AdsEventType, AdsTheme, Attribution, Decision, DecisionRequest, Offer, Placement, TokenPayload, } from "./types.js";
2
+ export { createAdsClient } from "./client.js";
3
+ export type { AdsClient, AdsClientOptions, DecisionFetcher } from "./client.js";
4
+ export { createMockFetcher, mockDecision } from "./mock.js";
5
+ export type { MockFetcherOptions, MockScenario } from "./mock.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,eAAe,EACf,KAAK,EACL,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhF,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5D,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createAdsClient } from "./client.js";
2
+ export { createMockFetcher, mockDecision } from "./mock.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
package/dist/mock.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { Decision } from "./types.js";
2
+ import type { DecisionFetcher } from "./client.js";
3
+ export type MockScenario = "default" | "single" | "multi" | "empty" | "minimal" | "no-image" | "long-text" | "cjk" | "emoji";
4
+ /** Build a deterministic mock decision for a scenario. */
5
+ export declare function mockDecision(scenario?: MockScenario): Decision;
6
+ export interface MockFetcherOptions {
7
+ /**
8
+ * Force one scenario. Without it the fetcher mirrors expected production
9
+ * behaviour: email gets a single offer, btp/ocp get the multi-offer list.
10
+ */
11
+ scenario?: MockScenario;
12
+ /** Simulated network latency in ms. */
13
+ delayMs?: number;
14
+ }
15
+ export declare function createMockFetcher(options?: MockFetcherOptions): DecisionFetcher;
16
+ //# sourceMappingURL=mock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,QAAQ,EAAS,MAAM,YAAY,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAiInD,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,QAAQ,GACR,OAAO,GACP,OAAO,GACP,SAAS,GACT,UAAU,GACV,WAAW,GACX,KAAK,GACL,OAAO,CAAC;AAEZ,0DAA0D;AAC1D,wBAAgB,YAAY,CAAC,QAAQ,GAAE,YAAwB,GAAG,QAAQ,CA0BzE;AAED,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,eAAe,CAmBnF"}
package/dist/mock.js ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Mock decision source — stands in for `POST /v1/decisions` until the
3
+ * backend (and its mock adapter deployment) is available. Shapes follow the
4
+ * frozen contract exactly; copy mirrors the Figma design fixtures so the
5
+ * renderers can be verified against the mock end-to-end.
6
+ */
7
+ const ATTRIBUTION_TEXT = {
8
+ copy: "Powered by AfterShip",
9
+ logo_url: null,
10
+ target_url: "https://www.aftership.com",
11
+ };
12
+ const ATTRIBUTION_WITH_LOGO = {
13
+ copy: "Powered by",
14
+ logo_url: "https://picsum.photos/seed/adslogo/96/32",
15
+ target_url: "https://www.aftership.com",
16
+ };
17
+ const BILLY_REID = {
18
+ id: "mock-offer-billy-reid",
19
+ advertiser: { name: "BILLY REID", logo_url: null },
20
+ heading: "Classic American style — 15% off select pieces",
21
+ subheading: "Refined wardrobe staples, made to last. As a thank-you for your recent order, enjoy 15% off full-priced products this week.",
22
+ target_url: "https://example.com/offers/billy-reid?click_id=mock-click-billy-reid",
23
+ cta: { accept_copy: "Claim Offer", decline_copy: "No thanks" },
24
+ images: [{ url: "https://picsum.photos/seed/billyreid/640/440" }],
25
+ disclosure: {
26
+ copy: "Terms & Conditions",
27
+ target_url: null,
28
+ content: "Savings compared to regular full price. Offer valid this week only.",
29
+ },
30
+ };
31
+ const GAINFUL = {
32
+ id: "mock-offer-gainful",
33
+ advertiser: { name: "GAINFUL", logo_url: null },
34
+ heading: "Personalized nutrition, built for you",
35
+ subheading: "Tailored blends based on your goals. Try your first order with 20% off, just for AfterShip shoppers.",
36
+ target_url: "https://example.com/offers/gainful?click_id=mock-click-gainful",
37
+ cta: { accept_copy: "Claim Offer", decline_copy: "No thanks" },
38
+ images: [{ url: "https://picsum.photos/seed/gainful/640/440" }],
39
+ disclosure: null,
40
+ };
41
+ const RAKUTEN = {
42
+ id: "mock-offer-rakuten",
43
+ advertiser: { name: "Rakuten", logo_url: "https://picsum.photos/seed/rakuten/64/64" },
44
+ heading: "BlendJet — you're eligible for $10 cash back",
45
+ subheading: "You unlocked $10 cash back on your next BlendJet order. Activate it before it expires.",
46
+ target_url: "https://example.com/offers/rakuten?click_id=mock-click-rakuten",
47
+ cta: { accept_copy: "Claim cash back", decline_copy: "No thanks" },
48
+ images: [{ url: "https://picsum.photos/seed/blendjet/640/440" }],
49
+ disclosure: {
50
+ copy: "Terms & Conditions",
51
+ target_url: "https://example.com/terms",
52
+ content: null,
53
+ },
54
+ };
55
+ const ATLAS_COFFEE = {
56
+ id: "mock-offer-atlas",
57
+ advertiser: { name: "ATLAS COFFEE CLUB", logo_url: null },
58
+ heading: "Taste coffee from around the world",
59
+ subheading: "First bag 50% off, roasted to order and shipped free.",
60
+ target_url: "https://example.com/offers/atlas?click_id=mock-click-atlas",
61
+ cta: { accept_copy: "Get 50% off", decline_copy: null },
62
+ images: [{ url: "https://picsum.photos/seed/atlascoffee/640/440" }],
63
+ disclosure: null,
64
+ };
65
+ /** Contract-guaranteed fields only — exercises the "缺失不展示" render paths. */
66
+ const MINIMAL = {
67
+ id: "mock-offer-minimal",
68
+ advertiser: null,
69
+ heading: null,
70
+ subheading: null,
71
+ target_url: "https://example.com/offers/minimal?click_id=mock-click-minimal",
72
+ cta: null,
73
+ images: [],
74
+ disclosure: null,
75
+ };
76
+ const NO_IMAGE = {
77
+ ...BILLY_REID,
78
+ id: "mock-offer-no-image",
79
+ images: [],
80
+ };
81
+ const LONG_TEXT = {
82
+ id: "mock-offer-long-text",
83
+ advertiser: { name: "THE EXCEEDINGLY LONG BRAND NAME COMPANY OF AMERICA", logo_url: null },
84
+ heading: "An extraordinarily long headline that just keeps going to make absolutely sure multi-line truncation works the way the design intends it to work",
85
+ subheading: "A description that is deliberately verbose. It rambles about premium materials, thoughtful craftsmanship, generous return windows, carbon-neutral shipping, and a founder story nobody asked for, so that three-line clamping has plenty of text to clamp.",
86
+ target_url: "https://example.com/offers/long?click_id=mock-click-long",
87
+ cta: { accept_copy: "Claim this remarkably good offer", decline_copy: "No thanks" },
88
+ images: [{ url: "https://picsum.photos/seed/longtext/640/440" }],
89
+ disclosure: null,
90
+ };
91
+ const CJK = {
92
+ id: "mock-offer-cjk",
93
+ advertiser: { name: "山田珈琲", logo_url: null },
94
+ heading: "感谢您的订单——精选咖啡豆限时 8 折",
95
+ subheading: "来自云南高山庄园的当季豆,下单即享顺丰包邮,本周专属优惠。",
96
+ target_url: "https://example.com/offers/cjk?click_id=mock-click-cjk",
97
+ cta: { accept_copy: "立即领取", decline_copy: "暂不需要" },
98
+ images: [{ url: "https://picsum.photos/seed/cjkcoffee/640/440" }],
99
+ disclosure: null,
100
+ };
101
+ const EMOJI = {
102
+ id: "mock-offer-emoji",
103
+ advertiser: { name: "SUNNY CLUB", logo_url: null },
104
+ heading: "Save 20% 🎉 on your summer order ☀️",
105
+ subheading: "Members get early access 🚀 plus free returns 💯 all season long.",
106
+ target_url: "https://example.com/offers/emoji?click_id=mock-click-emoji",
107
+ cta: { accept_copy: "Claim Offer 🎁", decline_copy: "No thanks" },
108
+ images: [{ url: "https://picsum.photos/seed/sunny/640/440" }],
109
+ disclosure: null,
110
+ };
111
+ /** Build a deterministic mock decision for a scenario. */
112
+ export function mockDecision(scenario = "default") {
113
+ const base = (offers, attribution = ATTRIBUTION_TEXT) => ({
114
+ decision_id: `mock-decision-${scenario}`,
115
+ offers,
116
+ attribution,
117
+ });
118
+ switch (scenario) {
119
+ case "single":
120
+ return base([BILLY_REID]);
121
+ case "multi":
122
+ return base([GAINFUL, BILLY_REID, RAKUTEN, ATLAS_COFFEE], ATTRIBUTION_WITH_LOGO);
123
+ case "empty":
124
+ return { decision_id: `mock-decision-${scenario}`, offers: [], attribution: null };
125
+ case "minimal":
126
+ return base([MINIMAL]);
127
+ case "no-image":
128
+ return base([NO_IMAGE]);
129
+ case "long-text":
130
+ return base([LONG_TEXT]);
131
+ case "cjk":
132
+ return base([CJK]);
133
+ case "emoji":
134
+ return base([EMOJI]);
135
+ case "default":
136
+ return base([GAINFUL, BILLY_REID, RAKUTEN, ATLAS_COFFEE]);
137
+ }
138
+ }
139
+ export function createMockFetcher(options = {}) {
140
+ const { scenario, delayMs = 0 } = options;
141
+ return async (request, { signal }) => {
142
+ if (delayMs > 0) {
143
+ await new Promise((resolve, reject) => {
144
+ const timer = setTimeout(resolve, delayMs);
145
+ signal.addEventListener("abort", () => {
146
+ clearTimeout(timer);
147
+ reject(signal.reason instanceof Error ? signal.reason : new Error("aborted"));
148
+ }, { once: true });
149
+ });
150
+ }
151
+ const effective = scenario ?? (request.placement === "email" ? "single" : "default");
152
+ return mockDecision(effective);
153
+ };
154
+ }
155
+ //# sourceMappingURL=mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.js","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Attribution, Decision, Offer } from "./types.js";
2
+ /** Frozen empty decision used for every fail-safe path. */
3
+ export declare const EMPTY_DECISION: Decision;
4
+ /**
5
+ * Normalize a raw offer. Returns null when the contract-guaranteed fields
6
+ * (`id`, `target_url`) are missing — such offers are dropped defensively.
7
+ */
8
+ export declare function sanitizeOffer(raw: unknown): Offer | null;
9
+ export declare function sanitizeAttribution(raw: unknown): Attribution | null;
10
+ /**
11
+ * Normalize an untrusted decision payload to the frozen contract shape.
12
+ * Accepts either the bare decision object or the gopkg `api.Response`
13
+ * meta/data envelope and unwraps the latter.
14
+ */
15
+ export declare function sanitizeDecision(raw: unknown): Decision;
16
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EACX,QAAQ,EACR,KAAK,EAIN,MAAM,YAAY,CAAC;AAEpB,2DAA2D;AAC3D,eAAO,MAAM,cAAc,EAAE,QAI3B,CAAC;AA+CH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,GAAG,IAAI,CAexD;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAOpE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAoBvD"}
@@ -0,0 +1,115 @@
1
+ /** Frozen empty decision used for every fail-safe path. */
2
+ export const EMPTY_DECISION = Object.freeze({
3
+ decision_id: "",
4
+ offers: [],
5
+ attribution: null,
6
+ });
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ /** Non-empty string or null. Empty strings are treated as missing (ENV-03). */
11
+ function asText(value) {
12
+ return typeof value === "string" && value.length > 0 ? value : null;
13
+ }
14
+ function sanitizeAdvertiser(raw) {
15
+ if (!isRecord(raw))
16
+ return null;
17
+ const name = asText(raw["name"]);
18
+ const logoUrl = asText(raw["logo_url"]);
19
+ if (name === null && logoUrl === null)
20
+ return null;
21
+ return { name, logo_url: logoUrl };
22
+ }
23
+ function sanitizeCta(raw) {
24
+ if (!isRecord(raw))
25
+ return null;
26
+ const accept = asText(raw["accept_copy"]);
27
+ const decline = asText(raw["decline_copy"]);
28
+ if (accept === null && decline === null)
29
+ return null;
30
+ return { accept_copy: accept, decline_copy: decline };
31
+ }
32
+ function sanitizeImages(raw) {
33
+ if (!Array.isArray(raw))
34
+ return [];
35
+ const images = [];
36
+ for (const item of raw) {
37
+ if (!isRecord(item))
38
+ continue;
39
+ const url = asText(item["url"]);
40
+ if (url !== null)
41
+ images.push({ url });
42
+ }
43
+ return images;
44
+ }
45
+ function sanitizeDisclosure(raw) {
46
+ if (!isRecord(raw))
47
+ return null;
48
+ const copy = asText(raw["copy"]);
49
+ const targetUrl = asText(raw["target_url"]);
50
+ const content = asText(raw["content"]);
51
+ if (copy === null && targetUrl === null && content === null)
52
+ return null;
53
+ return { copy, target_url: targetUrl, content };
54
+ }
55
+ /**
56
+ * Normalize a raw offer. Returns null when the contract-guaranteed fields
57
+ * (`id`, `target_url`) are missing — such offers are dropped defensively.
58
+ */
59
+ export function sanitizeOffer(raw) {
60
+ if (!isRecord(raw))
61
+ return null;
62
+ const id = asText(raw["id"]);
63
+ const targetUrl = asText(raw["target_url"]);
64
+ if (id === null || targetUrl === null)
65
+ return null;
66
+ return {
67
+ id,
68
+ advertiser: sanitizeAdvertiser(raw["advertiser"]),
69
+ heading: asText(raw["heading"]),
70
+ subheading: asText(raw["subheading"]),
71
+ target_url: targetUrl,
72
+ cta: sanitizeCta(raw["cta"]),
73
+ images: sanitizeImages(raw["images"]),
74
+ disclosure: sanitizeDisclosure(raw["disclosure"]),
75
+ };
76
+ }
77
+ export function sanitizeAttribution(raw) {
78
+ if (!isRecord(raw))
79
+ return null;
80
+ const copy = asText(raw["copy"]);
81
+ const logoUrl = asText(raw["logo_url"]);
82
+ const targetUrl = asText(raw["target_url"]);
83
+ if (copy === null && logoUrl === null && targetUrl === null)
84
+ return null;
85
+ return { copy, logo_url: logoUrl, target_url: targetUrl };
86
+ }
87
+ /**
88
+ * Normalize an untrusted decision payload to the frozen contract shape.
89
+ * Accepts either the bare decision object or the gopkg `api.Response`
90
+ * meta/data envelope and unwraps the latter.
91
+ */
92
+ export function sanitizeDecision(raw) {
93
+ if (!isRecord(raw))
94
+ return EMPTY_DECISION;
95
+ let body = raw;
96
+ const data = raw["data"];
97
+ if (isRecord(data) && ("offers" in data || "decision_id" in data)) {
98
+ body = data;
99
+ }
100
+ const offersRaw = body["offers"];
101
+ const offers = [];
102
+ if (Array.isArray(offersRaw)) {
103
+ for (const item of offersRaw) {
104
+ const offer = sanitizeOffer(item);
105
+ if (offer !== null)
106
+ offers.push(offer);
107
+ }
108
+ }
109
+ return {
110
+ decision_id: asText(body["decision_id"]) ?? "",
111
+ offers,
112
+ attribution: sanitizeAttribution(body["attribution"]),
113
+ };
114
+ }
115
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Frozen decision API contract types.
3
+ *
4
+ * Source of truth: docs/design/2026-07-13-ads-s2s-decision-api-design.md (§3.3).
5
+ * Field semantics:
6
+ * - Only `Offer.id` and `Offer.target_url` are guaranteed. Every other field may
7
+ * be missing (`null` for objects/strings, `[]` for arrays). Renderers must
8
+ * skip missing fields instead of assuming presence.
9
+ * - `Offer.target_url` must be used verbatim — it carries vendor attribution
10
+ * parameters (click_id); rewriting it breaks click billing.
11
+ * - `Decision.attribution` ("Powered by …") is a compliance element that must
12
+ * be displayed alongside the ad whenever provided.
13
+ */
14
+ export type Placement = "btp" | "ocp" | "email";
15
+ export interface DecisionRequest {
16
+ /**
17
+ * 32-char organization id. Required. Logical field of the request; at the
18
+ * wire it travels via the `am-organization-id` header, not the body — the
19
+ * injected fetcher lifts it out (contract 07-13 §2, backend #64).
20
+ */
21
+ organization_id: string;
22
+ /** Channel placement. Required. */
23
+ placement: Placement;
24
+ /** Connector (中台) order id. Optional; server enriches from it. */
25
+ order_id?: string;
26
+ /** Buyer email in plaintext (intranet only; hashed at vendor egress). */
27
+ email?: string;
28
+ }
29
+ export interface Advertiser {
30
+ name: string | null;
31
+ logo_url: string | null;
32
+ }
33
+ export interface OfferCta {
34
+ /** Accept-button copy, vendor/advertiser approved — do not invent replacements. */
35
+ accept_copy: string | null;
36
+ decline_copy: string | null;
37
+ }
38
+ export interface OfferImage {
39
+ url: string;
40
+ }
41
+ export interface OfferDisclosure {
42
+ /** Entry copy, e.g. "Terms & Conditions". */
43
+ copy: string | null;
44
+ target_url: string | null;
45
+ /** Long-form disclosure text (shown expanded / in a layer). */
46
+ content: string | null;
47
+ }
48
+ export interface Offer {
49
+ /** Unique offer id. Guaranteed. */
50
+ id: string;
51
+ advertiser: Advertiser | null;
52
+ heading: string | null;
53
+ subheading: string | null;
54
+ /** Click-through URL. Guaranteed. Must be used verbatim (contains click_id). */
55
+ target_url: string;
56
+ cta: OfferCta | null;
57
+ /** Creative images in order; take the first. Empty = text-only card. */
58
+ images: OfferImage[];
59
+ disclosure: OfferDisclosure | null;
60
+ }
61
+ export interface Attribution {
62
+ /** e.g. "Powered by". Compliance: must render alongside the ad when present. */
63
+ copy: string | null;
64
+ logo_url: string | null;
65
+ target_url: string | null;
66
+ }
67
+ export interface Decision {
68
+ /** Decision id — anchor for future event reporting & reconciliation. */
69
+ decision_id: string;
70
+ /** Ordered offers. Empty = fail-safe no-fill: render nothing. */
71
+ offers: Offer[];
72
+ attribution: Attribution | null;
73
+ }
74
+ /**
75
+ * Theme tokens (parent design §8). Merge order: host input > backend config
76
+ * defaults (config API pending review) > built-in defaults.
77
+ *
78
+ * Deliberately coarse-grained: the CTA button inherits `primaryColor`
79
+ * (background) and `borderRadius` instead of having its own bg/radius
80
+ * tokens. Shopify OCP does not consume these at all
81
+ * (sandbox forbids CSS — 07-10 §6).
82
+ */
83
+ export interface AdsTheme {
84
+ /** Accent color; the CTA button background inherits this. */
85
+ primaryColor?: string;
86
+ backgroundColor?: string;
87
+ cardColor?: string;
88
+ /** Title color; description derives from it (0.72 alpha) unless overridden. */
89
+ textColor?: string;
90
+ /** Description/body color. Absent → derived from `textColor`. */
91
+ descriptionColor?: string;
92
+ /** CTA button label color. Absent → built-in white. */
93
+ buttonTextColor?: string;
94
+ /** Corner radius of the offer image and CTA button (the section is flat — 07-17 design). */
95
+ borderRadius?: number;
96
+ fontFamily?: string;
97
+ density?: "compact" | "comfortable";
98
+ lang?: string;
99
+ }
100
+ /**
101
+ * Token payload carried by the email render token (07-13 §4). Fields mirror
102
+ * the issue request verbatim — the token never carries `placement`; the
103
+ * render service fills `"email"` itself (§4.2).
104
+ */
105
+ export interface TokenPayload {
106
+ organization_id: string;
107
+ order_id: string | null;
108
+ email: string | null;
109
+ store_platform: string | null;
110
+ store_key: string | null;
111
+ }
112
+ /**
113
+ * Renderer → host event callback. Reporting endpoints (e/imp, e/click) land
114
+ * additively after the data-reporting review; until then hosts typically log
115
+ * or no-op. `decision_id` / `offer_id` are the contract's attribution anchors.
116
+ */
117
+ export type AdsEventType = "impression" | "click" | "no_fill";
118
+ export interface AdsEvent {
119
+ type: AdsEventType;
120
+ decision_id: string;
121
+ offer_id?: string;
122
+ placement?: Placement;
123
+ timestamp: number;
124
+ meta?: Record<string, unknown>;
125
+ }
126
+ export type AdsEventSink = (event: AdsEvent) => void;
127
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Frozen decision API contract types.
3
+ *
4
+ * Source of truth: docs/design/2026-07-13-ads-s2s-decision-api-design.md (§3.3).
5
+ * Field semantics:
6
+ * - Only `Offer.id` and `Offer.target_url` are guaranteed. Every other field may
7
+ * be missing (`null` for objects/strings, `[]` for arrays). Renderers must
8
+ * skip missing fields instead of assuming presence.
9
+ * - `Offer.target_url` must be used verbatim — it carries vendor attribution
10
+ * parameters (click_id); rewriting it breaks click billing.
11
+ * - `Decision.attribution` ("Powered by …") is a compliance element that must
12
+ * be displayed alongside the ad whenever provided.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@aftership/ads-core",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Headless isomorphic core for AfterShip Ads self-render channel: frozen decision contract types, fail-safe client with injectable fetcher, mock decision source, and event hooks. Zero dependencies, zero DOM.",
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.build.json",
24
+ "dev": "tsc -p tsconfig.build.json --watch",
25
+ "typecheck": "tsc -p tsconfig.json --noEmit",
26
+ "test": "vitest run"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^5.7.2",
30
+ "vitest": "^2.1.8"
31
+ }
32
+ }