@reform-society/agera-core 0.5.0 → 0.6.0

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.d.ts CHANGED
@@ -218,6 +218,88 @@ type FetchBasinJWTOptions = {
218
218
  };
219
219
  declare function fetchBasinJWT(endpoint: string, storage: StorageAccessor, storageKey: string, options?: FetchBasinJWTOptions): Promise<string>;
220
220
  //#endregion
221
+ //#region src/lifecycle.d.ts
222
+ type NormalizedResult = {
223
+ delivery: "observed" | "unobserved";
224
+ operation: string;
225
+ operationStatus: "acknowledged" | "pending" | "completed" | "rejected" | "unknown";
226
+ contactStatus: "created" | "updated" | "unknown";
227
+ contactEvidence: "provider" | "inferred" | "unknown";
228
+ paymentStatus: "unknown" | "pending" | "succeeded" | "failed";
229
+ paymentEvidence: "provider" | "inferred" | "unknown";
230
+ nextAction?: {
231
+ type: "redirect" | "poll" | "verify";
232
+ url?: string;
233
+ };
234
+ outcome?: string;
235
+ extensions?: Record<string, unknown>;
236
+ };
237
+ type EventSourceKind = "form-submit" | "element-action";
238
+ type EventSource = {
239
+ client: string;
240
+ integration?: string;
241
+ kind?: EventSourceKind;
242
+ formId?: string;
243
+ triggerId?: string;
244
+ pagePath: string;
245
+ };
246
+ type RequestSnapshot = {
247
+ fields: Record<string, unknown>;
248
+ context: Record<string, unknown>;
249
+ config: Record<string, unknown>;
250
+ method: string;
251
+ delivery: "wait" | "background";
252
+ body?: unknown;
253
+ };
254
+ type EventEnvelope = {
255
+ schemaVersion: 1;
256
+ eventId: string;
257
+ occurredAt: string;
258
+ source: EventSource;
259
+ context: Record<string, unknown>;
260
+ result: NormalizedResult;
261
+ submissionId?: string;
262
+ flowId?: string;
263
+ attemptId?: string;
264
+ conversionId?: string;
265
+ request?: RequestSnapshot;
266
+ };
267
+ type LifecycleEvent = EventEnvelope & ({
268
+ type: "crm.dispatched";
269
+ submissionId: string;
270
+ request: RequestSnapshot;
271
+ } | {
272
+ type: "crm.response";
273
+ submissionId: string;
274
+ request: RequestSnapshot;
275
+ response: {
276
+ httpStatus: number;
277
+ raw: unknown;
278
+ };
279
+ } | {
280
+ type: "crm.failed" | "crm.cancelled";
281
+ submissionId: string;
282
+ request: RequestSnapshot;
283
+ } | {
284
+ type: "element.entered" | "element.clicked" | "element.viewed" | "step.changed";
285
+ } | {
286
+ type: "flow.started" | "flow.returned" | "flow.verified" | "flow.failed";
287
+ flowId: string;
288
+ attemptId: string;
289
+ });
290
+ type LifecycleType = LifecycleEvent["type"];
291
+ declare function unknownResult(delivery?: NormalizedResult["delivery"]): NormalizedResult;
292
+ /** Serializable copies omit functions, DOM handles, and cyclic references. */
293
+ declare function snapshot<T>(value: T): T;
294
+ declare function createLifecycleBus(onError?: () => void): {
295
+ on: <T extends LifecycleType>(type: T | "*", listener: (event: LifecycleEvent & {
296
+ type: T;
297
+ }) => unknown) => () => void;
298
+ emit: (event: LifecycleEvent) => void;
299
+ clear(): void;
300
+ };
301
+ declare function normalizeResult(value: unknown, httpStatus?: number): NormalizedResult;
302
+ //#endregion
221
303
  //#region src/crm-types.d.ts
