@warp-drive/experiments 0.2.7-alpha.19 → 0.2.7-alpha.20

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.
Files changed (97) hide show
  1. package/README.md +1 -1
  2. package/declarations/data-worker.d.ts +35 -2
  3. package/declarations/data-worker.d.ts.map +1 -0
  4. package/declarations/document-storage-CwwTQ49-.d.ts +91 -0
  5. package/declarations/document-storage-CwwTQ49-.d.ts.map +1 -0
  6. package/declarations/document-storage.d.ts +2 -1
  7. package/declarations/image-fetch.d.ts +60 -1
  8. package/declarations/image-fetch.d.ts.map +1 -0
  9. package/declarations/image-worker.d.ts +40 -1
  10. package/declarations/image-worker.d.ts.map +1 -0
  11. package/declarations/storage.d.ts +359 -7
  12. package/declarations/storage.d.ts.map +1 -0
  13. package/declarations/types-CRs-Qww6.d.ts +17 -0
  14. package/declarations/types-CRs-Qww6.d.ts.map +1 -0
  15. package/declarations/worker-fetch.d.ts +23 -1
  16. package/declarations/worker-fetch.d.ts.map +1 -0
  17. package/dist/data-worker.js +235 -339
  18. package/dist/data-worker.js.map +1 -0
  19. package/dist/document-storage-LM925Stz.js +270 -0
  20. package/dist/document-storage-LM925Stz.js.map +1 -0
  21. package/dist/document-storage.js +3 -1
  22. package/dist/image-fetch.js +108 -79
  23. package/dist/image-fetch.js.map +1 -0
  24. package/dist/image-worker.js +95 -95
  25. package/dist/image-worker.js.map +1 -0
  26. package/dist/storage.js +825 -1055
  27. package/dist/storage.js.map +1 -0
  28. package/dist/unpkg/dev/data-worker.js +231 -338
  29. package/dist/unpkg/dev/data-worker.js.map +1 -0
  30. package/dist/unpkg/dev/document-storage-DkiEg9rY.js +268 -0
  31. package/dist/unpkg/dev/document-storage-DkiEg9rY.js.map +1 -0
  32. package/dist/unpkg/dev/document-storage.js +2 -348
  33. package/dist/unpkg/dev/image-fetch.js +104 -71
  34. package/dist/unpkg/dev/image-fetch.js.map +1 -0
  35. package/dist/unpkg/dev/image-worker.js +93 -94
  36. package/dist/unpkg/dev/image-worker.js.map +1 -0
  37. package/dist/unpkg/dev/storage.js +819 -1075
  38. package/dist/unpkg/dev/storage.js.map +1 -0
  39. package/dist/unpkg/dev/worker-fetch.js +112 -145
  40. package/dist/unpkg/dev/worker-fetch.js.map +1 -0
  41. package/dist/unpkg/dev-deprecated/data-worker.js +231 -338
  42. package/dist/unpkg/dev-deprecated/data-worker.js.map +1 -0
  43. package/dist/unpkg/dev-deprecated/document-storage-DkiEg9rY.js +268 -0
  44. package/dist/unpkg/dev-deprecated/document-storage-DkiEg9rY.js.map +1 -0
  45. package/dist/unpkg/dev-deprecated/document-storage.js +2 -348
  46. package/dist/unpkg/dev-deprecated/image-fetch.js +104 -71
  47. package/dist/unpkg/dev-deprecated/image-fetch.js.map +1 -0
  48. package/dist/unpkg/dev-deprecated/image-worker.js +93 -94
  49. package/dist/unpkg/dev-deprecated/image-worker.js.map +1 -0
  50. package/dist/unpkg/dev-deprecated/storage.js +819 -1075
  51. package/dist/unpkg/dev-deprecated/storage.js.map +1 -0
  52. package/dist/unpkg/dev-deprecated/worker-fetch.js +112 -145
  53. package/dist/unpkg/dev-deprecated/worker-fetch.js.map +1 -0
  54. package/dist/unpkg/prod/data-worker.js +224 -324
  55. package/dist/unpkg/prod/data-worker.js.map +1 -0
  56. package/dist/unpkg/prod/document-storage-D0k_6GM9.js +262 -0
  57. package/dist/unpkg/prod/document-storage-D0k_6GM9.js.map +1 -0
  58. package/dist/unpkg/prod/document-storage.js +2 -338
  59. package/dist/unpkg/prod/image-fetch.js +101 -71
  60. package/dist/unpkg/prod/image-fetch.js.map +1 -0
  61. package/dist/unpkg/prod/image-worker.js +93 -94
  62. package/dist/unpkg/prod/image-worker.js.map +1 -0
  63. package/dist/unpkg/prod/storage.js +750 -979
  64. package/dist/unpkg/prod/storage.js.map +1 -0
  65. package/dist/unpkg/prod/worker-fetch.js +109 -145
  66. package/dist/unpkg/prod/worker-fetch.js.map +1 -0
  67. package/dist/unpkg/prod-deprecated/data-worker.js +224 -324
  68. package/dist/unpkg/prod-deprecated/data-worker.js.map +1 -0
  69. package/dist/unpkg/prod-deprecated/document-storage-D0k_6GM9.js +262 -0
  70. package/dist/unpkg/prod-deprecated/document-storage-D0k_6GM9.js.map +1 -0
  71. package/dist/unpkg/prod-deprecated/document-storage.js +2 -338
  72. package/dist/unpkg/prod-deprecated/image-fetch.js +101 -71
  73. package/dist/unpkg/prod-deprecated/image-fetch.js.map +1 -0
  74. package/dist/unpkg/prod-deprecated/image-worker.js +93 -94
  75. package/dist/unpkg/prod-deprecated/image-worker.js.map +1 -0
  76. package/dist/unpkg/prod-deprecated/storage.js +750 -979
  77. package/dist/unpkg/prod-deprecated/storage.js.map +1 -0
  78. package/dist/unpkg/prod-deprecated/worker-fetch.js +109 -145
  79. package/dist/unpkg/prod-deprecated/worker-fetch.js.map +1 -0
  80. package/dist/worker-fetch.js +116 -153
  81. package/dist/worker-fetch.js.map +1 -0
  82. package/package.json +9 -9
  83. package/declarations/data-worker/cache-handler.d.ts +0 -7
  84. package/declarations/data-worker/fetch.d.ts +0 -21
  85. package/declarations/data-worker/types.d.ts +0 -31
  86. package/declarations/data-worker/utils.d.ts +0 -12
  87. package/declarations/data-worker/worker.d.ts +0 -23
  88. package/declarations/document-storage/index.d.ts +0 -90
  89. package/declarations/image-worker/fetch.d.ts +0 -18
  90. package/declarations/image-worker/types.d.ts +0 -21
  91. package/declarations/image-worker/worker.d.ts +0 -14
  92. package/declarations/storage/-private/reactive-map.d.ts +0 -13
  93. package/declarations/storage/-private/storage-infra.d.ts +0 -104
  94. package/declarations/storage/cache.d.ts +0 -43
  95. package/declarations/storage/storage-resource.d.ts +0 -137
  96. package/declarations/storage/storage.d.ts +0 -88
  97. package/dist/index-CGCX7hY2.js +0 -349
@@ -1,1169 +1,913 @@
1
- import { defineSignal as defineSignal$1, makeInitializer, memoized } from '@warp-drive/core/signals/-leaked';
2
- import { defineSignal, withSignalStore, entangleSignal, getOrCreateInternalSignal, notifyInternalSignal } from '@warp-drive/core/signals/-private';
1
+ import { defineSignal, entangleSignal, getOrCreateInternalSignal, makeInitializer, memoized, notifyInternalSignal, withSignalStore } from "@warp-drive/core/signals/-leaked";
3
2
 
3
+ //#region src/storage/-private/reactive-map.ts
4
4
  /**
5
- * A reactive wrapper around the browser's Map API that provides
6
- * granular per-key reactivity via WarpDrive's signal system.
7
- */
8
- class SignalMap {
9
- _map = {};
10
- // _size is signal-backed via defineSignal below
11
-
12
- _signals = withSignalStore(this._map);
13
- subscribe(key) {
14
- (test => {
15
- if (!test) {
16
- throw new Error(`ReactiveMap keys must be strings, got ${typeof key}`);
17
- }
18
- })(typeof key === 'string');
19
- const existing = this._signals.has(key);
20
- entangleSignal(this._signals, this._map, key, undefined);
21
- const size = this._size;
22
- this._size = existing ? size : size + 1;
23
- return existing;
24
- }
25
- notify(key) {
26
- (test => {
27
- if (!test) {
28
- throw new Error(`ReactiveMap keys must be strings, got ${typeof key}`);
29
- }
30
- })(typeof key === 'string');
31
- const signal = getOrCreateInternalSignal(this._signals, this._map, key, 0);
32
- notifyInternalSignal(signal);
33
- }
34
- clear() {
35
- for (const value of this._signals.values()) {
36
- notifyInternalSignal(value);
37
- }
38
- this._size = 0;
39
- }
40
- get size() {
41
- return this._size;
42
- }
43
- }
44
- defineSignal(SignalMap.prototype, '_size', 0);
45
-
46
- const DEFAULT_CACHE_ID = 'reactive-cache';
47
- const CACHES = new Map();
48
- const StorageEvents = new BroadcastChannel('reactive-cache-events');
5
+ * A reactive wrapper around the browser's Map API that provides
6
+ * granular per-key reactivity via WarpDrive's signal system.
7
+ */
8
+ var SignalMap = class {
9
+ _map = {};
10
+ _signals = withSignalStore(this._map);
11
+ subscribe(key) {
12
+ ((test) => {
13
+ if (!test) throw new Error(`ReactiveMap keys must be strings, got ${typeof key}`);
14
+ })(typeof key === "string");
15
+ const existing = this._signals.has(key);
16
+ entangleSignal(this._signals, this._map, key, void 0);
17
+ const size = this._size;
18
+ this._size = existing ? size : size + 1;
19
+ return existing;
20
+ }
21
+ notify(key) {
22
+ ((test) => {
23
+ if (!test) throw new Error(`ReactiveMap keys must be strings, got ${typeof key}`);
24
+ })(typeof key === "string");
25
+ const signal = getOrCreateInternalSignal(this._signals, this._map, key, 0);
26
+ notifyInternalSignal(signal);
27
+ }
28
+ clear() {
29
+ for (const value of this._signals.values()) notifyInternalSignal(value);
30
+ this._size = 0;
31
+ }
32
+ get size() {
33
+ return this._size;
34
+ }
35
+ };
36
+ defineSignal(SignalMap.prototype, "_size", 0);
37
+
38
+ //#endregion
39
+ //#region src/storage/cache.ts
40
+ const DEFAULT_CACHE_ID = "reactive-cache";
41
+ const CACHES = /* @__PURE__ */ new Map();
42
+ const StorageEvents = new BroadcastChannel("reactive-cache-events");
49
43
  const CACHE_URL = `${location.origin}/api/reactive-cache.json`;
