@amplifyup/sdk 0.1.43 → 0.1.45

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/README.md CHANGED
@@ -88,6 +88,27 @@ Field names must match the component registry in AmplifyUp. Connect CMS document
88
88
  | Composer preview | Orchestrator layout provider | `?preview=true` / Composer iframe |
89
89
  | Runtime queries | Edge `/v1/query` | `queryContent` — search, load more, pickers |
90
90
 
91
+ ## Pattern pages (From page)
92
+
93
+ Components using Composer **From page** require `pageContext` on the provider (or `init`) so the first paint and first page view include fields. Late `setPageContext` is fine for subsequent events, not for rendering those components.
94
+
95
+ `resource.key` is the URL param value. If a mapped entity field (for example `post.slug`) does not match that param, the SDK warns and uses the URL param.
96
+
97
+ ```tsx
98
+ <AmplifyUpProvider
99
+ config={{ trackingId: 'your-tracking-id' }}
100
+ pageContext={{
101
+ resourceType: 'post',
102
+ key: slug,
103
+ fields: { title: post.title, slug: post.slug, category: post.category },
104
+ }}
105
+ >
106
+ {children}
107
+ </AmplifyUpProvider>
108
+ ```
109
+
110
+ Pass `pageContext` at provider/init for From-page components. Do not wait until after first paint. Fixed (static) page fields still attach to events when `pageContext` is omitted.
111
+
91
112
  The SDK maps Edge’s resolved tree into `layoutTree` and renders it. Edge already applies personalization and CMS projection — the site does not call CMS or Decision APIs for layout. After paint, the overlay calls `POST /v1/select` (or `selectVariants()`) then `/v1/resolve?variants=` so decisions stay on the edge (`clientPersonalization`).
92
113
 
93
114
  ## Runtime queries
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { P as PageConfig } from './types-DUP8Lb7R.mjs';
3
+ import { P as PageConfig } from './types-NdLzHxZo.mjs';
4
4
 
