@uniformdev/context 20.80.0 → 20.80.1-alpha.10.sha-e0cfb4ce44

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.
@@ -1,4 +1,4 @@
1
- import { p as paths$7, M as ManifestV2 } from '../types-DovaM4gV.mjs';
1
+ import { p as paths$7, M as ManifestV2 } from '../types-p9vnD820.mjs';
2
2
  import 'mitt';
3
3
 
4
4
  type LimitPolicy = <ReturnValue>(func: () => Promise<ReturnValue>) => Promise<ReturnValue>;
package/dist/api/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { p as paths$7, M as ManifestV2 } from '../types-DovaM4gV.js';
1
+ import { p as paths$7, M as ManifestV2 } from '../types-p9vnD820.js';
2
2
  import 'mitt';
3
3
 
4
4
  type LimitPolicy = <ReturnValue>(func: () => Promise<ReturnValue>) => Promise<ReturnValue>;
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { O as OutputSeverity, L as LogDrain, C as ContextPlugin, S as ScoreVector, A as AggregateDimension, T as TransitionDataStore, a as StorageCommands, V as VisitorData, Q as Quirks, b as TransitionDataStoreOptions, D as DecayFunction, c as CriteriaEvaluator, d as StringMatch, e as VariantMatchCriteria, f as LogMessage, P as PersonalizedVariant, g as PersonalizeOptions, h as Context, i as PersonalizedResult, j as VariationMatchDimensionCriteria, k as PersonalizationSelectionAlgorithmOptions, l as DevToolsEvents } from './types-DovaM4gV.mjs';
2
- export { m as AggregateDimensionInput, B as BehaviorTag, n as CONTEXTUAL_EDITING_TEST_NAME, o as CONTEXTUAL_EDITING_TEST_SELECTED_VARIANT_ID, q as CompositionMetadata, r as ContextEvents, s as ContextInstance, t as ContextOptions, u as ContextState, v as ContextStateUpdate, w as CriteriaEvaluatorParameters, x as CriteriaEvaluatorResult, y as DecayOptions, z as DevToolsActions, E as DevToolsDataEvent, F as DevToolsEvent, G as DevToolsForgetEvent, H as DevToolsHelloEvent, I as DevToolsLogEvent, J as DevToolsRawCommandsEvent, K as DevToolsState, N as DevToolsUiVersion, R as DevToolsUpdateEvent, U as DimensionMatch, W as EnrichmentCategory, X as EnrichmentData, Y as EventData, Z as GoalStateUpdate, _ as Goals, $ as GroupCriteriaEvaluator, a0 as IdentifyCommand, a1 as LogMessageGroup, a2 as LogMessageSingle, a3 as LogMessages, a4 as ManifestInstance, M as ManifestV2, a5 as MessageCategory, a6 as MessageFunc, a7 as ModifyScoreCommand, a8 as ModifySessionScoreCommand, a9 as NumberMatch, aa as PersonalizationEvent, ab as PersonalizationEventVariantId, ac as PersonalizationManifest, ad as PersonalizationSelectionAlgorithm, ae as PersonalizationSelectionAlgorithms, af as PersonalizeControlVariant, ag as PersonalizeVariants, ah as QuirkMatch, ai as SERVER_STATE_ID, aj as ServerToClientTransitionState, ak as SetConsentCommand, al as SetControlGroupCommand, am as SetGoalCommand, an as SetPersonalizeVariantControlCommand, ao as SetQuirkCommand, ap as SetTestCommand, aq as Severity, ar as Signal, as as SignalCriteria, at as SignalCriteriaGroup, au as SignalData, av as StorageCommand, aw as TestDefinition, ax as TestEvent, ay as TestOptions, az as TestResult, aA as TestVariant, aB as Tests, aC as TransitionDataStoreEvents, aD as VariationMatchMetadata, aE as VisitorDataStore, aF as VisitorDataStoreEvents, aG as VisitorDataStoreOptions, aH as areCommandsEqual, aI as emptyVisitorData, aJ as testVariations } from './types-DovaM4gV.mjs';
1
+ import { O as OutputSeverity, L as LogDrain, C as ContextPlugin, S as ScoreVector, A as AggregateDimension, T as TransitionDataStore, a as LogMessage, b as StorageCommands, V as VisitorData, Q as Quirks, c as TransitionDataStoreOptions, D as DecayFunction, d as CriteriaEvaluator, e as StringMatch, f as VariantMatchCriteria, P as PersonalizedVariant, g as PersonalizeOptions, h as Context, i as PersonalizedResult, j as VariationMatchDimensionCriteria, k as PersonalizationSelectionAlgorithmOptions, l as DevToolsEvents } from './types-p9vnD820.mjs';
2
+ export { m as AggregateDimensionInput, B as BehaviorTag, n as CONTEXTUAL_EDITING_TEST_NAME, o as CONTEXTUAL_EDITING_TEST_SELECTED_VARIANT_ID, q as CompositionMetadata, r as ContextEvents, s as ContextInstance, t as ContextOptions, u as ContextState, v as ContextStateUpdate, w as CriteriaEvaluatorParameters, x as CriteriaEvaluatorResult, y as DecayOptions, z as DevToolsActions, E as DevToolsDataEvent, F as DevToolsEvent, G as DevToolsForgetEvent, H as DevToolsHelloEvent, I as DevToolsLogEvent, J as DevToolsRawCommandsEvent, K as DevToolsState, N as DevToolsUiVersion, R as DevToolsUpdateEvent, U as DimensionMatch, W as EnrichmentCategory, X as EnrichmentData, Y as EventData, Z as GoalStateUpdate, _ as Goals, $ as GroupCriteriaEvaluator, a0 as IdentifyCommand, a1 as LogMessageGroup, a2 as LogMessageSingle, a3 as LogMessages, a4 as ManifestInstance, M as ManifestV2, a5 as MessageCategory, a6 as MessageFunc, a7 as ModifyScoreCommand, a8 as ModifySessionScoreCommand, a9 as NumberMatch, aa as PersonalizationEvent, ab as PersonalizationEventVariantId, ac as PersonalizationManifest, ad as PersonalizationSelectionAlgorithm, ae as PersonalizationSelectionAlgorithms, af as PersonalizeControlVariant, ag as PersonalizeVariants, ah as QuirkMatch, ai as SERVER_STATE_ID, aj as ServerToClientTransitionState, ak as SetConsentCommand, al as SetControlGroupCommand, am as SetGoalCommand, an as SetPersonalizeVariantControlCommand, ao as SetQuirkCommand, ap as SetTestCommand, aq as Severity, ar as Signal, as as SignalCriteria, at as SignalCriteriaGroup, au as SignalData, av as StorageCommand, aw as TestDefinition, ax as TestEvent, ay as TestOptions, az as TestResult, aA as TestVariant, aB as Tests, aC as TransitionDataStoreEvents, aD as VariationMatchMetadata, aE as VisitorDataStore, aF as VisitorDataStoreEvents, aG as VisitorDataStoreOptions, aH as areCommandsEqual, aI as emptyVisitorData, aJ as testVariations } from './types-p9vnD820.mjs';
3
3
  import 'mitt';
4
4
 
5
5
  /**
@@ -55,6 +55,27 @@ type CookieTransitionDataStoreOptions = {
55
55
  * local storage is the master copy. Defaults to SameSite=Lax.
56
56
  */
57
57
  cookieAttributes?: Cookies.CookieAttributes;
58
+ /**
59
+ * Ceiling on the size in bytes of the transition cookie, measured on the percent-encoded
60
+ * `name=value` pair as the request header carries it.
61
+ *
62
+ * When the visitor's data does not fit, the least valuable parts are dropped until it does,
63
+ * deterministically: dimensions the cookie would write as zero, then remaining dimensions by
64
+ * ascending score, then personalize variants, then test assignments. Control group membership
65
+ * is never dropped, so a budget below the cookie's empty cost is refused at construction.
66
+ *
67
+ * Covers this cookie only, not the quirk cookie, its attributes or other cookies on the domain.
68
+ * Local storage remains the complete copy, so only what the server is told is lost.
69
+ *
70
+ * @defaultValue undefined - the cookie is unbounded
71
+ */
72
+ maxBytes?: number;
73
+ /**
74
+ * Called with log messages the store emits, such as a report of what `maxBytes` dropped.
75
+ * Also settable after construction, which is how a host that builds the store as an argument
76
+ * to its `Context` points it at {@link Context.log}.
77
+ */
78
+ onLogMessage?: (message: LogMessage) => void;
58
79
  };
59
80
  declare const UNIFORM_DEFAULT_COOKIE_NAME = "ufvd";
60
81
  declare const UNIFORM_DEFAULT_QUIRK_COOKIE_NAME = "ufvdqk";
@@ -64,7 +85,9 @@ declare const UNIFORM_DEFAULT_QUIRK_COOKIE_NAME = "ufvdqk";
64
85
  */
65
86
  declare class CookieTransitionDataStore extends TransitionDataStore {
66
87
  #private;
67
- constructor({ serverCookieValue, experimental_quirksEnabled, quirkCookieValue, quirkCookieName, cookieName, cookieAttributes, }: CookieTransitionDataStoreOptions);
88
+ /** Where the store's log messages go. See {@link CookieTransitionDataStoreOptions.onLogMessage}. */
89
+ onLogMessage: ((message: LogMessage) => void) | undefined;
90
+ constructor({ serverCookieValue, experimental_quirksEnabled, quirkCookieValue, quirkCookieName, cookieName, cookieAttributes, maxBytes, onLogMessage, }: CookieTransitionDataStoreOptions);
68
91
  handleDelete(): Promise<void>;
69
92
  handleUpdateData(_: StorageCommands[], computedValue: VisitorData): Promise<void>;
70
93
  }
@@ -77,7 +100,19 @@ declare function parseQuirkCookie(quirkCookieValue: string): Quirks;
77
100
  declare function parseCookieScores(type: string | undefined): Record<string, string>;
78
101
  declare function parseCookieType(type: string | undefined): Record<string, string>;
79
102
  declare function decodeCookieType(type: Record<string, string>): Record<string, number>;
80
- declare function serializeCookie(data: VisitorData): string;
103
+ type SerializeCookieOptions = {
104
+ /**
105
+ * Ceiling on the size in bytes of the percent-encoded `name=value` pair.
106
+ * Parts of the visitor data are dropped until the cookie fits; see the drop order on
107
+ * {@link CookieTransitionDataStoreOptions.maxBytes}.
108
+ */
109
+ maxBytes?: number;
110
+ /** Cookie name, counted toward `maxBytes` along with the `=` that follows it. */
111
+ cookieName?: string;
112
+ /** Called once, when `maxBytes` forced at least one drop, with what was dropped. */
113
+ onBudgetExceeded?: (dropped: string[], bytes: number, maxBytes: number) => void;
114
+ };
115
+ declare function serializeCookie(data: VisitorData, options?: SerializeCookieOptions): string;
81
116
  declare function serializePersonalizeVariants({ personalizeVariants, }: Pick<VisitorData, 'personalizeVariants'>): string;
82
117
  declare function decodePersonalizeVariants(data: string | undefined): VisitorData['personalizeVariants'];
83
118
  declare function encodeCookieType(type: Record<string, number>): Record<string, string>;