50
44
  /**
51
- * Emits a {@link StorageEvent} on the local context to
52
- * match the broadcast event.
53
- */
45
+ * Emits a {@link StorageEvent} on the local context to
46
+ * match the broadcast event.
47
+ */
54
48
  function emitStorageEvent(event) {
55
- window.dispatchEvent(new CustomEvent('storage', {
56
- detail: event
57
- }));
49
+ window.dispatchEvent(new CustomEvent("storage", { detail: event }));
58
50
  }
59
-
60
51
  /**
61
- * Updates the cache and re-emits the event on the local context as a StorageEvent
62
- * This is necessary to trigger the reactive updates in the current tab.
63
- */
52
+ * Updates the cache and re-emits the event on the local context as a StorageEvent
53
+ * This is necessary to trigger the reactive updates in the current tab.
54
+ */
64
55
  function handleCacheEvent(event) {
65
- const {
66
- data
67
- } = event;
68
- const cache = CACHES.get(data.storageArea);
69
- // if we don't have an instance of the cache in this context
70
- // then we don't need to do anything since there are no reactive
71
- // subscribers to update
72
- if (!cache) {
73
- return;
74
- }
75
-
76
- // a StorageEvent with a key of null
77
- // is a signal that the entire cache should be cleared.
78
- if (data.key === null) {
79
- cache._data = new Map();
80
- emitStorageEvent({
81
- storageArea: cache,
82
- key: null,
83
- oldValue: null,
84
- newValue: null
85
- });
86
- return;
87
- }
88
- if (data.newValue === null) {
89
- // remove the key from the cache
90
- cache._data.delete(data.key);
91
- } else {
92
- // update the cache with the new value
93
- cache._data.set(data.key, data.newValue);
94
- }
95
-
96
- // emit a StorageEvent to trigger reactive updates in this context
97
- emitStorageEvent({
98
- storageArea: cache,
99
- key: data.key,
100
- oldValue: data.oldValue,
101
- newValue: data.newValue
102
- });
103
- }
104
-
105
- // subscribe to all storage events from other contexts to
106
- // keep caches in sync
107
- StorageEvents.addEventListener('message', handleCacheEvent);
108
-
56
+ const { data } = event;
57
+ const cache = CACHES.get(data.storageArea);
58
+ if (!cache) return;
59
+ if (data.key === null) {
60
+ cache._data = /* @__PURE__ */ new Map();
61
+ emitStorageEvent({
62
+ storageArea: cache,
63
+ key: null,
64
+ oldValue: null,
65
+ newValue: null
66
+ });
67
+ return;
68
+ }
69
+ if (data.newValue === null) cache._data.delete(data.key);
70
+ else cache._data.set(data.key, data.newValue);
71
+ emitStorageEvent({
72
+ storageArea: cache,
73
+ key: data.key,
74
+ oldValue: data.oldValue,
75
+ newValue: data.newValue
76
+ });
77
+ }
78
+ StorageEvents.addEventListener("message", handleCacheEvent);
109
79
  /**
110
- * A reactive interface for json stored in the browser [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) API.
111
- *
112
- * This is a good option for larger data sets than can be efficiently stored in localStorage
113
- * but should not be used as a permanent DB or storage solution.
114
- */
115
- class CacheStorage {
116
- #cache;
117
- #cacheId;
118
- #inititalization;
119
- _data = new Map();
120
- _nextUpdate = null;
121
- _bufferedEvents = [];
122
- async #initialize() {
123
- const cache = await caches.open(this.#cacheId);
124
- this.#cache = cache;
125
-
126
- // Load all existing entries into memory
127
- this._data = await deserializeFromCache(cache);
128
- return this;
129
- }
130
- constructor(cacheId) {
131
- this.#cacheId = cacheId;
132
- this.#cache = null;
133
- this.#inititalization = this.#initialize();
134
- }
135
- get length() {
136
- return this._data.size;
137
- }
138
- #update(key, oldValue, newValue) {
139
- const cache = this.#cache;
140
- if (!cache) {
141
- throw new Error('Cache not initialized');
142
- }
143
- this._bufferedEvents.push({
144
- storageArea: this.#cacheId,
145
- key: key,
146
- oldValue: oldValue,
147
- newValue: newValue
148
- });
149
- if (!this._nextUpdate) {
150
- this._nextUpdate = window.setTimeout(() => {
151
- const events = this._bufferedEvents;
152
- this._bufferedEvents = [];
153
- this._nextUpdate = null;
154
- void serializeToCache(cache, this._data);
155
- for (const event of events) {
156
- StorageEvents.postMessage(event);
157
- }
158
- }, 0);
159
- }
160
- }
161
- clear() {
162
- this._data = new Map();
163
- this.#update(null, null, null);
164
- }
165
- getItem(key) {
166
- return this._data.get(key) ?? null;
167
- }
168
- key(index) {
169
- (test => {
170
- if (!test) {
171
- throw new Error('ReactiveStorage.key: index must be a number');
172
- }
173
- })(typeof index === 'number');
174
- (test => {
175
- if (!test) {
176
- throw new Error('ReactiveStorage.key: index must be a non-negative integer');
177
- }
178
- })(index >= 0 && Number.isInteger(index));
179
- (test => {
180
- if (!test) {
181
- throw new Error('ReactiveStorage.key: index must be less than the number of items in storage');
182
- }
183
- })(index < this.length);
184
- let i = 0;
185
- for (const value of this._data.keys()) {
186
- if (i === index) {
187
- return value;
188
- }
189
- i++;
190
- }
191
- return null;
192
- }
193
- removeItem(key) {
194
- if (this._data.has(key)) {
195
- const oldValue = this._data.get(key) ?? null;
196
- this._data.delete(key);
197
- this.#update(key, oldValue, null);
198
- }
199
- }
200
- setItem(key, value) {
201
- (test => {
202
- if (!test) {
203
- throw new Error('ReactiveStorage.setItem: key must be a string');
204
- }
205
- })(typeof key === 'string');
206
- (test => {
207
- if (!test) {
208
- throw new Error('ReactiveStorage.setItem: value must be a string');
209
- }
210
- })(typeof value === 'string');
211
- const oldValue = this._data.get(key) ?? null;
212
- if (oldValue === value) {
213
- return;
214
- }
215
- this._data.set(key, value);
216
- this.#update(key, oldValue, value);
217
- }
218
-
219
- /**
220
- * Get the singleton CacheStorage instance.
221
- *
222
- */
223
- static get(cacheId = DEFAULT_CACHE_ID) {
224
- let cache = CACHES.get(cacheId);
225
- if (!cache) {
226
- cache = new CacheStorage(cacheId);
227
- CACHES.set(cacheId, cache);
228
- }
229
- return cache.#inititalization;
230
- }
231
- static expectCache(cacheId = DEFAULT_CACHE_ID) {
232
- const cache = CACHES.get(cacheId);
233
- if (!cache) {
234
- throw new Error(`Cache with id ${cacheId} not found. Make sure to call CacheStorage.get(${cacheId}) first.`);
235
- }
236
- return cache;
237
- }
238
-
239
- /**
240
- * Returns the IDs of all {@link CacheStorage} instances that have been
241
- * opened in this context via {@link CacheStorage.get}.
242
- */
243
- static getAllCacheIds() {
244
- return Array.from(CACHES.keys());
245
- }
246
- }
80
+ * A reactive interface for json stored in the browser [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) API.
81
+ *
82
+ * This is a good option for larger data sets than can be efficiently stored in localStorage
83
+ * but should not be used as a permanent DB or storage solution.
84
+ */
85
+ var CacheStorage = class CacheStorage {
86
+ #cache;
87
+ #cacheId;
88
+ #inititalization;
89
+ _data = /* @__PURE__ */ new Map();
90
+ _nextUpdate = null;
91
+ _bufferedEvents = [];
92
+ async #initialize() {
93
+ const cache = await caches.open(this.#cacheId);
94
+ this.#cache = cache;
95
+ this._data = await deserializeFromCache(cache);
96
+ return this;
97
+ }
98
+ constructor(cacheId) {
99
+ this.#cacheId = cacheId;
100
+ this.#cache = null;
101
+ this.#inititalization = this.#initialize();
102
+ }
103
+ get length() {
104
+ return this._data.size;
105
+ }
106
+ #update(key, oldValue, newValue) {
107
+ const cache = this.#cache;
108
+ if (!cache) throw new Error("Cache not initialized");
109
+ this._bufferedEvents.push({
110
+ storageArea: this.#cacheId,
111
+ key,
112
+ oldValue,
113
+ newValue
114
+ });
115
+ if (!this._nextUpdate) this._nextUpdate = window.setTimeout(() => {
116
+ const events = this._bufferedEvents;
117
+ this._bufferedEvents = [];
118
+ this._nextUpdate = null;
119
+ serializeToCache(cache, this._data);
120
+ for (const event of events) StorageEvents.postMessage(event);
121
+ }, 0);
122
+ }
123
+ clear() {
124
+ this._data = /* @__PURE__ */ new Map();
125
+ this.#update(null, null, null);
126
+ }
127
+ getItem(key) {
128
+ return this._data.get(key) ?? null;
129
+ }
130
+ key(index) {
131
+ ((test) => {
132
+ if (!test) throw new Error("ReactiveStorage.key: index must be a number");
133
+ })(typeof index === "number");
134
+ ((test) => {
135
+ if (!test) throw new Error("ReactiveStorage.key: index must be a non-negative integer");
136
+ })(index >= 0 && Number.isInteger(index));
137
+ ((test) => {
138
+ if (!test) throw new Error("ReactiveStorage.key: index must be less than the number of items in storage");
139
+ })(index < this.length);
140
+ let i = 0;
141
+ for (const value of this._data.keys()) {
142
+ if (i === index) return value;
143
+ i++;
144
+ }
145
+ return null;
146
+ }
147
+ removeItem(key) {
148
+ if (this._data.has(key)) {
149
+ const oldValue = this._data.get(key) ?? null;
150
+ this._data.delete(key);
151
+ this.#update(key, oldValue, null);
152
+ }
153
+ }
154
+ setItem(key, value) {
155
+ ((test) => {
156
+ if (!test) throw new Error("ReactiveStorage.setItem: key must be a string");
157
+ })(typeof key === "string");
158
+ ((test) => {
159
+ if (!test) throw new Error("ReactiveStorage.setItem: value must be a string");
160
+ })(typeof value === "string");
161
+ const oldValue = this._data.get(key) ?? null;
162
+ if (oldValue === value) return;
163
+ this._data.set(key, value);
164
+ this.#update(key, oldValue, value);
165
+ }
166
+ /**
167
+ * Get the singleton CacheStorage instance.
168
+ *
169
+ */
170
+ static get(cacheId = DEFAULT_CACHE_ID) {
171
+ let cache = CACHES.get(cacheId);
172
+ if (!cache) {
173
+ cache = new CacheStorage(cacheId);
174
+ CACHES.set(cacheId, cache);
175
+ }
176
+ return cache.#inititalization;
177
+ }
178
+ static expectCache(cacheId = DEFAULT_CACHE_ID) {
179
+ const cache = CACHES.get(cacheId);
180
+ if (!cache) throw new Error(`Cache with id ${cacheId} not found. Make sure to call CacheStorage.get(${cacheId}) first.`);
181
+ return cache;
182
+ }
183
+ /**
184
+ * Returns the IDs of all {@link CacheStorage} instances that have been
185
+ * opened in this context via {@link CacheStorage.get}.
186
+ */
187
+ static getAllCacheIds() {
188
+ return Array.from(CACHES.keys());
189
+ }
190
+ };
247
191
  function serializeToCache(cache, data) {
248
- const entries = Array.from(data.entries());
249
- const blob = JSON.stringify({
250
- version: 1,
251
- data: entries
252
- });
253
- const response = new Response(blob);
254
- return cache.put(CACHE_URL, response);
192
+ const entries = Array.from(data.entries());
193
+ const blob = JSON.stringify({
194
+ version: 1,
195
+ data: entries
196
+ });
197
+ const response = new Response(blob);
198
+ return cache.put(CACHE_URL, response);
255
199
  }
