gjendje 0.5.0 → 0.6.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # gjendje
4
4
 
5
- Every app juggles `localStorage`, `sessionStorage`, URL params, and in-memory state — each with its own API. **gjendje** replaces it all with a single primitive. Choose where state lives. The rest is handled.
5
+ Every app juggles localStorage, sessionStorage, URL params, and in-memory state — each with its own API. **gjendje** replaces it all with a single primitive. Choose where state lives. The rest is handled.
6
6
 
7
7
  ## Install
8
8
 
@@ -13,41 +13,40 @@ npm install gjendje
13
13
  ## Quick start
14
14
 
15
15
  ```ts
16
- import { state } from 'gjendje'
16
+ import { local, session, url, bucket, server } from 'gjendje'
17
17
 
18
- // 1. Create state
19
- const counter = state('counter', 0)
18
+ // localStorage survives refresh, works across tabs
19
+ const theme = local({ theme: 'light' })
20
20
 
21
- // 2. Read
22
- counter.get() // 0
21
+ // sessionStorage — survives refresh, gone when tab closes
22
+ const draft = session({ draft: '' })
23
23
 
24
- // 3. Write
25
- counter.set(1)
26
- counter.set((prev) => prev + 1)
24
+ // URL params — shareable via address bar
25
+ const filters = url({ q: '' })
27
26
 
28
- // 4. Subscribe
29
- counter.subscribe((value) => console.log(value))
27
+ // Storage Bucket — isolated, quota-managed, expirable
28
+ const cache = bucket({ cache: [] }, { bucket: { name: 'app-cache', expires: '7d' } })
30
29
 
31
- // 5. Reset
32
- counter.reset() // back to 0
30
+ // AsyncLocalStorage — server-side, session-scoped
31
+ const user = server({ user: null })
33
32
  ```
34
33
 
35
- To persist across page reloads, add a scope:
34
+ For in-memory state that doesn't persist, use `state` directly:
36
35
 
37
36
  ```ts
38
- const theme = state('theme', { default: 'light', scope: 'local' })
37
+ import { state } from 'gjendje'
39
38
 
40
- theme.set('dark')
41
- // Survives refresh — stored in localStorage
39
+ const counter = state({ counter: 0 })
40
+
41
+ counter.set((prev) => prev + 1)
42
42
  ```
43
43
 
44
44
  ## Configure
45
45
 
46
46
  Sets global defaults for all state instances.
47
47
 
48
- Call once at your app entry point.
49
-
50
48
  ```ts
49
+ // app.ts
51
50
  import { configure } from 'gjendje'
52
51
 
53
52
  configure({ scope: 'local' })
@@ -56,7 +55,7 @@ configure({ scope: 'local' })
56
55
  Now every `state` call inherits that default:
57
56
 
58
57
  ```ts
59
- const theme = state('theme', 'light')
58
+ const theme = state({ theme: 'light' })
60
59
 
61
60
  theme.scope // 'local' — derived from configure