@@ -245,4 +280,4 @@ type QuickConnectConfig = {
245
280
  declare function serializeQuickConnect(config: QuickConnectConfig): string;
246
281
  declare function parseQuickConnect(serialized: string): Required<QuickConnectConfig>;
247
282
 
248
- export { AggregateDimension, type ConsoleDebugLogDrainOptions, Context, type ContextDevToolOptions, ContextPlugin, CookieTransitionDataStore, type CookieTransitionDataStoreOptions, CriteriaEvaluator, DecayFunction, DevToolsEvents, ENR_SEPARATOR, EdgeNodeTagName, type EdgePersonalizeComponentOptions, type EdgeTestComponentOptions, EdgeTransitionDataStore, type EdgeTransitionDataStoreOptions, type EnableUniformInsightsOptions, KV_SEP, type LinearDecayOptions, LogDrain, LogMessage, OutputSeverity, PAIR_SEP, PersonalizationSelectionAlgorithmOptions, PersonalizeOptions, PersonalizedResult, PersonalizedVariant, QUIRK_SEP, type QuickConnectConfig, Quirks, STRONGEST_SCORE_PERSONALIZATION_ALGORITHM, ScoreVector, ScriptType, StorageCommands, StringMatch, TOP_DOWN_CRITERIA_PERSONALIZATION_ALGORITHM, TYPE_SEP, TransitionDataStore, TransitionDataStoreOptions, UNIFORM_DEFAULT_COOKIE_NAME, UNIFORM_DEFAULT_QUIRK_COOKIE_NAME, VariantMatchCriteria, VariationMatchDimensionCriteria, VisitorData, computeAggregateDimensions, cookieEvaluator, createConsoleLogDrain, createDebugConsoleLogDrain, createLinearDecay, currentPageEvaluator, decodeCookieType, decodePersonalizeVariants, enableConsoleLogDrain, enableContextDevTools, enableDebugConsoleLogDrain, enableUniformInsights, encodeCookieType, evaluateVariantMatch, eventEvaluator, explainStringMatch, explainStringMatchCriteria, getEnrichmentVectorKey, isStringMatch, pageViewCountDimension, pageViewCountEvaluator, parseCookieScores, parseCookieType, parseQuickConnect, parseQuirkCookie, parseScoreCookie, personalizeVariations, queryStringEvaluator, quirkEvaluator, serializeCookie, serializeCookieType, serializePersonalizeVariants, serializeQuickConnect, serializeQuirks, strongestScorePersonalizationSelectionAlgorithm, topDownCriteriaPersonalizationSelectionAlgorithm };
283
+ export { AggregateDimension, type ConsoleDebugLogDrainOptions, Context, type ContextDevToolOptions, ContextPlugin, CookieTransitionDataStore, type CookieTransitionDataStoreOptions, CriteriaEvaluator, DecayFunction, DevToolsEvents, ENR_SEPARATOR, EdgeNodeTagName, type EdgePersonalizeComponentOptions, type EdgeTestComponentOptions, EdgeTransitionDataStore, type EdgeTransitionDataStoreOptions, type EnableUniformInsightsOptions, KV_SEP, type LinearDecayOptions, LogDrain, LogMessage, OutputSeverity, PAIR_SEP, PersonalizationSelectionAlgorithmOptions, PersonalizeOptions, PersonalizedResult, PersonalizedVariant, QUIRK_SEP, type QuickConnectConfig, Quirks, STRONGEST_SCORE_PERSONALIZATION_ALGORITHM, ScoreVector, ScriptType, type SerializeCookieOptions, StorageCommands, StringMatch, TOP_DOWN_CRITERIA_PERSONALIZATION_ALGORITHM, TYPE_SEP, TransitionDataStore, TransitionDataStoreOptions, UNIFORM_DEFAULT_COOKIE_NAME, UNIFORM_DEFAULT_QUIRK_COOKIE_NAME, VariantMatchCriteria, VariationMatchDimensionCriteria, VisitorData, computeAggregateDimensions, cookieEvaluator, createConsoleLogDrain, createDebugConsoleLogDrain, createLinearDecay, currentPageEvaluator, decodeCookieType, decodePersonalizeVariants, enableConsoleLogDrain, enableContextDevTools, enableDebugConsoleLogDrain, enableUniformInsights, encodeCookieType, evaluateVariantMatch, eventEvaluator, explainStringMatch, explainStringMatchCriteria, getEnrichmentVectorKey, isStringMatch, pageViewCountDimension, pageViewCountEvaluator, parseCookieScores, parseCookieType, parseQuickConnect, parseQuirkCookie, parseScoreCookie, personalizeVariations, queryStringEvaluator, quirkEvaluator, serializeCookie, serializeCookieType, serializePersonalizeVariants, serializeQuickConnect, serializeQuirks, strongestScorePersonalizationSelectionAlgorithm, topDownCriteriaPersonalizationSelectionAlgorithm };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { O as OutputSeverity, L as LogDrain, C as ContextPlugin, S as ScoreVector, A as AggregateDimension, T as TransitionDataStore, a as StorageCommands, V as VisitorData, Q as Quirks, b as TransitionDataStoreOptions, D as DecayFunction, c as CriteriaEvaluator, d as StringMatch, e as VariantMatchCriteria, f as LogMessage, P as PersonalizedVariant, g as PersonalizeOptions, h as Context, i as PersonalizedResult, j as VariationMatchDimensionCriteria, k as PersonalizationSelectionAlgorithmOptions, l as DevToolsEvents } from './types-DovaM4gV.js';
2
- export { m as AggregateDimensionInput, B as BehaviorTag, n as CONTEXTUAL_EDITING_TEST_NAME, o as CONTEXTUAL_EDITING_TEST_SELECTED_VARIANT_ID, q as CompositionMetadata, r as ContextEvents, s as ContextInstance, t as ContextOptions, u as ContextState, v as ContextStateUpdate, w as CriteriaEvaluatorParameters, x as CriteriaEvaluatorResult, y as DecayOptions, z as DevToolsActions, E as DevToolsDataEvent, F as DevToolsEvent, G as DevToolsForgetEvent, H as DevToolsHelloEvent, I as DevToolsLogEvent, J as DevToolsRawCommandsEvent, K as DevToolsState, N as DevToolsUiVersion, R as DevToolsUpdateEvent, U as DimensionMatch, W as EnrichmentCategory, X as EnrichmentData, Y as EventData, Z as GoalStateUpdate, _ as Goals, $ as GroupCriteriaEvaluator, a0 as IdentifyCommand, a1 as LogMessageGroup, a2 as LogMessageSingle, a3 as LogMessages, a4 as ManifestInstance, M as ManifestV2, a5 as MessageCategory, a6 as MessageFunc, a7 as ModifyScoreCommand, a8 as ModifySessionScoreCommand, a9 as NumberMatch, aa as PersonalizationEvent, ab as PersonalizationEventVariantId, ac as PersonalizationManifest, ad as PersonalizationSelectionAlgorithm, ae as PersonalizationSelectionAlgorithms, af as PersonalizeControlVariant, ag as PersonalizeVariants, ah as QuirkMatch, ai as SERVER_STATE_ID, aj as ServerToClientTransitionState, ak as SetConsentCommand, al as SetControlGroupCommand, am as SetGoalCommand, an as SetPersonalizeVariantControlCommand, ao as SetQuirkCommand, ap as SetTestCommand, aq as Severity, ar as Signal, as as SignalCriteria, at as SignalCriteriaGroup, au as SignalData, av as StorageCommand, aw as TestDefinition, ax as TestEvent, ay as TestOptions, az as TestResult, aA as TestVariant, aB as Tests, aC as TransitionDataStoreEvents, aD as VariationMatchMetadata, aE as VisitorDataStore, aF as VisitorDataStoreEvents, aG as VisitorDataStoreOptions, aH as areCommandsEqual, aI as emptyVisitorData, aJ as testVariations } from './types-DovaM4gV.js';
1
+ import { O as OutputSeverity, L as LogDrain, C as ContextPlugin, S as ScoreVector, A as AggregateDimension, T as TransitionDataStore, a as LogMessage, b as StorageCommands, V as VisitorData, Q as Quirks, c as TransitionDataStoreOptions, D as DecayFunction, d as CriteriaEvaluator, e as StringMatch, f as VariantMatchCriteria, P as PersonalizedVariant, g as PersonalizeOptions, h as Context, i as PersonalizedResult, j as VariationMatchDimensionCriteria, k as PersonalizationSelectionAlgorithmOptions, l as DevToolsEvents } from './types-p9vnD820.js';
2
+ export { m as AggregateDimensionInput, B as BehaviorTag, n as CONTEXTUAL_EDITING_TEST_NAME, o as CONTEXTUAL_EDITING_TEST_SELECTED_VARIANT_ID, q as CompositionMetadata, r as ContextEvents, s as ContextInstance, t as ContextOptions, u as ContextState, v as ContextStateUpdate, w as CriteriaEvaluatorParameters, x as CriteriaEvaluatorResult, y as DecayOptions, z as DevToolsActions, E as DevToolsDataEvent, F as DevToolsEvent, G as DevToolsForgetEvent, H as DevToolsHelloEvent, I as DevToolsLogEvent, J as DevToolsRawCommandsEvent, K as DevToolsState, N as DevToolsUiVersion, R as DevToolsUpdateEvent, U as DimensionMatch, W as EnrichmentCategory, X as EnrichmentData, Y as EventData, Z as GoalStateUpdate, _ as Goals, $ as GroupCriteriaEvaluator, a0 as IdentifyCommand, a1 as LogMessageGroup, a2 as LogMessageSingle, a3 as LogMessages, a4 as ManifestInstance, M as ManifestV2, a5 as MessageCategory, a6 as MessageFunc, a7 as ModifyScoreCommand, a8 as ModifySessionScoreCommand, a9 as NumberMatch, aa as PersonalizationEvent, ab as PersonalizationEventVariantId, ac as PersonalizationManifest, ad as PersonalizationSelectionAlgorithm, ae as PersonalizationSelectionAlgorithms, af as PersonalizeControlVariant, ag as PersonalizeVariants, ah as QuirkMatch, ai as SERVER_STATE_ID, aj as ServerToClientTransitionState, ak as SetConsentCommand, al as SetControlGroupCommand, am as SetGoalCommand, an as SetPersonalizeVariantControlCommand, ao as SetQuirkCommand, ap as SetTestCommand, aq as Severity, ar as Signal, as as SignalCriteria, at as SignalCriteriaGroup, au as SignalData, av as StorageCommand, aw as TestDefinition, ax as TestEvent, ay as TestOptions, az as TestResult, aA as TestVariant, aB as Tests, aC as TransitionDataStoreEvents, aD as VariationMatchMetadata, aE as VisitorDataStore, aF as VisitorDataStoreEvents, aG as VisitorDataStoreOptions, aH as areCommandsEqual, aI as emptyVisitorData, aJ as testVariations } from './types-p9vnD820.js';
3
3
  import 'mitt';
4
4
 
5
5
  /**
@@ -55,6 +55,27 @@ type CookieTransitionDataStoreOptions = {
55
55
  * local storage is the master copy. Defaults to SameSite=Lax.
56
56
  */
57
57
  cookieAttributes?: Cookies.CookieAttributes;
58
+ /**
59
+ * Ceiling on the size in bytes of the transition cookie, measured on the percent-encoded
60
+ * `name=value` pair as the request header carries it.
61
+ *
62
+ * When the visitor's data does not fit, the least valuable parts are dropped until it does,
63
+ * deterministically: dimensions the cookie would write as zero, then remaining dimensions by
64
+ * ascending score, then personalize variants, then test assignments. Control group membership
65
+ * is never dropped, so a budget below the cookie's empty cost is refused at construction.
66
+ *
67
+ * Covers this cookie only, not the quirk cookie, its attributes or other cookies on the domain.
68
+ * Local storage remains the complete copy, so only what the server is told is lost.
69
+ *
70
+ * @defaultValue undefined - the cookie is unbounded
71
+ */
72
+ maxBytes?: number;
73
+ /**
74
+ * Called with log messages the store emits, such as a report of what `maxBytes` dropped.
75
+ * Also settable after construction, which is how a host that builds the store as an argument
76
+ * to its `Context` points it at {@link Context.log}.
77
+ */
78
+ onLogMessage?: (message: LogMessage) => void;
58
79
  };
59
80
  declare const UNIFORM_DEFAULT_COOKIE_NAME = "ufvd";
60
81
  declare const UNIFORM_DEFAULT_QUIRK_COOKIE_NAME = "ufvdqk";
@@ -64,7 +85,9 @@ declare const UNIFORM_DEFAULT_QUIRK_COOKIE_NAME = "ufvdqk";
64
85
  */
65
86
  declare class CookieTransitionDataStore extends TransitionDataStore {
66
87
  #private;
67
- constructor({ serverCookieValue, experimental_quirksEnabled, quirkCookieValue, quirkCookieName, cookieName, cookieAttributes, }: CookieTransitionDataStoreOptions);
88
+ /** Where the store's log messages go. See {@link CookieTransitionDataStoreOptions.onLogMessage}. */
89
+ onLogMessage: ((message: LogMessage) => void) | undefined;
90
+ constructor({ serverCookieValue, experimental_quirksEnabled, quirkCookieValue, quirkCookieName, cookieName, cookieAttributes, maxBytes, onLogMessage, }: CookieTransitionDataStoreOptions);
68
91
  handleDelete(): Promise<void>;
69
92
  handleUpdateData(_: StorageCommands[], computedValue: VisitorData): Promise<void>;
70
93
  }
