gjendje 0.1.0

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,1136 @@
1
+ 'use strict';
2
+
3
+ var async_hooks = require('async_hooks');
4
+
5
+ // src/adapters/server.ts
6
+
7
+ // src/batch.ts
8
+ var depth = 0;
9
+ var queue = /* @__PURE__ */ new Set();
10
+ function batch(fn) {
11
+ depth++;
12
+ try {
13
+ fn();
14
+ } finally {
15
+ depth--;
16
+ if (depth === 0) flush();
17
+ }
18
+ }
19
+ function notify(fn) {
20
+ if (depth > 0) {
21
+ queue.add(fn);
22
+ return;
23
+ }
24
+ fn();
25
+ }
26
+ function flush() {
27
+ while (queue.size > 0) {
28
+ const pending = new Set(queue);
29
+ queue.clear();
30
+ depth++;
31
+ try {
32
+ for (const notification of pending) {
33
+ notification();
34
+ }
35
+ } finally {
36
+ depth--;
37
+ }
38
+ }
39
+ }
40
+
41
+ // src/adapters/server.ts
42
+ var als = new async_hooks.AsyncLocalStorage();
43
+ async function withServerSession(fn) {
44
+ const store = /* @__PURE__ */ new Map();
45
+ return als.run(store, fn);
46
+ }
47
+ function createServerAdapter(key, defaultValue) {
48
+ const listeners = /* @__PURE__ */ new Set();
49
+ function getStore() {
50
+ return als.getStore();
51
+ }
52
+ let lastNotifiedValue = defaultValue;
53
+ const notifyListeners = () => {
54
+ for (const listener of listeners) {
55
+ listener(lastNotifiedValue);
56
+ }
57
+ };
58
+ return {
59
+ ready: Promise.resolve(),
60
+ get() {
61
+ const store = getStore();
62
+ if (!store) return defaultValue;
63
+ return store.has(key) ? store.get(key) : defaultValue;
64
+ },
65
+ set(value) {
66
+ const store = getStore();
67
+ if (!store) {
68
+ throw new Error(
69
+ "[state] Cannot set server-scoped state outside of a server session. Wrap your request handler with withServerSession()."
70
+ );
71
+ }
72
+ store.set(key, value);
73
+ lastNotifiedValue = value;
74
+ notify(notifyListeners);
75
+ },
76
+ subscribe(listener) {
77
+ listeners.add(listener);
78
+ return () => {
79
+ listeners.delete(listener);
80
+ };
81
+ },
82
+ destroy() {
83
+ listeners.clear();
84
+ }
85
+ };
86
+ }
87
+
88
+ // src/config.ts
89
+ var globalConfig = {};
90
+ function configure(config) {
91
+ globalConfig = { ...globalConfig, ...config };
92
+ }
93
+ function getConfig() {
94
+ return globalConfig;
95
+ }
96
+ function log(level, message) {
97
+ const configLevel = globalConfig.logLevel ?? "warn";
98
+ if (configLevel === "silent") return;
99
+ const priority = { debug: 0, warn: 1, error: 2 };
100
+ if (priority[level] >= priority[configLevel]) {
101
+ console[level](`[stead] ${message}`);
102
+ }
103
+ }
104
+ function reportError(key, scope, error) {
105
+ globalConfig.onError?.({ key, scope, error });
106
+ }
107
+
108
+ // src/persist.ts
109
+ function readAndMigrate(raw, options, key, scope) {
110
+ const currentVersion = options.version ?? 1;
111
+ const defaultValue = options.default;
112
+ try {
113
+ let isVersionedValue2 = function(value) {
114
+ const hasShape = value !== null && typeof value === "object" && "v" in value && "data" in value;
115
+ return hasShape && typeof value.v === "number";
116
+ };
117
+ var isVersionedValue = isVersionedValue2;
118
+ const parsed = JSON.parse(raw);
119
+ let storedVersion = 1;
120
+ let data;
121
+ if (isVersionedValue2(parsed)) {
122
+ storedVersion = parsed.v;
123
+ data = parsed.data;
124
+ } else {
125
+ storedVersion = 1;
126
+ data = parsed;
127
+ }
128
+ if (storedVersion < currentVersion && options.migrate) {
129
+ data = runMigrations(data, storedVersion, currentVersion, options.migrate);
130
+ if (key && scope) {
131
+ getConfig().onMigrate?.({
132
+ key,
133
+ scope,
134
+ fromVersion: storedVersion,
135
+ toVersion: currentVersion,
136
+ data
137
+ });
138
+ }
139
+ }
140
+ if (options.validate && !options.validate(data)) {
141
+ return defaultValue;
142
+ }
143
+ return data;
144
+ } catch {
145
+ log("debug", `Failed to read/migrate stored value \u2014 falling back to default.`);
146
+ return defaultValue;
147
+ }
148
+ }
149
+ function wrapForStorage(value, version) {
150
+ if (!version || version === 1) {
151
+ return JSON.stringify(value);
152
+ }
153
+ const envelope = { v: version, data: value };
154
+ return JSON.stringify(envelope);
155
+ }
156
+ function pickKeys(value, keys) {
157
+ if (!keys || typeof value !== "object" || value === null) return value;
158
+ const partial = {};
159
+ for (const k of keys) {
160
+ if (k in value) {
161
+ partial[k] = value[k];
162
+ }
163
+ }
164
+ return partial;
165
+ }
166
+ function mergeKeys(stored, defaultValue, keys) {
167
+ if (!keys || typeof stored !== "object" || stored === null) return stored;
168
+ return { ...defaultValue, ...stored };
169
+ }
170
+ function runMigrations(data, fromVersion, toVersion, migrations) {
171
+ let current = data;
172
+ for (let v = fromVersion; v < toVersion; v++) {
173
+ const migrateFn = migrations[v];
174
+ if (migrateFn) {
175
+ try {
176
+ current = migrateFn(current);
177
+ } catch {
178
+ log("warn", `Migration from v${v} failed \u2014 returning partially migrated value.`);
179
+ return current;
180
+ }
181
+ }
182
+ }
183
+ return current;
184
+ }
185
+
186
+ // src/adapters/storage.ts
187
+ function createStorageAdapter(storage, key, options) {
188
+ const { default: defaultValue, version, serialize, persist } = options;
189
+ const listeners = /* @__PURE__ */ new Set();
190
+ function read() {
191
+ try {
192
+ const raw = storage.getItem(key);
193
+ if (raw === null) return defaultValue;
194
+ let value;
195
+ if (serialize) {
196
+ try {
197
+ value = serialize.parse(raw);
198
+ } catch {
199
+ return defaultValue;
200
+ }
201
+ } else {
202
+ value = readAndMigrate(raw, options, key, options.scope);
203
+ }
204
+ return mergeKeys(value, defaultValue, persist);
205
+ } catch {
206
+ return defaultValue;
207
+ }
208
+ }
209
+ function write(value) {
210
+ try {
211
+ const toStore = pickKeys(value, persist);
212
+ const raw = serialize ? serialize.stringify(toStore) : wrapForStorage(toStore, version);
213
+ storage.setItem(key, raw);
214
+ } catch (e) {
215
+ log(
216
+ "error",
217
+ `Failed to write key "${key}" to storage: ${e instanceof Error ? e.message : String(e)}`
218
+ );
219
+ const isQuotaError = e instanceof DOMException && (e.name === "QuotaExceededError" || e.code === 22);
220
+ if (isQuotaError && options.scope) {
221
+ getConfig().onQuotaExceeded?.({ key, scope: options.scope, error: e });
222
+ }
223
+ }
224
+ }
225
+ let lastNotifiedValue = defaultValue;
226
+ const notifyListeners = () => {
227
+ for (const listener of listeners) {
228
+ listener(lastNotifiedValue);
229
+ }
230
+ };
231
+ function onStorageEvent(event) {
232
+ if (event.storageArea !== storage || event.key !== key) return;
233
+ lastNotifiedValue = read();
234
+ notify(notifyListeners);
235
+ }
236
+ if (typeof window !== "undefined") {
237
+ window.addEventListener("storage", onStorageEvent);
238
+ }
239
+ return {
240
+ ready: Promise.resolve(),
241
+ get() {
242
+ return read();
243
+ },
244
+ set(value) {
245
+ write(value);
246
+ lastNotifiedValue = value;
247
+ notify(notifyListeners);
248
+ },
249
+ subscribe(listener) {
250
+ listeners.add(listener);
251
+ return () => {
252
+ listeners.delete(listener);
253
+ };
254
+ },
255
+ destroy() {
256
+ listeners.clear();
257
+ if (typeof window !== "undefined") {
258
+ window.removeEventListener("storage", onStorageEvent);
259
+ }
260
+ }
261
+ };
262
+ }
263
+
264
+ // src/adapters/bucket.ts
265
+ function isBucketSupported() {
266
+ return typeof navigator !== "undefined" && "storageBuckets" in navigator && navigator.storageBuckets != null;
267
+ }
268
+ function parseExpiry(expires) {
269
+ if (typeof expires === "number") return expires;
270
+ const units = {
271
+ ms: 1,
272
+ s: 1e3,
273
+ m: 6e4,
274
+ h: 36e5,
275
+ d: 864e5,
276
+ w: 6048e5
277
+ };
278
+ const match = expires.match(/^(\d+(?:\.\d+)?)(ms|s|m|h|d|w)$/);
279
+ if (!match || !match[1] || !match[2]) return void 0;
280
+ const value = parseFloat(match[1]);
281
+ const unit = units[match[2]];
282
+ if (!unit) return void 0;
283
+ return Date.now() + value * unit;
284
+ }
285
+ function parseQuota(quota) {
286
+ if (typeof quota === "number") return quota;
287
+ const units = {
288
+ b: 1,
289
+ kb: 1024,
290
+ mb: 1048576,
291
+ gb: 1073741824
292
+ };
293
+ const match = quota.toLowerCase().match(/^(\d+(?:\.\d+)?)(b|kb|mb|gb)$/);
294
+ if (!match || !match[1] || !match[2]) return void 0;
295
+ const value = parseFloat(match[1]);
296
+ const unit = units[match[2]];
297
+ if (!unit) return void 0;
298
+ return Math.floor(value * unit);
299
+ }
300
+ function createBucketAdapter(key, bucketOptions, options) {
301
+ const { default: defaultValue } = options;
302
+ const fallbackScope = bucketOptions.fallback ?? "local";
303
+ const listeners = /* @__PURE__ */ new Set();
304
+ let lastNotifiedValue = defaultValue;
305
+ const notifyListeners = () => {
306
+ for (const listener of listeners) {
307
+ listener(lastNotifiedValue);
308
+ }
309
+ };
310
+ let delegate = null;
311
+ let isDestroyed = false;
312
+ function read() {
313
+ if (!delegate) return defaultValue;
314
+ return delegate.get();
315
+ }
316
+ function write(value) {
317
+ if (!delegate) return;
318
+ delegate.set(value);
319
+ }
320
+ const ready = (async () => {
321
+ try {
322
+ if (!isBucketSupported()) {
323
+ throw new Error("Storage Buckets not supported");
324
+ }
325
+ const openOptions = {
326
+ persisted: bucketOptions.persisted ?? false,
327
+ durability: "strict"
328
+ };
329
+ if (bucketOptions.expires != null) {
330
+ const parsed = parseExpiry(bucketOptions.expires);
331
+ if (parsed != null) {
332
+ openOptions.expires = parsed;
333
+ } else {
334
+ console.warn(
335
+ `[state] Invalid bucket expires format: "${bucketOptions.expires}". Expected a number or a string like "7d", "24h", "30m".`
336
+ );
337
+ }
338
+ }
339
+ if (bucketOptions.quota != null) {
340
+ const parsed = parseQuota(bucketOptions.quota);
341
+ if (parsed != null) {
342
+ openOptions.quota = parsed;
343
+ } else {
344
+ console.warn(
345
+ `[state] Invalid bucket quota format: "${bucketOptions.quota}". Expected a number or a string like "10mb", "50kb", "1gb".`
346
+ );
347
+ }
348
+ }
349
+ const bucketManager = navigator.storageBuckets;
350
+ if (!bucketManager) throw new Error("Storage Buckets not supported");
351
+ const bucket = await bucketManager.open(bucketOptions.name, openOptions);
352
+ const storage = await bucket.localStorage();
353
+ delegate = createStorageAdapter(storage, key, options);
354
+ } catch {
355
+ await Promise.resolve();
356
+ const fallbackStorage = fallbackScope === "tab" ? sessionStorage : localStorage;
357
+ delegate = createStorageAdapter(fallbackStorage, key, options);
358
+ }
359
+ if (isDestroyed) {
360
+ delegate?.destroy?.();
361
+ delegate = null;
362
+ return;
363
+ }
364
+ const storedValue = delegate.get();
365
+ if (JSON.stringify(storedValue) !== JSON.stringify(defaultValue)) {
366
+ lastNotifiedValue = storedValue;
367
+ notify(notifyListeners);
368
+ }
369
+ delegate.subscribe((value) => {
370
+ lastNotifiedValue = value;
371
+ notify(notifyListeners);
372
+ });
373
+ })();
374
+ return {
375
+ ready,
376
+ get() {
377
+ return read();
378
+ },
379
+ set(value) {
380
+ write(value);
381
+ lastNotifiedValue = value;
382
+ notify(notifyListeners);
383
+ },
384
+ subscribe(listener) {
385
+ listeners.add(listener);
386
+ return () => {
387
+ listeners.delete(listener);
388
+ };
389
+ },
390
+ destroy() {
391
+ isDestroyed = true;
392
+ listeners.clear();
393
+ delegate?.destroy?.();
394
+ delegate = null;
395
+ }
396
+ };
397
+ }
398
+
399
+ // src/adapters/local.ts
400
+ function createLocalAdapter(key, options) {
401
+ if (typeof localStorage === "undefined") {
402
+ throw new Error(
403
+ '[state] localStorage is not available. Use ssr: true or scope: "server" for server environments.'
404
+ );
405
+ }
406
+ return createStorageAdapter(localStorage, key, options);
407
+ }
408
+
409
+ // src/adapters/render.ts
410
+ function createRenderAdapter(defaultValue) {
411
+ let current = defaultValue;
412
+ const listeners = /* @__PURE__ */ new Set();
413
+ const notifyListeners = () => {
414
+ for (const listener of listeners) {
415
+ listener(current);
416
+ }
417
+ };
418
+ return {
419
+ ready: Promise.resolve(),
420
+ get() {
421
+ return current;
422
+ },
423
+ set(value) {
424
+ current = value;
425
+ notify(notifyListeners);
426
+ },
427
+ subscribe(listener) {
428
+ listeners.add(listener);
429
+ return () => {
430
+ listeners.delete(listener);
431
+ };
432
+ },
433
+ destroy() {
434
+ listeners.clear();
435
+ }
436
+ };
437
+ }
438
+
439
+ // src/adapters/sync.ts
440
+ function withSync(adapter, key, scope) {
441
+ const channelName = `state:${key}`;
442
+ const channel = typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(channelName) : null;
443
+ const listeners = /* @__PURE__ */ new Set();
444
+ adapter.subscribe((value) => {
445
+ for (const listener of listeners) {
446
+ listener(value);
447
+ }
448
+ });
449
+ if (channel) {
450
+ channel.onmessage = (event) => {
451
+ if (event.data == null || typeof event.data !== "object" || !("value" in event.data)) return;
452
+ const value = event.data.value;
453
+ adapter.set(value);
454
+ if (scope) {
455
+ getConfig().onSync?.({ key, scope, value, source: "remote" });
456
+ }
457
+ };
458
+ }
459
+ return {
460
+ get ready() {
461
+ return adapter.ready;
462
+ },
463
+ get() {
464
+ return adapter.get();
465
+ },
466
+ set(value) {
467
+ adapter.set(value);
468
+ channel?.postMessage({ value });
469
+ },
470
+ subscribe(listener) {
471
+ listeners.add(listener);
472
+ return () => {
473
+ listeners.delete(listener);
474
+ };
475
+ },
476
+ destroy() {
477
+ listeners.clear();
478
+ channel?.close();
479
+ adapter.destroy?.();
480
+ }
481
+ };
482
+ }
483
+
484
+ // src/adapters/tab.ts
485
+ function createTabAdapter(key, options) {
486
+ if (typeof sessionStorage === "undefined") {
487
+ throw new Error(
488
+ '[state] sessionStorage is not available. Use ssr: true or scope: "render" for server environments.'
489
+ );
490
+ }
491
+ return createStorageAdapter(sessionStorage, key, options);
492
+ }
493
+
494
+ // src/adapters/url.ts
495
+ function createUrlAdapter(key, defaultValue, serializer, persist) {
496
+ if (typeof window === "undefined") {
497
+ throw new Error("[state] URL scope is not available in this environment.");
498
+ }
499
+ const listeners = /* @__PURE__ */ new Set();
500
+ function read() {
501
+ try {
502
+ const params = new URLSearchParams(window.location.search);
503
+ const raw = params.get(key);
504
+ if (raw === null) return defaultValue;
505
+ return mergeKeys(serializer.parse(decodeURIComponent(raw)), defaultValue, persist);
506
+ } catch {
507
+ return defaultValue;
508
+ }
509
+ }
510
+ function write(value) {
511
+ const params = new URLSearchParams(window.location.search);
512
+ const toStore = pickKeys(value, persist);
513
+ const stringified = serializer.stringify(toStore);
514
+ const isDefault = stringified === serializer.stringify(defaultValue);
515
+ if (isDefault) {
516
+ params.delete(key);
517
+ } else {
518
+ params.set(key, encodeURIComponent(stringified));
519
+ }
520
+ const search = params.toString();
521
+ const newUrl = search ? `${window.location.pathname}?${search}${window.location.hash}` : `${window.location.pathname}${window.location.hash}`;
522
+ window.history.pushState(null, "", newUrl);
523
+ }
524
+ let lastNotifiedValue = defaultValue;
525
+ const notifyListeners = () => {
526
+ for (const listener of listeners) {
527
+ listener(lastNotifiedValue);
528
+ }
529
+ };
530
+ function onPopState() {
531
+ lastNotifiedValue = read();
532
+ notify(notifyListeners);
533
+ }
534
+ window.addEventListener("popstate", onPopState);
535
+ return {
536
+ ready: Promise.resolve(),
537
+ get() {
538
+ return read();
539
+ },
540
+ set(value) {
541
+ write(value);
542
+ lastNotifiedValue = value;
543
+ notify(notifyListeners);
544
+ },
545
+ subscribe(listener) {
546
+ listeners.add(listener);
547
+ return () => {
548
+ listeners.delete(listener);
549
+ };
550
+ },
551
+ destroy() {
552
+ listeners.clear();
553
+ window.removeEventListener("popstate", onPopState);
554
+ }
555
+ };
556
+ }
557
+
558
+ // src/registry.ts
559
+ function scopedKey(key, scope) {
560
+ return `${scope}:${key}`;
561
+ }
562
+ var registry = /* @__PURE__ */ new Map();
563
+ function getRegistered(key, scope) {
564
+ return registry.get(scopedKey(key, scope));
565
+ }
566
+ function register(key, scope, instance) {
567
+ const rKey = scopedKey(key, scope);
568
+ if (registry.has(rKey)) {
569
+ const existing = registry.get(rKey);
570
+ if (existing?.isDestroyed) {
571
+ registry.set(rKey, instance);
572
+ } else {
573
+ const config2 = getConfig();
574
+ if (config2.warnOnDuplicate) {
575
+ log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
576
+ }
577
+ }
578
+ return;
579
+ }
580
+ const config = getConfig();
581
+ if (config.maxKeys !== void 0 && registry.size >= config.maxKeys) {
582
+ throw new Error(
583
+ `[stead] maxKeys limit (${config.maxKeys}) reached. Cannot register state("${key}") with scope "${scope}".`
584
+ );
585
+ }
586
+ registry.set(rKey, instance);
587
+ config.onRegister?.({ key, scope });
588
+ }
589
+ function unregister(key, scope) {
590
+ registry.delete(scopedKey(key, scope));
591
+ }
592
+
593
+ // src/ssr.ts
594
+ function isServer() {
595
+ return typeof window === "undefined" || typeof document === "undefined";
596
+ }
597
+ var BROWSER_SCOPES = /* @__PURE__ */ new Set(["tab", "local", "url", "bucket"]);
598
+ function afterHydration(fn) {
599
+ if (isServer()) return Promise.resolve();
600
+ return new Promise((resolve) => {
601
+ Promise.resolve().then(() => {
602
+ if (typeof requestAnimationFrame !== "undefined") {
603
+ requestAnimationFrame(() => {
604
+ fn();
605
+ resolve();
606
+ });
607
+ } else {
608
+ setTimeout(() => {
609
+ fn();
610
+ resolve();
611
+ }, 0);
612
+ }
613
+ });
614
+ });
615
+ }
616
+
617
+ // src/core.ts
618
+ var PERSISTENT_SCOPES = /* @__PURE__ */ new Set(["local", "tab", "bucket"]);
619
+ var SYNCABLE_SCOPES = /* @__PURE__ */ new Set(["local", "bucket"]);
620
+ function resolveStorageKey(key, options) {
621
+ if (options.prefix === false) return key;
622
+ const prefix = options.prefix ?? getConfig().prefix;
623
+ return prefix ? `${prefix}:${key}` : key;
624
+ }
625
+ function resolveAdapter(key, scope, options) {
626
+ const storageKey = resolveStorageKey(key, options);
627
+ switch (scope) {
628
+ case "render":
629
+ return createRenderAdapter(options.default);
630
+ case "tab":
631
+ return createTabAdapter(storageKey, options);
632
+ case "local":
633
+ return createLocalAdapter(storageKey, options);
634
+ case "url":
635
+ return createUrlAdapter(
636
+ storageKey,
637
+ options.default,
638
+ options.serialize ?? {
639
+ stringify: (v) => JSON.stringify(v),
640
+ parse: (s) => JSON.parse(s)
641
+ },
642
+ options.persist
643
+ );
644
+ case "server":
645
+ return createServerAdapter(storageKey, options.default);
646
+ case "bucket": {
647
+ if (!options.bucket) {
648
+ throw new Error(
649
+ '[state] scope: "bucket" requires a bucket option. Example: { scope: "bucket", bucket: { name: "my-bucket" } }'
650
+ );
651
+ }
652
+ return createBucketAdapter(storageKey, options.bucket, options);
653
+ }
654
+ default: {
655
+ const _exhaustive = scope;
656
+ throw new Error(`[state] Unknown scope: ${_exhaustive}`);
657
+ }
658
+ }
659
+ }
660
+ function createBase(key, options) {
661
+ if (!key) {
662
+ throw new Error("[state] key must be a non-empty string.");
663
+ }
664
+ const config = getConfig();
665
+ if (config.keyPattern && !config.keyPattern.test(key)) {
666
+ throw new Error(
667
+ `[stead] Key "${key}" does not match the configured keyPattern ${config.keyPattern}.`
668
+ );
669
+ }
670
+ const scope = options.scope ?? config.defaultScope ?? "render";
671
+ const existing = getRegistered(key, scope);
672
+ if (existing && !existing.isDestroyed) return existing;
673
+ if (config.requireValidation && PERSISTENT_SCOPES.has(scope) && !options.validate) {
674
+ throw new Error(
675
+ `[stead] A validate function is required for persisted scope "${scope}" on state("${key}"). Set requireValidation: false in configure() to disable.`
676
+ );
677
+ }
678
+ const defaultValue = options.default;
679
+ const isSsrMode = (options.ssr ?? config.ssr) && BROWSER_SCOPES.has(scope);
680
+ const useRenderFallback = isSsrMode && isServer();
681
+ const storageKey = resolveStorageKey(key, options);
682
+ const baseAdapter = useRenderFallback ? createRenderAdapter(defaultValue) : resolveAdapter(key, scope, options);
683
+ const effectiveSync = options.sync ?? (config.sync && SYNCABLE_SCOPES.has(scope));
684
+ if (effectiveSync && !SYNCABLE_SCOPES.has(scope)) {
685
+ log(
686
+ "warn",
687
+ `sync: true is ignored for scope "${scope}". Only "local" and "bucket" scopes support cross-tab sync.`
688
+ );
689
+ }
690
+ const shouldSync = effectiveSync && SYNCABLE_SCOPES.has(scope) && !useRenderFallback;
691
+ const adapter = shouldSync ? withSync(baseAdapter, storageKey, scope) : baseAdapter;
692
+ let lastValue = adapter.get();
693
+ let _isDestroyed = false;
694
+ const _interceptors = /* @__PURE__ */ new Set();
695
+ const _hooks = /* @__PURE__ */ new Set();
696
+ let _settled = Promise.resolve();
697
+ let _resolveDestroyed;
698
+ const _destroyed = new Promise((resolve) => {
699
+ _resolveDestroyed = resolve;
700
+ });
701
+ let _hydrated;
702
+ if (isSsrMode && !isServer()) {
703
+ _hydrated = afterHydration(() => {
704
+ try {
705
+ const realAdapter = resolveAdapter(key, scope, options);
706
+ const storedValue = realAdapter.get();
707
+ const serverValue = defaultValue;
708
+ const clientValue = storedValue;
709
+ if (JSON.stringify(storedValue) !== JSON.stringify(defaultValue)) {
710
+ instance.set(storedValue);
711
+ }
712
+ config.onHydrate?.({ key, scope, serverValue, clientValue });
713
+ } catch (err) {
714
+ log("debug", `Hydration adapter unavailable for state("${key}") \u2014 using render fallback.`);
715
+ reportError(key, scope, err);
716
+ }
717
+ });
718
+ } else {
719
+ _hydrated = Promise.resolve();
720
+ }
721
+ const instance = {
722
+ get() {
723
+ return _isDestroyed ? lastValue : adapter.get();
724
+ },
725
+ peek() {
726
+ return _isDestroyed ? lastValue : adapter.get();
727
+ },
728
+ set(valueOrUpdater) {
729
+ if (_isDestroyed) return;
730
+ const prev = adapter.get();
731
+ let next = typeof valueOrUpdater === "function" ? valueOrUpdater(prev) : valueOrUpdater;
732
+ for (const interceptor of _interceptors) {
733
+ next = interceptor(next, prev);
734
+ }
735
+ lastValue = next;
736
+ adapter.set(next);
737
+ _settled = adapter.ready;
738
+ for (const hook of _hooks) {
739
+ hook(next, prev);
740
+ }
741
+ },
742
+ subscribe(listener) {
743
+ return adapter.subscribe(listener);
744
+ },
745
+ reset() {
746
+ if (_isDestroyed) return;
747
+ const prev = adapter.get();
748
+ let next = defaultValue;
749
+ for (const interceptor of _interceptors) {
750
+ next = interceptor(next, prev);
751
+ }
752
+ lastValue = next;
753
+ adapter.set(next);
754
+ _settled = adapter.ready;
755
+ for (const hook of _hooks) {
756
+ hook(next, prev);
757
+ }
758
+ },
759
+ get ready() {
760
+ return adapter.ready;
761
+ },
762
+ get settled() {
763
+ return _settled;
764
+ },
765
+ get hydrated() {
766
+ return _hydrated;
767
+ },
768
+ get destroyed() {
769
+ return _destroyed;
770
+ },
771
+ get scope() {
772
+ return scope;
773
+ },
774
+ get key() {
775
+ return key;
776
+ },
777
+ get isDestroyed() {
778
+ return _isDestroyed;
779
+ },
780
+ intercept(fn) {
781
+ _interceptors.add(fn);
782
+ return () => {
783
+ _interceptors.delete(fn);
784
+ };
785
+ },
786
+ use(fn) {
787
+ _hooks.add(fn);
788
+ return () => {
789
+ _hooks.delete(fn);
790
+ };
791
+ },
792
+ destroy() {
793
+ if (_isDestroyed) return;
794
+ lastValue = adapter.get();
795
+ _isDestroyed = true;
796
+ _interceptors.clear();
797
+ _hooks.clear();
798
+ adapter.destroy?.();
799
+ unregister(key, scope);
800
+ config.onDestroy?.({ key, scope });
801
+ _resolveDestroyed();
802
+ }
803
+ };
804
+ register(key, scope, instance);
805
+ return instance;
806
+ }
807
+
808
+ // src/collection.ts
809
+ function collection(key, options) {
810
+ const base = createBase(key, options);
811
+ const watchers = /* @__PURE__ */ new Map();
812
+ let prevItems = base.get();
813
+ const unsubscribe = base.subscribe((next) => {
814
+ if (watchers.size === 0) {
815
+ prevItems = next;
816
+ return;
817
+ }
818
+ for (const [watchKey, listeners] of watchers) {
819
+ const keyChanged = (prev, next2) => {
820
+ if (!prev || !next2 || typeof prev !== "object" || typeof next2 !== "object") {
821
+ return false;
822
+ }
823
+ const p = prev;
824
+ const n = next2;
825
+ return !Object.is(p[watchKey], n[watchKey]);
826
+ };
827
+ const changed = next.length !== prevItems.length || next.some((item, i) => i < prevItems.length && keyChanged(prevItems[i], item));
828
+ if (changed) {
829
+ for (const listener of listeners) {
830
+ listener(next);
831
+ }
832
+ }
833
+ }
834
+ prevItems = next;
835
+ });
836
+ const originalDestroy = base.destroy.bind(base);
837
+ return {
838
+ get() {
839
+ return base.get();
840
+ },
841
+ peek() {
842
+ return base.peek();
843
+ },
844
+ set(valueOrUpdater) {
845
+ base.set(valueOrUpdater);
846
+ },
847
+ subscribe(listener) {
848
+ return base.subscribe(listener);
849
+ },
850
+ reset() {
851
+ base.reset();
852
+ },
853
+ watch(watchKey, listener) {
854
+ let listeners = watchers.get(watchKey);
855
+ if (!listeners) {
856
+ listeners = /* @__PURE__ */ new Set();
857
+ watchers.set(watchKey, listeners);
858
+ }
859
+ listeners.add(listener);
860
+ return () => {
861
+ listeners.delete(listener);
862
+ if (listeners.size === 0) {
863
+ watchers.delete(watchKey);
864
+ }
865
+ };
866
+ },
867
+ add(...items) {
868
+ base.set((prev) => [...prev, ...items]);
869
+ },
870
+ remove(predicate, options2) {
871
+ if (options2?.one) {
872
+ let removed = false;
873
+ base.set(
874
+ (prev) => prev.filter((item) => {
875
+ if (!removed && predicate(item)) {
876
+ removed = true;
877
+ return false;
878
+ }
879
+ return true;
880
+ })
881
+ );
882
+ } else {
883
+ base.set((prev) => prev.filter((item) => !predicate(item)));
884
+ }
885
+ },
886
+ update(predicate, patch, options2) {
887
+ const applyPatch = typeof patch === "function" ? patch : (item) => ({ ...item, ...patch });
888
+ if (options2?.one) {
889
+ let updated = false;
890
+ base.set(
891
+ (prev) => prev.map((item) => {
892
+ if (!updated && predicate(item)) {
893
+ updated = true;
894
+ return applyPatch(item);
895
+ }
896
+ return item;
897
+ })
898
+ );
899
+ } else {
900
+ base.set((prev) => prev.map((item) => predicate(item) ? applyPatch(item) : item));
901
+ }
902
+ },
903
+ find(predicate) {
904
+ return base.get().find(predicate);
905
+ },
906
+ findAll(predicate) {
907
+ return base.get().filter(predicate);
908
+ },
909
+ has(predicate) {
910
+ return base.get().some(predicate);
911
+ },
912
+ get size() {
913
+ return base.get().length;
914
+ },
915
+ clear() {
916
+ base.set([]);
917
+ },
918
+ intercept(fn) {
919
+ return base.intercept(fn);
920
+ },
921
+ use(fn) {
922
+ return base.use(fn);
923
+ },
924
+ get scope() {
925
+ return base.scope;
926
+ },
927
+ get key() {
928
+ return base.key;
929
+ },
930
+ get isDestroyed() {
931
+ return base.isDestroyed;
932
+ },
933
+ get ready() {
934
+ return base.ready;
935
+ },
936
+ get settled() {
937
+ return base.settled;
938
+ },
939
+ get hydrated() {
940
+ return base.hydrated;
941
+ },
942
+ get destroyed() {
943
+ return base.destroyed;
944
+ },
945
+ destroy() {
946
+ watchers.clear();
947
+ unsubscribe();
948
+ originalDestroy();
949
+ }
950
+ };
951
+ }
952
+
953
+ // src/computed.ts
954
+ function computed(deps, fn) {
955
+ const listeners = /* @__PURE__ */ new Set();
956
+ let cached;
957
+ let isDirty = true;
958
+ let isDestroyed = false;
959
+ function getDepValues() {
960
+ return deps.map((dep) => dep.get());
961
+ }
962
+ function recompute() {
963
+ if (!isDirty) return cached;
964
+ cached = fn(getDepValues());
965
+ isDirty = false;
966
+ return cached;
967
+ }
968
+ const notifyListeners = () => {
969
+ const value = recompute();
970
+ for (const listener of listeners) {
971
+ listener(value);
972
+ }
973
+ };
974
+ const unsubscribers = deps.map(
975
+ (dep) => dep.subscribe(() => {
976
+ isDirty = true;
977
+ notify(notifyListeners);
978
+ })
979
+ );
980
+ recompute();
981
+ let resolveDestroyed;
982
+ const destroyedPromise = new Promise((resolve) => {
983
+ resolveDestroyed = resolve;
984
+ });
985
+ return {
986
+ key: "",
987
+ scope: "render",
988
+ get ready() {
989
+ return Promise.all(deps.map((d) => d.ready)).then(() => void 0);
990
+ },
991
+ get settled() {
992
+ return Promise.all(deps.map((d) => d.settled)).then(() => void 0);
993
+ },
994
+ get hydrated() {
995
+ return Promise.all(deps.map((d) => d.hydrated)).then(() => void 0);
996
+ },
997
+ get destroyed() {
998
+ return destroyedPromise;
999
+ },
1000
+ get isDestroyed() {
1001
+ return isDestroyed;
1002
+ },
1003
+ get() {
1004
+ return recompute();
1005
+ },
1006
+ peek() {
1007
+ return recompute();
1008
+ },
1009
+ subscribe(listener) {
1010
+ listeners.add(listener);
1011
+ return () => {
1012
+ listeners.delete(listener);
1013
+ };
1014
+ },
1015
+ destroy() {
1016
+ if (isDestroyed) return;
1017
+ isDestroyed = true;
1018
+ for (const unsub of unsubscribers) {
1019
+ unsub();
1020
+ }
1021
+ listeners.clear();
1022
+ resolveDestroyed();
1023
+ }
1024
+ };
1025
+ }
1026
+
1027
+ // src/effect.ts
1028
+ function effect(deps, fn) {
1029
+ let cleanup;
1030
+ let isStopped = false;
1031
+ function getDepValues() {
1032
+ return deps.map((dep) => dep.get());
1033
+ }
1034
+ function run() {
1035
+ if (isStopped) return;
1036
+ if (cleanup) {
1037
+ cleanup();
1038
+ cleanup = void 0;
1039
+ }
1040
+ cleanup = fn(getDepValues());
1041
+ }
1042
+ const unsubscribers = deps.map((dep) => dep.subscribe(() => run()));
1043
+ run();
1044
+ return {
1045
+ stop() {
1046
+ if (isStopped) return;
1047
+ isStopped = true;
1048
+ for (const unsub of unsubscribers) {
1049
+ unsub();
1050
+ }
1051
+ if (cleanup) {
1052
+ cleanup();
1053
+ cleanup = void 0;
1054
+ }
1055
+ }
1056
+ };
1057
+ }
1058
+
1059
+ // src/enhancers/watch.ts
1060
+ function withWatch(instance) {
1061
+ const watchers = /* @__PURE__ */ new Map();
1062
+ let prev = instance.get();
1063
+ const unsubscribe = instance.subscribe((next) => {
1064
+ if (watchers.size === 0) {
1065
+ prev = next;
1066
+ return;
1067
+ }
1068
+ if (next === null || typeof next !== "object") {
1069
+ prev = next;
1070
+ return;
1071
+ }
1072
+ for (const [watchKey, listeners] of watchers) {
1073
+ const prevVal = prev !== null && typeof prev === "object" ? prev[watchKey] : void 0;
1074
+ const nextVal = next[watchKey];
1075
+ if (!Object.is(prevVal, nextVal)) {
1076
+ for (const listener of listeners) {
1077
+ listener(nextVal);
1078
+ }
1079
+ }
1080
+ }
1081
+ prev = next;
1082
+ });
1083
+ const originalDestroy = instance.destroy.bind(instance);
1084
+ const result = Object.create(instance);
1085
+ result.watch = (watchKey, listener) => {
1086
+ if (!watchers.has(watchKey)) {
1087
+ watchers.set(watchKey, /* @__PURE__ */ new Set());
1088
+ }
1089
+ const listeners = watchers.get(watchKey);
1090
+ if (!listeners) return () => {
1091
+ };
1092
+ listeners.add(listener);
1093
+ return () => {
1094
+ listeners.delete(listener);
1095
+ if (listeners.size === 0) {
1096
+ watchers.delete(watchKey);
1097
+ }
1098
+ };
1099
+ };
1100
+ result.destroy = () => {
1101
+ watchers.clear();
1102
+ unsubscribe();
1103
+ originalDestroy();
1104
+ };
1105
+ return result;
1106
+ }
1107
+
1108
+ // src/factory.ts
1109
+ var instanceCache = /* @__PURE__ */ new Map();
1110
+ function state(key, options) {
1111
+ const config = getConfig();
1112
+ const scope = options.scope ?? config.defaultScope ?? "render";
1113
+ const ck = scopedKey(key, scope);
1114
+ const cached = instanceCache.get(ck);
1115
+ if (cached && !cached.isDestroyed) {
1116
+ if (config.warnOnDuplicate) {
1117
+ log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
1118
+ }
1119
+ return cached;
1120
+ }
1121
+ const base = createBase(key, options);
1122
+ const instance = withWatch(base);
1123
+ instanceCache.set(ck, instance);
1124
+ return instance;
1125
+ }
1126
+
1127
+ exports.batch = batch;
1128
+ exports.collection = collection;
1129
+ exports.computed = computed;
1130
+ exports.configure = configure;
1131
+ exports.effect = effect;
1132
+ exports.state = state;
1133
+ exports.withServerSession = withServerSession;
1134
+ exports.withWatch = withWatch;
1135
+ //# sourceMappingURL=chunk-GGB5QBDR.cjs.map
1136
+ //# sourceMappingURL=chunk-GGB5QBDR.cjs.map