@compilacion/colleciones-clientos 2.0.24 → 2.0.28

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.
@@ -611,6 +611,99 @@
611
611
 
612
612
  }
613
613
 
614
+ /**
615
+ * CollecionesEventGroup
616
+ * ----------------------
617
+ * A client-side grouping interface for events that belong to the same business occurrence.
618
+ *
619
+ * Design principles:
620
+ * - A group is purely a build-time construct; it does not change the wire format.
621
+ * - Each event in the group remains a standalone CollecionesEvent.
622
+ * - When getEvents() is called, group metadata is materialised onto each event:
623
+ * meta.eventGroup.id — the shared group id
624
+ * meta.eventGroup.groupClientEventIds — clientEventIds of all other events in the group
625
+ * - Semantic meaning of the relations between events is intentionally left to the domain model.
626
+ *
627
+ * Usage:
628
+ * const group = new CollecionesEventGroup();
629
+ *
630
+ * const eventA = group.addEvent();
631
+ * eventA.setEntity('listing');
632
+ * eventA.setAction('viewed');
633
+ *
634
+ * const eventB = group.addEvent();
635
+ * eventB.setEntity('session');
636
+ * eventB.setAction('active');
637
+ *
638
+ * tracker.trackGroup(group);
639
+ */
640
+
641
+
642
+ class CollecionesEventGroup {
643
+
644
+ /**
645
+ * Creates a new event group.
646
+ * The groupId is NOT generated up-front: a group's identity is defined by its composition,
647
+ * so the id is generated lazily in getEvents() and invalidated by every addEvent() call.
648
+ * Reading `group.groupId` before the first getEvents() returns null.
649
+ */
650
+ constructor() {
651
+ this.groupId = null;
652
+ this._events = [];
653
+ }
654
+
655
+ /**
656
+ * Adds an event to the group.
657
+ * When called without arguments it acts as an alias for `new CollecionesEvent()`,
658
+ * keeping the same familiar contract as building events directly.
659
+ * An existing CollecionesEvent instance may also be passed in.
660
+ *
661
+ * Adding an event changes the composition of the group, so the cached groupId is invalidated
662
+ * — the next getEvents() call will generate a fresh id and re-stamp every event.
663
+ *
664
+ * @param {CollecionesEvent|null} [event=null] - An existing event to register, or null to create a fresh one.
665
+ * @returns {CollecionesEvent} The event that was added.
666
+ * @throws {Error} If a non-null, non-CollecionesEvent value is passed.
667
+ */
668
+ addEvent(event = null) {
669
+ if (event !== null && !(event instanceof CollecionesEvent)) {
670
+ throw new Error('Event must be an instance of CollecionesEvent');
671
+ }
672
+ const newEvent = event ?? new CollecionesEvent();
673
+ this._events.push(newEvent);
674
+ this.groupId = null;
675
+ return newEvent;
676
+ }
677
+
678
+ /**
679
+ * Materialises group metadata onto every event and returns the full list.
680
+ * If no groupId is currently set, a new one is generated here. Each event then receives:
681
+ * - meta.eventGroup.id — the groupId of this group
682
+ * - meta.eventGroup.groupClientEventIds — clientEventIds of the sibling events (not self)
683
+ *
684
+ * Safe to call multiple times: idempotent as long as no addEvent() happened in between.
685
+ * After an addEvent() the cached groupId is null, so a subsequent getEvents() will mint a fresh id
686
+ * and re-stamp every event accordingly.
687
+ *
688
+ * Callers may also assign `group.groupId` directly (e.g. with a semantic name) before calling
689
+ * getEvents() to override the auto-generated value; do so AFTER all addEvent() calls,
690
+ * otherwise the next addEvent() will clear the override.
691
+ *
692
+ * @returns {CollecionesEvent[]} A shallow copy of the internal events array with group metadata applied.
693
+ */
694
+ getEvents() {
695
+ if (this.groupId === null) {
696
+ this.groupId = generateId();
697
+ }
698
+ const allClientEventIds = this._events.map(e => e.meta.clientEventId);
699
+ this._events.forEach(event => {
700
+ const peers = allClientEventIds.filter(id => id !== event.meta.clientEventId);
701
+ event.setEventGroup(this.groupId, peers);
702
+ });
703
+ return [...this._events];
704
+ }
705
+ }
706
+
614
707
  /**
615
708
  * Tracks events by enriching them with shared identifiers and routing through configured emitters.
616
709
  */
@@ -741,7 +834,6 @@
741
834
  if (!initialized) return false;
742
835
  if (!tracker) return false;
743
836
  if (!arg || typeof arg !== 'object') return false;
744
- if (!arg.entity || !arg.action) return false;
745
837
 
