pulse-updates 1.3.7 → 1.3.8

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.
@@ -45,7 +45,7 @@ export interface TrackOptions {
45
45
  flushIntervalMs?: number;
46
46
  /** Events per request. The server refuses more than 100. */
47
47
  maxBatch?: number;
48
- /** Events held before the oldest are dropped. Default 500. */
48
+ /** Best-effort events held before the oldest are dropped. Durable idempotent rows are never evicted. Default 500. */
49
49
  maxQueue?: number;
50
50
  /** Persists the outbox across process death. Use the same storage passed to initPulse. */
51
51
  storage?: ConfigStorage;
@@ -53,9 +53,12 @@ export interface TrackOptions {
53
53
  storageKey?: string;
54
54
  /** Per-request timeout. Default 10s. */
55
55
  timeoutMs?: number;
56
- /** Maximum delivery attempts before an event is dropped. Default 10. */
56
+ /** Maximum delivery attempts before a best-effort event is dropped. Durable idempotent events retry until server ACK. Default 10. */
57
57
  maxAttempts?: number;
58
- /** Return false until analytics consent exists. No event is retained before consent. */
58
+ /**
59
+ * Return false until analytics consent exists. No event is retained before consent. Consent is
60
+ * sampled at enqueue; an event already durably accepted remains eligible for later delivery.
61
+ */
59
62
  hasConsent?: () => boolean;
60
63
  /** Only these property keys may leave the process. */
61
64
  propertyAllowlist?: readonly string[];
@@ -84,12 +87,20 @@ export declare function configureTracking(opts: TrackOptions): void;
84
87
  * caller has to wrap.
85
88
  */
86
89
  export declare function track(event: string, props?: TrackedEventInput['props'], time?: Date): void;
90
+ /**
91
+ * Durably enqueue one logical event with a caller-owned UUID. Replaying the same id is safe:
92
+ * Pulse's server/read model deduplicates it, while the local outbox avoids duplicate rows that
93
+ * are still pending. `true` means the event is present in persistent storage, not delivered yet;
94
+ * once acknowledged, Track keeps it in that outbox through retries and process restarts until a
95
+ * successful server response removes it transactionally.
96
+ */
97
+ export declare function trackIdempotent(eventId: string, event: string, props?: TrackedEventInput['props'], time?: Date): boolean;
87
98
  /**
88
99
  * Send what is queued. Returns how many events the server accepted.
89
100
  *
90
- * Failures put the batch back at the front of the queue rather than dropping it: the
91
- * usual cause is a network that is about to come back, and the usual moment is right
92
- * after launch, when the first events of a session are the ones a funnel needs most.
101
+ * The batch remains in the durable queue while HTTP is in flight and is removed only after a
102
+ * successful response is also persisted locally. This intentionally permits duplicate replay
103
+ * after a crash, while a caller-owned event id lets the server/read model deduplicate it.
93
104
  */
94
105
  export declare function flushEvents(): Promise<number>;
95
106
  /** How many events are waiting — for a caller that wants to flush before backgrounding. */
@@ -1 +1 @@
1
- {"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/track.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,aAAa,CAAC;IAEjC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,wBAAwB,CAAC;IAEpD,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAE3B,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEtC,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAErC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE;QAAE,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK;YAAE,MAAM,EAAE,MAAM,IAAI,CAAA;SAAE,CAAA;KAAE,CAAC;IAE9G,+DAA+D;IAC/D,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAEvC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,cAAc,GAAG,YAAY,KAAK,IAAI,CAAC;IAE1F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACrE,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAoCD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAY1D;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CA2B1F;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAiEnD;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,eAAe,IAAI;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAEA;AAED,qEAAqE;AACrE,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAOnC"}
1
+ {"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/track.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,aAAa,CAAC;IAEjC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,wBAAwB,CAAC;IAEpD,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,qHAAqH;IACrH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,qIAAqI;IACrI,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAE3B,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEtC,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAErC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE;QAAE,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK;YAAE,MAAM,EAAE,MAAM,IAAI,CAAA;SAAE,CAAA;KAAE,CAAC;IAE9G,+DAA+D;IAC/D,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAEvC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,cAAc,GAAG,YAAY,KAAK,IAAI,CAAC;IAE1F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACrE,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAuCD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAY1D;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAE1F;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAClC,IAAI,CAAC,EAAE,IAAI,GACV,OAAO,CAIT;AA2GD;;;;;;GAMG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAiEnD;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,eAAe,IAAI;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAEA;AAED,qEAAqE;AACrE,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAOnC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-updates",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "Pulse app-experience SDK for React Native: updates, config, experiments, events, decisions and first-party links",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
package/src/config.ts CHANGED
@@ -50,6 +50,12 @@ export type ConfigSignatureStatus = 'not-configured' | 'verified' | 'cached-veri
50
50
  export interface ConfigStorage {
51
51
  getString(key: string): string | null | undefined;
52
52
  set(key: string, value: string): void;
53
+ /**
54
+ * False when `set` only mirrors a write and schedules asynchronous persistence. Durable
55
+ * acknowledgement APIs must fail closed because process death may still lose that write.
56
+ * Omitted means the historical synchronous-storage contract.
57
+ */
58
+ readonly supportsDurableSyncWrites?: boolean;
53
59
  }
54
60
 
55
61
  /**
package/src/init.ts CHANGED
@@ -45,6 +45,7 @@ import {
45
45
  flushEvents,
46
46
  getTrackingInfo,
47
47
  track,
48
+ trackIdempotent,
48
49
  type TrackedEventInput,
49
50
  } from './track';
50
51
 
@@ -140,6 +141,12 @@ export interface PulseClient {
140
141
  configUrl: string;
141
142
  trackUrl: string;
142
143
  track: (event: string, props?: TrackedEventInput['props'], time?: Date) => void;
144
+ trackIdempotent: (
145
+ eventId: string,
146
+ event: string,
147
+ props?: TrackedEventInput['props'],
148
+ time?: Date,
149
+ ) => boolean;
143
150
  flush: () => Promise<number>;
144
151
  setUser: (userId?: string, attributes?: Record<string, string>) => void;
145
152
  clearUser: () => void;
@@ -261,6 +268,7 @@ export function initPulse(opts: InitPulseOptions): PulseClient {
261
268
  configUrl,
262
269
  trackUrl,
263
270
  track,
271
+ trackIdempotent,
264
272
  flush: flushEvents,
265
273
  setUser: (nextUserId, attributes) => {
266
274
  userId = nextUserId?.trim() || undefined;
@@ -328,10 +336,17 @@ export async function initPulseAsync(
328
336
  const loaded = await Promise.all(keys.map(async (key) => [key, await opts.storage.getItem(key)] as const));
329
337
  const memory = new Map(loaded.filter((entry): entry is readonly [string, string] => entry[1] !== null));
330
338
  const storage: ConfigStorage = {
339
+ supportsDurableSyncWrites: false,
331
340
  getString: (key) => memory.get(key),
332
341
  set: (key, value) => {
333
342
  memory.set(key, value);
334
- void opts.storage.setItem(key, value);
343
+ try {
344
+ void Promise.resolve(opts.storage.setItem(key, value)).catch((error) => {
345
+ try { opts.onError?.(error); } catch { /* diagnostics never break persistence */ }
346
+ });
347
+ } catch (error) {
348
+ try { opts.onError?.(error); } catch { /* diagnostics never break persistence */ }
349
+ }
335
350
  },
336
351
  };
337
352
  return initPulse({ ...opts, storage });