256
200
  function deserializeFromCache(cache) {
257
- return cache.match(CACHE_URL).then(response => {
258
- if (!response) {
259
- return new Map();
260
- }
261
- return response.json().then(json => {
262
- if (json.version !== 1) {
263
- throw new Error(`Unsupported cache version: ${json.version}`);
264
- }
265
- return new Map(json.data);
266
- });
267
- });
201
+ return cache.match(CACHE_URL).then((response) => {
202
+ if (!response) return /* @__PURE__ */ new Map();
203
+ return response.json().then((json) => {
204
+ if (json.version !== 1) throw new Error(`Unsupported cache version: ${json.version}`);
205
+ return new Map(json.data);
206
+ });
207
+ });
268
208
  }
269
209
 
210
+ //#endregion
211
+ //#region src/storage/storage.ts
270
212
  let localStorageInstance = null;
271
213
  let sessionStorageInstance = null;
272
214
  let localStorageOptions = {};
273
215
  let sessionStorageOptions = {};
274
-
275
- // This pattern enables us to have a singleton service-like
276
- // instance that also functions as a global singleton that
277
- // can be imported and used outside of Ember's DI system.
278
216
  /**
279
- * Retrieves the singleton instance of the LocalStorage service.
280
- *
281
- * If the instance does not already exist, it is created.
282
- */
217
+ * Retrieves the singleton instance of the LocalStorage service.
218
+ *
219
+ * If the instance does not already exist, it is created.
220
+ */
283
221
  function getLocalStorage() {
284
- if (!localStorageInstance) {
285
- localStorageInstance = new ReactiveStorage(localStorage, localStorageOptions);
286
- }
287
- return localStorageInstance;
222
+ if (!localStorageInstance) localStorageInstance = new ReactiveStorage(localStorage, localStorageOptions);
223
+ return localStorageInstance;
288
224
  }
289
225
  function getSessionStorage() {
290
- if (!sessionStorageInstance) {
291
- sessionStorageInstance = new ReactiveStorage(sessionStorage, sessionStorageOptions);
292
- }
293
- return sessionStorageInstance;
226
+ if (!sessionStorageInstance) sessionStorageInstance = new ReactiveStorage(sessionStorage, sessionStorageOptions);
227
+ return sessionStorageInstance;
294
228
  }
295
- const CacheStorages = new Map();
229
+ const CacheStorages = /* @__PURE__ */ new Map();
296
230
  function getCacheStorage(namespace = null) {
297
- const key = namespace ?? DEFAULT_CACHE_ID;
298
- let cache = CacheStorages.get(key);
299
- if (!cache) {
300
- const storage = CacheStorage.expectCache(key);
301
- cache = new ReactiveStorage(storage);
302
- CacheStorages.set(key, cache);
303
- }
304
- return cache;
231
+ const key = namespace ?? "reactive-cache";
232
+ let cache = CacheStorages.get(key);
233
+ if (!cache) {
234
+ const storage = CacheStorage.expectCache(key);
235
+ cache = new ReactiveStorage(storage);
236
+ CacheStorages.set(key, cache);
237
+ }
238
+ return cache;
305
239
  }
306
-
307
240
  /**
308
- * Configure options for the localStorage singleton.
309
- * Must be called before getLocalStorage() is first invoked.
310
- */
241
+ * Configure options for the localStorage singleton.
242
+ * Must be called before getLocalStorage() is first invoked.
243
+ */
311
244
  function configureLocalStorage(options) {
312
- localStorageOptions = options;
245
+ localStorageOptions = options;
313
246
  }
314
-
315
247
  /**
316
- * Configure options for the sessionStorage singleton.
317
- * Must be called before getSessionStorage() is first invoked.
318
- */
248
+ * Configure options for the sessionStorage singleton.
249
+ * Must be called before getSessionStorage() is first invoked.
250
+ */
319
251
  function configureSessionStorage(options) {
320
- sessionStorageOptions = options;
252
+ sessionStorageOptions = options;
321
253
  }
322
-
323
254
  /**
324
- * Check if an error is a quota exceeded error
325
- */
255
+ * Check if an error is a quota exceeded error
256
+ */
326
257
  function isQuotaExceededError(error) {
327
- if (error instanceof DOMException) {
328
- // Most browsers
329
- if (error.name === 'QuotaExceededError') return true;
330
- // Firefox
331
- if (error.name === 'NS_ERROR_DOM_QUOTA_REACHED') return true;
332
- }
333
- return false;
258
+ if (error instanceof DOMException) {
259
+ if (error.name === "QuotaExceededError") return true;
260
+ if (error.name === "NS_ERROR_DOM_QUOTA_REACHED") return true;
261
+ }
262
+ return false;
334
263
  }
335
-
336
264
  /**
337
- * Check if an error indicates storage is unavailable (private browsing, etc.)
338
- */
265
+ * Check if an error indicates storage is unavailable (private browsing, etc.)
266
+ */
339
267
  function isStorageUnavailableError(error) {
340
- if (error instanceof DOMException) {
341
- // Safari private browsing, some security restrictions
342
- if (error.name === 'SecurityError') return true;
343
- // Some browsers throw this in private mode
344
- if (error.name === 'InvalidStateError') return true;
345
- }
346
- return false;
268
+ if (error instanceof DOMException) {
269
+ if (error.name === "SecurityError") return true;
270
+ if (error.name === "InvalidStateError") return true;
271
+ }
272
+ return false;
347
273
  }
