gjendje 1.3.3 → 1.3.4

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.
@@ -644,6 +644,7 @@ function createBucketAdapter(key, bucketOptions, options) {
644
644
  notify(notifyListeners);
645
645
  }
646
646
  }
647
+ if (isDestroyed) return;
647
648
  delegateUnsub = delegate.subscribe((value) => {
648
649
  lastNotifiedValue = value;
649
650
  notify(notifyListeners);
@@ -642,6 +642,7 @@ function createBucketAdapter(key, bucketOptions, options) {
642
642
  notify(notifyListeners);
643
643
  }
644
644
  }
645
+ if (isDestroyed) return;
645
646
  delegateUnsub = delegate.subscribe((value) => {
646
647
  lastNotifiedValue = value;
647
648
  notify(notifyListeners);
package/dist/index.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var chunkG6VLCNVY_cjs = require('./chunk-G6VLCNVY.cjs');
3
+ var chunkFD2LRNLT_cjs = require('./chunk-FD2LRNLT.cjs');
4
4
  var chunk3K5IERQC_cjs = require('./chunk-3K5IERQC.cjs');
5
5
 
6
6
  // src/collection.ts
7
7
  function collection(key, options) {
8
- const base = chunkG6VLCNVY_cjs.createBase(key, options);
8
+ const base = chunkFD2LRNLT_cjs.createBase(key, options);
9
9
  let watchers;
10
10
  let prevItems;
11
11
  let unsubscribe;
@@ -19,7 +19,8 @@ function collection(key, options) {
19
19
  prevItems = next;
20
20
  return;
21
21
  }
22
- const lengthChanged = next.length !== prevItems.length;
22
+ const prev = prevItems ?? [];
23
+ const lengthChanged = next.length !== prev.length;
23
24
  if (lengthChanged) {
24
25
  for (const [, listeners] of w) {
25
26
  for (const listener of listeners) {
@@ -32,11 +33,11 @@ function collection(key, options) {
32
33
  const len = next.length;
33
34
  let changedKeys;
34
35
  for (let i = 0; i < len; i++) {
35
- const prev = prevItems[i];
36
+ const prevItem = prev[i];
36
37
  const curr = next[i];
37
- if (prev === curr) continue;
38
- const p = chunkG6VLCNVY_cjs.isRecord(prev) ? prev : void 0;
39
- const n = chunkG6VLCNVY_cjs.isRecord(curr) ? curr : void 0;
38
+ if (prevItem === curr) continue;
39
+ const p = chunkFD2LRNLT_cjs.isRecord(prevItem) ? prevItem : void 0;
40
+ const n = chunkFD2LRNLT_cjs.isRecord(curr) ? curr : void 0;
40
41
  if (!p || !n) {
41
42
  for (const [, listeners] of w) {
42
43
  for (const listener of listeners) {
@@ -72,7 +73,7 @@ function collection(key, options) {
72
73
  const baseDestroy = base.destroy;
73
74
  const col = base;
74
75
  col.watch = (watchKey, listener) => {
75
- return chunkG6VLCNVY_cjs.addWatcher(ensureWatchers(), watchKey, listener);
76
+ return chunkFD2LRNLT_cjs.addWatcher(ensureWatchers(), watchKey, listener);
76
77
  };
77
78
  col.add = (...items) => {
78
79
  base.set(base.get().concat(items));
@@ -136,7 +137,10 @@ function collection(key, options) {
136
137
  col.destroy = () => {
137
138
  try {
138
139
  watchers?.clear();
140
+ watchers = void 0;
139
141
  unsubscribe?.();
142
+ unsubscribe = void 0;
143
+ prevItems = void 0;
140
144
  } finally {
141
145
  baseDestroy.call(col);
142
146
  }
@@ -149,7 +153,7 @@ function callDerivation(fn, depValues, key) {
149
153
  try {
150
154
  return fn(depValues);
151
155
  } catch (err) {
152
- const wrapped = new chunkG6VLCNVY_cjs.ComputedError(key, "memory", err);
156
+ const wrapped = new chunkFD2LRNLT_cjs.ComputedError(key, "memory", err);
153
157
  chunk3K5IERQC_cjs.reportError(key, "memory", wrapped);
154
158
  throw wrapped;
155
159
  }
@@ -165,7 +169,7 @@ function computed(deps, fn, options) {
165
169
  let cached;
166
170
  let isDirty = true;
167
171
  let isDestroyed = false;
168
- const lazyDestroyed = chunkG6VLCNVY_cjs.createLazyDestroyed();
172
+ const lazyDestroyed = chunkFD2LRNLT_cjs.createLazyDestroyed();
169
173
  const depValues = new Array(deps.length);
170
174
  const depLen = deps.length;
171
175
  function recompute() {
@@ -194,7 +198,7 @@ function computed(deps, fn, options) {
194
198
  const markDirty = () => {
195
199
  if (isDestroyed) return;
196
200
  isDirty = true;
197
- chunkG6VLCNVY_cjs.notify(notifyListeners);
201
+ chunkFD2LRNLT_cjs.notify(notifyListeners);
198
202
  };
199
203
  const unsubscribers = new Array(depLen);
200
204
  for (let i = 0; i < depLen; i++) {
@@ -202,12 +206,12 @@ function computed(deps, fn, options) {
202
206
  unsubscribers[i] = dep.subscribe(markDirty);
203
207
  }
204
208
  recompute();
205
- let readyPromise = chunkG6VLCNVY_cjs.RESOLVED;
206
- let hydratedPromise = chunkG6VLCNVY_cjs.RESOLVED;
207
- let settledPromise = chunkG6VLCNVY_cjs.RESOLVED;
209
+ let readyPromise = chunkFD2LRNLT_cjs.RESOLVED;
210
+ let hydratedPromise = chunkFD2LRNLT_cjs.RESOLVED;
211
+ let settledPromise = chunkFD2LRNLT_cjs.RESOLVED;
208
212
  let hasAsyncDep = false;
209
213
  for (let i = 0; i < depLen; i++) {
210
- if (deps[i].ready !== chunkG6VLCNVY_cjs.RESOLVED) {
214
+ if (deps[i].ready !== chunkFD2LRNLT_cjs.RESOLVED) {
211
215
  hasAsyncDep = true;
212
216
  break;
213
217
  }
@@ -239,7 +243,7 @@ function computed(deps, fn, options) {
239
243
  return hydratedPromise;
240
244
  },
241
245
  get destroyed() {
242
- if (isDestroyed) return chunkG6VLCNVY_cjs.RESOLVED;
246
+ if (isDestroyed) return chunkFD2LRNLT_cjs.RESOLVED;
243
247
  return lazyDestroyed.promise;
244
248
  },
245
249
  get isDestroyed() {
@@ -338,6 +342,7 @@ function effect(deps, fn, options) {
338
342
  for (let i = 0; i < depLen; i++) {
339
343
  unsubscribers[i]();
340
344
  }
345
+ unsubscribers.length = 0;
341
346
  } finally {
342
347
  if (cleanup) {
343
348
  try {
@@ -420,15 +425,13 @@ function withWatch(instance) {
420
425
  let watchers;
421
426
  let unsubscribe;
422
427
  let prev;
423
- let initialized = false;
424
428
  function ensureSubscription() {
425
- if (unsubscribe || initialized) return;
426
- initialized = true;
429
+ if (unsubscribe) return;
427
430
  prev = instance.get();
428
431
  unsubscribe = instance.subscribe((next) => {
429
432
  try {
430
433
  if (watchers && watchers.size > 0) {
431
- chunkG6VLCNVY_cjs.notifyWatchers(watchers, prev, next);
434
+ chunkFD2LRNLT_cjs.notifyWatchers(watchers, prev, next);
432
435
  }
433
436
  } finally {
434
437
  prev = next;
@@ -439,14 +442,13 @@ function withWatch(instance) {
439
442
  result.watch = (watchKey, listener) => {
440
443
  if (!watchers) watchers = /* @__PURE__ */ new Map();
441
444
  ensureSubscription();
442
- return chunkG6VLCNVY_cjs.addWatcher(watchers, watchKey, listener);
445
+ return chunkFD2LRNLT_cjs.addWatcher(watchers, watchKey, listener);
443
446
  };
444
447
  result.destroy = () => {
445
448
  watchers?.clear();
446
449
  watchers = void 0;
447
450
  unsubscribe?.();
448
451
  unsubscribe = void 0;
449
- initialized = false;
450
452
  prev = void 0;
451
453
  instance.destroy();
452
454
  };
@@ -464,15 +466,21 @@ function previous(source, options) {
464
466
  const notifyListeners = () => {
465
467
  listeners.notify(prev);
466
468
  };
467
- const unsubscribe = source.subscribe((next) => {
468
- const old = prev;
469
- prev = current;
470
- current = next;
471
- if (old !== prev) {
472
- chunkG6VLCNVY_cjs.notify(notifyListeners);
473
- }
474
- });
475
- const lazyDestroyed = chunkG6VLCNVY_cjs.createLazyDestroyed();
469
+ let unsubscribe;
470
+ try {
471
+ unsubscribe = source.subscribe((next) => {
472
+ const old = prev;
473
+ prev = current;
474
+ current = next;
475
+ if (old !== prev) {
476
+ chunkFD2LRNLT_cjs.notify(notifyListeners);
477
+ }
478
+ });
479
+ } catch {
480
+ listeners.clear();
481
+ throw new Error(`[gjendje] previous(): source.subscribe() threw for "${instanceKey}".`);
482
+ }
483
+ const lazyDestroyed = chunkFD2LRNLT_cjs.createLazyDestroyed();
476
484
  return {
477
485
  key: instanceKey,
478
486
  scope: "memory",
@@ -501,7 +509,7 @@ function previous(source, options) {
501
509
  destroy() {
502
510
  if (isDestroyed) return;
503
511
  isDestroyed = true;
504
- unsubscribe();
512
+ unsubscribe?.();
505
513
  listeners.clear();
506
514
  lazyDestroyed.resolve();
507
515
  }
@@ -519,10 +527,104 @@ function readonly(instance) {
519
527
 
520
528
  // src/select.ts
521
529
  var selectCounter = 0;
530
+ function callSelector(fn, value, key) {
531
+ try {
532
+ return fn(value);
533
+ } catch (err) {
534
+ const wrapped = new chunkFD2LRNLT_cjs.ComputedError(key, "memory", err);
535
+ chunk3K5IERQC_cjs.reportError(key, "memory", wrapped);
536
+ throw wrapped;
537
+ }
538
+ }
522
539
  function select(source, fn, options) {
523
- return computed([source], (values) => fn(values[0]), {
524
- key: options?.key ?? `select:${selectCounter++}`
525
- });
540
+ const instanceKey = options?.key ?? `select:${selectCounter++}`;
541
+ const listenerSet = /* @__PURE__ */ new Set();
542
+ let singleListener;
543
+ let listenerCount = 0;
544
+ let cached;
545
+ let isDirty = true;
546
+ let isDestroyed = false;
547
+ const lazyDestroyed = chunkFD2LRNLT_cjs.createLazyDestroyed();
548
+ function recompute() {
549
+ if (!isDirty) return cached;
550
+ cached = callSelector(fn, source.get(), instanceKey);
551
+ isDirty = false;
552
+ return cached;
553
+ }
554
+ const notifyListeners = () => {
555
+ if (isDestroyed) return;
556
+ const prev = cached;
557
+ const value = recompute();
558
+ if (value === prev) return;
559
+ if (singleListener !== void 0) {
560
+ chunk3K5IERQC_cjs.safeCall(singleListener, value, instanceKey, "memory");
561
+ return;
562
+ }
563
+ for (const l of listenerSet) {
564
+ chunk3K5IERQC_cjs.safeCall(l, value, instanceKey, "memory");
565
+ }
566
+ };
567
+ const markDirty = () => {
568
+ if (isDestroyed) return;
569
+ isDirty = true;
570
+ chunkFD2LRNLT_cjs.notify(notifyListeners);
571
+ };
572
+ const unsub = source.subscribe(markDirty);
573
+ recompute();
574
+ return {
575
+ key: instanceKey,
576
+ scope: "memory",
577
+ get ready() {
578
+ return source.ready;
579
+ },
580
+ get settled() {
581
+ return source.settled;
582
+ },
583
+ get hydrated() {
584
+ return source.hydrated;
585
+ },
586
+ get destroyed() {
587
+ if (isDestroyed) return chunkFD2LRNLT_cjs.RESOLVED;
588
+ return lazyDestroyed.promise;
589
+ },
590
+ get isDestroyed() {
591
+ return isDestroyed;
592
+ },
593
+ get() {
594
+ return recompute();
595
+ },
596
+ peek() {
597
+ return cached;
598
+ },
599
+ subscribe(listener) {
600
+ if (isDestroyed) return () => {
601
+ };
602
+ listenerSet.add(listener);
603
+ listenerCount++;
604
+ singleListener = listenerCount === 1 ? listener : void 0;
605
+ return () => {
606
+ listenerSet.delete(listener);
607
+ listenerCount--;
608
+ if (listenerCount === 1) {
609
+ singleListener = listenerSet.values().next().value;
610
+ } else {
611
+ singleListener = void 0;
612
+ }
613
+ };
614
+ },
615
+ destroy() {
616
+ if (isDestroyed) return;
617
+ isDestroyed = true;
618
+ try {
619
+ unsub();
620
+ listenerSet.clear();
621
+ listenerCount = 0;
622
+ singleListener = void 0;
623
+ } finally {
624
+ lazyDestroyed.resolve();
625
+ }
626
+ }
627
+ };
526
628
  }
527
629
 
528
630
  // src/shortcuts.ts
@@ -538,7 +640,7 @@ function extractEntry(entry) {
538
640
  }
539
641
  function scopeShortcut(scope, entry, options) {
540
642
  const [key, defaultValue] = extractEntry(entry);
541
- return chunkG6VLCNVY_cjs.createBase(key, { ...options, default: defaultValue, scope });
643
+ return chunkFD2LRNLT_cjs.createBase(key, { ...options, default: defaultValue, scope });
542
644
  }
543
645
  function _state(keyOrEntry, optionsOrDefault, extraOptions) {
544
646
  let key;
@@ -551,7 +653,7 @@ function _state(keyOrEntry, optionsOrDefault, extraOptions) {
551
653
  key = keyOrEntry;
552
654
  options = extraOptions ? { ...extraOptions, default: optionsOrDefault } : optionsOrDefault !== null && typeof optionsOrDefault === "object" && "default" in optionsOrDefault ? optionsOrDefault : { default: optionsOrDefault };
553
655
  }
554
- return chunkG6VLCNVY_cjs.createBase(key, options);
656
+ return chunkFD2LRNLT_cjs.createBase(key, options);
555
657
  }
556
658
  _state.local = (e, o) => scopeShortcut("local", e, o);
557
659
  _state.session = (e, o) => scopeShortcut("session", e, o);
@@ -562,47 +664,47 @@ var state = _state;
562
664
 
563
665
  Object.defineProperty(exports, "ComputedError", {
564
666
  enumerable: true,
565
- get: function () { return chunkG6VLCNVY_cjs.ComputedError; }
667
+ get: function () { return chunkFD2LRNLT_cjs.ComputedError; }
566
668
  });
567
669
  Object.defineProperty(exports, "GjendjeError", {
568
670
  enumerable: true,
569
- get: function () { return chunkG6VLCNVY_cjs.GjendjeError; }
671
+ get: function () { return chunkFD2LRNLT_cjs.GjendjeError; }
570
672
  });
571
673
  Object.defineProperty(exports, "HydrationError", {
572
674
  enumerable: true,
573
- get: function () { return chunkG6VLCNVY_cjs.HydrationError; }
675
+ get: function () { return chunkFD2LRNLT_cjs.HydrationError; }
574
676
  });
575
677
  Object.defineProperty(exports, "InterceptorError", {
576
678
  enumerable: true,
577
- get: function () { return chunkG6VLCNVY_cjs.InterceptorError; }
679
+ get: function () { return chunkFD2LRNLT_cjs.InterceptorError; }
578
680
  });
579
681
  Object.defineProperty(exports, "MigrationError", {
580
682
  enumerable: true,
581
- get: function () { return chunkG6VLCNVY_cjs.MigrationError; }
683
+ get: function () { return chunkFD2LRNLT_cjs.MigrationError; }
582
684
  });
583
685
  Object.defineProperty(exports, "StorageReadError", {
584
686
  enumerable: true,
585
- get: function () { return chunkG6VLCNVY_cjs.StorageReadError; }
687
+ get: function () { return chunkFD2LRNLT_cjs.StorageReadError; }
586
688
  });
587
689
  Object.defineProperty(exports, "StorageWriteError", {
588
690
  enumerable: true,
589
- get: function () { return chunkG6VLCNVY_cjs.StorageWriteError; }
691
+ get: function () { return chunkFD2LRNLT_cjs.StorageWriteError; }
590
692
  });
591
693
  Object.defineProperty(exports, "SyncError", {
592
694
  enumerable: true,
593
- get: function () { return chunkG6VLCNVY_cjs.SyncError; }
695
+ get: function () { return chunkFD2LRNLT_cjs.SyncError; }
594
696
  });
595
697
  Object.defineProperty(exports, "ValidationError", {
596
698
  enumerable: true,
597
- get: function () { return chunkG6VLCNVY_cjs.ValidationError; }
699
+ get: function () { return chunkFD2LRNLT_cjs.ValidationError; }
598
700
  });
599
701
  Object.defineProperty(exports, "batch", {
600
702
  enumerable: true,
601
- get: function () { return chunkG6VLCNVY_cjs.batch; }
703
+ get: function () { return chunkFD2LRNLT_cjs.batch; }
602
704
  });
603
705
  Object.defineProperty(exports, "shallowEqual", {
604
706
  enumerable: true,
605
- get: function () { return chunkG6VLCNVY_cjs.shallowEqual; }
707
+ get: function () { return chunkFD2LRNLT_cjs.shallowEqual; }
606
708
  });
607
709
  Object.defineProperty(exports, "configure", {
608
710
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -497,8 +497,8 @@ declare function destroyAll(): void;
497
497
 
498
498
  /**
499
499
  * A read-only reactive value derived from a single source.
500
- * Lighter than `computed` — skips multi-dep machinery (no array allocation,
501
- * no dependency loop). Ideal for projecting a single field or transformation.
500
+ * Lighter than `computed` — no array allocation, no dependency loop.
501
+ * Ideal for projecting a single field or transformation.
502
502
  */
503
503
  interface SelectInstance<T> extends ReadonlyInstance<T> {
504
504
  }
@@ -519,6 +519,8 @@ interface SelectOptions {
519
519
  * userName.get() // 'Jane'
520
520
  * userName.subscribe(name => console.log(name))
521
521
  * ```
522
+ *
523
+ * @throws {ComputedError} If the selector function throws during recomputation.
522
524
  */
523
525
  declare function select<TSource, TResult>(source: ReadonlyInstance<TSource>, fn: (value: TSource) => TResult, options?: SelectOptions): SelectInstance<TResult>;
524
526
 
package/dist/index.d.ts CHANGED
@@ -497,8 +497,8 @@ declare function destroyAll(): void;
497
497
 
498
498
  /**
499
499
  * A read-only reactive value derived from a single source.
500
- * Lighter than `computed` — skips multi-dep machinery (no array allocation,
501
- * no dependency loop). Ideal for projecting a single field or transformation.
500
+ * Lighter than `computed` — no array allocation, no dependency loop.
501
+ * Ideal for projecting a single field or transformation.
502
502
  */
503
503
  interface SelectInstance<T> extends ReadonlyInstance<T> {
504
504
  }
@@ -519,6 +519,8 @@ interface SelectOptions {
519
519
  * userName.get() // 'Jane'
520
520
  * userName.subscribe(name => console.log(name))
521
521
  * ```
522
+ *
523
+ * @throws {ComputedError} If the selector function throws during recomputation.
522
524
  */
523
525
  declare function select<TSource, TResult>(source: ReadonlyInstance<TSource>, fn: (value: TSource) => TResult, options?: SelectOptions): SelectInstance<TResult>;
524
526
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createBase, addWatcher, createLazyDestroyed, RESOLVED, notify, isRecord, notifyWatchers, ComputedError } from './chunk-PAJH64N4.js';
2
- export { ComputedError, GjendjeError, HydrationError, InterceptorError, MigrationError, StorageReadError, StorageWriteError, SyncError, ValidationError, batch, shallowEqual } from './chunk-PAJH64N4.js';
1
+ import { createBase, addWatcher, createLazyDestroyed, RESOLVED, notify, isRecord, notifyWatchers, ComputedError } from './chunk-TV5QHWN4.js';
2
+ export { ComputedError, GjendjeError, HydrationError, InterceptorError, MigrationError, StorageReadError, StorageWriteError, SyncError, ValidationError, batch, shallowEqual } from './chunk-TV5QHWN4.js';
3
3
  import { getRegistry, createListeners, safeCall, reportError } from './chunk-N54IDKRP.js';
4
4
  export { configure, destroyAll, resetConfig } from './chunk-N54IDKRP.js';
5
5
 
@@ -19,7 +19,8 @@ function collection(key, options) {
19
19
  prevItems = next;
20
20
  return;
21
21
  }
22
- const lengthChanged = next.length !== prevItems.length;
22
+ const prev = prevItems ?? [];
23
+ const lengthChanged = next.length !== prev.length;
23
24
  if (lengthChanged) {
24
25
  for (const [, listeners] of w) {
25
26
  for (const listener of listeners) {
@@ -32,10 +33,10 @@ function collection(key, options) {
32
33
  const len = next.length;
33
34
  let changedKeys;
34
35
  for (let i = 0; i < len; i++) {
35
- const prev = prevItems[i];
36
+ const prevItem = prev[i];
36
37
  const curr = next[i];
37
- if (prev === curr) continue;
38
- const p = isRecord(prev) ? prev : void 0;
38
+ if (prevItem === curr) continue;
39
+ const p = isRecord(prevItem) ? prevItem : void 0;
39
40
  const n = isRecord(curr) ? curr : void 0;
40
41
  if (!p || !n) {
41
42
  for (const [, listeners] of w) {
@@ -136,7 +137,10 @@ function collection(key, options) {
136
137
  col.destroy = () => {
137
138
  try {
138
139
  watchers?.clear();
140
+ watchers = void 0;
139
141
  unsubscribe?.();
142
+ unsubscribe = void 0;
143
+ prevItems = void 0;
140
144
  } finally {
141
145
  baseDestroy.call(col);
142
146
  }
@@ -338,6 +342,7 @@ function effect(deps, fn, options) {
338
342
  for (let i = 0; i < depLen; i++) {
339
343
  unsubscribers[i]();
340
344
  }
345
+ unsubscribers.length = 0;
341
346
  } finally {
342
347
  if (cleanup) {
343
348
  try {
@@ -420,10 +425,8 @@ function withWatch(instance) {
420
425
  let watchers;
421
426
  let unsubscribe;
422
427
  let prev;
423
- let initialized = false;
424
428
  function ensureSubscription() {
425
- if (unsubscribe || initialized) return;
426
- initialized = true;
429
+ if (unsubscribe) return;
427
430
  prev = instance.get();
428
431
  unsubscribe = instance.subscribe((next) => {
429
432
  try {
@@ -446,7 +449,6 @@ function withWatch(instance) {
446
449
  watchers = void 0;
447
450
  unsubscribe?.();
448
451
  unsubscribe = void 0;
449
- initialized = false;
450
452
  prev = void 0;
451
453
  instance.destroy();
452
454
  };
@@ -464,14 +466,20 @@ function previous(source, options) {
464
466
  const notifyListeners = () => {
465
467
  listeners.notify(prev);
466
468
  };
467
- const unsubscribe = source.subscribe((next) => {
468
- const old = prev;
469
- prev = current;
470
- current = next;
471
- if (old !== prev) {
472
- notify(notifyListeners);
473
- }
474
- });
469
+ let unsubscribe;
470
+ try {
471
+ unsubscribe = source.subscribe((next) => {
472
+ const old = prev;
473
+ prev = current;
474
+ current = next;
475
+ if (old !== prev) {
476
+ notify(notifyListeners);
477
+ }
478
+ });
479
+ } catch {
480
+ listeners.clear();
481
+ throw new Error(`[gjendje] previous(): source.subscribe() threw for "${instanceKey}".`);
482
+ }
475
483
  const lazyDestroyed = createLazyDestroyed();
476
484
  return {
477
485
  key: instanceKey,
@@ -501,7 +509,7 @@ function previous(source, options) {
501
509
  destroy() {
502
510
  if (isDestroyed) return;
503
511
  isDestroyed = true;
504
- unsubscribe();
512
+ unsubscribe?.();
505
513
  listeners.clear();
506
514
  lazyDestroyed.resolve();
507
515
  }
@@ -519,10 +527,104 @@ function readonly(instance) {
519
527
 
520
528
  // src/select.ts
521
529
  var selectCounter = 0;
530
+ function callSelector(fn, value, key) {
531
+ try {
532
+ return fn(value);
533
+ } catch (err) {
534
+ const wrapped = new ComputedError(key, "memory", err);
535
+ reportError(key, "memory", wrapped);
536
+ throw wrapped;
537
+ }
538
+ }
522
539
  function select(source, fn, options) {
523
- return computed([source], (values) => fn(values[0]), {
524
- key: options?.key ?? `select:${selectCounter++}`
525
- });
540
+ const instanceKey = options?.key ?? `select:${selectCounter++}`;
541
+ const listenerSet = /* @__PURE__ */ new Set();
542
+ let singleListener;
543
+ let listenerCount = 0;
544
+ let cached;
545
+ let isDirty = true;
546
+ let isDestroyed = false;
547
+ const lazyDestroyed = createLazyDestroyed();
548
+ function recompute() {
549
+ if (!isDirty) return cached;
550
+ cached = callSelector(fn, source.get(), instanceKey);
551
+ isDirty = false;
552
+ return cached;
553
+ }
554
+ const notifyListeners = () => {
555
+ if (isDestroyed) return;
556
+ const prev = cached;
557
+ const value = recompute();
558
+ if (value === prev) return;
559
+ if (singleListener !== void 0) {
560
+ safeCall(singleListener, value, instanceKey, "memory");
561
+ return;
562
+ }
563
+ for (const l of listenerSet) {
564
+ safeCall(l, value, instanceKey, "memory");
565
+ }
566
+ };
567
+ const markDirty = () => {
568
+ if (isDestroyed) return;
569
+ isDirty = true;
570
+ notify(notifyListeners);
571
+ };
572
+ const unsub = source.subscribe(markDirty);
573
+ recompute();
574
+ return {
575
+ key: instanceKey,
576
+ scope: "memory",
577
+ get ready() {
578
+ return source.ready;
579
+ },
580
+ get settled() {
581
+ return source.settled;
582
+ },
583
+ get hydrated() {
584
+ return source.hydrated;
585
+ },
586
+ get destroyed() {
587
+ if (isDestroyed) return RESOLVED;
588
+ return lazyDestroyed.promise;
589
+ },
590
+ get isDestroyed() {
591
+ return isDestroyed;
592
+ },
593
+ get() {
594
+ return recompute();
595
+ },
596
+ peek() {
597
+ return cached;
598
+ },
599
+ subscribe(listener) {
600
+ if (isDestroyed) return () => {
601
+ };
602
+ listenerSet.add(listener);
603
+ listenerCount++;
604
+ singleListener = listenerCount === 1 ? listener : void 0;
605
+ return () => {
606
+ listenerSet.delete(listener);
607
+ listenerCount--;
608
+ if (listenerCount === 1) {
609
+ singleListener = listenerSet.values().next().value;
610
+ } else {
611
+ singleListener = void 0;
612
+ }
613
+ };
614
+ },
615
+ destroy() {
616
+ if (isDestroyed) return;
617
+ isDestroyed = true;
618
+ try {
619
+ unsub();
620
+ listenerSet.clear();
621
+ listenerCount = 0;
622
+ singleListener = void 0;
623
+ } finally {
624
+ lazyDestroyed.resolve();
625
+ }
626
+ }
627
+ };
526
628
  }
527
629
 
528
630
  // src/shortcuts.ts
package/dist/server.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkG6VLCNVY_cjs = require('./chunk-G6VLCNVY.cjs');
3
+ var chunkFD2LRNLT_cjs = require('./chunk-FD2LRNLT.cjs');
4
4
  var chunk3K5IERQC_cjs = require('./chunk-3K5IERQC.cjs');
5
5
  var async_hooks = require('async_hooks');
6
6
 
@@ -32,7 +32,7 @@ function createServerAdapter(key, defaultValue) {
32
32
  }
33
33
  store.set(key, value);
34
34
  lastNotifiedValue = value;
35
- chunkG6VLCNVY_cjs.notify(notifyListeners);
35
+ chunkFD2LRNLT_cjs.notify(notifyListeners);
36
36
  },
37
37
  subscribe: listeners.subscribe,
38
38
  destroy() {
@@ -40,7 +40,7 @@ function createServerAdapter(key, defaultValue) {
40
40
  }
41
41
  };
42
42
  }
43
- chunkG6VLCNVY_cjs.registerServerAdapter(createServerAdapter);
43
+ chunkFD2LRNLT_cjs.registerServerAdapter(createServerAdapter);
44
44
 
45
45
  exports.createServerAdapter = createServerAdapter;
46
46
  exports.withServerSession = withServerSession;
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { registerServerAdapter, notify } from './chunk-PAJH64N4.js';
1
+ import { registerServerAdapter, notify } from './chunk-TV5QHWN4.js';
2
2
  import { createListeners } from './chunk-N54IDKRP.js';
3
3
  import { AsyncLocalStorage } from 'async_hooks';
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gjendje",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Storage-agnostic state management library for TypeScript and JavaScript",
5
5
  "keywords": [
6
6
  "state",
@@ -104,7 +104,6 @@
104
104
  "devDependencies": {
105
105
  "@biomejs/biome": "^2.4.6",
106
106
  "@changesets/cli": "^2.27.0",
107
- "@size-limit/preset-small-lib": "^11.0.0",
108
107
  "@testing-library/react": "^16.3.2",
109
108
  "@types/node": "^20.0.0",
110
109
  "@types/react": "^19.2.14",
@@ -116,7 +115,6 @@
116
115
  "publint": "^0.3.0",
117
116
  "react": "^19.2.4",
118
117
  "react-dom": "^19.2.4",
119
- "size-limit": "^11.0.0",
120
118
  "tinybench": "^6.0.0",
121
119
  "tsup": "^8.0.0",
122
120
  "tsx": "^4.21.0",
@@ -133,16 +131,31 @@
133
131
  "lint:check": "biome ci src/ __tests__/",
134
132
  "lint:fix": "biome check src/ __tests__/ --fix",
135
133
  "release": "pnpm build && pnpm changeset publish",
136
- "size": "size-limit",
137
134
  "test": "vitest run",
138
135
  "test:coverage": "vitest run --coverage",
139
136
  "test:watch": "vitest",
140
137
  "typecheck": "tsc --project tsconfig.test.json --skipLibCheck",
141
138
  "bench:setup": "pnpm add -D tsx zustand valtio",
142
139
  "bench": "tsx benchmarks/state-management.bench.ts",
143
- "bench:internal": "tsx benchmarks/internal.bench.ts",
144
- "bench:extended": "tsx benchmarks/internals-extended.bench.ts",
145
- "bench:save": "tsx benchmarks/internal.bench.ts --save",
146
- "bench:compare": "tsx benchmarks/internal.bench.ts --compare"
140
+ "bench:compare": "tsx benchmarks/edge-cases-comparison.bench.ts",
141
+ "bench:storage": "tsx benchmarks/storage-read.bench.ts",
142
+ "bench:url": "tsx benchmarks/url-read.bench.ts",
143
+ "bench:internal": "tsx benchmarks/internal/index.ts",
144
+ "bench:internal:lifecycle": "tsx benchmarks/internal/lifecycle.bench.ts",
145
+ "bench:internal:state-write": "tsx benchmarks/internal/state-write.bench.ts",
146
+ "bench:internal:computed": "tsx benchmarks/internal/computed.bench.ts",
147
+ "bench:internal:select": "tsx benchmarks/internal/select.bench.ts",
148
+ "bench:internal:effect": "tsx benchmarks/internal/effect.bench.ts",
149
+ "bench:internal:batch": "tsx benchmarks/internal/batch.bench.ts",
150
+ "bench:internal:collection": "tsx benchmarks/internal/collection.bench.ts",
151
+ "bench:internal:middleware": "tsx benchmarks/internal/middleware.bench.ts",
152
+ "bench:internal:subscription": "tsx benchmarks/internal/subscription.bench.ts",
153
+ "bench:internal:history": "tsx benchmarks/internal/history.bench.ts",
154
+ "bench:internal:readonly": "tsx benchmarks/internal/readonly.bench.ts",
155
+ "bench:internal:patch": "tsx benchmarks/internal/patch.bench.ts",
156
+ "bench:internal:registry": "tsx benchmarks/internal/registry.bench.ts",
157
+ "bench:internal:persist": "tsx benchmarks/internal/persist.bench.ts",
158
+ "bench:internal:read-cost": "tsx benchmarks/internal/read-cost.bench.ts",
159
+ "bench:internal:utilities": "tsx benchmarks/internal/utilities.bench.ts"
147
160
  }
148
161
  }