@ninetailed/experience.js 7.12.1 → 7.13.0-beta.1

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.
package/index.cjs.js CHANGED
@@ -143,7 +143,6 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
143
143
  }));
144
144
  });
145
145
  this.methods = {
146
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
146
  reset: (...args) => __awaiter(this, void 0, void 0, function* () {
148
147
  experience_jsShared.logger.debug('Resetting profile.');
149
148
  const instance = args[args.length - 1];
@@ -162,7 +161,6 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
162
161
  experience_jsShared.logger.info('Profile reset successful.');
163
162
  yield delay(10);
164
163
  }),
165
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
164
  debug: (...args) => __awaiter(this, void 0, void 0, function* () {
167
165
  const enabled = args[0];
168
166
  const instance = args[args.length - 1];
@@ -288,7 +286,6 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
288
286
  onTrackComponent() {
289
287
  return Promise.resolve();
290
288
  }
291
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
292
289
  setItemStart({
293
290
  abort,
294
291
  payload
@@ -562,7 +559,7 @@ const selectPluginsHavingExperienceSelectionMiddleware = plugins => {
562
559
  return filteredPlugins;
563
560
  };
564
561
 
565
- const createPassThroughMiddleware = () => {
562
+ const createPassThroughMiddleware$1 = () => {
566
563
  return ({
567
564
  experience,
568
565
  variant,
@@ -582,7 +579,7 @@ function createExperienceSelectionMiddleware({
582
579
  profile
583
580
  }) {
584
581
  if (profile === null) {
585
- return createPassThroughMiddleware();
582
+ return createPassThroughMiddleware$1();
586
583
  }
587
584
  const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
588
585
  const middlewareFunctions = [];
@@ -634,6 +631,110 @@ const makeExperienceSelectMiddleware = ({
634
631
  };
635
632
  };
636
633
 
634
+ /**
635
+ * Type guard that checks if an object implements the HasChangesModificationMiddleware interface
636
+ *
637
+ * @param arg Object to check
638
+ * @returns Boolean indicating if the object implements HasChangesModificationMiddleware
639
+ */
640
+ const hasChangesModificationMiddleware = arg => {
641
+ return typeof arg === 'object' && arg !== null && 'getChangesModificationMiddleware' in arg && typeof arg.getChangesModificationMiddleware === 'function';
642
+ };
643
+
644
+ /**
645
+ * Selects plugins that implement the HasChangesModificationMiddleware interface
646
+ *
647
+ * @param plugins Array of Ninetailed plugins
648
+ * @returns Array of plugins that implement HasChangesModificationMiddleware
649
+ */
650
+ const selectPluginsHavingChangesModificationMiddleware = plugins => {
651
+ const filteredPlugins = [];
652
+ for (const plugin of plugins) {
653
+ if (hasChangesModificationMiddleware(plugin)) {
654
+ filteredPlugins.push(plugin);
655
+ }
656
+ }
657
+ return filteredPlugins;
658
+ };
659
+
660
+ /**
661
+ * Creates a pass-through middleware that doesn't modify changes
662
+ */
663
+ const createPassThroughMiddleware = () => {
664
+ return ({
665
+ changes
666
+ }) => {
667
+ return {
668
+ changes
669
+ };
670
+ };
671
+ };
672
+ /**
673
+ * Creates a middleware function by composing middleware from multiple plugins
674
+ */
675
+ function createChangesModificationMiddleware({
676
+ plugins,
677
+ changes
678
+ }) {
679
+ const pluginsWithMiddleware = selectPluginsHavingChangesModificationMiddleware(plugins);
680
+ const middlewareFunctions = [];
681
+ for (const plugin of pluginsWithMiddleware) {
682
+ const middleware = plugin.getChangesModificationMiddleware({
683
+ changes
684
+ });
685
+ if (middleware !== undefined) {
686
+ middlewareFunctions.push(middleware);
687
+ }
688
+ }
689
+ // If no middleware functions were found, return a pass-through middleware
690
+ if (middlewareFunctions.length === 0) {
691
+ return createPassThroughMiddleware();
692
+ }
693
+ // Compose middleware functions using pipe
694
+ return experience_jsShared.pipe(...middlewareFunctions);
695
+ }
696
+ /**
697
+ * Creates a changes modification middleware system with change notification
698
+ *
699
+ * This follows the same pattern as the experience selection middleware system
700
+ */
701
+ const makeChangesModificationMiddleware = ({
702
+ plugins,
703
+ onChange,
704
+ changes
705
+ }) => {
706
+ let removeChangeListeners = [];
707
+ const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
708
+ const middleware = createChangesModificationMiddleware({
709
+ plugins,
710
+ changes
711
+ });
712
+ const addListeners = () => {
713
+ removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
714
+ const listener = () => {
715
+ // When a plugin changes, recreate the middleware and notify
716
+ const updatedMiddleware = createChangesModificationMiddleware({
717
+ plugins,
718
+ changes
719
+ });
720
+ onChange(updatedMiddleware);
721
+ };
722
+ return plugin.onChangeEmitter.addListener(listener);
723
+ });
724
+ };
725
+ // WARNING: This specific implementation using forEach is required.
726
+ // DO NOT replace with for...of or other loop constructs as they will break functionality.
727
+ // The exact reason is uncertain but appears related to the transpiler.
728
+ const removeListeners = () => {
729
+ removeChangeListeners.forEach(removeListener => removeListener());
730
+ };
731
+ return {
732
+ addListeners,
733
+ removeListeners,
734
+ middleware
735
+ };
736
+ };
737
+
637
738
  class EventBuilder {
638
739
  constructor(buildRequestContext) {
639
740
  this.buildRequestContext = buildRequestContext || buildClientNinetailedRequestContext;
@@ -1043,15 +1144,77 @@ class Ninetailed {
1043
1144
  };
1044
1145
  /**
1045
1146
  * Registers a callback to be notified when changes occur in the profile state.
1147
+ * Uses the changes modification middleware system to process changes.
1148
+ * Changes are processed in the following order:
1046
1149
  *
1047
1150
  * @param cb - Callback function that receives the changes state
1048
1151
  * @returns Function to unsubscribe from changes updates
1049
1152
  */
1050
1153
  this.onChangesChange = cb => {
1154
+ // Initially notify with current state
1051
1155
  this.notifyChangesCallback(cb, this._profileState);
1052
- return this.onProfileChange(profileState => {
1053
- this.notifyChangesCallback(cb, profileState);
1156
+ let middlewareChangeListeners = [];
1157
+ const removeProfileChangeListener = this.onProfileChange(profileState => {
1158
+ // Clean up any existing middleware listeners
1159
+ middlewareChangeListeners.forEach(removeListener => removeListener());
1160
+ middlewareChangeListeners = [];
1161
+ // If we're in loading or error state, simply pass through
1162
+ if (profileState.status !== 'success') {
1163
+ this.notifyChangesCallback(cb, profileState);
1164
+ return;
1165
+ }
1166
+ // Set up middleware for changes
1167
+ const {
1168
+ addListeners,
1169
+ removeListeners,
1170
+ middleware: changesModificationMiddleware
1171
+ } = makeChangesModificationMiddleware({
1172
+ plugins: this.plugins,
1173
+ changes: profileState.changes || [],
1174
+ onChange: updatedMiddleware => {
1175
+ // When plugin state changes, reapply middleware to current changes
1176
+ if (profileState.status === 'success' && profileState.changes) {
1177
+ const {
1178
+ changes: modifiedChanges
1179
+ } = updatedMiddleware({
1180
+ changes: profileState.changes
1181
+ });
1182
+ cb({
1183
+ status: 'success',
1184
+ changes: modifiedChanges,
1185
+ error: null
1186
+ });
1187
+ }
1188
+ }
1189
+ });
1190
+ // Add listeners for plugin changes
1191
+ addListeners();
1192
+ middlewareChangeListeners.push(removeListeners);
1193
+ // Apply middleware to current changes
1194
+ if (profileState.changes) {
1195
+ const {
1196
+ changes: modifiedChanges
1197
+ } = changesModificationMiddleware({
1198
+ changes: profileState.changes
1199
+ });
1200
+ cb({
1201
+ status: 'success',
1202
+ changes: modifiedChanges,
1203
+ error: null
1204
+ });
1205
+ } else {
1206
+ cb({
1207
+ status: 'success',
1208
+ changes: [],
1209
+ error: null
1210
+ });
1211
+ }
1054
1212
  });
1213
+ // Return a function that cleans up all listeners
1214
+ return () => {
1215
+ removeProfileChangeListener();
1216
+ middlewareChangeListeners.forEach(removeListener => removeListener());
1217
+ };
1055
1218
  };
1056
1219
  this.onIsInitialized = onIsInitializedCallback => {
1057
1220
  if (typeof onIsInitializedCallback === 'function') {
@@ -1189,7 +1352,6 @@ class Ninetailed {
1189
1352
  }
1190
1353
  logInvalidElement(element) {
1191
1354
  const isObject = typeof element === 'object' && element !== null;
1192
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1193
1355
  const constructorName = isObject ? element.constructor.name : '';
1194
1356
  const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
1195
1357
  experience_jsShared.logger.warn(`ElementSeenObserver.observeElement was called with an invalid element. Expected an Element but got ${typeof element}${isConstructorNameNotObject ? ` of type ${constructorName}` : ''}. This call will be ignored.`);
@@ -1415,6 +1577,7 @@ exports.PROFILE_RESET = PROFILE_RESET;
1415
1577
  exports.SET_ENABLED_FEATURES = SET_ENABLED_FEATURES;
1416
1578
  exports.buildClientNinetailedRequestContext = buildClientNinetailedRequestContext;
1417
1579
  exports.decodeExperienceVariantsMap = decodeExperienceVariantsMap;
1580
+ exports.makeChangesModificationMiddleware = makeChangesModificationMiddleware;
1418
1581
  exports.makeExperienceSelectMiddleware = makeExperienceSelectMiddleware;
1419
1582
  exports.selectPluginsHavingExperienceSelectionMiddleware = selectPluginsHavingExperienceSelectionMiddleware;
1420
1583
  exports.selectPluginsHavingOnChangeEmitter = selectPluginsHavingOnChangeEmitter;
package/index.esm.js CHANGED
@@ -62,9 +62,6 @@ const SET_ENABLED_FEATURES = 'setEnabledFeatures';
62
62
  const EMPTY_MERGE_ID = 'nt:empty-merge-id';
63
63
 
64
64
  const PLUGIN_NAME = 'ninetailed:core';
65
-
66
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
-
68
65
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
69
66
  class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
70
67
  constructor({
@@ -108,7 +105,6 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
108
105
  }));
109
106
  };
110
107
  this.methods = {
111
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
108
  reset: async function (...args) {
113
109
  logger.debug('Resetting profile.');
114
110
  const instance = args[args.length - 1];
@@ -127,7 +123,6 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
127
123
  logger.info('Profile reset successful.');
128
124
  await delay(10);
129
125
  },
130
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
126
  debug: async function (...args) {
132
127
  const enabled = args[0];
133
128
  const instance = args[args.length - 1];
@@ -244,7 +239,6 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
244
239
  onTrackComponent() {
245
240
  return Promise.resolve();
246
241
  }
247
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
248
242
  setItemStart({
249
243
  abort,
250
244
  payload
@@ -529,7 +523,7 @@ const selectPluginsHavingExperienceSelectionMiddleware = plugins => {
529
523
  return filteredPlugins;
530
524
  };
531
525
 
532
- const createPassThroughMiddleware = () => {
526
+ const createPassThroughMiddleware$1 = () => {
533
527
  return ({
534
528
  experience,
535
529
  variant,
@@ -549,7 +543,7 @@ function createExperienceSelectionMiddleware({
549
543
  profile
550
544
  }) {
551
545
  if (profile === null) {
552
- return createPassThroughMiddleware();
546
+ return createPassThroughMiddleware$1();
553
547
  }
554
548
  const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
555
549
  const middlewareFunctions = [];
@@ -602,6 +596,115 @@ const makeExperienceSelectMiddleware = ({
602
596
  };
603
597
  };
604
598
 
599
+ /**
600
+ * Type guard that checks if an object implements the HasChangesModificationMiddleware interface
601
+ *
602
+ * @param arg Object to check
603
+ * @returns Boolean indicating if the object implements HasChangesModificationMiddleware
604
+ */
605
+ const hasChangesModificationMiddleware = arg => {
606
+ return typeof arg === 'object' && arg !== null && 'getChangesModificationMiddleware' in arg && typeof arg.getChangesModificationMiddleware === 'function';
607
+ };
608
+
609
+ /**
610
+ * Selects plugins that implement the HasChangesModificationMiddleware interface
611
+ *
612
+ * @param plugins Array of Ninetailed plugins
613
+ * @returns Array of plugins that implement HasChangesModificationMiddleware
614
+ */
615
+ const selectPluginsHavingChangesModificationMiddleware = plugins => {
616
+ const filteredPlugins = [];
617
+ for (const plugin of plugins) {
618
+ if (hasChangesModificationMiddleware(plugin)) {
619
+ filteredPlugins.push(plugin);
620
+ }
621
+ }
622
+ return filteredPlugins;
623
+ };
624
+
625
+ /**
626
+ * Creates a pass-through middleware that doesn't modify changes
627
+ */
628
+ const createPassThroughMiddleware = () => {
629
+ return ({
630
+ changes
631
+ }) => {
632
+ return {
633
+ changes
634
+ };
635
+ };
636
+ };
637
+
638
+ /**
639
+ * Creates a middleware function by composing middleware from multiple plugins
640
+ */
641
+ function createChangesModificationMiddleware({
642
+ plugins,
643
+ changes
644
+ }) {
645
+ const pluginsWithMiddleware = selectPluginsHavingChangesModificationMiddleware(plugins);
646
+ const middlewareFunctions = [];
647
+ for (const plugin of pluginsWithMiddleware) {
648
+ const middleware = plugin.getChangesModificationMiddleware({
649
+ changes
650
+ });
651
+ if (middleware !== undefined) {
652
+ middlewareFunctions.push(middleware);
653
+ }
654
+ }
655
+
656
+ // If no middleware functions were found, return a pass-through middleware
657
+ if (middlewareFunctions.length === 0) {
658
+ return createPassThroughMiddleware();
659
+ }
660
+
661
+ // Compose middleware functions using pipe
662
+ return pipe(...middlewareFunctions);
663
+ }
664
+
665
+ /**
666
+ * Creates a changes modification middleware system with change notification
667
+ *
668
+ * This follows the same pattern as the experience selection middleware system
669
+ */
670
+ const makeChangesModificationMiddleware = ({
671
+ plugins,
672
+ onChange,
673
+ changes
674
+ }) => {
675
+ let removeChangeListeners = [];
676
+ const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
677
+ const middleware = createChangesModificationMiddleware({
678
+ plugins,
679
+ changes
680
+ });
681
+ const addListeners = () => {
682
+ removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
683
+ const listener = () => {
684
+ // When a plugin changes, recreate the middleware and notify
685
+ const updatedMiddleware = createChangesModificationMiddleware({
686
+ plugins,
687
+ changes
688
+ });
689
+ onChange(updatedMiddleware);
690
+ };
691
+ return plugin.onChangeEmitter.addListener(listener);
692
+ });
693
+ };
694
+
695
+ // WARNING: This specific implementation using forEach is required.
696
+ // DO NOT replace with for...of or other loop constructs as they will break functionality.
697
+ // The exact reason is uncertain but appears related to the transpiler.
698
+ const removeListeners = () => {
699
+ removeChangeListeners.forEach(removeListener => removeListener());
700
+ };
701
+ return {
702
+ addListeners,
703
+ removeListeners,
704
+ middleware
705
+ };
706
+ };
707
+
605
708
  class EventBuilder {
606
709
  constructor(buildRequestContext) {
607
710
  this.buildRequestContext = void 0;
@@ -1029,15 +1132,82 @@ class Ninetailed {
1029
1132
  };
1030
1133
  /**
1031
1134
  * Registers a callback to be notified when changes occur in the profile state.
1135
+ * Uses the changes modification middleware system to process changes.
1136
+ * Changes are processed in the following order:
1032
1137
  *
1033
1138
  * @param cb - Callback function that receives the changes state
1034
1139
  * @returns Function to unsubscribe from changes updates
1035
1140
  */
1036
1141
  this.onChangesChange = cb => {
1142
+ // Initially notify with current state
1037
1143
  this.notifyChangesCallback(cb, this._profileState);
1038
- return this.onProfileChange(profileState => {
1039
- this.notifyChangesCallback(cb, profileState);
1144
+ let middlewareChangeListeners = [];
1145
+ const removeProfileChangeListener = this.onProfileChange(profileState => {
1146
+ // Clean up any existing middleware listeners
1147
+ middlewareChangeListeners.forEach(removeListener => removeListener());
1148
+ middlewareChangeListeners = [];
1149
+
1150
+ // If we're in loading or error state, simply pass through
1151
+ if (profileState.status !== 'success') {
1152
+ this.notifyChangesCallback(cb, profileState);
1153
+ return;
1154
+ }
1155
+
1156
+ // Set up middleware for changes
1157
+ const {
1158
+ addListeners,
1159
+ removeListeners,
1160
+ middleware: changesModificationMiddleware
1161
+ } = makeChangesModificationMiddleware({
1162
+ plugins: this.plugins,
1163
+ changes: profileState.changes || [],
1164
+ onChange: updatedMiddleware => {
1165
+ // When plugin state changes, reapply middleware to current changes
1166
+ if (profileState.status === 'success' && profileState.changes) {
1167
+ const {
1168
+ changes: modifiedChanges
1169
+ } = updatedMiddleware({
1170
+ changes: profileState.changes
1171
+ });
1172
+ cb({
1173
+ status: 'success',
1174
+ changes: modifiedChanges,
1175
+ error: null
1176
+ });
1177
+ }
1178
+ }
1179
+ });
1180
+
1181
+ // Add listeners for plugin changes
1182
+ addListeners();
1183
+ middlewareChangeListeners.push(removeListeners);
1184
+
1185
+ // Apply middleware to current changes
1186
+ if (profileState.changes) {
1187
+ const {
1188
+ changes: modifiedChanges
1189
+ } = changesModificationMiddleware({
1190
+ changes: profileState.changes
1191
+ });
1192
+ cb({
1193
+ status: 'success',
1194
+ changes: modifiedChanges,
1195
+ error: null
1196
+ });
1197
+ } else {
1198
+ cb({
1199
+ status: 'success',
1200
+ changes: [],
1201
+ error: null
1202
+ });
1203
+ }
1040
1204
  });
1205
+
1206
+ // Return a function that cleans up all listeners
1207
+ return () => {
1208
+ removeProfileChangeListener();
1209
+ middlewareChangeListeners.forEach(removeListener => removeListener());
1210
+ };
1041
1211
  };
1042
1212
  this.onIsInitialized = onIsInitializedCallback => {
1043
1213
  if (typeof onIsInitializedCallback === 'function') {
@@ -1176,7 +1346,6 @@ class Ninetailed {
1176
1346
  }
1177
1347
  logInvalidElement(element) {
1178
1348
  const isObject = typeof element === 'object' && element !== null;
1179
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1180
1349
  const constructorName = isObject ? element.constructor.name : '';
1181
1350
  const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
1182
1351
  logger.warn(`ElementSeenObserver.observeElement was called with an invalid element. Expected an Element but got ${typeof element}${isConstructorNameNotObject ? ` of type ${constructorName}` : ''}. This call will be ignored.`);
@@ -1350,4 +1519,4 @@ const selectVariant = (baseline, variants, {
1350
1519
  };
1351
1520
  };
1352
1521
 
1353
- export { ANONYMOUS_ID, CHANGES_FALLBACK_CACHE, COMPONENT, COMPONENT_START, CONSENT, DEBUG_FLAG, EMPTY_MERGE_ID, EXPERIENCES_FALLBACK_CACHE, EventBuilder, HAS_SEEN_STICKY_COMPONENT, LEGACY_ANONYMOUS_ID, Ninetailed, NinetailedCorePlugin, OnChangeEmitter, PAGE_HIDDEN, PLUGIN_NAME, PROFILE_CHANGE, PROFILE_FALLBACK_CACHE, PROFILE_RESET, SET_ENABLED_FEATURES, buildClientNinetailedRequestContext, decodeExperienceVariantsMap, makeExperienceSelectMiddleware, selectPluginsHavingExperienceSelectionMiddleware, selectPluginsHavingOnChangeEmitter, selectVariant };
1522
+ export { ANONYMOUS_ID, CHANGES_FALLBACK_CACHE, COMPONENT, COMPONENT_START, CONSENT, DEBUG_FLAG, EMPTY_MERGE_ID, EXPERIENCES_FALLBACK_CACHE, EventBuilder, HAS_SEEN_STICKY_COMPONENT, LEGACY_ANONYMOUS_ID, Ninetailed, NinetailedCorePlugin, OnChangeEmitter, PAGE_HIDDEN, PLUGIN_NAME, PROFILE_CHANGE, PROFILE_FALLBACK_CACHE, PROFILE_RESET, SET_ENABLED_FEATURES, buildClientNinetailedRequestContext, decodeExperienceVariantsMap, makeChangesModificationMiddleware, makeExperienceSelectMiddleware, selectPluginsHavingExperienceSelectionMiddleware, selectPluginsHavingOnChangeEmitter, selectVariant };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninetailed/experience.js",
3
- "version": "7.12.1",
3
+ "version": "7.13.0-beta.1",
4
4
  "description": "Ninetailed SDK for javascript",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,8 +9,8 @@
9
9
  "directory": "packages/sdks/javascript"
10
10
  },
11
11
  "dependencies": {
12
- "@ninetailed/experience.js-plugin-analytics": "7.12.1",
13
- "@ninetailed/experience.js-shared": "7.12.1",
12
+ "@ninetailed/experience.js-plugin-analytics": "7.13.0-beta.1",
13
+ "@ninetailed/experience.js-shared": "7.13.0-beta.1",
14
14
  "analytics": "0.8.1",
15
15
  "uuid": "9.0.0"
16
16
  },
package/src/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './lib/plugins/selectPluginsHavingExperienceSelectionMiddleware';
8
8
  export * from './lib/plugins/selectPluginsHavingOnChangeEmitter';
9
9
  export * from './lib/types/interfaces/RequiresEventBuilder';
10
10
  export * from './lib/types/interfaces/HasExperienceSelectionMiddleware';
11
+ export * from './lib/types/interfaces/HasChangesModificationMiddleware';
11
12
  export * from './lib/types/interfaces/InterestedInSeenElements';
12
13
  export * from './lib/types/interfaces/InterestedInProfileChange';
13
14
  export * from './lib/types/interfaces/InterestedInHiddenPage';
@@ -82,11 +82,13 @@ export declare class Ninetailed implements NinetailedInstance {
82
82
  onSelectVariant: <Baseline extends Reference, Variant extends Reference>({ baseline, experiences }: OnSelectVariantArgs<Baseline, Variant>, cb: OnSelectVariantCallback<Baseline, Variant>) => () => void;
83
83
  /**
84
84
  * Registers a callback to be notified when changes occur in the profile state.
85
+ * Uses the changes modification middleware system to process changes.
86
+ * Changes are processed in the following order:
85
87
  *
86
88
  * @param cb - Callback function that receives the changes state
87
89
  * @returns Function to unsubscribe from changes updates
88
90
  */
89
- onChangesChange: (cb: OnChangesChangeCallback) => import("analytics").DetachListeners;
91
+ onChangesChange: (cb: OnChangesChangeCallback) => () => void;
90
92
  /**
91
93
  * Helper method to extract changes state from profile state and notify callback
92
94
  * @private
@@ -2,3 +2,4 @@ export * from './types';
2
2
  export { EXPERIENCE_TRAIT_PREFIX, selectDistribution, isExperienceMatch, selectVariant as selectExperienceVariant, selectHasVariants as selectHasExperienceVariants, selectVariants as selectExperienceVariants, selectBaselineWithVariants as selectExperienceBaselineWithVariants, selectExperience, selectActiveExperiments, } from '@ninetailed/experience.js-shared';
3
3
  export { decodeExperienceVariantsMap } from './decodeExperienceVariantsMap';
4
4
  export { makeExperienceSelectMiddleware } from './makeExperienceSelectMiddleware';
5
+ export { makeChangesModificationMiddleware } from './makeChangesModificationMiddleware';
@@ -0,0 +1,31 @@
1
+ import { Change } from '@ninetailed/experience.js-shared';
2
+ import { NinetailedPlugin } from '@ninetailed/experience.js-plugin-analytics';
3
+ import { ChangesModificationMiddleware } from '../types/interfaces/HasChangesModificationMiddleware';
4
+ /**
5
+ * Arguments for creating a changes modification middleware
6
+ */
7
+ type CreateChangesModificationMiddlewareArg = {
8
+ plugins: NinetailedPlugin[];
9
+ changes: Change[];
10
+ };
11
+ /**
12
+ * Arguments for making a changes modification middleware with change notification
13
+ */
14
+ type MakeChangesModificationMiddlewareArg = CreateChangesModificationMiddlewareArg & {
15
+ onChange: (middleware: ChangesModificationMiddleware) => void;
16
+ };
17
+ /**
18
+ * Result of creating a changes modification middleware
19
+ */
20
+ interface ChangesModificationMiddlewareResult {
21
+ addListeners: () => void;
22
+ removeListeners: () => void;
23
+ middleware: ChangesModificationMiddleware;
24
+ }
25
+ /**
26
+ * Creates a changes modification middleware system with change notification
27
+ *
28
+ * This follows the same pattern as the experience selection middleware system
29
+ */
30
+ export declare const makeChangesModificationMiddleware: ({ plugins, onChange, changes, }: MakeChangesModificationMiddlewareArg) => ChangesModificationMiddlewareResult;
31
+ export {};
@@ -1 +1 @@
1
- export type { Reference, Baseline, VariantRef, ExperienceConfiguration, BaselineWithVariants, Distribution, ExperienceType, } from '@ninetailed/experience.js-shared';
1
+ export type { Reference, Baseline, VariantRef, ExperienceConfiguration, EntryReplacement, InlineVariable, Distribution, ExperienceType, } from '@ninetailed/experience.js-shared';
@@ -0,0 +1,8 @@
1
+ import { HasChangesModificationMiddleware } from '../types/interfaces/HasChangesModificationMiddleware';
2
+ /**
3
+ * Type guard that checks if an object implements the HasChangesModificationMiddleware interface
4
+ *
5
+ * @param arg Object to check
6
+ * @returns Boolean indicating if the object implements HasChangesModificationMiddleware
7
+ */
8
+ export declare const hasChangesModificationMiddleware: (arg: unknown) => arg is HasChangesModificationMiddleware;
@@ -0,0 +1,9 @@
1
+ import { NinetailedPlugin } from '@ninetailed/experience.js-plugin-analytics';
2
+ import { HasChangesModificationMiddleware } from '../types/interfaces/HasChangesModificationMiddleware';
3
+ /**
4
+ * Selects plugins that implement the HasChangesModificationMiddleware interface
5
+ *
6
+ * @param plugins Array of Ninetailed plugins
7
+ * @returns Array of plugins that implement HasChangesModificationMiddleware
8
+ */
9
+ export declare const selectPluginsHavingChangesModificationMiddleware: (plugins: NinetailedPlugin[]) => HasChangesModificationMiddleware[];
@@ -1,4 +1,5 @@
1
- import { type Profile } from '@ninetailed/experience.js-shared';
1
+ import type { Profile, Change } from '@ninetailed/experience.js-shared';
2
2
  export type ProfileChangedPayload = {
3
3
  profile: Profile | null;
4
+ changes: Change[] | null;
4
5
  };
@@ -0,0 +1,30 @@
1
+ import { Change } from '@ninetailed/experience.js-shared';
2
+ /**
3
+ * Arguments passed to changes modification middleware
4
+ */
5
+ export interface ChangesModificationMiddlewareArg {
6
+ changes: Change[];
7
+ }
8
+ /**
9
+ * A middleware function that can modify changes
10
+ */
11
+ export type ChangesModificationMiddleware = (arg: ChangesModificationMiddlewareArg) => ChangesModificationMiddlewareArg;
12
+ /**
13
+ * Arguments for building a changes modification middleware
14
+ */
15
+ export type BuildChangesModificationMiddlewareArg = {
16
+ changes: Change[];
17
+ };
18
+ /**
19
+ * Type for a function that builds changes modification middleware
20
+ */
21
+ export type BuildChangesModificationMiddleware = (arg: BuildChangesModificationMiddlewareArg) => ChangesModificationMiddleware | undefined;
22
+ /**
23
+ * Interface for plugins that can provide changes modification middleware
24
+ */
25
+ export interface HasChangesModificationMiddleware {
26
+ /**
27
+ * Returns a middleware function that can modify changes
28
+ */
29
+ getChangesModificationMiddleware: BuildChangesModificationMiddleware;
30
+ }