348
274
  /**
349
- * A reactive wrapper around the Web Storage API (localStorage/sessionStorage)
350
- * that provides signal-based access to storage items and length.
351
- *
352
- * Will automatically update when storage events occur in other tabs/windows.
353
- */
354
- class ReactiveStorage {
355
- _storage;
356
- _options;
357
- _memoryOnly = false;
358
- _values = {};
359
- _effects = new Map();
360
- setEffect(key, fn) {
361
- (test => {
362
- if (!test) {
363
- throw new Error(`Can only set effect once for key: ${key}`);
364
- }
365
- })(!this._effects.has(key));
366
- this._effects.set(key, fn);
367
- }
368
- constructor(storage, options = {}) {
369
- this._storage = storage;
370
- this._options = options;
371
- if (!(storage instanceof CacheStorage)) {
372
- // Test if storage is accessible
373
- try {
374
- const testKey = '__storage_test__';
375
- storage.setItem(testKey, testKey);
376
- storage.removeItem(testKey);
377
- this._length = storage.length;
378
- } catch (error) {
379
- if (options.fallbackToMemory && isStorageUnavailableError(error)) {
380
- this._memoryOnly = true;
381
- this._length = 0;
382
- } else {
383
- throw error;
384
- }
385
- }
386
- }
387
-
388
- // bind to localStorage events to trigger reactivity
389
- if (!this._memoryOnly) {
390
- window.addEventListener('storage', event => {
391
- const data = 'detail' in event ? event.detail : event;
392
-
393
- // Only react to changes in the same storage area
394
- if (data.storageArea === storage) {
395
- this._values[data.key] = data.newValue;
396
- this._signals.notify(data.key);
397
- this._length = this._storage.length;
398
- const effect = this._effects.get(data.key);
399
- if (effect) {
400
- effect(data);
401
- }
402
- }
403
- });
404
- }
405
- }
406
-
407
- /**
408
- * Reactive access to the number of keys in Storage
409
- */
410
- get length() {
411
- return this._length;
412
- }
413
-
414
- /**
415
- * Non-reactive way to peek the current value of a key in Storage
416
- */
417
- peekItem(key) {
418
- (test => {
419
- if (!test) {
420
- throw new Error('ReactiveStorage.peekItem: key must be a string');
421
- }
422
- })(typeof key === 'string');
423
- const keyStr = String(key);
424
- if (this._memoryOnly) {
425
- return this._values[keyStr] ?? null;
426
- }
427
- const value = this._values[keyStr];
428
- if (value !== undefined) {
429
- return value;
430
- }
431
-
432
- // Not yet loaded, fetch from storage, but don't track access
433
- const item = this._storage.getItem(keyStr);
434
- // this._values[keyStr] = item;
435
- return item;
436
- }
437
-
438
- /**
439
- * Reactive access to Storage contents
440
- */
441
- getItem(key) {
442
- (test => {
443
- if (!test) {
444
- throw new Error('ReactiveStorage.getItem: key must be a string');
445
- }
446
- })(typeof key === 'string');
447
- const keyStr = String(key);
448
- if (this._memoryOnly) {
449
- this._signals.subscribe(keyStr);
450
- return this._values[keyStr] ?? null;
451
- }
452
- const value = this._values[keyStr];
453
- if (value !== undefined) {
454
- this._signals.subscribe(keyStr);
455
- return value;
456
- }
457
-
458
- // Not yet loaded, fetch from storage
459
- const item = this._storage.getItem(keyStr);
460
- this._values[keyStr] = item;
461
- this._signals.subscribe(keyStr);
462
- return item;
463
- }
464
-
465
- /**
466
- * Set a value in Storage, triggering reactivity
467
- */
468
- setItem(key, value) {
469
- (test => {
470
- if (!test) {
471
- throw new Error('ReactiveStorage.setItem: key must be a string');
472
- }
473
- })(typeof key === 'string');
474
- (test => {
475
- if (!test) {
476
- throw new Error('ReactiveStorage.setItem: value must be a string');
477
- }
478
- })(typeof value === 'string');
479
-
480
- // Coerce to strings to match Storage API behavior
481
- const keyStr = String(key);
482
- const valueStr = String(value);
483
- const current = this._values[keyStr];
484
- if (current === valueStr) {
485
- // No change
486
- return;
487
- }
488
-
489
- // console.trace(`set field ${key} => ${String(value)}`);
490
-
491
- if (this._memoryOnly) {
492
- const currentValue = this._values[keyStr];
493
- if (currentValue === null || currentValue === undefined) {
494
- this._length += 1;
495
- }
496
- this._values[keyStr] = valueStr;
497
- this._signals.notify(keyStr);
498
- return;
499
- }
500
- try {
501
- this._storage.setItem(keyStr, valueStr);
502
- this._values[keyStr] = valueStr;
503
- this._signals.notify(keyStr);
504
- this._length = this._storage.length;
505
- } catch (error) {
506
- if (isQuotaExceededError(error)) {
507
- if (this._options.updateOnQuotaExceeded) {
508
- // Update signal state even though write failed
509
- this._values[keyStr] = valueStr;
510
- this._signals.notify(keyStr);
511
- }
512
- if (this._options.onQuotaExceeded) {
513
- const result = this._options.onQuotaExceeded(keyStr, valueStr);
514
- const retry = result instanceof Promise ? false : result;
515
- if (retry) {
516
- // Retry the write after callback freed space
517
- this._storage.setItem(keyStr, valueStr);
518
- this._length = this._storage.length;
519
- }
520
- } else {
521
- throw error;
522
- }
523
- } else {
524
- throw error;
525
- }
526
- }
527
- }
528
-
529
- /**
530
- * Remove a value from Storage, triggering reactivity
531
- */
532
- removeItem(key) {
533
- (test => {
534
- if (!test) {
535
- throw new Error('ReactiveStorage.removeItem: key must be a string');
536
- }
537
- })(typeof key === 'string');
538
- const keyStr = String(key);
539
- if (this._memoryOnly) {
540
- const value = this._values[keyStr];
541
- if (value !== null && value !== undefined) {
542
- this._length -= 1;
543
- }
544
- this._values[keyStr] = null;
545
- return;
546
- }
547
- this._storage.removeItem(keyStr);
548
- this._values[keyStr] = null;
549
- this._signals.notify(keyStr);
550
- this._length = this._storage.length;
551
- }
552
-
553
- /**
554
- * Clears all keys from Storage, triggering reactivity
555
- */
556
- clear() {
557
- this._values = {};
558
- this._signals.clear();
559
- this._length = 0;
560
- if (this._memoryOnly) {
561
- return;
562
- }
563
- this._storage.clear();
564
- }
565
-
566
- /**
567
- * Reactive access to the key at the given index
568
- */
569
- key(index) {
570
- (test => {
571
- if (!test) {
572
- throw new Error('ReactiveStorage.key: index must be a number');
573
- }
574
- })(typeof index === 'number');
575
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
576
- this._length; // track length access
577
-
578
- if (this._memoryOnly) {
579
- const keys = Object.keys(this._values).filter(k => this._values[k] !== null);
580
- return keys[index] ?? null;
581
- }
582
- return this._storage.key(index);
583
- }
584
- }
585
- defineSignal$1(ReactiveStorage.prototype, '_length', 0);
586
- defineSignal$1(ReactiveStorage.prototype, '_signals', makeInitializer(() => new SignalMap()));
587
-
275
+ * A reactive wrapper around the Web Storage API (localStorage/sessionStorage)
276
+ * that provides signal-based access to storage items and length.
277
+ *
278
+ * Will automatically update when storage events occur in other tabs/windows.
279
+ */
280
+ var ReactiveStorage = class {
281
+ _storage;
282
+ _options;
283
+ _memoryOnly = false;
284
+ _values = {};
285
+ _effects = /* @__PURE__ */ new Map();
286
+ setEffect(key, fn) {
287
+ ((test) => {
288
+ if (!test) throw new Error(`Can only set effect once for key: ${key}`);
289
+ })(!this._effects.has(key));
290
+ this._effects.set(key, fn);
291
+ }
292
+ constructor(storage, options = {}) {
293
+ this._storage = storage;
294
+ this._options = options;
295
+ if (!(storage instanceof CacheStorage)) try {
296
+ const testKey = "__storage_test__";
297
+ storage.setItem(testKey, testKey);
298
+ storage.removeItem(testKey);
299
+ this._length = storage.length;
300
+ } catch (error) {
301
+ if (options.fallbackToMemory && isStorageUnavailableError(error)) {
302
+ this._memoryOnly = true;
303
+ this._length = 0;
304
+ } else throw error;
305
+ }
306
+ if (!this._memoryOnly) window.addEventListener("storage", (event) => {
307
+ const data = "detail" in event ? event.detail : event;
308
+ if (data.storageArea === storage) {
309
+ this._values[data.key] = data.newValue;
310
+ this._signals.notify(data.key);
311
+ this._length = this._storage.length;
312
+ const effect = this._effects.get(data.key);
313
+ if (effect) effect(data);
314
+ }
315
+ });
316
+ }
317
+ /**
318
+ * Reactive access to the number of keys in Storage
319
+ */
320
+ get length() {
321
+ return this._length;
322
+ }
323
+ /**
324
+ * Non-reactive way to peek the current value of a key in Storage
325
+ */
326
+ peekItem(key) {
327
+ ((test) => {
328
+ if (!test) throw new Error("ReactiveStorage.peekItem: key must be a string");
329
+ })(typeof key === "string");
330
+ const keyStr = String(key);
331
+ if (this._memoryOnly) return this._values[keyStr] ?? null;
332
+ const value = this._values[keyStr];
333
+ if (value !== void 0) return value;
334
+ return this._storage.getItem(keyStr);
335
+ }
336
+ /**
337
+ * Reactive access to Storage contents
338
+ */
339
+ getItem(key) {
340
+ ((test) => {
341
+ if (!test) throw new Error("ReactiveStorage.getItem: key must be a string");
342
+ })(typeof key === "string");
343
+ const keyStr = String(key);
344
+ if (this._memoryOnly) {
345
+ this._signals.subscribe(keyStr);
346
+ return this._values[keyStr] ?? null;
347
+ }
348
+ const value = this._values[keyStr];
349
+ if (value !== void 0) {
350
+ this._signals.subscribe(keyStr);
351
+ return value;
352
+ }
353
+ const item = this._storage.getItem(keyStr);
354
+ this._values[keyStr] = item;
355
+ this._signals.subscribe(keyStr);
356
+ return item;
357
+ }
358
+ /**
359
+ * Set a value in Storage, triggering reactivity
360
+ */
361
+ setItem(key, value) {
362
+ ((test) => {
363
+ if (!test) throw new Error("ReactiveStorage.setItem: key must be a string");
364
+ })(typeof key === "string");
365
+ ((test) => {
366
+ if (!test) throw new Error("ReactiveStorage.setItem: value must be a string");
367
+ })(typeof value === "string");
368
+ const keyStr = String(key);
369
+ const valueStr = String(value);
370
+ if (this._values[keyStr] === valueStr) return;
371
+ if (this._memoryOnly) {
372
+ const currentValue = this._values[keyStr];
373
+ if (currentValue === null || currentValue === void 0) this._length += 1;
374
+ this._values[keyStr] = valueStr;
375
+ this._signals.notify(keyStr);
376
+ return;
377
+ }
378
+ try {
379
+ this._storage.setItem(keyStr, valueStr);
380
+ this._values[keyStr] = valueStr;
381
+ this._signals.notify(keyStr);
382
+ this._length = this._storage.length;
383
+ } catch (error) {
384
+ if (isQuotaExceededError(error)) {
385
+ if (this._options.updateOnQuotaExceeded) {
386
+ this._values[keyStr] = valueStr;
387
+ this._signals.notify(keyStr);
388
+ }
389
+ if (this._options.onQuotaExceeded) {
390
+ const result = this._options.onQuotaExceeded(keyStr, valueStr);
391
+ if (result instanceof Promise ? false : result) {
392
+ this._storage.setItem(keyStr, valueStr);
393
+ this._length = this._storage.length;
394
+ }
395
+ } else throw error;
396
+ } else throw error;
397
+ }
398
+ }
399
+ /**
400
+ * Remove a value from Storage, triggering reactivity
401
+ */
402
+ removeItem(key) {
403
+ ((test) => {
404
+ if (!test) throw new Error("ReactiveStorage.removeItem: key must be a string");
405
+ })(typeof key === "string");
406
+ const keyStr = String(key);
407
+ if (this._memoryOnly) {
408
+ const value = this._values[keyStr];
409
+ if (value !== null && value !== void 0) this._length -= 1;
410
+ this._values[keyStr] = null;
411
+ return;
412
+ }
413
+ this._storage.removeItem(keyStr);
414
+ this._values[keyStr] = null;
415
+ this._signals.notify(keyStr);
416
+ this._length = this._storage.length;
417
+ }
418
+ /**
419
+ * Clears all keys from Storage, triggering reactivity
420
+ */
421
+ clear() {
422
+ this._values = {};
423
+ this._signals.clear();
424
+ this._length = 0;
425
+ if (this._memoryOnly) return;
426
+ this._storage.clear();
427
+ }
428
+ /**
429
+ * Reactive access to the key at the given index
430
+ */
431
+ key(index) {
432
+ ((test) => {
433
+ if (!test) throw new Error("ReactiveStorage.key: index must be a number");
434
+ })(typeof index === "number");
435
+ this._length;
436
+ if (this._memoryOnly) return Object.keys(this._values).filter((k) => this._values[k] !== null)[index] ?? null;
437
+ return this._storage.key(index);
438
+ }
439
+ };
440
+ defineSignal(ReactiveStorage.prototype, "_length", 0);
441
+ defineSignal(ReactiveStorage.prototype, "_signals", makeInitializer(() => new SignalMap()));
442
+
443
+ //#endregion
444
+ //#region src/storage/-private/storage-infra.ts
588
445
  /**
589
- * Configuration options for fields that are also query parameters
590
- */
591
-
446
+ * Configuration options for fields that are also query parameters
447
+ */
592
448
  /**
593
- * Metadata attached to persisted resource classes
594
- */
449
+ * Metadata attached to persisted resource classes
450
+ */
595
451
  /**
596
- * A function which generates a unique primary-key
597
- * string for a given LocalResource or SessionResource
598
- * instance.
599
- *
600
- * Use functions when you want to create more than
601
- * one instance of a resource type, each with its own
602
- * persisted data.
603
- */
604
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
452
+ * A function which generates a unique primary-key
453
+ * string for a given LocalResource or SessionResource
454
+ * instance.
455
+ *
456
+ * Use functions when you want to create more than
457
+ * one instance of a resource type, each with its own
458
+ * persisted data.
459
+ */
605
460
  /**
606
- * Symbol used to store persistence metadata on classes
607
- */
608
- const PERSISTED_RESOURCE_META = Symbol('StorageResourceMeta');
609
-
461
+ * Symbol used to store persistence metadata on classes
462
+ */
463
+ const PERSISTED_RESOURCE_META = Symbol("StorageResourceMeta");
610
464
  /**
611
- * Setup persisted resource metadata on target
612
- * if not already present
613
- *
614
- * @private
615
- */
465
+ * Setup persisted resource metadata on target
466
+ * if not already present
467
+ *
468
+ * @private
469
+ */
616
470
  function initMeta(target) {
617
- let meta = target[PERSISTED_RESOURCE_META];
618
- if (!meta) {
619
- meta = {
620
- id: '',
621
- namespace: null,
622
- pkFn: null,
623
- type: '',
624
- fields: new Map(),
625
- initializers: new Map(),
626
- paramConfigs: null,
627
- paramCompanion: null,
628
- instances: null,
629
- typeOverrides: null
630
- };
631
- target[PERSISTED_RESOURCE_META] = meta;
632
- }
633
- return meta;
471
+ let meta = target[PERSISTED_RESOURCE_META];
472
+ if (!meta) {
473
+ meta = {
474
+ id: "",
475
+ namespace: null,
476
+ pkFn: null,
477
+ type: "",
478
+ fields: /* @__PURE__ */ new Map(),
479
+ initializers: /* @__PURE__ */ new Map(),
480
+ paramConfigs: null,
481
+ paramCompanion: null,
482
+ instances: null,
483
+ typeOverrides: null
484
+ };
485
+ target[PERSISTED_RESOURCE_META] = meta;
486
+ }
487
+ return meta;
634
488
  }