62
61
  ```
@@ -65,33 +64,20 @@ theme.scope // 'local' — derived from configure
65
64
 
66
65
  ## Scopes
67
66
 
68
- |Scope |Description |
69
- |-----------|--------------------------------------------------------|
70
- |`render` | `memory` |
71
- |`local` |`localStorage` |
72
- |`server` |`AsyncLocalStorage` |
73
- |`bucket` | `Storage Buckets API ` |
74
- |`url` |`URLSearchParams` |
75
- |`tab` |`sessionStorage` |
67
+ | Scope | Backend | Shortcut |
68
+ |----------|----------------------|-------------|
69
+ | `memory` | In-memory | `state()` |
70
+ | `local` | `localStorage` | `local()` |
71
+ | `tab` | `sessionStorage` | `session()` |
72
+ | `url` | `URLSearchParams` | `url()` |
73
+ | `bucket` | Storage Buckets API | `bucket()` |
74
+ | `server` | `AsyncLocalStorage` | `server()` |
76
75
 
77
76
  [Scope decision guide](https://github.com/charliebeckstrand/gjendje/blob/main/docs/scopes.md)
78
77
 
79
78
  ## API
80
79
 
81
- #### `get()`
82
- Returns the current value. Reactive — triggers tracking in `computed` and `effect`.
83
-
84
- #### `set(value)` / `set(prev => next)`
85
- Replaces the current value. Accepts a direct value or an updater function that receives the previous value.
86
-
87
- #### `subscribe(fn)`
88
- Calls `fn` on every change. Returns an `unsubscribe` function.
89
-
90
- #### `watch(key, fn)`
91
- Like `subscribe`, but scoped to a single key within an object value. Only fires when that key changes.
92
-
93
- #### `intercept(fn)`
94
- Receives `(next, prev)` before each update. Return the value to store, or return `prev` to reject the change. Returns an `unsubscribe` function.
80
+ Every primitive — `state`, `local`, `session`, `url`, `bucket`, and `server` — shares the same core API: `get`, `set`, `reset`, `subscribe`, `watch`, `intercept`, and more.
95
81
 
96
82
  [Full API reference](https://github.com/charliebeckstrand/gjendje/blob/main/docs/api.md) · [Persistence reference](https://github.com/charliebeckstrand/gjendje/blob/main/docs/persistence.md)
97
83
 
@@ -633,6 +633,7 @@ function resolveStorageKey(key, options, configPrefix) {
633
633
  }
634
634
  function resolveAdapter(storageKey, scope, options) {
635
635
  switch (scope) {
636
+ case "memory":
636
637
  case "render":
637
638
  return createRenderAdapter(options.default);
638
639
  case "tab":
@@ -993,7 +994,8 @@ function createBase(key, options) {
993
994
  `[gjendje] Key "${key}" does not match the configured keyPattern ${config.keyPattern}.`
994
995
  );
995
996
  }
996
- const scope = options.scope ?? config.scope ?? "render";
997
+ const rawScope = options.scope ?? config.scope ?? "render";
998
+ const scope = rawScope === "memory" ? "render" : rawScope;
997
999
  const rKey = scopedKey(key, scope);
998
1000
  const existing = getRegistered(rKey);
999
1001
  if (existing && !existing.isDestroyed) return existing;
@@ -635,6 +635,7 @@ function resolveStorageKey(key, options, configPrefix) {
635
635
  }
636
636
  function resolveAdapter(storageKey, scope, options) {
637
637
  switch (scope) {
638
+ case "memory":
638
639
  case "render":
639
640
  return createRenderAdapter(options.default);
640
641
  case "tab":
@@ -995,7 +996,8 @@ function createBase(key, options) {
995
996
  `[gjendje] Key "${key}" does not match the configured keyPattern ${config.keyPattern}.`
996
997
  );
997
998
  }
998
- const scope = options.scope ?? config.scope ?? "render";
999
+ const rawScope = options.scope ?? config.scope ?? "render";
1000
+ const scope = rawScope === "memory" ? "render" : rawScope;
999
1001
  const rKey = scopedKey(key, scope);
1000
1002
  const existing = getRegistered(rKey);
1001
1003
  if (existing && !existing.isDestroyed) return existing;
package/dist/index.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var chunkSCIDKAI5_cjs = require('./chunk-SCIDKAI5.cjs');
3
+ var chunkIC7SQVDX_cjs = require('./chunk-IC7SQVDX.cjs');
4
4
 
5
5
  // src/collection.ts
6
6
  function collection(key, options) {
7
- const base = chunkSCIDKAI5_cjs.createBase(key, options);
7
+ const base = chunkIC7SQVDX_cjs.createBase(key, options);
8
8
  const watchers = /* @__PURE__ */ new Map();
9
9
  let prevItems = base.get();
10
10
  const unsubscribe = base.subscribe((next) => {
@@ -139,7 +139,7 @@ function collection(key, options) {
139
139
  // src/computed.ts
140
140
  var computedCounter = 0;
141
141
  function computed(deps, fn, options) {
142
- const listeners = chunkSCIDKAI5_cjs.createListeners();
142
+ const listeners = chunkIC7SQVDX_cjs.createListeners();
143
143
  const instanceKey = options?.key ?? `computed:${computedCounter++}`;
144
144
  let cached;
145
145
  let isDirty = true;
@@ -164,7 +164,7 @@ function computed(deps, fn, options) {
164
164
  };
165
165
  const markDirty = () => {
166
166
  isDirty = true;
167
- chunkSCIDKAI5_cjs.notify(notifyListeners);
167
+ chunkIC7SQVDX_cjs.notify(notifyListeners);
168
168
  };
169
169
  const unsubscribers = new Array(depLen);
170
170
  for (let i = 0; i < depLen; i++) {
@@ -224,7 +224,7 @@ function computed(deps, fn, options) {
224
224
 
225
225
  // src/devtools.ts
226
226
  function snapshot() {
227
- const registry = chunkSCIDKAI5_cjs.getRegistry();
227
+ const registry = chunkIC7SQVDX_cjs.getRegistry();
228
228
  const result = [];
229
229
  for (const instance of registry.values()) {
230
230
  result.push({
@@ -404,45 +404,10 @@ function withWatch(instance) {
404
404
  return result;
405
405
  }
406
406
 
407
- // src/factory.ts
408
- function state(key, optionsOrDefault) {
409
- const options = optionsOrDefault !== null && typeof optionsOrDefault === "object" && "default" in optionsOrDefault ? optionsOrDefault : { default: optionsOrDefault };
410
- if (!key) {
411
- throw new Error("[state] key must be a non-empty string.");
412
- }
413
- const config = chunkSCIDKAI5_cjs.getConfig();
414
- if (config.keyPattern && !config.keyPattern.test(key)) {
415
- throw new Error(
416
- `[gjendje] Key "${key}" does not match the configured keyPattern ${config.keyPattern}.`
417
- );
418
- }
419
- const scope = options.scope ?? config.scope ?? "render";
420
- const rKey = chunkSCIDKAI5_cjs.scopedKey(key, scope);
421
- const existing = chunkSCIDKAI5_cjs.getRegistered(rKey);
422
- if (existing && !existing.isDestroyed) {
423
- if (config.warnOnDuplicate) {
424
- chunkSCIDKAI5_cjs.log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
425
- }
426
- return existing;
427
- }
428
- if (scope === "render" && !options.ssr && !config.ssr) {
429
- if (options.sync) {
430
- chunkSCIDKAI5_cjs.log(
431
- "warn",
432
- `sync: true is ignored for scope "render". Only "local" and "bucket" scopes support cross-tab sync.`
433
- );
434
- }
435
- const instance = chunkSCIDKAI5_cjs.createRenderState(key, rKey, options, config);
436
- chunkSCIDKAI5_cjs.registerByKey(rKey, key, scope, instance, config);
437
- return instance;
438
- }
439
- return chunkSCIDKAI5_cjs.createBase(key, options);
440
- }
441
-
442
407
  // src/previous.ts
443
408
  var previousCounter = 0;
444
409
  function previous(source, options) {
445
- const listeners = chunkSCIDKAI5_cjs.createListeners();
410
+ const listeners = chunkIC7SQVDX_cjs.createListeners();
446
411
  const instanceKey = options?.key ?? `previous:${previousCounter++}`;
447
412
  let prev;
448
413
  let current = source.get();
@@ -452,7 +417,7 @@ function previous(source, options) {
452
417
  prev = current;
453
418
  current = next;
454
419
  if (old !== prev) {
455
- chunkSCIDKAI5_cjs.notify(() => listeners.notify(prev));
420
+ chunkIC7SQVDX_cjs.notify(() => listeners.notify(prev));
456
421
  }
457
422
  });
458
423
  let destroyedPromise;
@@ -514,7 +479,7 @@ function readonly(instance) {
514
479
  // src/select.ts
515
480
  var selectCounter = 0;
516
481
  function select(source, fn, options) {
517
- const listeners = chunkSCIDKAI5_cjs.createListeners();
482
+ const listeners = chunkIC7SQVDX_cjs.createListeners();
518
483
  const instanceKey = options?.key ?? `select:${selectCounter++}`;
519
484
  let cached;
520
485
  let isDirty = true;
@@ -533,7 +498,7 @@ function select(source, fn, options) {
533
498
  };
534
499
  const markDirty = () => {
535
500
  isDirty = true;
536
- chunkSCIDKAI5_cjs.notify(notifyListeners);
501
+ chunkIC7SQVDX_cjs.notify(notifyListeners);
537
502
  };
538
503
  const unsubscribe = source.subscribe(markDirty);
539
504
  recompute();
@@ -583,25 +548,110 @@ function select(source, fn, options) {
583
548
  };
584
549
  }
585
550
 
551
+ // src/factory.ts
552
+ function createState(key, options) {
553
+ if (!key) {
554
+ throw new Error("[state] key must be a non-empty string.");
555
+ }
556
+ const config = chunkIC7SQVDX_cjs.getConfig();
557
+ if (config.keyPattern && !config.keyPattern.test(key)) {
558
+ throw new Error(
559
+ `[gjendje] Key "${key}" does not match the configured keyPattern ${config.keyPattern}.`
560
+ );
561
+ }
562
+ const rawScope = options.scope ?? config.scope ?? "render";
563
+ const scope = rawScope === "memory" ? "render" : rawScope;
564
+ const rKey = chunkIC7SQVDX_cjs.scopedKey(key, scope);
565
+ const existing = chunkIC7SQVDX_cjs.getRegistered(rKey);
566
+ if (existing && !existing.isDestroyed) {
567
+ if (config.warnOnDuplicate) {
568
+ chunkIC7SQVDX_cjs.log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
569
+ }
570
+ return existing;
571
+ }
572
+ if (scope === "render" && !options.ssr && !config.ssr) {
573
+ if (options.sync) {
574
+ chunkIC7SQVDX_cjs.log(
575
+ "warn",
576
+ `sync: true is ignored for scope "render". Only "local" and "bucket" scopes support cross-tab sync.`
577
+ );
578
+ }
579
+ const instance = chunkIC7SQVDX_cjs.createRenderState(key, rKey, options, config);
580
+ chunkIC7SQVDX_cjs.registerByKey(rKey, key, scope, instance, config);
581
+ return instance;
582
+ }
583
+ return chunkIC7SQVDX_cjs.createBase(key, options);
584
+ }
585
+
586
+ // src/shortcuts.ts
587
+ function extractEntry(entry) {
588
+ const keys = Object.keys(entry);
589
+ if (keys.length !== 1) {
590
+ throw new Error(
591
+ `[gjendje] Shortcut expects exactly one key, got ${keys.length}: ${keys.join(", ")}`
592
+ );
593
+ }
594
+ const key = keys[0];
595
+ return [key, entry[key]];
596
+ }
597
+ function state(keyOrEntry, optionsOrDefault, extraOptions) {
598
+ let key;
599
+ let options;
600
+ if (typeof keyOrEntry === "object") {
601
+ const [entryKey, defaultValue] = extractEntry(keyOrEntry);
602
+ key = entryKey;
603
+ options = { ...optionsOrDefault, default: defaultValue };
604
+ } else {
605
+ key = keyOrEntry;
606
+ options = extraOptions ? { ...extraOptions, default: optionsOrDefault } : optionsOrDefault !== null && typeof optionsOrDefault === "object" && "default" in optionsOrDefault ? optionsOrDefault : { default: optionsOrDefault };
607
+ }
608
+ return createState(key, options);
609
+ }
610
+ function local(entry, options) {
611
+ const [key, defaultValue] = extractEntry(entry);
612
+ return createState(key, { ...options, default: defaultValue, scope: "local" });
613
+ }
614
+ function session(entry, options) {
615
+ const [key, defaultValue] = extractEntry(entry);
616
+ return createState(key, { ...options, default: defaultValue, scope: "tab" });
617
+ }
618
+ function url(entry, options) {
619
+ const [key, defaultValue] = extractEntry(entry);
620
+ return createState(key, { ...options, default: defaultValue, scope: "url" });
621
+ }
622
+ function server(entry, options) {
623
+ const [key, defaultValue] = extractEntry(entry);
624
+ return createState(key, { ...options, default: defaultValue, scope: "server" });
625
+ }
626
+ function bucket(entry, options) {
627
+ const [key, defaultValue] = extractEntry(entry);
628
+ return createState(key, { ...options, default: defaultValue, scope: "bucket" });
629
+ }
630
+
586
631
  Object.defineProperty(exports, "batch", {
587
632
  enumerable: true,
588
- get: function () { return chunkSCIDKAI5_cjs.batch; }
633
+ get: function () { return chunkIC7SQVDX_cjs.batch; }
589
634
  });
590
635
  Object.defineProperty(exports, "configure", {
591
636
  enumerable: true,
592
- get: function () { return chunkSCIDKAI5_cjs.configure; }
637
+ get: function () { return chunkIC7SQVDX_cjs.configure; }
593
638
  });
594
639
  Object.defineProperty(exports, "shallowEqual", {
595
640
  enumerable: true,
596
- get: function () { return chunkSCIDKAI5_cjs.shallowEqual; }
641
+ get: function () { return chunkIC7SQVDX_cjs.shallowEqual; }
597
642
  });
643
+ exports.bucket = bucket;
598
644
  exports.collection = collection;
599
645
  exports.computed = computed;
600
646
  exports.effect = effect;
647
+ exports.local = local;
601
648
  exports.previous = previous;
602
649
  exports.readonly = readonly;
603
650
  exports.select = select;
651
+ exports.server = server;
652
+ exports.session = session;
604
653
  exports.snapshot = snapshot;
605
654
  exports.state = state;
655
+ exports.url = url;
606
656
  exports.withHistory = withHistory;
607
657
  exports.withWatch = withWatch;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BaseInstance, U as Unsubscribe, S as StateOptions, R as ReadonlyInstance, a as Scope, b as StateInstance } from './types-BQb8Z14O.cjs';
2
- export { A as Adapter, c as BucketOptions, E as Enhancer, L as Listener, d as Serializer } from './types-BQb8Z14O.cjs';
1
+ import { B as BaseInstance, U as Unsubscribe, S as StateOptions, R as ReadonlyInstance, a as Scope, b as StateInstance } from './types-CmmPImmo.cjs';
2
+ export { A as Adapter, c as BucketOptions, E as Enhancer, L as Listener, d as Serializer } from './types-CmmPImmo.cjs';
3
3
 
4
4
  /**
5
5
  * Runs all state updates inside fn as a single batch.
@@ -286,26 +286,6 @@ interface WithWatch<T> {
286
286
  */
287
287
  declare function withWatch<TIn extends BaseInstance<any>>(instance: TIn): TIn & WithWatch<TIn extends BaseInstance<infer T> ? T : unknown>;
288
288
 
289
- type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
290
- /**
291
- * Create a stateful value.
292
- *
293
- * Same key + same scope always returns the same instance.
294
- * Change scope to move state anywhere.
295
- *
296
- * ```ts
297
- * const theme = state('theme', { default: 'light', scope: 'local' })
298
- * const filters = state('filters', { default: {}, scope: 'url' })
299
- * const user = state('user', { default: null, scope: 'server' })
300
- *
301
- * // Shorthand — pass a default value directly
302
- * const counter = state('counter', 0)
303
- * const name = state('name', 'guest')
304
- * ```
305
- */
306
- declare function state<T>(key: string, options: StateOptions<T>): StateInstance<T>;
307
- declare function state<T extends string | number | boolean | null | undefined>(key: string, defaultValue: T): StateInstance<Widen<T>>;
308
-
309
289
  /**
310
290
  * A read-only reactive value that tracks the previous value of a source.
311
291
  * Lighter than `withHistory` — stores only the single prior value,
@@ -384,10 +364,77 @@ interface SelectOptions {
384
364
  */
385
365
  declare function select<TSource, TResult>(source: ReadonlyInstance<TSource>, fn: (value: TSource) => TResult, options?: SelectOptions): SelectInstance<TResult>;
386
366
 
367
+ type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
368
+ type ShortcutOptions<T> = Omit<StateOptions<T>, 'default' | 'scope'>;
369
+ type BucketShortcutOptions<T> = Omit<StateOptions<T>, 'default' | 'scope' | 'bucket'> & Pick<StateOptions<T>, 'bucket'>;
370
+ /**
371
+ * Create a stateful value.
372
+ *
373
+ * Preferred — entry object form (key derived from property name):
374
+ *
375
+ * ```ts
376
+ * const counter = state({ counter: 0 })
377
+ * const theme = state({ theme: 'light' }, { scope: 'local' })
378
+ * ```
379
+ *
380
+ * Alternative — string key forms:
381
+ *
382
+ * ```ts
383
+ * const theme = state('theme', { default: 'light', scope: 'local' })
384
+ * const synced = state('theme', 'light', { scope: 'local', sync: true })
385
+ * const counter = state('counter', 0)
386
+ * ```
387
+ */
388
+ declare function state<T>(entry: Record<string, T>, options?: Omit<StateOptions<T>, 'default'>): StateInstance<T>;
389
+ declare function state<T>(key: string, options: StateOptions<T>): StateInstance<T>;
390
+ declare function state<T>(key: string, defaultValue: T, options: Omit<StateOptions<T>, 'default'>): StateInstance<T>;
391
+ declare function state<T extends string | number | boolean | null | undefined>(key: string, defaultValue: T): StateInstance<Widen<T>>;
392
+ /**
393
+ * Create state stored in `localStorage`.
394
+ *
395
+ * ```ts
396
+ * const theme = local({ theme: 'light' })
397
+ * const synced = local({ theme: 'dark' }, { sync: true })
398
+ * ```
399
+ */
400
+ declare function local<T>(entry: Record<string, T>, options?: ShortcutOptions<T>): StateInstance<T>;
401
+ /**
402
+ * Create state stored in `sessionStorage`.
403
+ *
404
+ * ```ts
405
+ * const draft = session({ draft: '' })
406
+ * ```
407
+ */
408
+ declare function session<T>(entry: Record<string, T>, options?: ShortcutOptions<T>): StateInstance<T>;
409
+ /**
410
+ * Create state stored in `URLSearchParams`.
411
+ *
412
+ * ```ts
413
+ * const filters = url({ filters: { q: '' } })
414
+ * ```
415
+ */
416
+ declare function url<T>(entry: Record<string, T>, options?: ShortcutOptions<T>): StateInstance<T>;
417
+ /**
418
+ * Create state stored in server-side `AsyncLocalStorage`.
419
+ *
420
+ * ```ts
421
+ * const user = server({ user: null })
422
+ * ```
423
+ */
424
+ declare function server<T>(entry: Record<string, T>, options?: ShortcutOptions<T>): StateInstance<T>;
425
+ /**
426
+ * Create state stored in a Storage Bucket.
427
+ *
428
+ * ```ts
429
+ * const data = bucket({ cache: [] }, { bucket: { name: 'my-bucket' } })
430
+ * ```
431
+ */
432
+ declare function bucket<T>(entry: Record<string, T>, options: BucketShortcutOptions<T>): StateInstance<T>;
433
+
387
434
  /**
388
435
  * Shallow equality check for primitives, arrays, and plain objects.
389
436
  * Returns true if the two values are structurally equal at one level deep.
390
437
  */
391
438
  declare function shallowEqual(a: unknown, b: unknown): boolean;
392
439
 
393
- export { BaseInstance, type CollectionInstance, type ComputedInstance, type ComputedOptions, type DestroyContext, type EffectHandle, type ErrorContext, type GjendjeConfig, type HistoryOptions, type HydrateContext, type LogLevel, type MigrateContext, type PreviousInstance, type PreviousOptions, type QuotaExceededContext, ReadonlyInstance, type RegisterContext, Scope, type SelectInstance, type SelectOptions, StateInstance, StateOptions, type StateSnapshot, type SyncContext, Unsubscribe, type WithHistoryInstance, type WithWatch, batch, collection, computed, configure, effect, previous, readonly, select, shallowEqual, snapshot, state, withHistory, withWatch };
440
+ export { BaseInstance, type CollectionInstance, type ComputedInstance, type ComputedOptions, type DestroyContext, type EffectHandle, type ErrorContext, type GjendjeConfig, type HistoryOptions, type HydrateContext, type LogLevel, type MigrateContext, type PreviousInstance, type PreviousOptions, type QuotaExceededContext, ReadonlyInstance, type RegisterContext, Scope, type SelectInstance, type SelectOptions, StateInstance, StateOptions, type StateSnapshot, type SyncContext, Unsubscribe, type WithHistoryInstance, type WithWatch, batch, bucket, collection, computed, configure, effect, local, previous, readonly, select, server, session, shallowEqual, snapshot, state, url, withHistory, withWatch };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BaseInstance, U as Unsubscribe, S as StateOptions, R as ReadonlyInstance, a as Scope, b as StateInstance } from './types-BQb8Z14O.js';
2
- export { A as Adapter, c as BucketOptions, E as Enhancer, L as Listener, d as Serializer } from './types-BQb8Z14O.js';
1
+ import { B as BaseInstance, U as Unsubscribe, S as StateOptions, R as ReadonlyInstance, a as Scope, b as StateInstance } from './types-CmmPImmo.js';
2
+ export { A as Adapter, c as BucketOptions, E as Enhancer, L as Listener, d as Serializer } from './types-CmmPImmo.js';
3
3
 
4
4
  /**
5
5
  * Runs all state updates inside fn as a single batch.
@@ -286,26 +286,6 @@ interface WithWatch<T> {
286
286
  */
287
287
  declare function withWatch<TIn extends BaseInstance<any>>(instance: TIn): TIn & WithWatch<TIn extends BaseInstance<infer T> ? T : unknown>;
288
288
 
289
- type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
290
- /**
291
- * Create a stateful value.
292
- *
293
- * Same key + same scope always returns the same instance.
294
- * Change scope to move state anywhere.
295
- *
296
- * ```ts
297
- * const theme = state('theme', { default: 'light', scope: 'local' })
298
- * const filters = state('filters', { default: {}, scope: 'url' })
299
- * const user = state('user', { default: null, scope: 'server' })
300
- *
301
- * // Shorthand — pass a default value directly
302
- * const counter = state('counter', 0)
303
- * const name = state('name', 'guest')
304
- * ```
305
- */
306
- declare function state<T>(key: string, options: StateOptions<T>): StateInstance<T>;
307
- declare function state<T extends string | number | boolean | null | undefined>(key: string, defaultValue: T): StateInstance<Widen<T>>;
308
-
309
289
  /**
310
290
  * A read-only reactive value that tracks the previous value of a source.
311
291
  * Lighter than `withHistory` — stores only the single prior value,
@@ -384,10 +364,77 @@ interface SelectOptions {
384
364
  */
385
365
  declare function select<TSource, TResult>(source: ReadonlyInstance<TSource>, fn: (value: TSource) => TResult, options?: SelectOptions): SelectInstance<TResult>;
386
366
 
367
+ type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
368
+ type ShortcutOptions<T> = Omit<StateOptions<T>, 'default' | 'scope'>;
369
+ type BucketShortcutOptions<T> = Omit<StateOptions<T>, 'default' | 'scope' | 'bucket'> & Pick<StateOptions<T>, 'bucket'>;
370
+ /**
371
+ * Create a stateful value.
372
+ *
373
+ * Preferred — entry object form (key derived from property name):
374
+ *
375
+ * ```ts
376
+ * const counter = state({ counter: 0 })
377
+ * const theme = state({ theme: 'light' }, { scope: 'local' })
378
+ * ```
379
+ *
380
+ * Alternative — string key forms:
381
+ *
382
+ * ```ts
383
+ * const theme = state('theme', { default: 'light', scope: 'local' })
384
+ * const synced = state('theme', 'light', { scope: 'local', sync: true })
385
+ * const counter = state('counter', 0)
386
+ * ```
387
+ */
388
+ declare function state<T>(entry: Record<string, T>, options?: Omit<StateOptions<T>, 'default'>): StateInstance<T>;
389
+ declare function state<T>(key: string, options: StateOptions<T>): StateInstance<T>;
390
+ declare function state<T>(key: string, defaultValue: T, options: Omit<StateOptions<T>, 'default'>): StateInstance<T>;
391
+ declare function state<T extends string | number | boolean | null | undefined>(key: string, defaultValue: T): StateInstance<Widen<T>>;
392
+ /**
393
+ * Create state stored in `localStorage`.
394
+ *
395
+ * ```ts
396
+ * const theme = local({ theme: 'light' })
397
+ * const synced = local({ theme: 'dark' }, { sync: true })
398
+ * ```
399
+ */
400
+ declare function local<T>(entry: Record<string, T>, options?: ShortcutOptions<T>): StateInstance<T>;
401
+ /**
402
+ * Create state stored in `sessionStorage`.
403
+ *
404
+ * ```ts
405
+ * const draft = session({ draft: '' })
406
+ * ```
407
+ */
408
+ declare function session<T>(entry: Record<string, T>, options?: ShortcutOptions<T>): StateInstance<T>;
409
+ /**
410
+ * Create state stored in `URLSearchParams`.
411
+ *
412
+ * ```ts
413
+ * const filters = url({ filters: { q: '' } })
414
+ * ```
415
+ */
416
+ declare function url<T>(entry: Record<string, T>, options?: ShortcutOptions<T>): StateInstance<T>;
417
+ /**
418
+ * Create state stored in server-side `AsyncLocalStorage`.
419
+ *
420
+ * ```ts
421
+ * const user = server({ user: null })
422
+ * ```
423
+ */
424
+ declare function server<T>(entry: Record<string, T>, options?: ShortcutOptions<T>): StateInstance<T>;
425
+ /**
426
+ * Create state stored in a Storage Bucket.
427
+ *
428
+ * ```ts
429
+ * const data = bucket({ cache: [] }, { bucket: { name: 'my-bucket' } })
430
+ * ```
431
+ */
432
+ declare function bucket<T>(entry: Record<string, T>, options: BucketShortcutOptions<T>): StateInstance<T>;
433
+
387
434
  /**
388
435
  * Shallow equality check for primitives, arrays, and plain objects.
389
436
  * Returns true if the two values are structurally equal at one level deep.
390
437
  */
391
438
  declare function shallowEqual(a: unknown, b: unknown): boolean;
392
439
 
393
- export { BaseInstance, type CollectionInstance, type ComputedInstance, type ComputedOptions, type DestroyContext, type EffectHandle, type ErrorContext, type GjendjeConfig, type HistoryOptions, type HydrateContext, type LogLevel, type MigrateContext, type PreviousInstance, type PreviousOptions, type QuotaExceededContext, ReadonlyInstance, type RegisterContext, Scope, type SelectInstance, type SelectOptions, StateInstance, StateOptions, type StateSnapshot, type SyncContext, Unsubscribe, type WithHistoryInstance, type WithWatch, batch, collection, computed, configure, effect, previous, readonly, select, shallowEqual, snapshot, state, withHistory, withWatch };
440
+ export { BaseInstance, type CollectionInstance, type ComputedInstance, type ComputedOptions, type DestroyContext, type EffectHandle, type ErrorContext, type GjendjeConfig, type HistoryOptions, type HydrateContext, type LogLevel, type MigrateContext, type PreviousInstance, type PreviousOptions, type QuotaExceededContext, ReadonlyInstance, type RegisterContext, Scope, type SelectInstance, type SelectOptions, StateInstance, StateOptions, type StateSnapshot, type SyncContext, Unsubscribe, type WithHistoryInstance, type WithWatch, batch, bucket, collection, computed, configure, effect, local, previous, readonly, select, server, session, shallowEqual, snapshot, state, url, withHistory, withWatch };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createBase, createListeners, getRegistry, getConfig, scopedKey, getRegistered, log, createRenderState, registerByKey, notify } from './chunk-Z32544QA.js';
2
- export { batch, configure, shallowEqual } from './chunk-Z32544QA.js';
1
+ import { createBase, createListeners, getRegistry, notify, getConfig, scopedKey, getRegistered, log, createRenderState, registerByKey } from './chunk-5J2W34MR.js';
2
+ export { batch, configure, shallowEqual } from './chunk-5J2W34MR.js';
3
3
 
4
4
  // src/collection.ts
5
5
  function collection(key, options) {
@@ -403,41 +403,6 @@ function withWatch(instance) {
403
403
  return result;
404
404
  }
405
405
 
406
- // src/factory.ts
407
- function state(key, optionsOrDefault) {
408
- const options = optionsOrDefault !== null && typeof optionsOrDefault === "object" && "default" in optionsOrDefault ? optionsOrDefault : { default: optionsOrDefault };
409
- if (!key) {
410
- throw new Error("[state] key must be a non-empty string.");
411
- }
412
- const config = getConfig();
413
- if (config.keyPattern && !config.keyPattern.test(key)) {
414
- throw new Error(
415
- `[gjendje] Key "${key}" does not match the configured keyPattern ${config.keyPattern}.`
416
- );
417
- }
418
- const scope = options.scope ?? config.scope ?? "render";
419
- const rKey = scopedKey(key, scope);
420
- const existing = getRegistered(rKey);
421
- if (existing && !existing.isDestroyed) {
422
- if (config.warnOnDuplicate) {
423
- log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
424
- }
425
- return existing;
426
- }
427
- if (scope === "render" && !options.ssr && !config.ssr) {
428
- if (options.sync) {
429
- log(
430
- "warn",
431
- `sync: true is ignored for scope "render". Only "local" and "bucket" scopes support cross-tab sync.`
432
- );
433
- }
434
- const instance = createRenderState(key, rKey, options, config);
435
- registerByKey(rKey, key, scope, instance, config);
436
- return instance;
437
- }
438
- return createBase(key, options);
439
- }
440
-
441
406
  // src/previous.ts
442
407
  var previousCounter = 0;
443
408
  function previous(source, options) {
@@ -582,4 +547,84 @@ function select(source, fn, options) {
582
547
  };
583
548
  }
584
549
 
585
- export { collection, computed, effect, previous, readonly, select, snapshot, state, withHistory, withWatch };
550
+ // src/factory.ts
551
+ function createState(key, options) {
552
+ if (!key) {
553
+ throw new Error("[state] key must be a non-empty string.");
554
+ }
555
+ const config = getConfig();
556
+ if (config.keyPattern && !config.keyPattern.test(key)) {
557
+ throw new Error(
558
+ `[gjendje] Key "${key}" does not match the configured keyPattern ${config.keyPattern}.`
559
+ );
560
+ }
561
+ const rawScope = options.scope ?? config.scope ?? "render";
562
+ const scope = rawScope === "memory" ? "render" : rawScope;
563
+ const rKey = scopedKey(key, scope);
564
+ const existing = getRegistered(rKey);
565
+ if (existing && !existing.isDestroyed) {
566
+ if (config.warnOnDuplicate) {
567
+ log("warn", `Duplicate state("${key}") with scope "${scope}". Returning cached instance.`);
568
+ }
569
+ return existing;
570
+ }
571
+ if (scope === "render" && !options.ssr && !config.ssr) {
572
+ if (options.sync) {
573
+ log(
574
+ "warn",
575
+ `sync: true is ignored for scope "render". Only "local" and "bucket" scopes support cross-tab sync.`
576
+ );
577
+ }
578
+ const instance = createRenderState(key, rKey, options, config);
579
+ registerByKey(rKey, key, scope, instance, config);
580
+ return instance;
581
+ }
582
+ return createBase(key, options);
583
+ }
584
+
585
+ // src/shortcuts.ts
586
+ function extractEntry(entry) {
587
+ const keys = Object.keys(entry);
588
+ if (keys.length !== 1) {
589
+ throw new Error(
590
+ `[gjendje] Shortcut expects exactly one key, got ${keys.length}: ${keys.join(", ")}`
591
+ );
592
+ }
593
+ const key = keys[0];
594
+ return [key, entry[key]];
595
+ }
596
+ function state(keyOrEntry, optionsOrDefault, extraOptions) {
597
+ let key;
598
+ let options;
599
+ if (typeof keyOrEntry === "object") {
600
+ const [entryKey, defaultValue] = extractEntry(keyOrEntry);
601
+ key = entryKey;
602
+ options = { ...optionsOrDefault, default: defaultValue };
603
+ } else {
604
+ key = keyOrEntry;
605
+ options = extraOptions ? { ...extraOptions, default: optionsOrDefault } : optionsOrDefault !== null && typeof optionsOrDefault === "object" && "default" in optionsOrDefault ? optionsOrDefault : { default: optionsOrDefault };
606
+ }
607
+ return createState(key, options);
608
+ }
609
+ function local(entry, options) {
610
+ const [key, defaultValue] = extractEntry(entry);
611
+ return createState(key, { ...options, default: defaultValue, scope: "local" });
612
+ }
613
+ function session(entry, options) {
614
+ const [key, defaultValue] = extractEntry(entry);
615
+ return createState(key, { ...options, default: defaultValue, scope: "tab" });
616
+ }
617
+ function url(entry, options) {
618
+ const [key, defaultValue] = extractEntry(entry);
619
+ return createState(key, { ...options, default: defaultValue, scope: "url" });
620
+ }
621
+ function server(entry, options) {
622
+ const [key, defaultValue] = extractEntry(entry);
623
+ return createState(key, { ...options, default: defaultValue, scope: "server" });
624
+ }
625
+ function bucket(entry, options) {
626
+ const [key, defaultValue] = extractEntry(entry);
627
+ return createState(key, { ...options, default: defaultValue, scope: "bucket" });
628
+ }
629
+
630
+ export { bucket, collection, computed, effect, local, previous, readonly, select, server, session, snapshot, state, url, withHistory, withWatch };
package/dist/server.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkSCIDKAI5_cjs = require('./chunk-SCIDKAI5.cjs');
3
+ var chunkIC7SQVDX_cjs = require('./chunk-IC7SQVDX.cjs');
4
4
  var async_hooks = require('async_hooks');
5
5
 
6
6
  var als = new async_hooks.AsyncLocalStorage();
@@ -9,7 +9,7 @@ async function withServerSession(fn) {
9
9
  return als.run(store, fn);
10
10
  }
11
11
  function createServerAdapter(key, defaultValue) {
12
- const listeners = chunkSCIDKAI5_cjs.createListeners();
12
+ const listeners = chunkIC7SQVDX_cjs.createListeners();
13
13
  function getStore() {
14
14
  return als.getStore();
15
15
  }
@@ -31,7 +31,7 @@ function createServerAdapter(key, defaultValue) {
31
31
  }
32
32
  store.set(key, value);
33
33
  lastNotifiedValue = value;
34
- chunkSCIDKAI5_cjs.notify(notifyListeners);
34
+ chunkIC7SQVDX_cjs.notify(notifyListeners);
35
35
  },
36
36
  subscribe: listeners.subscribe,
37
37
  destroy() {
@@ -39,7 +39,7 @@ function createServerAdapter(key, defaultValue) {
39
39
  }
40
40
  };
41
41
  }
42
- chunkSCIDKAI5_cjs.registerServerAdapter(createServerAdapter);
42
+ chunkIC7SQVDX_cjs.registerServerAdapter(createServerAdapter);
43
43
 
44
44
  exports.createServerAdapter = createServerAdapter;
45
45
  exports.withServerSession = withServerSession;
package/dist/server.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as Adapter } from './types-BQb8Z14O.cjs';
1
+ import { A as Adapter } from './types-CmmPImmo.cjs';
2
2
 
3
3
  declare function withServerSession<T>(fn: () => T): Promise<T>;
4
4
  declare function createServerAdapter<T>(key: string, defaultValue: T): Adapter<T>;
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as Adapter } from './types-BQb8Z14O.js';
1
+ import { A as Adapter } from './types-CmmPImmo.js';
2
2
 
3
3
  declare function withServerSession<T>(fn: () => T): Promise<T>;
4
4
  declare function createServerAdapter<T>(key: string, defaultValue: T): Adapter<T>;
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { registerServerAdapter, createListeners, notify } from './chunk-Z32544QA.js';
1
+ import { registerServerAdapter, createListeners, notify } from './chunk-5J2W34MR.js';
2
2
  import { AsyncLocalStorage } from 'async_hooks';
3
3
 
4
4
  var als = new AsyncLocalStorage();
@@ -1,4 +1,4 @@
1
- type Scope = 'render' | 'tab' | 'local' | 'url' | 'server' | 'bucket';
1
+ type Scope = 'memory' | 'render' | 'tab' | 'local' | 'url' | 'server' | 'bucket';
2
2
  interface Adapter<T> {
3
3
  get(): T;
4
4
  set(value: T): void;
@@ -1,4 +1,4 @@
1
- type Scope = 'render' | 'tab' | 'local' | 'url' | 'server' | 'bucket';
1
+ type Scope = 'memory' | 'render' | 'tab' | 'local' | 'url' | 'server' | 'bucket';
2
2
  interface Adapter<T> {
3
3
  get(): T;
4
4
  set(value: T): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gjendje",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "TypeScript state management",
5
5
  "keywords": [
6
6
  "state",