5
5
  interface AmplifyRendererProps {
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { P as PageConfig } from './types-DUP8Lb7R.js';
3
+ import { P as PageConfig } from './types-NdLzHxZo.js';
4
4
 
5
5
  interface AmplifyRendererProps {
6
6
  /**
@@ -407,6 +407,153 @@ function isComposerPreview() {
407
407
  return false;
408
408
  }
409
409
 
410
+ // src/page-context.ts
411
+ var defaultLog = {
412
+ warn: (...args) => {
413
+ if (typeof console !== "undefined") console.warn(...args);
414
+ }
415
+ };
416
+ function coerceField(def, raw, log) {
417
+ if (raw == null) return void 0;
418
+ if (def.multiValue) {
419
+ const list = Array.isArray(raw) ? raw : [raw];
420
+ const out = [];
421
+ for (const item of list) {
422
+ const coerced = coerceScalar(def, item, log);
423
+ if (coerced !== void 0) out.push(coerced);
424
+ }
425
+ return out;
426
+ }
427
+ return coerceScalar(def, raw, log);
428
+ }
429
+ function coerceScalar(def, raw, log) {
430
+ switch (def.dataType) {
431
+ case "string":
432
+ if (typeof raw === "string") return raw;
433
+ if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
434
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected string)`);
435
+ return void 0;
436
+ case "number": {
437
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
438
+ if (typeof raw === "string" && raw.trim() !== "" && Number.isFinite(Number(raw))) {
439
+ return Number(raw);
440
+ }
441
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected number)`);
442
+ return void 0;
443
+ }
444
+ case "boolean": {
445
+ if (typeof raw === "boolean") return raw;
446
+ if (raw === "true") return true;
447
+ if (raw === "false") return false;
448
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected boolean)`);
449
+ return void 0;
450
+ }
451
+ case "date": {
452
+ if (typeof raw === "string" && !Number.isNaN(Date.parse(raw))) return raw;
453
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected date)`);
454
+ return void 0;
455
+ }
456
+ default:
457
+ return void 0;
458
+ }
459
+ }
460
+ function applyPageContext(input, opts) {
461
+ const log = opts.log || defaultLog;
462
+ const key = String(input.key || "").trim();
463
+ if (!key || !input.resourceType) {
464
+ log.warn("AmplifyUp: pageContext needs resourceType and key");
465
+ return null;
466
+ }
467
+ const resource = opts.pageData?.resources?.[0];
468
+ if (resource && resource.type && resource.type !== input.resourceType) {
469
+ log.warn(
470
+ `AmplifyUp: pageContext resourceType "${input.resourceType}" does not match page resource "${resource.type}"`
471
+ );
472
+ return null;
473
+ }
474
+ const fieldDefs = resource?.fields || [];
475
+ const defByKey = new Map(fieldDefs.map((f) => [f.key, f]));
476
+ const rawFields = input.fields && typeof input.fields === "object" ? input.fields : {};
477
+ const fields = {};
478
+ if (fieldDefs.length === 0) {
479
+ Object.assign(fields, rawFields);
480
+ } else {
481
+ for (const [k, v] of Object.entries(rawFields)) {
482
+ const def = defByKey.get(k);
483
+ if (!def) {
484
+ log.warn(`AmplifyUp: unknown resource field "${k}" dropped`);
485
+ continue;
486
+ }
487
+ const coerced = coerceField(def, v, log);
488
+ if (coerced !== void 0) fields[k] = coerced;
489
+ }
490
+ }
491
+ const params = { ...opts.routeParams || {} };
492
+ const mapping = resource?.urlParamMapping;
493
+ let resourceKey = key;
494
+ if (mapping) {
495
+ const fromUrl = params[mapping.param];
496
+ const fromField = fields[mapping.field];
497
+ if (fromUrl) resourceKey = String(fromUrl);
498
+ if (fromUrl && fromField != null && String(fromField) !== String(fromUrl)) {
499
+ log.warn(
500
+ `AmplifyUp: resource key mismatch \u2014 URL :${mapping.param} is "${fromUrl}" but ${mapping.field} is "${String(fromField)}". Using the URL param.`
501
+ );
502
+ }
503
+ }
504
+ const pageFields = {};
505
+ for (const pf of opts.pageData?.pageFields || []) {
506
+ if (pf.source === "static") {
507
+ if (pf.value !== void 0) pageFields[pf.key] = pf.value;
508
+ continue;
509
+ }
510
+ if (pf.source === "mapped" && pf.resource && pf.field) {
511
+ pageFields[pf.key] = fields[pf.field];
512
+ }
513
+ }
514
+ return {
515
+ resource: {
516
+ type: input.resourceType,
517
+ key: resourceKey,
518
+ params,
519
+ fields
520
+ },
521
+ page: { fields: pageFields },
522
+ pageRoute: opts.pageRoute || ""
523
+ };
524
+ }
525
+ function staticPageFieldsFromSnapshot(pageData) {
526
+ const pageFields = {};
527
+ for (const pf of pageData?.pageFields || []) {
528
+ if (pf.source === "static" && pf.value !== void 0) {
529
+ pageFields[pf.key] = pf.value;
530
+ }
531
+ }
532
+ return pageFields;
533
+ }
534
+ function attachStaticPageContext(pageData, pageRoute) {
535
+ if (!pageData) return null;
536
+ const fields = staticPageFieldsFromSnapshot(pageData);
537
+ const hasStatic = Object.keys(fields).length > 0;
538
+ const hasRoute = !!pageRoute;
539
+ if (!hasStatic && !hasRoute) return null;
540
+ return {
541
+ page: { fields },
542
+ pageRoute: pageRoute || ""
543
+ };
544
+ }
545
+ function mergeEventContext(properties, attached) {
546
+ if (!attached) return properties;
547
+ return {
548
+ ...properties,
549
+ ...attached.resource ? { resource: attached.resource } : {},
550
+ page: attached.page,
551
+ ...attached.pageRoute ? { pageRoute: attached.pageRoute } : {}
552
+ };
553
+ }
554
+ function setPageResourceFields(fields) {
555
+ }
556
+
410
557
  // src/index.ts
411
558
  var DWELL_ACTIVITY_IDLE_MS = 2 * 60 * 1e3;
412
559
  var DWELL_POLL_MS = 5e3;
@@ -475,6 +622,11 @@ var AmplifyUpSDK = class {
475
622
  this.pageEngagementActive = false;
476
623
  /** Path of the last page() / routeChange — avoids duplicate SPA fires. */
477
624
  this.lastTrackedPath = null;
625
+ this.pendingPageContext = null;
626
+ this.attachedPageContext = null;
627
+ this.resolvedPageData = null;
628
+ this.resolvedPageRoute = "";
629
+ this.resolvedRouteParams = {};
478
630
  this.historyHooksInstalled = false;
479
631
  this.pageEngagementState = {
480
632
  reached: /* @__PURE__ */ new Set(),
@@ -637,8 +789,13 @@ var AmplifyUpSDK = class {
637
789
  // Enable debug by default
638
790
  target: this.target,
639
791
  dev: config.dev,
640
- ...config.siteId ? { siteId: config.siteId } : {}
792
+ ...config.siteId ? { siteId: config.siteId } : {},
793
+ ...config.pageContext ? { pageContext: config.pageContext } : {}
641
794
  };
795
+ if (config.pageContext) {
796
+ this.pendingPageContext = config.pageContext;
797
+ this.recomputeAttachedContext();
798
+ }
642
799
  try {
643
800
  const adapterConfig = {
644
801
  ...this.config,
@@ -995,6 +1152,43 @@ var AmplifyUpSDK = class {
995
1152
  this.componentViewObservers.forEach((obs) => obs.disconnect());
996
1153
  this.componentViewObservers = [];
997
1154
  }
1155
+ /**
1156
+ * Set resource context for this page. Prefer passing `pageContext` on
1157
+ * AmplifyUpProvider / init — required for components using From page.
1158
+ * Late calls still attach to subsequent events (not a second page view).
1159
+ */
1160
+ setPageContext(input) {
1161
+ this.pendingPageContext = input;
1162
+ this.recomputeAttachedContext();
1163
+ }
1164
+ /**
1165
+ * Apply published pageData (from /v1/resolve) so field validation and
1166
+ * mapped page fields can run.
1167
+ */
1168
+ applyResolvedPageData(pageData, pageRoute, routeParams) {
1169
+ this.resolvedPageData = pageData || null;
1170
+ if (pageRoute) this.resolvedPageRoute = pageRoute;
1171
+ if (routeParams) this.resolvedRouteParams = routeParams;
1172
+ this.recomputeAttachedContext();
1173
+ }
1174
+ recomputeAttachedContext() {
1175
+ if (!this.pendingPageContext) {
1176
+ this.attachedPageContext = attachStaticPageContext(
1177
+ this.resolvedPageData,
1178
+ this.resolvedPageRoute
1179
+ );
1180
+ return;
1181
+ }
1182
+ this.attachedPageContext = applyPageContext(this.pendingPageContext, {
1183
+ pageData: this.resolvedPageData,
1184
+ routeParams: this.resolvedRouteParams,
1185
+ pageRoute: this.resolvedPageRoute
1186
+ });
1187
+ setPageResourceFields(this.attachedPageContext?.resource?.fields || null);
1188
+ }
1189
+ getAttachedPageContext() {
1190
+ return this.attachedPageContext;
1191
+ }
998
1192
  /**
999
1193
  * Track a page view
1000
1194
  */
@@ -1016,7 +1210,7 @@ var AmplifyUpSDK = class {
1016
1210
  sessionId,
1017
1211
  trackingId: this.config?.trackingId,
1018
1212
  // Timestamp is included in event payload
1019
- ...properties
1213
+ ...mergeEventContext(properties || {}, this.attachedPageContext)
1020
1214
  };
1021
1215
  this.adapter.page(pageProperties);
1022
1216
  this.lastTrackedPath = url;
@@ -1040,7 +1234,7 @@ var AmplifyUpSDK = class {
1040
1234
  url: typeof window !== "undefined" ? window.location.href : void 0,
1041
1235
  path: properties?.path || (typeof window !== "undefined" ? window.location.pathname : void 0),
1042
1236
  // Timestamp is included in event payload
1043
- ...properties
1237
+ ...mergeEventContext(properties || {}, this.attachedPageContext)
1044
1238
  };
1045
1239
  if (!trackProperties.path && typeof window !== "undefined") {
1046
1240
  trackProperties.path = window.location.pathname;