635
489
  function useMeta(meta, instance) {
636
- // If a primary key function is defined, use it to generate
637
- // a unique ID for this instance
638
- if (meta.id === '' && meta.pkFn) {
639
- // we are in an instance context
640
- meta.instances = meta.instances ?? new WeakMap();
641
- let instanceMeta = meta.instances.get(instance);
642
- if (!instanceMeta) {
643
- instanceMeta = {
644
- ...meta
645
- };
646
- const pk = meta.pkFn(instance);
647
- (test => {
648
- if (!test) {
649
- throw new Error('Primary key function must return a non-empty string.');
650
- }
651
- })(typeof pk === 'string' && pk.length > 0);
652
- instanceMeta.id = pk;
653
- meta.instances.set(instance, instanceMeta);
654
- }
655
- return instanceMeta;
656
- }
657
- (test => {
658
- if (!test) {
659
- throw new Error('StorageResourceMeta must have a valid id.');
660
- }
661
- })(typeof meta.id === 'string' && meta.id.length > 0);
662
- return meta;
490
+ if (meta.id === "" && meta.pkFn) {
491
+ meta.instances = meta.instances ?? /* @__PURE__ */ new WeakMap();
492
+ let instanceMeta = meta.instances.get(instance);
493
+ if (!instanceMeta) {
494
+ instanceMeta = { ...meta };
495
+ const pk = meta.pkFn(instance);
496
+ ((test) => {
497
+ if (!test) throw new Error("Primary key function must return a non-empty string.");
498
+ })(typeof pk === "string" && pk.length > 0);
499
+ instanceMeta.id = pk;
500
+ meta.instances.set(instance, instanceMeta);
501
+ }
502
+ return instanceMeta;
503
+ }
504
+ ((test) => {
505
+ if (!test) throw new Error("StorageResourceMeta must have a valid id.");
506
+ })(typeof meta.id === "string" && meta.id.length > 0);
507
+ return meta;
663
508
  }
664
-
665
509
  /**
666
- * Load storage field
667
- */
510
+ * Load storage field
511
+ */
668
512
  function getField(instance, meta, key, overrideType) {
669
- const type = overrideType || meta.type;
670
- const storage = getStorage(type, meta.namespace);
671
- const stored = storage.getItem(keyFor(meta, key));
672
- if (stored) {
673
- return JSON.parse(stored);
674
- } else {
675
- // No stored value, check for initializer
676
- const initializer = meta.initializers.get(key);
677
- if (initializer) {
678
- const value = initializer.call(instance);
679
- if (value !== undefined) {
680
- return value;
681
- }
682
- }
683
- }
684
- return null;
513
+ const stored = getStorage(overrideType || meta.type, meta.namespace).getItem(keyFor(meta, key));
514
+ if (stored) return JSON.parse(stored);
515
+ else {
516
+ const initializer = meta.initializers.get(key);
517
+ if (initializer) {
518
+ const value = initializer.call(instance);
519
+ if (value !== void 0) return value;
520
+ }
521
+ }
522
+ return null;
685
523
  }
686
524
  function keyFor(meta, key) {
687
- return `persisted:${meta.id}:${key}`;
525
+ return `persisted:${meta.id}:${key}`;
688
526
  }
689
527
  function getStorage(type, namespace) {
690
- if (type === 'local-resource') {
691
- return getLocalStorage();
692
- } else if (type === 'session-resource') {
693
- return getSessionStorage();
694
- } else {
695
- return getCacheStorage(namespace);
696
- }
528
+ if (type === "local-resource") return getLocalStorage();
529
+ else if (type === "session-resource") return getSessionStorage();
530
+ else return getCacheStorage(namespace);
697
531
  }
698
-
699
532
  /**
700
- * Update storage field
701
- */
533
+ * Update storage field
534
+ */
702
535
  function setField(meta, key, value, overrideType) {
703
- const type = overrideType || meta.type;
704
- const storage = getStorage(type, meta.namespace);
705
- storage.setItem(keyFor(meta, key), JSON.stringify(value));
536
+ getStorage(overrideType || meta.type, meta.namespace).setItem(keyFor(meta, key), JSON.stringify(value));
706
537
  }
707
538
  function getResourceMeta(target) {
708
- const meta = target[PERSISTED_RESOURCE_META];
709
- (test => {
710
- if (!test) {
711
- throw new Error('StorageResourceMeta not found on target. Did you forget to use the @LocalResource() or @SessionResource() decorator?');
712
- }
713
- })(meta !== undefined);
714
- return meta;
539
+ const meta = target[PERSISTED_RESOURCE_META];
540
+ ((test) => {
541
+ if (!test) throw new Error("StorageResourceMeta not found on target. Did you forget to use the @LocalResource() or @SessionResource() decorator?");
542
+ })(meta !== void 0);
543
+ return meta;
715
544
  }
