@getrheo/attribution 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,328 @@
1
+ import { FlowManifest } from '@getrheo/contracts/manifest';
2
+ import { DecisionExpr, DecisionNode } from '@getrheo/contracts/decisions';
3
+ import { z } from 'zod';
4
+
5
+ /**
6
+ * Universal SDK attribute keys for mobile attribution / deep-link context.
7
+ * Provider adapters (AppsFlyer, Adjust, …) map native payloads → these names.
8
+ *
9
+ * Host-supplied `sdkAttributes` should avoid these prefixes to prevent clashes:
10
+ * `attribution.`, `acquisition.`, `link.`
11
+ */
12
+ /** Metadata about which MMP / SDK produced the current snapshot (string id, e.g. `appsflyer`). */
13
+ declare const ATTR_KEY_PROVIDER: "attribution.provider";
14
+ /** Whether the install/open was classified as organic (`boolean` in decision eval). */
15
+ declare const ATTR_KEY_IS_ORGANIC: "attribution.isOrganic";
16
+ /** Optional coarse confidence hint from the provider (`high` | `medium` | `low`). */
17
+ declare const ATTR_KEY_CONFIDENCE: "attribution.confidence";
18
+ /** Provider-specific match type string when available (probabilistic, etc.). */
19
+ declare const ATTR_KEY_MATCH_TYPE: "attribution.matchType";
20
+ /** Media / network / channel — paid ads, social, search, etc. */
21
+ declare const ATTR_KEY_ACQ_SOURCE: "acquisition.source";
22
+ declare const ATTR_KEY_ACQ_CAMPAIGN: "acquisition.campaign";
23
+ declare const ATTR_KEY_ACQ_CAMPAIGN_ID: "acquisition.campaignId";
24
+ declare const ATTR_KEY_ACQ_ADSET: "acquisition.adset";
25
+ declare const ATTR_KEY_ACQ_ADSET_ID: "acquisition.adsetId";
26
+ declare const ATTR_KEY_ACQ_CREATIVE: "acquisition.creative";
27
+ declare const ATTR_KEY_ACQ_CREATIVE_ID: "acquisition.creativeId";
28
+ /** Optional coarse channel bucket (e.g. social, search). */
29
+ declare const ATTR_KEY_ACQ_CHANNEL: "acquisition.channel";
30
+ /**
31
+ * Primary entry identifier from a universal / deep link — same decision surface as campaigns.
32
+ * Semantic: “what route or offer this open represents”.
33
+ */
34
+ declare const ATTR_KEY_LINK_ENTRY: "link.entry";
35
+ /**
36
+ * Flattened dynamic params from links / deferred payloads.
37
+ * Pattern: `link.ext.<paramKey>` where paramKey is provider-normalized (alphanumeric + underscore).
38
+ */
39
+ declare const LINK_EXT_PREFIX: "link.ext.";
40
+ /** Well-known link.param keys (optional convenience aliases providers may map). */
41
+ declare const ATTR_KEY_LINK_REFERRAL: "link.ext.referral_code";
42
+ declare const ATTR_KEY_LINK_PROMO: "link.ext.promo_code";
43
+ declare const ATTR_KEY_LINK_CONTENT_ID: "link.ext.content_id";
44
+
45
+ /**
46
+ * Provider-agnostic attribution context produced by MMP adapters before flattening to sdkAttributes.
47
+ */
48
+ type AttributionConfidenceLevel = 'high' | 'medium' | 'low';
49
+ /** Stable identifier for an attribution SDK integration (`appsflyer`, `adjust`, …). */
50
+ type AttributionProviderId = string;
51
+ interface AttributionFacet {
52
+ /** Organic vs paid/non-organic classification when available. */
53
+ isOrganic?: boolean;
54
+ matchType?: string;
55
+ confidence?: AttributionConfidenceLevel;
56
+ /** Epoch ms when install occurred (from provider when present). */
57
+ installTimestampMs?: number;
58
+ /** Epoch ms for attributed click when present. */
59
+ clickTimestampMs?: number;
60
+ }
61
+ interface AcquisitionFacet {
62
+ source?: string;
63
+ campaign?: string;
64
+ campaignId?: string;
65
+ adset?: string;
66
+ adsetId?: string;
67
+ creative?: string;
68
+ creativeId?: string;
69
+ channel?: string;
70
+ }
71
+ /**
72
+ * Deep links / universal links use the same branching surface as acquisition — primary route plus extensible params.
73
+ */
74
+ interface LinkFacet {
75
+ /** Primary entry / route / deep-link value (harmonized across providers). */
76
+ entry?: string;
77
+ /** Arbitrary key/value pairs (merged into `link.ext.*` flat keys). */
78
+ params?: Record<string, string>;
79
+ }
80
+ /**
81
+ * Single normalized snapshot emitted by an {@link AttributionProviderId}.
82
+ * Multiple facets may be filled from install conversion, deep link, or both.
83
+ */
84
+ interface NormalizedAttributionSnapshot {
85
+ providerId: AttributionProviderId;
86
+ /** When this snapshot was captured on-device (epoch ms). */
87
+ capturedAtMs: number;
88
+ attribution: AttributionFacet;
89
+ acquisition: AcquisitionFacet;
90
+ link: LinkFacet;
91
+ }
92
+
93
+ /**
94
+ * Maps a normalized snapshot into flat `sdkAttributes` entries for decision nodes.
95
+ * Omits undefined leaves; booleans stay booleans for boolean predicates.
96
+ */
97
+ declare const flattenAttributionSnapshotToSdkAttributes: (snapshot: NormalizedAttributionSnapshot) => Record<string, unknown>;
98
+ /**
99
+ * Returns true if the snapshot carries any branching-relevant data beyond provider id alone.
100
+ */
101
+ declare const normalizedSnapshotHasSignal: (snapshot: NormalizedAttributionSnapshot) => boolean;
102
+
103
+ /**
104
+ * Merges two snapshots from the same session/provider layer (e.g. install + deep link).
105
+ * Later `overlay` wins on conflicting scalar facets; link params are shallow-merged with overlay winning per key.
106
+ */
107
+ declare const mergeAttributionSnapshots: (base: NormalizedAttributionSnapshot | null, overlay: NormalizedAttributionSnapshot) => NormalizedAttributionSnapshot;
108
+
109
+ /** Suggested `attribution.provider` string ids for dashboard pickers (extend as we add adapters). */
110
+ declare const SUGGESTED_ATTRIBUTION_PROVIDER_IDS: readonly string[];
111
+ /** Values that may appear at `attribution.confidence` after flattening. */
112
+ declare const ATTRIBUTION_CONFIDENCE_LEVELS: readonly AttributionConfidenceLevel[];
113
+ /** Human-readable reference for dashboard + docs (SDK decision variables). */
114
+ declare const ATTRIBUTION_SDK_KEYS_DOC: ReadonlyArray<{
115
+ key: string;
116
+ description: string;
117
+ }>;
118
+ /**
119
+ * Catalog keys editors should offer for attribution / acquisition / deep-link branching
120
+ * (excludes template rows like `link.ext.<param>` — use a custom `link.ext.*` key for those).
121
+ */
122
+ declare const ATTRIBUTION_PREDEFINED_DECISION_KEYS: ReadonlyArray<{
123
+ key: string;
124
+ description: string;
125
+ }>;
126
+ declare const isAttributionPredefinedSdkKey: (key: string) => boolean;
127
+ declare const attributionPredefinedKeyDescription: (key: string) => string | undefined;
128
+
129
+ /** Canonical prefixes for MMP / deep-link context merged into SDK attributes. */
130
+ declare const ATTRIBUTION_CONTEXT_KEY_PREFIXES: readonly ["attribution.", "acquisition.", "link."];
131
+ declare const sdkKeyUsesAttributionContext: (key: string) => boolean;
132
+ declare const decisionExpressionUsesAttributionContext: (expr: DecisionExpr) => boolean;
133
+ /** Default `next` target from the manifest entry screen (screen or decision id), or null. */
134
+ declare const getEntryScreenDefaultJumpTarget: (manifest: FlowManifest) => string | null;
135
+ declare const entryDefaultNextIsDecisionNode: (manifest: FlowManifest) => boolean;
136
+ declare const decisionNodeUsesAttributionContext: (dn: DecisionNode) => boolean;
137
+ /**
138
+ * True when the entry screen's default next hop is a decision node whose rules read
139
+ * attribution / acquisition / link SDK keys — first evaluation can run before deferred MMP data arrives.
140
+ */
141
+ declare const entryDefaultNextIsAttributionDecision: (manifest: FlowManifest) => boolean;
142
+ /** Any decision node references attribution-style SDK keys. */
143
+ declare const manifestUsesAttributionInDecisions: (manifest: FlowManifest) => boolean;
144
+
145
+ /** Bump when the persisted envelope shape changes (invalidates old cache files). */
146
+ declare const ATTRIBUTION_CACHE_SCHEMA_VERSION = 1;
147
+ /** Default TTL for device-side attribution fallback (24 hours). */
148
+ declare const DEFAULT_ATTRIBUTION_CACHE_TTL_MS: number;
149
+ /**
150
+ * Persisted device cache — provider-agnostic normalized snapshot + bookkeeping.
151
+ * Stored as JSON (e.g. AsyncStorage). Not sent to Rheo servers by default.
152
+ */
153
+ declare const AttributionDeviceCacheEnvelopeSchema: z.ZodObject<{
154
+ schemaVersion: z.ZodLiteral<1>;
155
+ /** Epoch ms when this envelope was written. */
156
+ cachedAtMs: z.ZodNumber;
157
+ /** Snapshot identity — must match current adapter output shape. */
158
+ snapshot: z.ZodObject<{
159
+ providerId: z.ZodString;
160
+ capturedAtMs: z.ZodNumber;
161
+ attribution: z.ZodObject<{
162
+ isOrganic: z.ZodOptional<z.ZodBoolean>;
163
+ matchType: z.ZodOptional<z.ZodString>;
164
+ confidence: z.ZodOptional<z.ZodEnum<["high", "medium", "low"]>>;
165
+ installTimestampMs: z.ZodOptional<z.ZodNumber>;
166
+ clickTimestampMs: z.ZodOptional<z.ZodNumber>;
167
+ }, "strip", z.ZodTypeAny, {
168
+ isOrganic?: boolean | undefined;
169
+ matchType?: string | undefined;
170
+ confidence?: "high" | "medium" | "low" | undefined;
171
+ installTimestampMs?: number | undefined;
172
+ clickTimestampMs?: number | undefined;
173
+ }, {
174
+ isOrganic?: boolean | undefined;
175
+ matchType?: string | undefined;
176
+ confidence?: "high" | "medium" | "low" | undefined;
177
+ installTimestampMs?: number | undefined;
178
+ clickTimestampMs?: number | undefined;
179
+ }>;
180
+ acquisition: z.ZodObject<{
181
+ source: z.ZodOptional<z.ZodString>;
182
+ campaign: z.ZodOptional<z.ZodString>;
183
+ campaignId: z.ZodOptional<z.ZodString>;
184
+ adset: z.ZodOptional<z.ZodString>;
185
+ adsetId: z.ZodOptional<z.ZodString>;
186
+ creative: z.ZodOptional<z.ZodString>;
187
+ creativeId: z.ZodOptional<z.ZodString>;
188
+ channel: z.ZodOptional<z.ZodString>;
189
+ }, "strip", z.ZodTypeAny, {
190
+ source?: string | undefined;
191
+ campaign?: string | undefined;
192
+ campaignId?: string | undefined;
193
+ adset?: string | undefined;
194
+ adsetId?: string | undefined;
195
+ creative?: string | undefined;
196
+ creativeId?: string | undefined;
197
+ channel?: string | undefined;
198
+ }, {
199
+ source?: string | undefined;
200
+ campaign?: string | undefined;
201
+ campaignId?: string | undefined;
202
+ adset?: string | undefined;
203
+ adsetId?: string | undefined;
204
+ creative?: string | undefined;
205
+ creativeId?: string | undefined;
206
+ channel?: string | undefined;
207
+ }>;
208
+ link: z.ZodObject<{
209
+ entry: z.ZodOptional<z.ZodString>;
210
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
211
+ }, "strip", z.ZodTypeAny, {
212
+ params?: Record<string, string> | undefined;
213
+ entry?: string | undefined;
214
+ }, {
215
+ params?: Record<string, string> | undefined;
216
+ entry?: string | undefined;
217
+ }>;
218
+ }, "strip", z.ZodTypeAny, {
219
+ attribution: {
220
+ isOrganic?: boolean | undefined;
221
+ matchType?: string | undefined;
222
+ confidence?: "high" | "medium" | "low" | undefined;
223
+ installTimestampMs?: number | undefined;
224
+ clickTimestampMs?: number | undefined;
225
+ };
226
+ acquisition: {
227
+ source?: string | undefined;
228
+ campaign?: string | undefined;
229
+ campaignId?: string | undefined;
230
+ adset?: string | undefined;
231
+ adsetId?: string | undefined;
232
+ creative?: string | undefined;
233
+ creativeId?: string | undefined;
234
+ channel?: string | undefined;
235
+ };
236
+ link: {
237
+ params?: Record<string, string> | undefined;
238
+ entry?: string | undefined;
239
+ };
240
+ providerId: string;
241
+ capturedAtMs: number;
242
+ }, {
243
+ attribution: {
244
+ isOrganic?: boolean | undefined;
245
+ matchType?: string | undefined;
246
+ confidence?: "high" | "medium" | "low" | undefined;
247
+ installTimestampMs?: number | undefined;
248
+ clickTimestampMs?: number | undefined;
249
+ };
250
+ acquisition: {
251
+ source?: string | undefined;
252
+ campaign?: string | undefined;
253
+ campaignId?: string | undefined;
254
+ adset?: string | undefined;
255
+ adsetId?: string | undefined;
256
+ creative?: string | undefined;
257
+ creativeId?: string | undefined;
258
+ channel?: string | undefined;
259
+ };
260
+ link: {
261
+ params?: Record<string, string> | undefined;
262
+ entry?: string | undefined;
263
+ };
264
+ providerId: string;
265
+ capturedAtMs: number;
266
+ }>;
267
+ }, "strip", z.ZodTypeAny, {
268
+ schemaVersion: 1;
269
+ cachedAtMs: number;
270
+ snapshot: {
271
+ attribution: {
272
+ isOrganic?: boolean | undefined;
273
+ matchType?: string | undefined;
274
+ confidence?: "high" | "medium" | "low" | undefined;
275
+ installTimestampMs?: number | undefined;
276
+ clickTimestampMs?: number | undefined;
277
+ };
278
+ acquisition: {
279
+ source?: string | undefined;
280
+ campaign?: string | undefined;
281
+ campaignId?: string | undefined;
282
+ adset?: string | undefined;
283
+ adsetId?: string | undefined;
284
+ creative?: string | undefined;
285
+ creativeId?: string | undefined;
286
+ channel?: string | undefined;
287
+ };
288
+ link: {
289
+ params?: Record<string, string> | undefined;
290
+ entry?: string | undefined;
291
+ };
292
+ providerId: string;
293
+ capturedAtMs: number;
294
+ };
295
+ }, {
296
+ schemaVersion: 1;
297
+ cachedAtMs: number;
298
+ snapshot: {
299
+ attribution: {
300
+ isOrganic?: boolean | undefined;
301
+ matchType?: string | undefined;
302
+ confidence?: "high" | "medium" | "low" | undefined;
303
+ installTimestampMs?: number | undefined;
304
+ clickTimestampMs?: number | undefined;
305
+ };
306
+ acquisition: {
307
+ source?: string | undefined;
308
+ campaign?: string | undefined;
309
+ campaignId?: string | undefined;
310
+ adset?: string | undefined;
311
+ adsetId?: string | undefined;
312
+ creative?: string | undefined;
313
+ creativeId?: string | undefined;
314
+ channel?: string | undefined;
315
+ };
316
+ link: {
317
+ params?: Record<string, string> | undefined;
318
+ entry?: string | undefined;
319
+ };
320
+ providerId: string;
321
+ capturedAtMs: number;
322
+ };
323
+ }>;
324
+ type AttributionDeviceCacheEnvelope = z.infer<typeof AttributionDeviceCacheEnvelopeSchema>;
325
+
326
+ declare const AttributionConfidenceSchema: z.ZodEnum<["high", "medium", "low"]>;
327
+
328
+ export { ATTRIBUTION_CACHE_SCHEMA_VERSION, ATTRIBUTION_CONFIDENCE_LEVELS, ATTRIBUTION_CONTEXT_KEY_PREFIXES, ATTRIBUTION_PREDEFINED_DECISION_KEYS, ATTRIBUTION_SDK_KEYS_DOC, ATTR_KEY_ACQ_ADSET, ATTR_KEY_ACQ_ADSET_ID, ATTR_KEY_ACQ_CAMPAIGN, ATTR_KEY_ACQ_CAMPAIGN_ID, ATTR_KEY_ACQ_CHANNEL, ATTR_KEY_ACQ_CREATIVE, ATTR_KEY_ACQ_CREATIVE_ID, ATTR_KEY_ACQ_SOURCE, ATTR_KEY_CONFIDENCE, ATTR_KEY_IS_ORGANIC, ATTR_KEY_LINK_CONTENT_ID, ATTR_KEY_LINK_ENTRY, ATTR_KEY_LINK_PROMO, ATTR_KEY_LINK_REFERRAL, ATTR_KEY_MATCH_TYPE, ATTR_KEY_PROVIDER, type AcquisitionFacet, type AttributionConfidenceLevel, AttributionConfidenceSchema, type AttributionDeviceCacheEnvelope, AttributionDeviceCacheEnvelopeSchema, type AttributionFacet, type AttributionProviderId, DEFAULT_ATTRIBUTION_CACHE_TTL_MS, LINK_EXT_PREFIX, type LinkFacet, type NormalizedAttributionSnapshot, SUGGESTED_ATTRIBUTION_PROVIDER_IDS, attributionPredefinedKeyDescription, decisionExpressionUsesAttributionContext, decisionNodeUsesAttributionContext, entryDefaultNextIsAttributionDecision, entryDefaultNextIsDecisionNode, flattenAttributionSnapshotToSdkAttributes, getEntryScreenDefaultJumpTarget, isAttributionPredefinedSdkKey, manifestUsesAttributionInDecisions, mergeAttributionSnapshots, normalizedSnapshotHasSignal, sdkKeyUsesAttributionContext };
package/dist/index.js ADDED
@@ -0,0 +1,185 @@
1
+ import { collectDecisionSdkKeys, DecisionNodeIdSchema } from '@getrheo/contracts/decisions';
2
+ import { z } from 'zod';
3
+
4
+ // src/canonicalKeys.ts
5
+ var ATTR_KEY_PROVIDER = "attribution.provider";
6
+ var ATTR_KEY_IS_ORGANIC = "attribution.isOrganic";
7
+ var ATTR_KEY_CONFIDENCE = "attribution.confidence";
8
+ var ATTR_KEY_MATCH_TYPE = "attribution.matchType";
9
+ var ATTR_KEY_ACQ_SOURCE = "acquisition.source";
10
+ var ATTR_KEY_ACQ_CAMPAIGN = "acquisition.campaign";
11
+ var ATTR_KEY_ACQ_CAMPAIGN_ID = "acquisition.campaignId";
12
+ var ATTR_KEY_ACQ_ADSET = "acquisition.adset";
13
+ var ATTR_KEY_ACQ_ADSET_ID = "acquisition.adsetId";
14
+ var ATTR_KEY_ACQ_CREATIVE = "acquisition.creative";
15
+ var ATTR_KEY_ACQ_CREATIVE_ID = "acquisition.creativeId";
16
+ var ATTR_KEY_ACQ_CHANNEL = "acquisition.channel";
17
+ var ATTR_KEY_LINK_ENTRY = "link.entry";
18
+ var LINK_EXT_PREFIX = "link.ext.";
19
+ var ATTR_KEY_LINK_REFERRAL = "link.ext.referral_code";
20
+ var ATTR_KEY_LINK_PROMO = "link.ext.promo_code";
21
+ var ATTR_KEY_LINK_CONTENT_ID = "link.ext.content_id";
22
+
23
+ // src/flatten.ts
24
+ var sanitizeExtKey = (k) => k.replace(/[^a-zA-Z0-9_]/g, "_").slice(0, 64);
25
+ var flattenAttributionSnapshotToSdkAttributes = (snapshot) => {
26
+ const out = {};
27
+ out[ATTR_KEY_PROVIDER] = snapshot.providerId;
28
+ const { attribution, acquisition, link } = snapshot;
29
+ if (typeof attribution.isOrganic === "boolean") {
30
+ out[ATTR_KEY_IS_ORGANIC] = attribution.isOrganic;
31
+ }
32
+ if (attribution.matchType !== void 0 && attribution.matchType !== "") {
33
+ out[ATTR_KEY_MATCH_TYPE] = attribution.matchType;
34
+ }
35
+ if (attribution.confidence !== void 0) {
36
+ out[ATTR_KEY_CONFIDENCE] = attribution.confidence;
37
+ }
38
+ const setStr = (key, v) => {
39
+ if (v !== void 0 && v !== "") out[key] = v;
40
+ };
41
+ setStr(ATTR_KEY_ACQ_SOURCE, acquisition.source);
42
+ setStr(ATTR_KEY_ACQ_CAMPAIGN, acquisition.campaign);
43
+ setStr(ATTR_KEY_ACQ_CAMPAIGN_ID, acquisition.campaignId);
44
+ setStr(ATTR_KEY_ACQ_ADSET, acquisition.adset);
45
+ setStr(ATTR_KEY_ACQ_ADSET_ID, acquisition.adsetId);
46
+ setStr(ATTR_KEY_ACQ_CREATIVE, acquisition.creative);
47
+ setStr(ATTR_KEY_ACQ_CREATIVE_ID, acquisition.creativeId);
48
+ setStr(ATTR_KEY_ACQ_CHANNEL, acquisition.channel);
49
+ setStr(ATTR_KEY_LINK_ENTRY, link.entry);
50
+ const params = link.params ?? {};
51
+ for (const [rawKey, rawVal] of Object.entries(params)) {
52
+ const sk = sanitizeExtKey(rawKey);
53
+ if (!sk) continue;
54
+ if (rawVal !== void 0 && rawVal !== "") {
55
+ out[`${LINK_EXT_PREFIX}${sk}`] = rawVal;
56
+ }
57
+ }
58
+ return out;
59
+ };
60
+ var normalizedSnapshotHasSignal = (snapshot) => {
61
+ const flat = flattenAttributionSnapshotToSdkAttributes(snapshot);
62
+ const keys = Object.keys(flat).filter((k) => k !== ATTR_KEY_PROVIDER);
63
+ return keys.length > 0;
64
+ };
65
+
66
+ // src/merge.ts
67
+ var mergeAttributionSnapshots = (base, overlay) => {
68
+ if (!base) return overlay;
69
+ return {
70
+ providerId: overlay.providerId,
71
+ capturedAtMs: Math.max(base.capturedAtMs, overlay.capturedAtMs),
72
+ attribution: { ...base.attribution, ...overlay.attribution },
73
+ acquisition: { ...base.acquisition, ...overlay.acquisition },
74
+ link: {
75
+ entry: overlay.link.entry ?? base.link.entry,
76
+ params: { ...base.link.params, ...overlay.link.params }
77
+ }
78
+ };
79
+ };
80
+
81
+ // src/flowKeysDoc.ts
82
+ var SUGGESTED_ATTRIBUTION_PROVIDER_IDS = [
83
+ "appsflyer",
84
+ "adjust",
85
+ "branch",
86
+ "meta",
87
+ "google",
88
+ "tiktok",
89
+ "snapchat",
90
+ "twitter"
91
+ ];
92
+ var ATTRIBUTION_CONFIDENCE_LEVELS = [
93
+ "high",
94
+ "medium",
95
+ "low"
96
+ ];
97
+ var ATTRIBUTION_SDK_KEYS_DOC = [
98
+ { key: ATTR_KEY_PROVIDER, description: "Which integration produced this snapshot (e.g. appsflyer)." },
99
+ { key: ATTR_KEY_IS_ORGANIC, description: "Organic vs non-organic (use a Boolean compare in decisions)." },
100
+ { key: ATTR_KEY_MATCH_TYPE, description: "Provider match type when available." },
101
+ { key: ATTR_KEY_CONFIDENCE, description: "Coarse confidence hint (high | medium | low)." },
102
+ { key: ATTR_KEY_ACQ_SOURCE, description: "Media / network / source (Meta, Google, etc.)." },
103
+ { key: ATTR_KEY_ACQ_CAMPAIGN, description: "Campaign name." },
104
+ { key: ATTR_KEY_ACQ_CAMPAIGN_ID, description: "Campaign id." },
105
+ { key: ATTR_KEY_ACQ_ADSET, description: "Ad set / ad group name." },
106
+ { key: ATTR_KEY_ACQ_ADSET_ID, description: "Ad set id." },
107
+ { key: ATTR_KEY_ACQ_CREATIVE, description: "Creative / ad name." },
108
+ { key: ATTR_KEY_ACQ_CREATIVE_ID, description: "Creative id." },
109
+ { key: ATTR_KEY_ACQ_CHANNEL, description: "Optional channel bucket." },
110
+ { key: ATTR_KEY_LINK_ENTRY, description: "Primary deep link / route value (same decision surface as campaigns)." },
111
+ { key: `${LINK_EXT_PREFIX}<param>`, description: "Dynamic link parameters (e.g. link.ext.promo_code)." },
112
+ { key: ATTR_KEY_LINK_REFERRAL, description: "Example: mapped referral code param." },
113
+ { key: ATTR_KEY_LINK_PROMO, description: "Example: mapped promo code param." },
114
+ { key: ATTR_KEY_LINK_CONTENT_ID, description: "Example: mapped content id param." }
115
+ ];
116
+ var ATTRIBUTION_PREDEFINED_DECISION_KEYS = ATTRIBUTION_SDK_KEYS_DOC.filter((row) => !row.key.includes("<"));
117
+ var ATTRIBUTION_PREDEFINED_SDK_KEY_SET = new Set(
118
+ ATTRIBUTION_PREDEFINED_DECISION_KEYS.map((row) => row.key)
119
+ );
120
+ var isAttributionPredefinedSdkKey = (key) => ATTRIBUTION_PREDEFINED_SDK_KEY_SET.has(key);
121
+ var attributionPredefinedKeyDescription = (key) => ATTRIBUTION_PREDEFINED_DECISION_KEYS.find((row) => row.key === key)?.description;
122
+ var ATTRIBUTION_CONTEXT_KEY_PREFIXES = ["attribution.", "acquisition.", "link."];
123
+ var sdkKeyUsesAttributionContext = (key) => ATTRIBUTION_CONTEXT_KEY_PREFIXES.some((p) => key.startsWith(p));
124
+ var decisionExpressionUsesAttributionContext = (expr) => collectDecisionSdkKeys(expr).some(sdkKeyUsesAttributionContext);
125
+ var getEntryScreenDefaultJumpTarget = (manifest) => {
126
+ if (manifest.entryScreenId == null) return null;
127
+ const screen = manifest.screens.find((s) => s.id === manifest.entryScreenId);
128
+ const t = screen?.next?.default;
129
+ if (t == null || typeof t !== "string" || t.length === 0) return null;
130
+ return t;
131
+ };
132
+ var entryDefaultNextIsDecisionNode = (manifest) => {
133
+ const t = getEntryScreenDefaultJumpTarget(manifest);
134
+ if (!t) return false;
135
+ return DecisionNodeIdSchema.safeParse(t).success;
136
+ };
137
+ var decisionNodeUsesAttributionContext = (dn) => dn.cases.some((c) => decisionExpressionUsesAttributionContext(c.expression));
138
+ var entryDefaultNextIsAttributionDecision = (manifest) => {
139
+ const t = getEntryScreenDefaultJumpTarget(manifest);
140
+ if (!t || !DecisionNodeIdSchema.safeParse(t).success) return false;
141
+ const dn = manifest.decisionNodes?.find((d) => d.id === t);
142
+ if (!dn) return false;
143
+ return decisionNodeUsesAttributionContext(dn);
144
+ };
145
+ var manifestUsesAttributionInDecisions = (manifest) => (manifest.decisionNodes ?? []).some(decisionNodeUsesAttributionContext);
146
+ var AttributionConfidenceSchema = z.enum(["high", "medium", "low"]);
147
+
148
+ // src/cacheEnvelope.ts
149
+ var ATTRIBUTION_CACHE_SCHEMA_VERSION = 1;
150
+ var DEFAULT_ATTRIBUTION_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
151
+ var AttributionDeviceCacheEnvelopeSchema = z.object({
152
+ schemaVersion: z.literal(ATTRIBUTION_CACHE_SCHEMA_VERSION),
153
+ /** Epoch ms when this envelope was written. */
154
+ cachedAtMs: z.number(),
155
+ /** Snapshot identity — must match current adapter output shape. */
156
+ snapshot: z.object({
157
+ providerId: z.string().min(1),
158
+ capturedAtMs: z.number(),
159
+ attribution: z.object({
160
+ isOrganic: z.boolean().optional(),
161
+ matchType: z.string().optional(),
162
+ confidence: AttributionConfidenceSchema.optional(),
163
+ installTimestampMs: z.number().optional(),
164
+ clickTimestampMs: z.number().optional()
165
+ }),
166
+ acquisition: z.object({
167
+ source: z.string().optional(),
168
+ campaign: z.string().optional(),
169
+ campaignId: z.string().optional(),
170
+ adset: z.string().optional(),
171
+ adsetId: z.string().optional(),
172
+ creative: z.string().optional(),
173
+ creativeId: z.string().optional(),
174
+ channel: z.string().optional()
175
+ }),
176
+ link: z.object({
177
+ entry: z.string().optional(),
178
+ params: z.record(z.string(), z.string()).optional()
179
+ })
180
+ })
181
+ });
182
+
183
+ export { ATTRIBUTION_CACHE_SCHEMA_VERSION, ATTRIBUTION_CONFIDENCE_LEVELS, ATTRIBUTION_CONTEXT_KEY_PREFIXES, ATTRIBUTION_PREDEFINED_DECISION_KEYS, ATTRIBUTION_SDK_KEYS_DOC, ATTR_KEY_ACQ_ADSET, ATTR_KEY_ACQ_ADSET_ID, ATTR_KEY_ACQ_CAMPAIGN, ATTR_KEY_ACQ_CAMPAIGN_ID, ATTR_KEY_ACQ_CHANNEL, ATTR_KEY_ACQ_CREATIVE, ATTR_KEY_ACQ_CREATIVE_ID, ATTR_KEY_ACQ_SOURCE, ATTR_KEY_CONFIDENCE, ATTR_KEY_IS_ORGANIC, ATTR_KEY_LINK_CONTENT_ID, ATTR_KEY_LINK_ENTRY, ATTR_KEY_LINK_PROMO, ATTR_KEY_LINK_REFERRAL, ATTR_KEY_MATCH_TYPE, ATTR_KEY_PROVIDER, AttributionConfidenceSchema, AttributionDeviceCacheEnvelopeSchema, DEFAULT_ATTRIBUTION_CACHE_TTL_MS, LINK_EXT_PREFIX, SUGGESTED_ATTRIBUTION_PROVIDER_IDS, attributionPredefinedKeyDescription, decisionExpressionUsesAttributionContext, decisionNodeUsesAttributionContext, entryDefaultNextIsAttributionDecision, entryDefaultNextIsDecisionNode, flattenAttributionSnapshotToSdkAttributes, getEntryScreenDefaultJumpTarget, isAttributionPredefinedSdkKey, manifestUsesAttributionInDecisions, mergeAttributionSnapshots, normalizedSnapshotHasSignal, sdkKeyUsesAttributionContext };
184
+ //# sourceMappingURL=index.js.map
185
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canonicalKeys.ts","../src/flatten.ts","../src/merge.ts","../src/flowKeysDoc.ts","../src/builderHints.ts","../src/zodTypes.ts","../src/cacheEnvelope.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Universal SDK attribute keys for mobile attribution / deep-link context.\n * Provider adapters (AppsFlyer, Adjust, …) map native payloads → these names.\n *\n * Host-supplied `sdkAttributes` should avoid these prefixes to prevent clashes:\n * `attribution.`, `acquisition.`, `link.`\n */\n\n/** Metadata about which MMP / SDK produced the current snapshot (string id, e.g. `appsflyer`). */\nexport const ATTR_KEY_PROVIDER = 'attribution.provider' as const;\n\n/** Whether the install/open was classified as organic (`boolean` in decision eval). */\nexport const ATTR_KEY_IS_ORGANIC = 'attribution.isOrganic' as const;\n\n/** Optional coarse confidence hint from the provider (`high` | `medium` | `low`). */\nexport const ATTR_KEY_CONFIDENCE = 'attribution.confidence' as const;\n\n/** Provider-specific match type string when available (probabilistic, etc.). */\nexport const ATTR_KEY_MATCH_TYPE = 'attribution.matchType' as const;\n\n/** Media / network / channel — paid ads, social, search, etc. */\nexport const ATTR_KEY_ACQ_SOURCE = 'acquisition.source' as const;\n\nexport const ATTR_KEY_ACQ_CAMPAIGN = 'acquisition.campaign' as const;\nexport const ATTR_KEY_ACQ_CAMPAIGN_ID = 'acquisition.campaignId' as const;\nexport const ATTR_KEY_ACQ_ADSET = 'acquisition.adset' as const;\nexport const ATTR_KEY_ACQ_ADSET_ID = 'acquisition.adsetId' as const;\nexport const ATTR_KEY_ACQ_CREATIVE = 'acquisition.creative' as const;\nexport const ATTR_KEY_ACQ_CREATIVE_ID = 'acquisition.creativeId' as const;\n\n/** Optional coarse channel bucket (e.g. social, search). */\nexport const ATTR_KEY_ACQ_CHANNEL = 'acquisition.channel' as const;\n\n/**\n * Primary entry identifier from a universal / deep link — same decision surface as campaigns.\n * Semantic: “what route or offer this open represents”.\n */\nexport const ATTR_KEY_LINK_ENTRY = 'link.entry' as const;\n\n/**\n * Flattened dynamic params from links / deferred payloads.\n * Pattern: `link.ext.<paramKey>` where paramKey is provider-normalized (alphanumeric + underscore).\n */\nexport const LINK_EXT_PREFIX = 'link.ext.' as const;\n\n/** Well-known link.param keys (optional convenience aliases providers may map). */\nexport const ATTR_KEY_LINK_REFERRAL = 'link.ext.referral_code' as const;\nexport const ATTR_KEY_LINK_PROMO = 'link.ext.promo_code' as const;\nexport const ATTR_KEY_LINK_CONTENT_ID = 'link.ext.content_id' as const;\n","import {\n ATTR_KEY_ACQ_ADSET,\n ATTR_KEY_ACQ_ADSET_ID,\n ATTR_KEY_ACQ_CAMPAIGN,\n ATTR_KEY_ACQ_CAMPAIGN_ID,\n ATTR_KEY_ACQ_CHANNEL,\n ATTR_KEY_ACQ_CREATIVE,\n ATTR_KEY_ACQ_CREATIVE_ID,\n ATTR_KEY_ACQ_SOURCE,\n ATTR_KEY_CONFIDENCE,\n ATTR_KEY_IS_ORGANIC,\n ATTR_KEY_LINK_ENTRY,\n ATTR_KEY_MATCH_TYPE,\n ATTR_KEY_PROVIDER,\n LINK_EXT_PREFIX,\n} from './canonicalKeys';\nimport type { NormalizedAttributionSnapshot } from './types';\n\nconst sanitizeExtKey = (k: string): string =>\n k.replace(/[^a-zA-Z0-9_]/g, '_').slice(0, 64);\n\n/**\n * Maps a normalized snapshot into flat `sdkAttributes` entries for decision nodes.\n * Omits undefined leaves; booleans stay booleans for boolean predicates.\n */\nexport const flattenAttributionSnapshotToSdkAttributes = (\n snapshot: NormalizedAttributionSnapshot,\n): Record<string, unknown> => {\n const out: Record<string, unknown> = {};\n\n out[ATTR_KEY_PROVIDER] = snapshot.providerId;\n\n const { attribution, acquisition, link } = snapshot;\n\n if (typeof attribution.isOrganic === 'boolean') {\n out[ATTR_KEY_IS_ORGANIC] = attribution.isOrganic;\n }\n if (attribution.matchType !== undefined && attribution.matchType !== '') {\n out[ATTR_KEY_MATCH_TYPE] = attribution.matchType;\n }\n if (attribution.confidence !== undefined) {\n out[ATTR_KEY_CONFIDENCE] = attribution.confidence;\n }\n\n const setStr = (key: string, v?: string): void => {\n if (v !== undefined && v !== '') out[key] = v;\n };\n\n setStr(ATTR_KEY_ACQ_SOURCE, acquisition.source);\n setStr(ATTR_KEY_ACQ_CAMPAIGN, acquisition.campaign);\n setStr(ATTR_KEY_ACQ_CAMPAIGN_ID, acquisition.campaignId);\n setStr(ATTR_KEY_ACQ_ADSET, acquisition.adset);\n setStr(ATTR_KEY_ACQ_ADSET_ID, acquisition.adsetId);\n setStr(ATTR_KEY_ACQ_CREATIVE, acquisition.creative);\n setStr(ATTR_KEY_ACQ_CREATIVE_ID, acquisition.creativeId);\n setStr(ATTR_KEY_ACQ_CHANNEL, acquisition.channel);\n\n setStr(ATTR_KEY_LINK_ENTRY, link.entry);\n\n const params = link.params ?? {};\n for (const [rawKey, rawVal] of Object.entries(params)) {\n const sk = sanitizeExtKey(rawKey);\n if (!sk) continue;\n if (rawVal !== undefined && rawVal !== '') {\n out[`${LINK_EXT_PREFIX}${sk}`] = rawVal;\n }\n }\n\n return out;\n};\n\n/**\n * Returns true if the snapshot carries any branching-relevant data beyond provider id alone.\n */\nexport const normalizedSnapshotHasSignal = (snapshot: NormalizedAttributionSnapshot): boolean => {\n const flat = flattenAttributionSnapshotToSdkAttributes(snapshot);\n const keys = Object.keys(flat).filter((k) => k !== ATTR_KEY_PROVIDER);\n return keys.length > 0;\n};\n","import type { NormalizedAttributionSnapshot } from './types';\n\n/**\n * Merges two snapshots from the same session/provider layer (e.g. install + deep link).\n * Later `overlay` wins on conflicting scalar facets; link params are shallow-merged with overlay winning per key.\n */\nexport const mergeAttributionSnapshots = (\n base: NormalizedAttributionSnapshot | null,\n overlay: NormalizedAttributionSnapshot,\n): NormalizedAttributionSnapshot => {\n if (!base) return overlay;\n return {\n providerId: overlay.providerId,\n capturedAtMs: Math.max(base.capturedAtMs, overlay.capturedAtMs),\n attribution: { ...base.attribution, ...overlay.attribution },\n acquisition: { ...base.acquisition, ...overlay.acquisition },\n link: {\n entry: overlay.link.entry ?? base.link.entry,\n params: { ...base.link.params, ...overlay.link.params },\n },\n };\n};\n","import {\n ATTR_KEY_ACQ_ADSET,\n ATTR_KEY_ACQ_ADSET_ID,\n ATTR_KEY_ACQ_CAMPAIGN,\n ATTR_KEY_ACQ_CAMPAIGN_ID,\n ATTR_KEY_ACQ_CHANNEL,\n ATTR_KEY_ACQ_CREATIVE,\n ATTR_KEY_ACQ_CREATIVE_ID,\n ATTR_KEY_ACQ_SOURCE,\n ATTR_KEY_CONFIDENCE,\n ATTR_KEY_IS_ORGANIC,\n ATTR_KEY_LINK_CONTENT_ID,\n ATTR_KEY_LINK_ENTRY,\n ATTR_KEY_LINK_PROMO,\n ATTR_KEY_LINK_REFERRAL,\n ATTR_KEY_MATCH_TYPE,\n ATTR_KEY_PROVIDER,\n LINK_EXT_PREFIX,\n} from './canonicalKeys';\nimport type { AttributionConfidenceLevel } from './types';\n\n/** Suggested `attribution.provider` string ids for dashboard pickers (extend as we add adapters). */\nexport const SUGGESTED_ATTRIBUTION_PROVIDER_IDS: readonly string[] = [\n 'appsflyer',\n 'adjust',\n 'branch',\n 'meta',\n 'google',\n 'tiktok',\n 'snapchat',\n 'twitter',\n];\n\n/** Values that may appear at `attribution.confidence` after flattening. */\nexport const ATTRIBUTION_CONFIDENCE_LEVELS: readonly AttributionConfidenceLevel[] = [\n 'high',\n 'medium',\n 'low',\n];\n\n/** Human-readable reference for dashboard + docs (SDK decision variables). */\nexport const ATTRIBUTION_SDK_KEYS_DOC: ReadonlyArray<{ key: string; description: string }> = [\n { key: ATTR_KEY_PROVIDER, description: 'Which integration produced this snapshot (e.g. appsflyer).' },\n { key: ATTR_KEY_IS_ORGANIC, description: 'Organic vs non-organic (use a Boolean compare in decisions).' },\n { key: ATTR_KEY_MATCH_TYPE, description: 'Provider match type when available.' },\n { key: ATTR_KEY_CONFIDENCE, description: 'Coarse confidence hint (high | medium | low).' },\n { key: ATTR_KEY_ACQ_SOURCE, description: 'Media / network / source (Meta, Google, etc.).' },\n { key: ATTR_KEY_ACQ_CAMPAIGN, description: 'Campaign name.' },\n { key: ATTR_KEY_ACQ_CAMPAIGN_ID, description: 'Campaign id.' },\n { key: ATTR_KEY_ACQ_ADSET, description: 'Ad set / ad group name.' },\n { key: ATTR_KEY_ACQ_ADSET_ID, description: 'Ad set id.' },\n { key: ATTR_KEY_ACQ_CREATIVE, description: 'Creative / ad name.' },\n { key: ATTR_KEY_ACQ_CREATIVE_ID, description: 'Creative id.' },\n { key: ATTR_KEY_ACQ_CHANNEL, description: 'Optional channel bucket.' },\n { key: ATTR_KEY_LINK_ENTRY, description: 'Primary deep link / route value (same decision surface as campaigns).' },\n { key: `${LINK_EXT_PREFIX}<param>`, description: 'Dynamic link parameters (e.g. link.ext.promo_code).' },\n { key: ATTR_KEY_LINK_REFERRAL, description: 'Example: mapped referral code param.' },\n { key: ATTR_KEY_LINK_PROMO, description: 'Example: mapped promo code param.' },\n { key: ATTR_KEY_LINK_CONTENT_ID, description: 'Example: mapped content id param.' },\n];\n\n/**\n * Catalog keys editors should offer for attribution / acquisition / deep-link branching\n * (excludes template rows like `link.ext.<param>` — use a custom `link.ext.*` key for those).\n */\nexport const ATTRIBUTION_PREDEFINED_DECISION_KEYS: ReadonlyArray<{\n key: string;\n description: string;\n}> = ATTRIBUTION_SDK_KEYS_DOC.filter((row) => !row.key.includes('<'));\n\nconst ATTRIBUTION_PREDEFINED_SDK_KEY_SET: ReadonlySet<string> = new Set(\n ATTRIBUTION_PREDEFINED_DECISION_KEYS.map((row) => row.key),\n);\n\nexport const isAttributionPredefinedSdkKey = (key: string): boolean =>\n ATTRIBUTION_PREDEFINED_SDK_KEY_SET.has(key);\n\nexport const attributionPredefinedKeyDescription = (key: string): string | undefined =>\n ATTRIBUTION_PREDEFINED_DECISION_KEYS.find((row) => row.key === key)?.description;\n","import type { FlowManifest } from '@getrheo/contracts/manifest';\nimport {\n collectDecisionSdkKeys,\n DecisionNodeIdSchema,\n type DecisionExpr,\n type DecisionNode,\n} from '@getrheo/contracts/decisions';\n\n/** Canonical prefixes for MMP / deep-link context merged into SDK attributes. */\nexport const ATTRIBUTION_CONTEXT_KEY_PREFIXES = ['attribution.', 'acquisition.', 'link.'] as const;\n\nexport const sdkKeyUsesAttributionContext = (key: string): boolean =>\n ATTRIBUTION_CONTEXT_KEY_PREFIXES.some((p) => key.startsWith(p));\n\nexport const decisionExpressionUsesAttributionContext = (expr: DecisionExpr): boolean =>\n collectDecisionSdkKeys(expr).some(sdkKeyUsesAttributionContext);\n\n/** Default `next` target from the manifest entry screen (screen or decision id), or null. */\nexport const getEntryScreenDefaultJumpTarget = (manifest: FlowManifest): string | null => {\n if (manifest.entryScreenId == null) return null;\n const screen = manifest.screens.find((s) => s.id === manifest.entryScreenId);\n const t = screen?.next?.default;\n if (t == null || typeof t !== 'string' || t.length === 0) return null;\n return t;\n};\n\nexport const entryDefaultNextIsDecisionNode = (manifest: FlowManifest): boolean => {\n const t = getEntryScreenDefaultJumpTarget(manifest);\n if (!t) return false;\n return DecisionNodeIdSchema.safeParse(t).success;\n};\n\nexport const decisionNodeUsesAttributionContext = (dn: DecisionNode): boolean =>\n dn.cases.some((c) => decisionExpressionUsesAttributionContext(c.expression));\n\n/**\n * True when the entry screen's default next hop is a decision node whose rules read\n * attribution / acquisition / link SDK keys — first evaluation can run before deferred MMP data arrives.\n */\nexport const entryDefaultNextIsAttributionDecision = (manifest: FlowManifest): boolean => {\n const t = getEntryScreenDefaultJumpTarget(manifest);\n if (!t || !DecisionNodeIdSchema.safeParse(t).success) return false;\n const dn = manifest.decisionNodes?.find((d) => d.id === t);\n if (!dn) return false;\n return decisionNodeUsesAttributionContext(dn);\n};\n\n/** Any decision node references attribution-style SDK keys. */\nexport const manifestUsesAttributionInDecisions = (manifest: FlowManifest): boolean =>\n (manifest.decisionNodes ?? []).some(decisionNodeUsesAttributionContext);\n","import { z } from 'zod';\n\nexport const AttributionConfidenceSchema = z.enum(['high', 'medium', 'low']);\n","import { z } from 'zod';\nimport { AttributionConfidenceSchema } from './zodTypes';\n\n/** Bump when the persisted envelope shape changes (invalidates old cache files). */\nexport const ATTRIBUTION_CACHE_SCHEMA_VERSION = 1;\n\n/** Default TTL for device-side attribution fallback (24 hours). */\nexport const DEFAULT_ATTRIBUTION_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Persisted device cache — provider-agnostic normalized snapshot + bookkeeping.\n * Stored as JSON (e.g. AsyncStorage). Not sent to Rheo servers by default.\n */\nexport const AttributionDeviceCacheEnvelopeSchema = z.object({\n schemaVersion: z.literal(ATTRIBUTION_CACHE_SCHEMA_VERSION),\n /** Epoch ms when this envelope was written. */\n cachedAtMs: z.number(),\n /** Snapshot identity — must match current adapter output shape. */\n snapshot: z.object({\n providerId: z.string().min(1),\n capturedAtMs: z.number(),\n attribution: z.object({\n isOrganic: z.boolean().optional(),\n matchType: z.string().optional(),\n confidence: AttributionConfidenceSchema.optional(),\n installTimestampMs: z.number().optional(),\n clickTimestampMs: z.number().optional(),\n }),\n acquisition: z.object({\n source: z.string().optional(),\n campaign: z.string().optional(),\n campaignId: z.string().optional(),\n adset: z.string().optional(),\n adsetId: z.string().optional(),\n creative: z.string().optional(),\n creativeId: z.string().optional(),\n channel: z.string().optional(),\n }),\n link: z.object({\n entry: z.string().optional(),\n params: z.record(z.string(), z.string()).optional(),\n }),\n }),\n});\n\nexport type AttributionDeviceCacheEnvelope = z.infer<typeof AttributionDeviceCacheEnvelopeSchema>;\n"]}
@@ -0,0 +1,6 @@
1
+ [
2
+ {
3
+ "exportKey": ".",
4
+ "distFile": "./dist/index.js"
5
+ }
6
+ ]
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@getrheo/attribution",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "zod": "^3.23.8",
16
+ "@getrheo/contracts": "1.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.10.1",
20
+ "typescript": "^5.6.3",
21
+ "vitest": "^3.2.6",
22
+ "@rheo/config": "0.1.0"
23
+ },
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/madeinusmate/onboardly.git",
28
+ "directory": "packages/attribution"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "lint": "eslint .",
39
+ "typecheck": "tsc --noEmit",
40
+ "test": "vitest run",
41
+ "build": "node ../../scripts/build-publishable-package.mjs"
42
+ }
43
+ }