@namiml/sdk-core 3.4.4-dev.202607072107 → 3.4.4-dev.202607072357

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/dist/index.cjs CHANGED
@@ -98,7 +98,7 @@ const {
98
98
  // version — stamped by scripts/version.sh
99
99
  NAMI_SDK_VERSION: exports.NAMI_SDK_VERSION = "3.4.4",
100
100
  // full package version including dev suffix — stamped by scripts/version.sh
101
- NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.202607072107",
101
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.202607072357",
102
102
  // environments
103
103
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
104
104
  // error messages
@@ -61891,6 +61891,13 @@ const NamiHandoffTag = {
61891
61891
  BuySku: 'buysku',
61892
61892
  UserData: 'userdata',
61893
61893
  };
61894
+ /**
61895
+ * True iff `tag` is a value of the customer-facing `NamiHandoffTag` vocabulary.
61896
+ * Unknown tags are still delivered (pass-through) — this only drives a warning.
61897
+ */
61898
+ function isKnownHandoffTag(tag) {
61899
+ return Object.values(NamiHandoffTag).includes(tag);
61900
+ }
61894
61901
  /**
61895
61902
  * Permission types reportable through a handoff outcome.
61896
61903
  *
@@ -63988,6 +63995,8 @@ function validateForm(validators, formStates) {
63988
63995
  return errors;
63989
63996
  }
63990
63997
 
63998
+ /** Form field key convention for grouped handoffs: `<tag>::<payloadKey>`. */
63999
+ const HANDOFF_GROUP_KEY = /^([^:]+)::(.+)$/;
63991
64000
  class BasicNamiFlow {
63992
64001
  constructor(flowObject = {}) {
63993
64002
  this.id = flowObject.id ?? '';
@@ -64756,13 +64765,25 @@ class NamiFlow extends BasicNamiFlow {
64756
64765
  logger.warn("No formStates available for handoff sequence");
64757
64766
  return;
64758
64767
  }
64759
- const keys = Object.keys(formStates).sort();
64768
+ const keys = Object.keys(formStates);
64760
64769
  if (keys.length === 0) {
64761
64770
  logger.warn("formStates is empty, no handoffs to perform");
64762
64771
  return;
64763
64772
  }
64764
- // filter keys where value === true
64765
- const eligibleKeys = keys.filter((k) => formStates[k] === true);
64773
+ // Grouped path: at least one field uses the `tag::field` convention.
64774
+ if (keys.some((k) => HANDOFF_GROUP_KEY.test(k))) {
64775
+ const groups = this.buildHandoffGroups(formStates);
64776
+ if (groups.length === 0) {
64777
+ logger.info("No handoff groups after grouping");
64778
+ this.manager.resume();
64779
+ return;
64780
+ }
64781
+ this.activeHandoffSequence = { groups };
64782
+ this.resumeNextHandoff();
64783
+ return;
64784
+ }
64785
+ // Legacy path (unchanged): one handoff per true key, tag = field name.
64786
+ const eligibleKeys = keys.sort().filter((k) => formStates[k] === true);
64766
64787
  if (eligibleKeys.length === 0) {
64767
64788
  logger.info("No eligible handoff keys after filtering");
64768
64789
  this.manager.resume();
@@ -64774,6 +64795,34 @@ class NamiFlow extends BasicNamiFlow {
64774
64795
  };
64775
64796
  this.resumeNextHandoff();
64776
64797
  }
64798
+ /**
64799
+ * Reduce a flat `formStates` map into one bucket per distinct handoff tag,
64800
+ * using the `<tag>::<payloadKey>` field-key convention. Insertion order is
64801
+ * preserved (group order = first-appearance). Reserved-prefixed tags are
64802
+ * skipped so they never reach the host handler (NAM-1556). A group is kept
64803
+ * only if at least one of its fields is `true` (a permission the user
64804
+ * declined is not handed off); a kept group's payload rides verbatim,
64805
+ * including its `false` fields.
64806
+ */
64807
+ buildHandoffGroups(formStates) {
64808
+ const buckets = new Map();
64809
+ for (const key of Object.keys(formStates)) {
64810
+ const match = key.match(HANDOFF_GROUP_KEY);
64811
+ if (!match)
64812
+ continue;
64813
+ const [, tag, field] = match;
64814
+ if (tag.startsWith(RESERVED_HANDOFF_TAG_PREFIX)) {
64815
+ logger.warn(`Ignoring reserved handoff group tag '${tag}'`);
64816
+ continue;
64817
+ }
64818
+ if (!buckets.has(tag))
64819
+ buckets.set(tag, {});
64820
+ buckets.get(tag)[field] = formStates[key];
64821
+ }
64822
+ return Array.from(buckets, ([tag, data]) => ({ tag, data }))
64823
+ .filter((group) => Object.values(group.data).some((value) => value === true))
64824
+ .sort((a, b) => a.tag.localeCompare(b.tag));
64825
+ }
64777
64826
  resumeNextHandoff() {
64778
64827
  const sequence = this.activeHandoffSequence;
64779
64828
  if (!sequence) {
@@ -64781,6 +64830,24 @@ class NamiFlow extends BasicNamiFlow {
64781
64830
  this.manager.resume();
64782
64831
  return;
64783
64832
  }
64833
+ // Grouped path.
64834
+ if ("groups" in sequence) {
64835
+ if (sequence.groups.length === 0) {
64836
+ logger.info("All handoffs in the sequence are complete");
64837
+ this.activeHandoffSequence = null;
64838
+ this.manager.resume();
64839
+ return;
64840
+ }
64841
+ const next = sequence.groups.shift(); // safe: length > 0
64842
+ if (!isKnownHandoffTag(next.tag)) {
64843
+ logger.warn(`handoff group tag '${next.tag}' is outside the NamiHandoffTag vocabulary — passing through`);
64844
+ }
64845
+ this.activeHandoffSequence = sequence; // persist mutated groups
64846
+ logger.debug(`Starting grouped handoff for '${next.tag}' → ${JSON.stringify(next.data)}`);
64847
+ this.manager.deliverHandoff(next.tag, next.data);
64848
+ return;
64849
+ }
64850
+ // Legacy path (unchanged).
64784
64851
  if (sequence.remainingKeys.length === 0) {
64785
64852
  logger.info("All handoffs in the sequence are complete");
64786
64853
  this.activeHandoffSequence = null;
@@ -64790,10 +64857,7 @@ class NamiFlow extends BasicNamiFlow {
64790
64857
  const nextKey = sequence.remainingKeys.shift(); // safe: length > 0
64791
64858
  const value = sequence.formStates[nextKey];
64792
64859
  logger.debug(`Starting handoff for ${nextKey} → ${String(value)}`);
64793
- // persist updated sequence (remainingKeys mutated)
64794
64860
  this.activeHandoffSequence = sequence;
64795
- // Route through the manager's single delivery point so v2 handlers get
64796
- // a working complete() for sequence-key handoffs too.
64797
64861
  this.manager.deliverHandoff(`${nextKey}`, undefined);
64798
64862
  }
64799
64863
  }
package/dist/index.d.ts CHANGED
@@ -2249,6 +2249,11 @@ declare class NamiFlow extends BasicNamiFlow {
2249
2249
  activeHandoffSequence: {
2250
2250
  formStates: Record<string, boolean | string>;
2251
2251
  remainingKeys: string[];
2252
+ } | {
2253
+ groups: Array<{
2254
+ tag: string;
2255
+ data: Record<string, boolean | string>;
2256
+ }>;
2252
2257
  } | null;
2253
2258
  currentScreenState: TPaywallContext | null;
2254
2259
  isPaused: boolean;
@@ -2357,6 +2362,16 @@ declare class NamiFlow extends BasicNamiFlow {
2357
2362
  */
2358
2363
  buildUserDataEnvelope(formId: string): NamiUserDataEnvelope;
2359
2364
  flowHandoffFormSequence(): void;
2365
+ /**
2366
+ * Reduce a flat `formStates` map into one bucket per distinct handoff tag,
2367
+ * using the `<tag>::<payloadKey>` field-key convention. Insertion order is
2368
+ * preserved (group order = first-appearance). Reserved-prefixed tags are
2369
+ * skipped so they never reach the host handler (NAM-1556). A group is kept
2370
+ * only if at least one of its fields is `true` (a permission the user
2371
+ * declined is not handed off); a kept group's payload rides verbatim,
2372
+ * including its `false` fields.
2373
+ */
2374
+ private buildHandoffGroups;
2360
2375
  resumeNextHandoff(): void;
2361
2376
  }
2362
2377
 
package/dist/index.mjs CHANGED
@@ -96,7 +96,7 @@ const {
96
96
  // version — stamped by scripts/version.sh
97
97
  NAMI_SDK_VERSION = "3.4.4",
98
98
  // full package version including dev suffix — stamped by scripts/version.sh
99
- NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.202607072107",
99
+ NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.202607072357",
100
100
  // environments
101
101
  PRODUCTION = "production", DEVELOPMENT = "development",
102
102
  // error messages
@@ -61889,6 +61889,13 @@ const NamiHandoffTag = {
61889
61889
  BuySku: 'buysku',
61890
61890
  UserData: 'userdata',
61891
61891
  };
61892
+ /**
61893
+ * True iff `tag` is a value of the customer-facing `NamiHandoffTag` vocabulary.
61894
+ * Unknown tags are still delivered (pass-through) — this only drives a warning.
61895
+ */
61896
+ function isKnownHandoffTag(tag) {
61897
+ return Object.values(NamiHandoffTag).includes(tag);
61898
+ }
61892
61899
  /**
61893
61900
  * Permission types reportable through a handoff outcome.
61894
61901
  *
@@ -63986,6 +63993,8 @@ function validateForm(validators, formStates) {
63986
63993
  return errors;
63987
63994
  }
63988
63995
 
63996
+ /** Form field key convention for grouped handoffs: `<tag>::<payloadKey>`. */
63997
+ const HANDOFF_GROUP_KEY = /^([^:]+)::(.+)$/;
63989
63998
  class BasicNamiFlow {
63990
63999
  constructor(flowObject = {}) {
63991
64000
  this.id = flowObject.id ?? '';
@@ -64754,13 +64763,25 @@ class NamiFlow extends BasicNamiFlow {
64754
64763
  logger.warn("No formStates available for handoff sequence");
64755
64764
  return;
64756
64765
  }
64757
- const keys = Object.keys(formStates).sort();
64766
+ const keys = Object.keys(formStates);
64758
64767
  if (keys.length === 0) {
64759
64768
  logger.warn("formStates is empty, no handoffs to perform");
64760
64769
  return;
64761
64770
  }
64762
- // filter keys where value === true
64763
- const eligibleKeys = keys.filter((k) => formStates[k] === true);
64771
+ // Grouped path: at least one field uses the `tag::field` convention.
64772
+ if (keys.some((k) => HANDOFF_GROUP_KEY.test(k))) {
64773
+ const groups = this.buildHandoffGroups(formStates);
64774
+ if (groups.length === 0) {
64775
+ logger.info("No handoff groups after grouping");
64776
+ this.manager.resume();
64777
+ return;
64778
+ }
64779
+ this.activeHandoffSequence = { groups };
64780
+ this.resumeNextHandoff();
64781
+ return;
64782
+ }
64783
+ // Legacy path (unchanged): one handoff per true key, tag = field name.
64784
+ const eligibleKeys = keys.sort().filter((k) => formStates[k] === true);
64764
64785
  if (eligibleKeys.length === 0) {
64765
64786
  logger.info("No eligible handoff keys after filtering");
64766
64787
  this.manager.resume();
@@ -64772,6 +64793,34 @@ class NamiFlow extends BasicNamiFlow {
64772
64793
  };
64773
64794
  this.resumeNextHandoff();
64774
64795
  }
64796
+ /**
64797
+ * Reduce a flat `formStates` map into one bucket per distinct handoff tag,
64798
+ * using the `<tag>::<payloadKey>` field-key convention. Insertion order is
64799
+ * preserved (group order = first-appearance). Reserved-prefixed tags are
64800
+ * skipped so they never reach the host handler (NAM-1556). A group is kept
64801
+ * only if at least one of its fields is `true` (a permission the user
64802
+ * declined is not handed off); a kept group's payload rides verbatim,
64803
+ * including its `false` fields.
64804
+ */
64805
+ buildHandoffGroups(formStates) {
64806
+ const buckets = new Map();
64807
+ for (const key of Object.keys(formStates)) {
64808
+ const match = key.match(HANDOFF_GROUP_KEY);
64809
+ if (!match)
64810
+ continue;
64811
+ const [, tag, field] = match;
64812
+ if (tag.startsWith(RESERVED_HANDOFF_TAG_PREFIX)) {
64813
+ logger.warn(`Ignoring reserved handoff group tag '${tag}'`);
64814
+ continue;
64815
+ }
64816
+ if (!buckets.has(tag))
64817
+ buckets.set(tag, {});
64818
+ buckets.get(tag)[field] = formStates[key];
64819
+ }
64820
+ return Array.from(buckets, ([tag, data]) => ({ tag, data }))
64821
+ .filter((group) => Object.values(group.data).some((value) => value === true))
64822
+ .sort((a, b) => a.tag.localeCompare(b.tag));
64823
+ }
64775
64824
  resumeNextHandoff() {
64776
64825
  const sequence = this.activeHandoffSequence;
64777
64826
  if (!sequence) {
@@ -64779,6 +64828,24 @@ class NamiFlow extends BasicNamiFlow {
64779
64828
  this.manager.resume();
64780
64829
  return;
64781
64830
  }
64831
+ // Grouped path.
64832
+ if ("groups" in sequence) {
64833
+ if (sequence.groups.length === 0) {
64834
+ logger.info("All handoffs in the sequence are complete");
64835
+ this.activeHandoffSequence = null;
64836
+ this.manager.resume();
64837
+ return;
64838
+ }
64839
+ const next = sequence.groups.shift(); // safe: length > 0
64840
+ if (!isKnownHandoffTag(next.tag)) {
64841
+ logger.warn(`handoff group tag '${next.tag}' is outside the NamiHandoffTag vocabulary — passing through`);
64842
+ }
64843
+ this.activeHandoffSequence = sequence; // persist mutated groups
64844
+ logger.debug(`Starting grouped handoff for '${next.tag}' → ${JSON.stringify(next.data)}`);
64845
+ this.manager.deliverHandoff(next.tag, next.data);
64846
+ return;
64847
+ }
64848
+ // Legacy path (unchanged).
64782
64849
  if (sequence.remainingKeys.length === 0) {
64783
64850
  logger.info("All handoffs in the sequence are complete");
64784
64851
  this.activeHandoffSequence = null;
@@ -64788,10 +64855,7 @@ class NamiFlow extends BasicNamiFlow {
64788
64855
  const nextKey = sequence.remainingKeys.shift(); // safe: length > 0
64789
64856
  const value = sequence.formStates[nextKey];
64790
64857
  logger.debug(`Starting handoff for ${nextKey} → ${String(value)}`);
64791
- // persist updated sequence (remainingKeys mutated)
64792
64858
  this.activeHandoffSequence = sequence;
64793
- // Route through the manager's single delivery point so v2 handlers get
64794
- // a working complete() for sequence-key handoffs too.
64795
64859
  this.manager.deliverHandoff(`${nextKey}`, undefined);
64796
64860
  }
64797
64861
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.4-dev.202607072107",
3
+ "version": "3.4.4-dev.202607072357",
4
4
  "description": "Platform-agnostic core for the Nami SDK — business logic, API, types, and state management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",