@hkdigital/lib-core 0.5.58 → 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.
@@ -1,45 +1,3 @@
1
- /**
2
- * Route-aware data manager for page groups
3
- *
4
- * Tracks navigation within a route group and manages business/domain data.
5
- * Does NOT enforce transitions - allows free navigation via browser.
6
- *
7
- * Features:
8
- * - Current route tracking and synchronization
9
- * - Start path management
10
- * - Data properties for business/domain state
11
- * - Visited routes tracking
12
- *
13
- * Basic usage:
14
- * ```javascript
15
- * const machine = new PageMachine({
16
- * startPath: '/intro/start',
17
- * routes: ['/intro/start', '/intro/profile', '/intro/complete']
18
- * });
19
- *
20
- * // Sync machine with URL changes
21
- * $effect(() => {
22
- * machine.syncFromPath($page.url.pathname);
23
- * });
24
- *
25
- * // Check current route
26
- * if (machine.current === '/intro/profile') {
27
- * // Do something
28
- * }
29
- * ```
30
- *
31
- * Animations and page-specific logic should use $effect in pages:
32
- * ```javascript
33
- * // In +page.svelte
34
- * const animations = new PageAnimations();
35
- *
36
- * $effect(() => {
37
- * if (someCondition) {
38
- * animations.start();
39
- * }
40
- * });
41
- * ```
42
- */
43
1
  export default class PageMachine {
44
2
  /**
45
3
  * Constructor
@@ -50,17 +8,22 @@ export default class PageMachine {
50
8
  * @param {string[]} [config.routes=[]]
51
9
  * Optional list of valid routes (for validation/dev tools)
52
10
  * @param {Record<string, any>} [config.initialData={}]
53
- * Initial data properties (from server)
11
+ * Initial data properties (use KEY_ constants for keys)
54
12
  * @param {import('../../../logging/client.js').Logger} [config.logger]
55
13
  * Logger instance (optional)
56
14
  *
57
15
  * @example
58
16
  * ```javascript
17
+ * const ROUTE_INTRO = '/intro/start';
18
+ * const KEY_INTRO_COMPLETED = 'intro-completed';
19
+ * const KEY_SCORE = 'score';
20
+ *
59
21
  * const machine = new PageMachine({
60
- * startPath: '/intro/start',
61
- * routes: ['/intro/start', '/intro/profile'],
22
+ * startPath: ROUTE_INTRO,
23
+ * routes: [ROUTE_INTRO, '/intro/profile'],
62
24
  * initialData: {
63
- * INTRO_COMPLETED: false
25
+ * [KEY_INTRO_COMPLETED]: false,
26
+ * [KEY_SCORE]: 0
64
27
  * }
65
28
  * });
66
29
  * ```
@@ -102,34 +65,53 @@ export default class PageMachine {
102
65
  /**
103
66
  * Set a data property value
104
67
  *
105
- * @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)
106
73
  * @param {any} value - Property value
107
74
  *
108
75
  * @example
109
76
  * ```javascript
110
- * machine.setData('HAS_STRONG_PROFILE', true);
111
- * 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);
112
82
  * ```
113
83
  */
114
84
  setData(key: string, value: any): void;
115
85
  /**
116
86
  * Get a data property value
117
87
  *
118
- * @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)
119
93
  *
120
94
  * @returns {any} Property value or undefined
121
95
  *
122
96
  * @example
123
97
  * ```javascript
124
- * const hasProfile = machine.getData('HAS_STRONG_PROFILE');
125
- * 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
+ * });
126
105
  * ```
127
106
  */
128
107
  getData(key: string): any;
129
108
  /**
130
- * 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.
131
113
  *
132
- * @returns {Record<string, any>} Copy of all data
114
+ * @returns {Record<string, any>} Plain object with all data
133
115
  *
134
116
  * @example
135
117
  * ```javascript
@@ -141,31 +123,89 @@ export default class PageMachine {
141
123
  /**
142
124
  * Update multiple data properties at once
143
125
  *
126
+ * Each property update triggers fine-grained reactivity.
127
+ *
144
128
  * @param {Record<string, any>} dataUpdates
145
- * Object with key-value pairs
129
+ * Object with key-value pairs (use KEY_ constants for keys)
146
130
  *
147
131
  * @example
148
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
+ *
149
137
  * machine.updateData({
150
- * HAS_STRONG_PROFILE: true,
151
- * PROFILE_SCORE: 85,
152
- * MATCHED_SECTOR: 'technology'
138
+ * [KEY_HAS_STRONG_PROFILE]: true,
139
+ * [KEY_PROFILE_SCORE]: 85,
140
+ * [KEY_MATCHED_SECTOR]: 'technology'
153
141
  * });
154
142
  * ```
155
143
  */
156
144
  updateData(dataUpdates: Record<string, any>): void;
145
+ /**
146
+ * Delete a data property
147
+ *
148
+ * @param {string} key - Property key to delete (use KEY_ constant)
149
+ *
150
+ * @returns {boolean} True if the key existed and was deleted
151
+ *
152
+ * @example
153
+ * ```javascript
154
+ * const KEY_TEMPORARY_FLAG = 'temporary-flag';
155
+ *
156
+ * machine.deleteData(KEY_TEMPORARY_FLAG);
157
+ * ```
158
+ */
159
+ deleteData(key: string): boolean;
160
+ /**
161
+ * Check if data property exists
162
+ *
163
+ * @param {string} key - Property key to check (use KEY_ constant)
164
+ *
165
+ * @returns {boolean} True if the key exists
166
+ *
167
+ * @example
168
+ * ```javascript
169
+ * const KEY_TUTORIAL_SEEN = 'tutorial-seen';
170
+ *
171
+ * if (machine.hasData(KEY_TUTORIAL_SEEN)) {
172
+ * // Skip tutorial
173
+ * }
174
+ * ```
175
+ */
176
+ hasData(key: string): boolean;
177
+ /**
178
+ * Clear all data properties
179
+ *
180
+ * @example
181
+ * ```javascript
182
+ * machine.clearData(); // Reset all game data
183
+ * ```
184
+ */
185
+ clearData(): void;
186
+ /**
187
+ * Get number of data properties
188
+ *
189
+ * @returns {number} Number of data entries
190
+ */
191
+ get dataSize(): number;
157
192
  /**
158
193
  * Check if a route has been visited
159
194
  *
195
+ * Automatically reactive - creates a dependency on the visited routes set.
196
+ *
160
197
  * @param {string} route - Route path to check
161
198
  *
162
199
  * @returns {boolean} True if the route has been visited
163
200
  *
164
201
  * @example
165
202
  * ```javascript
166
- * if (machine.hasVisited('/intro/profile')) {
167
- * // User has seen profile page, skip intro
168
- * }
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
+ * });
169
209
  * ```
170
210
  */
171
211
  hasVisited(route: string): boolean;
@@ -183,16 +223,30 @@ export default class PageMachine {
183
223
  */
184
224
  get hasVisitedStart(): boolean;
185
225
  /**
186
- * Get all visited routes
226
+ * Get all visited routes as array
227
+ *
228
+ * Note: Returns a snapshot (plain array), not reactive.
187
229
  *
188
230
  * @returns {string[]} Array of visited route paths
189
231
  */
190
232
  getVisitedRoutes(): string[];
191
233
  /**
192
234
  * Reset visited routes tracking
193
- * Useful for testing or resetting experience
235
+ *
236
+ * Clears all visited routes and marks only the current route as visited.
237
+ *
238
+ * @example
239
+ * ```javascript
240
+ * machine.resetVisitedRoutes(); // Reset progress tracking
241
+ * ```
194
242
  */
195
243
  resetVisitedRoutes(): void;
244
+ /**
245
+ * Get number of visited routes
246
+ *
247
+ * @returns {number} Number of routes visited
248
+ */
249
+ get visitedRoutesCount(): number;
196
250
  /**
197
251
  * Get the start path
198
252
  *
@@ -7,14 +7,28 @@
7
7
  * Features:
8
8
  * - Current route tracking and synchronization
9
9
  * - Start path management
10
- * - Data properties for business/domain state
11
- * - Visited routes tracking
10
+ * - Data properties for business/domain state (using SvelteMap)
11
+ * - Visited routes tracking (using SvelteSet)
12
+ * - Fine-grained reactivity without manual revision tracking
13
+ *
14
+ * Best practices:
15
+ * - Use constants for routes: `const ROUTE_INTRO = '/intro/start'`
16
+ * - Use KEY_ constants for data: `const KEY_SCORE = 'score'`
12
17
  *
13
18
  * Basic usage:
14
19
  * ```javascript
20
+ * // Define constants
21
+ * const ROUTE_INTRO = '/intro/start';
22
+ * const KEY_SCORE = 'score';
23
+ * const KEY_TUTORIAL_SEEN = 'tutorial-seen';
24
+ *
15
25
  * const machine = new PageMachine({
16
- * startPath: '/intro/start',
17
- * routes: ['/intro/start', '/intro/profile', '/intro/complete']
26
+ * startPath: ROUTE_INTRO,
27
+ * routes: [ROUTE_INTRO, '/intro/profile', '/intro/complete'],
28
+ * initialData: {
29
+ * [KEY_SCORE]: 0,
30
+ * [KEY_TUTORIAL_SEEN]: false
31
+ * }
18
32
  * });
19
33
  *
20
34
  * // Sync machine with URL changes
@@ -22,10 +36,18 @@
22
36
  * machine.syncFromPath($page.url.pathname);
23
37
  * });
24
38
  *
25
- * // Check current route
26
- * if (machine.current === '/intro/profile') {
27
- * // Do something
28
- * }
39
+ * // Check current route (reactive)
40
+ * $effect(() => {
41
+ * if (machine.current === ROUTE_INTRO) {
42
+ * console.log('On intro page');
43
+ * }
44
+ * });
45
+ *
46
+ * // Access data (reactive, fine-grained)
47
+ * $effect(() => {
48
+ * const score = machine.getData(KEY_SCORE);
49
+ * console.log('Score changed:', score);
50
+ * });
29
51
  * ```
30
52
  *
31
53
  * Animations and page-specific logic should use $effect in pages:
@@ -40,6 +62,8 @@
40
62
  * });
41
63
  * ```
42
64
  */
65
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
66
+
43
67
  export default class PageMachine {
44
68
  /**
45
69
  * Logger instance
@@ -67,25 +91,18 @@ export default class PageMachine {
67
91
  #routes = [];
68
92
 
69
93
  /**
70
- * Data properties for business/domain state
71
- * Can be initialized from server and synced back
72
- * @type {Record<string, any>}
94
+ * Reactive map for business/domain data
95
+ * Uses SvelteMap for fine-grained reactivity
96
+ * @type {SvelteMap<string, any>}
73
97
  */
74
- #data = $state({});
98
+ #data;
75
99
 
76
100
  /**
77
- * Track which routes have been visited during this session
78
- * Useful for showing first-time hints/tips
79
- * @type {Set<string>}
101
+ * Reactive set for visited routes
102
+ * Uses SvelteSet for automatic reactivity
103
+ * @type {SvelteSet<string>}
80
104
  */
81
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
82
- #visitedRoutes = new Set();
83
-
84
- /**
85
- * Revision counter for triggering reactivity
86
- * @type {number}
87
- */
88
- #revision = $state(0);
105
+ #visitedRoutes;
89
106
 
90
107
  /**
91
108
  * Constructor
@@ -96,17 +113,22 @@ export default class PageMachine {
96
113
  * @param {string[]} [config.routes=[]]
97
114
  * Optional list of valid routes (for validation/dev tools)
98
115
  * @param {Record<string, any>} [config.initialData={}]
99
- * Initial data properties (from server)
116
+ * Initial data properties (use KEY_ constants for keys)
100
117
  * @param {import('../../../logging/client.js').Logger} [config.logger]
101
118
  * Logger instance (optional)
102
119
  *
103
120
  * @example
104
121
  * ```javascript
122
+ * const ROUTE_INTRO = '/intro/start';
123
+ * const KEY_INTRO_COMPLETED = 'intro-completed';
124
+ * const KEY_SCORE = 'score';
125
+ *
105
126
  * const machine = new PageMachine({
106
- * startPath: '/intro/start',
107
- * routes: ['/intro/start', '/intro/profile'],
127
+ * startPath: ROUTE_INTRO,
128
+ * routes: [ROUTE_INTRO, '/intro/profile'],
108
129
  * initialData: {
109
- * INTRO_COMPLETED: false
130
+ * [KEY_INTRO_COMPLETED]: false,
131
+ * [KEY_SCORE]: 0
110
132
  * }
111
133
  * });
112
134
  * ```
@@ -119,9 +141,17 @@ export default class PageMachine {
119
141
  this.logger = logger;
120
142
  this.#startPath = startPath;
121
143
  this.#routes = routes;
122
- this.#data = initialData;
123
144
  this.#current = startPath;
124
145
 
146
+ // Initialize reactive data structures
147
+ this.#data = new SvelteMap();
148
+ this.#visitedRoutes = new SvelteSet();
149
+
150
+ // Populate initial data
151
+ for (const [key, value] of Object.entries(initialData)) {
152
+ this.#data.set(key, value);
153
+ }
154
+
125
155
  // Mark start path as visited
126
156
  this.#visitedRoutes.add(startPath);
127
157
  }
@@ -146,7 +176,6 @@ export default class PageMachine {
146
176
  const oldRoute = this.#current;
147
177
  this.#current = matchedRoute;
148
178
  this.#visitedRoutes.add(matchedRoute);
149
- this.#revision++;
150
179
 
151
180
  this.logger?.debug(`Route changed: ${oldRoute} → ${matchedRoute}`);
152
181
 
@@ -208,43 +237,59 @@ export default class PageMachine {
208
237
  /**
209
238
  * Set a data property value
210
239
  *
211
- * @param {string} key - Property key
240
+ * Automatically reactive - effects watching this key will re-run.
241
+ * Uses fine-grained reactivity, so only effects watching this specific
242
+ * key will be triggered.
243
+ *
244
+ * @param {string} key - Property key (use KEY_ constant)
212
245
  * @param {any} value - Property value
213
246
  *
214
247
  * @example
215
248
  * ```javascript
216
- * machine.setData('HAS_STRONG_PROFILE', true);
217
- * machine.setData('PROFILE_SCORE', 85);
249
+ * const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
250
+ * const KEY_PROFILE_SCORE = 'profile-score';
251
+ *
252
+ * machine.setData(KEY_HAS_STRONG_PROFILE, true);
253
+ * machine.setData(KEY_PROFILE_SCORE, 85);
218
254
  * ```
219
255
  */
220
256
  setData(key, value) {
221
- this.#data[key] = value;
222
- this.#revision++;
257
+ this.#data.set(key, value);
223
258
  }
224
259
 
225
260
  /**
226
261
  * Get a data property value
227
262
  *
228
- * @param {string} key - Property key
263
+ * Automatically reactive - creates a dependency on this specific key.
264
+ * The effect will only re-run when THIS key changes, not when other
265
+ * keys change.
266
+ *
267
+ * @param {string} key - Property key (use KEY_ constant)
229
268
  *
230
269
  * @returns {any} Property value or undefined
231
270
  *
232
271
  * @example
233
272
  * ```javascript
234
- * const hasProfile = machine.getData('HAS_STRONG_PROFILE');
235
- * const score = machine.getData('PROFILE_SCORE');
273
+ * const KEY_SCORE = 'score';
274
+ *
275
+ * // Reactive - re-runs only when KEY_SCORE changes
276
+ * $effect(() => {
277
+ * const score = machine.getData(KEY_SCORE);
278
+ * console.log('Score:', score);
279
+ * });
236
280
  * ```
237
281
  */
238
282
  getData(key) {
239
- // Access revision to ensure reactivity
240
- this.#revision;
241
- return this.#data[key];
283
+ return this.#data.get(key);
242
284
  }
243
285
 
244
286
  /**
245
- * Get all data properties
287
+ * Get all data properties as plain object
246
288
  *
247
- * @returns {Record<string, any>} Copy of all data
289
+ * Note: This returns a snapshot (plain object), not a reactive map.
290
+ * Use this for serialization or server sync, not for reactive tracking.
291
+ *
292
+ * @returns {Record<string, any>} Plain object with all data
248
293
  *
249
294
  * @example
250
295
  * ```javascript
@@ -253,31 +298,93 @@ export default class PageMachine {
253
298
  * ```
254
299
  */
255
300
  getAllData() {
256
- // Access revision to ensure reactivity
257
- this.#revision;
258
- return { ...this.#data };
301
+ return Object.fromEntries(this.#data);
259
302
  }
260
303
 
261
304
  /**
262
305
  * Update multiple data properties at once
263
306
  *
307
+ * Each property update triggers fine-grained reactivity.
308
+ *
264
309
  * @param {Record<string, any>} dataUpdates
265
- * Object with key-value pairs
310
+ * Object with key-value pairs (use KEY_ constants for keys)
266
311
  *
267
312
  * @example
268
313
  * ```javascript
314
+ * const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
315
+ * const KEY_PROFILE_SCORE = 'profile-score';
316
+ * const KEY_MATCHED_SECTOR = 'matched-sector';
317
+ *
269
318
  * machine.updateData({
270
- * HAS_STRONG_PROFILE: true,
271
- * PROFILE_SCORE: 85,
272
- * MATCHED_SECTOR: 'technology'
319
+ * [KEY_HAS_STRONG_PROFILE]: true,
320
+ * [KEY_PROFILE_SCORE]: 85,
321
+ * [KEY_MATCHED_SECTOR]: 'technology'
273
322
  * });
274
323
  * ```
275
324
  */
276
325
  updateData(dataUpdates) {
277
326
  for (const [key, value] of Object.entries(dataUpdates)) {
278
- this.#data[key] = value;
327
+ this.#data.set(key, value);
279
328
  }
280
- this.#revision++;
329
+ }
330
+
331
+ /**
332
+ * Delete a data property
333
+ *
334
+ * @param {string} key - Property key to delete (use KEY_ constant)
335
+ *
336
+ * @returns {boolean} True if the key existed and was deleted
337
+ *
338
+ * @example
339
+ * ```javascript
340
+ * const KEY_TEMPORARY_FLAG = 'temporary-flag';
341
+ *
342
+ * machine.deleteData(KEY_TEMPORARY_FLAG);
343
+ * ```
344
+ */
345
+ deleteData(key) {
346
+ return this.#data.delete(key);
347
+ }
348
+
349
+ /**
350
+ * Check if data property exists
351
+ *
352
+ * @param {string} key - Property key to check (use KEY_ constant)
353
+ *
354
+ * @returns {boolean} True if the key exists
355
+ *
356
+ * @example
357
+ * ```javascript
358
+ * const KEY_TUTORIAL_SEEN = 'tutorial-seen';
359
+ *
360
+ * if (machine.hasData(KEY_TUTORIAL_SEEN)) {
361
+ * // Skip tutorial
362
+ * }
363
+ * ```
364
+ */
365
+ hasData(key) {
366
+ return this.#data.has(key);
367
+ }
368
+
369
+ /**
370
+ * Clear all data properties
371
+ *
372
+ * @example
373
+ * ```javascript
374
+ * machine.clearData(); // Reset all game data
375
+ * ```
376
+ */
377
+ clearData() {
378
+ this.#data.clear();
379
+ }
380
+
381
+ /**
382
+ * Get number of data properties
383
+ *
384
+ * @returns {number} Number of data entries
385
+ */
386
+ get dataSize() {
387
+ return this.#data.size;
281
388
  }
282
389
 
283
390
  /* ===== Visited Routes Tracking ===== */
@@ -285,20 +392,23 @@ export default class PageMachine {
285
392
  /**
286
393
  * Check if a route has been visited
287
394
  *
395
+ * Automatically reactive - creates a dependency on the visited routes set.
396
+ *
288
397
  * @param {string} route - Route path to check
289
398
  *
290
399
  * @returns {boolean} True if the route has been visited
291
400
  *
292
401
  * @example
293
402
  * ```javascript
294
- * if (machine.hasVisited('/intro/profile')) {
295
- * // User has seen profile page, skip intro
296
- * }
403
+ * // Reactive - re-runs when visited routes change
404
+ * $effect(() => {
405
+ * if (machine.hasVisited('/intro/profile')) {
406
+ * console.log('User has seen profile page');
407
+ * }
408
+ * });
297
409
  * ```
298
410
  */
299
411
  hasVisited(route) {
300
- // Access revision to ensure reactivity
301
- this.#revision;
302
412
  return this.#visitedRoutes.has(route);
303
413
  }
304
414
 
@@ -319,24 +429,38 @@ export default class PageMachine {
319
429
  }
320
430
 
321
431
  /**
322
- * Get all visited routes
432
+ * Get all visited routes as array
433
+ *
434
+ * Note: Returns a snapshot (plain array), not reactive.
323
435
  *
324
436
  * @returns {string[]} Array of visited route paths
325
437
  */
326
438
  getVisitedRoutes() {
327
- // Access revision to ensure reactivity
328
- this.#revision;
329
439
  return Array.from(this.#visitedRoutes);
330
440
  }
331
441
 
332
442
  /**
333
443
  * Reset visited routes tracking
334
- * Useful for testing or resetting experience
444
+ *
445
+ * Clears all visited routes and marks only the current route as visited.
446
+ *
447
+ * @example
448
+ * ```javascript
449
+ * machine.resetVisitedRoutes(); // Reset progress tracking
450
+ * ```
335
451
  */
336
452
  resetVisitedRoutes() {
337
453
  this.#visitedRoutes.clear();
338
454
  this.#visitedRoutes.add(this.#current);
339
- this.#revision++;
455
+ }
456
+
457
+ /**
458
+ * Get number of visited routes
459
+ *
460
+ * @returns {number} Number of routes visited
461
+ */
462
+ get visitedRoutesCount() {
463
+ return this.#visitedRoutes.size;
340
464
  }
341
465
 
342
466
  /* ===== Start Path Methods ===== */
@@ -91,7 +91,12 @@ export class PuzzleState extends PageMachine {
91
91
  ROUTE_LEVEL1,
92
92
  ROUTE_LEVEL2,
93
93
  ROUTE_COMPLETE
94
- ]
94
+ ],
95
+ initialData: {
96
+ [KEY_TUTORIAL_SEEN]: false,
97
+ [KEY_HIGHEST_LEVEL]: 1,
98
+ [KEY_DIFFICULTY]: 'normal'
99
+ }
95
100
  });
96
101
 
97
102
  this.#gameLogic = new PuzzleGameLogic();
@@ -293,15 +298,26 @@ Use PageMachine's data properties for **persistent settings and progress**:
293
298
  - ✅ Settings that survive page navigation
294
299
  - ✅ Data that might be saved to server
295
300
 
301
+ **IMPORTANT**: Use KEY_ constants for data keys to get:
302
+ - ✅ Autocomplete support
303
+ - ✅ Typo prevention
304
+ - ✅ Easy refactoring
305
+ - ✅ Self-documenting code
306
+
296
307
  ```javascript
308
+ // Define constants (at top of file)
297
309
  const KEY_TUTORIAL_SEEN = 'tutorial-seen';
298
310
  const KEY_DIFFICULTY = 'difficulty';
299
311
  const KEY_HIGHEST_LEVEL = 'highest-level';
300
312
 
301
- // Persistent data
302
- pageMachine.setData(KEY_TUTORIAL_SEEN, true);
303
- pageMachine.setData(KEY_DIFFICULTY, 'hard');
304
- pageMachine.setData(KEY_HIGHEST_LEVEL, 5);
313
+ // Use constants (not strings!)
314
+ pageMachine.setData(KEY_TUTORIAL_SEEN, true); // ✅ Good
315
+ pageMachine.setData(KEY_DIFFICULTY, 'hard'); // ✅ Good
316
+ pageMachine.setData(KEY_HIGHEST_LEVEL, 5); // ✅ Good
317
+
318
+ // DON'T use magic strings
319
+ pageMachine.setData('tutorial-seen', true); // ❌ Avoid
320
+ pageMachine.setData('TUTORIAL_SEEN', true); // ❌ Avoid
305
321
  ```
306
322
 
307
323
  ### When to use GameLogic with `$state`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.5.58",
3
+ "version": "0.5.59",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"