746
838
  const normalizeIdentifiers = (identifiers) => {
747
839
  if (Array.isArray(identifiers)) {
@@ -784,7 +876,7 @@
784
876
  };
785
877
 
786
878
  // Collection items are stored as `{ identifierField: value }` on the event.
787
- const addCollectionItem = (collectionName, identifierName, identifierValue) => {
879
+ const addCollectionItem = (event, collectionName, identifierName, identifierValue) => {
788
880
  if (!collectionName || !identifierName || identifierValue === undefined || identifierValue === null) {
789
881
  return;
790
882
  }
@@ -795,24 +887,24 @@
795
887
  // ARRAY collections are the simplest form:
796
888
  // they are arrays of primitive values that should be copied into the event
797
889
  // using the configured identifier field.
798
- const applyArrayCollection = (collectionName, collectionConfig) => {
890
+ const applyArrayCollection = (event, collectionName, collectionConfig) => {
799
891
  if (!Array.isArray(collectionConfig.array)) {
800
892
  return false;
801
893
  }
802
894
  const identifierName = collectionConfig.identifierField;
803
895
  collectionConfig.array.forEach((value) => {
804
896
  if (Array.isArray(value)) {
805
- value.forEach((nestedValue) => addCollectionItem(collectionName, identifierName, nestedValue));
897
+ value.forEach((nestedValue) => addCollectionItem(event, collectionName, identifierName, nestedValue));
806
898
  return;
807
899
  }
808
- addCollectionItem(collectionName, identifierName, value);
900
+ addCollectionItem(event, collectionName, identifierName, value);
809
901
  });
810
902
  return true;
811
903
  };
812
904
 
813
905
  // EVALUATED collections derive their identifier value from a path on each item.
814
906
  // An optional filter object narrows the source array before mapping.
815
- const applyEvaluatedCollection = (collectionName, collectionConfig) => {
907
+ const applyEvaluatedCollection = (event, collectionName, collectionConfig) => {
816
908
  if (!Array.isArray(collectionConfig.itemsField)) {
817
909
  return false;
818
910
  }
@@ -834,14 +926,14 @@
834
926
  const identifierName = collectionConfig.identifierField;
835
927
  items.forEach((item) => {
836
928
  const identifierValue = getByPath(item, collectionConfig.path) ?? item?.[identifierName] ?? item?.id;
837
- addCollectionItem(collectionName, identifierName, identifierValue);
929
+ addCollectionItem(event, collectionName, identifierName, identifierValue);
838
930
  });
839
931
  return true;
840
932
  };
841
933
 
842
934
  // PARAMTABLE rows use dynamic field names that are prefixed by the collection name:
843
935
  // e.g. `listingParamTableItemsField` and `listingIdentifierValueParamTable`.
844
- const applyParamTableCollection = (collectionName, collectionConfig) => {
936
+ const applyParamTableCollection = (event, collectionName, collectionConfig) => {
845
937
  if (!Array.isArray(collectionConfig.table)) {
846
938
  return false;
847
939
  }
@@ -854,73 +946,100 @@
854
946
  }
855
947
  const identifierName = row[identifierNameKey];
856
948
  const identifierValue = row[identifierValueKey];
857
- addCollectionItem(collectionName, identifierName, identifierValue);
949
+ addCollectionItem(event, collectionName, identifierName, identifierValue);
858
950
  });
859
951
  return true;
860
952
  };
861
953
 
862
- const applyCollectionConfig = (collectionName, collectionConfig) => {
954
+ const applyCollectionConfig = (event, collectionName, collectionConfig) => {
863
955
  if (!collectionConfig || typeof collectionConfig !== 'object') {
864
956
  return;
865
957
  }
866
958
 
867
959
  if (collectionConfig.type === 'ARRAY') {
868
- applyArrayCollection(collectionName, collectionConfig);
960
+ applyArrayCollection(event, collectionName, collectionConfig);
869
961
  return;
870
962
  }
871
963
  if (collectionConfig.type === 'EVALUATED') {
872
- applyEvaluatedCollection(collectionName, collectionConfig);
964
+ applyEvaluatedCollection(event, collectionName, collectionConfig);
873
965
  return;
874
966
  }
875
967
  if (collectionConfig.type === 'PARAMTABLE') {
876
- applyParamTableCollection(collectionName, collectionConfig);
968
+ applyParamTableCollection(event, collectionName, collectionConfig);
877
969
  }
878
970
  };
879
971
 
880
- const event = new CollecionesEvent();
881
- event.setEntity(arg.entity);
882
- event.setAction(arg.action);
883
-
884
- normalizeIdentifiers(arg.identifiers).forEach(identifier => {
885
- if (typeof identifier !== 'object' || identifier === null) return;
886
- if (!identifier.name || !identifier.value) return;
887
- event.setIdentifier(identifier.name, identifier.value);
888
- });
972
+ const populateEvent = (eventArg, event) => {
973
+ event.setEntity(eventArg.entity);
974
+ event.setAction(eventArg.action);
889
975
 
890
- if (arg.actor?.entity) {
891
- event.setActor(arg.actor.entity);
892
- let identifiers = arg.actor?.identifiers;
893
- if (!Array.isArray(identifiers)) {
894
- identifiers = identifiers?.[arg.actor.entity] ?? identifiers;
895
- }
896
- normalizeIdentifiers(identifiers).forEach(identifier => {
976
+ normalizeIdentifiers(eventArg.identifiers).forEach(identifier => {
897
977
  if (typeof identifier !== 'object' || identifier === null) return;
898
978
  if (!identifier.name || !identifier.value) return;
899
- event.setActorIdentifier(identifier.name, identifier.value);
979
+ event.setIdentifier(identifier.name, identifier.value);
900
980
  });
901
- }
902
981
 
903
- const relations = arg?.relations ?? arg?.rerelations;
904
- if (relations && typeof relations === 'object') {
905
- Object.keys(relations).forEach(relationName => {
906
- event.setReference(relationName);
907
- Object.keys(relations[relationName]).forEach(identifierName => {
908
- event.setReferenceIdentifier(relationName, identifierName, relations[relationName][identifierName]);
982
+ if (eventArg.actor?.entity) {
983
+ event.setActor(eventArg.actor.entity);
984
+ let identifiers = eventArg.actor?.identifiers;
985
+ if (!Array.isArray(identifiers)) {
986
+ identifiers = identifiers?.[eventArg.actor.entity] ?? identifiers;
987
+ }
988
+ normalizeIdentifiers(identifiers).forEach(identifier => {
989
+ if (typeof identifier !== 'object' || identifier === null) return;
990
+ if (!identifier.name || !identifier.value) return;
991
+ event.setActorIdentifier(identifier.name, identifier.value);
909
992
  });
910
- });
911
- }
912
- if (arg?.adjectives && typeof arg.adjectives === 'object') {
913
- Object.keys(arg.adjectives).forEach(adjectiveName => {
914
- event.addAdjective(adjectiveName, arg.adjectives[adjectiveName]);
915
- });
916
- }
993
+ }
994
+
995
+ const relations = eventArg?.relations ?? eventArg?.rerelations;
996
+ if (relations && typeof relations === 'object') {
997
+ Object.keys(relations).forEach(relationName => {
998
+ event.setReference(relationName);
999
+ Object.keys(relations[relationName]).forEach(identifierName => {
1000
+ event.setReferenceIdentifier(relationName, identifierName, relations[relationName][identifierName]);
1001
+ });
1002
+ });
1003
+ }
1004
+ if (eventArg?.adjectives && typeof eventArg.adjectives === 'object') {
1005
+ Object.keys(eventArg.adjectives).forEach(adjectiveName => {
1006
+ event.addAdjective(adjectiveName, eventArg.adjectives[adjectiveName]);
1007
+ });
1008
+ }
1009
+
1010
+ if (eventArg?.collections && typeof eventArg.collections === 'object') {
1011
+ Object.keys(eventArg.collections).forEach(collectionName => {
1012
+ applyCollectionConfig(event, collectionName, eventArg.collections[collectionName]);
1013
+ });
1014
+ }
1015
+ };
917
1016
 
918
- if (arg?.collections && typeof arg.collections === 'object') {
919
- Object.keys(arg.collections).forEach(collectionName => {
920
- applyCollectionConfig(collectionName, arg.collections[collectionName]);
1017
+ // Group form: payload carries an `events` array (and optionally a semantic `eventGroup` name).
1018
+ // Each item is a standalone event spec; they are bundled into a CollecionesEventGroup so that
1019
+ // shared group metadata (id + sibling clientEventIds) is materialised onto every event.
1020
+ if (Array.isArray(arg.events)) {
1021
+ const group = new CollecionesEventGroup();
1022
+ let added = 0;
1023
+ arg.events.forEach(eventArg => {
1024
+ if (!eventArg || typeof eventArg !== 'object') return;
1025
+ if (!eventArg.entity || !eventArg.action) return;
1026
+ const event = group.addEvent();
1027
+ populateEvent(eventArg, event);
1028
+ added++;
921
1029
  });
1030
+ if (added === 0) return false;
1031
+ // Apply the semantic group name AFTER addEvent calls: each addEvent invalidates
1032
+ // group.groupId, so an override set earlier would be cleared by the loop above.
1033
+ if (typeof arg.eventGroup === 'string' && arg.eventGroup.length > 0) {
1034
+ group.groupId = arg.eventGroup;
1035
+ }
1036
+ tracker.trackGroup(group);
1037
+ return true;
922
1038
  }
923
1039
 
1040
+ if (!arg.entity || !arg.action) return false;
1041
+ const event = new CollecionesEvent();
1042
+ populateEvent(arg, event);
924
1043
  tracker.track(event);
925
1044
  return true;
926
1045
  };