@hkdigital/lib-core 0.5.57 → 0.5.59

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.
@@ -264,7 +264,7 @@ export default class Logger extends EventEmitter {
264
264
  ...this.#defaultContext
265
265
  };
266
266
 
267
- const childName = `${this.name}>${name}`;
267
+ const childName = `${this.name}:${name}`;
268
268
 
269
269
  const child = new Logger(childName, level ?? this.level, context);
270
270
 
@@ -5,124 +5,113 @@ export default class PageMachine {
5
5
  * @param {Object} config - Configuration object
6
6
  * @param {string} config.startPath
7
7
  * Start path for this route group (e.g., '/game/play')
8
- * @param {Record<string, string>} [config.routeMap={}]
9
- * Map of states to route paths
8
+ * @param {string[]} [config.routes=[]]
9
+ * Optional list of valid routes (for validation/dev tools)
10
10
  * @param {Record<string, any>} [config.initialData={}]
11
- * Initial data properties (from server)
12
- * @param {Record<string, Function>} [config.onEnterHooks={}]
13
- * Map of states to onEnter hook functions
11
+ * Initial data properties (use KEY_ constants for keys)
14
12
  * @param {import('../../../logging/client.js').Logger} [config.logger]
15
- * Logger instance (optional, if not provided no logging occurs)
13
+ * Logger instance (optional)
16
14
  *
17
15
  * @example
18
16
  * ```javascript
17
+ * const ROUTE_INTRO = '/intro/start';
18
+ * const KEY_INTRO_COMPLETED = 'intro-completed';
19
+ * const KEY_SCORE = 'score';
20
+ *
19
21
  * const machine = new PageMachine({
20
- * startPath: '/intro/start',
21
- * routeMap: {
22
- * [STATE_START]: '/intro/start',
23
- * [STATE_ANIMATE]: '/intro/animate'
24
- * },
22
+ * startPath: ROUTE_INTRO,
23
+ * routes: [ROUTE_INTRO, '/intro/profile'],
25
24
  * initialData: {
26
- * INTRO_COMPLETED: false
27
- * },
28
- * onEnterHooks: {
29
- * [STATE_ANIMATE]: (done) => {
30
- * setTimeout(() => done(STATE_START), 1000);
31
- * return {
32
- * abort: () => clearTimeout(...),
33
- * complete: () => done(STATE_START)
34
- * };
35
- * }
25
+ * [KEY_INTRO_COMPLETED]: false,
26
+ * [KEY_SCORE]: 0
36
27
  * }
37
28
  * });
38
29
  * ```
39
30
  */
40
- constructor({ startPath, routeMap, initialData, onEnterHooks, logger }: {
31
+ constructor({ startPath, routes, initialData, logger }: {
41
32
  startPath: string;
42
- routeMap?: Record<string, string> | undefined;
33
+ routes?: string[] | undefined;
43
34
  initialData?: Record<string, any> | undefined;
44
- onEnterHooks?: Record<string, Function> | undefined;
45
35
  logger?: import("../../../logging/client.js").Logger | undefined;
46
36
  });
47
37
  /**
48
- * Logger instance for state machine
38
+ * Logger instance
49
39
  * @type {import('../../../logging/client.js').Logger}
50
40
  */
51
41
  logger: import("../../../logging/client.js").Logger;
52
42
  /**
53
- * Synchronize machine state with URL path
43
+ * Synchronize machine with URL path
54
44
  *
55
- * Call this in a $effect that watches $page.url.pathname
56
- * Automatically tracks visited states and triggers onEnter hooks
45
+ * Call this in a $effect that watches $page.url.pathname.
46
+ * Automatically tracks visited routes.
57
47
  *
58
48
  * @param {string} currentPath - Current URL pathname
59
49
  *
60
- * @returns {boolean} True if state was changed
50
+ * @returns {boolean} True if route was changed
61
51
  */
62
52
  syncFromPath(currentPath: string): boolean;
63
53
  /**
64
- * Get route path for a given state
65
- *
66
- * @param {string} state - State name
67
- *
68
- * @returns {string} Route path or null if no mapping
69
- */
70
- getPathForState(state: string): string;
71
- /**
72
- * Navigate to the route path for a given state
73
- *
74
- * @param {string} state - State name
75
- */
76
- navigateToState(state: string): void;
77
- /**
78
- * Get route path for current state
79
- *
80
- * @returns {string|null} Route path or null if no mapping
81
- */
82
- getCurrentPath(): string | null;
83
- /**
84
- * Get current state
54
+ * Get current route
85
55
  *
86
- * @returns {string} Current state name
56
+ * @returns {string} Current route path
87
57
  */
88
58
  get current(): string;
89
59
  /**
90
- * Get the route map
60
+ * Get the routes list
91
61
  *
92
- * @returns {Record<string, string>} Copy of route map
62
+ * @returns {string[]} Copy of routes list
93
63
  */
94
- get routeMap(): Record<string, string>;
64
+ get routes(): string[];
95
65
  /**
96
66
  * Set a data property value
97
67
  *
98
- * @param {string} key - Property key
68
+ * Automatically reactive - effects watching this key will re-run.
69
+ * Uses fine-grained reactivity, so only effects watching this specific
70
+ * key will be triggered.
71
+ *
72
+ * @param {string} key - Property key (use KEY_ constant)
99
73
  * @param {any} value - Property value
100
74
  *
101
75
  * @example
102
76
  * ```javascript
103
- * machine.setData('HAS_STRONG_PROFILE', true);
104
- * machine.setData('PROFILE_SCORE', 85);
77
+ * const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
78
+ * const KEY_PROFILE_SCORE = 'profile-score';
79
+ *
80
+ * machine.setData(KEY_HAS_STRONG_PROFILE, true);
81
+ * machine.setData(KEY_PROFILE_SCORE, 85);
105
82
  * ```
106
83
  */
107
84
  setData(key: string, value: any): void;
108
85
  /**
109
86
  * Get a data property value
110
87
  *
111
- * @param {string} key - Property key
88
+ * Automatically reactive - creates a dependency on this specific key.
89
+ * The effect will only re-run when THIS key changes, not when other
90
+ * keys change.
91
+ *
92
+ * @param {string} key - Property key (use KEY_ constant)
112
93
  *
113
94
  * @returns {any} Property value or undefined
114
95
  *
115
96
  * @example
116
97
  * ```javascript
117
- * const hasProfile = machine.getData('HAS_STRONG_PROFILE');
118
- * const score = machine.getData('PROFILE_SCORE');
98
+ * const KEY_SCORE = 'score';
99
+ *
100
+ * // Reactive - re-runs only when KEY_SCORE changes
101
+ * $effect(() => {
102
+ * const score = machine.getData(KEY_SCORE);
103
+ * console.log('Score:', score);
104
+ * });
119
105
  * ```
120
106
  */
121
107
  getData(key: string): any;
122
108
  /**
123
- * Get all data properties
109
+ * Get all data properties as plain object
110
+ *
111
+ * Note: This returns a snapshot (plain object), not a reactive map.
112
+ * Use this for serialization or server sync, not for reactive tracking.
124
113
  *
125
- * @returns {Record<string, any>} Copy of all data
114
+ * @returns {Record<string, any>} Plain object with all data
126
115
  *
127
116
  * @example
128
117
  * ```javascript
@@ -134,154 +123,173 @@ export default class PageMachine {
134
123
  /**
135
124
  * Update multiple data properties at once
136
125
  *
137
- * @param {Record<string, any>} dataUpdates - Object with key-value pairs
126
+ * Each property update triggers fine-grained reactivity.
127
+ *
128
+ * @param {Record<string, any>} dataUpdates
129
+ * Object with key-value pairs (use KEY_ constants for keys)
138
130
  *
139
131
  * @example
140
132
  * ```javascript
133
+ * const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
134
+ * const KEY_PROFILE_SCORE = 'profile-score';
135
+ * const KEY_MATCHED_SECTOR = 'matched-sector';
136
+ *
141
137
  * machine.updateData({
142
- * HAS_STRONG_PROFILE: true,
143
- * PROFILE_SCORE: 85,
144
- * MATCHED_SECTOR: 'technology'
138
+ * [KEY_HAS_STRONG_PROFILE]: true,
139
+ * [KEY_PROFILE_SCORE]: 85,
140
+ * [KEY_MATCHED_SECTOR]: 'technology'
145
141
  * });
146
142
  * ```
147
143
  */
148
144
  updateData(dataUpdates: Record<string, any>): void;
149
145
  /**
150
- * Check if a state has been visited
146
+ * Delete a data property
151
147
  *
152
- * @param {string} state - State name to check
148
+ * @param {string} key - Property key to delete (use KEY_ constant)
153
149
  *
154
- * @returns {boolean} True if the state has been visited
150
+ * @returns {boolean} True if the key existed and was deleted
155
151
  *
156
152
  * @example
157
153
  * ```javascript
158
- * if (machine.hasVisited(STATE_PROFILE)) {
159
- * // User has seen profile page, skip intro
160
- * }
154
+ * const KEY_TEMPORARY_FLAG = 'temporary-flag';
155
+ *
156
+ * machine.deleteData(KEY_TEMPORARY_FLAG);
161
157
  * ```
162
158
  */
163
- hasVisited(state: string): boolean;
159
+ deleteData(key: string): boolean;
164
160
  /**
165
- * Check if the start state has been visited
161
+ * Check if data property exists
162
+ *
163
+ * @param {string} key - Property key to check (use KEY_ constant)
166
164
  *
167
- * @returns {boolean} True if the start state has been visited
165
+ * @returns {boolean} True if the key exists
168
166
  *
169
167
  * @example
170
168
  * ```javascript
171
- * if (machine.hasVisitedStart) {
172
- * // User has been to the start page
169
+ * const KEY_TUTORIAL_SEEN = 'tutorial-seen';
170
+ *
171
+ * if (machine.hasData(KEY_TUTORIAL_SEEN)) {
172
+ * // Skip tutorial
173
173
  * }
174
174
  * ```
175
175
  */
176
- get hasVisitedStart(): boolean;
176
+ hasData(key: string): boolean;
177
177
  /**
178
- * Get all visited states
178
+ * Clear all data properties
179
179
  *
180
- * @returns {string[]} Array of visited state names
181
- */
182
- getVisitedStates(): string[];
183
- /**
184
- * Reset visited states tracking
185
- * Useful for testing or resetting experience
180
+ * @example
181
+ * ```javascript
182
+ * machine.clearData(); // Reset all game data
183
+ * ```
186
184
  */
187
- resetVisitedStates(): void;
185
+ clearData(): void;
188
186
  /**
189
- * Get the start path
187
+ * Get number of data properties
190
188
  *
191
- * @returns {string} Start path
189
+ * @returns {number} Number of data entries
192
190
  */
193
- get startPath(): string;
191
+ get dataSize(): number;
194
192
  /**
195
- * Get the start state
193
+ * Check if a route has been visited
196
194
  *
197
- * @returns {string} Start state name
198
- */
199
- get startState(): string;
200
- /**
201
- * Check if the supplied path matches the start path
195
+ * Automatically reactive - creates a dependency on the visited routes set.
202
196
  *
203
- * @param {string} path - Path to check
197
+ * @param {string} route - Route path to check
204
198
  *
205
- * @returns {boolean} True if path matches start path
199
+ * @returns {boolean} True if the route has been visited
206
200
  *
207
201
  * @example
208
202
  * ```javascript
209
- * if (machine.isStartPath('/game/play')) {
210
- * // User is on the start page
211
- * }
203
+ * // Reactive - re-runs when visited routes change
204
+ * $effect(() => {
205
+ * if (machine.hasVisited('/intro/profile')) {
206
+ * console.log('User has seen profile page');
207
+ * }
208
+ * });
212
209
  * ```
213
210
  */
214
- isStartPath(path: string): boolean;
211
+ hasVisited(route: string): boolean;
215
212
  /**
216
- * Check if currently on the start state
213
+ * Check if the start route has been visited
217
214
  *
218
- * @returns {boolean} True if current state is the start state
215
+ * @returns {boolean} True if the start route has been visited
219
216
  *
220
217
  * @example
221
218
  * ```javascript
222
- * if (machine.isOnStartState) {
223
- * // Show onboarding
219
+ * if (machine.hasVisitedStart) {
220
+ * // User has been to the start page
224
221
  * }
225
222
  * ```
226
223
  */
227
- get isOnStartState(): boolean;
224
+ get hasVisitedStart(): boolean;
228
225
  /**
229
- * Navigate to the start path
226
+ * Get all visited routes as array
230
227
  *
231
- * @example
232
- * ```javascript
233
- * // Redirect user to start
234
- * machine.redirectToStartPath();
235
- * ```
228
+ * Note: Returns a snapshot (plain array), not reactive.
229
+ *
230
+ * @returns {string[]} Array of visited route paths
236
231
  */
237
- redirectToStartPath(): void;
232
+ getVisitedRoutes(): string[];
238
233
  /**
239
- * Abort current state's transitions
240
- * Cancels animations/operations immediately (incomplete state)
234
+ * Reset visited routes tracking
235
+ *
236
+ * Clears all visited routes and marks only the current route as visited.
241
237
  *
242
238
  * @example
243
239
  * ```javascript
244
- * // User clicks "Cancel" button
245
- * machine.abortTransitions();
240
+ * machine.resetVisitedRoutes(); // Reset progress tracking
246
241
  * ```
247
242
  */
248
- abortTransitions(): void;
243
+ resetVisitedRoutes(): void;
244
+ /**
245
+ * Get number of visited routes
246
+ *
247
+ * @returns {number} Number of routes visited
248
+ */
249
+ get visitedRoutesCount(): number;
250
+ /**
251
+ * Get the start path
252
+ *
253
+ * @returns {string} Start path
254
+ */
255
+ get startPath(): string;
249
256
  /**
250
- * Complete current state's transitions immediately
251
- * Fast-forwards animations/operations to completion (complete state)
257
+ * Check if the supplied path matches the start path
258
+ *
259
+ * @param {string} path - Path to check
260
+ *
261
+ * @returns {boolean} True if path matches start path
252
262
  *
253
263
  * @example
254
264
  * ```javascript
255
- * // User clicks "Skip" or "Next" button
256
- * machine.completeTransitions();
265
+ * if (machine.isStartPath('/game/play')) {
266
+ * // User is on the start page
267
+ * }
257
268
  * ```
258
269
  */
259
- completeTransitions(): void;
270
+ isStartPath(path: string): boolean;
260
271
  /**
261
- * Check if current state has transitions that can be completed
272
+ * Check if currently on the start path
262
273
  *
263
- * @returns {boolean} True if completeTransitions() can be called
274
+ * @returns {boolean} True if current route is the start path
264
275
  *
265
276
  * @example
266
- * ```svelte
267
- * {#if machine.canCompleteTransitions}
268
- * <button onclick={() => machine.completeTransitions()}>Skip</button>
269
- * {/if}
277
+ * ```javascript
278
+ * if (machine.isOnStartPath) {
279
+ * // Show onboarding
280
+ * }
270
281
  * ```
271
282
  */
272
- get canCompleteTransitions(): boolean;
283
+ get isOnStartPath(): boolean;
273
284
  /**
274
- * Check if current state has transitions that can be aborted
275
- *
276
- * @returns {boolean} True if abortTransitions() can be called
285
+ * Navigate to the start path
277
286
  *
278
287
  * @example
279
- * ```svelte
280
- * {#if machine.canAbortTransitions}
281
- * <button onclick={() => machine.abortTransitions()}>Cancel</button>
282
- * {/if}
288
+ * ```javascript
289
+ * // Redirect user to start
290
+ * machine.redirectToStartPath();
283
291
  * ```
284
292
  */
285
- get canAbortTransitions(): boolean;
293
+ redirectToStartPath(): void;
286
294
  #private;
287
295
  }