@hkdigital/lib-core 0.5.88 → 0.5.90

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,152 @@
1
+ export default class ReactiveDataStore {
2
+ /**
3
+ * Constructor
4
+ *
5
+ * @param {Object} [options]
6
+ * @param {Record<string, any>} [options.initialData={}]
7
+ * Initial key-value pairs
8
+ * @param {boolean} [options.strictMode=true]
9
+ * Throw error when accessing uninitialized keys
10
+ * @param {boolean} [options.productionGuard=false]
11
+ * Dev-only mode: no-op on SET, throw on GET in production
12
+ * @param {string} [options.errorPrefix='Data key']
13
+ * Prefix for error messages
14
+ */
15
+ constructor({ initialData, strictMode, productionGuard, errorPrefix }?: {
16
+ initialData?: Record<string, any> | undefined;
17
+ strictMode?: boolean | undefined;
18
+ productionGuard?: boolean | undefined;
19
+ errorPrefix?: string | undefined;
20
+ });
21
+ /**
22
+ * Set a data property value
23
+ *
24
+ * Automatically reactive - effects watching this key will re-run.
25
+ * Uses fine-grained reactivity via SvelteMap.
26
+ *
27
+ * With productionGuard: silent no-op in production (safe to call)
28
+ *
29
+ * @param {string} key - Property key
30
+ * @param {any} value - Property value
31
+ *
32
+ * @example
33
+ * ```javascript
34
+ * store.set('score', 100);
35
+ * store.set('playerName', 'Alice');
36
+ * ```
37
+ */
38
+ set(key: string, value: any): void;
39
+ /**
40
+ * Get a data property value
41
+ *
42
+ * Automatically reactive - creates a dependency on this specific key.
43
+ * The effect will only re-run when THIS key changes.
44
+ *
45
+ * With strictMode: throws if key is not initialized
46
+ * With productionGuard: throws in production (programming error)
47
+ *
48
+ * @param {string} key - Property key
49
+ *
50
+ * @returns {any} Property value
51
+ *
52
+ * @throws {Error} If key not initialized (strictMode)
53
+ * @throws {Error} If accessed in production (productionGuard)
54
+ *
55
+ * @example
56
+ * ```javascript
57
+ * // Reactive - re-runs only when 'score' changes
58
+ * $effect(() => {
59
+ * const score = store.get('score');
60
+ * console.log('Score:', score);
61
+ * });
62
+ * ```
63
+ */
64
+ get(key: string): any;
65
+ /**
66
+ * Get all data properties as plain object
67
+ *
68
+ * Note: Returns a snapshot (plain object), not reactive.
69
+ * Use for serialization or inspection, not for reactive tracking.
70
+ *
71
+ * @returns {Record<string, any>} Plain object with all data
72
+ *
73
+ * @example
74
+ * ```javascript
75
+ * const allData = store.getAll();
76
+ * await saveToServer(allData);
77
+ * ```
78
+ */
79
+ getAll(): Record<string, any>;
80
+ /**
81
+ * Update multiple data properties at once
82
+ *
83
+ * Each property update triggers fine-grained reactivity.
84
+ * Only effects watching the specific changed keys will re-run.
85
+ *
86
+ * @param {Record<string, any>} updates
87
+ * Object with key-value pairs to update
88
+ *
89
+ * @example
90
+ * ```javascript
91
+ * store.update({
92
+ * score: 100,
93
+ * level: 5,
94
+ * completed: true
95
+ * });
96
+ * ```
97
+ */
98
+ update(updates: Record<string, any>): void;
99
+ /**
100
+ * Delete a data property
101
+ *
102
+ * @param {string} key - Property key to delete
103
+ *
104
+ * @returns {boolean} True if the key existed and was deleted
105
+ *
106
+ * @example
107
+ * ```javascript
108
+ * store.delete('temporaryFlag');
109
+ * ```
110
+ */
111
+ delete(key: string): boolean;
112
+ /**
113
+ * Check if data property exists
114
+ *
115
+ * With productionGuard: throws in production (same as get)
116
+ *
117
+ * @param {string} key - Property key to check
118
+ *
119
+ * @returns {boolean} True if the key exists
120
+ *
121
+ * @throws {Error} If accessed in production (productionGuard)
122
+ *
123
+ * @example
124
+ * ```javascript
125
+ * if (store.has('tutorialSeen')) {
126
+ * // Skip tutorial
127
+ * }
128
+ * ```
129
+ */
130
+ has(key: string): boolean;
131
+ /**
132
+ * Clear all data properties
133
+ *
134
+ * @example
135
+ * ```javascript
136
+ * store.clear(); // Reset all data
137
+ * ```
138
+ */
139
+ clear(): void;
140
+ /**
141
+ * Get number of data properties
142
+ *
143
+ * @returns {number} Number of data entries
144
+ *
145
+ * @example
146
+ * ```javascript
147
+ * console.log(`Store has ${store.size} entries`);
148
+ * ```
149
+ */
150
+ get size(): number;
151
+ #private;
152
+ }
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Reactive key-value data store with fine-grained reactivity
3
+ *
4
+ * Built on SvelteMap for fine-grained reactivity where effects only re-run
5
+ * when the specific keys they access change.
6
+ *
7
+ * Features:
8
+ * - Fine-grained reactivity using SvelteMap
9
+ * - Strict mode: throws on access to uninitialized keys
10
+ * - Production guard: dev-only data throws on read in production
11
+ * - Initialization from plain object
12
+ *
13
+ * @example
14
+ * ```javascript
15
+ * // Regular data store
16
+ * const store = new ReactiveDataStore({
17
+ * initialData: { score: 0, level: 1 }
18
+ * });
19
+ *
20
+ * store.set('score', 100);
21
+ * const score = store.get('score'); // Reactive access
22
+ *
23
+ * // Dev-only data store
24
+ * const devStore = new ReactiveDataStore({
25
+ * initialData: { autoNav: false },
26
+ * productionGuard: true,
27
+ * errorPrefix: 'Dev data key'
28
+ * });
29
+ *
30
+ * devStore.set('autoNav', true); // No-op in production
31
+ * devStore.get('autoNav'); // Throws in production
32
+ * ```
33
+ */
34
+
35
+ import { SvelteMap } from 'svelte/reactivity';
36
+ import { dev } from '$app/environment';
37
+
38
+ export default class ReactiveDataStore {
39
+ /**
40
+ * Internal reactive map
41
+ * @type {SvelteMap<string, any>}
42
+ */
43
+ #data;
44
+
45
+ /**
46
+ * Throw on access to uninitialized keys
47
+ * @type {boolean}
48
+ */
49
+ #strictMode;
50
+
51
+ /**
52
+ * Guard against production access (for dev-only data)
53
+ * @type {boolean}
54
+ */
55
+ #productionGuard;
56
+
57
+ /**
58
+ * Prefix for error messages
59
+ * @type {string}
60
+ */
61
+ #errorPrefix;
62
+
63
+ /**
64
+ * Constructor
65
+ *
66
+ * @param {Object} [options]
67
+ * @param {Record<string, any>} [options.initialData={}]
68
+ * Initial key-value pairs
69
+ * @param {boolean} [options.strictMode=true]
70
+ * Throw error when accessing uninitialized keys
71
+ * @param {boolean} [options.productionGuard=false]
72
+ * Dev-only mode: no-op on SET, throw on GET in production
73
+ * @param {string} [options.errorPrefix='Data key']
74
+ * Prefix for error messages
75
+ */
76
+ constructor({
77
+ initialData = {},
78
+ strictMode = true,
79
+ productionGuard = false,
80
+ errorPrefix = 'Data key'
81
+ } = {}) {
82
+ this.#strictMode = strictMode;
83
+ this.#productionGuard = productionGuard;
84
+ this.#errorPrefix = errorPrefix;
85
+
86
+ // Initialize map
87
+ this.#data = new SvelteMap();
88
+
89
+ // Only populate initial data in dev mode if guard is enabled
90
+ if (!productionGuard || dev) {
91
+ for (const [key, value] of Object.entries(initialData)) {
92
+ this.#data.set(key, value);
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Set a data property value
99
+ *
100
+ * Automatically reactive - effects watching this key will re-run.
101
+ * Uses fine-grained reactivity via SvelteMap.
102
+ *
103
+ * With productionGuard: silent no-op in production (safe to call)
104
+ *
105
+ * @param {string} key - Property key
106
+ * @param {any} value - Property value
107
+ *
108
+ * @example
109
+ * ```javascript
110
+ * store.set('score', 100);
111
+ * store.set('playerName', 'Alice');
112
+ * ```
113
+ */
114
+ set(key, value) {
115
+ // Production guard: no-op silently (safe to call conditionally)
116
+ if (this.#productionGuard && !dev) {
117
+ return;
118
+ }
119
+
120
+ this.#data.set(key, value);
121
+ }
122
+
123
+ /**
124
+ * Get a data property value
125
+ *
126
+ * Automatically reactive - creates a dependency on this specific key.
127
+ * The effect will only re-run when THIS key changes.
128
+ *
129
+ * With strictMode: throws if key is not initialized
130
+ * With productionGuard: throws in production (programming error)
131
+ *
132
+ * @param {string} key - Property key
133
+ *
134
+ * @returns {any} Property value
135
+ *
136
+ * @throws {Error} If key not initialized (strictMode)
137
+ * @throws {Error} If accessed in production (productionGuard)
138
+ *
139
+ * @example
140
+ * ```javascript
141
+ * // Reactive - re-runs only when 'score' changes
142
+ * $effect(() => {
143
+ * const score = store.get('score');
144
+ * console.log('Score:', score);
145
+ * });
146
+ * ```
147
+ */
148
+ get(key) {
149
+ // Production guard: THROW on read in production
150
+ if (this.#productionGuard && !dev) {
151
+ throw new Error(
152
+ `${this.#errorPrefix} "${key}" accessed in production. ` +
153
+ `This data is only available in development mode.`
154
+ );
155
+ }
156
+
157
+ // Strict mode: validate key exists
158
+ if (this.#strictMode && !this.#data.has(key)) {
159
+ throw new Error(
160
+ `${this.#errorPrefix} "${key}" is not initialized.`
161
+ );
162
+ }
163
+
164
+ return this.#data.get(key);
165
+ }
166
+
167
+ /**
168
+ * Get all data properties as plain object
169
+ *
170
+ * Note: Returns a snapshot (plain object), not reactive.
171
+ * Use for serialization or inspection, not for reactive tracking.
172
+ *
173
+ * @returns {Record<string, any>} Plain object with all data
174
+ *
175
+ * @example
176
+ * ```javascript
177
+ * const allData = store.getAll();
178
+ * await saveToServer(allData);
179
+ * ```
180
+ */
181
+ getAll() {
182
+ if (this.#productionGuard && !dev) {
183
+ return {};
184
+ }
185
+
186
+ return Object.fromEntries(this.#data);
187
+ }
188
+
189
+ /**
190
+ * Update multiple data properties at once
191
+ *
192
+ * Each property update triggers fine-grained reactivity.
193
+ * Only effects watching the specific changed keys will re-run.
194
+ *
195
+ * @param {Record<string, any>} updates
196
+ * Object with key-value pairs to update
197
+ *
198
+ * @example
199
+ * ```javascript
200
+ * store.update({
201
+ * score: 100,
202
+ * level: 5,
203
+ * completed: true
204
+ * });
205
+ * ```
206
+ */
207
+ update(updates) {
208
+ if (this.#productionGuard && !dev) {
209
+ return;
210
+ }
211
+
212
+ for (const [key, value] of Object.entries(updates)) {
213
+ this.#data.set(key, value);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Delete a data property
219
+ *
220
+ * @param {string} key - Property key to delete
221
+ *
222
+ * @returns {boolean} True if the key existed and was deleted
223
+ *
224
+ * @example
225
+ * ```javascript
226
+ * store.delete('temporaryFlag');
227
+ * ```
228
+ */
229
+ delete(key) {
230
+ if (this.#productionGuard && !dev) {
231
+ return false;
232
+ }
233
+
234
+ return this.#data.delete(key);
235
+ }
236
+
237
+ /**
238
+ * Check if data property exists
239
+ *
240
+ * With productionGuard: throws in production (same as get)
241
+ *
242
+ * @param {string} key - Property key to check
243
+ *
244
+ * @returns {boolean} True if the key exists
245
+ *
246
+ * @throws {Error} If accessed in production (productionGuard)
247
+ *
248
+ * @example
249
+ * ```javascript
250
+ * if (store.has('tutorialSeen')) {
251
+ * // Skip tutorial
252
+ * }
253
+ * ```
254
+ */
255
+ has(key) {
256
+ // Production guard: THROW on read in production
257
+ if (this.#productionGuard && !dev) {
258
+ throw new Error(
259
+ `${this.#errorPrefix} "${key}" existence check in production. ` +
260
+ `This data is only available in development mode.`
261
+ );
262
+ }
263
+
264
+ return this.#data.has(key);
265
+ }
266
+
267
+ /**
268
+ * Clear all data properties
269
+ *
270
+ * @example
271
+ * ```javascript
272
+ * store.clear(); // Reset all data
273
+ * ```
274
+ */
275
+ clear() {
276
+ if (this.#productionGuard && !dev) {
277
+ return;
278
+ }
279
+
280
+ this.#data.clear();
281
+ }
282
+
283
+ /**
284
+ * Get number of data properties
285
+ *
286
+ * @returns {number} Number of data entries
287
+ *
288
+ * @example
289
+ * ```javascript
290
+ * console.log(`Store has ${store.size} entries`);
291
+ * ```
292
+ */
293
+ get size() {
294
+ if (this.#productionGuard && !dev) {
295
+ return 0;
296
+ }
297
+
298
+ return this.#data.size;
299
+ }
300
+ }
@@ -1 +1,2 @@
1
1
  export { default as SubscribersCount } from "./classes/subscribers-count/SubscribersCount.js";
2
+ export { default as ReactiveDataStore } from "./classes/reactive-data-store/ReactiveDataStore.svelte.js";
@@ -1 +1,2 @@
1
1
  export { default as SubscribersCount } from './classes/subscribers-count/SubscribersCount.js';
2
+ export { default as ReactiveDataStore } from './classes/reactive-data-store/ReactiveDataStore.svelte.js';
@@ -9,6 +9,8 @@ export default class PageMachine {
9
9
  * Optional list of valid routes (for validation/dev tools)
10
10
  * @param {Record<string, any>} [config.initialData={}]
11
11
  * Initial data properties (use KEY_ constants for keys)
12
+ * @param {Record<string, any>} [config.initialDevData={}]
13
+ * Initial dev data properties (use KEY_DEV_ constants for keys)
12
14
  * @param {import('../../../logging/client.js').Logger} [config.logger]
13
15
  * Logger instance (optional)
14
16
  *
@@ -17,6 +19,7 @@ export default class PageMachine {
17
19
  * const ROUTE_INTRO = '/intro/start';
18
20
  * const KEY_INTRO_COMPLETED = 'intro-completed';
19
21
  * const KEY_SCORE = 'score';
22
+ * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
20
23
  *
21
24
  * const machine = new PageMachine({
22
25
  * startPath: ROUTE_INTRO,
@@ -24,14 +27,18 @@ export default class PageMachine {
24
27
  * initialData: {
25
28
  * [KEY_INTRO_COMPLETED]: false,
26
29
  * [KEY_SCORE]: 0
30
+ * },
31
+ * initialDevData: {
32
+ * [KEY_DEV_AUTO_NAVIGATION]: false
27
33
  * }
28
34
  * });
29
35
  * ```
30
36
  */
31
- constructor({ startPath, routes, initialData, logger }: {
37
+ constructor({ startPath, routes, initialData, initialDevData, logger }: {
32
38
  startPath: string;
33
39
  routes?: string[] | undefined;
34
40
  initialData?: Record<string, any> | undefined;
41
+ initialDevData?: Record<string, any> | undefined;
35
42
  logger?: import("../../../logging/client.js").Logger | undefined;
36
43
  });
37
44
  /**
@@ -63,264 +70,69 @@ export default class PageMachine {
63
70
  */
64
71
  get routes(): string[];
65
72
  /**
66
- * Set a data property value
73
+ * Get the reactive data store
67
74
  *
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.
75
+ * Provides read-only access to the data store instance.
76
+ * Access all data methods through this property.
71
77
  *
72
- * @param {string} key - Property key (use KEY_ constant)
73
- * @param {any} value - Property value
74
- *
75
- * @example
76
- * ```javascript
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);
82
- * ```
83
- */
84
- setData(key: string, value: any): void;
85
- /**
86
- * Get a data property value
87
- *
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)
93
- *
94
- * @returns {any} Property value or undefined
78
+ * @returns {ReactiveDataStore} The data store
95
79
  *
96
80
  * @example
97
81
  * ```javascript
98
82
  * const KEY_SCORE = 'score';
99
83
  *
100
- * // Reactive - re-runs only when KEY_SCORE changes
84
+ * // Set data
85
+ * machine.data.set(KEY_SCORE, 100);
86
+ *
87
+ * // Get data (reactive)
101
88
  * $effect(() => {
102
- * const score = machine.getData(KEY_SCORE);
89
+ * const score = machine.data.get(KEY_SCORE);
103
90
  * console.log('Score:', score);
104
91
  * });
105
- * ```
106
- */
107
- getData(key: string): any;
108
- /**
109
- * Get all data properties as plain object
110
92
  *
111
- * Note: This returns a snapshot (plain object), not a reactive map.
112
- * Use this for serialization or server sync, not for reactive tracking.
113
- *
114
- * @returns {Record<string, any>} Plain object with all data
115
- *
116
- * @example
117
- * ```javascript
118
- * const allData = machine.getAllData();
119
- * await playerService.saveData(allData);
93
+ * // Other operations
94
+ * machine.data.update({ [KEY_SCORE]: 200 });
95
+ * machine.data.has(KEY_SCORE);
96
+ * machine.data.delete(KEY_SCORE);
97
+ * machine.data.clear();
98
+ * console.log(machine.data.size);
120
99
  * ```
121
100
  */
122
- getAllData(): Record<string, any>;
101
+ get data(): ReactiveDataStore;
123
102
  /**
124
- * Update multiple data properties at once
103
+ * Get the reactive dev data store
125
104
  *
126
- * Each property update triggers fine-grained reactivity.
105
+ * Provides read-only access to the dev data store instance.
106
+ * Access all dev data methods through this property.
127
107
  *
128
- * @param {Record<string, any>} dataUpdates
129
- * Object with key-value pairs (use KEY_ constants for keys)
108
+ * Dev data is only available in development mode. In production:
109
+ * - SET operations are silent no-ops
110
+ * - GET/HAS operations throw errors (programming errors)
130
111
  *
131
- * @example
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
- *
137
- * machine.updateData({
138
- * [KEY_HAS_STRONG_PROFILE]: true,
139
- * [KEY_PROFILE_SCORE]: 85,
140
- * [KEY_MATCHED_SECTOR]: 'technology'
141
- * });
142
- * ```
143
- */
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
112
+ * @returns {ReactiveDataStore} The dev data store
151
113
  *
152
114
  * @example
153
115
  * ```javascript
154
- * const KEY_TEMPORARY_FLAG = 'temporary-flag';
116
+ * const KEY_DEV_AUTO_NAV = 'dev-auto-navigation';
155
117
  *
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;
192
- /**
193
- * Set a dev data property value
194
- *
195
- * Automatically reactive - effects watching this key will re-run.
196
- * Only available in dev mode - no-op in production.
197
- *
198
- * @param {string} key - Property key (use KEY_DEV_ constant)
199
- * @param {any} value - Property value
200
- *
201
- * @example
202
- * ```javascript
203
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
204
- * const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
205
- *
206
- * machine.setDevData(KEY_DEV_AUTO_NAVIGATION, true);
207
- * machine.setDevData(KEY_DEV_SKIP_ANIMATIONS, false);
208
- * ```
209
- */
210
- setDevData(key: string, value: any): void;
211
- /**
212
- * Get a dev data property value
118
+ * // Set dev data (no-op in production)
119
+ * machine.devData.set(KEY_DEV_AUTO_NAV, true);
213
120
  *
214
- * Automatically reactive - creates a dependency on this specific key.
215
- * Only available in dev mode - returns undefined in production.
216
- *
217
- * @param {string} key - Property key (use KEY_DEV_ constant)
218
- *
219
- * @returns {any} Property value or undefined
220
- *
221
- * @example
222
- * ```javascript
223
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
224
- *
225
- * // Reactive - re-runs only when KEY_DEV_AUTO_NAVIGATION changes
121
+ * // Get dev data (throws in production)
226
122
  * $effect(() => {
227
- * const autoNav = machine.getDevData(KEY_DEV_AUTO_NAVIGATION);
228
- * console.log('Auto-navigation:', autoNav);
123
+ * const autoNav = machine.devData.get(KEY_DEV_AUTO_NAV);
124
+ * console.log('Auto-nav:', autoNav);
229
125
  * });
230
- * ```
231
- */
232
- getDevData(key: string): any;
233
- /**
234
- * Get all dev data properties as plain object
235
- *
236
- * Note: Returns a snapshot (plain object), not reactive.
237
- * Only available in dev mode - returns empty object in production.
238
- *
239
- * @returns {Record<string, any>} Plain object with all dev data
240
- *
241
- * @example
242
- * ```javascript
243
- * const allDevData = machine.getAllDevData();
244
- * console.log('Dev settings:', allDevData);
245
- * ```
246
- */
247
- getAllDevData(): Record<string, any>;
248
- /**
249
- * Update multiple dev data properties at once
250
- *
251
- * Each property update triggers fine-grained reactivity.
252
- * Only available in dev mode - no-op in production.
253
- *
254
- * @param {Record<string, any>} dataUpdates
255
- * Object with key-value pairs (use KEY_DEV_ constants for keys)
256
- *
257
- * @example
258
- * ```javascript
259
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
260
- * const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
261
- *
262
- * machine.updateDevData({
263
- * [KEY_DEV_AUTO_NAVIGATION]: true,
264
- * [KEY_DEV_SKIP_ANIMATIONS]: false
265
- * });
266
- * ```
267
- */
268
- updateDevData(dataUpdates: Record<string, any>): void;
269
- /**
270
- * Delete a dev data property
271
126
  *
272
- * Only available in dev mode - no-op in production.
273
- *
274
- * @param {string} key - Property key to delete (use KEY_DEV_ constant)
275
- *
276
- * @returns {boolean} True if the key existed and was deleted
277
- *
278
- * @example
279
- * ```javascript
280
- * const KEY_DEV_TEMP_FLAG = 'dev-temp-flag';
281
- *
282
- * machine.deleteDevData(KEY_DEV_TEMP_FLAG);
127
+ * // Other operations
128
+ * machine.devData.update({ [KEY_DEV_AUTO_NAV]: false });
129
+ * machine.devData.has(KEY_DEV_AUTO_NAV);
130
+ * machine.devData.delete(KEY_DEV_AUTO_NAV);
131
+ * machine.devData.clear();
132
+ * console.log(machine.devData.size);
283
133
  * ```
284
134
  */
285
- deleteDevData(key: string): boolean;
286
- /**
287
- * Check if dev data property exists
288
- *
289
- * Only available in dev mode - returns false in production.
290
- *
291
- * @param {string} key - Property key to check (use KEY_DEV_ constant)
292
- *
293
- * @returns {boolean} True if the key exists
294
- *
295
- * @example
296
- * ```javascript
297
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
298
- *
299
- * if (machine.hasDevData(KEY_DEV_AUTO_NAVIGATION)) {
300
- * // Dev setting exists
301
- * }
302
- * ```
303
- */
304
- hasDevData(key: string): boolean;
305
- /**
306
- * Clear all dev data properties
307
- *
308
- * Only available in dev mode - no-op in production.
309
- *
310
- * @example
311
- * ```javascript
312
- * machine.clearDevData(); // Reset all dev settings
313
- * ```
314
- */
315
- clearDevData(): void;
316
- /**
317
- * Get number of dev data properties
318
- *
319
- * Only available in dev mode - returns 0 in production.
320
- *
321
- * @returns {number} Number of dev data entries
322
- */
323
- get devDataSize(): number;
135
+ get devData(): ReactiveDataStore;
324
136
  /**
325
137
  * Check if a route has been visited
326
138
  *
@@ -425,3 +237,4 @@ export default class PageMachine {
425
237
  redirectToStartPath(): void;
426
238
  #private;
427
239
  }
240
+ import { ReactiveDataStore } from '../../classes.js';
@@ -69,8 +69,8 @@
69
69
  * });
70
70
  * ```
71
71
  */
72
- import { SvelteMap, SvelteSet } from 'svelte/reactivity';
73
- import { dev } from '$app/environment';
72
+ import { SvelteSet } from 'svelte/reactivity';
73
+ import { ReactiveDataStore } from '../../classes.js';
74
74
 
75
75
  export default class PageMachine {
76
76
  /**
@@ -99,17 +99,14 @@ export default class PageMachine {
99
99
  #routes = [];
100
100
 
101
101
  /**
102
- * Reactive map for business/domain data
103
- * Uses SvelteMap for fine-grained reactivity
104
- * @type {SvelteMap<string, any>}
102
+ * Reactive data store for business/domain data
103
+ * @type {ReactiveDataStore}
105
104
  */
106
105
  #data;
107
106
 
108
107
  /**
109
- * Reactive map for dev-mode helper data
110
- * Uses SvelteMap for fine-grained reactivity
111
- * Only available in dev mode
112
- * @type {SvelteMap<string, any>|null}
108
+ * Reactive data store for dev-mode helper data
109
+ * @type {ReactiveDataStore}
113
110
  */
114
111
  #devData;
115
112
 
@@ -130,6 +127,8 @@ export default class PageMachine {
130
127
  * Optional list of valid routes (for validation/dev tools)
131
128
  * @param {Record<string, any>} [config.initialData={}]
132
129
  * Initial data properties (use KEY_ constants for keys)
130
+ * @param {Record<string, any>} [config.initialDevData={}]
131
+ * Initial dev data properties (use KEY_DEV_ constants for keys)
133
132
  * @param {import('../../../logging/client.js').Logger} [config.logger]
134
133
  * Logger instance (optional)
135
134
  *
@@ -138,6 +137,7 @@ export default class PageMachine {
138
137
  * const ROUTE_INTRO = '/intro/start';
139
138
  * const KEY_INTRO_COMPLETED = 'intro-completed';
140
139
  * const KEY_SCORE = 'score';
140
+ * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
141
141
  *
142
142
  * const machine = new PageMachine({
143
143
  * startPath: ROUTE_INTRO,
@@ -145,11 +145,14 @@ export default class PageMachine {
145
145
  * initialData: {
146
146
  * [KEY_INTRO_COMPLETED]: false,
147
147
  * [KEY_SCORE]: 0
148
+ * },
149
+ * initialDevData: {
150
+ * [KEY_DEV_AUTO_NAVIGATION]: false
148
151
  * }
149
152
  * });
150
153
  * ```
151
154
  */
152
- constructor({ startPath, routes = [], initialData = {}, logger = null }) {
155
+ constructor({ startPath, routes = [], initialData = {}, initialDevData = {}, logger = null }) {
153
156
  if (!startPath) {
154
157
  throw new Error('PageMachine requires startPath parameter');
155
158
  }
@@ -160,18 +163,17 @@ export default class PageMachine {
160
163
  this.#current = startPath;
161
164
 
162
165
  // Initialize reactive data structures
163
- this.#data = new SvelteMap();
164
- this.#visitedRoutes = new SvelteSet();
166
+ this.#data = new ReactiveDataStore({
167
+ initialData
168
+ });
165
169
 
166
- // Initialize dev data (only in dev mode)
167
- if (dev) {
168
- this.#devData = new SvelteMap();
169
- }
170
+ this.#devData = new ReactiveDataStore({
171
+ initialData: initialDevData,
172
+ productionGuard: true,
173
+ errorPrefix: 'Dev data key'
174
+ });
170
175
 
171
- // Populate initial data
172
- for (const [key, value] of Object.entries(initialData)) {
173
- this.#data.set(key, value);
174
- }
176
+ this.#visitedRoutes = new SvelteSet();
175
177
 
176
178
  // Mark start path as visited
177
179
  this.#visitedRoutes.add(startPath);
@@ -256,324 +258,75 @@ export default class PageMachine {
256
258
  /* ===== Data Properties (Business/Domain State) ===== */
257
259
 
258
260
  /**
259
- * Set a data property value
260
- *
261
- * Automatically reactive - effects watching this key will re-run.
262
- * Uses fine-grained reactivity, so only effects watching this specific
263
- * key will be triggered.
264
- *
265
- * @param {string} key - Property key (use KEY_ constant)
266
- * @param {any} value - Property value
267
- *
268
- * @example
269
- * ```javascript
270
- * const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
271
- * const KEY_PROFILE_SCORE = 'profile-score';
272
- *
273
- * machine.setData(KEY_HAS_STRONG_PROFILE, true);
274
- * machine.setData(KEY_PROFILE_SCORE, 85);
275
- * ```
276
- */
277
- setData(key, value) {
278
- this.#data.set(key, value);
279
- }
280
-
281
- /**
282
- * Get a data property value
261
+ * Get the reactive data store
283
262
  *
284
- * Automatically reactive - creates a dependency on this specific key.
285
- * The effect will only re-run when THIS key changes, not when other
286
- * keys change.
263
+ * Provides read-only access to the data store instance.
264
+ * Access all data methods through this property.
287
265
  *
288
- * @param {string} key - Property key (use KEY_ constant)
289
- *
290
- * @returns {any} Property value or undefined
266
+ * @returns {ReactiveDataStore} The data store
291
267
  *
292
268
  * @example
293
269
  * ```javascript
294
270
  * const KEY_SCORE = 'score';
295
271
  *
296
- * // Reactive - re-runs only when KEY_SCORE changes
272
+ * // Set data
273
+ * machine.data.set(KEY_SCORE, 100);
274
+ *
275
+ * // Get data (reactive)
297
276
  * $effect(() => {
298
- * const score = machine.getData(KEY_SCORE);
277
+ * const score = machine.data.get(KEY_SCORE);
299
278
  * console.log('Score:', score);
300
279
  * });
301
- * ```
302
- */
303
- getData(key) {
304
- return this.#data.get(key);
305
- }
306
-
307
- /**
308
- * Get all data properties as plain object
309
- *
310
- * Note: This returns a snapshot (plain object), not a reactive map.
311
- * Use this for serialization or server sync, not for reactive tracking.
312
- *
313
- * @returns {Record<string, any>} Plain object with all data
314
- *
315
- * @example
316
- * ```javascript
317
- * const allData = machine.getAllData();
318
- * await playerService.saveData(allData);
319
- * ```
320
- */
321
- getAllData() {
322
- return Object.fromEntries(this.#data);
323
- }
324
-
325
- /**
326
- * Update multiple data properties at once
327
- *
328
- * Each property update triggers fine-grained reactivity.
329
- *
330
- * @param {Record<string, any>} dataUpdates
331
- * Object with key-value pairs (use KEY_ constants for keys)
332
- *
333
- * @example
334
- * ```javascript
335
- * const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
336
- * const KEY_PROFILE_SCORE = 'profile-score';
337
- * const KEY_MATCHED_SECTOR = 'matched-sector';
338
- *
339
- * machine.updateData({
340
- * [KEY_HAS_STRONG_PROFILE]: true,
341
- * [KEY_PROFILE_SCORE]: 85,
342
- * [KEY_MATCHED_SECTOR]: 'technology'
343
- * });
344
- * ```
345
- */
346
- updateData(dataUpdates) {
347
- for (const [key, value] of Object.entries(dataUpdates)) {
348
- this.#data.set(key, value);
349
- }
350
- }
351
-
352
- /**
353
- * Delete a data property
354
- *
355
- * @param {string} key - Property key to delete (use KEY_ constant)
356
- *
357
- * @returns {boolean} True if the key existed and was deleted
358
280
  *
359
- * @example
360
- * ```javascript
361
- * const KEY_TEMPORARY_FLAG = 'temporary-flag';
362
- *
363
- * machine.deleteData(KEY_TEMPORARY_FLAG);
281
+ * // Other operations
282
+ * machine.data.update({ [KEY_SCORE]: 200 });
283
+ * machine.data.has(KEY_SCORE);
284
+ * machine.data.delete(KEY_SCORE);
285
+ * machine.data.clear();
286
+ * console.log(machine.data.size);
364
287
  * ```
365
288
  */
366
- deleteData(key) {
367
- return this.#data.delete(key);
368
- }
369
-
370
- /**
371
- * Check if data property exists
372
- *
373
- * @param {string} key - Property key to check (use KEY_ constant)
374
- *
375
- * @returns {boolean} True if the key exists
376
- *
377
- * @example
378
- * ```javascript
379
- * const KEY_TUTORIAL_SEEN = 'tutorial-seen';
380
- *
381
- * if (machine.hasData(KEY_TUTORIAL_SEEN)) {
382
- * // Skip tutorial
383
- * }
384
- * ```
385
- */
386
- hasData(key) {
387
- return this.#data.has(key);
388
- }
389
-
390
- /**
391
- * Clear all data properties
392
- *
393
- * @example
394
- * ```javascript
395
- * machine.clearData(); // Reset all game data
396
- * ```
397
- */
398
- clearData() {
399
- this.#data.clear();
400
- }
401
-
402
- /**
403
- * Get number of data properties
404
- *
405
- * @returns {number} Number of data entries
406
- */
407
- get dataSize() {
408
- return this.#data.size;
289
+ get data() {
290
+ return this.#data;
409
291
  }
410
292
 
411
293
  /* ===== Dev Data Properties (Dev-Mode Helpers) ===== */
412
294
 
413
295
  /**
414
- * Set a dev data property value
296
+ * Get the reactive dev data store
415
297
  *
416
- * Automatically reactive - effects watching this key will re-run.
417
- * Only available in dev mode - no-op in production.
298
+ * Provides read-only access to the dev data store instance.
299
+ * Access all dev data methods through this property.
418
300
  *
419
- * @param {string} key - Property key (use KEY_DEV_ constant)
420
- * @param {any} value - Property value
301
+ * Dev data is only available in development mode. In production:
302
+ * - SET operations are silent no-ops
303
+ * - GET/HAS operations throw errors (programming errors)
421
304
  *
422
- * @example
423
- * ```javascript
424
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
425
- * const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
426
- *
427
- * machine.setDevData(KEY_DEV_AUTO_NAVIGATION, true);
428
- * machine.setDevData(KEY_DEV_SKIP_ANIMATIONS, false);
429
- * ```
430
- */
431
- setDevData(key, value) {
432
- if (!dev) return;
433
- this.#devData.set(key, value);
434
- }
435
-
436
- /**
437
- * Get a dev data property value
438
- *
439
- * Automatically reactive - creates a dependency on this specific key.
440
- * Only available in dev mode - returns undefined in production.
441
- *
442
- * @param {string} key - Property key (use KEY_DEV_ constant)
443
- *
444
- * @returns {any} Property value or undefined
305
+ * @returns {ReactiveDataStore} The dev data store
445
306
  *
446
307
  * @example
447
308
  * ```javascript
448
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
309
+ * const KEY_DEV_AUTO_NAV = 'dev-auto-navigation';
310
+ *
311
+ * // Set dev data (no-op in production)
312
+ * machine.devData.set(KEY_DEV_AUTO_NAV, true);
449
313
  *
450
- * // Reactive - re-runs only when KEY_DEV_AUTO_NAVIGATION changes
314
+ * // Get dev data (throws in production)
451
315
  * $effect(() => {
452
- * const autoNav = machine.getDevData(KEY_DEV_AUTO_NAVIGATION);
453
- * console.log('Auto-navigation:', autoNav);
316
+ * const autoNav = machine.devData.get(KEY_DEV_AUTO_NAV);
317
+ * console.log('Auto-nav:', autoNav);
454
318
  * });
455
- * ```
456
- */
457
- getDevData(key) {
458
- if (!dev) return undefined;
459
- return this.#devData.get(key);
460
- }
461
-
462
- /**
463
- * Get all dev data properties as plain object
464
- *
465
- * Note: Returns a snapshot (plain object), not reactive.
466
- * Only available in dev mode - returns empty object in production.
467
- *
468
- * @returns {Record<string, any>} Plain object with all dev data
469
319
  *
470
- * @example
471
- * ```javascript
472
- * const allDevData = machine.getAllDevData();
473
- * console.log('Dev settings:', allDevData);
320
+ * // Other operations
321
+ * machine.devData.update({ [KEY_DEV_AUTO_NAV]: false });
322
+ * machine.devData.has(KEY_DEV_AUTO_NAV);
323
+ * machine.devData.delete(KEY_DEV_AUTO_NAV);
324
+ * machine.devData.clear();
325
+ * console.log(machine.devData.size);
474
326
  * ```
475
327
  */
476
- getAllDevData() {
477
- if (!dev) return {};
478
- return Object.fromEntries(this.#devData);
479
- }
480
-
481
- /**
482
- * Update multiple dev data properties at once
483
- *
484
- * Each property update triggers fine-grained reactivity.
485
- * Only available in dev mode - no-op in production.
486
- *
487
- * @param {Record<string, any>} dataUpdates
488
- * Object with key-value pairs (use KEY_DEV_ constants for keys)
489
- *
490
- * @example
491
- * ```javascript
492
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
493
- * const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
494
- *
495
- * machine.updateDevData({
496
- * [KEY_DEV_AUTO_NAVIGATION]: true,
497
- * [KEY_DEV_SKIP_ANIMATIONS]: false
498
- * });
499
- * ```
500
- */
501
- updateDevData(dataUpdates) {
502
- if (!dev) return;
503
- for (const [key, value] of Object.entries(dataUpdates)) {
504
- this.#devData.set(key, value);
505
- }
506
- }
507
-
508
- /**
509
- * Delete a dev data property
510
- *
511
- * Only available in dev mode - no-op in production.
512
- *
513
- * @param {string} key - Property key to delete (use KEY_DEV_ constant)
514
- *
515
- * @returns {boolean} True if the key existed and was deleted
516
- *
517
- * @example
518
- * ```javascript
519
- * const KEY_DEV_TEMP_FLAG = 'dev-temp-flag';
520
- *
521
- * machine.deleteDevData(KEY_DEV_TEMP_FLAG);
522
- * ```
523
- */
524
- deleteDevData(key) {
525
- if (!dev) return false;
526
- return this.#devData.delete(key);
527
- }
528
-
529
- /**
530
- * Check if dev data property exists
531
- *
532
- * Only available in dev mode - returns false in production.
533
- *
534
- * @param {string} key - Property key to check (use KEY_DEV_ constant)
535
- *
536
- * @returns {boolean} True if the key exists
537
- *
538
- * @example
539
- * ```javascript
540
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
541
- *
542
- * if (machine.hasDevData(KEY_DEV_AUTO_NAVIGATION)) {
543
- * // Dev setting exists
544
- * }
545
- * ```
546
- */
547
- hasDevData(key) {
548
- if (!dev) return false;
549
- return this.#devData.has(key);
550
- }
551
-
552
- /**
553
- * Clear all dev data properties
554
- *
555
- * Only available in dev mode - no-op in production.
556
- *
557
- * @example
558
- * ```javascript
559
- * machine.clearDevData(); // Reset all dev settings
560
- * ```
561
- */
562
- clearDevData() {
563
- if (!dev) return;
564
- this.#devData.clear();
565
- }
566
-
567
- /**
568
- * Get number of dev data properties
569
- *
570
- * Only available in dev mode - returns 0 in production.
571
- *
572
- * @returns {number} Number of dev data entries
573
- */
574
- get devDataSize() {
575
- if (!dev) return 0;
576
- return this.#devData.size;
328
+ get devData() {
329
+ return this.#devData;
577
330
  }
578
331
 
579
332
  /* ===== Visited Routes Tracking ===== */
@@ -78,6 +78,10 @@ const KEY_TUTORIAL_SEEN = 'tutorial-seen';
78
78
  const KEY_HIGHEST_LEVEL = 'highest-level';
79
79
  const KEY_DIFFICULTY = 'difficulty';
80
80
 
81
+ // Dev data keys (use KEY_DEV_ prefix)
82
+ const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
83
+ const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
84
+
81
85
  export class PuzzleState extends PageMachine {
82
86
  #logic;
83
87
 
@@ -96,6 +100,10 @@ export class PuzzleState extends PageMachine {
96
100
  [KEY_TUTORIAL_SEEN]: false,
97
101
  [KEY_HIGHEST_LEVEL]: 1,
98
102
  [KEY_DIFFICULTY]: 'normal'
103
+ },
104
+ initialDevData: {
105
+ [KEY_DEV_AUTO_NAVIGATION]: false,
106
+ [KEY_DEV_SKIP_ANIMATIONS]: false
99
107
  }
100
108
  });
101
109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.5.88",
3
+ "version": "0.5.90",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -1 +0,0 @@
1
- export { default as SubScribersCount } from "./SubscribersCount.js";
@@ -1 +0,0 @@
1
- export { default as SubScribersCount } from './SubscribersCount.js';