gjendje 1.3.0 → 1.3.1

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.
@@ -4,7 +4,14 @@
4
4
  var globalConfig = {};
5
5
  var PERSISTENT_SCOPES = /* @__PURE__ */ new Set(["local", "session", "bucket"]);
6
6
  function configure(config) {
7
- globalConfig = { ...globalConfig, ...config };
7
+ for (const key of Object.keys(config)) {
8
+ const value = config[key];
9
+ if (value === void 0) {
10
+ delete globalConfig[key];
11
+ } else {
12
+ globalConfig[key] = value;
13
+ }
14
+ }
8
15
  if (globalConfig.registry === false && globalConfig.scope && PERSISTENT_SCOPES.has(globalConfig.scope)) {
9
16
  log(
10
17
  "warn",
@@ -12,6 +19,9 @@ function configure(config) {
12
19
  );
13
20
  }
14
21
  }
22
+ function resetConfig() {
23
+ globalConfig = {};
24
+ }
15
25
  function getConfig() {
16
26
  return globalConfig;
17
27
  }
@@ -102,16 +112,27 @@ function unregisterByKey(rKey) {
102
112
  function getRegistry() {
103
113
  return registry;
104
114
  }
115
+ function destroyAll() {
116
+ const instances = [...registry.values()];
117
+ for (const instance of instances) {
118
+ if (!instance.isDestroyed) {
119
+ instance.destroy();
120
+ }
121
+ }
122
+ registry.clear();
123
+ }
105
124
 
106
125
  exports.PERSISTENT_SCOPES = PERSISTENT_SCOPES;
107
126
  exports.configure = configure;
108
127
  exports.createListeners = createListeners;
128
+ exports.destroyAll = destroyAll;
109
129
  exports.getConfig = getConfig;
110
130
  exports.getRegistered = getRegistered;
111
131
  exports.getRegistry = getRegistry;
112
132
  exports.log = log;
113
133
  exports.registerNew = registerNew;
114
134
  exports.reportError = reportError;
135
+ exports.resetConfig = resetConfig;
115
136
  exports.safeCall = safeCall;
116
137
  exports.safeCallChange = safeCallChange;
117
138
  exports.safeCallConfig = safeCallConfig;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkARCB4TVB_cjs = require('./chunk-ARCB4TVB.cjs');
3
+ var chunk3YGK3425_cjs = require('./chunk-3YGK3425.cjs');
4
4
 
5
5
  // src/batch.ts
6
6
  var depth = 0;
@@ -26,8 +26,17 @@ function notify(fn) {
26
26
  }
27
27
  fn();
28
28
  }
29
+ var MAX_FLUSH_ITERATIONS = 100;
29
30
  function flush() {
31
+ let iterations = 0;
30
32
  while (queue.length > 0) {
33
+ if (++iterations > MAX_FLUSH_ITERATIONS) {
34
+ console.error(
35
+ "[gjendje] Batch flush exceeded maximum iterations \u2014 possible infinite loop. Remaining queued notifications have been dropped."
36
+ );
37
+ queue = [];
38
+ break;
39
+ }
31
40
  generation++;
32
41
  const current = queue;
33
42
  queue = [];
@@ -203,7 +212,7 @@ function notifyWatchers(watchers, prev, next) {
203
212
  const nextVal = nextObj?.[watchKey];
204
213
  if (!Object.is(prevVal, nextVal)) {
205
214
  for (const listener of listeners) {
206
- chunkARCB4TVB_cjs.safeCall(listener, nextVal);
215
+ chunk3YGK3425_cjs.safeCall(listener, nextVal);
207
216
  }
208
217
  }
209
218
  }
@@ -230,7 +239,7 @@ function readAndMigrate(raw, options, key, scope) {
230
239
  if (storedVersion < currentVersion && options.migrate) {
231
240
  data = runMigrations(data, storedVersion, currentVersion, options.migrate, key, scope);
232
241
  if (key && scope) {
233
- chunkARCB4TVB_cjs.safeCallConfig(chunkARCB4TVB_cjs.getConfig().onMigrate, {
242
+ chunk3YGK3425_cjs.safeCallConfig(chunk3YGK3425_cjs.getConfig().onMigrate, {
234
243
  key,
235
244
  scope,
236
245
  fromVersion: storedVersion,
@@ -241,19 +250,19 @@ function readAndMigrate(raw, options, key, scope) {
241
250
  }
242
251
  if (options.validate && !options.validate(data)) {
243
252
  if (key && scope) {
244
- const config = chunkARCB4TVB_cjs.getConfig();
245
- chunkARCB4TVB_cjs.safeCallConfig(config.onValidationFail, { key, scope, value: data });
253
+ const config = chunk3YGK3425_cjs.getConfig();
254
+ chunk3YGK3425_cjs.safeCallConfig(config.onValidationFail, { key, scope, value: data });
246
255
  const validationErr = new ValidationError(key, scope, data);
247
- chunkARCB4TVB_cjs.safeCallConfig(config.onError, { key, scope, error: validationErr });
256
+ chunk3YGK3425_cjs.safeCallConfig(config.onError, { key, scope, error: validationErr });
248
257
  }
249
258
  return defaultValue;
250
259
  }
251
260
  return data;
252
261
  } catch (err) {
253
- chunkARCB4TVB_cjs.log("debug", `Failed to read/migrate stored value \u2014 falling back to default.`);
262
+ chunk3YGK3425_cjs.log("debug", `Failed to read/migrate stored value \u2014 falling back to default.`);
254
263
  if (key && scope) {
255
264
  const readErr = new StorageReadError(key, scope, err);
256
- chunkARCB4TVB_cjs.safeCallConfig(chunkARCB4TVB_cjs.getConfig().onError, { key, scope, error: readErr });
265
+ chunk3YGK3425_cjs.safeCallConfig(chunk3YGK3425_cjs.getConfig().onError, { key, scope, error: readErr });
257
266
  }
258
267
  return defaultValue;
259
268
  }
@@ -282,7 +291,7 @@ function mergeKeys(stored, defaultValue, keys) {
282
291
  var MAX_MIGRATION_STEPS = 1e3;
283
292
  function runMigrations(data, fromVersion, toVersion, migrations, key, scope) {
284
293
  if (fromVersion < 0 || toVersion - fromVersion > MAX_MIGRATION_STEPS) {
285
- chunkARCB4TVB_cjs.log("warn", `Migration range v${fromVersion}\u2192v${toVersion} is out of bounds \u2014 skipping.`);
294
+ chunk3YGK3425_cjs.log("warn", `Migration range v${fromVersion}\u2192v${toVersion} is out of bounds \u2014 skipping.`);
286
295
  return data;
287
296
  }
288
297
  let current = data;
@@ -292,10 +301,10 @@ function runMigrations(data, fromVersion, toVersion, migrations, key, scope) {
292
301
  try {
293
302
  current = migrateFn(current);
294
303
  } catch (err) {
295
- chunkARCB4TVB_cjs.log("warn", `Migration from v${v} failed \u2014 returning partially migrated value.`);
304
+ chunk3YGK3425_cjs.log("warn", `Migration from v${v} failed \u2014 returning partially migrated value.`);
296
305
  if (key && scope) {
297
306
  const migrationErr = new MigrationError(key, scope, v, toVersion, err);
298
- chunkARCB4TVB_cjs.safeCallConfig(chunkARCB4TVB_cjs.getConfig().onError, { key, scope, error: migrationErr });
307
+ chunk3YGK3425_cjs.safeCallConfig(chunk3YGK3425_cjs.getConfig().onError, { key, scope, error: migrationErr });
299
308
  }
300
309
  return current;
301
310
  }
@@ -307,13 +316,22 @@ function runMigrations(data, fromVersion, toVersion, migrations, key, scope) {
307
316
  // src/adapters/storage.ts
308
317
  function createStorageAdapter(storage, key, options) {
309
318
  const { default: defaultValue, version, serialize, persist } = options;
310
- const listeners = chunkARCB4TVB_cjs.createListeners();
319
+ const listeners = chunk3YGK3425_cjs.createListeners();
311
320
  let cachedRaw;
312
321
  let cachedValue;
313
322
  let cacheValid = false;
314
323
  function parse(raw) {
315
324
  if (serialize) {
316
- return serialize.parse(raw);
325
+ const value = serialize.parse(raw);
326
+ if (options.validate && !options.validate(value)) {
327
+ const scope = options.scope ?? "local";
328
+ const config = chunk3YGK3425_cjs.getConfig();
329
+ chunk3YGK3425_cjs.safeCallConfig(config.onValidationFail, { key, scope, value });
330
+ const validationErr = new ValidationError(key, scope, value);
331
+ chunk3YGK3425_cjs.safeCallConfig(config.onError, { key, scope, error: validationErr });
332
+ return defaultValue;
333
+ }
334
+ return value;
317
335
  }
318
336
  return readAndMigrate(raw, options, key, options.scope);
319
337
  }
@@ -364,11 +382,11 @@ function createStorageAdapter(storage, key, options) {
364
382
  const isQuota = e instanceof DOMException && (e.name === "QuotaExceededError" || e.code === 22);
365
383
  const scope = options.scope ?? "local";
366
384
  const writeErr = new StorageWriteError(key, scope, e, isQuota);
367
- chunkARCB4TVB_cjs.log("error", writeErr.message);
385
+ chunk3YGK3425_cjs.log("error", writeErr.message);
368
386
  if (isQuota) {
369
- chunkARCB4TVB_cjs.safeCallConfig(chunkARCB4TVB_cjs.getConfig().onQuotaExceeded, { key, scope, error: writeErr });
387
+ chunk3YGK3425_cjs.safeCallConfig(chunk3YGK3425_cjs.getConfig().onQuotaExceeded, { key, scope, error: writeErr });
370
388
  }
371
- chunkARCB4TVB_cjs.reportError(key, scope, writeErr);
389
+ chunk3YGK3425_cjs.reportError(key, scope, writeErr);
372
390
  }
373
391
  }
374
392
  let lastNotifiedValue = defaultValue;
@@ -445,7 +463,7 @@ function parseQuota(quota) {
445
463
  function createBucketAdapter(key, bucketOptions, options) {
446
464
  const { default: defaultValue } = options;
447
465
  const fallbackScope = bucketOptions.fallback ?? "local";
448
- const listeners = chunkARCB4TVB_cjs.createListeners();
466
+ const listeners = chunk3YGK3425_cjs.createListeners();
449
467
  let lastNotifiedValue = defaultValue;
450
468
  const notifyListeners = () => listeners.notify(lastNotifiedValue);
451
469
  const fallbackStorage = fallbackScope === "session" ? sessionStorage : localStorage;
@@ -464,7 +482,7 @@ function createBucketAdapter(key, bucketOptions, options) {
464
482
  if (parsed != null) {
465
483
  openOptions.expires = parsed;
466
484
  } else {
467
- chunkARCB4TVB_cjs.log(
485
+ chunk3YGK3425_cjs.log(
468
486
  "warn",
469
487
  `Invalid bucket expires format: "${bucketOptions.expires}". Expected a number or a string like "7d", "24h", "30m".`
470
488
  );
@@ -475,7 +493,7 @@ function createBucketAdapter(key, bucketOptions, options) {
475
493
  if (parsed != null) {
476
494
  openOptions.quota = parsed;
477
495
  } else {
478
- chunkARCB4TVB_cjs.log(
496
+ chunk3YGK3425_cjs.log(
479
497
  "warn",
480
498
  `Invalid bucket quota format: "${bucketOptions.quota}". Expected a number or a string like "10mb", "50kb", "1gb".`
481
499
  );
@@ -496,17 +514,17 @@ function createBucketAdapter(key, bucketOptions, options) {
496
514
  }
497
515
  const bucketValue = delegate.get();
498
516
  if (hadUserWrite && shallowEqual(bucketValue, defaultValue)) {
499
- chunkARCB4TVB_cjs.safeCallConfig(chunkARCB4TVB_cjs.getConfig().onExpire, { key, scope: "bucket", expiredAt: Date.now() });
517
+ chunk3YGK3425_cjs.safeCallConfig(chunk3YGK3425_cjs.getConfig().onExpire, { key, scope: "bucket", expiredAt: Date.now() });
500
518
  }
501
519
  if (hadUserWrite) {
502
520
  delegate.set(currentValue);
503
521
  }
504
522
  } catch (err) {
505
- chunkARCB4TVB_cjs.log(
523
+ chunk3YGK3425_cjs.log(
506
524
  "warn",
507
525
  `Storage Bucket initialization failed for key "${key}" \u2014 using ${fallbackScope} fallback.`
508
526
  );
509
- chunkARCB4TVB_cjs.reportError(key, "bucket", err);
527
+ chunk3YGK3425_cjs.reportError(key, "bucket", err);
510
528
  }
511
529
  if (isDestroyed) return;
512
530
  const storedValue = delegate.get();
@@ -545,7 +563,7 @@ function createMemoryAdapter(defaultValue) {
545
563
  const listeners = /* @__PURE__ */ new Set();
546
564
  const notifyListeners = () => {
547
565
  for (const listener of listeners) {
548
- chunkARCB4TVB_cjs.safeCall(listener, current);
566
+ chunk3YGK3425_cjs.safeCall(listener, current);
549
567
  }
550
568
  };
551
569
  return {
@@ -578,13 +596,13 @@ function withSync(adapter, key, scope) {
578
596
  channel = new BroadcastChannel(channelName);
579
597
  } catch (err) {
580
598
  const syncErr = new SyncError(key, scope ?? "local", err);
581
- chunkARCB4TVB_cjs.log("warn", `Failed to create BroadcastChannel for key "${key}" \u2014 cross-tab sync disabled.`);
599
+ chunk3YGK3425_cjs.log("warn", `Failed to create BroadcastChannel for key "${key}" \u2014 cross-tab sync disabled.`);
582
600
  if (scope) {
583
- chunkARCB4TVB_cjs.reportError(key, scope, syncErr);
601
+ chunk3YGK3425_cjs.reportError(key, scope, syncErr);
584
602
  }
585
603
  }
586
604
  }
587
- const listeners = chunkARCB4TVB_cjs.createListeners();
605
+ const listeners = chunk3YGK3425_cjs.createListeners();
588
606
  let isDestroyed = false;
589
607
  const unsubscribeAdapter = adapter.subscribe((value) => {
590
608
  listeners.notify(value);
@@ -599,13 +617,13 @@ function withSync(adapter, key, scope) {
599
617
  try {
600
618
  adapter.set(value);
601
619
  if (scope) {
602
- chunkARCB4TVB_cjs.safeCallConfig(chunkARCB4TVB_cjs.getConfig().onSync, { key, scope, value, source: "remote" });
620
+ chunk3YGK3425_cjs.safeCallConfig(chunk3YGK3425_cjs.getConfig().onSync, { key, scope, value, source: "remote" });
603
621
  }
604
622
  } catch (err) {
605
623
  const syncErr = new SyncError(key, scope ?? "local", err);
606
- chunkARCB4TVB_cjs.log("error", syncErr.message);
624
+ chunk3YGK3425_cjs.log("error", syncErr.message);
607
625
  if (scope) {
608
- chunkARCB4TVB_cjs.reportError(key, scope, syncErr);
626
+ chunk3YGK3425_cjs.reportError(key, scope, syncErr);
609
627
  }
610
628
  }
611
629
  };
@@ -624,9 +642,9 @@ function withSync(adapter, key, scope) {
624
642
  channel.postMessage({ value });
625
643
  } catch (err) {
626
644
  const syncErr = new SyncError(key, scope ?? "local", err);
627
- chunkARCB4TVB_cjs.log("error", syncErr.message);
645
+ chunk3YGK3425_cjs.log("error", syncErr.message);
628
646
  if (scope) {
629
- chunkARCB4TVB_cjs.reportError(key, scope, syncErr);
647
+ chunk3YGK3425_cjs.reportError(key, scope, syncErr);
630
648
  }
631
649
  }
632
650
  }
@@ -648,11 +666,11 @@ function withSync(adapter, key, scope) {
648
666
  }
649
667
 
650
668
  // src/adapters/url.ts
651
- function createUrlAdapter(key, defaultValue, serializer, persist) {
669
+ function createUrlAdapter(key, defaultValue, serializer, persist, urlReplace) {
652
670
  if (typeof window === "undefined") {
653
671
  throw new Error("[gjendje] URL scope is not available in this environment.");
654
672
  }
655
- const listeners = chunkARCB4TVB_cjs.createListeners();
673
+ const listeners = chunk3YGK3425_cjs.createListeners();
656
674
  const defaultSerialized = serializer.stringify(defaultValue);
657
675
  let cachedSearch;
658
676
  let cachedValue = defaultValue;
@@ -688,7 +706,11 @@ function createUrlAdapter(key, defaultValue, serializer, persist) {
688
706
  }
689
707
  const search = params.toString();
690
708
  const newUrl = search ? `${window.location.pathname}?${search}${window.location.hash}` : `${window.location.pathname}${window.location.hash}`;
691
- window.history.pushState(null, "", newUrl);
709
+ if (urlReplace) {
710
+ window.history.replaceState(null, "", newUrl);
711
+ } else {
712
+ window.history.pushState(null, "", newUrl);
713
+ }
692
714
  cachedSearch = search ? `?${search}` : "";
693
715
  cachedValue = persist ? mergeKeys(toStore, defaultValue, persist) : value;
694
716
  } catch {
@@ -777,7 +799,8 @@ function resolveAdapter(storageKey, scope, options) {
777
799
  storageKey,
778
800
  options.default,
779
801
  options.serialize ?? { stringify: JSON.stringify, parse: JSON.parse },
780
- options.persist
802
+ options.persist,
803
+ options.urlReplace
781
804
  );
782
805
  case "server":
783
806
  if (!_serverAdapterFactory) {
@@ -846,11 +869,11 @@ var StateImpl = class {
846
869
  next = interceptor(next, prev);
847
870
  }
848
871
  } catch (err) {
849
- chunkARCB4TVB_cjs.reportError(this.key, this.scope, err);
872
+ chunk3YGK3425_cjs.reportError(this.key, this.scope, err);
850
873
  throw err;
851
874
  }
852
875
  if (!Object.is(original, next)) {
853
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onIntercept, {
876
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onIntercept, {
854
877
  key: this.key,
855
878
  scope: this.scope,
856
879
  original,
@@ -863,10 +886,10 @@ var StateImpl = class {
863
886
  const s = this._s;
864
887
  if (s.changeHandlers !== void 0 && s.changeHandlers.size > 0) {
865
888
  for (const hook of s.changeHandlers) {
866
- chunkARCB4TVB_cjs.safeCallChange(hook, next, prev);
889
+ chunk3YGK3425_cjs.safeCallChange(hook, next, prev);
867
890
  }
868
891
  }
869
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onChange, {
892
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onChange, {
870
893
  key: this.key,
871
894
  scope: this.scope,
872
895
  value: next,
@@ -897,7 +920,7 @@ var StateImpl = class {
897
920
  s.lastValue = next;
898
921
  this._adapter.set(next);
899
922
  s.settled = this._adapter.ready;
900
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onReset, { key: this.key, scope: this.scope, previousValue: prev });
923
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onReset, { key: this.key, scope: this.scope, previousValue: prev });
901
924
  this._notifyChange(next, prev);
902
925
  }
903
926
  get ready() {
@@ -953,7 +976,7 @@ var StateImpl = class {
953
976
  if (Object.hasOwn(prevRec, key)) {
954
977
  filtered[key] = partialRec[key];
955
978
  } else {
956
- chunkARCB4TVB_cjs.log("warn", `patch("${this.key}") ignored unknown key "${key}" (strict mode).`);
979
+ chunk3YGK3425_cjs.log("warn", `patch("${this.key}") ignored unknown key "${key}" (strict mode).`);
957
980
  }
958
981
  }
959
982
  return { ...prev, ...filtered };
@@ -971,8 +994,8 @@ var StateImpl = class {
971
994
  s.watchers?.clear();
972
995
  s.watchUnsub?.();
973
996
  this._adapter.destroy?.();
974
- chunkARCB4TVB_cjs.unregisterByKey(this._rKey);
975
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onDestroy, { key: this.key, scope: this.scope });
997
+ chunk3YGK3425_cjs.unregisterByKey(this._rKey);
998
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onDestroy, { key: this.key, scope: this.scope });
976
999
  if (s.resolveDestroyed) {
977
1000
  s.resolveDestroyed();
978
1001
  } else {
@@ -1049,11 +1072,11 @@ var MemoryStateImpl = class {
1049
1072
  next = interceptor(next, prev);
1050
1073
  }
1051
1074
  } catch (err) {
1052
- chunkARCB4TVB_cjs.reportError(this.key, this.scope, err);
1075
+ chunk3YGK3425_cjs.reportError(this.key, this.scope, err);
1053
1076
  throw err;
1054
1077
  }
1055
1078
  if (!Object.is(original, next)) {
1056
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onIntercept, {
1079
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onIntercept, {
1057
1080
  key: this.key,
1058
1081
  scope: this.scope,
1059
1082
  original,
@@ -1068,10 +1091,10 @@ var MemoryStateImpl = class {
1068
1091
  }
1069
1092
  if (ext !== void 0 && ext.changeHandlers !== void 0 && ext.changeHandlers.size > 0) {
1070
1093
  for (const hook of ext.changeHandlers) {
1071
- chunkARCB4TVB_cjs.safeCallChange(hook, next, prev);
1094
+ chunk3YGK3425_cjs.safeCallChange(hook, next, prev);
1072
1095
  }
1073
1096
  }
1074
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onChange, {
1097
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onChange, {
1075
1098
  key: this.key,
1076
1099
  scope: this.scope,
1077
1100
  value: next,
@@ -1085,7 +1108,7 @@ var MemoryStateImpl = class {
1085
1108
  c.listeners = listeners;
1086
1109
  c.notifyFn = () => {
1087
1110
  for (const l of listeners) {
1088
- chunkARCB4TVB_cjs.safeCall(l, c.current);
1111
+ chunk3YGK3425_cjs.safeCall(l, c.current);
1089
1112
  }
1090
1113
  };
1091
1114
  }
@@ -1108,11 +1131,11 @@ var MemoryStateImpl = class {
1108
1131
  next = interceptor(next, prev);
1109
1132
  }
1110
1133
  } catch (err) {
1111
- chunkARCB4TVB_cjs.reportError(this.key, this.scope, err);
1134
+ chunk3YGK3425_cjs.reportError(this.key, this.scope, err);
1112
1135
  throw err;
1113
1136
  }
1114
1137
  if (!Object.is(original, next)) {
1115
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onIntercept, {
1138
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onIntercept, {
1116
1139
  key: this.key,
1117
1140
  scope: this.scope,
1118
1141
  original,
@@ -1127,11 +1150,11 @@ var MemoryStateImpl = class {
1127
1150
  }
1128
1151
  if (ext !== void 0 && ext.changeHandlers !== void 0 && ext.changeHandlers.size > 0) {
1129
1152
  for (const hook of ext.changeHandlers) {
1130
- chunkARCB4TVB_cjs.safeCallChange(hook, next, prev);
1153
+ chunk3YGK3425_cjs.safeCallChange(hook, next, prev);
1131
1154
  }
1132
1155
  }
1133
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onReset, { key: this.key, scope: this.scope, previousValue: prev });
1134
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onChange, {
1156
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onReset, { key: this.key, scope: this.scope, previousValue: prev });
1157
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onChange, {
1135
1158
  key: this.key,
1136
1159
  scope: this.scope,
1137
1160
  value: next,
@@ -1192,7 +1215,7 @@ var MemoryStateImpl = class {
1192
1215
  if (Object.hasOwn(prevRec, key)) {
1193
1216
  filtered[key] = partialRec[key];
1194
1217
  } else {
1195
- chunkARCB4TVB_cjs.log("warn", `patch("${this.key}") ignored unknown key "${key}" (strict mode).`);
1218
+ chunk3YGK3425_cjs.log("warn", `patch("${this.key}") ignored unknown key "${key}" (strict mode).`);
1196
1219
  }
1197
1220
  }
1198
1221
  return { ...prev, ...filtered };
@@ -1227,8 +1250,8 @@ var MemoryStateImpl = class {
1227
1250
  }
1228
1251
  c.listeners?.clear();
1229
1252
  c.notifyFn = void 0;
1230
- if (this._rKey) chunkARCB4TVB_cjs.unregisterByKey(this._rKey);
1231
- chunkARCB4TVB_cjs.safeCallConfig(this._config.onDestroy, { key: this.key, scope: this.scope });
1253
+ if (this._rKey) chunk3YGK3425_cjs.unregisterByKey(this._rKey);
1254
+ chunk3YGK3425_cjs.safeCallConfig(this._config.onDestroy, { key: this.key, scope: this.scope });
1232
1255
  if (ext?.resolveDestroyed) {
1233
1256
  ext.resolveDestroyed();
1234
1257
  } else if (ext !== void 0) {
@@ -1240,7 +1263,7 @@ function createBase(key, options) {
1240
1263
  if (!key) {
1241
1264
  throw new Error("[gjendje] key must be a non-empty string.");
1242
1265
  }
1243
- const config = chunkARCB4TVB_cjs.getConfig();
1266
+ const config = chunk3YGK3425_cjs.getConfig();
1244
1267
  if (config.keyPattern && !config.keyPattern.test(key)) {
1245
1268
  throw new Error(
1246
1269
  `[gjendje] Key "${key}" does not match the configured keyPattern ${config.keyPattern}.`
@@ -1250,7 +1273,7 @@ function createBase(key, options) {
1250
1273
  const scope = rawScope === "render" ? "memory" : rawScope;
1251
1274
  if (scope === "memory") {
1252
1275
  if (options.sync || config.sync) {
1253
- chunkARCB4TVB_cjs.log(
1276
+ chunk3YGK3425_cjs.log(
1254
1277
  "warn",
1255
1278
  `sync: true is ignored for scope "memory". Only "local" and "bucket" scopes support cross-tab sync.`
1256
1279
  );
@@ -1258,27 +1281,27 @@ function createBase(key, options) {
1258
1281
  if (config.registry === false) {
1259
1282
  return new MemoryStateImpl(key, "", options, config);
1260
1283
  }
1261
- const rKey2 = chunkARCB4TVB_cjs.scopedKey(key, scope);
1262
- const existing2 = chunkARCB4TVB_cjs.getRegistered(rKey2);
1284
+ const rKey2 = chunk3YGK3425_cjs.scopedKey(key, scope);
1285
+ const existing2 = chunk3YGK3425_cjs.getRegistered(rKey2);
1263
1286
  if (existing2 && !existing2.isDestroyed) {
1264
1287
  if (config.warnOnDuplicate) {
1265
- chunkARCB4TVB_cjs.log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
1288
+ chunk3YGK3425_cjs.log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
1266
1289
  }
1267
1290
  return existing2;
1268
1291
  }
1269
1292
  const instance2 = new MemoryStateImpl(key, rKey2, options, config);
1270
- chunkARCB4TVB_cjs.registerNew(rKey2, key, scope, instance2, config, existing2);
1293
+ chunk3YGK3425_cjs.registerNew(rKey2, key, scope, instance2, config, existing2);
1271
1294
  return instance2;
1272
1295
  }
1273
- const rKey = chunkARCB4TVB_cjs.scopedKey(key, scope);
1274
- const existing = chunkARCB4TVB_cjs.getRegistered(rKey);
1296
+ const rKey = chunk3YGK3425_cjs.scopedKey(key, scope);
1297
+ const existing = chunk3YGK3425_cjs.getRegistered(rKey);
1275
1298
  if (existing && !existing.isDestroyed) {
1276
1299
  if (config.warnOnDuplicate) {
1277
- chunkARCB4TVB_cjs.log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
1300
+ chunk3YGK3425_cjs.log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
1278
1301
  }
1279
1302
  return existing;
1280
1303
  }
1281
- if (config.requireValidation && chunkARCB4TVB_cjs.PERSISTENT_SCOPES.has(scope) && !options.validate) {
1304
+ if (config.requireValidation && chunk3YGK3425_cjs.PERSISTENT_SCOPES.has(scope) && !options.validate) {
1282
1305
  throw new Error(
1283
1306
  `[gjendje] A validate function is required for persisted scope "${scope}" on state("${key}"). Set requireValidation: false in configure() to disable.`
1284
1307
  );
@@ -1287,7 +1310,7 @@ function createBase(key, options) {
1287
1310
  const useMemoryFallback = isSsrMode && isServer();
1288
1311
  const effectiveSync = options.sync ?? (config.sync && SYNCABLE_SCOPES.has(scope));
1289
1312
  if (effectiveSync && !SYNCABLE_SCOPES.has(scope)) {
1290
- chunkARCB4TVB_cjs.log(
1313
+ chunk3YGK3425_cjs.log(
1291
1314
  "warn",
1292
1315
  `sync: true is ignored for scope "${scope}". Only "local" and "bucket" scopes support cross-tab sync.`
1293
1316
  );
@@ -1310,22 +1333,22 @@ function createBase(key, options) {
1310
1333
  if (!shallowEqual(storedValue, options.default)) {
1311
1334
  instance.set(storedValue);
1312
1335
  }
1313
- chunkARCB4TVB_cjs.safeCallConfig(config.onHydrate, {
1336
+ chunk3YGK3425_cjs.safeCallConfig(config.onHydrate, {
1314
1337
  key,
1315
1338
  scope,
1316
1339
  serverValue: options.default,
1317
1340
  clientValue: storedValue
1318
1341
  });
1319
1342
  } catch (err) {
1320
- chunkARCB4TVB_cjs.log("debug", `Hydration adapter unavailable for state("${key}") \u2014 using memory fallback.`);
1343
+ chunk3YGK3425_cjs.log("debug", `Hydration adapter unavailable for state("${key}") \u2014 using memory fallback.`);
1321
1344
  const hydrationErr = new HydrationError(key, scope, err);
1322
- chunkARCB4TVB_cjs.reportError(key, scope, hydrationErr);
1345
+ chunk3YGK3425_cjs.reportError(key, scope, hydrationErr);
1323
1346
  } finally {
1324
1347
  realAdapter?.destroy?.();
1325
1348
  }
1326
1349
  });
1327
1350
  }
1328
- chunkARCB4TVB_cjs.registerNew(rKey, key, scope, instance, config, existing);
1351
+ chunk3YGK3425_cjs.registerNew(rKey, key, scope, instance, config, existing);
1329
1352
  return instance;
1330
1353
  }
1331
1354
 
@@ -1,4 +1,4 @@
1
- import { safeCall, getConfig, log, scopedKey, getRegistered, registerNew, PERSISTENT_SCOPES, reportError, safeCallConfig, safeCallChange, unregisterByKey, createListeners } from './chunk-FAISWCIZ.js';
1
+ import { safeCall, getConfig, log, scopedKey, getRegistered, registerNew, PERSISTENT_SCOPES, reportError, safeCallConfig, safeCallChange, unregisterByKey, createListeners } from './chunk-YPT6TO4H.js';
2
2
 
3
3
  // src/batch.ts
4
4
  var depth = 0;
@@ -24,8 +24,17 @@ function notify(fn) {
24
24
  }
25
25
  fn();
26
26
  }
27
+ var MAX_FLUSH_ITERATIONS = 100;
27
28
  function flush() {
29
+ let iterations = 0;
28
30
  while (queue.length > 0) {
31
+ if (++iterations > MAX_FLUSH_ITERATIONS) {
32
+ console.error(
33
+ "[gjendje] Batch flush exceeded maximum iterations \u2014 possible infinite loop. Remaining queued notifications have been dropped."
34
+ );
35
+ queue = [];
36
+ break;
37
+ }
29
38
  generation++;
30
39
  const current = queue;
31
40
  queue = [];
@@ -311,7 +320,16 @@ function createStorageAdapter(storage, key, options) {
311
320
  let cacheValid = false;
312
321
  function parse(raw) {
313
322
  if (serialize) {
314
- return serialize.parse(raw);
323
+ const value = serialize.parse(raw);
324
+ if (options.validate && !options.validate(value)) {
325
+ const scope = options.scope ?? "local";
326
+ const config = getConfig();
327
+ safeCallConfig(config.onValidationFail, { key, scope, value });
328
+ const validationErr = new ValidationError(key, scope, value);
329
+ safeCallConfig(config.onError, { key, scope, error: validationErr });
330
+ return defaultValue;
331
+ }
332
+ return value;
315
333
  }
316
334
  return readAndMigrate(raw, options, key, options.scope);
317
335
  }
@@ -646,7 +664,7 @@ function withSync(adapter, key, scope) {
646
664
  }
647
665
 
648
666
  // src/adapters/url.ts
649
- function createUrlAdapter(key, defaultValue, serializer, persist) {
667
+ function createUrlAdapter(key, defaultValue, serializer, persist, urlReplace) {
650
668
  if (typeof window === "undefined") {
651
669
  throw new Error("[gjendje] URL scope is not available in this environment.");
652
670
  }
@@ -686,7 +704,11 @@ function createUrlAdapter(key, defaultValue, serializer, persist) {
686
704
  }
687
705
  const search = params.toString();
688
706
  const newUrl = search ? `${window.location.pathname}?${search}${window.location.hash}` : `${window.location.pathname}${window.location.hash}`;
689
- window.history.pushState(null, "", newUrl);
707
+ if (urlReplace) {
708
+ window.history.replaceState(null, "", newUrl);
709
+ } else {
710
+ window.history.pushState(null, "", newUrl);
711
+ }
690
712
  cachedSearch = search ? `?${search}` : "";
691
713
  cachedValue = persist ? mergeKeys(toStore, defaultValue, persist) : value;
692
714
  } catch {
@@ -775,7 +797,8 @@ function resolveAdapter(storageKey, scope, options) {
775
797
  storageKey,
776
798
  options.default,
777
799
  options.serialize ?? { stringify: JSON.stringify, parse: JSON.parse },
778
- options.persist
800
+ options.persist,
801
+ options.urlReplace
779
802
  );
780
803
  case "server":
781
804
  if (!_serverAdapterFactory) {
@@ -2,7 +2,14 @@
2
2
  var globalConfig = {};
3
3
  var PERSISTENT_SCOPES = /* @__PURE__ */ new Set(["local", "session", "bucket"]);
4
4
  function configure(config) {
5
- globalConfig = { ...globalConfig, ...config };
5
+ for (const key of Object.keys(config)) {
6
+ const value = config[key];
7
+ if (value === void 0) {
8
+ delete globalConfig[key];
9
+ } else {
10
+ globalConfig[key] = value;
11
+ }
12
+ }
6
13
  if (globalConfig.registry === false && globalConfig.scope && PERSISTENT_SCOPES.has(globalConfig.scope)) {
7
14
  log(
8
15
  "warn",
@@ -10,6 +17,9 @@ function configure(config) {
10
17
  );
11
18
  }
12
19
  }
20
+ function resetConfig() {
21
+ globalConfig = {};
22
+ }
13
23
  function getConfig() {
14
24
  return globalConfig;
15
25
  }
@@ -100,5 +110,14 @@ function unregisterByKey(rKey) {
100
110
  function getRegistry() {
101
111
  return registry;
102
112
  }
113
+ function destroyAll() {
114
+ const instances = [...registry.values()];
115
+ for (const instance of instances) {
116
+ if (!instance.isDestroyed) {
117
+ instance.destroy();
118
+ }
119
+ }
120
+ registry.clear();
121
+ }
103
122
 
104
- export { PERSISTENT_SCOPES, configure, createListeners, getConfig, getRegistered, getRegistry, log, registerNew, reportError, safeCall, safeCallChange, safeCallConfig, scopedKey, unregisterByKey };
123
+ export { PERSISTENT_SCOPES, configure, createListeners, destroyAll, getConfig, getRegistered, getRegistry, log, registerNew, reportError, resetConfig, safeCall, safeCallChange, safeCallConfig, scopedKey, unregisterByKey };