@@ -77,7 +100,19 @@ declare function parseQuirkCookie(quirkCookieValue: string): Quirks;
77
100
  declare function parseCookieScores(type: string | undefined): Record<string, string>;
78
101
  declare function parseCookieType(type: string | undefined): Record<string, string>;
79
102
  declare function decodeCookieType(type: Record<string, string>): Record<string, number>;
80
- declare function serializeCookie(data: VisitorData): string;
103
+ type SerializeCookieOptions = {
104
+ /**
105
+ * Ceiling on the size in bytes of the percent-encoded `name=value` pair.
106
+ * Parts of the visitor data are dropped until the cookie fits; see the drop order on
107
+ * {@link CookieTransitionDataStoreOptions.maxBytes}.
108
+ */
109
+ maxBytes?: number;
110
+ /** Cookie name, counted toward `maxBytes` along with the `=` that follows it. */
111
+ cookieName?: string;
112
+ /** Called once, when `maxBytes` forced at least one drop, with what was dropped. */
113
+ onBudgetExceeded?: (dropped: string[], bytes: number, maxBytes: number) => void;
114
+ };
115
+ declare function serializeCookie(data: VisitorData, options?: SerializeCookieOptions): string;
81
116
  declare function serializePersonalizeVariants({ personalizeVariants, }: Pick<VisitorData, 'personalizeVariants'>): string;
82
117
  declare function decodePersonalizeVariants(data: string | undefined): VisitorData['personalizeVariants'];
83
118
  declare function encodeCookieType(type: Record<string, number>): Record<string, string>;