222
304
  type CounterUpdateConfig = {
223
305
  name: string;
@@ -303,12 +385,14 @@ type CrmOutcome = {
303
385
  status: "succeeded";
304
386
  httpStatus: number;
305
387
  response: Record<string, unknown> | null;
388
+ normalized?: NormalizedResult;
306
389
  } | {
307
390
  status: "accepted";
308
391
  } | {
309
392
  status: "rejected";
310
393
  httpStatus: number;
311
394
  response: Record<string, unknown> | null;
395
+ normalized?: NormalizedResult;
312
396
  } | {
313
397
  status: "failed";
314
398
  cause: unknown;
@@ -321,88 +405,6 @@ declare function executeCrm(gateway: CrmGateway, command: CrmCommand, options?:
321
405
  signal?: AbortSignal;
322
406
  }): Promise<CrmOutcome>;
323
407
  //#endregion
324
- //#region src/lifecycle.d.ts
325
- type NormalizedResult = {
326
- delivery: "observed" | "unobserved";
327
- operation: string;
328
- operationStatus: "acknowledged" | "pending" | "completed" | "rejected" | "unknown";
329
- contactStatus: "created" | "updated" | "unknown";
330
- contactEvidence: "provider" | "inferred" | "unknown";
331
- paymentStatus: "unknown" | "pending" | "succeeded" | "failed";
332
- paymentEvidence: "provider" | "inferred" | "unknown";
333
- nextAction?: {
334
- type: "redirect" | "poll" | "verify";
335
- url?: string;
336
- };
337
- outcome?: string;
338
- extensions?: Record<string, unknown>;
339
- };
340
- type EventSourceKind = "form-submit" | "element-action";
341
- type EventSource = {
342
- client: string;
343
- integration?: string;
344
- kind?: EventSourceKind;
345
- formId?: string;
346
- triggerId?: string;
347
- pagePath: string;
348
- };
349
- type RequestSnapshot = {
350
- fields: Record<string, unknown>;
351
- context: Record<string, unknown>;
352
- config: Record<string, unknown>;
353
- method: string;
354
- delivery: "wait" | "background";
355
- body?: unknown;
356
- };
357
- type EventEnvelope = {
358
- schemaVersion: 1;
359
- eventId: string;
360
- occurredAt: string;
361
- source: EventSource;
362
- context: Record<string, unknown>;
363
- result: NormalizedResult;
364
- submissionId?: string;
365
- flowId?: string;
366
- attemptId?: string;
367
- conversionId?: string;
368
- request?: RequestSnapshot;
369
- };
370
- type LifecycleEvent = EventEnvelope & ({
371
- type: "crm.dispatched";
372
- submissionId: string;
373
- request: RequestSnapshot;
374
- } | {
375
- type: "crm.response";
376
- submissionId: string;
377
- request: RequestSnapshot;
378
- response: {
379
- httpStatus: number;
380
- raw: unknown;
381
- };
382
- } | {
383
- type: "crm.failed" | "crm.cancelled";
384
- submissionId: string;
385
- request: RequestSnapshot;
386
- } | {
387
- type: "element.entered" | "element.clicked" | "element.viewed" | "step.changed";
388
- } | {
389
- type: "flow.started" | "flow.returned" | "flow.verified" | "flow.failed";
390
- flowId: string;
391
- attemptId: string;
392
- });
393
- type LifecycleType = LifecycleEvent["type"];
394
- declare function unknownResult(delivery?: NormalizedResult["delivery"]): NormalizedResult;
395
- /** Serializable copies omit functions, DOM handles, and cyclic references. */
396
- declare function snapshot<T>(value: T): T;
397
- declare function createLifecycleBus(onError?: () => void): {
398
- on: <T extends LifecycleType>(type: T | "*", listener: (event: LifecycleEvent & {
399
- type: T;
400
- }) => unknown) => () => void;
401
- emit: (event: LifecycleEvent) => void;
402
- clear(): void;
403
- };
404
- declare function normalizeResult(value: unknown, httpStatus?: number): NormalizedResult;
405
- //#endregion
406
408
  //#region src/tracking.d.ts
407
409
  type ValueResolver = string | number | boolean | null | undefined | ValueResolver[] | {
408
410
  [key: string]: ValueResolver;
@@ -571,6 +573,7 @@ type CrmFetchOptions = {
571
573
  sourceUrl?: string;
572
574
  deviceType?: DeviceType;
573
575
  method?: "POST" | "PATCH" | "PUT";
576
+ signal?: AbortSignal;
574
577
  };
575
578
  declare function crmFetch(url: string, payload: Record<string, unknown> | object, options?: CrmFetchOptions): Promise<CrmFetchResult>;
576
579
  //#endregion
package/dist/index.js CHANGED
@@ -754,41 +754,6 @@ async function fetchBasinJWT(endpoint, storage, storageKey, options = {}) {
754
754
  }
755
755
  }
756
756
  //#endregion
757
- //#region src/crm-submission.ts
758
- function isAbortError(error) {
759
- return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
760
- }
761
- async function executeCrm(gateway, command, options = {}) {
762
- const { signal } = options;
763
- if (signal?.aborted) return { status: "aborted" };
764
- try {
765
- const result = await gateway({
766
- client: command.client,
767
- method: command.method ?? "POST",
768
- delivery: command.delivery ?? "wait",
769
- payload: command.payload,
770
- signal
771
- });
772
- if (result.kind === "accepted") return { status: "accepted" };
773
- const { ok, status, data } = result.response;
774
- return ok ? {
775
- status: "succeeded",
776
- httpStatus: status,
777
- response: data
778
- } : {
779
- status: "rejected",
780
- httpStatus: status,
781
- response: data
782
- };
783
- } catch (error) {
784
- if (signal?.aborted || isAbortError(error)) return { status: "aborted" };
785
- return {
786
- status: "failed",
787
- cause: error
788
- };
789
- }
790
- }
791
- //#endregion
792
757
  //#region src/lifecycle.ts
793
758
  function unknownResult(delivery = "unobserved") {
794
759
  return {
@@ -894,6 +859,45 @@ function normalizeResult(value, httpStatus) {
894
859
  return result;
895
860
  }
896
861
  //#endregion
862
+ //#region src/crm-submission.ts
863
+ function isAbortError$1(error) {
864
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
865
+ }
866
+ async function executeCrm(gateway, command, options = {}) {
867
+ const { signal } = options;
868
+ if (signal?.aborted) return { status: "aborted" };
869
+ try {
870
+ const result = await gateway({
871
+ client: command.client,
872
+ method: command.method ?? "POST",
873
+ delivery: command.delivery ?? "wait",
874
+ payload: command.payload,
875
+ signal
876
+ });
877
+ if (result.kind === "accepted") return { status: "accepted" };
878
+ const { ok, status, data, cancelled, normalized } = result.response;
879
+ if (cancelled) return { status: "aborted" };
880
+ const extra = normalized !== void 0 ? { normalized: normalizeResult(normalized, status) } : {};
881
+ return ok ? {
882
+ status: "succeeded",
883
+ httpStatus: status,
884
+ response: data,
885
+ ...extra
886
+ } : {
887
+ status: "rejected",
888
+ httpStatus: status,
889
+ response: data,
890
+ ...extra
891
+ };
892
+ } catch (error) {
893
+ if (signal?.aborted || isAbortError$1(error)) return { status: "aborted" };
894
+ return {
895
+ status: "failed",
896
+ cause: error
897
+ };
898
+ }
899
+ }
900
+ //#endregion
897
901
  //#region src/tracking.ts
898
902
  function firstDefined(values) {
899
903
  return values.find((value) => value !== void 0 && value !== "");
@@ -1414,19 +1418,43 @@ function getOrCreateVisitorId(storage) {
1414
1418
  //#endregion
1415
1419
  //#region src/crm-fetch.ts
1416
1420
  const noop = () => {};
1421
+ function isRecord$1(value) {
1422
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1423
+ }
1424
+ function isAbortError(error) {
1425
+ return isRecord$1(error) && error.name === "AbortError";
1426
+ }
1427
+ /** Unwrap the v1 lifecycle envelope `{ schemaVersion: 1, raw, result }` when present. */
1428
+ function unwrapEnvelope(raw, status) {
1429
+ if (isRecord$1(raw) && raw.schemaVersion === 1 && Object.hasOwn(raw, "raw") && isRecord$1(raw.result)) return {
1430
+ data: raw.raw,
1431
+ normalized: normalizeResult(raw.result, status)
1432
+ };
1433
+ return { data: raw };
1434
+ }
1417
1435
  async function crmFetch(url, payload, options = {}) {
1418
- const { waitForResponse = true, sourceUrl, deviceType, method = "POST" } = options;
1436
+ const { waitForResponse = true, sourceUrl, deviceType, method = "POST", signal } = options;
1419
1437
  const body = JSON.stringify(payload);
1420
- const headers = { "Content-Type": "application/json" };
1438
+ const headers = {
1439
+ "Content-Type": "application/json",
1440
+ "X-Agera-Response-Version": "1"
1441
+ };
1421
1442
  if (sourceUrl) headers["x-source"] = sourceUrl;
1422
1443
  if (deviceType !== void 0) headers["x-device-type"] = deviceType;
1444
+ if (signal?.aborted) return {
1445
+ ok: false,
1446
+ status: 0,
1447
+ data: null,
1448
+ cancelled: true
1449
+ };
1423
1450
  if (!waitForResponse) {
1424
1451
  try {
1425
1452
  fetch(url, {
1426
1453
  method,
1427
1454
  headers,
1428
1455
  body,
1429
- keepalive: true
1456
+ keepalive: true,
1457
+ signal
1430
1458
  }).catch(noop);
1431
1459
  } catch {}
1432
1460
  return {
@@ -1439,18 +1467,25 @@ async function crmFetch(url, payload, options = {}) {
1439
1467
  const response = await fetch(url, {
1440
1468
  method,
1441
1469
  headers,
1442
- body
1470
+ body,
1471
+ signal
1443
1472
  });
1444
- let data = null;
1473
+ let raw = null;
1445
1474
  try {
1446
- data = await response.json();
1475
+ raw = await response.json();
1447
1476
  } catch {}
1448
1477
  return {
1449
1478
  ok: response.ok,
1450
1479
  status: response.status,
1451
- data
1480
+ ...unwrapEnvelope(raw, response.status)
1481
+ };
1482
+ } catch (error) {
1483
+ if (signal?.aborted || isAbortError(error)) return {
1484
+ ok: false,
1485
+ status: 0,
1486
+ data: null,
1487
+ cancelled: true
1452
1488
  };
1453
- } catch {
1454
1489
  return {
1455
1490
  ok: false,
1456
1491
  status: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reform-society/agera-core",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "DOM-free core logic for Agera",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",