@salesforce/storefront-next-runtime 0.1.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.
Files changed (49) hide show
  1. package/LICENSE.txt +181 -0
  2. package/README.md +158 -0
  3. package/dist/ComponentContext.js +14 -0
  4. package/dist/ComponentContext.js.map +1 -0
  5. package/dist/DesignContext.js +769 -0
  6. package/dist/DesignContext.js.map +1 -0
  7. package/dist/DesignContext2.js +6 -0
  8. package/dist/PageDesignerProvider.js +53 -0
  9. package/dist/PageDesignerProvider.js.map +1 -0
  10. package/dist/PageRegistration.js +29 -0
  11. package/dist/PageRegistration.js.map +1 -0
  12. package/dist/PreviewContext.js +18 -0
  13. package/dist/PreviewContext.js.map +1 -0
  14. package/dist/design-messaging.d.ts +3 -0
  15. package/dist/design-messaging.js +3 -0
  16. package/dist/design-mode.d.ts +40 -0
  17. package/dist/design-mode.d.ts.map +1 -0
  18. package/dist/design-mode.js +3 -0
  19. package/dist/design-react-core.d.ts +69 -0
  20. package/dist/design-react-core.d.ts.map +1 -0
  21. package/dist/design-react-core.js +23 -0
  22. package/dist/design-react-core.js.map +1 -0
  23. package/dist/design-react.d.ts +130 -0
  24. package/dist/design-react.d.ts.map +1 -0
  25. package/dist/design-react.js +488 -0
  26. package/dist/design-react.js.map +1 -0
  27. package/dist/design-styles.css +232 -0
  28. package/dist/design.d.ts +2 -0
  29. package/dist/design.js +219 -0
  30. package/dist/design.js.map +1 -0
  31. package/dist/events.d.ts +208 -0
  32. package/dist/events.d.ts.map +1 -0
  33. package/dist/events.js +104 -0
  34. package/dist/events.js.map +1 -0
  35. package/dist/index.d.ts +215 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index2.d.ts +1171 -0
  38. package/dist/index2.d.ts.map +1 -0
  39. package/dist/messaging-api.js +340 -0
  40. package/dist/messaging-api.js.map +1 -0
  41. package/dist/modeDetection.js +50 -0
  42. package/dist/modeDetection.js.map +1 -0
  43. package/dist/scapi.d.ts +29278 -0
  44. package/dist/scapi.d.ts.map +1 -0
  45. package/dist/scapi.js +2 -0
  46. package/dist/scapi.js.map +1 -0
  47. package/dist/types.d.ts +13293 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/package.json +108 -0
