@memberjunction/core-entities 5.44.0 → 5.45.1

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.
@@ -0,0 +1,180 @@
1
+ import { BaseEngine, } from '@memberjunction/core';
2
+ import { NormalizeUUID, UUIDsEqual } from '@memberjunction/global';
3
+ /** Client-side cap applied by the {@link UserRoutineEngine.RecentRuns} getter. */
4
+ const RECENT_RUNS_CAP = 500;
5
+ /**
6
+ * UserRoutineEngine provides centralized, cached access to the current user's
7
+ * User Routines ("MJ: User Routines"), their notification recipients, and their
8
+ * recent runs. It is the single data source the User Routines UI widgets consume —
9
+ * components should NOT issue ad-hoc RunViews for data this engine already caches.
10
+ *
11
+ * Unlike {@link UserInfoEngine} this engine is NOT auto-configured at startup —
12
+ * routines are a lazily-entered surface, so every consumer calls
13
+ * `await UserRoutineEngine.Instance.Config(false, user, provider)` on entry
14
+ * (a no-op once loaded).
15
+ *
16
+ * All three configs load UNFILTERED (client and server alike) and the public
17
+ * getters scope to the loaded user. Unfiltered + unordered configs are the
18
+ * canonical BaseEngine shape: the engine maintains the arrays IN PLACE on
19
+ * save / delete / remote-invalidate entity events — no filter snapshot to go
20
+ * stale after creating or deleting a routine, and no full-refresh churn.
21
+ * Row-level visibility is the server's job (entity permissions / RLS), not a
22
+ * client-side Filter's.
23
+ *
24
+ * Usage:
25
+ * ```typescript
26
+ * const engine = UserRoutineEngine.Instance;
27
+ * await engine.Config(false, currentUser, provider);
28
+ * const routines = engine.Routines; // current user's routines
29
+ * const runs = engine.RunsForRoutine(routineId); // most recent first
30
+ * engine.Routines$.subscribe(r => ...); // reactive updates
31
+ * ```
32
+ */
33
+ export class UserRoutineEngine extends BaseEngine {
34
+ constructor() {
35
+ super(...arguments);
36
+ // Private storage for entity data (populated by BaseEngine.Load via Configs)
37
+ this._Routines = [];
38
+ this._Recipients = [];
39
+ this._Runs = [];
40
+ // Track the user ID we loaded data for
41
+ this._loadedForUserId = null;
42
+ }
43
+ /**
44
+ * Returns the global instance of the class. This is a singleton class, so there is
45
+ * only one instance of it in the application. Do not directly create new instances
46
+ * of it, always use this method (or `GetProviderInstance` for non-default providers).
47
+ */
48
+ static get Instance() {
49
+ return super.getInstance();
50
+ }
51
+ /**
52
+ * Configures the engine by loading the current user's routines, recipients, and runs.
53
+ *
54
+ * @param forceRefresh - If true, forces a refresh from the server even if data is cached
55
+ * @param contextUser - The user context (required for server-side, auto-detected on client)
56
+ * @param provider - Optional custom metadata provider
57
+ */
58
+ async Config(forceRefresh, contextUser, provider) {
59
+ const md = provider ?? this.ProviderToUse;
60
+ const userId = contextUser?.ID || md.CurrentUser?.ID;
61
+ if (!userId) {
62
+ console.warn('UserRoutineEngine: No user context available, skipping configuration');
63
+ return;
64
+ }
65
+ // Unfiltered + unordered on purpose: this is the canonical BaseEngine shape,
66
+ // so the engine maintains all three arrays in place on entity events (no
67
+ // stale filter snapshots, no full-refresh churn). The cache is user-agnostic;
68
+ // getters scope per user, so a user change needs no reload.
69
+ const configs = [
70
+ {
71
+ Type: 'entity',
72
+ EntityName: 'MJ: User Routines',
73
+ PropertyName: '_Routines',
74
+ CacheLocal: true,
75
+ },
76
+ {
77
+ Type: 'entity',
78
+ EntityName: 'MJ: User Routine Recipients',
79
+ PropertyName: '_Recipients',
80
+ CacheLocal: true,
81
+ },
82
+ {
83
+ Type: 'entity',
84
+ EntityName: 'MJ: User Routine Runs',
85
+ PropertyName: '_Runs',
86
+ CacheLocal: true,
87
+ },
88
+ ];
89
+ await super.Load(configs, provider, forceRefresh, contextUser);
90
+ this._loadedForUserId = userId;
91
+ }
92
+ // ========================================================================
93
+ // OBSERVABLE ACCESSORS
94
+ // ========================================================================
95
+ /**
96
+ * Observable stream of the routines cache. Emits the current (raw/unfiltered) array on
97
+ * subscribe and re-emits on save / delete / remote-invalidate / refresh. Consumers that
98
+ * need the per-user view should read the {@link Routines} getter inside the subscription.
99
+ */
100
+ get Routines$() {
101
+ return this.ObserveProperty('_Routines');
102
+ }
103
+ /** Observable stream of the recipients cache (raw). */
104
+ get Recipients$() {
105
+ return this.ObserveProperty('_Recipients');
106
+ }
107
+ /** Observable stream of the runs cache (raw). */
108
+ get Runs$() {
109
+ return this.ObserveProperty('_Runs');
110
+ }
111
+ // ========================================================================
112
+ // PUBLIC ACCESSORS (per-user filtered)
113
+ // ========================================================================
114
+ /** The loaded user's routines, sorted by name. */
115
+ get Routines() {
116
+ if (!this._loadedForUserId)
117
+ return [];
118
+ return this.GetConfigData('_Routines')
119
+ .filter((r) => UUIDsEqual(r.UserID, this._loadedForUserId))
120
+ .sort((a, b) => (a.Name || '').localeCompare(b.Name || ''));
121
+ }
122
+ /** All recipients across the loaded user's routines. */
123
+ get Recipients() {
124
+ const routineIds = this.loadedRoutineIdSet();
125
+ return this.GetConfigData('_Recipients').filter((r) => routineIds.has(NormalizeUUID(r.RoutineID)));
126
+ }
127
+ /**
128
+ * Recent runs across the loaded user's routines, most recent first (capped at
129
+ * {@link RECENT_RUNS_CAP} rows client-side).
130
+ */
131
+ get RecentRuns() {
132
+ const routineIds = this.loadedRoutineIdSet();
133
+ return this.GetConfigData('_Runs')
134
+ .filter((r) => routineIds.has(NormalizeUUID(r.RoutineID)))
135
+ .sort((a, b) => new Date(b.StartedAt).getTime() - new Date(a.StartedAt).getTime())
136
+ .slice(0, RECENT_RUNS_CAP);
137
+ }
138
+ // ========================================================================
139
+ // CONVENIENCE METHODS
140
+ // ========================================================================
141
+ /** A routine by ID (loaded user's routines only). */
142
+ GetRoutineByID(routineId) {
143
+ return this.Routines.find((r) => UUIDsEqual(r.ID, routineId));
144
+ }
145
+ /** Runs for one routine, most recent first. */
146
+ RunsForRoutine(routineId, maxRuns) {
147
+ const runs = this.GetConfigData('_Runs')
148
+ .filter((r) => UUIDsEqual(r.RoutineID, routineId))
149
+ .sort((a, b) => new Date(b.StartedAt).getTime() - new Date(a.StartedAt).getTime());
150
+ return maxRuns != null && maxRuns > 0 ? runs.slice(0, maxRuns) : runs;
151
+ }
152
+ /** Recipients for one routine, in Sequence order. */
153
+ RecipientsForRoutine(routineId) {
154
+ return this.GetConfigData('_Recipients')
155
+ .filter((r) => UUIDsEqual(r.RoutineID, routineId))
156
+ .sort((a, b) => a.Sequence - b.Sequence);
157
+ }
158
+ /** Routines for an arbitrary user (server-side / admin scenarios; raw cache scan). */
159
+ GetRoutinesForUser(userId) {
160
+ return (this._Routines || [])
161
+ .filter((r) => UUIDsEqual(r.UserID, userId))
162
+ .sort((a, b) => (a.Name || '').localeCompare(b.Name || ''));
163
+ }
164
+ /** The user ID this engine was loaded for. */
165
+ get LoadedForUserId() {
166
+ return this._loadedForUserId;
167
+ }
168
+ /**
169
+ * Force-refreshes all cached data from the server. Rarely needed — the unfiltered
170
+ * configs are maintained in place on entity events — but useful as an explicit
171
+ * "pull latest" (e.g. the UI's refresh button picking up dispatcher-written runs).
172
+ */
173
+ async Refresh(contextUser) {
174
+ await this.Config(true, contextUser);
175
+ }
176
+ loadedRoutineIdSet() {
177
+ return new Set(this.Routines.map((r) => NormalizeUUID(r.ID)));
178
+ }
179
+ }
180
+ //# sourceMappingURL=UserRoutineEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserRoutineEngine.js","sourceRoot":"","sources":["../../src/engines/UserRoutineEngine.ts"],"names":[],"mappings":"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"}