716
545
  function _createStorageResource(id, type, namespace) {
717
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
718
- return function (target) {
719
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
720
- const meta = getResourceMeta(target.prototype);
721
- meta.id = typeof id === 'string' ? id : '';
722
- meta.pkFn = typeof id === 'function' ? id : null;
723
- meta.type = type;
724
- meta.namespace = namespace;
725
- if (meta.pkFn !== null) {
726
- // for dynamic instances, we install effects only once
727
- // fields have been defined and a key created for the
728
- // instance.
729
- // for this, we defer effect installation until
730
- // until object instantiation by wrapping the constructor
731
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
732
- const originalConstructor = target.prototype.constructor;
733
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
734
- target.prototype.constructor = function DynamicStorageInitializer(...args) {
735
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, new-cap
736
- const instance = new originalConstructor(...args);
737
- void installEffectsForDynamicInstance(meta, instance);
738
- return instance;
739
- };
740
- }
741
- if (!('toJSON' in target.prototype)) {
742
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
743
- target.prototype.toJSON = function () {
744
- const instanceMeta = useMeta(meta, this);
745
- // we only serialize the id and any localStorage fields, not sessionStorage fields.
746
- const isLocalResource = meta.type === 'local-resource' || meta.type === 'cache-resource';
747
- const fields = isLocalResource ? new Set(meta.fields.keys()) : new Set();
748
- for (const [key, overrideType] of meta.typeOverrides?.entries() ?? []) {
749
- if (overrideType === 'local-storage') {
750
- fields.add(key);
751
- } else if (overrideType === 'session-storage' && isLocalResource) {
752
- fields.delete(key);
753
- }
754
- }
755
- let idKey = '$key';
756
- while (fields.has(idKey)) {
757
- idKey = `_${idKey}`;
758
- }
759
- const data = {
760
- [idKey]: instanceMeta.id
761
- };
762
- for (const key of fields) {
763
- const value = getField(this, instanceMeta, key, meta.typeOverrides?.get(key) ?? null);
764
- if (value !== null) {
765
- data[key] = value;
766
- }
767
- }
768
- return data;
769
- };
770
- }
771
- };
546
+ return function(target) {
547
+ const meta = getResourceMeta(target.prototype);
548
+ meta.id = typeof id === "string" ? id : "";
549
+ meta.pkFn = typeof id === "function" ? id : null;
550
+ meta.type = type;
551
+ meta.namespace = namespace;
552
+ if (meta.pkFn !== null) {
553
+ const originalConstructor = target.prototype.constructor;
554
+ target.prototype.constructor = function DynamicStorageInitializer(...args) {
555
+ const instance = new originalConstructor(...args);
556
+ installEffectsForDynamicInstance(meta, instance);
557
+ return instance;
558
+ };
559
+ }
560
+ if (!("toJSON" in target.prototype)) target.prototype.toJSON = function() {
561
+ const instanceMeta = useMeta(meta, this);
562
+ const isLocalResource = meta.type === "local-resource" || meta.type === "cache-resource";
563
+ const fields = isLocalResource ? new Set(meta.fields.keys()) : /* @__PURE__ */ new Set();
564
+ for (const [key, overrideType] of meta.typeOverrides?.entries() ?? []) if (overrideType === "local-storage") fields.add(key);
565
+ else if (overrideType === "session-storage" && isLocalResource) fields.delete(key);
566
+ let idKey = "$key";
567
+ while (fields.has(idKey)) idKey = `_${idKey}`;
568
+ const data = { [idKey]: instanceMeta.id };
569
+ for (const key of fields) {
570
+ const value = getField(this, instanceMeta, key, meta.typeOverrides?.get(key) ?? null);
571
+ if (value !== null) data[key] = value;
572
+ }
573
+ return data;
574
+ };
575
+ };
772
576
  }
773
577
  async function installEffectsForDynamicInstance(meta, instance) {
774
- await Promise.resolve();
775
- for (const [key, value] of meta.fields.entries()) {
776
- if (typeof value === 'function') {
777
- void installEffect(useMeta(meta, instance), key);
778
- }
779
- }
578
+ await Promise.resolve();
579
+ for (const [key, value] of meta.fields.entries()) if (typeof value === "function") installEffect(useMeta(meta, instance), key);
780
580
  }
781
-
782
581
  /**
783
- * Returns the descriptor but is cast
784
- * to void to satisfy Typescript's incorrect
785
- * typing for legacy decorators.
786
- */
582
+ * Returns the descriptor but is cast
583
+ * to void to satisfy Typescript's incorrect
584
+ * typing for legacy decorators.
585
+ */
787
586
  function setupField(target, key, orgDesc, type) {
788
- const meta = initMeta(target);
789
- meta.fields.set(key, null);
790
- const overrideType = type === 'local' ? 'local-storage' : type === 'session' ? 'session-storage' : type === 'cache' ? 'cache-storage' : null;
791
- if (overrideType) {
792
- meta.typeOverrides = meta.typeOverrides || new Map();
793
- meta.typeOverrides.set(key, overrideType);
794
- }
795
- // @ts-expect-error initializer does exist
796
- const initializer = orgDesc?.initializer || null;
797
- meta.initializers.set(key, initializer);
798
- const desc = {
799
- configurable: true,
800
- enumerable: true,
801
- get() {
802
- return getField(this, useMeta(meta, this), key, overrideType);
803
- },
804
- set(value) {
805
- setField(useMeta(meta, this), key, value, overrideType);
806
- }
807
- };
808
- return memoized(target, key, desc);
587
+ const meta = initMeta(target);
588
+ meta.fields.set(key, null);
589
+ const overrideType = type === "local" ? "local-storage" : type === "session" ? "session-storage" : type === "cache" ? "cache-storage" : null;
590
+ if (overrideType) {
591
+ meta.typeOverrides = meta.typeOverrides || /* @__PURE__ */ new Map();
592
+ meta.typeOverrides.set(key, overrideType);
593
+ }
594
+ const initializer = orgDesc?.initializer || null;
595
+ meta.initializers.set(key, initializer);
596
+ return memoized(target, key, {
597
+ configurable: true,
598
+ enumerable: true,
599
+ get() {
600
+ return getField(this, useMeta(meta, this), key, overrideType);
601
+ },
602
+ set(value) {
603
+ setField(useMeta(meta, this), key, value, overrideType);
604
+ }
605
+ });
809
606
  }
810
607
  async function installEffect(meta, key) {
811
- await Promise.resolve();
812
- const effect = meta.fields.get(key);
813
- const storage = getStorage(meta.type, meta.namespace);
814
- storage.setEffect(keyFor(meta, key), event => {
815
- const oldValue = event.oldValue ? JSON.parse(event.oldValue) : null;
816
- const newValue = event.newValue ? JSON.parse(event.newValue) : null;
817
- effect({
818
- key,
819
- from: oldValue,
820
- to: newValue
821
- });
822
- });
608
+ await Promise.resolve();
609
+ const effect = meta.fields.get(key);
610
+ getStorage(meta.type, meta.namespace).setEffect(keyFor(meta, key), (event) => {
611
+ const oldValue = event.oldValue ? JSON.parse(event.oldValue) : null;
612
+ const newValue = event.newValue ? JSON.parse(event.newValue) : null;
613
+ effect({
614
+ key,
615
+ from: oldValue,
616
+ to: newValue
617
+ });
618
+ });
823
619
  }
824
-
825
620
  /**
826
- * Get or create the param companion object for a StorageResource instance.
827
- *
828
- * The companion object contains URL-serialized versions of all @param decorated fields.
829
- * Each param field gets a corresponding property on the companion that:
830
- * - Reads: Serializes the storage value to a URL string (returns null if not active)
831
- * - Writes: Deserializes the URL string and updates the storage value
832
- *
833
- * The companion object is reactive using trackedObject, ensuring that changes
834
- * to the underlying storage fields trigger updates in the query param system.
835
- *
836
- * This companion object is what QPRoute will bind to for URL query params.
837
- *
838
- * @private
839
- * @param instance - The StorageResource instance
840
- * @param groupControlMap - Optional map of fieldName -> controlFieldName for grouped params
841
- * @returns The companion object with serialized param properties
842
- */
621
+ * Get or create the param companion object for a StorageResource instance.
622
+ *
623
+ * The companion object contains URL-serialized versions of all @param decorated fields.
624
+ * Each param field gets a corresponding property on the companion that:
625
+ * - Reads: Serializes the storage value to a URL string (returns null if not active)
626
+ * - Writes: Deserializes the URL string and updates the storage value
627
+ *
628
+ * The companion object is reactive using trackedObject, ensuring that changes
629
+ * to the underlying storage fields trigger updates in the query param system.
630
+ *
631
+ * This companion object is what QPRoute will bind to for URL query params.
632
+ *
633
+ * @private
634
+ * @param instance - The StorageResource instance
635
+ * @param groupControlMap - Optional map of fieldName -> controlFieldName for grouped params
636
+ * @returns The companion object with serialized param properties
637
+ */
843
638
  function getParamCompanion(instance, groupControlMap) {
844
- const meta = getResourceMeta(instance);
845
- const instanceMeta = useMeta(meta, instance);
846
-
847
- // Return existing companion if already created
848
- if (instanceMeta.paramCompanion) {
849
- return instanceMeta.paramCompanion;
850
- }
851
-
852
- // Create new companion object
853
- const companion = {};
854
- instanceMeta.paramCompanion = companion;
855
-
856
- // Install a property for each param
857
- const paramConfigs = instanceMeta.paramConfigs;
858
- if (!paramConfigs || paramConfigs.size === 0) {
859
- return companion;
860
- }
861
- for (const [fieldName, config] of paramConfigs.entries()) {
862
- const controlFieldName = groupControlMap?.[fieldName];
863
- createParamField(companion, fieldName, config, controlFieldName, instance, meta);
864
- }
865
- return companion;
639
+ const meta = getResourceMeta(instance);
640
+ const instanceMeta = useMeta(meta, instance);
641
+ if (instanceMeta.paramCompanion) return instanceMeta.paramCompanion;
642
+ const companion = {};
643
+ instanceMeta.paramCompanion = companion;
644
+ const paramConfigs = instanceMeta.paramConfigs;
645
+ if (!paramConfigs || paramConfigs.size === 0) return companion;
646
+ for (const [fieldName, config] of paramConfigs.entries()) {
647
+ const controlFieldName = groupControlMap?.[fieldName];
648
+ createParamField(companion, fieldName, config, controlFieldName, instance, meta);
649
+ }
650
+ return companion;
866
651
  }
867
652
  function isActiveGroupParam(companion, controlFieldName) {
868
- // if the url value for the control param is `null` we are inactive
869
- const controlValue = companion[controlFieldName];
870
- return controlValue !== null;
653
+ return companion[controlFieldName] !== null;
871
654
  }