@@ -245,4 +280,4 @@ type QuickConnectConfig = {
245
280
  declare function serializeQuickConnect(config: QuickConnectConfig): string;
246
281
  declare function parseQuickConnect(serialized: string): Required<QuickConnectConfig>;
247
282
 
248
- export { AggregateDimension, type ConsoleDebugLogDrainOptions, Context, type ContextDevToolOptions, ContextPlugin, CookieTransitionDataStore, type CookieTransitionDataStoreOptions, CriteriaEvaluator, DecayFunction, DevToolsEvents, ENR_SEPARATOR, EdgeNodeTagName, type EdgePersonalizeComponentOptions, type EdgeTestComponentOptions, EdgeTransitionDataStore, type EdgeTransitionDataStoreOptions, type EnableUniformInsightsOptions, KV_SEP, type LinearDecayOptions, LogDrain, LogMessage, OutputSeverity, PAIR_SEP, PersonalizationSelectionAlgorithmOptions, PersonalizeOptions, PersonalizedResult, PersonalizedVariant, QUIRK_SEP, type QuickConnectConfig, Quirks, STRONGEST_SCORE_PERSONALIZATION_ALGORITHM, ScoreVector, ScriptType, StorageCommands, StringMatch, TOP_DOWN_CRITERIA_PERSONALIZATION_ALGORITHM, TYPE_SEP, TransitionDataStore, TransitionDataStoreOptions, UNIFORM_DEFAULT_COOKIE_NAME, UNIFORM_DEFAULT_QUIRK_COOKIE_NAME, VariantMatchCriteria, VariationMatchDimensionCriteria, VisitorData, computeAggregateDimensions, cookieEvaluator, createConsoleLogDrain, createDebugConsoleLogDrain, createLinearDecay, currentPageEvaluator, decodeCookieType, decodePersonalizeVariants, enableConsoleLogDrain, enableContextDevTools, enableDebugConsoleLogDrain, enableUniformInsights, encodeCookieType, evaluateVariantMatch, eventEvaluator, explainStringMatch, explainStringMatchCriteria, getEnrichmentVectorKey, isStringMatch, pageViewCountDimension, pageViewCountEvaluator, parseCookieScores, parseCookieType, parseQuickConnect, parseQuirkCookie, parseScoreCookie, personalizeVariations, queryStringEvaluator, quirkEvaluator, serializeCookie, serializeCookieType, serializePersonalizeVariants, serializeQuickConnect, serializeQuirks, strongestScorePersonalizationSelectionAlgorithm, topDownCriteriaPersonalizationSelectionAlgorithm };
283
+ export { AggregateDimension, type ConsoleDebugLogDrainOptions, Context, type ContextDevToolOptions, ContextPlugin, CookieTransitionDataStore, type CookieTransitionDataStoreOptions, CriteriaEvaluator, DecayFunction, DevToolsEvents, ENR_SEPARATOR, EdgeNodeTagName, type EdgePersonalizeComponentOptions, type EdgeTestComponentOptions, EdgeTransitionDataStore, type EdgeTransitionDataStoreOptions, type EnableUniformInsightsOptions, KV_SEP, type LinearDecayOptions, LogDrain, LogMessage, OutputSeverity, PAIR_SEP, PersonalizationSelectionAlgorithmOptions, PersonalizeOptions, PersonalizedResult, PersonalizedVariant, QUIRK_SEP, type QuickConnectConfig, Quirks, STRONGEST_SCORE_PERSONALIZATION_ALGORITHM, ScoreVector, ScriptType, type SerializeCookieOptions, StorageCommands, StringMatch, TOP_DOWN_CRITERIA_PERSONALIZATION_ALGORITHM, TYPE_SEP, TransitionDataStore, TransitionDataStoreOptions, UNIFORM_DEFAULT_COOKIE_NAME, UNIFORM_DEFAULT_QUIRK_COOKIE_NAME, VariantMatchCriteria, VariationMatchDimensionCriteria, VisitorData, computeAggregateDimensions, cookieEvaluator, createConsoleLogDrain, createDebugConsoleLogDrain, createLinearDecay, currentPageEvaluator, decodeCookieType, decodePersonalizeVariants, enableConsoleLogDrain, enableContextDevTools, enableDebugConsoleLogDrain, enableUniformInsights, encodeCookieType, evaluateVariantMatch, eventEvaluator, explainStringMatch, explainStringMatchCriteria, getEnrichmentVectorKey, isStringMatch, pageViewCountDimension, pageViewCountEvaluator, parseCookieScores, parseCookieType, parseQuickConnect, parseQuirkCookie, parseScoreCookie, personalizeVariations, queryStringEvaluator, quirkEvaluator, serializeCookie, serializeCookieType, serializePersonalizeVariants, serializeQuickConnect, serializeQuirks, strongestScorePersonalizationSelectionAlgorithm, topDownCriteriaPersonalizationSelectionAlgorithm };
package/dist/index.esm.js CHANGED
@@ -1074,11 +1074,14 @@ var bton = (base64) => {
1074
1074
  return sign ? -number : number;
1075
1075
  };
1076
1076
 
1077
+ // src/storage/util/zeroScore.ts
1078
+ var isZeroCookieScore = (score) => Math.abs(score) < 1;
1079
+
1077
1080
  // src/storage/CookieTransitionDataStore.ts
1078
1081
  var ssr = typeof document === "undefined";
1079
1082
  var UNIFORM_DEFAULT_COOKIE_NAME = "ufvd";
1080
1083
  var UNIFORM_DEFAULT_QUIRK_COOKIE_NAME = `ufvdqk`;
1081
- var _cookieName, _quirkCookieName, _cookieAttributes, _quirksEnabled;
1084
+ var _cookieName, _quirkCookieName, _cookieAttributes, _quirksEnabled, _maxBytes;
1082
1085
  var CookieTransitionDataStore = class extends TransitionDataStore {
1083
1086
  constructor({
1084
1087
  serverCookieValue,
@@ -1086,7 +1089,9 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1086
1089
  quirkCookieValue,
1087
1090
  quirkCookieName = UNIFORM_DEFAULT_QUIRK_COOKIE_NAME,
1088
1091
  cookieName = UNIFORM_DEFAULT_COOKIE_NAME,
1089
- cookieAttributes = { sameSite: "lax" }
1092
+ cookieAttributes = { sameSite: "lax" },
1093
+ maxBytes,
1094
+ onLogMessage
1090
1095
  }) {
1091
1096
  super({
1092
1097
  initialData: ssr ? parseScoreCookie(serverCookieValue, experimental_quirksEnabled ? quirkCookieValue : void 0) : void 0
@@ -1095,10 +1100,21 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1095
1100
  __privateAdd(this, _quirkCookieName);
1096
1101
  __privateAdd(this, _cookieAttributes);
1097
1102
  __privateAdd(this, _quirksEnabled);
1103
+ __privateAdd(this, _maxBytes);
1104
+ /** Where the store's log messages go. See {@link CookieTransitionDataStoreOptions.onLogMessage}. */
1105
+ __publicField(this, "onLogMessage");
1098
1106
  __privateSet(this, _cookieName, cookieName);
1099
1107
  __privateSet(this, _cookieAttributes, cookieAttributes);
1100
1108
  __privateSet(this, _quirkCookieName, quirkCookieName);
1101
1109
  __privateSet(this, _quirksEnabled, experimental_quirksEnabled || false);
1110
+ __privateSet(this, _maxBytes, maxBytes);
1111
+ this.onLogMessage = onLogMessage;
1112
+ const minimumBytes = minimumCookieBytes(cookieName);
1113
+ if (maxBytes !== void 0 && maxBytes < minimumBytes) {
1114
+ throw new Error(
1115
+ `maxBytes of ${maxBytes} is below the ${minimumBytes} bytes the "${cookieName}" cookie costs when it carries nothing but control group membership, so no visitor could ever meet it.`
1116
+ );
1117
+ }
1102
1118
  }
1103
1119
  handleDelete() {
1104
1120
  if (ssr) return Promise.resolve();
@@ -1108,7 +1124,15 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1108
1124
  async handleUpdateData(_, computedValue) {
1109
1125
  if (ssr) return;
1110
1126
  if (computedValue.consent) {
1111
- Cookies.set(__privateGet(this, _cookieName), serializeCookie(computedValue), __privateGet(this, _cookieAttributes));
1127
+ const cookieValue = serializeCookie(computedValue, {
1128
+ maxBytes: __privateGet(this, _maxBytes),
1129
+ cookieName: __privateGet(this, _cookieName),
1130
+ onBudgetExceeded: (dropped, bytes, maxBytes) => {
1131
+ var _a;
1132
+ return (_a = this.onLogMessage) == null ? void 0 : _a.call(this, ["info", 151, { dropped, bytes, maxBytes }]);
1133
+ }
1134
+ });
1135
+ Cookies.set(__privateGet(this, _cookieName), cookieValue, __privateGet(this, _cookieAttributes));
1112
1136
  if (__privateGet(this, _quirksEnabled) && Object.keys(computedValue.quirks || {}).length) {
1113
1137
  const quirkCookieValue = serializeQuirks(computedValue.quirks || {});
1114
1138
  Cookies.set(__privateGet(this, _quirkCookieName), quirkCookieValue, __privateGet(this, _cookieAttributes));
@@ -1125,6 +1149,7 @@ _cookieName = new WeakMap();
1125
1149
  _quirkCookieName = new WeakMap();
1126
1150
  _cookieAttributes = new WeakMap();
1127
1151
  _quirksEnabled = new WeakMap();
1152
+ _maxBytes = new WeakMap();
1128
1153
  var TYPE_SEP = "~";
1129
1154
  var PAIR_SEP = "!";
1130
1155
  var KV_SEP = "-";
@@ -1196,18 +1221,71 @@ function decodeCookieType(type) {
1196
1221
  return acc;
1197
1222
  }, {});
1198
1223
  }
1199
- function serializeCookie(data) {
1224
+ function serializeCookie(data, options = {}) {
1225
+ const { maxBytes, cookieName = UNIFORM_DEFAULT_COOKIE_NAME, onBudgetExceeded } = options;
1226
+ const parts = {
1227
+ tests: { ...data.tests },
1228
+ sessionScores: encodeCookieType(data.sessionScores),
1229
+ scores: encodeCookieType(data.scores),
1230
+ controlGroup: data.controlGroup ? "1" : "0",
1231
+ personalizeVariants: encodePersonalizeVariants(data.personalizeVariants)
1232
+ };
1233
+ let value = serializeCookieParts(parts);
1234
+ if (maxBytes === void 0) {
1235
+ return value;
1236
+ }
1237
+ const overhead = encodeCookieName(cookieName).length + 1;
1238
+ const measure = () => overhead + Cookies.converter.write(value, cookieName).length;
1239
+ if (measure() <= maxBytes) {
1240
+ return value;
1241
+ }
1242
+ const candidates = dropCandidates(parts, data);
1243
+ const dropped = [];
1244
+ for (let i = 0; i < candidates.length && measure() > maxBytes; i++) {
1245
+ const { bucket, key } = candidates[i];
1246
+ delete parts[bucket][key];
1247
+ dropped.push(`${bucket}.${key}`);
1248
+ value = serializeCookieParts(parts);
1249
+ }
1250
+ if (dropped.length) {
1251
+ onBudgetExceeded == null ? void 0 : onBudgetExceeded(dropped, measure(), maxBytes);
1252
+ }
1253
+ return value;
1254
+ }
1255
+ function serializeCookieParts(parts) {
1200
1256
  return [
1201
- serializeCookieType(data.tests),
1202
- serializeCookieType(encodeCookieType(data.sessionScores)),
1203
- serializeCookieType(encodeCookieType(data.scores)),
1204
- data.controlGroup ? "1" : "0",
1205
- serializePersonalizeVariants(data)
1257
+ serializeCookieType(parts.tests),
1258
+ serializeCookieType(parts.sessionScores),
1259
+ serializeCookieType(parts.scores),
1260
+ parts.controlGroup,
1261
+ serializeCookieType(parts.personalizeVariants)
1206
1262
  ].join(TYPE_SEP);
1207
1263
  }
1264
+ function dropCandidates(parts, data) {
1265
+ const dimensions = ["sessionScores", "scores"].flatMap(
1266
+ (bucket, order) => Object.keys(parts[bucket]).map((key) => {
1267
+ var _a;
1268
+ return { bucket, key, order, score: (_a = data[bucket][key]) != null ? _a : 0 };
1269
+ })
1270
+ );
1271
+ dimensions.sort(
1272
+ (a, b) => Number(isZeroCookieScore(b.score)) - Number(isZeroCookieScore(a.score)) || a.score - b.score || a.order - b.order || (a.key < b.key ? -1 : 1)
1273
+ );
1274
+ const keysOf = (bucket) => Object.keys(parts[bucket]).sort().map((key) => ({ bucket, key }));
1275
+ return [...dimensions, ...keysOf("personalizeVariants"), ...keysOf("tests")];
1276
+ }
1277
+ function minimumCookieBytes(cookieName) {
1278
+ return encodeCookieName(cookieName).length + 6;
1279
+ }
1280
+ function encodeCookieName(name) {
1281
+ return encodeURIComponent(name).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent).replace(/\(/g, "%28").replace(/\)/g, "%29");
1282
+ }
1208
1283
  function serializePersonalizeVariants({
1209
1284
  personalizeVariants
1210
1285
  }) {
1286
+ return serializeCookieType(encodePersonalizeVariants(personalizeVariants));
1287
+ }
1288
+ function encodePersonalizeVariants(personalizeVariants) {
1211
1289
  const data = {};
1212
1290
  if (typeof personalizeVariants === "object") {
1213
1291
  Object.keys(personalizeVariants).forEach((personalizationName) => {
@@ -1219,8 +1297,7 @@ function serializePersonalizeVariants({
1219
1297
  data[personalizationName] = results.join(",");
1220
1298
  });
1221
1299
  }
1222
- const serialized = serializeCookieType(data);
1223
- return serialized;
1300
+ return data;
1224
1301
  }
1225
1302
  function decodePersonalizeVariants(data) {
1226
1303
  const parsed = parseCookieType(data);
@@ -1293,6 +1370,9 @@ fetchData_fn = async function() {
1293
1370
  }
1294
1371
  };
1295
1372
 
1373
+ // src/storage/util/systemDimension.ts
1374
+ var isSystemDimension = (dimension) => dimension.startsWith("$");
1375
+
1296
1376
  // src/storage/linearDecay.ts
1297
1377
  function createLinearDecay(options) {
1298
1378
  const { gracePeriod = 864e5, decayRate = 1 / 30, decayCap = 0.95 } = options != null ? options : {};
@@ -1318,7 +1398,7 @@ function createLinearDecay(options) {
1318
1398
  }
1319
1399
  function decayVector(vector, decay) {
1320
1400
  for (const key in vector) {
1321
- if (key.startsWith("$")) {
1401
+ if (isSystemDimension(key)) {
1322
1402
  continue;
1323
1403
  }
1324
1404
  vector[key] *= decay;
@@ -1462,7 +1542,7 @@ key_fn = function(key) {
1462
1542
 
1463
1543
  // src/storage/VisitorDataStore.ts
1464
1544
  var STORAGE_KEY = "ufvisitor";
1465
- var _mitt2, _persist, _visitTimeout, _options, _VisitorDataStore_instances, currentData_get, replaceData_fn, setVisitTimeout_fn, isExpired_fn, handleCaps_fn, defaultData_fn;
1545
+ var _mitt2, _persist, _visitTimeout, _options, _VisitorDataStore_instances, currentData_get, replaceData_fn, setVisitTimeout_fn, isExpired_fn, handleCaps_fn, pruningManifest_get, handlePruning_fn, pruneScores_fn, defaultData_fn;
1466
1546
  var VisitorDataStore = class {
1467
1547
  constructor(options) {
1468
1548
  __privateAdd(this, _VisitorDataStore_instances);
@@ -1580,6 +1660,7 @@ replaceData_fn = function(data, quiet = false) {
1580
1660
  sessionScores: data.sessionScores,
1581
1661
  onLogMessage: __privateGet(this, _options).onLogMessage
1582
1662
  });
1663
+ __privateMethod(this, _VisitorDataStore_instances, handlePruning_fn).call(this, data);
1583
1664
  const haveScoresChanged = !dequal4(oldData == null ? void 0 : oldData.visitorData.scores, data.scores);
1584
1665
  const haveSessionScoresChanged = !dequal4(oldData == null ? void 0 : oldData.visitorData.sessionScores, data.sessionScores);
1585
1666
  const haveQuirksChanged = !dequal4(oldData == null ? void 0 : oldData.visitorData.quirks, data.quirks);
@@ -1657,6 +1738,58 @@ handleCaps_fn = function(scores) {
1657
1738
  }
1658
1739
  }
1659
1740
  };
1741
+ pruningManifest_get = function() {
1742
+ var _a, _b, _c, _d, _e;
1743
+ const project = (_b = (_a = __privateGet(this, _options).manifest) == null ? void 0 : _a.data) == null ? void 0 : _b.project;
1744
+ const populated = [(_c = project == null ? void 0 : project.pz) == null ? void 0 : _c.sig, (_d = project == null ? void 0 : project.pz) == null ? void 0 : _d.enr, (_e = project == null ? void 0 : project.pz) == null ? void 0 : _e.agg, project == null ? void 0 : project.test].some(
1745
+ (definitions) => definitions && Object.keys(definitions).length > 0
1746
+ );
1747
+ return populated ? __privateGet(this, _options).manifest : void 0;
1748
+ };
1749
+ /**
1750
+ * IMPORTANT: This function mutates the input data, as #replaceData does.
1751
+ *
1752
+ * Removes stored keys that the host has opted to stop carrying. The two manifest-driven
1753
+ * prunes are skipped entirely when no populated manifest is loaded, so a partial load destroys
1754
+ * nothing; zero-score pruning consults no manifest and always runs when enabled.
1755
+ */
1756
+ handlePruning_fn = function(data) {
1757
+ var _a, _b;
1758
+ const { pruneUnknownTests, pruneUnknownDimensions } = __privateGet(this, _options);
1759
+ const manifest = pruneUnknownTests || pruneUnknownDimensions ? __privateGet(this, _VisitorDataStore_instances, pruningManifest_get) : void 0;
1760
+ const pruned = [];
1761
+ if (pruneUnknownTests && manifest) {
1762
+ for (const test in data.tests) {
1763
+ if (!manifest.getTest(test)) {
1764
+ delete data.tests[test];
1765
+ pruned.push(`tests.${test}`);
1766
+ }
1767
+ }
1768
+ }
1769
+ __privateMethod(this, _VisitorDataStore_instances, pruneScores_fn).call(this, data.scores, "scores", pruned, manifest);
1770
+ __privateMethod(this, _VisitorDataStore_instances, pruneScores_fn).call(this, data.sessionScores, "sessionScores", pruned, manifest);
1771
+ if (pruned.length) {
1772
+ (_b = (_a = __privateGet(this, _options)).onLogMessage) == null ? void 0 : _b.call(_a, ["info", 150, pruned]);
1773
+ }
1774
+ };
1775
+ /**
1776
+ * IMPORTANT: This function mutates the input scores.
1777
+ */
1778
+ pruneScores_fn = function(scores, bucket, pruned, manifest) {
1779
+ const { pruneUnknownDimensions, pruneZeroScores } = __privateGet(this, _options);
1780
+ if (!pruneUnknownDimensions && !pruneZeroScores) {
1781
+ return;
1782
+ }
1783
+ const pruneUnknown = pruneUnknownDimensions && manifest;
1784
+ for (const dim in scores) {
1785
+ const isUnknown = pruneUnknown && !isSystemDimension(dim) && !manifest.getDimensionByKey(dim);
1786
+ const isZero = pruneZeroScores && (pruneZeroScores === "round" ? isZeroCookieScore(scores[dim]) : scores[dim] === 0);
1787
+ if (isUnknown || isZero) {
1788
+ delete scores[dim];
1789
+ pruned.push(`${bucket}.${dim}`);
1790
+ }
1791
+ }
1792
+ };
1660
1793
  defaultData_fn = function() {
1661
1794
  var _a, _b, _c;
1662
1795
  return {
@@ -2743,6 +2876,13 @@ var messageContent = {
2743
2876
  ],
2744
2877
  131: () => ["storage", "server to client transition data was discarded"],
2745
2878
  140: (decayMessage) => ["storage", `score decay was applied: ${decayMessage}`],
2879
+ 150: (keys) => ["storage", `pruned from stored visitor data: ${keys.join(", ")}`],
2880
+ 151: ({ dropped, bytes, maxBytes }) => [
2881
+ "storage",
2882
+ `transition cookie exceeded its ${maxBytes} byte budget; dropped ${dropped.join(
2883
+ ", "
2884
+ )} to reach ${bytes} bytes`
2885
+ ],
2746
2886
  // SIGNAL EVAL
2747
2887
  200: () => ["signals", "evaluating signals"],
2748
2888
  201: (signal) => ["signals", `evaluating signal ${signal.id} (${signal.dur})`],
package/dist/index.js CHANGED
@@ -1167,11 +1167,14 @@ var bton = (base64) => {
1167
1167
  return sign ? -number : number;
1168
1168
  };
1169
1169
 
1170
+ // src/storage/util/zeroScore.ts
1171
+ var isZeroCookieScore = (score) => Math.abs(score) < 1;
1172
+
1170
1173
  // src/storage/CookieTransitionDataStore.ts
1171
1174
  var ssr = typeof document === "undefined";
1172
1175
  var UNIFORM_DEFAULT_COOKIE_NAME = "ufvd";
1173
1176
  var UNIFORM_DEFAULT_QUIRK_COOKIE_NAME = `ufvdqk`;
1174
- var _cookieName, _quirkCookieName, _cookieAttributes, _quirksEnabled;
1177
+ var _cookieName, _quirkCookieName, _cookieAttributes, _quirksEnabled, _maxBytes;
1175
1178
  var CookieTransitionDataStore = class extends TransitionDataStore {
1176
1179
  constructor({
1177
1180
  serverCookieValue,
@@ -1179,7 +1182,9 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1179
1182
  quirkCookieValue,
1180
1183
  quirkCookieName = UNIFORM_DEFAULT_QUIRK_COOKIE_NAME,
1181
1184
  cookieName = UNIFORM_DEFAULT_COOKIE_NAME,
1182
- cookieAttributes = { sameSite: "lax" }
1185
+ cookieAttributes = { sameSite: "lax" },
1186
+ maxBytes,
1187
+ onLogMessage
1183
1188
  }) {
1184
1189
  super({
1185
1190
  initialData: ssr ? parseScoreCookie(serverCookieValue, experimental_quirksEnabled ? quirkCookieValue : void 0) : void 0
@@ -1188,10 +1193,21 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1188
1193
  __privateAdd(this, _quirkCookieName);
1189
1194
  __privateAdd(this, _cookieAttributes);
1190
1195
  __privateAdd(this, _quirksEnabled);
1196
+ __privateAdd(this, _maxBytes);
1197
+ /** Where the store's log messages go. See {@link CookieTransitionDataStoreOptions.onLogMessage}. */
1198
+ __publicField(this, "onLogMessage");
1191
1199
  __privateSet(this, _cookieName, cookieName);
1192
1200
  __privateSet(this, _cookieAttributes, cookieAttributes);
1193
1201
  __privateSet(this, _quirkCookieName, quirkCookieName);
1194
1202
  __privateSet(this, _quirksEnabled, experimental_quirksEnabled || false);
1203
+ __privateSet(this, _maxBytes, maxBytes);
1204
+ this.onLogMessage = onLogMessage;
1205
+ const minimumBytes = minimumCookieBytes(cookieName);
1206
+ if (maxBytes !== void 0 && maxBytes < minimumBytes) {
1207
+ throw new Error(
1208
+ `maxBytes of ${maxBytes} is below the ${minimumBytes} bytes the "${cookieName}" cookie costs when it carries nothing but control group membership, so no visitor could ever meet it.`
1209
+ );
1210
+ }
1195
1211
  }
1196
1212
  handleDelete() {
1197
1213
  if (ssr) return Promise.resolve();
@@ -1201,7 +1217,15 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1201
1217
  async handleUpdateData(_, computedValue) {
1202
1218
  if (ssr) return;
1203
1219
  if (computedValue.consent) {
1204
- import_js_cookie.default.set(__privateGet(this, _cookieName), serializeCookie(computedValue), __privateGet(this, _cookieAttributes));
1220
+ const cookieValue = serializeCookie(computedValue, {
1221
+ maxBytes: __privateGet(this, _maxBytes),
1222
+ cookieName: __privateGet(this, _cookieName),
1223
+ onBudgetExceeded: (dropped, bytes, maxBytes) => {
1224
+ var _a;
1225
+ return (_a = this.onLogMessage) == null ? void 0 : _a.call(this, ["info", 151, { dropped, bytes, maxBytes }]);
1226
+ }
1227
+ });
1228
+ import_js_cookie.default.set(__privateGet(this, _cookieName), cookieValue, __privateGet(this, _cookieAttributes));
1205
1229
  if (__privateGet(this, _quirksEnabled) && Object.keys(computedValue.quirks || {}).length) {
1206
1230
  const quirkCookieValue = serializeQuirks(computedValue.quirks || {});
1207
1231
  import_js_cookie.default.set(__privateGet(this, _quirkCookieName), quirkCookieValue, __privateGet(this, _cookieAttributes));
@@ -1218,6 +1242,7 @@ _cookieName = new WeakMap();
1218
1242
  _quirkCookieName = new WeakMap();
1219
1243
  _cookieAttributes = new WeakMap();
1220
1244
  _quirksEnabled = new WeakMap();
1245
+ _maxBytes = new WeakMap();
1221
1246
  var TYPE_SEP = "~";
1222
1247
  var PAIR_SEP = "!";
1223
1248
  var KV_SEP = "-";
@@ -1289,18 +1314,71 @@ function decodeCookieType(type) {
1289
1314
  return acc;
1290
1315
  }, {});
1291
1316
  }
1292
- function serializeCookie(data) {
1317
+ function serializeCookie(data, options = {}) {
1318
+ const { maxBytes, cookieName = UNIFORM_DEFAULT_COOKIE_NAME, onBudgetExceeded } = options;
1319
+ const parts = {
1320
+ tests: { ...data.tests },
1321
+ sessionScores: encodeCookieType(data.sessionScores),
1322
+ scores: encodeCookieType(data.scores),
1323
+ controlGroup: data.controlGroup ? "1" : "0",
1324
+ personalizeVariants: encodePersonalizeVariants(data.personalizeVariants)
1325
+ };
1326
+ let value = serializeCookieParts(parts);
1327
+ if (maxBytes === void 0) {
1328
+ return value;
1329
+ }
1330
+ const overhead = encodeCookieName(cookieName).length + 1;
1331
+ const measure = () => overhead + import_js_cookie.default.converter.write(value, cookieName).length;
1332
+ if (measure() <= maxBytes) {
1333
+ return value;
1334
+ }
1335
+ const candidates = dropCandidates(parts, data);
1336
+ const dropped = [];
1337
+ for (let i = 0; i < candidates.length && measure() > maxBytes; i++) {
1338
+ const { bucket, key } = candidates[i];
1339
+ delete parts[bucket][key];
1340
+ dropped.push(`${bucket}.${key}`);
1341
+ value = serializeCookieParts(parts);
1342
+ }
1343
+ if (dropped.length) {
1344
+ onBudgetExceeded == null ? void 0 : onBudgetExceeded(dropped, measure(), maxBytes);
1345
+ }
1346
+ return value;
1347
+ }
1348
+ function serializeCookieParts(parts) {
1293
1349
  return [
1294
- serializeCookieType(data.tests),
1295
- serializeCookieType(encodeCookieType(data.sessionScores)),
1296
- serializeCookieType(encodeCookieType(data.scores)),
1297
- data.controlGroup ? "1" : "0",
1298
- serializePersonalizeVariants(data)
1350
+ serializeCookieType(parts.tests),
1351
+ serializeCookieType(parts.sessionScores),
1352
+ serializeCookieType(parts.scores),
1353
+ parts.controlGroup,
1354
+ serializeCookieType(parts.personalizeVariants)
1299
1355
  ].join(TYPE_SEP);
1300
1356
  }
1357
+ function dropCandidates(parts, data) {
1358
+ const dimensions = ["sessionScores", "scores"].flatMap(
1359
+ (bucket, order) => Object.keys(parts[bucket]).map((key) => {
1360
+ var _a;
1361
+ return { bucket, key, order, score: (_a = data[bucket][key]) != null ? _a : 0 };
1362
+ })
1363
+ );
1364
+ dimensions.sort(
1365
+ (a, b) => Number(isZeroCookieScore(b.score)) - Number(isZeroCookieScore(a.score)) || a.score - b.score || a.order - b.order || (a.key < b.key ? -1 : 1)
1366
+ );
1367
+ const keysOf = (bucket) => Object.keys(parts[bucket]).sort().map((key) => ({ bucket, key }));
1368
+ return [...dimensions, ...keysOf("personalizeVariants"), ...keysOf("tests")];
1369
+ }
1370
+ function minimumCookieBytes(cookieName) {
1371
+ return encodeCookieName(cookieName).length + 6;
1372
+ }
1373
+ function encodeCookieName(name) {
1374
+ return encodeURIComponent(name).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent).replace(/\(/g, "%28").replace(/\)/g, "%29");
1375
+ }
1301
1376
  function serializePersonalizeVariants({
1302
1377
  personalizeVariants
1303
1378
  }) {
1379
+ return serializeCookieType(encodePersonalizeVariants(personalizeVariants));
1380
+ }
1381
+ function encodePersonalizeVariants(personalizeVariants) {
1304
1382
  const data = {};
1305
1383
  if (typeof personalizeVariants === "object") {
1306
1384
  Object.keys(personalizeVariants).forEach((personalizationName) => {
@@ -1312,8 +1390,7 @@ function serializePersonalizeVariants({
1312
1390
  data[personalizationName] = results.join(",");
1313
1391
  });
1314
1392
  }
1315
- const serialized = serializeCookieType(data);
1316
- return serialized;
1393
+ return data;
1317
1394
  }
1318
1395
  function decodePersonalizeVariants(data) {
1319
1396
  const parsed = parseCookieType(data);
@@ -1386,6 +1463,9 @@ fetchData_fn = async function() {
1386
1463
  }
1387
1464
  };
1388
1465
 
1466
+ // src/storage/util/systemDimension.ts
1467
+ var isSystemDimension = (dimension) => dimension.startsWith("$");
1468
+
1389
1469
  // src/storage/linearDecay.ts
1390
1470
  function createLinearDecay(options) {
1391
1471
  const { gracePeriod = 864e5, decayRate = 1 / 30, decayCap = 0.95 } = options != null ? options : {};
@@ -1411,7 +1491,7 @@ function createLinearDecay(options) {
1411
1491
  }
1412
1492
  function decayVector(vector, decay) {
1413
1493
  for (const key in vector) {
1414
- if (key.startsWith("$")) {
1494
+ if (isSystemDimension(key)) {
1415
1495
  continue;
1416
1496
  }
1417
1497
  vector[key] *= decay;
@@ -1555,7 +1635,7 @@ key_fn = function(key) {
1555
1635
 
1556
1636
  // src/storage/VisitorDataStore.ts
1557
1637
  var STORAGE_KEY = "ufvisitor";
1558
- var _mitt2, _persist, _visitTimeout, _options, _VisitorDataStore_instances, currentData_get, replaceData_fn, setVisitTimeout_fn, isExpired_fn, handleCaps_fn, defaultData_fn;
1638
+ var _mitt2, _persist, _visitTimeout, _options, _VisitorDataStore_instances, currentData_get, replaceData_fn, setVisitTimeout_fn, isExpired_fn, handleCaps_fn, pruningManifest_get, handlePruning_fn, pruneScores_fn, defaultData_fn;
1559
1639
  var VisitorDataStore = class {
1560
1640
  constructor(options) {
1561
1641
  __privateAdd(this, _VisitorDataStore_instances);
@@ -1673,6 +1753,7 @@ replaceData_fn = function(data, quiet = false) {
1673
1753
  sessionScores: data.sessionScores,
1674
1754
  onLogMessage: __privateGet(this, _options).onLogMessage
1675
1755
  });
1756
+ __privateMethod(this, _VisitorDataStore_instances, handlePruning_fn).call(this, data);
1676
1757
  const haveScoresChanged = !(0, import_lite4.dequal)(oldData == null ? void 0 : oldData.visitorData.scores, data.scores);
1677
1758
  const haveSessionScoresChanged = !(0, import_lite4.dequal)(oldData == null ? void 0 : oldData.visitorData.sessionScores, data.sessionScores);
1678
1759
  const haveQuirksChanged = !(0, import_lite4.dequal)(oldData == null ? void 0 : oldData.visitorData.quirks, data.quirks);
@@ -1750,6 +1831,58 @@ handleCaps_fn = function(scores) {
1750
1831
  }
1751
1832
  }
1752
1833
  };
1834
+ pruningManifest_get = function() {
1835
+ var _a, _b, _c, _d, _e;
1836
+ const project = (_b = (_a = __privateGet(this, _options).manifest) == null ? void 0 : _a.data) == null ? void 0 : _b.project;
1837
+ const populated = [(_c = project == null ? void 0 : project.pz) == null ? void 0 : _c.sig, (_d = project == null ? void 0 : project.pz) == null ? void 0 : _d.enr, (_e = project == null ? void 0 : project.pz) == null ? void 0 : _e.agg, project == null ? void 0 : project.test].some(
1838
+ (definitions) => definitions && Object.keys(definitions).length > 0
1839
+ );
1840
+ return populated ? __privateGet(this, _options).manifest : void 0;
1841
+ };
1842
+ /**
1843
+ * IMPORTANT: This function mutates the input data, as #replaceData does.
1844
+ *
1845
+ * Removes stored keys that the host has opted to stop carrying. The two manifest-driven
1846
+ * prunes are skipped entirely when no populated manifest is loaded, so a partial load destroys
1847
+ * nothing; zero-score pruning consults no manifest and always runs when enabled.
1848
+ */
1849
+ handlePruning_fn = function(data) {
1850
+ var _a, _b;
1851
+ const { pruneUnknownTests, pruneUnknownDimensions } = __privateGet(this, _options);
1852
+ const manifest = pruneUnknownTests || pruneUnknownDimensions ? __privateGet(this, _VisitorDataStore_instances, pruningManifest_get) : void 0;
1853
+ const pruned = [];
1854
+ if (pruneUnknownTests && manifest) {
1855
+ for (const test in data.tests) {
1856
+ if (!manifest.getTest(test)) {
1857
+ delete data.tests[test];
1858
+ pruned.push(`tests.${test}`);
1859
+ }
1860
+ }
1861
+ }
1862
+ __privateMethod(this, _VisitorDataStore_instances, pruneScores_fn).call(this, data.scores, "scores", pruned, manifest);
1863
+ __privateMethod(this, _VisitorDataStore_instances, pruneScores_fn).call(this, data.sessionScores, "sessionScores", pruned, manifest);
1864
+ if (pruned.length) {
1865
+ (_b = (_a = __privateGet(this, _options)).onLogMessage) == null ? void 0 : _b.call(_a, ["info", 150, pruned]);
1866
+ }
1867
+ };
1868
+ /**
1869
+ * IMPORTANT: This function mutates the input scores.
1870
+ */
1871
+ pruneScores_fn = function(scores, bucket, pruned, manifest) {
1872
+ const { pruneUnknownDimensions, pruneZeroScores } = __privateGet(this, _options);
1873
+ if (!pruneUnknownDimensions && !pruneZeroScores) {
1874
+ return;
1875
+ }
1876
+ const pruneUnknown = pruneUnknownDimensions && manifest;
1877
+ for (const dim in scores) {
1878
+ const isUnknown = pruneUnknown && !isSystemDimension(dim) && !manifest.getDimensionByKey(dim);
1879
+ const isZero = pruneZeroScores && (pruneZeroScores === "round" ? isZeroCookieScore(scores[dim]) : scores[dim] === 0);
1880
+ if (isUnknown || isZero) {
1881
+ delete scores[dim];
1882
+ pruned.push(`${bucket}.${dim}`);
1883
+ }
1884
+ }
1885
+ };
1753
1886
  defaultData_fn = function() {
1754
1887
  var _a, _b, _c;
1755
1888
  return {
@@ -2836,6 +2969,13 @@ var messageContent = {
2836
2969
  ],
2837
2970
  131: () => ["storage", "server to client transition data was discarded"],
2838
2971
  140: (decayMessage) => ["storage", `score decay was applied: ${decayMessage}`],
2972
+ 150: (keys) => ["storage", `pruned from stored visitor data: ${keys.join(", ")}`],
2973
+ 151: ({ dropped, bytes, maxBytes }) => [
2974
+ "storage",
2975
+ `transition cookie exceeded its ${maxBytes} byte budget; dropped ${dropped.join(
2976
+ ", "
2977
+ )} to reach ${bytes} bytes`
2978
+ ],
2839
2979
  // SIGNAL EVAL
2840
2980
  200: () => ["signals", "evaluating signals"],
2841
2981
  201: (signal) => ["signals", `evaluating signal ${signal.id} (${signal.dur})`],
package/dist/index.mjs CHANGED
@@ -1074,11 +1074,14 @@ var bton = (base64) => {
1074
1074
  return sign ? -number : number;
1075
1075
  };
1076
1076
 
1077
+ // src/storage/util/zeroScore.ts
1078
+ var isZeroCookieScore = (score) => Math.abs(score) < 1;
1079
+
1077
1080
  // src/storage/CookieTransitionDataStore.ts
1078
1081
  var ssr = typeof document === "undefined";
1079
1082
  var UNIFORM_DEFAULT_COOKIE_NAME = "ufvd";
1080
1083
  var UNIFORM_DEFAULT_QUIRK_COOKIE_NAME = `ufvdqk`;
1081
- var _cookieName, _quirkCookieName, _cookieAttributes, _quirksEnabled;
1084
+ var _cookieName, _quirkCookieName, _cookieAttributes, _quirksEnabled, _maxBytes;
1082
1085
  var CookieTransitionDataStore = class extends TransitionDataStore {
1083
1086
  constructor({
1084
1087
  serverCookieValue,
@@ -1086,7 +1089,9 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1086
1089
  quirkCookieValue,
1087
1090
  quirkCookieName = UNIFORM_DEFAULT_QUIRK_COOKIE_NAME,
1088
1091
  cookieName = UNIFORM_DEFAULT_COOKIE_NAME,
1089
- cookieAttributes = { sameSite: "lax" }
1092
+ cookieAttributes = { sameSite: "lax" },
1093
+ maxBytes,
1094
+ onLogMessage
1090
1095
  }) {
1091
1096
  super({
1092
1097
  initialData: ssr ? parseScoreCookie(serverCookieValue, experimental_quirksEnabled ? quirkCookieValue : void 0) : void 0
@@ -1095,10 +1100,21 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1095
1100
  __privateAdd(this, _quirkCookieName);
1096
1101
  __privateAdd(this, _cookieAttributes);
1097
1102
  __privateAdd(this, _quirksEnabled);
1103
+ __privateAdd(this, _maxBytes);
1104
+ /** Where the store's log messages go. See {@link CookieTransitionDataStoreOptions.onLogMessage}. */
1105
+ __publicField(this, "onLogMessage");
1098
1106
  __privateSet(this, _cookieName, cookieName);
1099
1107
  __privateSet(this, _cookieAttributes, cookieAttributes);
1100
1108
  __privateSet(this, _quirkCookieName, quirkCookieName);
1101
1109
  __privateSet(this, _quirksEnabled, experimental_quirksEnabled || false);
1110
+ __privateSet(this, _maxBytes, maxBytes);
1111
+ this.onLogMessage = onLogMessage;
1112
+ const minimumBytes = minimumCookieBytes(cookieName);
1113
+ if (maxBytes !== void 0 && maxBytes < minimumBytes) {
1114
+ throw new Error(
1115
+ `maxBytes of ${maxBytes} is below the ${minimumBytes} bytes the "${cookieName}" cookie costs when it carries nothing but control group membership, so no visitor could ever meet it.`
1116
+ );
1117
+ }
1102
1118
  }
1103
1119
  handleDelete() {
1104
1120
  if (ssr) return Promise.resolve();
@@ -1108,7 +1124,15 @@ var CookieTransitionDataStore = class extends TransitionDataStore {
1108
1124
  async handleUpdateData(_, computedValue) {
1109
1125
  if (ssr) return;
1110
1126
  if (computedValue.consent) {
1111
- Cookies.set(__privateGet(this, _cookieName), serializeCookie(computedValue), __privateGet(this, _cookieAttributes));
1127
+ const cookieValue = serializeCookie(computedValue, {
1128
+ maxBytes: __privateGet(this, _maxBytes),
1129
+ cookieName: __privateGet(this, _cookieName),
1130
+ onBudgetExceeded: (dropped, bytes, maxBytes) => {
1131
+ var _a;
1132
+ return (_a = this.onLogMessage) == null ? void 0 : _a.call(this, ["info", 151, { dropped, bytes, maxBytes }]);
1133
+ }
1134
+ });
1135
+ Cookies.set(__privateGet(this, _cookieName), cookieValue, __privateGet(this, _cookieAttributes));
1112
1136
  if (__privateGet(this, _quirksEnabled) && Object.keys(computedValue.quirks || {}).length) {
1113
1137
  const quirkCookieValue = serializeQuirks(computedValue.quirks || {});
1114
1138
  Cookies.set(__privateGet(this, _quirkCookieName), quirkCookieValue, __privateGet(this, _cookieAttributes));
@@ -1125,6 +1149,7 @@ _cookieName = new WeakMap();
1125
1149
  _quirkCookieName = new WeakMap();
1126
1150
  _cookieAttributes = new WeakMap();
1127
1151
  _quirksEnabled = new WeakMap();
1152
+ _maxBytes = new WeakMap();
1128
1153
  var TYPE_SEP = "~";
1129
1154
  var PAIR_SEP = "!";
1130
1155
  var KV_SEP = "-";
@@ -1196,18 +1221,71 @@ function decodeCookieType(type) {
1196
1221
  return acc;
1197
1222
  }, {});
1198
1223
  }
1199
- function serializeCookie(data) {
1224
+ function serializeCookie(data, options = {}) {
1225
+ const { maxBytes, cookieName = UNIFORM_DEFAULT_COOKIE_NAME, onBudgetExceeded } = options;
1226
+ const parts = {
1227
+ tests: { ...data.tests },
1228
+ sessionScores: encodeCookieType(data.sessionScores),
1229
+ scores: encodeCookieType(data.scores),
1230
+ controlGroup: data.controlGroup ? "1" : "0",
1231
+ personalizeVariants: encodePersonalizeVariants(data.personalizeVariants)
1232
+ };
1233
+ let value = serializeCookieParts(parts);
1234
+ if (maxBytes === void 0) {
1235
+ return value;
1236
+ }
1237
+ const overhead = encodeCookieName(cookieName).length + 1;
1238
+ const measure = () => overhead + Cookies.converter.write(value, cookieName).length;
1239
+ if (measure() <= maxBytes) {
1240
+ return value;
1241
+ }
1242
+ const candidates = dropCandidates(parts, data);
1243
+ const dropped = [];
1244
+ for (let i = 0; i < candidates.length && measure() > maxBytes; i++) {
1245
+ const { bucket, key } = candidates[i];
1246
+ delete parts[bucket][key];
1247
+ dropped.push(`${bucket}.${key}`);
1248
+ value = serializeCookieParts(parts);
1249
+ }
1250
+ if (dropped.length) {
1251
+ onBudgetExceeded == null ? void 0 : onBudgetExceeded(dropped, measure(), maxBytes);
1252
+ }
1253
+ return value;
1254
+ }
1255
+ function serializeCookieParts(parts) {
1200
1256
  return [
1201
- serializeCookieType(data.tests),
1202
- serializeCookieType(encodeCookieType(data.sessionScores)),
1203
- serializeCookieType(encodeCookieType(data.scores)),
1204
- data.controlGroup ? "1" : "0",
1205
- serializePersonalizeVariants(data)
1257
+ serializeCookieType(parts.tests),
1258
+ serializeCookieType(parts.sessionScores),
1259
+ serializeCookieType(parts.scores),
1260
+ parts.controlGroup,
1261
+ serializeCookieType(parts.personalizeVariants)
1206
1262
  ].join(TYPE_SEP);
1207
1263
  }
1264
+ function dropCandidates(parts, data) {
1265
+ const dimensions = ["sessionScores", "scores"].flatMap(
1266
+ (bucket, order) => Object.keys(parts[bucket]).map((key) => {
1267
+ var _a;
1268
+ return { bucket, key, order, score: (_a = data[bucket][key]) != null ? _a : 0 };
1269
+ })
1270
+ );
1271
+ dimensions.sort(
1272
+ (a, b) => Number(isZeroCookieScore(b.score)) - Number(isZeroCookieScore(a.score)) || a.score - b.score || a.order - b.order || (a.key < b.key ? -1 : 1)
1273
+ );
1274
+ const keysOf = (bucket) => Object.keys(parts[bucket]).sort().map((key) => ({ bucket, key }));
1275
+ return [...dimensions, ...keysOf("personalizeVariants"), ...keysOf("tests")];
1276
+ }
1277
+ function minimumCookieBytes(cookieName) {
1278
+ return encodeCookieName(cookieName).length + 6;
1279
+ }
1280
+ function encodeCookieName(name) {
1281
+ return encodeURIComponent(name).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent).replace(/\(/g, "%28").replace(/\)/g, "%29");
1282
+ }
1208
1283
  function serializePersonalizeVariants({
1209
1284
  personalizeVariants
1210
1285
  }) {
1286
+ return serializeCookieType(encodePersonalizeVariants(personalizeVariants));
1287
+ }
1288
+ function encodePersonalizeVariants(personalizeVariants) {
1211
1289
  const data = {};
1212
1290
  if (typeof personalizeVariants === "object") {
1213
1291
  Object.keys(personalizeVariants).forEach((personalizationName) => {
@@ -1219,8 +1297,7 @@ function serializePersonalizeVariants({
1219
1297
  data[personalizationName] = results.join(",");
1220
1298
  });
1221
1299
  }
1222
- const serialized = serializeCookieType(data);
1223
- return serialized;
1300
+ return data;
1224
1301
  }
1225
1302
  function decodePersonalizeVariants(data) {
1226
1303
  const parsed = parseCookieType(data);
@@ -1293,6 +1370,9 @@ fetchData_fn = async function() {
1293
1370
  }
1294
1371
  };
1295
1372
 
1373
+ // src/storage/util/systemDimension.ts
1374
+ var isSystemDimension = (dimension) => dimension.startsWith("$");
1375
+
1296
1376
  // src/storage/linearDecay.ts
1297
1377
  function createLinearDecay(options) {
1298
1378
  const { gracePeriod = 864e5, decayRate = 1 / 30, decayCap = 0.95 } = options != null ? options : {};
@@ -1318,7 +1398,7 @@ function createLinearDecay(options) {
1318
1398
  }
1319
1399
  function decayVector(vector, decay) {
1320
1400
  for (const key in vector) {
1321
- if (key.startsWith("$")) {
1401
+ if (isSystemDimension(key)) {
1322
1402
  continue;
1323
1403
  }
1324
1404
  vector[key] *= decay;
@@ -1462,7 +1542,7 @@ key_fn = function(key) {
1462
1542
 
1463
1543
  // src/storage/VisitorDataStore.ts
1464
1544
  var STORAGE_KEY = "ufvisitor";
1465
- var _mitt2, _persist, _visitTimeout, _options, _VisitorDataStore_instances, currentData_get, replaceData_fn, setVisitTimeout_fn, isExpired_fn, handleCaps_fn, defaultData_fn;
1545
+ var _mitt2, _persist, _visitTimeout, _options, _VisitorDataStore_instances, currentData_get, replaceData_fn, setVisitTimeout_fn, isExpired_fn, handleCaps_fn, pruningManifest_get, handlePruning_fn, pruneScores_fn, defaultData_fn;
1466
1546
  var VisitorDataStore = class {
1467
1547
  constructor(options) {
1468
1548
  __privateAdd(this, _VisitorDataStore_instances);
@@ -1580,6 +1660,7 @@ replaceData_fn = function(data, quiet = false) {
1580
1660
  sessionScores: data.sessionScores,
1581
1661
  onLogMessage: __privateGet(this, _options).onLogMessage
1582
1662
  });
1663
+ __privateMethod(this, _VisitorDataStore_instances, handlePruning_fn).call(this, data);
1583
1664
  const haveScoresChanged = !dequal4(oldData == null ? void 0 : oldData.visitorData.scores, data.scores);
1584
1665
  const haveSessionScoresChanged = !dequal4(oldData == null ? void 0 : oldData.visitorData.sessionScores, data.sessionScores);
1585
1666
  const haveQuirksChanged = !dequal4(oldData == null ? void 0 : oldData.visitorData.quirks, data.quirks);
@@ -1657,6 +1738,58 @@ handleCaps_fn = function(scores) {
1657
1738
  }
1658
1739
  }
1659
1740
  };
1741
+ pruningManifest_get = function() {
1742
+ var _a, _b, _c, _d, _e;
1743
+ const project = (_b = (_a = __privateGet(this, _options).manifest) == null ? void 0 : _a.data) == null ? void 0 : _b.project;
1744
+ const populated = [(_c = project == null ? void 0 : project.pz) == null ? void 0 : _c.sig, (_d = project == null ? void 0 : project.pz) == null ? void 0 : _d.enr, (_e = project == null ? void 0 : project.pz) == null ? void 0 : _e.agg, project == null ? void 0 : project.test].some(
1745
+ (definitions) => definitions && Object.keys(definitions).length > 0
1746
+ );
1747
+ return populated ? __privateGet(this, _options).manifest : void 0;
1748
+ };
1749
+ /**
1750
+ * IMPORTANT: This function mutates the input data, as #replaceData does.
1751
+ *
1752
+ * Removes stored keys that the host has opted to stop carrying. The two manifest-driven
1753
+ * prunes are skipped entirely when no populated manifest is loaded, so a partial load destroys
1754
+ * nothing; zero-score pruning consults no manifest and always runs when enabled.
1755
+ */
1756
+ handlePruning_fn = function(data) {
1757
+ var _a, _b;
1758
+ const { pruneUnknownTests, pruneUnknownDimensions } = __privateGet(this, _options);
1759
+ const manifest = pruneUnknownTests || pruneUnknownDimensions ? __privateGet(this, _VisitorDataStore_instances, pruningManifest_get) : void 0;
1760
+ const pruned = [];
1761
+ if (pruneUnknownTests && manifest) {
1762
+ for (const test in data.tests) {
1763
+ if (!manifest.getTest(test)) {
1764
+ delete data.tests[test];
1765
+ pruned.push(`tests.${test}`);
1766
+ }
1767
+ }
1768
+ }
1769
+ __privateMethod(this, _VisitorDataStore_instances, pruneScores_fn).call(this, data.scores, "scores", pruned, manifest);
1770
+ __privateMethod(this, _VisitorDataStore_instances, pruneScores_fn).call(this, data.sessionScores, "sessionScores", pruned, manifest);
1771
+ if (pruned.length) {
1772
+ (_b = (_a = __privateGet(this, _options)).onLogMessage) == null ? void 0 : _b.call(_a, ["info", 150, pruned]);
1773
+ }
1774
+ };
1775
+ /**
1776
+ * IMPORTANT: This function mutates the input scores.
1777
+ */
1778
+ pruneScores_fn = function(scores, bucket, pruned, manifest) {
1779
+ const { pruneUnknownDimensions, pruneZeroScores } = __privateGet(this, _options);
1780
+ if (!pruneUnknownDimensions && !pruneZeroScores) {
1781
+ return;
1782
+ }
1783
+ const pruneUnknown = pruneUnknownDimensions && manifest;
1784
+ for (const dim in scores) {
1785
+ const isUnknown = pruneUnknown && !isSystemDimension(dim) && !manifest.getDimensionByKey(dim);
1786
+ const isZero = pruneZeroScores && (pruneZeroScores === "round" ? isZeroCookieScore(scores[dim]) : scores[dim] === 0);
1787
+ if (isUnknown || isZero) {
1788
+ delete scores[dim];
1789
+ pruned.push(`${bucket}.${dim}`);
1790
+ }
1791
+ }
1792
+ };
1660
1793
  defaultData_fn = function() {
1661
1794
  var _a, _b, _c;
1662
1795
  return {
@@ -2743,6 +2876,13 @@ var messageContent = {
2743
2876
  ],
2744
2877
  131: () => ["storage", "server to client transition data was discarded"],
2745
2878
  140: (decayMessage) => ["storage", `score decay was applied: ${decayMessage}`],
2879
+ 150: (keys) => ["storage", `pruned from stored visitor data: ${keys.join(", ")}`],
2880
+ 151: ({ dropped, bytes, maxBytes }) => [
2881
+ "storage",
2882
+ `transition cookie exceeded its ${maxBytes} byte budget; dropped ${dropped.join(
2883
+ ", "
2884
+ )} to reach ${bytes} bytes`
2885
+ ],
2746
2886
  // SIGNAL EVAL
2747
2887
  200: () => ["signals", "evaluating signals"],
2748
2888
  201: (signal) => ["signals", `evaluating signal ${signal.id} (${signal.dur})`],
@@ -666,6 +666,41 @@ type VisitorDataStoreOptions = {
666
666
  * Only required if using multiple Context instances on the same domain and need to separate them.
667
667
  */
668
668
  partitionKey?: string;
669
+ /**
670
+ * Removes stored A/B test assignments for tests that are no longer in the manifest.
671
+ * Does nothing until a manifest with contents is loaded - an empty manifest is read as a
672
+ * manifest that did not load, not as a project that deleted everything - so a partial or
673
+ * failed load cannot destroy anything. Pruning is irreversible.
674
+ *
675
+ * @defaultValue false
676
+ */
677
+ pruneUnknownTests?: boolean;
678
+ /**
679
+ * Removes stored dimensions that are no longer in the manifest - a signal whose key is gone,
680
+ * or an enrichment whose category is gone. Dimensions Uniform writes itself, whose keys begin
681
+ * with `$`, are kept: they are never in the manifest.
682
+ * Does nothing until a manifest with contents is loaded - an empty manifest is read as a
683
+ * manifest that did not load, not as a project that deleted everything - so a partial or
684
+ * failed load cannot destroy anything. Pruning is irreversible.
685
+ *
686
+ * @defaultValue false
687
+ */
688
+ pruneUnknownDimensions?: boolean;
689
+ /**
690
+ * Removes stored dimensions whose score is zero. Pruning is irreversible.
691
+ *
692
+ * `true` removes a score of exactly zero. A decayed 0.6 is real data on the client, so it stays.
693
+ *
694
+ * `'round'` also removes any score whose magnitude is below 1, negatives included - every score
695
+ * the transition cookie would write as zero - so the cookie and local storage hold the same keys.
696
+ *
697
+ * Note that the weakest-dimension personalization operator is affected by pruning zeros, since a
698
+ * pruned dimension is no longer a candidate for it. Every other operator reads an absent
699
+ * dimension as zero, which is what it was.
700
+ *
701
+ * @defaultValue false
702
+ */
703
+ pruneZeroScores?: boolean | 'round';
669
704
  };
670
705
  type VisitorDataStoreEvents = {
671
706
  /**
@@ -846,6 +881,14 @@ type LogMessages = {
846
881
  131: MessageFunc;
847
882
  /** Decay function executed */
848
883
  140: MessageFunc<string>;
884
+ /** Keys were pruned from the stored visitor data */
885
+ 150: MessageFunc<string[]>;
886
+ /** Transition cookie did not fit its byte budget, so data was dropped from it */
887
+ 151: MessageFunc<{
888
+ dropped: string[];
889
+ bytes: number;
890
+ maxBytes: number;
891
+ }>;
849
892
  /** Signals evaluation beginning */
850
893
  200: MessageFunc;
851
894
  /** Evaluation of a specific signal beginning */
@@ -1396,4 +1439,4 @@ declare global {
1396
1439
  }
1397
1440
  }
1398
1441
 
1399
- export { GroupCriteriaEvaluator as $, type AggregateDimension as A, type BehaviorTag as B, type ContextPlugin as C, type DecayFunction as D, type DevToolsDataEvent as E, type DevToolsEvent as F, type DevToolsForgetEvent as G, type DevToolsHelloEvent as H, type DevToolsLogEvent as I, type DevToolsRawCommandsEvent as J, type DevToolsState as K, type LogDrain as L, type ManifestV2 as M, type DevToolsUiVersion as N, type OutputSeverity as O, type PersonalizedVariant as P, type Quirks as Q, type DevToolsUpdateEvent as R, type ScoreVector as S, TransitionDataStore as T, type DimensionMatch as U, type VisitorData as V, type EnrichmentCategory as W, type EnrichmentData as X, type EventData as Y, type GoalStateUpdate as Z, type Goals as _, type StorageCommands as a, type IdentifyCommand as a0, type LogMessageGroup as a1, type LogMessageSingle as a2, type LogMessages as a3, ManifestInstance as a4, type MessageCategory as a5, type MessageFunc as a6, type ModifyScoreCommand as a7, type ModifySessionScoreCommand as a8, type NumberMatch as a9, type TestVariant as aA, type Tests as aB, type TransitionDataStoreEvents as aC, type VariationMatchMetadata as aD, VisitorDataStore as aE, type VisitorDataStoreEvents as aF, type VisitorDataStoreOptions as aG, areCommandsEqual as aH, emptyVisitorData as aI, testVariations as aJ, type PersonalizationEvent as aa, type PersonalizationEventVariantId as ab, type PersonalizationManifest as ac, type PersonalizationSelectionAlgorithm as ad, type PersonalizationSelectionAlgorithms as ae, type PersonalizeControlVariant as af, type PersonalizeVariants as ag, type QuirkMatch as ah, SERVER_STATE_ID as ai, type ServerToClientTransitionState as aj, type SetConsentCommand as ak, type SetControlGroupCommand as al, type SetGoalCommand as am, type SetPersonalizeVariantControlCommand as an, type SetQuirkCommand as ao, type SetTestCommand as ap, type Severity as aq, type Signal as ar, type SignalCriteria as as, type SignalCriteriaGroup as at, type SignalData as au, type StorageCommand as av, type TestDefinition as aw, type TestEvent as ax, type TestOptions as ay, type TestResult as az, type TransitionDataStoreOptions as b, type CriteriaEvaluator as c, type StringMatch as d, type VariantMatchCriteria as e, type LogMessage as f, type PersonalizeOptions as g, Context as h, type PersonalizedResult as i, type VariationMatchDimensionCriteria as j, type PersonalizationSelectionAlgorithmOptions as k, type DevToolsEvents as l, type AggregateDimensionInput as m, CONTEXTUAL_EDITING_TEST_NAME as n, CONTEXTUAL_EDITING_TEST_SELECTED_VARIANT_ID as o, type paths as p, type CompositionMetadata as q, type ContextEvents as r, type ContextInstance as s, type ContextOptions as t, type ContextState as u, type ContextStateUpdate as v, type CriteriaEvaluatorParameters as w, type CriteriaEvaluatorResult as x, type DecayOptions as y, type DevToolsActions as z };
1442
+ export { GroupCriteriaEvaluator as $, type AggregateDimension as A, type BehaviorTag as B, type ContextPlugin as C, type DecayFunction as D, type DevToolsDataEvent as E, type DevToolsEvent as F, type DevToolsForgetEvent as G, type DevToolsHelloEvent as H, type DevToolsLogEvent as I, type DevToolsRawCommandsEvent as J, type DevToolsState as K, type LogDrain as L, type ManifestV2 as M, type DevToolsUiVersion as N, type OutputSeverity as O, type PersonalizedVariant as P, type Quirks as Q, type DevToolsUpdateEvent as R, type ScoreVector as S, TransitionDataStore as T, type DimensionMatch as U, type VisitorData as V, type EnrichmentCategory as W, type EnrichmentData as X, type EventData as Y, type GoalStateUpdate as Z, type Goals as _, type LogMessage as a, type IdentifyCommand as a0, type LogMessageGroup as a1, type LogMessageSingle as a2, type LogMessages as a3, ManifestInstance as a4, type MessageCategory as a5, type MessageFunc as a6, type ModifyScoreCommand as a7, type ModifySessionScoreCommand as a8, type NumberMatch as a9, type TestVariant as aA, type Tests as aB, type TransitionDataStoreEvents as aC, type VariationMatchMetadata as aD, VisitorDataStore as aE, type VisitorDataStoreEvents as aF, type VisitorDataStoreOptions as aG, areCommandsEqual as aH, emptyVisitorData as aI, testVariations as aJ, type PersonalizationEvent as aa, type PersonalizationEventVariantId as ab, type PersonalizationManifest as ac, type PersonalizationSelectionAlgorithm as ad, type PersonalizationSelectionAlgorithms as ae, type PersonalizeControlVariant as af, type PersonalizeVariants as ag, type QuirkMatch as ah, SERVER_STATE_ID as ai, type ServerToClientTransitionState as aj, type SetConsentCommand as ak, type SetControlGroupCommand as al, type SetGoalCommand as am, type SetPersonalizeVariantControlCommand as an, type SetQuirkCommand as ao, type SetTestCommand as ap, type Severity as aq, type Signal as ar, type SignalCriteria as as, type SignalCriteriaGroup as at, type SignalData as au, type StorageCommand as av, type TestDefinition as aw, type TestEvent as ax, type TestOptions as ay, type TestResult as az, type StorageCommands as b, type TransitionDataStoreOptions as c, type CriteriaEvaluator as d, type StringMatch as e, type VariantMatchCriteria as f, type PersonalizeOptions as g, Context as h, type PersonalizedResult as i, type VariationMatchDimensionCriteria as j, type PersonalizationSelectionAlgorithmOptions as k, type DevToolsEvents as l, type AggregateDimensionInput as m, CONTEXTUAL_EDITING_TEST_NAME as n, CONTEXTUAL_EDITING_TEST_SELECTED_VARIANT_ID as o, type paths as p, type CompositionMetadata as q, type ContextEvents as r, type ContextInstance as s, type ContextOptions as t, type ContextState as u, type ContextStateUpdate as v, type CriteriaEvaluatorParameters as w, type CriteriaEvaluatorResult as x, type DecayOptions as y, type DevToolsActions as z };
@@ -666,6 +666,41 @@ type VisitorDataStoreOptions = {
666
666
  * Only required if using multiple Context instances on the same domain and need to separate them.
667
667
  */
668
668
  partitionKey?: string;
669
+ /**
670
+ * Removes stored A/B test assignments for tests that are no longer in the manifest.
671
+ * Does nothing until a manifest with contents is loaded - an empty manifest is read as a
672
+ * manifest that did not load, not as a project that deleted everything - so a partial or
673
+ * failed load cannot destroy anything. Pruning is irreversible.
674
+ *
675
+ * @defaultValue false
676
+ */
677
+ pruneUnknownTests?: boolean;
678
+ /**
679
+ * Removes stored dimensions that are no longer in the manifest - a signal whose key is gone,
680
+ * or an enrichment whose category is gone. Dimensions Uniform writes itself, whose keys begin
681
+ * with `$`, are kept: they are never in the manifest.
682
+ * Does nothing until a manifest with contents is loaded - an empty manifest is read as a
683
+ * manifest that did not load, not as a project that deleted everything - so a partial or
684
+ * failed load cannot destroy anything. Pruning is irreversible.
685
+ *
686
+ * @defaultValue false
687
+ */
688
+ pruneUnknownDimensions?: boolean;
689
+ /**
690
+ * Removes stored dimensions whose score is zero. Pruning is irreversible.
691
+ *
692
+ * `true` removes a score of exactly zero. A decayed 0.6 is real data on the client, so it stays.
693
+ *
694
+ * `'round'` also removes any score whose magnitude is below 1, negatives included - every score
695
+ * the transition cookie would write as zero - so the cookie and local storage hold the same keys.
696
+ *
697
+ * Note that the weakest-dimension personalization operator is affected by pruning zeros, since a
698
+ * pruned dimension is no longer a candidate for it. Every other operator reads an absent
699
+ * dimension as zero, which is what it was.
700
+ *
701
+ * @defaultValue false
702
+ */
703
+ pruneZeroScores?: boolean | 'round';
669
704
  };
670
705
  type VisitorDataStoreEvents = {
671
706
  /**
@@ -846,6 +881,14 @@ type LogMessages = {
846
881
  131: MessageFunc;
847
882
  /** Decay function executed */
848
883
  140: MessageFunc<string>;
884
+ /** Keys were pruned from the stored visitor data */
885
+ 150: MessageFunc<string[]>;
886
+ /** Transition cookie did not fit its byte budget, so data was dropped from it */
887
+ 151: MessageFunc<{
888
+ dropped: string[];
889
+ bytes: number;
890
+ maxBytes: number;
891
+ }>;
849
892
  /** Signals evaluation beginning */
850
893
  200: MessageFunc;
851
894
  /** Evaluation of a specific signal beginning */
@@ -1396,4 +1439,4 @@ declare global {
1396
1439
  }
1397
1440
  }
1398
1441
 
1399
- export { GroupCriteriaEvaluator as $, type AggregateDimension as A, type BehaviorTag as B, type ContextPlugin as C, type DecayFunction as D, type DevToolsDataEvent as E, type DevToolsEvent as F, type DevToolsForgetEvent as G, type DevToolsHelloEvent as H, type DevToolsLogEvent as I, type DevToolsRawCommandsEvent as J, type DevToolsState as K, type LogDrain as L, type ManifestV2 as M, type DevToolsUiVersion as N, type OutputSeverity as O, type PersonalizedVariant as P, type Quirks as Q, type DevToolsUpdateEvent as R, type ScoreVector as S, TransitionDataStore as T, type DimensionMatch as U, type VisitorData as V, type EnrichmentCategory as W, type EnrichmentData as X, type EventData as Y, type GoalStateUpdate as Z, type Goals as _, type StorageCommands as a, type IdentifyCommand as a0, type LogMessageGroup as a1, type LogMessageSingle as a2, type LogMessages as a3, ManifestInstance as a4, type MessageCategory as a5, type MessageFunc as a6, type ModifyScoreCommand as a7, type ModifySessionScoreCommand as a8, type NumberMatch as a9, type TestVariant as aA, type Tests as aB, type TransitionDataStoreEvents as aC, type VariationMatchMetadata as aD, VisitorDataStore as aE, type VisitorDataStoreEvents as aF, type VisitorDataStoreOptions as aG, areCommandsEqual as aH, emptyVisitorData as aI, testVariations as aJ, type PersonalizationEvent as aa, type PersonalizationEventVariantId as ab, type PersonalizationManifest as ac, type PersonalizationSelectionAlgorithm as ad, type PersonalizationSelectionAlgorithms as ae, type PersonalizeControlVariant as af, type PersonalizeVariants as ag, type QuirkMatch as ah, SERVER_STATE_ID as ai, type ServerToClientTransitionState as aj, type SetConsentCommand as ak, type SetControlGroupCommand as al, type SetGoalCommand as am, type SetPersonalizeVariantControlCommand as an, type SetQuirkCommand as ao, type SetTestCommand as ap, type Severity as aq, type Signal as ar, type SignalCriteria as as, type SignalCriteriaGroup as at, type SignalData as au, type StorageCommand as av, type TestDefinition as aw, type TestEvent as ax, type TestOptions as ay, type TestResult as az, type TransitionDataStoreOptions as b, type CriteriaEvaluator as c, type StringMatch as d, type VariantMatchCriteria as e, type LogMessage as f, type PersonalizeOptions as g, Context as h, type PersonalizedResult as i, type VariationMatchDimensionCriteria as j, type PersonalizationSelectionAlgorithmOptions as k, type DevToolsEvents as l, type AggregateDimensionInput as m, CONTEXTUAL_EDITING_TEST_NAME as n, CONTEXTUAL_EDITING_TEST_SELECTED_VARIANT_ID as o, type paths as p, type CompositionMetadata as q, type ContextEvents as r, type ContextInstance as s, type ContextOptions as t, type ContextState as u, type ContextStateUpdate as v, type CriteriaEvaluatorParameters as w, type CriteriaEvaluatorResult as x, type DecayOptions as y, type DevToolsActions as z };
1442
+ export { GroupCriteriaEvaluator as $, type AggregateDimension as A, type BehaviorTag as B, type ContextPlugin as C, type DecayFunction as D, type DevToolsDataEvent as E, type DevToolsEvent as F, type DevToolsForgetEvent as G, type DevToolsHelloEvent as H, type DevToolsLogEvent as I, type DevToolsRawCommandsEvent as J, type DevToolsState as K, type LogDrain as L, type ManifestV2 as M, type DevToolsUiVersion as N, type OutputSeverity as O, type PersonalizedVariant as P, type Quirks as Q, type DevToolsUpdateEvent as R, type ScoreVector as S, TransitionDataStore as T, type DimensionMatch as U, type VisitorData as V, type EnrichmentCategory as W, type EnrichmentData as X, type EventData as Y, type GoalStateUpdate as Z, type Goals as _, type LogMessage as a, type IdentifyCommand as a0, type LogMessageGroup as a1, type LogMessageSingle as a2, type LogMessages as a3, ManifestInstance as a4, type MessageCategory as a5, type MessageFunc as a6, type ModifyScoreCommand as a7, type ModifySessionScoreCommand as a8, type NumberMatch as a9, type TestVariant as aA, type Tests as aB, type TransitionDataStoreEvents as aC, type VariationMatchMetadata as aD, VisitorDataStore as aE, type VisitorDataStoreEvents as aF, type VisitorDataStoreOptions as aG, areCommandsEqual as aH, emptyVisitorData as aI, testVariations as aJ, type PersonalizationEvent as aa, type PersonalizationEventVariantId as ab, type PersonalizationManifest as ac, type PersonalizationSelectionAlgorithm as ad, type PersonalizationSelectionAlgorithms as ae, type PersonalizeControlVariant as af, type PersonalizeVariants as ag, type QuirkMatch as ah, SERVER_STATE_ID as ai, type ServerToClientTransitionState as aj, type SetConsentCommand as ak, type SetControlGroupCommand as al, type SetGoalCommand as am, type SetPersonalizeVariantControlCommand as an, type SetQuirkCommand as ao, type SetTestCommand as ap, type Severity as aq, type Signal as ar, type SignalCriteria as as, type SignalCriteriaGroup as at, type SignalData as au, type StorageCommand as av, type TestDefinition as aw, type TestEvent as ax, type TestOptions as ay, type TestResult as az, type StorageCommands as b, type TransitionDataStoreOptions as c, type CriteriaEvaluator as d, type StringMatch as e, type VariantMatchCriteria as f, type PersonalizeOptions as g, Context as h, type PersonalizedResult as i, type VariationMatchDimensionCriteria as j, type PersonalizationSelectionAlgorithmOptions as k, type DevToolsEvents as l, type AggregateDimensionInput as m, CONTEXTUAL_EDITING_TEST_NAME as n, CONTEXTUAL_EDITING_TEST_SELECTED_VARIANT_ID as o, type paths as p, type CompositionMetadata as q, type ContextEvents as r, type ContextInstance as s, type ContextOptions as t, type ContextState as u, type ContextStateUpdate as v, type CriteriaEvaluatorParameters as w, type CriteriaEvaluatorResult as x, type DecayOptions as y, type DevToolsActions as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniformdev/context",
3
- "version": "20.80.0",
3
+ "version": "20.80.1-alpha.10.sha-e0cfb4ce44",
4
4
  "description": "Uniform Context core package",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "main": "./dist/index.js",
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "6eec8f028463f30667b59af913a74d56960dde11"
73
+ "gitHead": "e0cfb4ce4435ea507f36d3246fc886c24f8ebe16"
74
74
  }