@memberjunction/react-runtime 5.40.2 → 5.42.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/.turbo/turbo-build.log +14 -14
- package/CHANGELOG.md +64 -0
- package/dist/324.runtime.umd.js +228 -148
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime.umd.js +7879 -3926
- package/dist/utilities/cache-manager.d.ts +2 -1
- package/dist/utilities/cache-manager.d.ts.map +1 -1
- package/dist/utilities/cache-manager.js +22 -3
- package/dist/utilities/cache-manager.js.map +1 -1
- package/dist/utilities/user-state.d.ts +7 -0
- package/dist/utilities/user-state.d.ts.map +1 -0
- package/dist/utilities/user-state.js +48 -0
- package/dist/utilities/user-state.js.map +1 -0
- package/package.json +6 -6
- package/src/__tests__/cache-manager.test.ts +64 -0
- package/src/__tests__/user-state.test.ts +155 -0
- package/src/index.ts +9 -0
- package/src/utilities/cache-manager.ts +28 -6
- package/src/utilities/user-state.ts +129 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helpers for the per-user interactive-component settings contract
|
|
3
|
+
* (`savedUserSettings` in / `onSaveUserSettings` out).
|
|
4
|
+
*
|
|
5
|
+
* These helpers are intentionally **pure and framework-agnostic** so the Angular
|
|
6
|
+
* host bridge and the Node test harness share one implementation and the scoping /
|
|
7
|
+
* serialization logic can be unit tested in isolation. The host wires these to a
|
|
8
|
+
* durable per-user store (e.g. `UserInfoEngine`); the runtime itself never persists.
|
|
9
|
+
*
|
|
10
|
+
* @module @memberjunction/react-runtime
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Prefix applied to every interactive-component user-settings storage key. The
|
|
15
|
+
* full key is `InteractiveComponents_UserState_Root/<scope>`, where `<scope>` is
|
|
16
|
+
* resolved by {@link resolveUserStateScope}. It is deliberately long and unique
|
|
17
|
+
* to avoid collisions with other namespaced keys in the shared
|
|
18
|
+
* `MJ: User Settings` keyspace. Keeping the prefix in one place also avoids
|
|
19
|
+
* stringly-typed drift between the seed (read) and persist (write) paths.
|
|
20
|
+
*/
|
|
21
|
+
export const USER_STATE_KEY_PREFIX = 'InteractiveComponents_UserState_Root/';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the stable per-component scope used to namespace a component's
|
|
25
|
+
* persisted user settings.
|
|
26
|
+
*
|
|
27
|
+
* An explicit, host-supplied scope always wins (used when a host needs to
|
|
28
|
+
* differentiate two instances of the same component spec — e.g. scope a form's
|
|
29
|
+
* settings by the entity it edits). Otherwise the scope is derived from the
|
|
30
|
+
* component spec's `namespace` + `name`.
|
|
31
|
+
*
|
|
32
|
+
* The result is lowercased to avoid case-variant duplicate rows in the settings
|
|
33
|
+
* store (per MJ's user-settings key convention).
|
|
34
|
+
*
|
|
35
|
+
* @returns the resolved scope, or `null` when no stable scope can be derived
|
|
36
|
+
* (e.g. an unnamed component) — signaling the caller to skip persistence.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveUserStateScope(
|
|
39
|
+
explicitScope: string | undefined | null,
|
|
40
|
+
namespace: string | undefined | null,
|
|
41
|
+
name: string | undefined | null
|
|
42
|
+
): string | null {
|
|
43
|
+
const explicit = explicitScope?.trim();
|
|
44
|
+
if (explicit) {
|
|
45
|
+
return explicit.toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
const cleanName = name?.trim();
|
|
48
|
+
if (!cleanName) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const cleanNamespace = namespace?.trim();
|
|
52
|
+
const scope = cleanNamespace ? `${cleanNamespace}/${cleanName}` : cleanName;
|
|
53
|
+
return scope.toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the full storage key for a resolved scope, or `null` when the scope is
|
|
58
|
+
* `null` (persistence should be skipped).
|
|
59
|
+
*/
|
|
60
|
+
export function userStateStorageKey(scope: string | null): string | null {
|
|
61
|
+
return scope ? `${USER_STATE_KEY_PREFIX}${scope}` : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Safely parse a stored settings blob into a plain object. Returns `{}` for
|
|
66
|
+
* null/empty input, invalid JSON, or any non-object JSON (arrays, primitives) —
|
|
67
|
+
* persisted user settings are always a flat key/value object.
|
|
68
|
+
*/
|
|
69
|
+
export function parseStoredUserSettings(raw: string | undefined | null): Record<string, unknown> {
|
|
70
|
+
if (!raw) {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(raw);
|
|
75
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
76
|
+
return parsed as Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
return {};
|
|
79
|
+
} catch {
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Merge host-provided defaults with the stored per-user settings. Stored values
|
|
86
|
+
* win, so a returning user sees their saved preferences while a host can still
|
|
87
|
+
* seed sensible first-run defaults that fill any gaps.
|
|
88
|
+
*/
|
|
89
|
+
export function mergeUserSettings(
|
|
90
|
+
hostDefaults: Record<string, unknown> | undefined | null,
|
|
91
|
+
stored: Record<string, unknown> | undefined | null
|
|
92
|
+
): Record<string, unknown> {
|
|
93
|
+
return { ...(hostDefaults ?? {}), ...(stored ?? {}) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Apply an `onSaveUserSettings` payload to the host's current settings snapshot.
|
|
98
|
+
*
|
|
99
|
+
* **Merge, not replace.** The contract asks components to pass the complete
|
|
100
|
+
* settings object, but the host must be resilient to a component (especially an
|
|
101
|
+
* AI-generated one) passing only the changed keys — and to the stale-prop case
|
|
102
|
+
* where a component spreads the mount-time `savedUserSettings` prop, which the
|
|
103
|
+
* host deliberately never refreshes mid-session (no re-render on save). Under
|
|
104
|
+
* full-replace semantics either slip silently wipes every other saved
|
|
105
|
+
* preference; under merge the worst case is a no-op.
|
|
106
|
+
*
|
|
107
|
+
* **Removing a key requires explicit intent**: set its value to `null` and the
|
|
108
|
+
* key is deleted from the snapshot (reads fall back to defaults via the
|
|
109
|
+
* documented `savedUserSettings?.key ?? fallback` pattern). `undefined` values
|
|
110
|
+
* are treated the same way, since `JSON.stringify` would drop them from the
|
|
111
|
+
* persisted blob anyway and the in-memory snapshot must stay consistent with
|
|
112
|
+
* what is stored.
|
|
113
|
+
*
|
|
114
|
+
* @returns a new object — neither input is mutated.
|
|
115
|
+
*/
|
|
116
|
+
export function applyUserSettingsUpdate(
|
|
117
|
+
current: Record<string, unknown> | undefined | null,
|
|
118
|
+
incoming: Record<string, unknown> | undefined | null
|
|
119
|
+
): Record<string, unknown> {
|
|
120
|
+
const next: Record<string, unknown> = { ...(current ?? {}) };
|
|
121
|
+
for (const [key, value] of Object.entries(incoming ?? {})) {
|
|
122
|
+
if (value === null || value === undefined) {
|
|
123
|
+
delete next[key];
|
|
124
|
+
} else {
|
|
125
|
+
next[key] = value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return next;
|
|
129
|
+
}
|