872
-
873
655
  /**
874
- * The default value of a param is determined by:
875
- * - the getDefault() function if provided, and its return is not undefined
876
- * - the initializer value provided to the field, if not undefined
877
- * - null otherwise
878
- *
879
- * The value is the deserialized form (i.e., the local storage value type)
880
- */
656
+ * The default value of a param is determined by:
657
+ * - the getDefault() function if provided, and its return is not undefined
658
+ * - the initializer value provided to the field, if not undefined
659
+ * - null otherwise
660
+ *
661
+ * The value is the deserialized form (i.e., the local storage value type)
662
+ */
881
663
  function getParamFieldDefaultValue(meta, config, fieldName, instance) {
882
- const defaultValue = config.getDefault?.(instance);
883
- if (defaultValue !== undefined) {
884
- return defaultValue;
885
- }
886
- const initializer = meta.initializers.get(fieldName);
887
- if (initializer) {
888
- const initValue = initializer.call(instance);
889
- if (initValue !== undefined) {
890
- return initValue;
891
- }
892
- }
893
- return null;
664
+ const defaultValue = config.getDefault?.(instance);
665
+ if (defaultValue !== void 0) return defaultValue;
666
+ const initializer = meta.initializers.get(fieldName);
667
+ if (initializer) {
668
+ const initValue = initializer.call(instance);
669
+ if (initValue !== void 0) return initValue;
670
+ }
671
+ return null;
894
672
  }
895
673
  function createParamField(companion, fieldName, config, controlFieldName, instance, meta) {
896
- const desc = {
897
- configurable: true,
898
- enumerable: true,
899
- get() {
900
- // Mark as initialized on first access, but continue with normal logic
901
- if (!config.initialized) {
902
- config.initialized = true;
903
- return null;
904
- }
905
-
906
- // Next, check if this param is part of a group and if its control param is active
907
- // If not active, return null to indicate inactive state
908
- if (controlFieldName && !isActiveGroupParam(companion, controlFieldName)) {
909
- return null;
910
- }
911
-
912
- // Next, check if the currently stored value is the default
913
- // value. If so, return null to indicate inactive state
914
- const defaultValue = getParamFieldDefaultValue(meta, config, fieldName, instance);
915
- const rawValue = instance[fieldName];
916
- if (rawValue === defaultValue) {
917
- return null;
918
- }
919
-
920
- // Serialize to URL format
921
- const serialized = config.serialize(rawValue, instance);
922
-
923
- // Return null if serialization returns empty, indicating no URL representation
924
- if (!serialized) {
925
- return null;
926
- }
927
- return serialized;
928
- },
929
- set(urlValue) {
930
- /**
931
- * set will never come from anything except URL deserialization.
932
- * which is managed by Ember.
933
- *
934
- * Our job is to not unnecessarily update the storage resource
935
- * so we need to run the compare function to see if the
936
- * incoming URL value is different from the existing local value.
937
- */
938
-
939
- // If null or empty, skip deserialization
940
- if (!urlValue) {
941
- return;
942
- }
943
- const rawValue = instance[fieldName];
944
-
945
- // Compare incoming URL value with existing local value
946
- const rawValueSerialized = config.serialize(rawValue, instance);
947
- if (rawValueSerialized === urlValue) {
948
- // No change
949
- return;
950
- }
951
-
952
- // Deserialize from URL format
953
- const newRawValue = config.deserialize(urlValue, instance);
954
-
955
- // Update the storage resource
956
- instance[fieldName] = newRawValue;
957
- }
958
- };
959
- memoized(companion, fieldName, desc);
960
- Object.defineProperty(companion, fieldName, desc);
961
- }
962
-
674
+ const desc = {
675
+ configurable: true,
676
+ enumerable: true,
677
+ get() {
678
+ if (!config.initialized) {
679
+ config.initialized = true;
680
+ return null;
681
+ }
682
+ if (controlFieldName && !isActiveGroupParam(companion, controlFieldName)) return null;
683
+ const defaultValue = getParamFieldDefaultValue(meta, config, fieldName, instance);
684
+ const rawValue = instance[fieldName];
685
+ if (rawValue === defaultValue) return null;
686
+ const serialized = config.serialize(rawValue, instance);
687
+ if (!serialized) return null;
688
+ return serialized;
689
+ },
690
+ set(urlValue) {
691
+ /**
692
+ * set will never come from anything except URL deserialization.
693
+ * which is managed by Ember.
694
+ *
695
+ * Our job is to not unnecessarily update the storage resource
696
+ * so we need to run the compare function to see if the
697
+ * incoming URL value is different from the existing local value.
698
+ */
699
+ if (!urlValue) return;
700
+ const rawValue = instance[fieldName];
701
+ if (config.serialize(rawValue, instance) === urlValue) return;
702
+ instance[fieldName] = config.deserialize(urlValue, instance);
703
+ }
704
+ };
705
+ memoized(companion, fieldName, desc);
706
+ Object.defineProperty(companion, fieldName, desc);
707
+ }
708
+
709
+ //#endregion
710
+ //#region src/storage/storage-resource.ts
963
711
  /**
964
- * Decorator which transforms a class into a StorageResource
965
- * persisted in localStorage.
966
- *
967
- * LocalResources must either be singletons or expect all instances
968
- * to share state unless a primary key function is provided.
969
- *
970
- * When a primary key function is provided, each instance
971
- * will have its own persisted data based on the key generated
972
- * by the function.
973
- *
974
- * The function will be called once per instance during
975
- * initialization to determine the unique ID for that instance.
976
- */
712
+ * Decorator which transforms a class into a StorageResource
713
+ * persisted in localStorage.
714
+ *
715
+ * LocalResources must either be singletons or expect all instances
716
+ * to share state unless a primary key function is provided.
717
+ *
718
+ * When a primary key function is provided, each instance
719
+ * will have its own persisted data based on the key generated
720
+ * by the function.
721
+ *
722
+ * The function will be called once per instance during
723
+ * initialization to determine the unique ID for that instance.
724
+ */
977
725
  function LocalResource(id) {
978
- return _createStorageResource(id, 'local-resource', null);
726
+ return _createStorageResource(id, "local-resource", null);
979
727
  }
980
-
981
728
  /**
982
- * Decorator which transforms a class into a StorageResource
983
- * persisted in sessionStorage.
984
- *
985
- * SessionResources must either be singletons or expect all instances
986
- * to share state unless a primary key function is provided.
987
- *
988
- * When a primary key function is provided, each instance
989
- * will have its own persisted data based on the key generated
990
- * by the function.
991
- *
992
- * The function will be called once per instance during
993
- * initialization to determine the unique ID for that instance.
994
- */
729
+ * Decorator which transforms a class into a StorageResource
730
+ * persisted in sessionStorage.
731
+ *
732
+ * SessionResources must either be singletons or expect all instances
733
+ * to share state unless a primary key function is provided.
734
+ *
735
+ * When a primary key function is provided, each instance
736
+ * will have its own persisted data based on the key generated
737
+ * by the function.
738
+ *
739
+ * The function will be called once per instance during
740
+ * initialization to determine the unique ID for that instance.
741
+ */
995
742
  function SessionResource(id) {
996
- return _createStorageResource(id, 'session-resource', null);
743
+ return _createStorageResource(id, "session-resource", null);
997
744
  }