@@ -0,0 +1,232 @@
1
+ /*
2
+ * Copyright (c) 2025, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+
8
+ /* Base styles shared by both component and fragment */
9
+ .pd-design__decorator {
10
+ /* Temporary color for drop targets */
11
+ --pd-design-drop-target-color: #008827;
12
+ --pd-design-outline-width: 1px;
13
+ --pd-design-selected-color: #005fb2;
14
+ --pd-design-drop-target-width: 2px;
15
+ --pd-design-fallback-badge-color: rgb(172, 47, 109);
16
+ --pd-design-region-border-color: #3e3e3c;
17
+ --pd-design-drop-target-offset: 10px;
18
+ --pd-design-base-z-index: 30;
19
+ }
20
+
21
+ .pd-design__component,
22
+ .pd-design__region,
23
+ .pd-design__fragment {
24
+ position: relative;
25
+ }
26
+
27
+ .pd-design__component {
28
+ --pd-design-color: #0070d2;
29
+ }
30
+
31
+ .pd-design__fragment {
32
+ --pd-design-color: #8402ad;
33
+ }
34
+
35
+ .pd-design__component.pd-design__component--unlocalized,
36
+ .pd-design__component.pd-design__decorator--selected.pd-design__component--unlocalized {
37
+ --pd-design-color: rgb(215, 58, 138);
38
+ }
39
+
40
+ .pd-design__frame__overlay {
41
+ pointer-events: none;
42
+ display: none;
43
+ position: absolute;
44
+ top: 0;
45
+ left: 0;
46
+ width: 100%;
47
+ height: 100%;
48
+ z-index: calc(var(--pd-design-base-z-index) + 2);
49
+ }
50
+
51
+ .pd-design__component--unlocalized .pd-design__frame--visible .pd-design__frame__overlay {
52
+ display: flex;
53
+ justify-content: center;
54
+ align-items: center;
55
+ }
56
+
57
+
58
+ .pd-design__component--unlocalized .pd-design__frame__overlay {
59
+ background-color: rgba(0, 0, 0, 0.25);
60
+ }
61
+
62
+ /* Shared state styles */
63
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame--x::before,
64
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame--x::after,
65
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame--y::before,
66
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame--y::after {
67
+ background-color: var(--pd-design-color);
68
+ content: '';
69
+ display: block;
70
+ position: absolute;
71
+ z-index: var(--pd-design-base-z-index);
72
+ }
73
+
74
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame--y::before {
75
+ height: var(--pd-design-outline-width);
76
+ left: -1px;
77
+ top: -1px;
78
+ width: calc(100% + 2px);
79
+ }
80
+
81
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame--y::after {
82
+ bottom: -1px;
83
+ height: var(--pd-design-outline-width);
84
+ left: -1px;
85
+ width: calc(100% + 2px)
86
+ }
87
+
88
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame--x::before {
89
+ height: 100%;
90
+ left: -1px;
91
+ width: var(--pd-design-outline-width);
92
+ }
93
+
94
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame--x::after {
95
+ height: 100%;
96
+ right: -1px;
97
+ width: var(--pd-design-outline-width);
98
+ }
99
+
100
+ .pd-design__frame__label {
101
+ align-items: center;
102
+ background: var(--pd-design-color);
103
+ border-top-left-radius: 4px;
104
+ border-top-right-radius: 4px;
105
+ color: white;
106
+ font-family: 'Salesforce Sans', sans-serif;
107
+ font-size: 0.75rem;
108
+ font-weight: 500;
109
+ height: 32px;
110
+ left: 50%;
111
+ letter-spacing: 0.5px;
112
+ padding: 0 12px;
113
+ position: absolute;
114
+ top: -32px;
115
+ transform: translateX(-50%);
116
+ display: none;
117
+ white-space: nowrap;
118
+ }
119
+
120
+ .pd-design__frame__toolbox {
121
+ align-items: center;
122
+ background: var(--pd-design-color);
123
+ display: flex;
124
+ position: absolute;
125
+ right: 0;
126
+ top: 0;
127
+ visibility: hidden;
128
+ z-index: calc(var(--pd-design-base-z-index) + 3);
129
+ }
130
+
131
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame__toolbox {
132
+ visibility: visible;
133
+ }
134
+
135
+ .pd-design__frame.pd-design__frame--visible > .pd-design__frame__label {
136
+ display: inline-flex;
137
+ }
138
+
139
+ .pd-design__component.pd-design__decorator--selected {
140
+ --pd-design-color: var(--pd-design-selected-color);
141
+ --pd-design-outline-width: 2px;
142
+ }
143
+
144
+ .pd-design__frame__name {
145
+ overflow: hidden;
146
+ text-overflow: ellipsis;
147
+ white-space: nowrap;
148
+ }
149
+
150
+ .pd-design__frame__toolbox-button {
151
+ align-items: center;
152
+ background: rgba(0, 0, 0, 0);
153
+ border: none;
154
+ cursor: pointer;
155
+ display: flex;
156
+ height: 20px;
157
+ justify-content: center;
158
+ padding: 0;
159
+ transition: background-color 0.2s ease-in-out;
160
+ width: 20px;
161
+ }
162
+
163
+ .pd-design__frame__toolbox-button:hover {
164
+ background: rgba(0, 0, 0, 0.2);
165
+ }
166
+
167
+ .pd-design__frame__delete-icon,
168
+ .pd-design__frame__move-icon {
169
+ color: white;
170
+ fill: white;
171
+ height: 12px;
172
+ width: 12px;
173
+ }
174
+
175
+ .pd-design__frame__fallback-badge {
176
+ background: var(--pd-design-fallback-badge-color);
177
+ border-radius: 15rem;
178
+ display: inline-block;
179
+ font-size: .75rem;
180
+ line-height: normal;
181
+ margin-left: .5rem;
182
+ padding: .25rem .5rem;
183
+ white-space: nowrap;
184
+ }
185
+
186
+ .pd-design__region {
187
+ --pd-design-color: var(--pd-design-drop-target-color);
188
+
189
+ outline: 1px dotted var(--pd-design-region-border-color);
190
+ min-height: 50px;
191
+ min-width: 50px;
192
+ }
193
+
194
+ .pd-design__region--hovered {
195
+ --pd-design-outline-width: 1px;
196
+ }
197
+
198
+ .pd-design__component__drop-target {
199
+ background-color: var(--pd-design-drop-target-color);
200
+ height: 0;
201
+ left: 0;
202
+ position: absolute;
203
+ top: 0;
204
+ width: 0;
205
+ z-index: var(--pd-design-base-z-index);
206
+ }
207
+
208
+ .pd-design__drop-target__y-before > .pd-design__component__drop-target {
209
+ height: var(--pd-design-drop-target-width);
210
+ top: calc(calc(var(--pd-design-drop-target-offset) * -1) - calc(var(--pd-design-drop-target-width) / 2));
211
+ width: 100%;
212
+ }
213
+
214
+ .pd-design__drop-target__y-after > .pd-design__component__drop-target {
215
+ bottom: calc(var(--pd-design-drop-target-offset) - calc(var(--pd-design-drop-target-width) / 2));
216
+ height: var(--pd-design-drop-target-width);
217
+ top: unset;
218
+ width: 100%;
219
+ }
220
+
221
+ .pd-design__drop-target__x-before > .pd-design__component__drop-target {
222
+ height: 100%;
223
+ left: calc(calc(var(--pd-design-drop-target-offset) * -1) - calc(var(--pd-design-drop-target-width) / 2));
224
+ width: var(--pd-design-drop-target-width);
225
+ }
226
+
227
+ .pd-design__drop-target__x-after > .pd-design__component__drop-target {
228
+ height: 100%;
229
+ left: unset;
230
+ right: calc(var(--pd-design-drop-target-offset) - calc(var(--pd-design-drop-target-width) / 2));
231
+ width: var(--pd-design-drop-target-width);
232
+ }
@@ -0,0 +1,2 @@
1
+ import { a as DesignMetadata, c as LoaderNames, i as ComponentRegistryOptions, n as ComponentId, o as Entry, r as ComponentModule, s as FrameworkAdapter, t as ComponentRegistry } from "./index.js";
2
+ export { ComponentId, ComponentModule, ComponentRegistry, ComponentRegistryOptions, DesignMetadata, Entry, FrameworkAdapter, LoaderNames };
package/dist/design.js ADDED
@@ -0,0 +1,219 @@
1
+ //#region src/design/registry/registry.ts
2
+ /**
3
+ * Framework-agnostic ComponentRegistry manages component loading with static registration.
4
+ *
5
+ * Features:
6
+ * - Framework agnostic core with adapter pattern
7
+ * - Lazy loading via framework adapters for code splitting
8
+ * - Static component registration via build plugins (no dynamic discovery)
9
+ * - Design mode decoration via framework adapters
10
+ * - Request deduplication for concurrent component loads
11
+ * - Component metadata handled via API (not stored in registry)
12
+ *
13
+ * @template TProps - Component props type
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const registry = new ComponentRegistry({
18
+ * adapter: new ReactAdapter(),
19
+ * designDecorator: createDesignDecorator,
20
+ * });
21
+ *
22
+ * // Components are pre-registered via static registry plugin
23
+ * // Get a component
24
+ * const Hero = registry.getComponent('hero');
25
+ *
26
+ * // Preload for SSR
27
+ * await registry.preload('hero');
28
+ * ```
29
+ */
30
+ var ComponentRegistry = class {
31
+ registry = /* @__PURE__ */ new Map();
32
+ pending = /* @__PURE__ */ new Map();
33
+ cancelled = /* @__PURE__ */ new Set();
34
+ adapter;
35
+ constructor({ adapter }) {
36
+ this.adapter = adapter;
37
+ }
38
+ /**
39
+ * Registers a component in the registry with the specified id.
40
+ * If a component with the same id already exists, it will be overwritten.
41
+ */
42
+ registerComponent(id, component) {
43
+ const prev = this.registry.get(id) ?? {
44
+ id,
45
+ raw: null
46
+ };
47
+ this.registry.set(id, {
48
+ ...prev,
49
+ id,
50
+ raw: component
51
+ });
52
+ }
53
+ /**
54
+ * Registers a dynamic importer for a component id. Useful if you don't want to rely on scanning.
55
+ */
56
+ registerImporter(id, importer, loaderNames) {
57
+ const prev = this.registry.get(id) ?? {
58
+ id,
59
+ raw: null
60
+ };
61
+ this.registry.set(id, {
62
+ ...prev,
63
+ id,
64
+ import: importer,
65
+ loaderNames
66
+ });
67
+ }
68
+ /**
69
+ * Retrieves a component by id. Returns a framework-specific component type.
70
+ * In lazy loading scenarios, this will be a lazy component if the component
71
+ * is discovered via dynamic import. In design mode, the returned component
72
+ * is decorated via `designDecorator`.
73
+ */
74
+ getComponent(id) {
75
+ const e = this.ensureLocalEntry(id);
76
+ if (!e) return null;
77
+ const comp = e.raw ?? e.lazy ?? null;
78
+ if (!comp) return null;
79
+ return this.adapter.decorateComponent(comp);
80
+ }
81
+ /**
82
+ * Preload the JS chunk for a component id (use in route loaders/SSR to avoid waterfalls).
83
+ *
84
+ * This method ensures the component module is loaded and cached. Concurrent calls
85
+ * for the same component ID are automatically deduplicated via the pending map
86
+ * in ensureDiscovered().
87
+ *
88
+ * @throws Error if the component cannot be discovered
89
+ */
90
+ async preload(id) {
91
+ const e = await this.ensureDiscovered(id);
92
+ if (e?.lazy || e?.raw) return;
93
+ throw new Error(`Component "${id}" could not be discovered (no importer, no raw/lazy).`);
94
+ }
95
+ /** Get loader function names for external invocation. */
96
+ getLoaderNames(id) {
97
+ return this.registry.get(id)?.loaderNames;
98
+ }
99
+ hasLoaders(id) {
100
+ return Object.values(this.registry.get(id)?.loaderNames || {}).filter(Boolean).length > 0;
101
+ }
102
+ /**
103
+ * Call a loader function for a component externally.
104
+ *
105
+ * @param id - Component ID
106
+ * @param loaderArgs - Arguments to pass to the loader function
107
+ * @param loaderType - Type of loader to call ('loader' or 'clientLoader')
108
+ * @returns Promise resolving to the loader result
109
+ */
110
+ async callLoader(id, loaderArgs, loaderType = "loader") {
111
+ const loaderName = this.getLoaderNames(id)?.[loaderType];
112
+ if (!loaderName) return Promise.resolve(void 0);
113
+ const entry = this.registry.get(id);
114
+ if (!entry?.import) throw new Error(`No importer found for component: ${id}`);
115
+ try {
116
+ const loaderFunction = (await entry.import())[loaderName];
117
+ if (typeof loaderFunction !== "function") return;
118
+ return await loaderFunction(loaderArgs);
119
+ } catch (error) {
120
+ throw new Error(`Failed to call ${loaderType} for component '${id}': ${error.message}`);
121
+ }
122
+ }
123
+ /** Get fallback component if available. */
124
+ getFallback(id) {
125
+ return this.registry.get(id)?.fallback;
126
+ }
127
+ /**
128
+ * Returns all registered component IDs.
129
+ * Useful for debugging and introspection.
130
+ */
131
+ getRegisteredIds() {
132
+ return Array.from(this.registry.keys());
133
+ }
134
+ /**
135
+ * Checks if a component is registered.
136
+ */
137
+ has(id) {
138
+ return this.registry.has(id);
139
+ }
140
+ /**
141
+ * Clears all cached components and cancels pending discoveries.
142
+ * In-flight async operations will be cancelled and their promises will reject.
143
+ * Useful for testing or hot module replacement.
144
+ */
145
+ clear() {
146
+ for (const id of this.pending.keys()) this.cancelled.add(id);
147
+ this.registry.clear();
148
+ this.pending.clear();
149
+ }
150
+ ensureLocalEntry(id) {
151
+ const cached = this.registry.get(id);
152
+ if (cached) return cached;
153
+ const placeholder = {
154
+ id,
155
+ raw: null
156
+ };
157
+ this.registry.set(id, placeholder);
158
+ this.ensureDiscovered(id);
159
+ return placeholder;
160
+ }
161
+ /**
162
+ * Ensures a component is discovered and cached.
163
+ * Only returns early if a raw (eagerly loaded) component exists.
164
+ * Otherwise, attempts to discover via registered importer.
165
+ *
166
+ * @throws Error if the discovery is cancelled via clear()
167
+ */
168
+ async ensureDiscovered(id) {
169
+ const existing = this.registry.get(id);
170
+ if (existing?.raw) return existing;
171
+ if (this.pending.has(id)) return this.pending.get(id) ?? null;
172
+ const work = (async () => {
173
+ if (this.cancelled.has(id)) {
174
+ this.cancelled.delete(id);
175
+ throw new Error(`Component discovery for "${id}" was cancelled`);
176
+ }
177
+ let entry = this.registry.get(id) ?? {
178
+ id,
179
+ raw: null
180
+ };
181
+ if (entry.import) {
182
+ entry = await this.buildFromImporter(id, entry.import);
183
+ if (this.cancelled.has(id)) {
184
+ this.cancelled.delete(id);
185
+ throw new Error(`Component discovery for "${id}" was cancelled`);
186
+ }
187
+ this.registry.set(id, entry);
188
+ return entry;
189
+ }
190
+ return this.registry.get(id) ?? null;
191
+ })();
192
+ this.pending.set(id, work);
193
+ try {
194
+ const done = await work;
195
+ this.pending.delete(id);
196
+ return done;
197
+ } catch (error) {
198
+ this.pending.delete(id);
199
+ throw error;
200
+ }
201
+ }
202
+ async buildFromImporter(id, importer) {
203
+ const mod = await importer();
204
+ return this.buildFromLoadedModule(id, importer, mod);
205
+ }
206
+ buildFromLoadedModule(id, importer, mod) {
207
+ return {
208
+ id,
209
+ raw: null,
210
+ lazy: this.adapter.createLazyComponent(importer),
211
+ import: importer,
212
+ fallback: mod.fallback
213
+ };
214
+ }
215
+ };
216
+
217
+ //#endregion
218
+ export { ComponentRegistry };
219
+ //# sourceMappingURL=design.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design.js","names":["placeholder: Entry<TProps, TFrameworkComponent>"],"sources":["../src/design/registry/registry.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ComponentId,\n LoaderNames,\n ComponentModule,\n Entry,\n FrameworkAdapter,\n ComponentRegistryOptions,\n} from './types';\n\n/**\n * Framework-agnostic ComponentRegistry manages component loading with static registration.\n *\n * Features:\n * - Framework agnostic core with adapter pattern\n * - Lazy loading via framework adapters for code splitting\n * - Static component registration via build plugins (no dynamic discovery)\n * - Design mode decoration via framework adapters\n * - Request deduplication for concurrent component loads\n * - Component metadata handled via API (not stored in registry)\n *\n * @template TProps - Component props type\n *\n * @example\n * ```tsx\n * const registry = new ComponentRegistry({\n * adapter: new ReactAdapter(),\n * designDecorator: createDesignDecorator,\n * });\n *\n * // Components are pre-registered via static registry plugin\n * // Get a component\n * const Hero = registry.getComponent('hero');\n *\n * // Preload for SSR\n * await registry.preload('hero');\n * ```\n */\nexport class ComponentRegistry<TProps, TFrameworkComponent = unknown> {\n private readonly registry = new Map<ComponentId, Entry<TProps, TFrameworkComponent>>();\n private readonly pending = new Map<ComponentId, Promise<Entry<TProps, TFrameworkComponent> | null>>();\n private readonly cancelled = new Set<ComponentId>();\n\n private readonly adapter: FrameworkAdapter<TProps, TFrameworkComponent>;\n\n constructor({ adapter }: ComponentRegistryOptions<TProps, TFrameworkComponent>) {\n this.adapter = adapter;\n }\n\n /**\n * Registers a component in the registry with the specified id.\n * If a component with the same id already exists, it will be overwritten.\n */\n registerComponent(id: ComponentId, component: TFrameworkComponent): void {\n const prev = this.registry.get(id) ?? ({ id, raw: null } as Entry<TProps, TFrameworkComponent>);\n this.registry.set(id, { ...prev, id, raw: component });\n }\n\n /**\n * Registers a dynamic importer for a component id. Useful if you don't want to rely on scanning.\n */\n registerImporter(\n id: ComponentId,\n importer: () => Promise<ComponentModule<TProps, TFrameworkComponent>>,\n loaderNames?: LoaderNames\n ): void {\n const prev = this.registry.get(id) ?? ({ id, raw: null } as Entry<TProps, TFrameworkComponent>);\n this.registry.set(id, { ...prev, id, import: importer, loaderNames });\n }\n\n /**\n * Retrieves a component by id. Returns a framework-specific component type.\n * In lazy loading scenarios, this will be a lazy component if the component\n * is discovered via dynamic import. In design mode, the returned component\n * is decorated via `designDecorator`.\n */\n getComponent(id: ComponentId): TFrameworkComponent | null {\n const e = this.ensureLocalEntry(id);\n if (!e) return null;\n\n const comp = e.raw ?? e.lazy ?? null;\n if (!comp) return null;\n\n return this.adapter.decorateComponent(comp);\n }\n\n /**\n * Preload the JS chunk for a component id (use in route loaders/SSR to avoid waterfalls).\n *\n * This method ensures the component module is loaded and cached. Concurrent calls\n * for the same component ID are automatically deduplicated via the pending map\n * in ensureDiscovered().\n *\n * @throws Error if the component cannot be discovered\n */\n async preload(id: ComponentId): Promise<void> {\n // Wait for discovery to finish (this.pending deduplicates concurrent calls)\n const e = await this.ensureDiscovered(id);\n\n // If we have a lazy or raw component, we're done.\n // The importer was already called during ensureDiscovered if needed.\n if (e?.lazy || e?.raw) {\n return;\n }\n\n // At this point discovery finished and we still don't have a component:\n // reject so the nearest ErrorBoundary can render an error state.\n throw new Error(`Component \"${id}\" could not be discovered (no importer, no raw/lazy).`);\n }\n\n /** Get loader function names for external invocation. */\n getLoaderNames(id: ComponentId): LoaderNames | undefined {\n return this.registry.get(id)?.loaderNames;\n }\n\n hasLoaders(id: ComponentId): boolean {\n return Object.values(this.registry.get(id)?.loaderNames || {}).filter(Boolean).length > 0;\n }\n\n /**\n * Call a loader function for a component externally.\n *\n * @param id - Component ID\n * @param loaderArgs - Arguments to pass to the loader function\n * @param loaderType - Type of loader to call ('loader' or 'clientLoader')\n * @returns Promise resolving to the loader result\n */\n async callLoader(id: ComponentId, loaderArgs: unknown, loaderType: keyof LoaderNames = 'loader'): Promise<unknown> {\n // Get loader names for the component\n const loaderNames = this.getLoaderNames(id);\n const loaderName = loaderNames?.[loaderType];\n\n if (!loaderName) {\n return Promise.resolve(undefined);\n }\n\n // Get the entry to access the import function\n const entry = this.registry.get(id);\n if (!entry?.import) {\n throw new Error(`No importer found for component: ${id}`);\n }\n\n try {\n // Import the module and get the loader function\n const module = await entry.import();\n const loaderFunction = module[loaderName];\n\n if (typeof loaderFunction !== 'function') {\n return undefined;\n }\n\n // Call the loader function with the provided arguments\n return await loaderFunction(loaderArgs);\n } catch (error) {\n throw new Error(`Failed to call ${loaderType} for component '${id}': ${(error as Error).message}`);\n }\n }\n\n /** Get fallback component if available. */\n getFallback(id: ComponentId): TFrameworkComponent | undefined {\n return this.registry.get(id)?.fallback;\n }\n\n /**\n * Returns all registered component IDs.\n * Useful for debugging and introspection.\n */\n getRegisteredIds(): ComponentId[] {\n return Array.from(this.registry.keys());\n }\n\n /**\n * Checks if a component is registered.\n */\n has(id: ComponentId): boolean {\n return this.registry.has(id);\n }\n\n /**\n * Clears all cached components and cancels pending discoveries.\n * In-flight async operations will be cancelled and their promises will reject.\n * Useful for testing or hot module replacement.\n */\n clear(): void {\n // Mark all pending discoveries as cancelled\n for (const id of this.pending.keys()) {\n this.cancelled.add(id);\n }\n\n this.registry.clear();\n this.pending.clear();\n }\n\n /* ==================== Private Methods ==================== */\n\n private ensureLocalEntry(id: ComponentId): Entry<TProps, TFrameworkComponent> | null {\n const cached = this.registry.get(id);\n if (cached) {\n return cached;\n }\n\n // Create a placeholder entry so concurrent calls coalesce.\n const placeholder: Entry<TProps, TFrameworkComponent> = { id, raw: null };\n this.registry.set(id, placeholder);\n\n // Kick off discovery in background; callers that need it awaited should call ensureDiscovered.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.ensureDiscovered(id);\n\n return placeholder;\n }\n\n /**\n * Ensures a component is discovered and cached.\n * Only returns early if a raw (eagerly loaded) component exists.\n * Otherwise, attempts to discover via registered importer.\n *\n * @throws Error if the discovery is cancelled via clear()\n */\n private async ensureDiscovered(id: ComponentId): Promise<Entry<TProps, TFrameworkComponent> | null> {\n const existing = this.registry.get(id);\n\n if (existing?.raw) return existing;\n\n if (this.pending.has(id)) {\n return this.pending.get(id) ?? null;\n }\n\n const work = (async () => {\n // Check if cancelled before starting work\n if (this.cancelled.has(id)) {\n this.cancelled.delete(id);\n throw new Error(`Component discovery for \"${id}\" was cancelled`);\n }\n\n // Handle explicit importer registered via static registry\n let entry = this.registry.get(id) ?? ({ id, raw: null } as Entry<TProps, TFrameworkComponent>);\n if (entry.import) {\n entry = await this.buildFromImporter(id, entry.import);\n\n // Check if cancelled after async operation\n if (this.cancelled.has(id)) {\n this.cancelled.delete(id);\n throw new Error(`Component discovery for \"${id}\" was cancelled`);\n }\n\n this.registry.set(id, entry);\n return entry;\n }\n\n // No fallback scanning needed - components are pre-registered via static registry\n return this.registry.get(id) ?? null;\n })();\n\n this.pending.set(id, work);\n try {\n const done = await work;\n this.pending.delete(id);\n return done;\n } catch (error) {\n this.pending.delete(id);\n throw error;\n }\n }\n\n private async buildFromImporter(\n id: ComponentId,\n importer: () => Promise<ComponentModule<TProps, TFrameworkComponent>>\n ): Promise<Entry<TProps, TFrameworkComponent>> {\n const mod = await importer();\n return this.buildFromLoadedModule(id, importer, mod);\n }\n\n private buildFromLoadedModule(\n id: ComponentId,\n importer: () => Promise<ComponentModule<TProps, TFrameworkComponent>>,\n mod: ComponentModule<TProps, TFrameworkComponent>\n ): Entry<TProps, TFrameworkComponent> {\n // Use adapter to create lazy component\n const lazyComp = this.adapter.createLazyComponent(importer);\n\n return {\n id,\n raw: null,\n lazy: lazyComp,\n import: importer,\n fallback: mod.fallback,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,208 @@
1
+ import { a as ShopperSearch, i as ShopperProducts, n as ShopperBasketsV2, t as ShopperBasketsV1 } from "./types.js";
2
+ import "openapi-fetch";
3
+
4
+ //#region src/events/types.d.ts
5
+
6
+ type BasketProductItem = ShopperBasketsV1.schemas['ProductItem'] | ShopperBasketsV2.schemas['ProductItem'];
7
+ type Basket = ShopperBasketsV1.schemas['Basket'] | ShopperBasketsV2.schemas['Basket'];
8
+ /**
9
+ * Interface for analytics user properties.
10
+ * This can be extended by external code via module augmentation without modifying the events module.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * declare module '@storefront-next/runtime/events' {
15
+ * interface AnalyticsUser {
16
+ * customField: string;
17
+ * preferences: Record<string, any>;
18
+ * loyaltyTier?: 'bronze' | 'silver' | 'gold';
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ interface AnalyticsUser {
24
+ userType: 'registered' | 'guest';
25
+ usid?: string;
26
+ sid?: string;
27
+ encUserId?: string;
28
+ customerId?: string;
29
+ customerNo?: string;
30
+ firstName?: string;
31
+ lastName?: string;
32
+ email?: string;
33
+ }
34
+ /**
35
+ * Placeholder type for future payload types.
36
+ * This allows the payload union to be extended in the future.
37
+ */
38
+ interface PayloadTbd {}
39
+ /**
40
+ * Union type for analytics event payloads.
41
+ * Currently includes AnalyticsUser, with PayloadTbd as a placeholder for future types.
42
+ */
43
+ type AnalyticsPayload = AnalyticsUser | PayloadTbd;
44
+ type BaseEvent = {
45
+ eventType: string;
46
+ payload: AnalyticsPayload;
47
+ deviceInfo?: string;
48
+ };
49
+ interface ViewPageEvent extends BaseEvent {
50
+ eventType: 'view_page';
51
+ path: string;
52
+ }
53
+ interface ViewProductEvent extends BaseEvent {
54
+ eventType: 'view_product';
55
+ product: ShopperProducts.schemas['Product'];
56
+ }
57
+ interface ViewSearchEvent extends BaseEvent {
58
+ eventType: 'view_search';
59
+ searchInputText: string;
60
+ searchResults: ShopperSearch.schemas['ProductSearchHit'][];
61
+ sort: string;
62
+ refinements: ShopperSearch.schemas['ProductSearchResult']['selectedRefinements'];
63
+ }
64
+ interface ViewCategoryEvent extends BaseEvent {
65
+ eventType: 'view_category';
66
+ category: ShopperProducts.schemas['Category'];
67
+ searchResults: ShopperSearch.schemas['ProductSearchHit'][];
68
+ sort: string;
69
+ refinements: ShopperSearch.schemas['ProductSearchResult']['selectedRefinements'];
70
+ }
71
+ interface ViewRecommenderEvent extends BaseEvent {
72
+ eventType: 'view_recommender';
73
+ recommenderId: string;
74
+ recommenderName: string;
75
+ products: ShopperSearch.schemas['ProductSearchHit'][];
76
+ }
77
+ interface ClickProductInCategoryEvent extends BaseEvent {
78
+ eventType: 'click_product_in_category';
79
+ category: ShopperProducts.schemas['Category'];
80
+ product: ShopperSearch.schemas['ProductSearchHit'];
81
+ }
82
+ interface ClickProductInSearchEvent extends BaseEvent {
83
+ eventType: 'click_product_in_search';
84
+ searchInputText: string;
85
+ product: ShopperSearch.schemas['ProductSearchHit'];
86
+ }
87
+ interface ClickProductInRecommenderEvent extends BaseEvent {
88
+ eventType: 'click_product_in_recommender';
89
+ recommenderId: string;
90
+ recommenderName: string;
91
+ product: ShopperSearch.schemas['ProductSearchHit'];
92
+ }
93
+ interface CartItemAddEvent extends BaseEvent {
94
+ eventType: 'cart_item_add';
95
+ cartItems: Array<BasketProductItem>;
96
+ }
97
+ interface CheckoutStartEvent extends BaseEvent {
98
+ eventType: 'checkout_start';
99
+ basket: Basket;
100
+ }
101
+ interface CheckoutStepEvent extends BaseEvent {
102
+ eventType: 'checkout_step';
103
+ stepName: string;
104
+ stepNumber: number;
105
+ basket: Basket;
106
+ }
107
+ interface ViewSearchSuggestionEvent extends BaseEvent {
108
+ eventType: 'view_search_suggestion';
109
+ searchInputText: string;
110
+ suggestions: Array<string>;
111
+ }
112
+ interface ClickSearchSuggestionEvent extends BaseEvent {
113
+ eventType: 'click_search_suggestion';
114
+ searchInputText: string;
115
+ suggestion: string;
116
+ }
117
+ /**
118
+ * Interface for custom analytics events.
119
+ * Extend this interface via module augmentation.
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * declare module '@storefront-next/runtime/events' {
124
+ * interface AnalyticsEventExtensions {
125
+ * CustomEvent: BaseEvent & { eventType: 'custom'; data: string };
126
+ * }
127
+ * }
128
+ * ```
129
+ */
130
+ interface AnalyticsEventExtensions {}
131
+ /**
132
+ * Union type for all analytics events.
133
+ *
134
+ * Custom types can be added by extending the AnalyticsEventExtensions interface.
135
+ */
136
+ type AnalyticsEvent = ViewPageEvent | ViewProductEvent | ViewSearchEvent | ViewCategoryEvent | ViewRecommenderEvent | ClickProductInCategoryEvent | ClickProductInSearchEvent | ClickProductInRecommenderEvent | CartItemAddEvent | CheckoutStartEvent | CheckoutStepEvent | ViewSearchSuggestionEvent | ClickSearchSuggestionEvent | AnalyticsEventExtensions[keyof AnalyticsEventExtensions];
137
+ /**
138
+ * Helper type for mapping event_type to the corresponding event type.
139
+ */
140
+ type EventTypeMap = { [K in AnalyticsEvent as K['eventType']]: K };
141
+ /**
142
+ * Helper type for extracting event payload data for a given event type.
143
+ */
144
+ type EventPayload<T extends AnalyticsEvent['eventType']> = Omit<EventTypeMap[T], 'eventType' | 'payload'> & {
145
+ payload: AnalyticsPayload;
146
+ };
147
+ /**
148
+ * Minimal interface for engagement adapters that can send analytics events.
149
+ * Engagemet Adapters must implement this interface to work with the event mediator.
150
+ */
151
+ interface EventAdapter {
152
+ name: string;
153
+ sendEvent?: (event: AnalyticsEvent) => Promise<unknown>;
154
+ }
155
+ /**
156
+ * Generic event mediator interface for tracking events.
157
+ * This can be used for analytics, telemetry, or any other event tracking system.
158
+ */
159
+ type EventMediator = {
160
+ track: (event: AnalyticsEvent) => void;
161
+ };
162
+ //#endregion
163
+ //#region src/events/events.d.ts
164
+ /**
165
+ * Type-safe event creation function
166
+ *
167
+ * This generic function allows creating any event type under AnalyticsEvent
168
+ * with full type safety. The event type is inferred from the string literal
169
+ * passed as the first parameter, and TypeScript will enforce the correct
170
+ * data properties for that specific event type.
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * const viewPageEvent = createEvent('view_page', { path: '/products', payload });
175
+ * const viewProductEvent = createEvent('view_product', { product, payload });
176
+ * ```
177
+ */
178
+ declare function createEvent<T extends AnalyticsEvent['eventType']>(eventType: T, data: EventPayload<T>): EventTypeMap[T];
179
+ /**
180
+ * Send a view page event to the event mediator
181
+ *
182
+ * This wrapper function is used in the automated page view event tracking client middleware.
183
+ * This function exists to support build-time checks and type safety.
184
+ *
185
+ * @param event - The view page event to send
186
+ * @param eventMediator - The event mediator to send the event to
187
+ */
188
+ declare function sendViewPageEvent(event: ViewPageEvent, eventMediator: EventMediator): void;
189
+ //#endregion
190
+ //#region src/events/mediator.d.ts
191
+ /**
192
+ * Get the event mediator singleton instance
193
+ *
194
+ * Returns the singleton EventMediator instance, creating it if it doesn't exist.
195
+ *
196
+ * @param getAdapters - Function that returns the current array of engagement adapters.
197
+ * @returns EventMediator instance (singleton) or undefined if not on client side
198
+ */
199
+ declare function getEventMediator(getAdapters: () => EventAdapter[]): EventMediator | undefined;
200
+ /**
201
+ * Reset the event mediator singleton (for testing only)
202
+ *
203
+ * This function clears the singleton instance, allowing tests to create a fresh mediator.
204
+ */
205
+ declare function resetEventMediator(): void;
206
+ //#endregion
207
+ export { AnalyticsEvent, AnalyticsEventExtensions, AnalyticsPayload, AnalyticsUser, BaseEvent, CartItemAddEvent, CheckoutStartEvent, CheckoutStepEvent, ClickProductInCategoryEvent, ClickProductInRecommenderEvent, ClickProductInSearchEvent, ClickSearchSuggestionEvent, EventAdapter, EventMediator, EventPayload, EventTypeMap, PayloadTbd, ViewCategoryEvent, ViewPageEvent, ViewProductEvent, ViewRecommenderEvent, ViewSearchEvent, ViewSearchSuggestionEvent, createEvent, getEventMediator, resetEventMediator, sendViewPageEvent };
208
+ //# sourceMappingURL=events.d.ts.map