998
-
999
745
  /**
1000
- * Decorator which transforms a class into a StorageResource
1001
- * persisted via the [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
1002
- * api and shared across all tabs/windows under the same origin.
1003
- *
1004
- * CacheResources must either be singletons or expect all instances
1005
- * to share state unless a primary key function is provided.
1006
- *
1007
- * When a primary key function is provided, each instance
1008
- * will have its own persisted data based on the key generated
1009
- * by the function.
1010
- *
1011
- * The function will be called once per instance during
1012
- * initialization to determine the unique ID for that instance.
1013
- *
1014
- * All object cached in the same `namespace` share the namespace's storage context,
1015
- * so partitioning can be achieved by using different namespaces for different groups
1016
- * of data.
1017
- */
746
+ * Decorator which transforms a class into a StorageResource
747
+ * persisted via the [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
748
+ * api and shared across all tabs/windows under the same origin.
749
+ *
750
+ * CacheResources must either be singletons or expect all instances
751
+ * to share state unless a primary key function is provided.
752
+ *
753
+ * When a primary key function is provided, each instance
754
+ * will have its own persisted data based on the key generated
755
+ * by the function.
756
+ *
757
+ * The function will be called once per instance during
758
+ * initialization to determine the unique ID for that instance.
759
+ *
760
+ * All object cached in the same `namespace` share the namespace's storage context,
761
+ * so partitioning can be achieved by using different namespaces for different groups
762
+ * of data.
763
+ */
1018
764
  function CacheResource(id, namespace = null) {
1019
- return _createStorageResource(id, 'cache-resource', namespace);
765
+ return _createStorageResource(id, "cache-resource", namespace);
1020
766
  }
1021
-
1022
767
  /**
1023
- * Decorator which marks a property as a field on
1024
- * a LocalResource or SessionResource
1025
- *
1026
- * The field's value will be initialized from the persisted resource data
1027
- * if available, falling back to the property's default value otherwise.
1028
- *
1029
- * Fields can be of any type that is serializable to and restorable from JSON,
1030
- * but complex types (like objects or arrays) should be handled with care to avoid
1031
- * unintended mutations or reactivity issues.
1032
- *
1033
- * By default, fields are persisted in the storage type defined by the resource decorator
1034
- * (@LocalResource or @SessionResource). However, you can override this behavior
1035
- * by passing 'local' or 'session' as an argument to the decorator.
1036
- *
1037
- * ---
1038
- *
1039
- * **Example:**
1040
- *
1041
- * ```ts
1042
- * @LocalResource('user-settings')
1043
- * class UserSettings {
1044
- * @field
1045
- * theme: 'light' | 'dark' = 'light';
1046
- *
1047
- * @field('session')
1048
- * sessionToken: string | null = null;
1049
- * }
1050
- * ```
1051
- *
1052
- */
1053
-
768
+ * Decorator which marks a property as a field on
769
+ * a LocalResource or SessionResource
770
+ *
771
+ * The field's value will be initialized from the persisted resource data
772
+ * if available, falling back to the property's default value otherwise.
773
+ *
774
+ * Fields can be of any type that is serializable to and restorable from JSON,
775
+ * but complex types (like objects or arrays) should be handled with care to avoid
776
+ * unintended mutations or reactivity issues.
777
+ *
778
+ * By default, fields are persisted in the storage type defined by the resource decorator
779
+ * (@LocalResource or @SessionResource). However, you can override this behavior
780
+ * by passing 'local' or 'session' as an argument to the decorator.
781
+ *
782
+ * ---
783
+ *
784
+ * **Example:**
785
+ *
786
+ * ```ts
787
+ * @LocalResource('user-settings')
788
+ * class UserSettings {
789
+ * @field
790
+ * theme: 'light' | 'dark' = 'light';
791
+ *
792
+ * @field('session')
793
+ * sessionToken: string | null = null;
794
+ * }
795
+ * ```
796
+ *
797
+ */
1054
798
  function field(...args) {
1055
- if (args.length === 0) {
1056
- return setupField;
1057
- }
1058
- if (args.length === 1) {
1059
- const type = args[0];
1060
- return (target, key, desc) => setupField(target, key, desc, type);
1061
- }
1062
- if (args.length === 2 || args.length === 3) {
1063
- const [target, key, descriptor] = args;
1064
- return setupField(target, key, descriptor);
1065
- }
799
+ if (args.length === 0) {
800
+ ((test) => {
801
+ if (!test) throw new Error("field decorator requires at least 1 argument or should be used without parens");
802
+ })(true);
803
+ return setupField;
804
+ }
805
+ if (args.length === 1) {
806
+ const type = args[0];
807
+ return (target, key, desc) => setupField(target, key, desc, type);
808
+ }
809
+ if (args.length === 2 || args.length === 3) {
810
+ const [target, key, descriptor] = args;
811
+ return setupField(target, key, descriptor);
812
+ }
1066
813
  }
1067
814
  function input(type) {
1068
- (test => {
1069
- if (!test) {
1070
- throw new Error('input decorator requires a valid type argument');
1071
- }
1072
- })(['number', 'boolean', 'float'].includes(type));
1073
- return function (target, key, desc) {
1074
- // eslint-disable-next-line @typescript-eslint/unbound-method
1075
- const originalSet = desc.set;
1076
- desc.set = function (value) {
1077
- switch (type) {
1078
- case 'number':
1079
- value = Number(value);
1080
- break;
1081
- case 'boolean':
1082
- value = value === 'false' || value === '' || value === '0' || value === 'undefined' || value === 'null' || value === false || value === 0 || value === null || value === undefined ? 0 : 1;
1083
- break;
1084
- default:
1085
- (test => {
1086
- {
1087
- throw new Error(`Unsupported input type: ${type}`);
1088
- }
1089
- })();
1090
- }
1091
- originalSet.call(this, value);
1092
- };
1093
- return desc;
1094
- };
815
+ ((test) => {
816
+ if (!test) throw new Error("input decorator requires a valid type argument");
817
+ })([
818
+ "number",
819
+ "boolean",
820
+ "float"
821
+ ].includes(type));
822
+ return function(target, key, desc) {
823
+ const originalSet = desc.set;
824
+ desc.set = function(value) {
825
+ switch (type) {
826
+ case "number":
827
+ value = Number(value);
828
+ break;
829
+ case "boolean":
830
+ value = value === "false" || value === "" || value === "0" || value === "undefined" || value === "null" || value === false || value === 0 || value === null || value === void 0 ? 0 : 1;
831
+ break;
832
+ default: ((test) => {
833
+ if (!test) throw new Error(`Unsupported input type: ${type}`);
834
+ })(false);
835
+ }
836
+ originalSet.call(this, value);
837
+ };
838
+ return desc;
839
+ };
1095
840
  }
1096
-
1097
841
  /**
1098
- * Effects are fields that run a side-effecting function.
1099
- *
1100
- * Effects are intended to enable synchronizing get states between
1101
- * tabs or windows that result in needing to synchronize other
1102
- * non-reactive state.
1103
- *
1104
- * For example, when a user selects a light/dark mode theme preference
1105
- * that differs from the system preference, effects can be used to trigger
1106
- * DOM updates on the documentElement necessary to ensure its state is
1107
- * consistent with the persisted resource state and reactive application state.
1108
- *
1109
- * To do this without an effect would require either setting up your own
1110
- * storage event listeners or consuming the reactive state of the property
1111
- * in another effect-like API such as an Ember modifier or React useEffect,
1112
- *
1113
- * Effects *only* run when the stored value changes due to storage events
1114
- * emitted from other tabs or windows. They do not run when the property
1115
- * is updated in the same context.
1116
- *
1117
- * ---
1118
- *
1119
- * **Example:**
1120
- *
1121
- * ```ts
1122
- * @LocalResource('user-preferences')
1123
- * class UserPreferences {
1124
- * @effect(syncThemeToDOM)
1125
- * explicitThemePreference: 'light' | 'dark' | null = null;
1126
- *
1127
- * @matchMedia('(prefers-color-scheme: dark)')
1128
- * systemPrefersDarkMode: boolean = false;
1129
- * }
1130
- *
1131
- * function syncThemeToDOM(update: ValueTransition<'light' | 'dark' | null>): void {
1132
- * const newTheme = update.to;
1133
- * document.documentElement.style.colorScheme = newTheme ?? 'light dark';
1134
- *
1135
- * if (newTheme === 'dark') {
1136
- * document.documentElement.classList.add('dark-theme');
1137
- * document.documentElement.classList.remove('light-theme');
1138
- * } else if (newTheme === 'light') {
1139
- * document.documentElement.classList.add('light-theme');
1140
- * document.documentElement.classList.remove('dark-theme');
1141
- * } else {
1142
- * document.documentElement.classList.remove('light-theme');
1143
- * document.documentElement.classList.remove('dark-theme');
1144
- * }
1145
- * }
1146
- * ```
1147
- */
842
+ * Effects are fields that run a side-effecting function.
843
+ *
844
+ * Effects are intended to enable synchronizing get states between
845
+ * tabs or windows that result in needing to synchronize other
846
+ * non-reactive state.
847
+ *
848
+ * For example, when a user selects a light/dark mode theme preference
849
+ * that differs from the system preference, effects can be used to trigger
850
+ * DOM updates on the documentElement necessary to ensure its state is
851
+ * consistent with the persisted resource state and reactive application state.
852
+ *
853
+ * To do this without an effect would require either setting up your own
854
+ * storage event listeners or consuming the reactive state of the property
855
+ * in another effect-like API such as an Ember modifier or React useEffect,
856
+ *
857
+ * Effects *only* run when the stored value changes due to storage events
858
+ * emitted from other tabs or windows. They do not run when the property
859
+ * is updated in the same context.
860
+ *
861
+ * ---
862
+ *
863
+ * **Example:**
864
+ *
865
+ * ```ts
866
+ * @LocalResource('user-preferences')
867
+ * class UserPreferences {
868
+ * @effect(syncThemeToDOM)
869
+ * explicitThemePreference: 'light' | 'dark' | null = null;
870
+ *
871
+ * @matchMedia('(prefers-color-scheme: dark)')
872
+ * systemPrefersDarkMode: boolean = false;
873
+ * }
874
+ *
875
+ * function syncThemeToDOM(update: ValueTransition<'light' | 'dark' | null>): void {
876
+ * const newTheme = update.to;
877
+ * document.documentElement.style.colorScheme = newTheme ?? 'light dark';
878
+ *
879
+ * if (newTheme === 'dark') {
880
+ * document.documentElement.classList.add('dark-theme');
881
+ * document.documentElement.classList.remove('light-theme');
882
+ * } else if (newTheme === 'light') {
883
+ * document.documentElement.classList.add('light-theme');
884
+ * document.documentElement.classList.remove('dark-theme');
885
+ * } else {
886
+ * document.documentElement.classList.remove('light-theme');
887
+ * document.documentElement.classList.remove('dark-theme');
888
+ * }
889
+ * }
890
+ * ```
891
+ */
1148
892
  function effect(fn, type) {
1149
- const overrideType = type === 'local' ? 'local-storage' : type === 'session' ? 'session-storage' : null;
1150
- return function effectField(target, key, _descriptor) {
1151
- const meta = initMeta(target);
1152
- meta.fields.set(key, fn);
1153
-
1154
- // only install the effect if we are not in a primary-keyed instance context
1155
- if (meta.pkFn === null) void installEffect(meta, key);
1156
- return {
1157
- configurable: true,
1158
- enumerable: true,
1159
- get() {
1160
- return getField(this, useMeta(meta, this), key, overrideType);
1161
- },
1162
- set(value) {
1163
- setField(useMeta(meta, this), key, value, overrideType);
1164
- }
1165
- };
1166
- };
1167
- }
1168
-
893
+ const overrideType = type === "local" ? "local-storage" : type === "session" ? "session-storage" : null;
894
+ return function effectField(target, key, _descriptor) {
895
+ const meta = initMeta(target);
896
+ meta.fields.set(key, fn);
897
+ if (meta.pkFn === null) installEffect(meta, key);
898
+ return {
899
+ configurable: true,
900
+ enumerable: true,
901
+ get() {
902
+ return getField(this, useMeta(meta, this), key, overrideType);
903
+ },
904
+ set(value) {
905
+ setField(useMeta(meta, this), key, value, overrideType);
906
+ }
907
+ };
908
+ };
909
+ }
910
+
911
+ //#endregion
1169
912
  export { CacheResource, CacheStorage, DEFAULT_CACHE_ID, LocalResource, SessionResource, getParamCompanion as _getParamCompanion, initMeta as _initMeta, configureLocalStorage, configureSessionStorage, effect, field, getCacheStorage, getLocalStorage, getSessionStorage, input };
913
+ //# sourceMappingURL=storage.js.map